学习C++ Primer时遇到的问题及解释
chenm91
感觉:
l 啰嗦有时会掩盖主题:这本书确实有些啰嗦,比如在讲函数重载的时候,讲了太长一大段(有两节是打了*号的,看还是不看?),而在TC++PL中则精炼很多。这让人有些很难接受,很多时候好像让人有些找不到纲了。
l 举例偏难有时也会掩盖主题:这本书使用的例子“文本查询系统”有些偏难。在讲述C++的一些关键性概念的时候,应该将读者的注意力都集中在这些概念上,可是通过这样一个难懂的例子来讲述,我觉得无疑是将问题复杂化了,尤其是在讲述面向对象和STL相关概念时,我无法接受,尽管确实通过这个例子显示了C++的NB之处。而在TC++PL中,不管讲什么概念,大都采用了一些简单易懂的例子,让人很舒服的一针见血的享受关键的知识点。
l 将STL分开讲不太好:这本书将“容器类”放到基本语言篇,将“泛型算法”放到基于过程的程序设计篇。尽管有他的道理,但是实际上STL是属于泛型程序设计的,还是将STL独立起来讲述比较好。读者也好对STL做一个选择:学还是不学。TC++PL则将其独立起来。
l 还是TC++PL好些:不管怎样,这本书确实很全面地介绍C++的几乎所有知识点,有浅有深。但是总的来看这本书有点硕士毕业论文的味道,太冗长,有凑页数的味道。TC++PL则有期刊论文的味道。看完两遍C++ Primer,学到不少东西,不过还是准备以TC++PL为主,再看一遍。
1、在VC的MFC程序中能否使用cout输出?
答:不能。必须使用CDC来进行输出。
2、C++中子类的构造函数是否会自动调用基类构造函数?
答:会。如果在子类的构造函数中不显式指定调用基类构造函数,则将自动调用基类缺省构造函数,所以基类一定要有缺省构造函数。如果子类的构造函数除了调用基类构造函数以外什么都不做,不能不写,必须写一个空函数。
3、子类对象的初始化过程是怎样的?
答:先调用基类构造函数来初始化相关的基类子对象,然后再执行子类(派生类)的构造函数。
4、名字空间的使用有哪些方式?
答:大概有三种方式:
l 使用名字空间别名加上名称限制修饰符
namespace LIB = IBM_Canada_Laboratory;
LIB::Array<int> ia(1024);
l using指示符,使名字空间内的所有声明可见
using namespace IBM_Canada_Laboratory;
l using声明,使名字空间中的单个声明可见
using IBM_Canada_Laboratory::Matrix;
using指示符和using声明的本质是不同的。
5、如何声明一个对象已经在文件外被定义?
答:使用extern来声明。可以在多个地方多次声明同一个对象,但是一个对象只能被定义一次,而且多个声明必须明确无误的指向这个定义实体。声明不分配内存,定义分配内存。
6、类的缺省构造函数会为类的数据成员初始化吗?
答:不会。只会分配内存,不会初始化。必须在缺省构造函数中显式进行初始化。
7、下面的对象初始化是调用赋值操作符吗?
BaseClass base1; BaseClass base2 = base1;
答:不是。是调用拷贝构造函数。
8、类中的常量数据成员应该在哪里被初始化?
答:应该在类的成员初始化列表中初始化。
9、typedef引入了新的类型吗?
答:没有。它仅仅是用来为内置或用户定义的数据类型引入助记符号。
10、空指针解引用会出错吗?
答:会。对指针解引用之前一定要先确定其不为零。
11、算术异常会抛出实际异常吗?
答:算术异常是指计算过程中出现不正确或未定义的值,如除以0或溢出。但是不会导致抛出实际的异常。
12、size_t是什么类型?
答:这是用typedef引入的类型助记符,代替unsigned int。
13、用sizeof可以获得一个数组的长度,为什么在传递数组给函数时还要带上长度参数?
答:用sizeof是可以获取一个数组的长度,如int a[10]; int len = sizeof(a)/sizeof(int);。但是,在用数组作为参数传递给函数时,数组变为了指针,所以在函数内部不能获取数组长度。
14、算术转换的指导原则是什么?
答:有两个通用原则:一、类型总是被提升为较宽的类型;二、所有含有小于整型的有序类型,在计算之前,其类型都会被转换成整型。
15、都有哪些显式强制转换操作符?它们的功能是什么?
答:有static_cast、const_cast、dynamic_cast和reinterpret_cast。static_cast可用来执行任何编译器可隐式执行的任何类型转换。const_cast用来将常量对象或指针转换为非常量对象或指针。reinterpret_cast通常对于操作数的位模式执行一个比较低层次的重新解释,相当于旧式的指针强制转换。dynamic_cast支持在运行时刻识别由指针或引用指向的类对象。
16、对于类对象的定义为什么最好在用之前才定义,而不要在一开始就定义?
答:对于内置类型,它的定义只是分配内存,不会做更多的处理。而对于类对象,它的定义除了分配内存外,还要调用构造函数。因此,如果在函数或语句块一开始就定义类对象,必然会导致构造函数的调用,而这些调用的开销可能是不必要的。这使得程序的效率下降。因此,最好是当真正要用的时候才去定义比较好。
17、容器类型的容量与长度的区别?
答:容量只与连续存储的容器类相关,如vector、deque或string,而对于非连续存储的容器类,如list,则没有容量的概念。它是指在容器下一次需要增长自己之前能够被加入到容器中的元素的总数,可调用capacity()操作来获得。长度是指容器当前拥有元素的个数,可调用size()操作来获得。容量的增长方式是与具体实现有关的。
18、迭代器的基本概念是什么?
答:迭代器提供了一种一般化的方法,对顺序或关联容器类型中的每个元素进行连续访问。
19、什么时候函数应该采用引用参数?
答:一、希望改变实参的值,又不想用指针操作;二、希望返回更多的值;三、实参为大型类对象,按值传递效率低。
20、函数参数表中的省略号是什么意思?
答:省略号告诉编译器不用对这个函数的参数进行参数类型检查了,参数可以有0个或多个。
21、extern的用法有哪些?
答:一、链接指示符,用来声明函数由其他语言编写,如extern “C”;二、声明一个全局变量,表示该变量在其他文件中定义了。声明函数时可省略掉。
22、函数名和函数名取地址都可以作为函数指针吗?
答:可以。因此,函数指针和函数指针解引用都可以用来调用函数。
23、类型安全链接的含义是什么?
答:在编译阶段,有一种机制可以把函数参数的类型和数目编码在函数名中,该机制叫做类型安全链接。所以,一个函数并非仅仅通过函数名来识别的,这是函数重载的基础。
24、静态对象会被缺省初始化吗?
答:会。缺省初始化为0。
25、用delete删除动态内存分配的空间时,是否要先判断指针为NULL?
答:不用,程序内部会自动判断。但是最好在删除之后将指针设为NULL。
26、using声明和using指示符的本质有什么不同?
答:using声明相当于在声明的语句处引入一个别名,该别名与声明的名称空间中的同名对象或函数一样。using指示符相当于把名称空间在其定义处就地剥开,并不引入新的别名。
27、函数重载时是否考虑const参数的影响?
答:对于const对象来说,函数重载不考虑,即认为带有const和非const对象参数的函数是相同的函数声明。而对于const指针或引用,则认为是不同的函数声明。
28、extern “c”链接指示符能否将c函数引入到重载函数集合中?
答:能,但只能引入一个。即c语言的函数只能有一个属于重载函数集合。
29、函数模板实例化的过程中,模板类型参数是如何被确定类型的?
答:是通过调用函数时的实参类型来推演模板的参数类型的,该过程称为模板实参推演。
30、异常处理子句catch中声明异常类对象有何用?
答:这个异常类对象可以捕获异常发生时,由throw子句所产生异常类对象。如果改为异常类对象的引用,则还可进一步修改由throw子句抛出的异常类对象,并重新抛出。
31、如果函数抛出了其异常规范之外的异常时会出现什么情况?
答:系统调用C++标准库定义的函数unexpected(),其将调用terminate()。
32、指定一个空的异常规范和不指定异常规范一样吗?
答:不一样。空的异常规范表示该函数不能抛出任何异常,而不指定异常规范表示该函数可以抛出任何异常。
33、在程序设计中异常设计是否很困难?
答:是。异常是库接口的一部分,什么时候抛出异常,什么时候库自己处理,在那些调用程序中处理抛出的异常都是一个问题。
34、STL的主要组成部分包括哪些?
答:有容器类(包含数据)、算法(对数据的操作)、迭代器(连接容器和算法)以及函数对象(定义具体的操作方式)。
35、函数对象的一般用法如何?
答:一般将函数对象定义为一个类,然后需要重载该类的调用操作符。一般的泛型算法都可以接受函数对象或函数指针。函数指针的用法缺点是不能支持函数内联。
36、函数对象从哪里来?
答:一、标准库预定义的一组算术、关系和逻辑函数对象;二、定义自己的函数对象;三、一组预定义的函数适配器,对函数对象进行特殊化或者扩展。
37、什么是函数适配器?
答:标准库提供的一组特殊的类,用来特殊化或者扩展函数对象。它被分成下面两类:绑定器(binder)和取反器(negator)。
38、排序算法能用在list上吗?
答:不能,也不能用在联合容器set或map上。
39、类在定义之前能定义类对象吗?
答:不能。但可以先声明这个类,然后定义该类的指针或引用。
40、声明const成员函数时是否要在定义函数时也加上const?
答:在声明和定义函数的时候都必须加上const。
41、const成员函数有什么要求?
答:要求在const函数当中不能修改类的数据成员。对于那些不修改数据成员的函数最好将其声明为const函数。有一个例外,如果数据成员定义为mutable类型,则在const函数中也可以修改该数据成员。
42、静态数据成员如何初始化?
答:应该在类体定义之外初始化,但不能在类的构造函数中初始化,因为静态成员不属于某个类对象,而是属于类的。静态数据成员可通过类的限定修饰符来访问。
43、什么是嵌套类?
答:在一个类A的类体中定义的另一个类B,称为嵌套类(nested class)。
44、如何调用类的缺省构造函数定义类对象?
答:不能这样写:ClassA obj(); 这样会被认为声明了一个函数,必须这样写:ClassA obj;。一般情况都必须为类提供缺省构造函数。
45、应用在构造函数上的explicit有什么作用?
答:一般构造函数如果只含有一个参数,则编译器可以隐式的调用该构造函数,将参数类型转换为类类型,但是这种隐式转换在某些时候是存在隐患的,在构造函数前面加上explicit就是为了防止这种隐式调用发生。
46、一个类对象的容器类是如何初始化的?
答:先创建该类的临时对象,并用缺省构造函数初始化;然后将临时对象通过拷贝构造函数拷贝到容器类当中的所有类对象上;然后删除临时对象。
47、类的成员类对象在构造函数中初始化时,放在成员初始化表中与放在构造函数体中有什么区别?
答:实际上类的初始化分为两个阶段,第一个阶段是初始化阶段,它调用所有成员类的缺省构造函数或者调用成员初始化表;第二个阶段是计算阶段,它调用函数体内的语句。所以如果将成员类对象放在函数体中初始化,实际上是先调用缺省构造函数,然后执行赋值操作,效率大大降低。一般准则是所有的成员类对象都应该将其放在成员初始化表中。另外对于const成员和引用成员必须在成员初始化表中。
48、拷贝构造函数和拷贝赋值操作符是一回事吗?
答:不是。但一般来说,两者应该同时定义或者禁止。
49、前置和后置++和--操作符函数原型有什么不同?
答:后置的操作符函数参数比前置的操作符函数多一个int参数,该参数在函数定义中并不会使用。
50、转换函数与构造函数都可完成类型转换,它们有什么区别?
答:构造函数(带有一个参数的)可完成其它类型向该类类型转换操作,而转换函数是从该类类型向指定的类型转换的操作。转换函数原型不能含有返回值和参数。它的定义形式如下:operator type() {}。
51、用户定义的转换包括什么?
答:包括调用转换函数或一个参数的构造函数。
52、标准C++支持为模板参数提供默认实参吗?
答:支持,但VC++6.0不支持。
53、类模板的成员函数的实例化是不是随着类模板的实例化进行的?
答:不是,成员函数的实例化是当函数被调用的时候才实例化的。
54、面向对象编程的基础是什么?
答:两个基础:多态(polymorphism)和动态绑定(dynamic binding)。多态指的是基类的指针或引用可以指向基类派生的任何子类,即一个基类指针或引用可能的实际类型是多种的。动态绑定指的是当用基类指针调用函数时,如果函数是虚函数(virtual function),则实际调用哪个函数是在运行时刻根据指针的实际类型来动态决定了。
55、在面向对象设计中是否一次性完成类结构设计?
答:一般很难,通常是反复迭代,要求对不断演化过程中的类层次结构进行添加和修改。
56、基类的一般用法如何?
答:定义基类的指针或引用(如果包含纯虚函数,则不能定义抽象基类的对象),然后用它们来操纵任何从该基类派生来的子类对象。
57、面向对象基类的设计主要要考虑的问题是什么?
答:确定哪些成员为protected;确定哪些函数为虚函数。
58、派生类的虚函数原型是否要指定virtual?
答:可要可不要。
59、派生类能否定义函数,以重载基类当中的函数?
答:可以。采用在派生类中使用using声明可做到。
60、派生类的构造函数执行包括几个步骤?
答:三个步骤:基类构造函数;成员类构造函数;派生类构造函数。
61、类的构造函数的访问权限只能是public吗?
答:不是。可以是protected和private两种。当构造函数为protected时,只希望在派生类中构造该类。当构造函数为private时,不希望任何地方来构造该类,如果有友元类,可以在友元类中构造该类。
62、派生类的构造函数要调用基类的缺省构造函数,是否要显式调用?
答:不用。会被隐式调用。
63、析构函数是否也可以做成动态绑定?
答:可以,只要声明为虚函数。
64、虚拟函数在.cpp文件中定义时是否要加virtual?
答:不能加。
65、如何静态调用虚拟函数?
答:用类域限定符。
66、clone函数是干什么的?
答:是用来克隆一个类对象,并返回克隆后的对象指针。
67、dynamic_cast是干什么用的?
答:它是RTTI(运行时刻类型识别)的一部分。它支持在运行时刻查询一个多态指针或引用所指向的对象的实际类型。
68、多继承下的多个基类一般与派生类存在什么关系?
答:一般每个基类代表派生类完整接口的一个方面。
69、private继承意味着什么?
答:由于基类的所有成员(public和protected成员)都变成子类的private成员,包括基类的公共接口也变成私有的了,因此,子类不能给外部直接提供基类的公共接口,而必须重新实现它们。这往往是由于子类仅仅希望得到基类的数据成员,但不希望继承基类的接口。
70、什么是RTTI?
答:运行时刻类型识别,可使得程序可以知道指向基类的指针或引用实际所指的对象的类型。它提供了两个操作符:dynamic_cast,允许在运行时刻进行类型转换,把基类指针转换成派生类指针;typeid,指出指针或引用的实际派生类型。
71、typeid操作符如何使用?
答:typeid的参数如为类对象,则返回该类对象的实际类型。如为类指针,则返回该指针的定义类型。返回的值实际上是一个type_info类。
72、类成员函数的声明和定义中都要指定异常规范吗?
答:都要。