- 关于this指针
成员函数通过一个名为this的额外的隐式参数来访问调用它的对象。当我们调用一个成员函数时,用请求该函数的对象初始化this。
1 total.isbn(); 2 //等价于编译器重写为 3 Sale_data::isbn(&total);
因为this的目的总是指向“这个”对象,所以this是一个常量指针,我们不允许改变this中保存的地址。
例如this的类型是Sale_data* const。但是this需要遵守初始化规则,所以我们不能把this绑定到一个常量对象。也即:我们不能在一个常量对象上调用普通的成员函数。
为了解决这一问题,引入了常量成员函数,即在成员函数之后加const。
常量对象、常量对象的引用或指针都只能调用常量成员函数。
- 类作用域
类作用域的特点是:编译器分两步来处理:首先编译成员的声明,然后才轮到成员函数体。因此,成员函数体可以随意使用类中的其他成员而无须在意这些成员出现的次序。
类型名要特殊处理。在类中,如果成员使用了外层作用域中的某个名字,而该名字代表一种类型,则类不能在之后重新定义该名字。因此:类型名定义应该出现在类的开始处。
1 typedef double Money; 2 class Account 3 { 4 public: 5 Money balance() { return bal; } 6 private: 7 typedef double Money; //错误:不能重新定义Money 8 Money bal; 9 }; 10 11 12 typedef double Money; 13 class Account 14 { 15 typedef double Money; //可以 16 public: 17 Money balance() { return bal; } 18 private: 19 Money bal; 20 };
类的作者常常需要定义一些辅助函数,比如add、read、print。尽管这些函数定义的操作从概念上来说属于类的接口组成部分,但它们实际上并不属于类的本身。(因为类本身的话,会附加this参数)
1 istream &read(istream &is, Sale_data &item) 2 { 3 ; 4 } 5 ostream &print(ostream &os, const Sale_data &item) 6 { 7 ;//print函数不负责换行,一般来说执行输出任务的函数应该尽量减少对格式的控制,由用户代码确定是否换行。 8 }
- 构造函数
构造函数不能声明为const,当我们创建类的一个const对象时,直到构造函数完成初始化过程,对象才能真正取得“常量”属性。
如果我们的类没有显式地定义构造函数,那么编译器就会为我们隐式地定义一个默认构造函数。
合成的默认构造函数:1)如果存在类内的初始值,用它来初始化成员;2)否则,默认初始化该成员。
对于一个普通的类来说,必须定义它自己的默认构造函数,原因有三
1)编译器只有在发现类不包含任何构造函数的情况下才会替我们生成一个默认构造函数;
2)合成的默认构造函数可能执行错误的操作。回忆之前:在块中定义的内置类型或复合类型的对象被默认初始化,则它们的值将是未定义的。
3)有时候编译器不能为某些类合成默认的构造函数。
=default
我们定义此构造函数的目的仅仅是因为我们既需要其他形式的构造函数,也需要默认的构造函数。
构造函数初始化列表
当某个数据成员被构造函数初始化列表忽略时,它将以与合成默认构造函数相同的方式隐式初始化(在函数体执行之前)。
成员初始化的顺序与它们在类定义中的顺序一致。所以,Best Practice:初始值列表最好与成员声明顺序一致。
C++11委托构造函数
- 类类型转换
我们可以为类定义隐式转换规则。如果构造函数只接受一个实参,则它实际上定义了次类类型的隐式转换机制。切记:只允许一步类类型转换。
使用explicit抑制隐式转换:
explicit只能在类内声明构造函数时使用,在类外部定义时不应该重复。
explicit只能用于直接初始化,不能用于拷贝初始化。
1 Sale_data item1(null_data);//正确 2 Sale_data item2=null_data;//错误
explicit可以显式转换
1 item.combine(Sale_data(null_book)); 2 item.combine(static_cast<Sale_data>(cin))
- 聚合类:可以使用花括号成员初始值列表
所以成员都是public
没有定义构造函数
没有类内初始值
没有基类,没有virtual
- 字面值常量类:
数据类型都是字面值类型的聚合类是字面值常量类,或者符合下述条件的类:
1)数据类型都是字面值类型;
2)至少有一个constexpr构造函数;
3)数据成员:内置类型的初始值必须是常量表达式;类类型初始值必须使用constexpr构造函数;
4)类必须使用默认的析构函数
- 类的静态成员
静态成员函数不与任何对象绑定在一起,即:不能使用this指针,不能声明为const。
当在类外部定义静态成员时,不能重复static关键字;因为不属于对象,所以不是由构造函数中初始化:需要在类的外部定义和初始化每个静态成员。
1 class Account 2 { 3 static double interestRate; 4 }; 5 double Account::interestRate = 0;
当静态成员是const的时候,可以在类内提供初始值。此时,也应该在类外定义一下该成员。
1 class Account 2 { 3 static constexpr double interestRate = 0.8; 4 }; 5 constexpr double Account::interestRate;
静态成员可以是不完全类型;可以做默认实参。非静态成员不能做默认实参,因为它值本身是对象的一部分,所以无法真正提供一个对象以便从中获取成员的值。
- 友元
如果类想把一个函数作为它的友元,只需要增加一条以friend关键字开始的函数声明即可。
友元的声明仅仅是指定了访问权限,而非一个通常意义上的函数声明。如果希望调用某个友元函数,那么必须在友元声明之外再专门对函数进行一次声明。
(许多编译器并未强制限定友元函数必须在使用之前在类的外部声明)
令成员函数为友元
需要仔细组织程序结构以满足声明和定义的彼此依赖关系。
1 class Window_mgr 2 { 3 public: 4 void clear(); 5 6 }; 7 class Screen 8 { 9 //不同于普通函数,之前必须声明 10 friend void Window_mgr::clear(); 11 }; 12 13 //Screen定义后,再定义clear函数,因为要用到Screen 14 void Window_mgr::clear() 15 { 16 Screen s; 17 }
类和非成员函数的声明不是必须在它们的友元声明之前。当一个名字第一次出现在一个友元声明中时,我们隐式地假定该名字在当前作用域中可见。
1 class X 2 { 3 public: 4 friend void f() {/*定义f*/; } 5 X() { f(); }//错误,f还没有声明 6 void g(); 7 }; 8 9 void X::g() { f(); }//错误,f还没有声明 10 void f();//声明那个定义在X中的函数 11 void X::g() { f(); }//正确
类的声明
在声明之后定义之前,类是一个不完全类型。只能在有限情况下使用:
1)可以定义指向这种类型的引用或指针;2)可以声明(但不能定义)以不完全类型作为参数或者返回类型的函数。