return0;
}
intFnl()
{
int*p=newint(5);
return*p;
}
24、如果有:
stringecho;声明,表达式:
echo=="yes"能够求值吗?
25、什么是类的继承?
什么是类的派生?
两者之间有什么联系?
26、类的继承方式有几种?
各怎样定义?
27、派生类构造函数执行的一般次序是怎样的?
派生类析构函数的执行次序是怎样的?
28、什么是二义性问题?
二义性问题在什么情况下产生?
解决二义性问题的方法什么?
29、作用域分辨符和虚基类技术有什么区别?
30、什么叫做虚基类?
虚基类有何作用?
31、什么是赋值兼容规则?
赋值兼容规则中所指的替代包括哪些情况?
举例说明。
32、什么叫做多态性?
在C++中是如何实现多态的?
33、运算符重载的规则是什么?
34、比较两种重载形式。
35、什么是需函数?
解决什么问题?
36、什么是抽象类?
解决什么问题?
37、什么叫做群体数据?
群体数据可分为几类?
各有什么特征?
38、什么是浅拷贝?
浅拷贝会带来的问题。
39、定义并实现双向链表中使用的结点类DNODE。
40、什么叫做栈?
对栈中元素的操作有何特性?
41、什么是队列?
对队列中元素的操作有何特性?
42、什么是容器?
有哪些类型?
43、向量(vector)的特性有哪些?
44、简述折半查找方法的基本思想。
45、最重要的三个输出流是什么?
46、最重要的三个输入流是什么?
47、异常处理的基本思想是什么?
48、Windows的编程模式是怎样的?
《面向对象程序设计》答疑库答案
1、
2、
dval=ival*ulval; //ival被转换为unsignedlong
//乘法运算的结果被转换为double
dval=ulval+fval; //ulval被转换为float
//加法运算的结果被转换为double
3、
解:
变量有以下几种存储类型:
auto存储类型:
采用堆栈方式分配内存空间,属于一时性存储,其存储空间可以被若干变量多次覆盖使用;
register存储类型:
存放在通用寄存器中;
extern存储类型:
在所有函数和程序段中都可引用;
static存储类型:
在内存中是以固定地址存放的,在整个程序运行期间都有效。
4、
while语句的语法形式:
while (表达式) 语句
执行顺序是:
先判断表达式(循环控制条件)的值,若表达式的值为true,再执行循环体(语句)。
使用while语句时应该注意,一般来说在循环体中,应该包含改变循环条件表达式值的语句,否则便会造成无限循环(死循环)。
do-while语法形式:
do 语句
while(表达式);
执行顺序是:
先执行循环体语句,后判断循环条件表达式的值。
表达式为true时,继续执行循环体,表达式为false则结束循环。
图7-2是do-while语句的流程图。
与使用while语句时一样,应该注意,在循环体中要包含改变循环条件表达式值的语句,否则便会造成无限循环(死循环)。
do-while与while语句都是实现循环结构,两者的区别是:
while语句先判断表达式的值,为true时,再执行循环体;而do-while语句是先执行循环体,再判断表达式的值,下面的例7-3用do-while语句完成了与例7-1同样的功能。
5、
结构体是由不同数据类型的数据组成的集合体,结构体类型的声明形式如下:
struct 结构名
{
数据类型说明1 成员名1;
数据类型说明2 成员名2;
┆
数据类型说明n 成员名n;
};
仅仅声明结构体类型是不够的,要使用结构体数据,还要声明结构体变量。
结构体类型变量声明的语法形式如下:
结构名 结构变量名;
有时需要使几个不同类型的变量共用同一组内存单元,这时可以声明一个联合体类型,联合体类型声明的语法形式是:
union 联合体
{
数据类型说明1 成员名1;
数据类型说明2 成员名2;
┆
数据类型说明n 成员名n;
}
与结构体一样,对联合体类型进行了声明之后,就可以说明联合体变量了。
联合体类型变量说明的语法形式是:
联合名 联合变量名;
联合体是一个特殊的结构,其所有成员共享同一段存储空间,存储空间的大小取决于存储单元最大的成员的数据类型。
6、
调用其他函数的函数被称为主调函数,被其他函数调用的函数称为被调函数。
一个函数很可能既调用别的函数又被别的函数调用,这样,它可能在某一个调用与被调用关系中充当主调函数,而在另一个调用与被调用关系中充当被调函数。
7、
一个C++程序经过编译以后生成可执行的代码,形成后缀为exe的文件,存放在外存储器中。
当程序被启动时,首先从外存将程序代码装载到内存的代码区,然后从入口地址(main()函数的起始处)开始执行。
程序在执行过程中,如果遇到了对其他函数的调用,则暂停当前函数的执行,保存下一条指令的地址(即返回地址,作为从子函数返回后继续执行的入口点),并保存现场,然后转到子函数的入口地址,执行子函数。
当遇到return语句或者子函数结束时,则恢复先前保存的现场,并从先前保存的返回地址开始继续执行。
8、
在函数未被调用时,函数的形参并不占有实际的内存空间,也没有实际的值。
只有在函数被调用时才为形参分配存储单元,并将实参与形参结合。
实参可以是常量、变量或表达式,其类型必须与形参相符。
函数的参数传递指的就是形参与实参结合(简称形实结合)的过程,形实结合的方式有值调用和引用调用。
⑴值调用
值调用是指当发生函数调用时,给形参分配内存空间,并用实参来初始化形参(直接将实参的值传递给形参)。
这一过程是参数值的单向传递过程,一旦形参获得了值便与实参脱离关系,此后无论形参发生了怎样的改变,都不会影响到实参。
⑵引用调用
我们已经看到,值调用时参数是单向传递,那么如何使在子函数中对形参所做的更改对主调函数中的实参有效呢?
这就需要使用引用调用。
引用是一种特殊类型的变量,可以被认为是另一个变量的别名。
通过引用名与通过被引用的变量名访问变量的效果是一样的。
引用也可以作为形参,如果将引用作为形参,情况便稍有不同。
这是因为,形参的初始化不在类型说明时进行,而是在执行主调函数中的调用语句时,才为形参分配内存空间,同时用实参来初始化形参。
这样引用类型的形参就通过形实结合,成为实参的一个别名,对形参的任何操作也就会直接作用于实参。
用引用作为形参的函数调用,称为引用调用
9、
在程序中,一个函数就是一个操作的名字,正是靠类似于自然语言的各种各样的名字,我们才能写出易于理解和修改的程序。
于是就产生了这样一个问题:
如何把人类自然语言中有细微差别的概念映射到编程语言中。
通常,自然语言中一个词可以代表许多种不同的含义,需要依赖上下文来确定。
这就是所谓一词多义,反映到程序中就是重载。
例如,我们说"擦桌子、擦皮鞋、擦车"时,都用同一个"擦"字,但所使用的方法截然不同。
人类完全可以理解这样的语言,因为我们从生活实践中学会了各种不同的"擦"的方法,知道对不同的物品要用对应的"擦"法,所以没有人会啰嗦到说"请用擦桌子的方法擦桌子,用擦皮鞋的方法擦皮鞋。
"计算机是否也具有同样的能力呢?
这取决于我们编写的程序。
C++语言中提供了对函数重载的支持,使我们在编程时可以对不同的功能赋予相同的函数名,编译时会根据上下文(实参的类型和个数)来确定使用哪一具体功能。
严格地说,两个以上的函数,取相同的函数名,但是形参的个数或者类型不同,编译器根据实参和形参的类型及个数的最佳匹配,自动确定调用哪一个函数,这就是函数的重载。
10、
抽象、封装、继承、多态是面向对象程序设计的基本特点。
抽象,是人类认识问题的最基本手段之一。
面向对象方法中的抽象是指对具体问题(对象)进行概括,抽出一类对象的公共性质并加以描述的过程。
抽象的过程,就是对问题进行分析和认识的过程。
一般来讲,对一个问题的抽象应该包括两个方面--数据抽象和代码抽象(或称为行为抽象)。
前者描述某类对象的属性或状态,也就是此类对象区别于被类对象的特征物理量;后者描述某类对象的共同行为特征或具有的共同功能
将抽象得到的数据成员和代码成员相结合,形成一个有机的整体,也就是将数据与操作数据的行为进行有机地结合,这就是封装。
在面向对象程序设计中,可以通过封装,将一部分成员作为类与外部的接口,将其他的成员隐蔽起来,以达到对数据访问权限的合理控制,使程序中不同部分之间的相互影响减少到最低限度。
在C++中,是利用类(class)的形式来实现封装的。
继承,就是解决如何利用前人已经进行过较为深入研究所得到的结果,如何将后来对问题又有的更深入更新的认识融入已有的成果中这这些问题的良策。
C++语言中提供了类的继承机制,允许程序员在保持原有类特性的基础上,进行更具体、更详细的说明。
通过类的这种层次结构,可以很好地反映出认识的发展过程。
多态,是指类中具有相似功能的不同函数使用同一个名称来实现。
这也是人类思维方式的一种直接模拟。
。
多态是通过重载函数和虚函数等技术来实现的。
11、
类是面向对象程序设计方法的核心,利用它可以实现对数据的封装、隐蔽,通过类的继承与派生,能够实现对问题的深入抽象描述。
类和基本类型的不同之处在于,类这个特殊类型中同时包含了对数据进行操作的函数。
类的成员包括数据成员和函数成员,分别描述问题的属性和行为,是不可分割的两个方面。
数据成员的声明方式与一般的变量相同,只要将这个声明放在类的主体中即可。
类的数据成员与一般变量的区别在于其访问权限可以由类来控制。
函数成员是描述类行为的成员,一般在类中声明原型,在类外定义函数的具体实现。
12、
构造函数的作用就是在对象被创建时利用特定的值构造对象,将对象初始化为一个特定的状态,使此对象具有区别于彼对象的特征,完成的就是一个从一般到具体的过程。
构造函数在对象创建的时候由系统自动调用。
析构函数与构造函数的作用几乎正好相反,它是用来完成对象被删除前的一些清理工作,也就是专门做扫尾工作的。
一般情况下,析构函数是在对象的生存期即将结束的时刻由系统自动调用的,它的调用完成之后,对象也就消失了,相应的内存空间也被释放。
13、
类的组合(也称聚集),描述的就是一个类内嵌其他类的对象作为成员的情况,它们之间的关系是一种包含与被包含的关系。
在面向对象程序设计中,可以对复杂对象进行分解、抽象,把一个复杂对象分解为简单对象的组合,由比较容易理解和实现的部件对象装配而成。
14、
为了声明类模板,应在类的声明之前加上一个模板参数表,参数表里面的形式类型名用来说明成员数据和成员函数的类型。
类模板的声明形式如下:
template <模板参数表>
类声明
"模板参数表"中可以包含下列内容:
①class标识符(指明可以接受一个类型参数。
)
②类型说明符标识符(指明可以接受一个由"类型说明符"所规定类型的常量作为参数。
)
15、
作用域讨论的是标识符的有效范围,作用域是一个标识符在程序正文中有效的区域。
C++的作用域分为函数原形作用域、块作用域(局部作用域)、类作用域和文件作用域。
如果对象的生存期与程序运行期相同,我们称它具有静态生存期。
在文件作用域中声明的对象都是具有静态生存期的。
如果要在函数内部的块作用域中声明具有静态生存期的对象,则要使用关键字static。
除了上述两种情况,其余的对象都具有动态生存期。
16、
类的静态数据成员是类的数据成员的一种特例,采用static关键字声明。
对于静态数据成员,每个类只有一个拷贝,由所有该类的对象共同维护和使用,这个共同维护、使用也就实现了同一类的不同对象之间的数据共享。
使用static关键字声明的函数成员是静态函数成员。
静态函数成员属于整个类,由同一个类的所有对家共同维护,为这些对象所共享。
静态函数成员具有以下两个方面的好处,一是由于静态成员函数只能直接访问同一个类的静态数据成员,可以保证不会对该类的其余数据成员造成负面影响;二是同一个类只维护一个静态函数成员的拷贝,节约了系统的开销,提高了程序的运行效率。
17、
友元提供了不同类或对象的成员函数之间、类的成员函数与一般函数之间进行数据共享的机制。
也就是说,通过友元的方式,一个普通函数或者类的成员函数可以访问到封装于某一类中的数据,这相当于给类的封装挖了一个小小的孔,把数据的隐蔽掀开了一个小小的角,通过它,可以看到类内部的一些属性。
从这个角度来讲,友元是对数据隐蔽和封装的破坏。
但是考虑到数据共享的必要性,为了提高程序的效率,很多情况下这种小的破坏也是必要的,关键是一个度的问题。
18、
在说明引用时用const修饰,被说明的引用为常引用。
常引用所引用的对象不能被更新。
常对象是这样的对象,它的数据成员值在对象的整个生存期间内不能被改变。
也就是说,常对象必须进行初始化,而且不能被更新。
19、
到现在为止,我们已经学习到了很多完整的C++源程序实例,分析它们的结构,基本上都是由三个部分构成:
类的声明、类的成员的实现和主函数。
因为我们所举的例子都比较小,所以所有这三个部分都写在同一个文件中。
在实际程序设计中,一个源程序至少要按照结构的部分划分为三个文件:
类声明文件(*.h文件)、类实现文件(*.cpp文件)和类的使用文件(*.cpp,主函数文件)。
对于更为复杂的程序,每一个类都有单独的定义和实现文件。
采用这样的组织结构,可以对不同的文件进行单独编写、编译,最后再连接;同时可以充分利用类的封装特性,在程序的调试、修改时只对其中某一个类的定义和实现进行操作,而其余部分根本就不用改动。
20、
指针p没有初始化,也就是没有指向某个确定的内存单元,它指向内存中的一个随机地址,给这个随机地址赋值是非常危险的。
21、
可以实现参数双向传递的目的。
可以实现减少函数调用时数据传递的开销的目的。
22、
this指针是一个隐含于每一个类的成员函数中的特殊指针(包括构造函数和析构函数),它用于指向正在被成员函数操作的对象。
例如:
Rectangle类成员函数Move()被定义为:
Rectangle:
:
Move(intx,inty)
{
X=x;
Y=y;
}
对于下面的语句:
Rectangle rect;
rect.Move(10,20);
当调用成员函数Move()时,该成员函数的this指针指向对象rect。
this指针是C++用来实现封装的一种机制,它将成员和用于操作这些成员的成员函数连接在一起。
当一个成员函数操作数据成员时,在实现时,都表示是对this指针所指向的对象的数据成员的操作。
C++编译器所理解的成员函数Move()的定义为:
Rectangle:
:
Move(intx,inty)
{
this->X=x;
this->Y=y;
}
这样,当使用Move()操作不同对象时,this指针也指向不同的对象,成员函数Move()就能够操作不同对象的数据成员。
一般的程序设计中,通常不直接使用this指针来引用对象成员。
this是一个指针变量,因此在成员函数中,可以使用*this来标识正在调用该函数的对象。
23、
此程序中给*p分配的内存没有被释放掉。
改正:
#include
int*Fn1();
intmain()
{
int*a=Fnl();
cout<<"thevalueofais:
"<<*a;
deletea;
return0;
}
int*Fnl()
{
int*p=newint(5);
return p;
}
24、
string类有一个带有一个参数char*的构造函数,这种形式的构造函数具有类型强制作用,编译器能自动地使用这个构造函数,用"abc"建立一个string类对象,实现了类型转换,所以,这个表达式能够求值。
25、
所谓继承就是从先辈处得到属性和行为特征。
类的继承,是新的类从已有类那里得到已有的特性。
从另一个角度来看这个问题,从已有类产生新类的过程就是类的派生。
类的继承与派生机制允许程序员在保持原有类特性的基础上,进行更具体、更详细的修改和扩充。
由原有的类产生新类时,新类便包含了原有类的特征,同时也可以加入自己所特有的新特性。
原有的类称为基类或父类,产生的新类称为派生类或子类。
派生类同样也可以作为基类派生新的类,这样就形成了类的层次结构。
类的派生实际是一种演化、发展过程,即通过扩展、更改和特殊化,从一个已知类出发建立一个新类。
通过类的派生可以建立具有共同关键特征的对象家族,从而实现代码的重用,这种继承和派生的机制对于已有程序的发展和改进是极为有利的。
26、
类的继承方式有public(公有继承)、protected(保护继承)和private(私有继承)三种,不同的继承方式,导致原来具有不同访问属性的基类成员在派生类中的访问属性也有所不同。
当类的继承方式为公有继承时,基类的公有和保护成员的访问属性在派生类中不变,而基类的私有成员不可访问,即基类的公有成员和保护成员被继承到派生类中仍作为派生类的公有成员和保护成员,派生类的其他成员可以直接访问它们。
当类的继承方式为私有继承时,基类中的公有成员和保护成员都以私有成员身份出现在派生类中,而基类的私有成员在派生类中不可访问。
也就是说基类的公有成员和保护成员被继承后作为派生类的私有成员,派生类的其他成员可以直接访问它们,但是在类外部通过派生类的对象无法访问。
保护继承中,基类的公有和保护成员都以保护成员的身份出现在派生类中,而基类的私有成员不可访问。
27、
派生类构造函数执行的一般次序如下:
①调用基类构造函数;
②调用内嵌成员对象的构造函数;
③派生类的构造函数体中的内容。
派生类析构函数的执行次序与相应的构造函数的执行顺序相反。
28、
二义性问题,也就是成员的唯一标识问题。
⑴当派生类有多个基类,而这些基类中有两个以上具有同名成员,派生类的对象及派生类内部通过派生类名直接访问基类中的这些成员时,就会产生二义性。
⑵当派生类有多个基类,而这些基类中的部分或全部又有一个共同的基类,这时也会产生二义性问题
对于⑴,可以使用作用域分辨符":
:
"来解决。
对于⑵,同样可以使用作用域分辨符":
:
"来解决。
但,注意到⑵的二义性是由重复拷贝问题带来的,而且,有时,我们不希望这种重复拷贝问题存在。
而使用作用域分辨符不能从根本上解决⑵的问题,所以,我们希望能有办法解决重复拷贝问题。
重复拷贝问题解决了,由此带来的二义性问题也解决了。
这就是--虚基类技术。
29、
前者在派生类中拥有同名成员的多个拷贝,分别通过直接基类名来唯一标识,可以存放不同的数据,进行不同的操作,后者只维护一个成员拷贝。
相比之下,前者可以容纳更多的数据,而后者使用更为简洁,内存空间更为节省。
30、
在派生类使用虚基类关键字virtual,按"class 派生类名:
:
virtual继承方式基类名"语法形式进行声明时,声明中的基类就为派生类的虚基类。
当某类的部分或全部直接基类是从另一个基类共同派生而来时,这些直接基类中,从上一级基类继承来的成员就拥有相同的名称,派生类的对象的这些同名成员在内存中同时拥有多个拷贝。
可以使用作用域分辨符来唯一标识并分别访问它们。
也可以将直接基类的共同基类设置为虚基类,这时从不同的路径继承过来的该类成员在内存中只拥有一个拷贝,这样就解决了同名成员的唯一标识问题。
31、
赋值兼容规则是指在需要基类对象的任何地方都可以使用公有派生类的对象来替代。
赋值兼容规则中所指的替代包括以下的情况:
◆派生类的对象可以赋值给基类对象。
◆派生类的对象可以初始化基类的引用。
◆派生类对象的地址可以赋给指向基类的指针。
classB
{...}
classD:
publicB
{...}
Bb1,*pb1;
Dd1;
这时:
①派生类对象可以赋值给基类对象:
b1=d1;
②派生类的对象也可以初始化基类对象的引用:
B&bb=d1;
③派生类对象的地址