版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
习题解答:题1.4为何采用二进制:采用不同进制对CPU的运算器设计以及内存设计影响较大,当计算机能处理同样大小的数据时,采用不同进制会影响计算机的制造成本。研究证明:存储同样大小的数采用e进制最为经济。2<e=2.718<3更接近3进制,但3进制的电路没有二进制稳定,故最终采用二进制。二进制每位有两个状态,三进制每位3个状态。不妨认为每个状态需要1个电子元件,则p位q进制的状态总数为qp=M,M就是该存储单元能存储的最大数值。由qp=M可得,所需要的位数。制造p位q进制存储器需要的电子元件总数量。为了使电路成本最经济,求函数f(q)关于q的极小值。故对f(q)求导并令,可得:可得1-log(q)=0,进而可得
习题解答:题1.6十进制数转换为二进制数可使用连续除2求余法:即用得到的商再次除2求余,先后得到的余数为自右至左的二进制数字。因此,(233)10=(11101001)2。可用3位二进制(不够补0)转换1位八进制数,因此,(11101001)2=(351)8。可用4位二进制(不够补0)转换1位十六进制数,因此,(11101001)2=(E9)16。
相反地,二进制转换为十进制数可采用高位连续乘二加低位法。习题解答:题1.13编译程序将C++程序编译为目标文件如.obj文件,然后和标准库中的函数如sin(x)连接。标准库是一种特定结构的库文件,用.obj文件可以创建库文件或者加入库文件。编译程序一般会附带提供库管理程序,用于创建或管理库文件。因此,程序员自己完全可以通过库管理程序创建自己的私有库。
实际上,许多标准库都是由C程序或者C++程序编译产生的。习题解答:题2.2一位二进制即可表示两个状态,故布尔变量的两个值false和true只需一位二进制表示。可用二进制数(0)2表示false,用二进制数(1)2表示true。布尔变量在分配内存时分配一个字节,因为在现代计算机中,内存是以字节为单位进行编址的,也就是说“一位二进制”或“若干位二进制”是没有地址的。注意:对于类class、结构struct或联合union中的数据成员,可将其定义为位段成员。作为位段成员的数据成员可根据需要,分配若干位二进制存储数据成员的值。同理,位段成员也是没有地址的。例如,在X86windows环境32位编译模式下。structA{intx:1; //位段成员:使用1位二进制,无地址inty:13; //位段成员:使用13位二进制,无地址intz:18; //位段成员:使用18位二进制,无地址shortu; //普通成员:使用16位二进制,有地址}a;//sizeof(A)=sizeof(int)+sizeof(short)=6字节short*p=&a.u;//正确:非位段成员有地址int*q=&a.x; //错误:位段成员a.x没有地址
习题解答:题2.6在X86模式下,即在32位编译模式下,内存地址采用32位二进制表示,因此,对于“int*”类型的指针变量,其值是一个内存地址,需要4个字节即32位二进制存储。不同类型的指针变量存储的均是内存地址,因此,任何类型的指针变量在X86模式下,均需4个字节的内存单元。故对于int*类型和void*类型的指针变量,存储它们的值所需的内存单元字节数相同。注意:指针变量所需的字节数与硬件、操作系统和编译模式相关。指针类型看起来复杂,但它是一种简单类型,其值是“不可再分的”,可用printf(“%p”,…)打印。void*p表示指针变量p分配4个字节的存储单元,其存储的值是一块内存的地址,但该内存块的字节数不定。double*q表示指针变量p分配4个字节的存储单元,其存储的值是一块内存的地址,但该内存块的字节数通常为8个字节(double类型的值可占用8、10、12个字节,由硬件、操作系统及编译决定)。
注意,void*类型的p可以指向任意变量或者分配的内存。例如,p=&q;
p=newint[15];习题解答:题2.7编译程序一般默认整型常量如5的类型为int,而不是short或者long等类型。默认浮点型常量如3.2的类型为double,而不是float或者longdouble等类型。默认"abc"的类型为constchar[4],即可理解为常量字符数组{'a','b','c','\0'}。因此,可以用其初始化constchara[4]="abc";或者chara[4]="abc“;
初始化只读字符指针变量时,默认“abc”的类型也可理解为constchar*。因此,可用于如下定义是正确的:constchar*p=“abc”;但是,如下定义char*p=“abc”;是错误的,因为指向只读实体的指针不能赋值给指向可写实体的指针。
注意:"abc"s为string类型的常量,"abc"sv为string_view类型的常量。需要#include<string>以及usingnamespacestd才能使用string等类型。习题解答:题3.8内联inline:即将被调用的内联函数的函数体代码插入到函数调用处,以取代编译为函数调用call,从而避免通过call调用该函数产生的调用开销。call在执行内联函数的函数体前,需要通过压栈传递实参、通过压栈保护重要寄存器、函数体执行完毕需要通过出栈恢复重要寄存器、通过出栈或栈指针操作保持栈平衡,这些因调用产生的额外代价称为调用开销。当内联函数的函数体有效代码小于调用开销时,内联可以使整个程序变短并提高执行速度和效率。内联失败即不在调用处插入函数体代码,这并不表示编译出现错误。内联函数的函数体应比较短,且应该使用顺序执行指令。
注意:
当一个函数被成功内联后,该函数就没有存在的必要了,因为所有的call指令都被其函数体有效代码替换掉了。因此,该函数在内联成功后,可以从程序中删除,这也意味其它.cpp无法访问该函数,故内联函数具有static特性,即内联函数的作用域是文件作用域。因此,可以在不同的.cpp文件定义返回类型、函数名、参数类型完全相同的inline函数。习题解答:题3.10在X86模式下,使用VS2019编译和运行,输出结果不相等:ri=4,rj=20。删除printf语句中的8,输出结果ri=3和rj=20。再删除printf语句中的7,输出结果ri=2和rj=20。原因:ri引用的局部变量i分配的栈内存位置不变,但是该位置的值因printf传递实参的不同而改变。调用printf函数时,实参是通过压栈传递的。ri引用的f的局部变量i也是在栈上分配内存的,f返回后,f的栈内存已经释放,但ri引用的内存位置不变。f释放的栈内存接着供调用printf传递实参使用。VS2019在传递实参时自右向左将实参压栈,如果删除printf最右边的实参,则意味着ri引用的栈内存对应于printf的实参不断向左推移,因而其结果分别为4、3、2。
由此可见,这种引用被调函数f栈内存的用法容易引起副作用,且是不太安全的。出现这种现象的原因是:不同函数f和printf共用栈内存。如果操作系统或编译程序不采用共用栈内存的方式,则不会出现上述输出结果随删除printf实参推移的现象。为了使程序的可移植性更好,应避免主调函数main的引用变量引用被调函数f的栈内存i:包括引用被调函数的参数和被调函数的非static局部变量。习题解答:题3.13可以完成赋值运算“*(a+2)=10;”和“*(p+2)=10;”,因为等号左边的表达式都是传统左值。不能完成“a++”运算,但可以完成“p++”运算。因为a为数组首址,在程序开始运行时,它是一个不可修改的值,即a是一个地址常量,故不能作“a++”运算;但p是一个可写指针变量,即p的类型没有定义为“int*
const
p=a;”,故可以进行“p++”运算。在VS2019的X86编译模式下,变量名a代表数组,故sizeof(a)=10*sizeof(int)=40字节。变量名p是一个指针变量,任何指针变量都用于存储一个地址,而X86编译模式下一个地址占用4个字节,故sizof(p)=4字节。
注意,虽然数组名也可以表示数组的首地址,例如,在“int*p=a;”中,a表示数组首地址。但是,a优先被解释为代表一个数组。在“int*p=a;”中,一维数组a的地址可被视为单重指针,因此,指针常量a可以赋值给指针变量p。习题解答:题4.6“printf("%d,%d",23,34L,56)”语法上是正确的。因为printf的函数原型为“intprintf(constchar*,…)”,省略参数…表示可以出现任意个任意类型的实参,故出现23,34L,56作为实参是合法的。打印结果为“23,34”。如果sizeof(int)<sizeof(long),则打印的是34L的低地址部分的值,而34L高地址部分的值和56的值都将被忽略,因为“%d,%d”只要求打印两个整数,打印格式为十进制形式。
注意,上述printf调用即使只给出一个整数23,上述语句也是正确的。另外,printf的返回值为实际打印的字符个数。因此,若x是一个正整数,则printf(“%d”,x)返回x的十进制有效数字位数。习题解答:题4.7对于简单类型的内存分配和释放几乎没有区别当new为对象或对象数组分配内存时,会先调用malloc函数为对象或对象数组分配内存,然后再调用构造函数对对象或对象数组的每个元素进行构造。当delete用于指向对象的指针、或delete[]用于对象数组的指针时,会先调用析构函数析构对象或对象数组的每个元素,然后释放该指针指向的对象或对象数组的内存。
“delete对象数组指针”可造成内存泄漏,因为它只析构对象数组的第一个元素。
注意,对于类A及其派生类B,不应在A的非构造函数中,使用this如new(this)
A()调用类A的构造函数;也不应A的非析构函数中,使用this如deletethis或this->~A()调用类A的析构函数。这两种做法都可能破坏类B对象的多态特性,当B的实例函数成员调用A的实例函数成员时,因为this虽然是在类A的实例函数中使用,但它实际上可能指向的是类B的对象。习题解答:题4.8对于定义的类T,构造函数和析构函数的隐含参数this的类型为T*const,因为构造和析构函数的隐含参数的类型是固定的。隐含参数this也是函数的参数,故可以作为函数重载的考虑因素。构造函数的隐含参数类型是固定的,但还允许其显式参数表的参数(个数或类型)发生变化,故构造函数是可以重载的。析构函数的显式参数表必须为空,且其隐含参数this是类型固定的,故析构函数不允许被重载
注意,对于类的实例函数成员,隐含参数this是重载的考虑因素。例如:classA{intx;public:intf(intx); //重载函数f:this的类型为A*constthisintf(intx)const; //重载函数f:this的类型为constA*constthisA(); //重载构造函数:this的类型为A*constthisA(intx); //重载构造函数:this的类型为A*constthis+x的类型int}a; //a.f(3)将调用intf(intx):因为this指向可写对象,实参a可写
constAb(3); //b.f(3)将调用intf(intx)const:因为this指向不可写对象,实参b不可写习题解答:题5.3mutable成员是一种在整个对象为const类型、而又希望其实例数据成员可以被修改的成员。mutable成员可以同时定义为volatile成员。但mutable不能同时定义为static即静态数据成员,也不能同时定义为const成员。
注意,mutable只能用于定义类的实例数据成员。例如:structA{mutablevolatileintx;inty;}a; //a可被赋值,故a.x和a.y均可被赋值。
constAb; //b只读不可被赋值,故b.y也不可被赋值。但是机动成员b.x可被赋值。习题解答:题5.9静态数据成员依赖于类存在,可供所有对象访问,故静态数据成员是全局的。因此,在类体外定义静态数据成员时,不能使用static(即文件作用域)修饰静态数据成员。故在类体外定义“staticintA::y=6;”和“staticintA::z=7”有static是错误的。此外,静态数据成员“A::z”在类体内说明为volatile成员,故在类体外定义静态数据成员A::z时,也应该用volatile修饰A::z。因此,正确的类体外定义为“volatileintA::z=7;”。
注意,无论本题的类A是用class定义的,还是用struct定义的,在类体外定义静态数据成员时,不要顾忌这些静态数据成员的可访问性。但是,在静态数据成员定义结束后,对静态数据成员取值或者赋值,都要检查该成员的可访问性。例如,若主函数不是类A的友元函数,则它不能访问类A的私有或保护的静态数据成员以及静态函数成员。习题解答:题5.10所有静态数据成员的定义都是正确的。在类体外定义静态数据成员时,其格式通常为“类型
类名::静态成员名=初始值”。故对于本题出现的两个j的定义“int*A::j=&y;”和“intA::*j=&A::x;”,“int*A::j=&y;”是在类体外定义静态数据成员;而“intA::*j=&A::x;”是定义全局变量j,其类型为实例数据成员指针类型”
intA::*”。类A定义的静态数据成员a的类型为实例数据成员指针“intA::*”。故根据(2)在类体外定义静态数据成员a时,其格式“intA::*A::a=&A::x;”应包括类型“intA::*”,另外包括“类名::静态成员名”定义形式“A::a”。
注意,只有取实例数据成员x的“地址”例如“&A::x”,才能得到实例数据成员指针类型“intA::*“的值,该值可以初始化“intA::*“类型的静态数据成员A::a。故“intA::*A::a=&A::x;”是正确的。而取静态数据成员k的地址只能得到一个普通指针形式*,例如,可定义”int*p=&A::k;”。由于A::k引用y,故有int*p=&A::kint*p=&y;习题解答:题6.4根据作用域规则,作用域越小的标识符,被访问的优先级越高。在派生类的实例函数成员中,优先访问的是派生类的实例数据成员,而不是与其同名基类数据成员。如果希望访问基类的同名数据成员,可以使用“基类名::基类数据成员名”的形式。如果基类及派生类的数据成员与派生类实例函数成员的参数同名,则优先访问的是该实例函数成员的参数,因为其作用域范围更小。若希望访问基类的同名数据成员则如(2)所示,如果希望访问派生类的同名数据成员,可以使用“派生类名::数据成员名”或“this->数据成员名”的形式。
注意,如果希望访问类外变量如全局变量或模块变量x,则可以在函数成员中使用::x的形式。不能用::x标识main内部定义的局部变量x或其内部static变量x。
习题解答:题6.5函数参数相当于函数的一个局部变量,是要在栈上分配内存的。只是参数的初始化是在函数调用时通过实参传递完成的。如果用未完全声明的类作为函数参数,由于不知道该类需要多少字节,故无法为其分配内存,因此,不能直接使用类未完全声明的类名作为函数参数的类型。但是,可以使用该类的指针或引用作为函数参数的类型。因为任何指针需要的内存空间都是定数(用于存放一个地址内存单元字节数是确定的),故编译可以为这种参数分配内存。注意,引用类型在编译为汇编代码时实现为指针类型。
注意,引用分为两种:有址引用即&定义的引用,以及无址引用即&&定义的引用。类的引用都是可以作为函数参数类型的,不管该类是否已经完成声明或定义。&说明的参数优先匹配有地址的实参如变量,而&&优先匹配常量。
习题解答:题6.13有址引用变量引用有内存地址的对象,如果是只读有址引用也可以引用常量对象(历史原因)。无址引用变量引用常量对象、或者函数返回的非有址引用对象如intf()或int&&f()。两种引用在编译为汇编代码时均实现为指针。但是,在高级语言一级,它们的字节数即被引用对象的字节数(引用变量共享被引用对象的内存)。
实例:struct
A{intx,y;}a={1,2};//a为可写的传统左值变量(对象)A&k(){returna;} //返回有址引用对象Af(){returna;} //返回非有址引用对象:即不是A&f()函数A&&g(){return{1,2};} //返回非有址引用对象:即不是A&g()函数。等价于A{2,3}intmain(int
argc,char*v[],char*e[]){A&m=a; //引用传统左值对象aA&n=k(); //引用k()引用的传统左值对象aconst
A&p={2,3}; //引用常量对象,{2,3}等价于A{2,3}A&&x=f(); //引用返回的非有址引用对象A&&y=g(); //引用返回的非有址引用对象A&&z={2,3}; //引用常量对象。{2,3}等价于A{2,3}}习题解答:题7.11在派生类STUDENT中在protected下使用了usingPASSWORD::getChar,将继承后的getChar的访问权限从私有的修改为保护的。该函数是输入字符时不回显字符的函数。同时STUDENT定义了自己的getChar回显函数,对main隐藏基类的getChar是因为STUDENT的getChar功能不同。如果将派生类和基类的getChar()的virtual都改为static,就不会通过虚函数入口地址表指针实现晚期绑定和多态,STUDENT的getChar就不能达到输入姓名时回显姓名的效果。何时被隐藏、或者被覆盖,需要根据实际情况设计:通常需要考虑访问的安全性和访问的必要性。
注意,隐藏和覆盖是相对不同场景或函数而言的。习题解答:题7.12
structA{char*a,b,*geta();charA::*p;char*A::*q();char*(A::*r)();};voidmain(void){Aa;a.p=&A::a;a.p=&A::b;a.q=&A::geta;a.r=a.geta;}(1) 在“a.p=&A::a”中,无法将等号右边“char*A::*”类型的值赋值给“charA::*”类型的变量a.p。(2) 在“a.q=&A::geta”中,a.q是一个无参函数,不能像变量那样被赋值。(3) 在“a.r=a.geta”中,a.geta是一个无参函数(真实地址),而a.r需要一个实例函数成员指针,需要用一个相对对象首地址的偏移量对a.r赋值。可改为“a.r=&A::geta”。习题解答:题8.1
需要表现出多态行为的实例函数成员可以定义为虚函数。构造函数无需多态,除构造函数外,有this参数的其他实例函数成员都能定义为虚函数。鉴于父类指针变量既可以指向父类对象也可指向子类对象,父类引用变量既可以引用父类对象也可引用子类对象,在通过父类指针变量或父类引用变量调用虚函数时,可通过该指针变量或引用变量所关联的实际对象类型,通过晚期绑定找到对应对象类型的虚函数并调用。“delete指针变量”可触发对析构函数的调用:根据指针变量指向的对象,找到对象内部的虚函数入口地址表指针,进而通过该指针找到要调用的实际析构函数,可在一定程度上防止内存泄漏。注意:静态函数成员没有this参数,调用时不传递对象实参,因而不是实例函数成员,故不能通过存储在对象内部的虚函数入口地址表指针,实现合适的多态行为调用。习题解答:题8.9
classA{inta;virtualintf();virtualintg()=0;public:
virtualA(); //(1)从”virtualA()”中去掉virtual,构造函数不能为虚函数。}a; //(2)抽象类A有纯虚函数,故不能定义a为抽象类对象。
classB:A{
longf(); //(3)返回类型long与A的虚函数intf()不同,即longf()不能自动成为虚函数。intg(int);}b; //(4)B有继承的纯虚函数未定义,故B是抽象类,不能产生对象。A*p=newA; //(5)抽象类A不能产生对象。B*q=newB; //(6)抽象类B不能产生对象。intf(A,B); //(7)抽象类A、B不能产生对象。Ag(B&); //(8)函数g不能产生抽象类A的对象返回。inth(B*);习题解答:题8.13
广义表的元素要么是单个字符,要么又是一个广义表。可以分别定义元素ELEMS和广义表LISTS,它们的内部均有ELEMS*指针,用于指向下一个元素或者广义表。ELEMS指针指向元素ELEMS,自然没有任何问题;但指向另一类型LISTS的对象,却可能产生类型不相容问题。为解决这一类型“相容”性问题,应将LISTS定义为ELEMS的子类,这样父类指针ELEMS*可以指向子类LISTS对象。各自的打印函数print应定义为虚函数,以根据ELEMS*指针指向的实际对象类型,表现出适当的多态打印行为。classELEMS{charc;ELEMS*n;public:virtualvoidprint();ELEMS*getn(){returnn;}ELEMS*setn(ELEMS*a){n=a;returnthis;}ELEMS(charx,ELEMS*y=0){c=x;n=y;}};voidELEMS::print(){printf("%c",c);}classLISTS:publicELEMS{ELEMS*e;public:voidprint();//自动成为虚函数
LISTS&join(ELEMS*e);LISTS(charx):ELEMS(x){e=0;}};习题解答:题9.4派生类的基类的构造函数都是带参数的;或者派生类的对象成员的构造函数都是带参数的;派生类包含只读、引用类型的实例数据成员且没有默认值时;派生类及其继承的数据成员不全是公有成员;派生类包含指针类型的实例数据成员时。
注意,当前3个条件同时满足时,派生类必须自定义构造函数。第5个条件是参考条件,因为指针指向的资源如内存,通常由构造函数分配、由析构函数释放,而构造和析构函数会自动执行,从而实现资源的自动分配与回收管理。必须定义构造函数的条件:必须定义析构函数的条件:当类的实例数据成员为指针类型,必须定义析构函数以便自动释放资源。习题解答:题9.9(1)类A的可访问成员及权限:private:inta;protected:intb,c;public:intd,e;~A();(2)类B的可访问成员及权限:private:inta;//自定义的aprotected:intb,A::b,c(或A::c),A::e,f;A::~A();public:intd(或A::d),e,g;
classA{inta;protected:intb,c;public:intd,e;~A();};classB:protectedA{inta;protected:intb,f;public:inte,g;A::d;};classC:A{inta;protected:intb,f;public:inte,g;usingA::d;};structD:B,C{inta;protected:intb,f;public:inte,g;};(3)类C的可访问成员及权限:private:inta,A::b,c,A::e;A::~A();protected:intb,f;public:intd(或A::d),e,g;(4)类D的可访问成员及权限:protected:intB::b,B::A::b,B::A::e,C::b,b,c(或B::c或B::A::c),B::f,C::f,f;B::A::~A();public:inta,B::d(或B::A::d),C::d(或C::A::d),B::e,C::e,e,B::g,C::g,g;注意:如果派生类不是基类的友元,则不能访问基类的私有成员。习题解答:题9.10
#include<iostream>usingnamespacestd;structA{A(){cout<<'A';}};structB{B(){cout<<'B';}};structC:A{C(){cout<<'C';}};structD:A,B{D(){cout<<'D';}};structE:A,B,virtualC{Dd;E(){cout<<'E';}};structF:A,virtualB,virtualC,D,E{Cc,d;Ee;F(){cout<<'F';}};voidmain(void){Aa;cout<<'\n’; //ABb;cout<<'\n’; //BCc;cout<<'\n’; //ACDd;cout<<'\n’; //ABDEe;cout<<'\n’; //ACABABDEFf;cout<<'\n’; //BACAABDABABDEACACACABABDEF}CADABEABCAd:DABFABCADABEd:DABc:CAd:CAe:EABCAe:d:DABAB习题解答:题10.3摆放3个异常处理过程的顺序为:catch(int*)、catch(constvoid*)、catch(...)。catch(constint*)可以放在catch(void*)后面而不影响其捕获异常。
如再加catch(volatilevoid*)、catch(constvolatilevoid*)、catch(volatileint*)、catch(constvolatileint*),则摆放顺序为:catch(int*);catch(void*);catch(constint*);catch(constvoid*);catch(volatileint*);catch(volatilevoid*);catch(constvolatileint*);catch(constvolatilevoid*);catch(...);注意,(3)和(4)可以成组整体移动到(6)的后面,也会使每个异常都有捕获异常的机会。习题解答:题10.5如果用new分配内存,则编译程序不会自动释放内存,因此,C++的异常处理机制也不会负责这些内存的释放。。为此,应在抛出异常之前,执行“delete指针或地址”语句,析构和释放单个异常对象内存;或执行“delete[]对象数组指针”语句,析构每个数组元素存储的对象,然后释放异常对象数组所占的内存。
注意,对于编译程序为函数分配内存并构造的局部对象如Aa;当抛出异常以后,所有这些局部非static对象都会自动析构。不管函数调用的嵌套层次有多深,从抛出异常到捕获异常,每层函数的局部非static对象都会自动析构,从而不会产生内存泄漏。习题解答:题10.11断言是一种检查程序编译或执行条件是否满足的机制。编译断言又称为静态断言static_assert,编译时检查编译环境是否满足要求,如不满足则输出断言表达式并停止编译。例如,检查整数是否采用32位的编译环境,则使用static_assert(sizeof(int)==4);动态断言assert运行时检查程序执行条件是否满足,如不满足则输出断言表达式并停止执行。例如,检查在当前位置变量x是否大于0,可以使用assert(x>0);。
注意,在静态断言static_assert(表达式)中,表达式不应包含程序里定义的变量,表达式应能在编译时计算出常量值。习题解答:题11.5
intx=7;inty=::x+5;structA{intx;staticinty;public:A&operator+=(A&);operatorint(){returnx+y;};A(intx=::x+1,inty=::y+11){A::x=x;A::y=y;}};A&A::operator+=(A&a){x+=a.x;y+=a.y;return*this;}intA::y=20;voidmain(void){Aa(2,5),b(6),c;inti,&j=i,*p=&A::y;i=b.y;j=b.x;i=*p;j=c;i=a+c;i=b+=c;i=((a+=c)=b)+9;}涉及运算符::的用法,运算符重载,实例与静态数据成员,数据成员指针,有址引用&与传统左值,参数默认值。解题技巧:构造全局与局部变量表,静态数据成员被所有对象共享。x(即::x)=7y(即::y)=::x+5=7+5=12A::y=20
a.x=2a.y=5b.x=6b.y=::y+11=12+11=23习题解答:题11.5
intx=7;inty=::x+5;structA{intx;staticinty;public:A&operator+=(A&);operatorint(){returnx+y;};A(intx=::x+1,inty=::y+11){A::x=x;A::y=y;}};A&A::operator+=(A&a){x+=a.x;y+=a.y;return*this;}intA::y=20;voidmain(void){Aa(2,5),b(6),c;inti,&j=i,*p=&A::y;i=b.y;j=b.x;i=*p;j=c;i=a+c;i=b+=c;i=((a+=c)=b)+9;}运算符::的用法,运算符重载,实例与静态数据成员,数据成员指针,有址引用&与传统左值,参数默认值。解题技巧:构造全局与局部变量表,静态数据成员被所有对象共享。x(即::x)=7y(即::y)=::x+5=7+5=12A::y=20→5a.x=2a.y=5
b.x=6b.y=::y+11=12+11=23→23→23习题解答:题11.5
intx=7;inty=::x+5;structA{intx;staticinty;public:A&operator+=(A&);operatorint(){returnx+y;};A(intx=::x+1,inty=::y+11){A::x=x;A::y=y;}};A&A::operator+=(A&a){x+=a.x;y+=a.y;return*this;}intA::y=20;voidmain(void){Aa(2,5),b(6),c;inti,&j=i,*p=&A::y;i=b.y;j=b.x;i=*p;j=c;i=a+c;i=b+=c;i=((a+=c)=b)+9;}运算符::的用法,运算符重载,实例与静态数据成员,数据成员指针,有址引用&与传统左值,参数默认值。解题技巧:构造全局与局部变量表,静态数据成员被所有对象共享。x(即::x)=7y(即::y)=::x+5=7+5=12A::y=20
→5→23a.x=2a.y=5→23b.x=6
b.y=::y+11=12+11=23c.x=::x+1=7+1=8c.y=::y+11=12+11=23i=b.yi=j=b.xi=*p=*(&A::y)=A::y=23=6=23→23→23→23习题解答:题11.5
intx=7;inty=::x+5;structA{intx;staticinty;public:A&operator+=(A&);operatorint(){returnx+y;};A(intx=::x+1,inty=::y+11){A::x=x;A::y=y;}};A&A::operator+=(A&a){x+=a.x;y+=a.y;return*this;}intA::y=20;voidmain(void){Aa(2,5),b(6),c;inti,&j=i,*p=&A::y;i=b.y;j=b.x;i=*p;j=c;i=a+c;i=b+=c;i=((a+=c)=b)+9;}运算符::的用法,运算符重载,实例与静态数据成员,数据成员指针,有址引用&与传统左值,参数默认值。解题技巧:构造全局与局部变量表,静态数据成员被所有对象共享。x(即::x)=7y(即::y)=::x+5=7+5=12A::y=20
→5→23→23
a.x=2a.y=5→23→23b.x=6b.y=::y+11=12+11=23→23
c.x=::x+1=7+1=8c.y=::y+11=12+11=23
i=b.y=23i=j=b.x=6i=*p=*(&A::y)=A::y=23i=j=c=i=a+c=a.x+a.y+c.x+c.y=2+23+8+23=568+23=31c.x+c.y=习题解答:题11.5
intx=7;inty=::x+5;structA{intx;staticinty;public:A&operator+=(A&);operatorint(){returnx+y;};A(intx=::x+1,inty=::y+11){A::x=x;A::y=y;}};A&A::operator+=(A&a){x+=a.x;y+=a.y;return*this;}intA::y=20;voidmain(void){Aa(2,5),b(6),c;inti,&j=i,*p=&A::y;i=b.y;j=b.x;i=*p;j=c;i=a+c;i=b+=c;i=((a+=c)=b)+9;}运算符::的用法,运算符重载,实例与静态数据成员,数据成员指针,有址引用&与传统左值,参数默认值。解题技巧:构造全局与局部变量表,静态数据成员被所有对象共享。x(即::x)=7y(即::y)=::x+5=7+5=12A::y=20→5→23→23
a.x=2a.y=5→23→23b.x=6
b.y=::y+11=12+11=23→23c.x=::x+1=7+1=8c.y=::y+11=12+11=23
i=b.y=23i=j=b.x=6i=*p=*(&A::y)=A::y=23i=j=c=c.x+c.y=8+23=31i=a+c=a.x+a.y+c.x+c.y=2+23+8+23=56i=b+=c=b.x+b.y=14+46=60→i=b→14→46→46→46→46习题解答:题11.5
intx=7;inty=::x+5;structA{intx;staticinty;public:A&operator+=(A&);operatorint(){returnx+y;};A(intx=::x+1,inty=::y+11){A::x=x;A::y=y;}};A&A::operator+=(A&a){x+=a.x;y+=a.y;return*this;}intA::y=20;voidmain(void){Aa(2,5),b(6),c;inti,&j=i,*p=&A::y;i=b.y;j=b.x;i=*p;j=c;i=a+c;i=b+=c;i=((a+=c)=b)+9;}运算符::的用法,运算符重载,实例与静态数据成员,数据成员指针,有址引用&与传统左值,参数默认值。解题技巧:构造全局与局部变量表,静态数据成员被所有对象共享。x(即::x)=7y(即::y)=::x+5=7+5=12A::y=20→5→23→23→46
a.x=2a.y=5→23→23→46b.x=6
b.y=::y+11=12+11=23→23→46c.x=::x+1=7+1=8c.y=::y+11=12+11=23
i=b.y=23i=j=b.x=6i=*p=*(&A::y)=A::y=23i=j=c=c.x+c.y=8+23=31i=a+c=a.x+a.y+c.x+c.y=2+23+8+23=56i=b+=c→i=b=b.x+b.y=14+46=60i=((a+=c)=b)+9→14→46→10→92→92→92→92→i=(a=b)+9习题解答:题11.5
intx=7;inty=::x+5;structA{intx;staticinty;public:A&operator+=(A&);operatorint(){returnx+y;};A(intx=::x+1,inty=::y+11){A::x=x;A::y=y;}};A&A::operator+=(A&a){x+=a.x;y+=a.y;return*this;}intA::y=20;voidmain(void){Aa(2,5),b(6),c;inti,&j=i,*p=&A::y;i=b.y;j=b.x;i=*p;j=c;i=a+c;i=b+=c;i=((a+=c)=b)+9;}运算符::的用法,运算符重载,实例与静态数据成员,数据成员指针,有址引用&与传统左值,参数默认值。解题技巧:构造全局与局部变量表,静态数据成员被所有对象共享。x(即::x)=7y(即::y)=::x+5=7+5=12A::y=20→5→23→23→46→92
a.x=2a.y=5→23→23→46→92b.x=6
b.y=::y+11=12+11=23→23→46→92c.x=::x+1=7+1=8c.y=::y+11=12+11=23→46→92
i=b.y=23i=j=b.x=6i=*p=*(&A::y)=A::y=23i=j=c=c.x+c.y=8+23=31i=a+c=a.x+a.y+c.x+c.y=2+23+8+23=56i=b+=c→i=b=b.x+b.y=14+46=60i=((a+=c)=b)+9→i=(a=b)+9→14a.x+a.y+9=14+92+9=115→10→14→92→92→92→92→i=a+9=习题解答:题11.7不能使用多继承,因为不支持classSTACK:QUEUE,QUEUE{}的定义形式,故只能定义classSTACK:publicQUEUE{QUEUEq;},委托对象成员q代理一个基类的功能,对不支持多继承的语言如java,一般采用委托代理模式处理多继承。最好形成父子关系类,即QUEUE通过public方式继承到STACK。形成父类关系的好处是constQUEUE&类型的形参可以直接接受子类对象(如constSTACK&或STACK&&类型)
作为实参。注意定义移动和深拷贝构造函数、移动和深拷贝赋值函数。且应保证宏观和微观一致:如宏观为深拷贝构造STACT(constSTACK&),则微观应调用深拷贝构造QUEUE(constQUEUE&),而不应调用移动构造QUEUE(QUEUE&&);如宏观为移动构造STACT(STACK&&),则微观应调用移动构造QUEUE(QUEUE&&),而不应调用深拷贝构造QUEUE(constQUEUE&)。对于对赋值运算也应同样处理。在基类的非构造函数中不应调用new(this)QUEUE(…),在基类非析构函数中不应调基类析构函数,即不应执行this->~QUEUE()或deletethis;若前述函数被STACK的实例函数调用,这会破坏STACK对象的多态性。对于重载的赋值运算,应防止有人写类似“a=a;”的语句。应将基类实例成员函数尽量定义为虚函数。关于双队列模拟一个栈有多种方法,本解答采用双队列空间的利用率较高的方法。
习题解答:题12.3使用“*const_cast<int*>(&x)=3;”不能修改全局变量x的值。同理(int&)x=3也不能修改全局变量x的值。简单类型的只读变量受到内存保护,不能改变该简单类型只读变量的值。打印x的值发现x的值没变。
注意,目前的内存保护是单重的,而不是多重的:即不能既保护整块内存,又保护其中的部分存储单元。例如:structA{intx;constinty;mutableintz;};Aa;//整块内存a可写,a.y不可写,a.x和a.z可写。constAb={1,2,3};//整块内存b不可写,a.x和a.y不可写,a.z可写。注意,通过类型转换能够使a.y可写。(int
&)(a.y)=5,打印a.y其值发生了变化。习题解答:题12.5多态检查需要检查被转换对象的类是否包含虚函数。static_cast在转换对象时不会进行多态检查。当派生类有虚基类时,不能用static_cast将基类向下转换至派生类,因为原基类对象没有派生类中的虚基类到物理基类的映射信息。
例如,当派生类有虚基类时:structA{intx,y;}a;class
B{};class
C:virtualA,B{}c;voidmain(){
C*p=(C*)&a;//报错,不安全p=static_cast<C*>(&a);//错误
Cq=static_cast<C>(a);//错误,将覆盖}
c的内存布局ABA的映射指针classD:A,B{}d;d的内存布局ABA习题解答:题13.4以某个类型实参特化类模板,在特化的实例类中可以重新定义其数据成员和函数成员。仅特化类模板实例类的部分函数成员时,其函数原型与类模板的函数成员必须相同,并且其多态特性必须保持同类模板的原函数成员相同。
注意,以某个类型实参特化类模板,相当于定义了一个新的实例类,以代替按模板用该类型实参生成的实例类。既然是新的实例类,那就可以和原来生成的实例完全不同。
习题解答:题13.10直接定义“template<classT>intoperator==(constT&x,constT&y){returnx==y;}”是不行的,因为一旦用int作T的实参实例化,则实例函数operator==<int>(constint&x,constint&y)的参数都是简单
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 2025年厦门市集美区杏滨中心幼儿园招聘备考题库完整答案详解
- 食品饮用水安全责任制度
- 基金公司内部审计制度
- 复印店内部管理制度
- 江南环保内部管理制度
- 银川市政府内部汇报制度
- 2025年石城县文化旅游发展集团有限公司下属子公司经理(职业经理人)招聘备考题库及完整答案详解1套
- 2025年河南花花牛乳业集团股份有限公司招聘15人备考题库带答案详解
- 长沙市望城区人民医院2025年面向社会公开招聘编外合同制专业技术人员备考题库及答案详解参考
- 2025年昆明华航技工学校蒙自校区招聘18人备考题库及答案详解(考点梳理)
- SYT 6968-2021 油气输送管道工程水平定向钻穿越设计规范-PDF解密
- PCB制造成本参数
- 2024-2025年上海中考英语真题及答案解析
- 第6课第1课时呵护花季激扬青春【中职专用】《心理健康与职业生涯》(高教版2023基础模块)
- 道路绿化养护投标方案(技术方案)
- 品牌策划与推广(第3版 数字教材版) 课件全套 人大 第1-9章 品牌的本质及其定位决策-营销活动策划与管理
- 爆破作业人员教育培训制度
- 辊道窑作业标准指导书
- GB/T 24421.1-2023服务业组织标准化工作指南第1部分:总则
- 井巷用全自动全液压凿岩台车设计书
- 蚕桑产业建设汇报材料(四)
评论
0/150
提交评论