1. 类的定义和声明
一旦类定义完成后,就没有任何方式可以增加成员了。
在类内部定义的成员函数(函数实现体在类中),则会认为是inline成员函数(该函数被调用时直接换成函数体)
- 不完全类型:声明了该类,在定义该类之前,成为不完全类型:
- 只有当类定义体完成之后才能定义类,所以类不能具有自身类型的数据成员(因为该类定义还没有完成,不知道存储该类的对象所需的空间,无法完成初始化)
- 但类数据成员可以是指向自身类型的指针或引用,因为指针或者引用仅仅是一个地址空间或者别名,所以分配空间与该类无关,所以可以有。
- 类的定义必须以分号结束,因为类的定义之后可以接受对象定义列表。
- mutable关键字: 数据成员是可变的(在const成员函数内也可变)。
2. this指针
this指针和调用成员函数的对象绑定在一起;
this指针是一个const指针,即指针本身不能改变,但是指向的内容可以改变(易理解,this和对象关联,对象本身在函数调用过程中不能改变,但是对象中的数据成员可以改变)
- const 成员函数不能改变其所操作的对象的数据成员,是只读函数。并且必须在声明和定义中均出现,不然会有编译错误。
- 由上知,const成员函数既不能改变this所指内容,也不能改变this地址。
3. 类作用域
在类作用域外(即分号结束),定义类的数据成员和函数定义均需要指明属于哪个类(完全限定名)。
- 形参和函数体中可以不指明类直接用类的数据成员,因为函数名字已经指明了属于哪一个类;
- 函数返回类型在函数名之前,在遇到函数返回类型的时候不知道属于哪个类,所以如果函数返回类型用到了类中定义的某一个类型,则需要指明完全限定名。
类定义分为两个阶段:
- 编译成员声明;
- 只有在所有成员出现之后,编译它们的定义本身。
名字查找
- 在改名字的块(函数作用域)中查找;
- 在所属类成员(类作用域)的声明;
- 在外围作用域查找(如全局变量)等等。
4. 构造函数
构造函数不能声明为const类型, 因为const函数除mutable数据成员外其他数据成员是不可改变的,而构造函数就是给成员函数赋值的,两者相悖,所以error。
构造函数过程:
- 初始化阶段(初始化列表),也就是为各个数据成员分配相应的空间;
- 普通计算阶段(函数体中的操作,如给数据成员赋值等)。
由上可知,类数据成员初始化化完成之后才会进入构造函数函数体中。
必须使用初始化列表的情况:
- 任何const成员数据,因为const变量一旦确定不能改变,则必须在初始化时确定,在普通计算阶段(构造函数体内)进行赋值操作时不可以的;
- 引用类型成员, 因为引用是别名,则定义引用时必须初始化,在函数体中直接赋值时该引用必须是已经定义过得,所以引用也必须在初始化列表中;
- 没有默认构造函数的类类型的成员,如果没有为类成员提供初始化式,则编译器将会调用。
数据成员在初始化次序与其声明顺序有关,与在初始化列表中的顺序无关。
如果类包含内置或复合类型的成员,则该类应该定义自己的构造函数来初始化这些成员。
- 抑制构造函数的隐式转化,使用explicit关键字
class A{
A(string str);
bool isSame(A&);
}
string str = "str";
A a = new A("aaa");
a.isSame(str);//会隐式的将string类型转化为A对象,进行判断。但是这个往往会造成误区,所以用explicit关键字来说明参数不接受隐式转化, explicit bool isSame(A&)
5. 友员
为了实现一个类使用另一个类的私有成员。
例子:
class A{
friend class B; //B可以用A中的私有成员和函数,但B不是A的数据成员
friend int C::get(); // C的get方法可以用A中的私有成员和函数,但此函数不是A的成员函数
}
- 必须先定义包含成员函数的类,才能将成员类或者函数定义为友元。即必须定义A,才能定义B和C。因为B和C中依赖A中定义的成员。
- 但是它们的声明没有限制。
6. static类成员
- 与类有关,与类对象无关;
优点:
- 在类作用域中,避免与其他变量名冲突;
- 封装,只与该类有关;
- 可以用于存储和类对象无关的成员,意图明确。如Thread类中的sleep()函数。
没有this指针
- const static成员,可以在类的定义体重初始化,但是仍需在类外进行定义。普通成员不行。
- static数据成员的类型可以是该成员所属的类型,因为static成员独立于任何对象而存在,不是类类型对象的组成部分。(非static必须是指针或引用)
class A{
static A a; //ok
}