MoreEffectiveC++笔记 3基础议题
1指针和引用的区别
- 指针可以指向空值,引用必须绑定到另一个变量。
int main(){
int *p = NULL;
int &ref = *p;//尝试设置一个绑定到空的引用
cout<<ref;//g++下编译通过 运行时提示段错误
return 0;
}
- 引用必须被初始化,但是指针未初始化是合法的。
int main(){
int &a;//error: ‘a’ declared as reference but not initialized
int b = 1;
int &c = b;
int *p;
return 0;
}
- 引用永远绑定在一个变量上,指针可以重新指向新的对象。
int main(){
int a = 1,b = 2,c = 3;
int &ra = a;
int *pb = &b;
ra = c;//更改绑定的数值
pb = &c;//指向新的位置 不更改原先的值
cout<<a;//3
cout<<b;//2
return 0;
}
总结:当指向可能为空或者需要更改指向的对象的时候适合使用指针,此外当我们需要重载一些运算符比如 [ ] 的时候应当返回引用。
2尽量使用C++风格转换
C++ cast功能包括const_cast、static_cast、dynamic_cast、reinterpret_cast。
C传统转换风格写法为 (type)expression。
- C转换风格不对去除const以及基类转换为子类的转换进行语法区分;C类型转换难以在程序语句中进行识别。
- static_cast在功能上与C风格是一样的含义,但包含限制(不能把结构体转化为int或者把double转化为指针,也不能去除const属性)
- const_cast用于去除表达式的const或者volatileness属性,此外的转换都会被拒绝。
- dynamic_cast用于安全的从父类指针引用转换为子类指针引用,当指针转换失败返回空指针,当引用转换失败抛出异常。除了继承关系外的转换会被拒绝。
- reintrepret_cast最普通的用处是对函数指针类型进行转换。
3不用对数组使用多态
当我们定义一个子类数组传入到父类形参的函数体中进行遍历时,使用下标读取数组对象时,编译器会根据size决定指针跳跃多少字节来访问下个对象。
class Parent{
public:
int i = 1;
};
class Child:public Parent{ //子类多一个整形
public:
int j = 2;
};
void foo(Parent pars[],int num){//使用父类形参以求多态
for(int i = 0;i<num;i++){
cout<<pars[i].i<<" ";//输出结果1 2 1 2 1 2 1 2 1 2
}
}
int main(){
Child childs[10];
foo(childs,10);
return 0;
}
例子中的父类包含一个整形,子类除了继承的i之外自身额外定义了j,这样导致子类大小恰好时父类的两倍,编译器在函数中进行遍历时会按父类的size进行下标遍历,导致无法预期的错误。
- 经过在g++测试,假如父类中定义了虚函数,上文的例子将会正常输出10个1
4避免无用的缺省构造函数
如果定义了带有参数的构造函数,缺省的无参数构造函数就变为无法调用。首先这会带来一些限制,此时我们只能定义栈上的数组。
class A{
public:
int i;
A(int i){
this->i = i;
}
};
int main(){
A arr[] = {
A(1),A(2),A(3)
};//如果不用vector 唯一定义数组的方法。
return 0;
}
如果非要建在堆上,那必须定义一个指针的数组,逐个new新的对象。这样我们必须逐个释放堆上空间,而且指针也带来了内存的浪费。或者考虑用placement new技术在一个先分配好的空间里,但是大家都不熟悉。
缺少缺省函数虽然产生了上述限制,但是相应的能够提高软件整体的可靠性,这将确保所有对象是由定义好的构造函数完整正确的构造得到。