操作符(operators)
#
# 操作符(operators)
# Item 5:对定制的“类型转换函数”保持警觉, cast function
允许编译器执行隐式类型转换, 坏处大于好处, 所以对于自己定制的类型转换函数,要小心(拷贝构造, 拷贝赋值):
主要是一些内建default对类对象的类型转换, 比如int和short之间类型的默认转换
一些转换函数 在自己想不到的时候, 会偶然被调用;
所以自定义类型转换, 要避免发生隐式类型转换, 它最好只能够非常清晰地从type A到typeB
关键词 explicit。这个特性之所以被导入,就是为了解决隐式类型转换带来的问题。其用法十分直接易懂,只要将 constructors声明为 explicit,编译器就不能因隐式类型转换而调用它们。特别是对于单一参数的构造函数,加上explicit;
单一参数的构造函数, 大概会有两种理解方式, 单一参数, 多参数但是后面的参数有默认值,
class Name{ Name(const string& s ); // 把string转换为Name ... }; class Rational{ Rational(int numerator=0, ind denominator=1); // 把int转换为Rational // 为了让 Rational objects 能够被隐式转换为doubles(这对掺杂有 Rational objects的混合算术运算可能有用) operator double() const; // 把rational转换为const, 在cout<<Rational找不到的时候,可能把这个Rational当作double来输出 ... };
1
2
3
4
5
6
7
8
9
10
11
12愈有经验的 C++程序员愈可能避免使用类型转换操作符
template<class T> class Array{ public: class ArraySize{ // 这个新加入的class就被称为proxy class public: ArraySize(int numElements) : theSize(numElements) {} int size() const { return theSize;} private: int theSize; }; Array(int lowBound, int highBound); // 双参数的ctor, 没法成为类型转换函数 Array(int size); // 本来的语义是构造一个包含size个元素的Array,但是使用()却会造成一个单参数的implicit类型转换 // 使用explicit可以避免implicit的类型转换 如下: explicit Array(int size); Array(ArraySize size); // 使用了上面的proxy class } //如果不是使用下标[], 一不小心使用了(), 就会造成一个类型转换; Array<int> a(10); Array<int> b(10) a == b[10]; // ?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
考虑 Array template的时候。需要一种方法,不仅允许以一个整数作为constructor 自变量来指定数组大小,又要阻止一个整数被隐式转换为一个临时性Array 对象。所以要产生一个新的 class,名为 ArraySize。
类似 ArraySize 这样的 classes,往往被称为proxy classes,因为它的每一个对象都是为了其他对象而存在的, 把 ArraySize 嵌套放进 Array 内,强调一个事实:它永远与 Array搭配使用
template<class T> class Array{ public: class ArraySize{ // 这个新加入的class就被称为proxy class public: ArraySize(int numElements) : theSize(numElements) {} int size() const { return theSize;} private: int theSize; }; Array(int lowBound, int highBound); // 双参数的ctor, 没法成为类型转换函数 Array(ArraySize size); // 使用了上面的proxy class }
1
2
3
4
5
6
7
8
9
10
11
12
13
# Item 6:区别 increment/decrement 操作符的前置(prefix)和后置(postfix)形式
重载的时候, 前置是operator++(),后置是operator++(int), ++C没有临时变量,C++是有的
返回const原值是为了避免连加, 但是却能够把这个值赋给别人: c++++ --> ints 并不允许连续两次使用后置式 increment 操作符
const UPInt UPInt::opreator++(){ // ++a, 没有临时变量, 理论上更高效 *this += 1; return *this; } const UPInt UPInt::opreator++(int){ // a++, 所以后置的时候, 会用到前置的重构在函数 UPInt oldValue = *this; ++(*this); return oldValue; }
1
2
3
4
5
6
7
8
9
10
# Item 7:千万不要重载 &&,|| 和, 操作符
C++允许为“用户定制类型”量身定做&&和||操作符。做法是对operator&&和 operator||两函数进行重载工作。你可以在global scope 或是在每个 class内做这件事儿。然而如果你决定运用这个机会,你必须知道,从此“函数调用 语义”会取代“骤死式 语义”,也就是说将operator&&重载之
if(expr1 && expr2) ... // 上面的语句将会改为一下两种操作之一 if(expr1.operator&&(expr2) ... if(operator&&(expr1, expr2)) ...
1
2
3
4
5重载&& , ||操作符之后, 再调用这些重载函数, 所有的expr都需要已经被计算出来, 但通常我们在用这些操作符的时候会把一些没有计算出来的表达式放在里面去做||, &&操作, 这样就不能构成“骤死式 语义”
C++并未明确定义函数调用动作中各参数的计算顺序,所以没办法知道expr1和expr2哪个会先被计算出来。
# Item 8:了解各种不同意义的 new和 delete
对不同含义的new和delete, 需要充分搞清楚区别和含义;[拓展: 侯捷, 内存管理课程]
// new operator:
string *ps = new string("Memory management");
delete ps; //只有new operator出来的内存才能delete, malloc出来的要经过cast成为对象的指针才能够使用delete;
delete [] ps; // 如果ps是一个数组的话, 需要使用delete []
// operator new: 唯一任务就是分配内存, 返回一个void*类型的指针
void* operator new(size_t size);
void* rawMemory = operator new(sizeof(string)); //可以直接这样使用,像使用malloc一样, 把内存转换成对象是new operator做的事情;
string* ps = static_cast<string*>(rawMemory); // operator new之后的raw内存, 再cast成特定的指针类型
// 使用operator delete: 类似于malloc之后的free
operater delete(rawMemory);
//placement new:返回指向一个Widget object的指针,构造于buffer上, 当运行到shared memory或memory-mapped I/O时候,这种函数也许有用的,等于是结合了new operator和operator new, 构成了一个先operator new然后再隐式用new做了一次static_cast
Widget* constructWidgetInBuffer(void *buffer, int widgetSize){
return new (buffer) Widget(widgetSize); // 这里等于是结合了new operator和operator new, 构成了一个隐式的cast
}
// 指针数组
int* bestPractice = new *int[10];
char* bestPractice[] = {"C++", "Python", "JavaScript"};
// 数组指针
int (*bestPractice)[10];
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22