Thinking in C++1. private 与 protect 关键字的区别。
子类能访问基类的protect成员而不能访问private成员。
2. 友元,friend 如何声明一个友元。
3. 缺省构造函数。当类声明了一个带参数的构造函数后而没有声明无参数构造函数,编译器还会为它生成一个默认的缺省构造函数吗?分析下例的错误:
class A{
public:
A(int i){
cout<<i<<endl;
}
};
void main(){
A a;
}
4. 构造函数有没有返回值?析构函数有没有返回值?析构函数可不可以带参数?
5. 解释一下重载
函数重载允许两个或更多个函数使用同一个名字限制条件是它们的参数表必须不同参数类型不同或参数的数目不同。
6. 重载函数如何来区分彼此?
7. 解释缺省参数函数
8. 下例能编译通过吗?为什么。重载的缺省参数函数不能与其它函数产生二义。
void s(int i){
cout<<i<<endl;
}
void s(int i, int j=0){
cout<<i<<endl;
}
void main(){
s(5);
}
9. 能否用返回值来区分重载函数?
10. 看下例回答以下问题:
a. 常量必须初始化,下面的代码又和问题:
const int i;
i=20;
b. 常量有没有存储控件,或者只是编译时的符号而已?
不一定
c. 常量一定是在编译时就确定其值的吗?
const int i=100;
const int j=i+100;
long address=(long)&j; //强迫编译器为常量分配存储空间
char buf[j+10];
void main(){
const char c=cin.get();
const char c2=c-'a'+'A';
cout<<c<<" "<<c2<<endl;
}
11. 看下例回答以下问题:
a. 能否将一个非常量对象的地址赋给一个常量指针?
b. 能否将一个常量对象的抵制赋给一个非常量指针?若确要如此,该如何做?
void main(){
const int i=5;
int *j=const_cast<int *>(&i);
}
const int* x; //常量指针
int* const x=&d; //指针常量
int const* x; //常量指针
const int* const x=&d; //常量指针常量
12. 函数调用中值传递使用常量定义无甚意义:
void f(const int i);
但函数返回值的常量定义有特殊作用,看下例:
class X{
int i;
public:
void modify(){cout<<"haha"<<endl;}
};
const X fun(){return X();}
void main(){
//! fun().modify(); 常量不能为左值,所以下例也不成立:
//! const X g;
//! g.modify();
//!!! 常量一定不能为左值吗? 看16题
}
13. 常量的典型应用:
void u(const int* p);
const char* v();
14. 分析下例说出错误的原因:
class X{};
X f(){return X();}
void g1(X&){}
void g2(const X&){}
void main(){
//! g1(f()); 临时变量都是常量
g2(f());
}
15. 如何使用类常量成员,如何初始化?
class Y{
public:
const size;
Y();
};
Y::Y():size(100){}
class X{
enum{i=100};
};
//! class X{
//! const int i;
//! X(){};
//! };
16. 将一个成员函数声明为常量函数有什么作用?
a. 编译器保证该函数不得修改成员变量的值
b. 允许一个常量对象调用此函数
class X{
public:
void f() const{
cout<<"haha..."<<endl;
}
};
void main(){
const X cs;
cs.f();
}
17. volatile关键字有什么作用?
volatile 告诉编译器不要对其做任何自作多情的假设与优化。比如建立一个 const volatile 的对象,告诉编译器程序员不得改变此对象,但不是说该对象不会发生改变,来自外界的工具可能会改变它,比如硬件的端口通信。
18. 内联函数与普通函数、宏的差别?
使用内联函数的地方,编译器会对其代码进行展开,而不是插入一个跳转。这样提高了函数调用的效率。内联函数不像宏,它是一个真正的函数,有参数类型检查。
注意:在“类中”定义的成员函数都是内联函数。
19. 哪两种情况会使编译器放弃内联?
a. 函数题太复杂
b. 代码中会显式或者隐式的确函数地址
20. 预处理器的特殊功能:
a. #define DEBUG(X) cout<<#X"="<<X<<endl
b. #define TRACE(X) cout<<#X<<endl,X
c. #define FILE(X) char* X##_string
使用:
int f(){
return 9;
}
void main(){
char i[]="haha";
DEBUG(i);
for(int j=0;j<5;j++)
TRACE(f());
FILE(one)=i;
cout<<one_string<<endl;
}
21. 有没有代码能在主函数的第一行代码之前或最后一行代码之后执行?
class Obj{
char cT;
public:
Obj(char c){
cT=c;
cout<<"Obj::Obj() for"<<c<<endl;
}
~Obj(){
cout<<"Obj::~Obj() for"<<cT<<endl;
}
};
Obj A('A');
void f(){
static Obj B('B');
}
void main(){
cout<<"inside main()"<<endl;
f();
cout<<"leaving main()"<<endl;
}
OUTPUT:
Obj::Obj() forA
inside main()
Obj::Obj() forB
leaving main()
Obj::~Obj() forB
Obj::~Obj() forA
22. static 关键字的作用:
a. 静态存储
b. 控制可见性
以下都是全局定义的变量
extern int a=0; ===> int a=0; (静态)
extern void f(); ===> void f();
static int a=0; ===> 本文件内可见
static void f(); ===> 本文件内可见
23. 名字空间的使用:
namespace simon{
class A{
public:
void print(){
cout<<"this is simon::A"<<endl;
}
};
char str[]="this is simon::str";
}
void main(){
simon::A v;
v.print();
using namespace simon;
cout<<str<<endl;
}
24. 静态成员函数与普通成员函数有什么区别?
静态成员函数只能访问静态成员变量与静态成员函数。为什么呢?想一想那个this指针,静态成员函数是没有this指针的。
25. 在C++中用到一个C库中的函数,为了防止编译器按照C++的方式对符号进行访问以致无法定位函数实体该如何做?
extern"C" float f(int a, float b);
c++中 f() ===> _f_int_float
c中 f() ===> _f
26. 如何改变一个指针常量的指向?
void main(){
int i(1);
int j(2);
int * const p=&i;
int * pp=(int*)&p;
*pp=(int)&j;
cout<<*p<<endl;
}
27. 解释下面例子错误的原因
const int iii=20;
int & q=iii; //错误,非常量引用不能引用常量
int & qq=20; //错误,非常量引用不能引用常量
const int & qqq=iii; //正确
qqq=23; //错误,常量不能作为左值
引用必须跟一块合法的存储空间联系起来。
a.引用必须被初始化
b. 引用一旦指向一个对象,就不能再指向其它
c. 不能引用NULL ,不能引用常量(除非是常量引用)
28. 当用引用作函数参数时,其参数必须是非常量,即不能是直接值或返回的临时变量
void f(int &){}
void g(const int &){}
void main(){
//f(1);
g(1);
}
29. 什么情况下需要一个拷贝构造函数?
需要用类作值传递时
class X;
void f(X x2);
X x1;
f(x1);
X x2=x1;
30. 拷贝构造函数的形式: X::X(X&){}
31. 尽量避免类的值传递,用什么方法可以阻止用户的类值传递?
只要声明一个私有的拷贝构造函数即可:
class X{
X(X& x) {
cout<<"hehe"<<endl;
}
public:
X(){}
};
void main(){
X xx;
//! X x2=xx;
}
32. 定义一个函数指针,指向一个类的非静态成员函数,并调用:
class X;
typedef void (X::*FPT) ();
class X{
public:
int xi;
FPT pp;
X(int j){
xi=j;
pp=X::f;
(this->*pp)();
}
void f(){
cout<<xi<<endl;
}
};
void main(){
X x1(1),x2(2);
FPT p1,p2;
p1=X::f;
p2=x1.f;
(x2.*p1)();
(x2.*p2)();
}
注意,非静态的成员始终要跟this指针结合使用,要用一个对象来使用它而不是一个类型。即使是在类定义内部也要显式用this来强调这种关系。
33. 下例发生了几次拷贝构造函数调用?一次
class X{
public:
X(){}
X(X&){
cout<<"haha..."<<endl;
}
};
void main(){
X x1;
X x2=x1;
x2=x1;
}
34. 避免使用运算符重载!!!
35. 类成员运算符重载与全局运算符重载的区别? 参数的个数
36. 在继承链上,子类的构造函数需要显式的调用基类的构造函数,而析构函数呢?不需要,它们会被自动调用。语法:
foo::foo(int i):base_foo(i){...}
37. 什么是构造函数初始化列表?
构造函数的调用顺序是:先基类的构造函数,然后是成员对象的构造函数,构造顺序不受初始化列表中的排列顺序影响。同等级成员的初始化列表由其在类声明中的顺序决定。析构顺序与构造顺序相反。
38. 举例说明为什么有时候必须要初始化列表?
class Z{
public:
Z(int i) {
cout<<"huhu..."<<endl;
}
};
class X{
public:
X(int i) {
cout<<"hehe..."<<endl;
}
};
class Y :public X{
public:
Z z1;
Y(int i):X(i),z1(6) {
cout<<"haha..."<<endl;
}
};
void main(){
Y y(5);
}
试试将上面例子的初始化列表移到函数体内部,会发生什么?
39. 基类中对函数foo进行了多次重载,子类中重定义函数foo,能否在子类中调用foo的基类版本?
不能,子类重定义将屏蔽所有基类版本。
class X{
public:
f(int i,int j) {cout<<"haha"<<endl;}
f(int i){cout<<"haha"<<endl;}
};
class Y:public X{
public:
f(int i){cout<<"hehe"<<endl;}
};
void main(){
Y y;
//! y.f(3,4);
}
40. 谈谈public,private,protect关键字的区别
41. 谈谈私有继承的性质
42. 如何在子类中开放私有集成基类的借口
class X{
public:
void f(){cout<<"haha"<<endl;}
void ff(){cout<<"hehe"<<endl;}
};
class Y: X{
public:
X::f;
};
void main(){
Y y;
y.f();
//! y.ff();
}
43. 判断下列例子的输出:
class X{
public:
void foo() {cout<<"X::foo"<<endl;}
};
class Y: public X{
public:
void foo() {cout<<"Y::foo"<<endl;}
};
void bar(X* p){
p->foo();
}
void barbar(X& x){
x.foo();
}
void main(){
Y y;
X* p;
X& m=y;
p=&y;
p->foo();
bar(p);
barbar(m);
}
要想通过p调用Y::foo()该怎么做? 利用virtual 实现多态
44. 晚捆绑。解释编译器是如何来做到晚捆绑的?
a. 为每个包含虚函数的类创建一个表(VTABLE)
b. 在该表中放置虚函数地址
c. 为每个有虚函数的类的对象放置一个指针:VPTR,指向响应的VTABLE
45. class no_virtual{
int a;
void f(){};
};
class one_virtual{
int a;
virtual void f(){};
};
class only_virtual{
virtual void f(){};
};
class two_virtual{
virtual void f(){};
virtual void g(){};
};
class null{};
void main(){
cout<< sizeof(no_virtual)
<< sizeof(one_virtual)
<< sizeof(only_virtual)
<< sizeof(two_virtual)
<< sizeof(null)
<<endl;
}
OUTPUT:48441 说明为什么是这个结果?
谈谈在one_virtual 中VPTR与 int a 的位置关系是怎么样的?
46. 每个有虚函数的类都有一个自己的VTABLE,画图解释继承链中的VTABLE关系
47.
Instrument * i;
Brass B;
i = &B;
i->adjust(1);
写出最后一行的汇编代码:
28: Instrument* pi;
29:
Brass B;
004010A8 8D 4D F8 lea ecx,[ebp-8]
004010AB E8 73 FF FF FF call @ILT+30(Brass::Brass) (00401023)
30: pi=&B;
004010B0 8D 45 F8 lea eax,[ebp-8]
004010B3 89 45 FC mov dword ptr [ebp-4],eax
31: pi->adjust(1);
004010B6 8B F4 mov esi,esp
004010B8 6A 01 push 1
004010BA 8B 4D FC mov ecx,dword ptr [ebp-4] ;this指针
004010BD 8B 11 mov edx,dword ptr [ecx] ;VPTR指针
004010BF 8B 4D FC mov ecx,dword ptr [ebp-4] ;作参数
004010C2 FF 52 08 call dword ptr [edx+8] ;取虚函数
非静态成员函数的调用都涉及到将this指针传递给函数
48. 什么是抽象基类,纯虚函数?
抽象基类可不可以有实例? 不能
纯虚函数可不可以有实现? 能
class X{
public:
virtual void f()=0 {
cout<<"haha"<<endl;
}
};
49. 在派生类中增加新的虚函数,能不能用基类的指针调用之,为什么?请从VTPR与VTABLE的角度来解释。
如果此时确切的知道基类指针指向的是派生类对象,需要调用该函数,该如何做?RTTI
class X{
public:
virtual void f(){cout<<"haha"<<endl;}
};
class Y :public X{
public:
virtual void f(){cout<<"hehe"<<endl;}
virtual void ff(){cout<<"hehehehe"<<endl;}
};
void main(){
X* p;
Y y;
p=&y;
Y* py=NULL;
py=static_cast<Y*>(p);
if(py) py->ff();
}
50. 看下面的例子,谈谈为什么要避免值传递:
class X{
int xi;
public:
X(int i){xi=i;}
virtual int f() {return xi; }
};
class Y :public X{
int yj;
public:
Y(int i, int j):X(i),yj(j){}
virtual int f() {return X::f()+yj;}
};
void call(X B){cout<<B.f()<<endl;}
void main(){
Y A(5,8);
call(A);
}
OUTPUT:5
类对象在进行值传递时发生了“对象切片”,无论是默认的拷贝构造函数还是手工提供的,编译器都会在其中放一段代码对VPTR进行初始化。这里call的参数类型声明为基类,则调用了基类的拷贝构造函数,将对象的VTPR指向了基类的VTABLE(原本应该指向派生类的VTABLE的)。于是错误发生了。
回忆一下,什么方法可以迫使程序员放弃值传递? 私有拷贝构造函数
51. 无论是手工提供的构造函数还是默认的构造函数,无论是拷贝构造函数还是一般构造函数,编译器都会在其中加上一段代码用以初始化VPTR(当然是有虚函数的类的构造函数)
52. 编译器插入到构造函数中的秘密代码一般有哪几部分?
a. 初始化VPTR(对于有虚函数的类来说)
b. 检查this指针(因为对象有可能没有构造成功,operator new)
c. 调用基类的构造函数(如果在派生类的初始化参数列表中没有显式的调用基类的构造函数,则编译器会自动调用基类的无参数构造函数(默认的或者手工的)即使派生类构造函数在函数体内部调用了基类构造函数也不例外,如果这时基类没有无参数构造函数(比如它只手工提供了带参数的构造函数)则编译出错)
class X{
public:
X() {cout<<"X::X()"<<endl;}
X(int i){cout<<"X::X(int)"<<endl;}
};
class Y :public X{
public:
Y(int i){X(5);}
};
void main(){
Y y(6);
}
OUTPUT:X::X()
X::X(int)
53. 说明下例为什么无法编译通过?
class X{
public:
X(int i) {cout<<"X::X(int)"<<endl;}
};
class Y :public X{
public:
};
void main(){
Y y;
}
如何才能通过?
54. 上例中如果派生类改为:
class Y :public X{
public:
Y(){}
};
为何不能编译通过?如何修改?
规则:应该始终在初始化列表中调用基类构造函数
55. 如果在构造函数中调用了虚函数,只有虚函数的本地版本被调用。
class X{
public:
virtual void f(){cout<<"X::f"<<endl;}
X() {f();}
};
class Y : public X{
public:
virtual void f(){cout<<"Y::f"<<endl;}
Y() {f();}
};
class Z : public Y{
public:
virtual void f(){cout<<"Z::f"<<endl;}
Z() {f();}
};
void main(){
Z z;
}
OUTPUT:X::f
Y::f
Z::f
Z的构造函数被调用时,编译器会先调用X的构造函数,然后是Y的,因为构造函数都会将对象的VPTR指向自己类型的VTABLE因此当构造到达X的构造函数体内时,VPTR是指向X的VTABLE的,因此调用的函数就是X版本的函数。
56. 构造函数能不能是虚函数? 不能。但试想用一个基类指针去析构一个派生类对象时会发生什么事情? 如何解决? 令析构函数为虚函数
规则:只要使用多态,就应该让析构函数为虚函数。
析构函数不需要像构造函数那样显式的调用基类的析构函数,编译器会自动调用基类的构造函数。
57. 讨论,若在析构函数中调用了虚函数,应该调用虚函数的哪个版本? 本地版。只有本地版才会只处理本地数据,不会去处理一个也许几经被“析构”的派生类数据。
因此晚捆绑机制在析构函数中被忽略。
58. 模板的语法
类模板:
template<class T>
class array{
T aT[100];
public:
array() {for(int i=0;i<100;i++) aT[i]=(T)i;}
T getAt(int index);
};
template<class T>
T array<T>::getAt(int index){return aT[index];}
void main(){
array<double> Darray;
cout<<Darray.getAt(8)<<endl;
}
常量模板:
template<int size=100>
class array{
double aT[size];
public:
array() {for(int i=0;i<size;i++) aT[i]=i;}
double getAt(int index);
};
template<int size>
double array<size>::getAt(int index){return aT[index];}
void main(){
array<10> Darray;
cout<<Darray.getAt(8)<<endl;
array<> Dsarray;
cout<<Dsarray.getAt(99)<<endl;
}
函数模板:
template<class T>
T Tgetmem(T* pT, int index){ return pT[index];}
void main(){
double dd[100];
for(int i=0;i<100; i++) dd[i]=i;
cout<<Tgetmem(dd,5)<<endl;
}
59. 多重继承。应避免。当多重集成中出现菱形继承时:出现这种情况:
为了消除继承人中包含两个base类的实体,该如何? 虚拟继承
class base{};
class d1: virtual base{};
class d2: virtual base{};
class mi: public d1, public d2{};
60. RTTI。一般我们将一个指针向上映射为一个基类指针,然后通过虚函数使用基类的接口。但是偶尔我们需要知道一个基类指针指向对象的确切类型,于是有了RTTI。
注意,RTTI依赖与驻留在虚函数表中的类型信息,所以在一个没有虚函数的类上使用RTTI是会出错的。
61. RTTI的两种用法:
a. 得到类型的名字,或者比较类型,判断类型。
typeid,比如S是一个shape* ===> typeid(*S).name()
b. 向下安全映射
shape* sp= new circle;
circle* cp= dynamic_cast<circle*>(sp);
if(cp) cout<<”cast successed”<<endl;
62. 注意:typeid中要传入的是对象本身,对于指针要去内容,对于引用直接传引用比如:
class X;
class Y: public X;
X* xp= new Y;
typeid(* xp)==typeid(Y);
typeid(xp) ==typeid(X*);
63. 注意,void指针没有类型,因此RTTI对于void指针是无用的。
64. RTTI可以让我们用一个多态指针来发现类型信息,但是应该尽可能的使用虚函数,而不是RTTI。
65. 解释一下RTTI的机制。
RTTI通过在VTABLE中放置一个额外的指针来实现的。这个指针指向一个typeinfo的结构体,typeid()其实就是通过VPTR来取typeinfo信息。
66. C++的异常与Windows的结构化异常SHE不是一回事。