1
1、任何类型都可以当作异常类型。
2、异常对象不是创建在普通的函数堆栈上,而是创建在专用的异常堆栈上,因此才能跨越多个函数。
3、如果一个异常抛出后没有被处理,那么异常处理机制会检测到它并调用标准库函数unexcepted()来处理。unexcepted()函数的默认行为是,调用terminate()结束程序。可以使用另一个标准库函数set_unexcepted()预设一个回调函数,用来处理运行时产生的非预期异常。
4、全局对象在程序开始运行之前构造,如果全局对象的构造函数中抛出异常,将引发terminate()终止程序。全局对象的析构函数在程序结束后才被调用,因此也存在这样的问题。
5、派生类异常对象的捕获要放在其基类异常对象的捕获代码之前,否则派生类异常的处理代码不会有机会执行。
1、为什么C++中纯虚函数声明是这样的:
virtual void func(void) = 0;
为什么纯虚函数所在的类(即抽象类)无法实例化?
因为在语言层面,函数名即为函数地址,声明一个函数后面加上=0的意思是初始化这个函数为零,而初始化一个函数为零意味着函数地址为0,这样就是告诉编译器,不要为这个函数编址,从而阻止抽象类的实例化。另外,在C++中,只有虚函数可以初始化。
1、类型转换的本质是创建一个新的目标对象,并用源对象来初始化。这个过程中,源对象并没有发生变化。
2、一定程度上,可以把带有一个参数的构造函数看成一种类型转换函数。例如:
class Point {
public:
Point(float x);
...
}
Point p1 = 10.5; //调用Point::Point(float)
p1 = 12.3; //先调用Point::Point(float)把12.3转换成一个Point临时对象,然后调用默认的operator ==()拷贝赋值
上面的代码中,构造函数起到了类型转换的作用。可以在构造函数前使用explict关键字,将构造函数声明为显式,以避免这种可能存在的转换。
3、C++有几个类型转换运算符:static_cast、const_cast、reinterpret_cast、dynamic_cast。简述如下:
static_cast<dest_type>(src_obj):相当于常用的强制转换,但在多重继承中,会正确调整指针的值,而强制转换不进行调整。它可以遍历继承树确定src_obj与dest_type的关系,不过这种遍历只在编译时进行,也就是静态的。用它做向下转换可能会有隐患。
const_cast<dest_type>(src_obj):去除src_obj对象的const/volatile属性。
reinterpret_cast<dest_type>(src_obj):可以使用这个运算符将一个整数转换成一个地址,或者在任何两种类型的指针之间互转。这是一种十分危险的转换,慎用!
dynamic_cast<dest_type>(src_obj):在运行时,通过遍历继承树来确定src_obj与dest_type的关系。它可以用来转换指针和引用,但不能转换对象。当目标类型为某种类型的指针(包括void *)时,若转换成功,则返回目标类型的指针,失败则返回NULL;当目标类型为某种类型的引用,若转换成功,返回目标类型的引用,失败则抛出std::bad_cast异常(因为不存在空引用)。dynamic_cast只能用于多态对象,否则会引发编译错误。
1、初始化和赋值是不同的。
初始化:在对象创建时即使用初始值填充对象的内存单元,无数据类型转换等中间过程,不会产生临时对象。
赋值:调用默认的”=”运算符,要转换数据类型,可能产生临时对象。
2、没有参数或者参数全部有默认值的构造函数都称为默认构造函数。编写这样的类会得到一个警告,当实例化这个类的时候,若试图使用默认构造函数,就会得到一个错误:“对重载函数的调用不明确”。
3、strlen函数计算的字符串长度为有效长度,不包括’/0′,而strcpy则连’/0′一起拷贝。
转自:
http://www.huubby6.tk/2010/01/04/1001041907/
本文内容来自林锐的《高质量程序设计指南-c/c++语言》一书。
1、宏定义中,带参数的宏体与各个形参应分别用括号括起来,以避免编译器进行代码替换时出现边际效应。
2、#是构串操作符,只能用来修饰带参数宏的形参,它的作用是将实参的字符序列(不是实参的值)转换为字符串常量。例如:
1 |
#define OUTPUT(word) #word#word |
而下面的两句:
1 |
int abc = 100; |
2 |
cout<<OUTPUT(abc)<<endl; //此时即使abc未定义,也不会出错。 |
输出结果为:abcabc.
3、##是合并操作符,它将左右两边的字符序列合并成一个标识符。注意:合并后形成的标识符必须已定义,否则编译器会报错:“标识符未定义”。例如:
1 |
#define MERGENAME(class) class##name |
而下面这两句
1 |
int abcname = 10; |
2 |
//cout<<MERGENAME(abcname)<<endl; //经友人提醒,此句有误,这样写会导致编译报错,“标识符未定义” |
3 |
cout<<MERGENAME(abc)<<endl; |
输出为:10.
4、可以使用宏代码封装子功能,然后像使用函数一样调用这个宏。使用宏代码的优势在于,编译预处理器用复制代码的方式取代函数调用过程,省去参数压栈、生成汇编CALL指令、返回参数、执行return等过程,从而提高了执行效率。但复制宏代码将导致代码膨胀,而且宏代码无法调试,存在不少缺点。可以使用内联函数替换宏代码,以弥补这些缺点。
1、内联函数与宏一样是进行代码展开的,但内联函数可以调试,可以进行参数、返回值的类型检查和自动类型转换,并且内联函数还有宏无法做到的功能:访问类私有成员。为什么内联函数展开后还能调试?这里的调试不是指展开后的内联函数。内联函数之所以可以调试,是因为在调试(Debug)模式下,内联函数没有进行真正的代码展开;只有在发行(Release)版中,编译器才会真正的内联,进行代码展开。VS2005可以设置内联函数是否展开,在工程属性的配置属性中,C/C++ -> 优化 -> 内联函数展开。
2、C++中的内联函数既具有宏代码的效率,又有类型检查等优点。因此,在C++中,应尽量用内联函数代替宏(除了assert断言宏,原因在于断言要求只在debug版中起效,而release版中不能受影响)。在boost库中可以看到很多内联的全局函数和成员函数。
3、函数内联后,编译器可以通过上下文相关的优化技术对展开的代码进行深入优化。这种优化在普通函数中无法进行,因为一旦进入函数体之后,也就脱离了调用环境的上下文。
4、内联会进行代码展开,造成代码膨胀,仅仅省去了参数压栈,跳转,退栈和返回的操作。若代码逻辑比较复杂,或存在循环等,则不适宜使用内联函数。
另:inline是一种“用于实现的关键字”,而不是一种“用于声明的关键字”。所以,inline必须与函数定义放在一起才能使函数内联,仅放在函数声明中不起作用。(本条未经测试,环境不具备)。
1、任何类型都可以当作异常类型。这样的语句
1 |
try { |
2 |
throw 1; |
3 |
} catch ( int ) { |
4 |
cout<< "exception!" <<endl; |
5 |
} |
也是正确的。
2、异常对象不是创建在普通的函数堆栈上,而是创建在专用的异常堆栈上,因此才能跨越多个函数。
3、如果一个异常抛出后没有被处理,那么异常处理机制会检测到它并调用标准库函数unexcepted()来处理。unexcepted()函数的默认行为是,调用terminate()结束程序。可以使用另一个标准库函数set_unexcepted()预设一个回调函数,用来处理运行时产生的非预期异常。
4、全局对象在程序开始运行之前构造,如果全局对象的构造函数中抛出异常,将引发terminate()终止程序。全局对象的析构函数在程序结束后才被调用,因此也存在这样的问题。
5、派生类异常对象的捕获要放在其基类异常对象的捕获代码之前,否则派生类异常的处理代码不会有机会执行。
1、为什么C++中纯虚函数声明是这样的:
1 |
virtual void func( void ) = 0; |
为什么纯虚函数所在的类(即抽象类)无法实例化?
因为在语言层面,函数名即为函数地址,声明一个函数后面加上=0的意思是初始化这个函数为零,而初始化一个函数为零意味着函数地址为0,这样就是告诉编译器,不要为这个函数编址,从而阻止抽象类的实例化。另外,在C++中,只有虚函数可以初始化。
1、类型转换的本质是创建一个新的目标对象,并用源对象来初始化。这个过程中,源对象并没有发生变化。
2、一定程度上,可以把带有一个参数的构造函数看成一种类型转换函数。例如:
1 |
class Point { |
2 |
public : |
3 |
Point( float x); |
4 |
... |
5 |
} |
6 |
Point p1 = 10.5; //调用Point::Point(float) |
7 |
p1 = 12.3; //先调用Point::Point(float)把12.3转换成一个Point临时对象,然后调用默认的operator ==()拷贝赋值 |
上面的代码中,构造函数起到了类型转换的作用。可以在构造函数前使用explict关键字,将构造函数声明为显式,以避免这种可能存在的转换。
3、C++有几个类型转换运算符:static_cast、const_cast、reinterpret_cast、dynamic_cast。简述如下:
static_cast<dest_type>(src_obj):相当于常用的强制转换,但在多重继承中,会正确调整指针的值,而强制转换不进行调整。它可以遍历继承树确定src_obj与dest_type的关系,不过这种遍历只在编译时进行,也就是静态的。用它做向下转换可能会有隐患。
const_cast<dest_type>(src_obj):去除src_obj对象的const/volatile属性。
reinterpret_cast<dest_type>(src_obj):可以使用这个运算符将一个整数转换成一个地址,或者在任何两种类型的指针之间互转。这是一种十分危险的转换,慎用!
dynamic_cast<dest_type>(src_obj):在运行时,通过遍历继承树来确定src_obj与dest_type的关系。它可以用来转换指针和引用,但不能转换对象。当目标类型为某种类型的指针(包括void *)时,若转换成功,则返回目标类型的指针,失败则返回NULL;当目标类型为某种类型的引用,若转换成功,返回目标类型的引用,失败则抛出std::bad_cast异常(因为不存在空引用)。dynamic_cast只能用于多态对象,否则会引发编译错误。
1、初始化和赋值是不同的。
初始化:在对象创建时即使用初始值填充对象的内存单元,无数据类型转换等中间过程,不会产生临时对象。
赋值:调用默认的”=”运算符,要转换数据类型,可能产生临时对象。
2、没有参数或者参数全部有默认值的构造函数都称为默认构造函数。编写这样的类会得到一个警告,当实例化这个类的时候,若试图使用默认构造函数,就会得到一个错误:“对重载函数的调用不明确”。
3、strlen函数计算的字符串长度为有效长度,不包括’/0′,而strcpy则连’/0′一起拷贝。