1.成员函数通过一个名为this的额外的隐式参数来访问调用它的对象:当我们调用一个成员函数时,请求用该对象的地址初始化this指针(即谁调用成员函数,成员函数的this指针就指向谁)。
2.this指针总是指向该对象,所有this指针是一个常量指针。
(二) const成员函数: (1)问题所在:默认情况下,this指针是顶层const,则不能将this指针绑定到一个常量对象上,意味着我们不能在一个常量对象上调用普通的成员函数。
(2)解决方法:C++的做法是把const关键字放在成员函数的参数列表后,此时表示this是一个指向常量对象的常量指针,这种成员函数被称为常量成员函数。
(3)const成员函数注意事项:1.const成员函数不会修改类的数据成员;
2.const成员函数的声明和定义都得加上const;
3.常量对象以及常量对象的引用或者指针都只能调用常量成员函数。
1.类可以自定义某种类型在类内的别名,类型成员一样有访问权限(一旦定义了别名,就表示在该类作用域下有该类型)。
2.定义在类内的函数自动声明为内联函数,在类外定义成员函数需要声明inline。
3.一般情况下,const成员函数不能修改成员变量,用mutable关键字修饰成员变量,在const成员函数内就可以修改该成员变量了。
4.this指针指向类对象,对this指针解引用即该对象,可以定义返回this的成员函数;如果定义的返回类型不是引用,返回的就是this的副本;const成员函数如果以引用形式返回this,返回类型需要是一个常量引用。
(四)类的静态成员: (1)声明静态成员:1.在成员的声明之前加上关键字static是的其与该类关联在一起。
2.静态成员变量被所有对象共享,只有一份数据。
3.静态成员函数不含this指针,静态成员函数不能被声明成const的。
4.静态成员也有访问权限。
5.静态成员函数只能访问静态成员变量。
(2)使用类的静态成员:- 通过作用域访问——类名::静态成员。
- 实例化对象访问——对象.静态成员。
1.当在外部定义静态成员时,不能重复使用static关键字,该关键字只出现在类内部的声明语句中。
2.由于静态成员不属于任何一个对象,所以它们不是由构造函数进行初始化的。
二、类的作用域前置知识:
类的定义分为两步处理:
- 首先编译成员的声明。
- 直到类全部可见后才编译函数体。
因为成员函数体直到整个类可见后才会被处理,所以它能使用类中定义的任何名字。
(一)用于类成员声明的名字查找:先在类的作用域中查找,如果没找到,继续查找外层作用域。
typedef double Money;
string bal;
class Account
{
public:
Money balance() { return bal; }
private:
Money bal;
};
当编译器看到balance函数的声明语句时,现在Account类的范围内寻找对Money的声明,并且编译器只考虑Account中使用Money前出现的声明,因为没有找到匹配的成员,编译器会继续在Account的外层作用域中查找,即typedef语句;另一方面,函数体会在类全部可见后才被处理,因此该函数的return语句返回名为bal的成员,而非外层作用域的string对象。
(二)成员定义中的普通块作用域的名字查找:1.首先,在成员函数内查找该名字的声明,并且只有在函数使用之前出现的声明才被考虑。
2.如果在成员函数内没有找到,则在类中继续查找,这时类的所有成员都可以被考虑。
3.如果类中没有该名字的声明,在成员函数定义之前的作用域继续查找。
1.当类没有声明任何构造函数时,编译器会自动地生成默认构造函数。
2.默认构造函数的初始化规则:
·如果存在类内的初始值,用它来初始化成员。
·否则默认初始化该成员。
3.有的时候编译器不能为某些类合成默认的构造函数,例如,类中包含一个其他类类型的成员且这个成员的类型没有默认构造函数,那么编译器将无法初始化该成员。
4.在C++11新标准中,如果我们需要默认的行为,可以在参数列表后面写上
= default来要求编译器生成构造函数。
1.如果没有在构造函数的初始值列表中显示地初始化成员,则该成员将在构造函数体之前执行默认初始化。
2.在类中使用初始值列表对类的成员初始化才是真正的初始化。
3.构造函数初始值列表有时必不可少,如果成员是const或者引用的话,必须初始化;再者,如果成员是其他类且没有提供构造函数,也必须初始化。
4.构造函数初始值列表中的成员函数初始化顺序最好与成员声明的书序保持一致。
(三)委托构造函数:1.C++11中可以使用委托构造函数。一个委托构造函数使用它所属类的其他构造函数执行它自己的初始化过程,或者说它把它自己的一些(或者全部)职责委托给了其他构造函数。
2.一个委托构造函数有一个成员初始值列表和一个函数体,成员初始值列表只有一个唯一的入口——类名本身(即其他构造函数);委托构造函数的参数列表需要与另一个构造函数相匹配;
3.受委托的构造函数执行完后才执行委托构造函数函数体中的语句。
(四)隐式的类类型转换:1.如果构造函数只用一个实参就可以调用,则它实际上定义了转换为此类类型的隐式转换机制。
2.将构造函数声明为explicit可以抑制构造函数的隐式转换。
四、友元- 类可以允许其他类或者函数访问它的非公有成员,方法是令其他类或者函数成为它的友元(friend)。
- 友元关系不存在传递性,每个类负责控制之间的友元类或者友元函数。
1.友元声明只能出现在类的内部但具体位置不限。
2.最好是在类的开始或结束位置集中声明友元。
3.重载的函数是不同的函数,如果一个类想把一组重载函数声明成它的友元,需要对这组函数中的每一个分别声明。
(二)友元类型:1.全局函数做友元。
2.类做友元。
3.类的成员函数做友元。
五、拷贝控制拷贝控制操作:
·拷贝和移动构造函数定义了当用同类型的另一个对象初始化本对象时做什么。
·拷贝和移动赋值运算符定义了将一个对象赋予同类型的另一个对象时做什么。
·析构函数定义了当此类型对象销毁时做什么。
1.合成拷贝构造函数:
-
对于某些类来说,合成拷贝构造函数用来阻止我们拷贝该类类型的对象(如输入输出对象);而一般情况下,合成的拷贝构造函数会将其参数的成员逐个拷贝到正在创建的对象中。
-
对于类类型,使用其拷贝构造函数进行拷贝;对于内置类型则直接拷贝。
2.拷贝初始化:
-
当使用直接初始化时,实际上是要求编译器使用普通的函数匹配来选择与我们提供的参数最匹配的构造函数。
-
当使用拷贝初始化时,实际上是要求编译器将右侧运算对象拷贝到正在创建的对象中,必要时进行类型转换;拷贝初始化是依靠拷贝或者移动构造函数来完成的。
-
拷贝初始化发生的其他情况:
a.将一个对象作为实参传递给一个非引用类型的形参(所以拷贝构造函数的第一个参数必须是引用);
b.从一个返回类型为非引用类型的函数返回一个对象;
c.用花括号列表初始化一个数组中的元素或一个聚合类中的成员。
1.重载赋值运算符:
2.合成拷贝赋值运算符:
- 对于某些类,合成拷贝赋值运算符用来禁止该类型对象的赋值;如果拷贝赋值运算符并非出于此目的,它会将右侧运算对象的每个非static成员赋予左侧运算对象的对应成员。
析构函数释放对象所使用的资源,并销毁对象的非static数据成员。
1.析构函数的工作:
- 在一个析构函数中,首先执行函数体,然后销毁成员,并且成员按照初始化顺序的逆序销毁。
2.析构函数调用时机:
无论何时一个对象被销毁,就会自动调用其析构函数:
a.变量离开其作用域时被销毁;
b.当一个对象被销毁时,其成员被销毁;
c.容器被销毁时,其元素被销毁;
d.对于动态分配的对象,当对指向它的指针应用delete运算符时被销毁;
e.对于临时对象,当创建它的完整表达式结束时被销毁。
3.合成析构函数:
- 当一个类未定义之间的析构函数时,编译器会为它定义一个合成析构函数。
- 对于某些类,合成析构函数被用来阻止该类型的对象被销毁;如果不是这种情况,合成析构函数的函数体就为空。
- 我们可以通过将拷贝控制成员定义为=default来显示地要求编译器生成合成的版本。
1.定义删除的函数:
- C++11新标准下,我们可以通过将拷贝构造函数和拷贝赋值运算符定义为删除的函数来阻止拷贝。
- 在函数的参数列表后面加上=delete即可。
struct NoCopy
{
NoCopy() = default; //使用合成的默认构造函数
NoCopy(const NoCopy&) = delete; //阻止拷贝
NoCopy operator=(const NoCopy&) = delete; //阻止赋值
~NoCopy() = default; //使用合成的析构函数
};
2.析构函数不能是删除的函数:
- 我们不能删除析构函数,否则就无法销毁此类型的对象了。
- 对于一个删除了析构函数的类型,编译器将不允许定义该类型的变量或创建该类的临时对象。
- 对于一个删除了析构函数的类型,我们可以动态分配这种类型的对象,但是不能释放这些对象。
3.合成的拷贝控制成员可能是删除的:
-
对于某些类来说,编译器合成的拷贝控制成员是删除的:
a.如果类的某个成员的析构函数是删除的或者不可访问的(如private),则类的合成析构函数被定义为是删除的。b.如果类的某个成员的拷贝构造函数是删除的或者不可访问的,则类的合成拷贝构造函数被定义为是删除的。如果类的某个成员的析构函数是删除的或者不可访问的,则类合成的拷贝构造函数也被定义为删除的。
c.如果类的某个成员的拷贝赋值运算符是删除的或者不可访问的,或是类有一个const的或引用成员,则类的合成拷贝赋值运算符被定义为删除的。
d.如果类的某个成员的析构函数是删除的或不可访问的,或是类有一个引用成员,它没有类内初始化器,或是类有一个const成员,它没有类内初始化器且其类型未显示定义默认构造函数,则该类的默认构造函数被定义为删除的。
4.private拷贝控制:
- 在新标准发布之前,可以通过将类的拷贝构造函数和拷贝赋值运算符声明为private的来阻止拷贝(可以不定义)。
1.左值持久;右值短暂,右值要么是字面常量,要么是在表达式求值过程中创建的临时对象。
2.我们可以将一个const的左值引用或者一个右值引用绑定到右值上。
3.变量是左值,因此我们不能将一个右值引用直接绑定到一个变量上,即使这个变量是右值引用类型也不行。
4.在头文件utility中有一个move函数,用它可以获得绑定到左值上的右值引用。(调用move即告诉编译器,我们有一个左值,希望像一个右值一样处理它)。
int&& r1 = 42;
int&& r2 = move(r1);
(2)移动构造函数和移动构造赋值运算符:
1.移动构造函数:
- 移动构造函数的第一个参数是该类类型的一个引用。
- 除了完成资源移动,移动构造函数还必须确保移后源对象处于销毁它是无害的状态;一旦资源完成移动,源对象必须不再指向被移动的资源——这些资源的所有权已经归属新创建的对象。
2.移动操作、标准库容器和异常:
- 由于移动操作“窃取”资源,它通常不分配任何资源,因此,移动操作通常不会抛出任何异常。
- 在一个函数的参数列表后指定noexcept表示该函数不会抛出异常;在构造函数中,noexcept出现在参数列表和初始化列表开始的冒号之间。
- 必须在类头文件的声明中和定义中(如果定义在类外的话)都指定noexcept。
3.移动赋值运算符:
- 类似于拷贝赋值运算符,移动赋值运算符必须正确处理自赋值。
4.移后源对象必须可析构。
5.合成的移动操作:



