C++程序设计课后题
P36
10.
1 |
|
11.
1 |
|
13.
1 |
|
14.
1 |
|
P70
6.
1 |
|
P122
1.
构造函数和析构函数在面向对象编程中扮演着至关重要的角色。它们主要用于对象的创建和销毁过程,确保对象在使用前后能够保持有效的状态。下面将详细介绍这两个特殊成员函数的作用及自定义它们的情况。
一、构造函数的作用
- 初始化对象:构造函数用于初始化对象的成员变量,确保每个成员变量都有一个合适的初始值。
- 资源分配:如果对象需要占用额外的资源(如动态内存、文件描述符等),构造函数是申请这些资源的合适地点。
- 满足特定条件的对象创建:在某些情况下,只有当特定条件满足时才能创建对象,构造函数可以包含逻辑来检查这些条件。
- 依赖性注入:构造函数可以接收参数,用于注入依赖,如服务、配置或其他对象,实现解耦和可测试性。
二、析构函数的作用
- 资源释放:析构函数负责释放对象在生命周期中申请的资源,如动态内存、文件描述符等,以防止资源泄露。
- 状态更新:对象销毁前可能需要更新某些状态或通知其他部分的代码,析构函数是执行这些操作的理想地点。
- 持久化数据:如果对象在生命周期中有状态改变且需要保存到磁盘,析构函数可以是实施这一操作的好地方。
- 对象清理:在对象被销毁之前,析构函数确保与之相关的所有操作都已经妥善完成或回滚,保持系统的稳定性。
三、什么时候需要自定义构造函数和析构函数
- 当类的成员变量需要在对象创建时进行特定的初始化:如果仅仅依靠编译器生成的默认构造函数无法正确初始化对象,就需要自定义构造函数。
- 当类需要管理资源:如果类的对象会分配动态内存、文件、网络连接或其他任何形式的资源,就需要自定义构造函数来分配(和析构函数来释放)这些资源。
- 当需要限制对象创建的方式:如果类的实例化需要满足特定条件,或者想要控制如何以及何时创建对象,自定义构造函数是实现这一点的方式。
- 当类的对象需要在某些条件下持续追踪或修改状态:如果对象的状态改变需要在销毁前保存或处理,就需要自定义析构函数来确保这些操作的正确执行。
综上所述,通过自定义构造函数和析构函数,可以精确控制对象的创建和销毁过程,确保资源的合理使用和及时回收,以及满足特定的业务逻辑需求。这不仅有助于提高代码的可维护性和可读性,也是实现某些复杂功能的关键所在。
2.
1 |
|
3.
修改后报错:
文件名 | 行 | 列 | 描述 |
---|---|---|---|
D:/Files/Documents/untitled1.cpp | 6 | 2 | [错误] 'Date::Date(int, int, int)' cannot be overloaded with 'Date::Date(int, int, int)' |
D:/Files/Documents/untitled1.cpp | 5 | 2 | [说明] previous declaration 'Date::Date(int, int, int)' |
D:/Files/Documents/untitled1.cpp | 17 | 1 | [错误] no declaration matches 'Date::Date(int, int)' |
D:/Files/Documents/untitled1.cpp | 3 | 7 | [说明] candidates are: 'constexpr Date::Date(Date&&)' |
D:/Files/Documents/untitled1.cpp | 3 | 7 | [说明] 'constexpr Date::Date(const Date&)' |
D:/Files/Documents/untitled1.cpp | 8 | 2 | [说明] 'Date::Date()' |
D:/Files/Documents/untitled1.cpp | 7 | 2 | [说明] 'Date::Date(int)' |
D:/Files/Documents/untitled1.cpp | 15 | 1 | [说明] 'Date::Date(int, int, int)' |
D:/Files/Documents/untitled1.cpp | 3 | 7 | [说明] 'class Date' defined here |
D:/Files/Documents/untitled1.cpp | 0 | -1 | In function 'int main()': |
D:/Files/Documents/untitled1.cpp | 34 | 16 | [错误] no matching function for call to 'Date::Date(int, int)' |
D:/Files/Documents/untitled1.cpp | 24 | 1 | [说明] candidate: 'Date::Date()' |
D:/Files/Documents/untitled1.cpp | 24 | 1 | [说明] candidate expects 0 arguments, 2 provided |
D:/Files/Documents/untitled1.cpp | 20 | 1 | [说明] candidate: 'Date::Date(int)' |
D:/Files/Documents/untitled1.cpp | 20 | 1 | [说明] candidate expects 1 argument, 2 provided |
D:/Files/Documents/untitled1.cpp | 15 | 1 | [说明] candidate: 'Date::Date(int, int, int)' |
D:/Files/Documents/untitled1.cpp | 15 | 1 | [说明] candidate expects 3 arguments, 2 provided |
D:/Files/Documents/untitled1.cpp | 3 | 7 | [说明] candidate: 'constexpr Date::Date(const Date&)' |
D:/Files/Documents/untitled1.cpp | 3 | 7 | [说明] candidate expects 1 argument, 2 provided |
D:/Files/Documents/untitled1.cpp | 3 | 7 | [说明] candidate: 'constexpr Date::Date(Date&&)' |
D:/Files/Documents/untitled1.cpp | 3 | 7 | [说明] candidate expects 1 argument, 2 provided |
修改后:
1 |
|
4.
1 |
|
5.
1 |
|
9.
1 |
|
P157
7.
1 |
|
P203
5.
有以下程序结构,请分析访问属性。
1 |
|
请问:
(1)在 main 函数中能否用b1.i,b1.j和bl.k 引用派生类B对象bI 中基类A的成员?
(2)派生类B 中的成员函数能否调用基类A中的成员函数f1和f2?
(3)派生类B 中的成员函数能否引用基类A中的数据成员i,j,k?
(4)能否在main 函数中用cl.i,cl.j,cl.k,cl.m,cl.n,cl.p引用基类A 的成员i,j,k,派生类B 的成员 m,n,以及派生类C的成员 P?
(5)能否在main 函数中用cl.f1(),c1.c2(),c1.f3()和c1.f4()调用f1,f2,f3,f4成员函数
(6)派生类C的成员函数f4能否调用基类A中的成员函数f1,f2和派生类中的成员函数f3?
——————
(1)在 main 函数中不能直接用b1.i, b1.j和b1.k引用派生类B对象b1中基类A的成员,因为基类A的公共成员在派生类B中是保护成员。
(2)派生类B 中的成员函数可以调用基类A中的成员函数f1和f2,但需要通过基类A的对象或指针来调用。例如:a1.f1() 或 a1.f2()。
(3)派生类B 中的成员函数可以引用基类A中的数据成员i,j,k,但需要通过基类A的对象或指针来引用。例如:a1.i, a1.j, a1.k。
(4)不能在main 函数中用cl.i, cl.j, cl.k, cl.m, cl.n, cl.p引用基类A 的成员i,j,k,派生类B 的成员 m,n,以及派生类C的成员 P。因为这些成员都是私有的,只能在它们所在的类内部访问。
(5)不能在main 函数中用cl.f1(),c1.f2(),c1.f3()和c1.f4()调用f1,f2, f3,f4成员函数。因为f1和f2是基类A的私有成员函数,只能在基类A的内部访问;f3是派生类B的公共成员函数,可以在派生类B及其派生类的对象上调用;f4是派生类C的公共成员函数,可以在派生类C及其派生类的对象上调用。
(6)派生类C的成员函数f4能够调用基类A中的成员函数f1,但不能直接调用f2和派生类中的成员函数f3
在C++中,访问权限分为公有(public)、保护(protected)和私有(private)。继承关系可以通过这些访问权限来控制基类成员在派生类中的可见性。
下面分析每个类的成员在各自类及其派生类中的访问属性:
基类 A 的成员
void f1()
: 公有成员函数,可以在类A及其所有派生类中直接访问。int i
: 公有数据成员,可以在类A及其所有派生类中直接访问。void f2()
: 保护成员函数,在类A及其派生类B、C、D中可以访问,但在类外不可访问。int j
: 保护数据成员,在类A及其派生类B、C、D中可以访问,但在类外不可访问。int k
: 私有数据成员,只能在类A中访问,在派生类B、C、D中均不可直接访问。
派生类 B 的成员
void f3()
: 公有成员函数,可以在类B及其所有派生类中直接访问。int m
: 保护数据成员,可以在类B及其派生类C、D中访问,但在类A中不能直接访问。int n
: 私有数据成员,只能在类B中访问,在派生类C、D中不可直接访问。
派生类 C 的成员
void f4()
: 公有成员函数,可以在类C及其所有派生类中直接访问。int p
: 私有数据成员,只能在类C中访问,在派生类D中不可直接访问。
派生类 D 的成员
由于D是C的私有派生类,D的成员在C的外部是不可见的。因此,我们无法确定D的成员在D以外的范围的访问属性。
main函数中的访问情况
在main函数中,我们可以创建对象a1、b1和c1,并访问它们的公有成员。以下是各个对象的公有成员的访问情况:
a1.f1()
: 可以调用,因为f1是公有成员函数。a1.i
: 可以访问,因为i是公有数据成员。b1.f1()
: 可以调用,因为f1是继承自A的公有成员函数。b1.f3()
: 可以调用,因为f3是B的公有成员函数。b1.i
: 可以访问,因为i是继承自A的公有数据成员。c1.f1()
: 可以调用,因为f1是继承自A的公有成员函数。c1.f3()
: 可以调用,因为f3是继承自B的公有成员函数。c1.f4()
: 可以调用,因为f4是C的公有成员函数。c1.i
: 可以访问,因为i是继承自A的公有数据成员。
注意:对于保护成员和私有成员,它们不能在类的外部直接访问,但可以在派生类中通过特定的方式访问(如通过公有成员函数间接访问)。
6.
有以下程序结构,请分析所有成员在各类的范围内的访问属性:
1 |
|
在分析所有成员在各类范围内的访问属性之前,我们需要了解C++中的访问控制和继承关系。在C++中,访问权限分为公有(public)、保护(protected)和私有(private)。继承关系可以通过这些访问权限来控制基类成员在派生类中的可见性。
下面分析每个类的成员在各自类及其派生类中的访问属性:
基类 A 的成员
void f1()
: 公有成员函数,可以在类A及其所有派生类中直接访问。void f2()
: 保护成员函数,在类A及其派生类B、C、D中可以访问,但在类外不可访问。int i
: 私有数据成员,只能在类A中访问,在派生类B、C、D中均不可直接访问。
派生类 B 的成员
void f3()
: 公有成员函数,可以在类B及其所有派生类中直接访问。int k
: 公有数据成员,可以在类B及其所有派生类中直接访问。int m
: 私有数据成员,只能在类B中访问,在派生类C、D中不可直接访问。
派生类 C 的成员
void f4()
: 公有成员函数,可以在类C及其派生类D中直接访问,但在类B中不能直接访问(因为C是B的保护派生类)。int n
: 保护数据成员,可以在类C及其派生类D中访问,在类B中不能直接访问。int p
: 私有数据成员,只能在类C中访问,在派生类D中不可直接访问。
派生类 D 的成员
void f5()
: 公有成员函数,只能在类D中访问,因为D是C的私有派生类,f5在类D外部不可直接访问。int q
: 保护数据成员,只能在类D中访问。int r
: 私有数据成员,只能在类D中访问。
总结
- 在
main()
函数中,只有公有成员可以直接被访问。对于类A、B、C、D的对象a1、b1、c1、d1,只有各自的公有成员函数和数据成员可以被直接访问。 - 保护成员和私有成员不能在类的外部直接访问,但可以在派生类中通过特定的方式访问(如通过公有成员函数间接访问)。
- 特别注意,由于D是C的私有派生类,D的成员在C的外部是不可见的,这意味着即使f4在C中是公有的,也不能在D的对象上直接调用f4,除非在D内部提供了相应的接口。
7.
1 |
|
程序执行过程如下:
定义了一个名为A的类,包含三个构造函数和一个display成员函数。构造函数分别接受不同数量的参数,用于初始化类的成员变量a和b。display函数用于输出成员变量a和b的值。
定义了一个名为B的类,继承自A类。B类包含四个构造函数和一个display1成员函数。构造函数分别接受不同数量的参数,用于初始化基类A的成员变量a、b和派生类B的成员变量c。display1函数调用基类的display函数并输出成员变量c的值。
在main函数中,创建了四个B类的对象:b1、b2、b3和b4。分别使用不同的构造函数进行初始化。
调用每个对象的display1函数,输出它们的成员变量a、b和c的值。
具体执行过程如下:
- b1: 使用默认构造函数A(),a和b都被初始化为0,c被初始化为0。输出结果为 "a=0 b=0 c=0"。
- b2: 使用带一个整数参数的构造函数A(int i),a被初始化为1,b被初始化为0,c被初始化为0。输出结果为 "a=1 b=0 c=0"。
- b3: 使用带两个整数参数的构造函数A(int i, int j),a被初始化为1,b被初始化为3,c被初始化为0。输出结果为 "a=1 b=3 c=0"。
- b4: 使用带三个整数参数的构造函数B(int i, int j, int k),a被初始化为1,b被初始化为3,c被初始化为5。输出结果为 "a=1 b=3 c=5"。
8.
1 |
|
程序执行过程如下:
- 进入main函数。
- 创建C类的对象c1。
- 调用C类的构造函数,输出 "constructing C"。
- 在C类的构造函数中,由于C类继承自B类,因此需要先调用B类的构造函数。
- 调用B类的构造函数,输出 "constructing B"。
- 在B类的构造函数中,由于B类继承自A类,因此需要先调用A类的构造函数。
- 调用A类的构造函数,输出 "constructing A"。
- A类的构造函数执行完毕,返回到B类的构造函数。
- B类的构造函数执行完毕,返回到C类的构造函数。
- C类的构造函数执行完毕,对象c1的创建完成。
- main函数结束,开始执行对象的析构。
- 首先调用C类的析构函数,输出 "destructing C"。
- 在C类的析构函数中,由于C类继承自B类,因此需要先调用B类的析构函数。
- 调用B类的析构函数,输出 "destructing B"。
- 在B类的析构函数中,由于B类继承自A类,因此需要先调用A类的析构函数。
- 调用A类的析构函数,输出 "destructing A"。
- A类的析构函数执行完毕,返回到B类的析构函数。
- B类的析构函数执行完毕,返回到C类的析构函数。
- C类的析构函数执行完毕,对象c1的析构完成。
- main函数结束,程序执行完毕。
9.
1 |
|