C++里你知道的和不知道的一些东西

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′一起拷贝。

你可能感兴趣的:(C++,优化,exception,Class,float,编译器)