25.有关函数的默认参数
默认参数只能被指定一次,并且必须在文件中第一次声明该函数时指定,即在声明中指定,不能在实现中指定。虽然默认参数只能指定一次,但你可以为每个重载函数的默认参数指定不同的值。这样,不同的重载函数可以有不同的默认参数。
所有默认参数都必须出现在非默认参数的右边。
26.struct和union的区别
union在任一时刻只有一个成员处于活动状态,这是因为union中的成员共用一个内存块,这个内存块的大小取决于union的成员中占用最大内存空间的成员的长度。struct变量所占的内存大小则是struct的全体成员所占内存空间大小的总和。见下例:
int main()
{
union myunion{
int a;
int b;
}anuion;
anuion.a = 1;
anuion.b = 2;
cout<<anuion.a + anuion.b<<endl;//result is 4,because the parameter a and b is equal
cout<<& anuion.a<<endl;//0012FF7C,this is a address of the union parameter
cout<<& anuion.b<<endl;//0012FF7C,it is the same
return 0;
}
可见,对成员b的赋值即是对a的赋值,因为这两个成员是共享一个内存空间的。
union不能使用protected。
27.struct前加typedef的原因
主要是为了方便移植到C代码中。下面是常见的定义。
typedef struct MYSTRUCT
{
int a;
int b;
}MYSTRUCT,*LPMYSTRUCT;//the name is not necessarily the same
这样无论是C++还是在C中,都可以直接用MYSTRUCT bar;定义一个MYSTRUCT的结构变量bar,用LPMYSTRUCT pbar;来定义一个指向MYSTRUCT类型的指针变量pbar。
28.new与malloc,delete与free
new后面只能跟与指针变量所指向数据相同的数据类型,不能跟指针类型。malloc可以使用指针类型。下面看看两者的一个区别:
int *p = new int;//correct
int *p = new double;//no error
int *p = new int*;//error
int *p = (int*)malloc(sizeof(int));//correct
int *p = (int*)malloc(sizeof(int*));//no error
int *p = (int*)malloc(sizeof(double));//no error
sizeof()只是用来指定一个空间,最终目的是得出一个整数的值。上面的sizeof()甚至可以用数字来代替。
29.使用对象时需要注意的一些问题。
(1)传递对象时一个潜在的问题
当将把一个对象作为参数传递给函数时,在函数体里面会生成这个对象的副本,它是按位从原始对象那里初始化的。如果原始对象中有一个已分配空间的指针变量,那么在函数中的副本的指针变量和原始指针变量是指向同一个地址的。当函数执行完毕后,对象的副本将被销毁,这时会调用对象副本的析构函数,如果这个析构函数使用了delete或free来释放原来分配给对象副本中指针变量的内存,那么也就释放了原始对象中指针变量所指向的内存。当主程序退出时,原始对象也调用它的析构函数,这时再一次释放原始对象中指针中变量所指向的内存,就会发生对同一块动态分配的内存释放两次的操作,这是一个未定义的操作,会产生严重的错误。
(2)使用函数返回对象时的潜在问题
在函数中返回一个对象时,函数创建了一个临时对象来保存要返回的值,而函数所返回的对象实际上是这个临时对象。在对象的值被返回后,临时对象将被销毁,这样将会产生一些不可预料的副作用,例如(1)中所说的那样。
30.解决使用对象时产生的副作用的方法。
为了解决传递对象和返回对象时可能产生的副作用,可以使用传递对象指针或对象引用的方法。
还有另外一种方法,就是使用复制构造函数。复制构造函数会在下面几种情况下被自动调用:
(1)当对象被作为参数传递给函数时。
(2)当使用一个对象来初始化另一个对象时。
(3)当创建临时对象作为函数的返回结果时。
复制构造函数的定义和实现可以是下面的形式:
class myclass{
int *p;
myclass(const myclass &ob);//define the copy constructor
…
}
myclass::myclass(const myclass &ob)//implement of the copy constructor
{
p = new int;//allocate a new address
*p = *obj.p;//use the new pointer to store the original pointer
}
复制构造函数是C++的复杂性的体现之一。java和C#是没有复制构造函数的,它们只能通过引用来操作对象。
31.运算符重载相关
使用成员函数重载二元运算符时,运算符重载函数只有一个参数,这是因为另一个参数是被隐式传递了,这个参数就是大名鼎鼎this指针啦,它是指向调用函数的指针的。由于它是隐式被使用,所以一般不用写出来。
使用成员函数重型一元运算符时,例如++,--这两个自增,自减运算符,它们在使用的时候是有前缀和后缀两种方式,因此在实现运算符重载函数时是有些区别的,具体是返回对象的时机不同。同时,后缀形式的运算符重载函数定义是objtype operator++(int notused);
使用友员函数来重载运算符,即非成员函数重载运算符时,由于没有this指针,所以运算符所需要的参数都要作显式定义。在一般情况下都应该使用成员函数来重载运算符,友员函数重载运算符是用来处理特殊情况的。
重载[]运算符的时候,可以将函数operator[]()的返回类型指定为引用类型,这样函数operator[]()就可以用地赋值运算符的左边了,如:a[2] = 3;
重载运算符的一些限制:重载运算符不能改变运算符的优先级;不能改变运算符所需操作数的个数;运算符函数不能有默认参数,除非是函数调用运算符。
不能重载的运算符有:“.”,“.*”,“::”,“?:”,“#”,“##”
非成员函数重载不能重载的运算符有:“:”,“=”,“()”,“[]”,“->”
32.使用虚方式继承
如果不使用虚方式继承,那么在多重继承的时候会产生歧义。如果两个派生类derived1和derived2继承同一个基类base,然后又有一个类derived3是从derived1和derived2继承的,那么derived3类就会有两个基类base的副本。当在derived3的对象中使用从上面base继承过来的成员变量i,那么这个i可以是derived1从base继承来的,也可以是derived2从base继承来的,这就产生了歧义。
避免这种歧义的产生就是使用虚方式的继承。如class derived1:virtual public base{…}和class derived2:virtual public base{…}这样在class derived3:public derived1,public derived2{…}中就不会产生歧义了。因这虚方式的继承使derived3中只有base的一个副本。
33.访问控制符public,private,protected的使用方法总结
(1)public
当类的成员被声明为public时,这个成员可以被程序的其他部分访问。
在public继承下,基类的所有类型,即public,private,protected在派生类中将保持其本身的特性,不会改变。
(2)private
当类的成员被声明为private时,这个成员只能在定义它的类中使用。就算是在该类的派生类中也不能被访问。
在private继承下,基类的public和protected成员在派生类中都将变成private成员。
(3)protected
当类的成员被声明为protected时,这个成员只能在类的继承层次中被访问,即派生类可以对这个成员进行访问(除非使用private继承改变了protected的属性),但它对类的继承层次外的代码是私有的。
在protected继承下,基类的public和protected成员在派生类中都将成为protected成员。private不变。
如果从基类继承时,有些成员变成了private,但你又想使其中一个或多个函数可以被派生类所访问,怎么办呢?可以使用授权访问的方式。例如:
class base{
public:
int i;
};
class derived:private base{
public:
base::j;//use the private member like this
}
34.const的用法
(1)const int* p;//指针所指向的内容是固定的
(2)int const *p;//同上
(3)int* const p;//指针是个常量
(4)const int* const p;//综合了(1)和(3)
对于(1)(2)和(4)的情况,不能对*p进行赋值,对于(3)和(4)的情况,不能对p进行赋值。
(5)const int foo();//函数返回的值是const类型
(6)int foo() const;//const加在函数后面表示这个函数不能修改类中的成员变量
使用const可以使我们的代码更安全。
35.友员函数的作用(简单说明)
在重载某些特定的运算符时,友员函数很有用。其次,友员函数简化了某些I/O函数的创建
36.C++的动态特点
动态性是面向对象程序语言的三大特点之一。在C++中,动态性分为编译时的动态性和运行时的动态性。函数重载和运算符重载体现了编译时的动态性,而虚函数和派生则体现了运行时的动态性。
还有两个相关的术语:早绑定和晚绑定。这两者对应于编译时的动作和运行时的动作。
早绑定是指函数的调用在编译时就被确定了。也就是说,调用函数需要的所有信息在程序编译的时候就已经确定了。早绑定的示例有:标准函数调用,重载函数调用和运算符重载函数的调用。它的优点是运行速度快,需要的内存少。缺点是缺少灵活性。
晚绑定是指函数的调用是在运行时确定的。晚绑定是使用虚函数和派生类来实现的。它的优点是灵活性大,可以被用来支持公共接口,不同的对象使用该接口来定义它们自己的实现。缺点是执行速度慢。
37.虚函数的作用
为什么需要虚函数?简单来说就是为了实现“一个接口,多个方法”的功能。我们知道虚函数可以被派生类重新定义(注意不是重载哦),如果在一个基类定义了一个虚函数,然后多个类从这个基类派生且各自重定义了基类中的虚函数。那么我们可以定义一个指向基类的指针,通过对这个指针赋以不同类的对象值,就可以使用同一个指针来调用不同的方法(虽然方法名一样,但实现却不一样哦)。
这样,在基类中所定义的虚函数就像是一个接口,派生类只要保持与基类的接口一致,就可以实现自己的方法。不同的派生类可以共享基类中的同一个接口。
对于纯虚函数,它在基类中没有实现,只能被派生类来实现。包含纯虚函数的类又叫抽象类,它不能定义对象,但能定义抽象类的指针接口。
对于一般的小型系统,我们可能体会不到虚函数给我们带来的方便,但是随着开发的系统越来越大,你就会发现虚函数真是个好东西。
38.C++中的异常处理
能捕获基类对象的catch也能捕获派生类的对象,因此若要想catch能捕获正确的数据,就要把catch派生类对象的语句放在catch基类对象的语句前面。
捕获所有的异常:catch(…),这句一般放在所有catch语句的是后面,这样就能除了捕获特定数据类型的异常外还能捕获一些未知类型的异常。
为了限制函数中所抛出的异常,可以这样定义函数:
type foo(type1 bar) throw(type2,type3)
这样,函数foo中就只能抛出type3,type3这两种数据类型的异常。
39.使用typeid
使用typeid可以获得各种数据的类型。需要包含的头文件是<typeinfo>。
使用方法:typeid(expr).name(),函数返回data的类型。
typeid也可以应用于模板类的对象。
40.名字空间相关问题
(1)匿名名字空间
它可以把名字空间的成员限制在一个文件上使用,和static的用法差不多。例如在文件1中有匿名空间namespace{int k;},则k这个变量只能在文件1中使用,在其它文件中就算使用了extern引入k也不能使用k。
(2)在过去的C++标准中,C++函数库是在全局名字空间的,但是在最新的标准中,C++函数被定义在了std名字空间中,这就是为什么有些程序需要using namespace std;后才能使用函数库中的函数。
41.对象成员初始化
如果类中有成员是用const来声明的,那么在类的成员函数中就不能对其进行初始化。但使用成员初始化语法可以通过构造函数对其进行初始化。格式如下:
construtor(arg-list):member1(initializer),member2(initializer),…,memberN(initializer){…}
这样就可以初始化那些用const声明的成员常量啦。
42.关于.*和->*这两个成员指针
这两个并不是真正的指针,它只是成员在对象中的偏移量。看下面的程序理解吧:
class myclass{
public:
int sum;
void sum_it(int x);
};
void myclass::sum_it(int x){
int i;
sum = 0;
for(i = x; i; i--)
sum += i;
}
int main()
{
int myclass:: *dp;//指向myclass中整数类型成员变量的指针
void(myclass::*fp)(int x);//指向myclass中成员函数的指针
myclass c;
dp = &myclass::sum;//获得成员变量的地址
fp = &myclass::sum_it;//获得成员函数的地址
(c.*fp)(7);
cout<< "summation of 7 is "<<c.*dp;
return 0;
}
43.转换函数
如果需要在表达式中将自己创建的类与其它数据类型进行混合运算,可以在类的重载运算符函数中实现,更简单的是使用转换函数。转换函数的形式:operator type(){return value}。
例如可以实现类的对象和整数类型相加。