第3章 类与对象的基本知识
1.例3-1的思考题:
数据成员都有自己的访问属性,例3-1中数据成员访问属性均为public,如果将其中的部分属性改为private,试试看,在main()中还能否用“.”操作符直接访问。
【分析与解答】不能,因为私有属性是不对外公开的,用于信息隐藏。
2.例3-4的思考题:
在例3-4的main()函数中添加如下一条语句。
与例3-1的输出结果比较,你能得出什么结论?
【分析与解答】对应的输出结果如下。
输出对象date1占用的内存空间和例3-1是一样的,说明给类添加成员函数不会影响对象的大小,成员函数存储在内存中,但只有一份,与创建对象的数量无关。当用sizeof运算符计算对象占用的字节时,仅计算数据成员,不包括成员函数代码所占空间。
3.例3-5的思考题:
①如果将li03_05_main.cpp中的“#include"li03_02.h"”改为“#include <li03_02.h>”,重新编译会怎样?为什么?
②如果将例3-4中的函数调用语句“date2.Display();”改为如下形式。
重新编译会怎样?为什么?
③如果需要显示单独的数据成员,应该怎样编写输出语句?
【分析与解答】①重新编译,程序会出现如下错误提示。
因为在文件包含指令中,如果用一对尖括号,则直接到系统文件夹下查找该文件,而不是到当前文件夹下查找,所以找不到此文件。该文件为用户自己定义的头文件,一般放在用户文件夹中,不属于名字空间的范围。
②重新编译这个程序时,编译系统会指示最后一条语句错误。
原因很简单,因为在类外不能通过对象直接访问类的私有成员。
③因为是显示单独的数据成员,该例中数据成员均为私有成员,不能直接引用,因此应该通过公有成员函数访问私有成员,语句如下。
4.例3-6的思考题:
①将例3-6中的函数SetDate()的3条语句均改写成带this指针的语句。
②将例3-6中的函数Display()中的最后一条语句改写如下。
重新编译运行程序,结果是否有变化?
【分析与解答】①因为类的成员函数都自带this指针,在大部分程序中一般不显式地写出this指针,但实际上与显式写出this指针效果是一样的,因此,改写后带this指针的等效函数代码如下。
②Display()中的最后一条语句改写之后重新编译运行程序,结果没有变化。因为this指针在函数中一般缺省,所以改写后结果不会有变化。
5.例3-7的思考题:
将例3-7main()中的第一条语句:“CDate today(2019,3,9);”改为“CDate today;”,重新编译肯定有错误信息,如何以最简单的方式修改程序,保证main()的函数第一条语句为“CDate today;”时,程序运行结果与例3-7的运行结果相同?
【分析与解答】直接改为“CDate today;”后编译器会指出这条语句非法,并给出错误原因“no appropriate default constructor available”,即找不到合适的构造函数。而这一点是初学者很容易疏漏的地方,在本章的后一节会重点讲述,在此提醒读者注意。在类的定义中,只定义了一个带3个形式参数(简称形参)的构造函数,要求对象提供对应的3个实参。根据第2章的知识,函数是可以带默认参数值的,构造函数也一样。因此,最简单的解决方案就是在类内声明构造函数原型的时候提供3个默认参数值,即将语句“CDate(int, int, int);”改为“CDate(int=2019, int=3, int=9);”,则程序的运行结果就与例3-7完全相同。
6.3.4.2节的思考题:
假设类定义中已有如下两个构造函数。
下面的语句合法吗?如果有问题,原因是什么?
【分析与解答】
前两个对象的定义是合法的,因为分别可以自动调用有3个形参和无参的构造函数;对象day3的定义非法,因为程序中没有提供只需要1个实参的构造函数,已定义的带参构造函数不具有默认值,因此该对象找不到可以自动调用的重载构造函数;最后一条有问题的语句严格说并不是非法的,只是该语句实际的意思并不是编程者原本希望的那样。如果认为它是对名为day4的一个对象的定义,就是错误的。它实际上可以理解为一个名为day4的函数的声明,该函数不带参数,返回CDate型的一个值。如果要声明一个名为day4的对象且不提供实参,则应采用与day2一样的方式定义,即“CDate day4;”。
7.例3-8的思考题:
在li03_08_main.cpp中增加一条定义语句“CDate otherday(2011,4);”,同时在“return 0;”之前增加一条语句“otherday.Display();”,重新编译程序,结果是否正确?新增的最后一行输出结果是什么?
【分析与解答】本程序中的构造函数提供了3个默认参数值,定义对象时需要提供的实参的个数可以是0~3个,因此所增加的对象otherday仅提供前两个实参是正确的。由于增加了一条调用Display的语句,最后一行新增加的输出为:2011-4-1。
8.3.4.4节的思考题:
若定义一个柜子类型CCupboard如下。
柜子可能是正方体,也可能是长方体,要实现用语句“CCupboard Cfood(50);”来定义一个棱长为50的正方体柜子,构造函数应怎样设计?
【分析与解答】要实现用语句“CCupboard Cfood(50);”来定义一个棱长为50的正方体柜子,需要重载含有一个形参的构造函数。
这样的重载构造函数可以用一个形参x的值分别初始化长、宽、高。由于构造函数只有单一形参,所以必要时编译器可将它用于类型的隐式转换。示例代码如下。
第一条语句调用默认构造函数或有默认参数值的构造函数创建Cfood。第二条语句将用实参30.5调用构造函数“CCupboard (double x )”,进行从类型double到CCupboard的隐式转换,相当于“Cfood=Cfood(30.5)”。
9.3.4.5节的思考题:
假设类Point有3个重载的构造函数,其原型分别如下。
有如下语句,请分析各自调用哪种构造函数。
【分析与解答】定义新对象时均要调用构造函数,而匹配重载函数的基本原则就是实参应与形参对应,根据实参是否是类的对象可以判断调用的是复制构造函数还是普通构造函数,因此以上定义的5个对象:(a)调用带参的构造函数;(b)调用无参构造函数;(c)调用复制构造函数;(d)调用复制构造函数,此定义等效于“Point p4(p1)”;(e)调用构造函数,p2已经定义,由Point(4,5)构造一个无名对象,并将对象的成员值赋值给p2,通过默认的赋值符号重载函数完成(见第7章多态性),此处不是调用复制构造函数。
10.例3-11的思考题:
本例中如果内存分配失败,会抛出异常并终止程序,若希望管理此类故障,让程序顺利运行,应如何优化代码?
【分析与解答】在使用new操作符为变量分配内存时,若分配失败会抛出异常bad_alloc,可以在构造函数中添加异常处理。
what()函数是异常类的成员函数,描述抛出异常的原因。
11.例3-16的思考题:
将li3_16_main.cpp中的“CDate &pDate=DateA;”改为两条语句“CDate &pDate; pDate=DateA;”,重新编译运行程序,观察错误信息并解释原因。
【分析与解答】无论是对象引用或是普通变量引用,都要求在定义之初作初始化,并且一旦经过初始化,在程序中的别名关系将不会被更改。以上的修改,是先定义一个引用pDate,再对其进行赋值,试图通过赋值的方式使其获得指代的对象,但这是不对的,赋值与初始化有着本质的区别。因此上机编译时共出现4个报错信息。
12.例3-20的思考题:
①将主函数中作为左值的函数调用语句由“Fun(DateA)=CDate(2019,10,1);”改为“Fun(tDate)=CDate(2019,10,1);”,重新运行程序,请写出输出结果的最后6行,并分析与例3-20结果最后6行的区别及原因。
②将本例中的函数修改如下。
重新编译会产生告警信息,为什么?
【分析与解答】①语句的修改其实就体现在函数调用时实参由DateA改为了tDate,这时pDate成为实参tDate的别名,因此函数内修改的是tDate,返回的其实也是tDate,最后通过赋值tDate获得了无名对象的值“2019-10-1”,而这一调用过程与DateA无关,因此它还保持前面得到的“2019-3-13”这个值。函数运行最后6行的结果如下。
其中有两行的结果有变化。
②程序重新编译时系统会给出如下告警提示。
因为本例中将局部自动对象作为引用返回值。return后面返回的必定是在该函数执行结束之后仍然存在内存空间的对象或变量,因此引用对象参数可以返回,但值形参或函数中定义的局部自动对象均不可以,因为局部自动对象在该函数结束后其内存空间释放,无法再作为主调函数中的左值变量使用。