1,构造、析构与异常
对于构造函数而言:
- 不建议在构造函数中跑出异常;
- 构造函数抛出异常时,析构函数将不会执行,需要手动去释放内存。
对于析构函数而言:
- 析构函数不应该抛出异常;
- 当析构函数中会有一些可能发生异常时,那么就必须要把这种可能发生的异常完全封装在析构函数内部,决不能让它抛出函数之外;
- 析构函数异常相对要复杂一些,存在一种冲突状态,程序将直接崩溃:异常的被称为“栈展开(stack unwinding)”的过程中时,从析构函数抛出异常,C++运行时系统会处于无法决断的境遇,因此C++语言担保,当处于这一点时,会调用 terminate()来杀死进程。因此,当处理另一个异常的过程中时,不要从析构函数抛出异常, 抛出异常时,其子对象将被逆序析构。
2,指针与数组
Q:
main()
{
char*a[]={"work","at","alibaba"};
char**pa=a;
pa++;
printf("%s",*pa);
}
上述程序的执行结果是?
A:
首先,对于编译器而言,是没有数组这一概念的,数组全部都被看做指针, 因此,a[]就是*a,那么a换成了pa,pa即是a,只是一个名字的变化而已。根据pa++,也就是取a[1][]的值,也就是"at"
3,scanf与printf
scanf和printf对数据长度的控制不同:
- scanf不能控制精度只能控制长度,比如%m.nf是不允许的的,但是可以指定%mf,其中m为整数。
- printf可以控制长度也可以控制精度。
4,二维数组的定义
定义二维数组,行数可以省略,但是列数一定需要指定,因为编译器会根据列数来进行寻址。
多维数组也是一样,只有最靠近数组名的那一维大小可以省略。
5,类的隐式转换
- 当A是基类,B是派生类,则此时B可以隐式转换为A,因为向上级类型转换时隐式的,部分元素丢弃可以自动完成。但是,A不能隐式地转换为B,因为向下是显式的,不知道添加的值什么。
6,switch
switch语句中,如果不加break,则成功判断成功之后的语句会忽略之后的case条件判断,一直执行下去,直到break语句或者case语句。
换句话说,switch语句中没有break会执行满足条件的和之后的所有条件。
7,虚函数与内联
虚函数不可以内联,因为虚函数是在运行期间的时候确定调用的函数,而内联函数是在编译期间进行代码展开,两者冲突,因此没有一起使用的做法。
当然,内联只是对编译器的一种请求,是否真正内联是要看编译器的处理,虚函数可以声明为内联,但是编译器一定不会响应内联的请求。
8,字符数组的输入
Q:
定义语句:
int b;
char c[10];
则正确的输入语句为?
A:
- scanf("%d%s", &b, &c);
- scanf("%d%s", &b, &c);
对于一个在栈上分配的数组,且在创建的代码块中进行访问的化,“c”实际上有两种含义:
- 一个指向十个char类型元素的数组;
- 一个char* 类型的指针。
对于第一种而言,对其进行sizeof©操作,得到的结果为10。
那么,何时是第一种,何时是第二种呢?
实际上,第二种是第一种的一种语法糖,是语言设计这为了方便而下放的一个空子。这种语法糖,在二维数组及以上就不成立了。
这就是所谓的上下文语义(编译器的语义)。
- scanf("%s", c); //这里c是一个char* 类型的指针,编译器相信程序员将它指向了一块内存块;
- scanf("%s", &c); //这里c是一个指向十个char元素的数组的指针,这种才是最正统的用法。
9,宏定义与语法检查
宏定义不做语法检查。
预处理是在编译之前的处理,而编译的工作之一就是语法检查,因此预处理并不做语法检查。
只有当调用这个宏定义时才会去检查定义是否正确。
10,拷贝构造函数的调用
拷贝构造函数的调用时机:
- 当用类的一个对象初始化类的另一个对象时;
- 如果函数的形参是类的对象,调用函数时,进行形参和实参的结合;
- 如果函数的返回值是类的对象,函数执行完成返回调用者时;
- 需要产生一个临时类对象时;