this指针
类的成员函数用名为this的额外的隐式参数来访问调用它的那个对象。
this是一个常量指针,我们不能改变this中保存的地址。
默认情况下,this是指向非常量对象的常量指针。这样的this不能绑定到一个常量对象上。自然,常量对象无法调用普通的成员函数。在成员函数的参数列表之后加上const,则此成员变量的this指针就变成指向常量对象的指针,常量对象就可以调用这个成员函数。
也就是说,常量对象,常量对象的引用或指针都只能调用常量成员函数
既可以用this.数据成员来访问对象的数据成员,也可以用*this把对象当成一个整体访问。
类的作用域
类本身就是一个作用域。类中的成员函数可以随意使用类中的其他成员而无须在意这些成员出现的次序。因为,编译器分两步处理类:首先编译类成员的声明,然后编译成员函数体。
定义与类相关的非成员函数
若从概念上来说,这些非成员函数是类的接口的一部分,那么一般应把它们的声明与类的声明放在同一个头文件中。
合成的默认构造函数
如果没有显式定义构造函数,那么编译器提供一个合成的默认构造函数。它按如下规则初始化类的数据成员:
1.如果存在类内初始值,那么用它来初始化成员
2.否则,默认初始化该成员。
由以上规则知,若没有显式定义构造函数,也没有提供类内初始值,而且类中含有内置类型或复合类型(比如数组和指针)的对象,那么这些数据成员对象是未被初始化的。
注意,有的编译器只支持为静态整型数据成员提供类内初始值,此时只能显式定义构造函数初始化类的数据成员
对于一个普通 的类来说,必须定义它自己的默认构造函数
class和struct定义类的唯一区别是默认的访问权限
使用struct关键字定义类,则定义在第一个访问说明符之前的成员是public的
使用class关键字定义类,则定义在第一个访问说明符之前的成员是private的
友元函数和友元类
定义友元函数时,执行以下步骤:
1.在类定义的内部的任何位置( 最好集中放在类定义的开始或者结束前的位置),添加一句以friend开头的函数声明语句。(这里的声明只是影响访问权限,并不是普通意义上的函数声明)
2.在类定义的外部声明函数
将某个类的某个成员函数定义为友元函数时,要明确指出该成员函数属于哪个类
定义友元类
在类定义的内部,添加一句代码:
friend class 友元类名;
类的类型成员
位置:通常放在类开始的地方。因为类型成员必须先定义后使用。
好处:除了能简化类型的表示外,还能隐藏真实的类型
inline成员函数
可以在声明和定义的地方同时说明inline。不过,最好只在类外部定义的时候说明inline,这样使类更容易理解。
inline成员函数应该定义在类定义所在的头文件中。
当提供类内初始值时,必须用符号=或者花括号表示
例,
class Can{
private:
int year=2015;
vector<string> vs{string("jinling")}; //vs是有一个元素的vector对象
};
返回*this的成员函数
让成员函数返回*this,且返回值的类型是引用,那么可以实现链式函数调用。
注意一个const函数若以引用的形式返回*this,那么它的返回值类型是常量引用
基于const的重载
常常根据成员函数是否是const的,对其进行重载。同时将两个函数体内相同的实际操作封装成一个inline的私有常量函数。
类声明和类定义的作用域
类声明的形式如:class Can;
在类的声明之后,定义之前,只能有限地使用该类:可以定义指向这种类型的指针或引用,也可以声明(但是不能定义)以该类型为参数或者返回类型的函数
类内允许包含指向自身类型的引用或指针,但是不能包含自身类型的数据成员(因为在类定义完成之前,编译器不知道该类型的变量需要多少空间)
类的作用域
一个 类就是一个作用域,所以,当我们在类的外部定义成员函数时,必须同时指定类名和函数名。一旦遇到类名,定义的剩余部分就在类的作用域之内了。这里的剩余部分包括参数列表和函数体(所以,使用类中定义的名字时,不需要作用域操作符)。返回类型中使用的名字在类的作用域之外。因此必须指明返回类型是哪个类的成员。
因为类的定义分两步处理,首先编译成员(包括数据成员和成员函数)的声明,其次编译函数体。所以,
成员函数函数体的名字:函数体可使用类中定义的所有名字。
成员函数声明中使用的名字:包括返回类型和参数列表中使用的名字,都必须在使用前可见(该名字的声明出现在类中,且在该成员函数声明之前,或者该名字的声明在类的外层作用域中)。
成员函数定义中的名字的查找顺序:
1.首先在成员函数内查找该名字的声明
2.若1没找到,则在类内查找(类中的所有名字都可以被考虑)
3.若还没找到,则在成员函数定义之前的作用域内继续查找
不要让成员函数的形参名与类的某个数据成员名冲突。因为这时,函数的形参会隐藏类的数据成员。
构造函数的初始化列表
建议使用构造函数的初始化列表初始化数据成员
1.效率不同
使用构造函数的初始化列表,能直接初始化数据成员。效率更高
不使用构造函数的初始化列表,在构造函数的函数体内部对数据成员赋值,实际上执行了两步,1.数据成员在构造函数体之前执行默认初始化2.在构造函数体内对数据成员赋值。
因此,效率较低
2.有些数据成员必须初始化。比如const数据成员,引用数据成员,以及没有默认构造函数的类类型数据成员
数据成员的初始化顺序
与它们在类的定义中出现的顺序一致
委托构造函数
一个构造函数调用它所属类的其他构造函数执行它自己的初始化过程。被调用 的构造函数写在调用者初始化列表的位置。执行这个 构造函数的时候,受委托的构造函数的初始化列表和函数体被依次执行。然后再执行委托构造函数的函数体
什么时候自动执行默认构造函数
1.对象被默认初始化时。默认初始化发生在以下场合:
- 不使用任何初始值定义一个非静态局部变量或数组
- 类使用合成构造函数且类含有类类型的数据成员
- 类类型的数据成员没有在初始化列表中被显示初始化
2.对象被值初始化时。值初始化发生在以下场合:
- 不使用任何初始值定义一个静态局部变量
- 数组初始化时,初始值的数量小于数组大小
- 使用T()显式请求值初始化
隐式的类类型转换(转换构造函数)
如果构造函数只接受一个实参,则它实际上定义了实参类型转换为类类型的隐式转换机制。在需要类类型的地方都可以用构造函数 的参数类型作为替代
抑制构造函数定义的隐式转换
将转构造函数声明前加上explicit,则该构造函数不能 用于隐式类型转换,只能用于直接初始化。即只能写成Can can(实参);的形式
用explicit构造函数显示初始化
在需要类类型的地方写成 Can(实参)或者static_cast<Can>(实参)的形式
string(const char *)不是explicit的。vector<T> v(n)是explicit的
聚合类
必须满足以下条件:
- 所有成员都是public的
- 没有定义任何构造函数
- 没有类内初始值
- 没有基类,也没有virtual函数
字面值常量类
前面说过,constexpr函数的参数和返回值都必须是字面值类型。而前面也说过,算术类型,引用和指针都是字面值类型。这里,介绍另一种字面值类型:字面值常量类
1.数据成员都是字面值类型的聚合类是字面值常量类
2.除了聚合类,若一个类满足以下条件,它也是一个 字面值常量类
- 数据成员都是字面值类型
- 至少含有一个constexpr构造函数
- 若数据成员含有类内初始值,当数据成员是内置类型时,初始值必须是常量表达式;当数据成员是类类型时,初始值必须用成员自己的constexpr构造函数
- 类必须使用析构函数的默认定义
constexpr构造函数
constexpr构造函数是构造函数,所以不能有返回值,同时它是constexpr函数,所以能拥有的唯一可执行语句就是return语句。综合起来,constexpr构造函数的函数体一般来说是空的。
定义形式:
constexpr 类名(形参表):初始化列表{空的函数体} //初始化列表中的初始值必须是常量表达式或者constexpr构造函数
例:
class Animal{public:
constexpr Animal(int l=4,int a=2,bool f=false ):legs(l),age(a),flying(f){}
constexpr getAge(){return age;}
private:
int legs;
int age;
bool flying;
};
用法:constexpr 类名 对象名(实参表); //比一般类对象的定义多加了constexpr关键字
例如 constexpr Animal ani(4,5,false);
用途:用于生成constexpr对象以及constexpr函数的参数或返回类型
注意,constexpr产生的对象的所有成员都是constexpr, 该对象也是constexpr对象了,可用于只使用constexpr的场合。
类的静态成员
静态成员的声明:在成员的声明之前加上关键字static
类的静态数据成员和静态成员函数存在于任何对象之外。特别要指出,静态成员函数不包含this指针,所以它不能显式使用this指针,也不能调用非静态成员(此时隐式使用了this指针)。可以直接调用静态数据成员。静态成员函数也不能声明成const的。
例如,
class Can{
public:
static string& getFac();
private:
static string factory;
int year=2015;
vector<string> vs{string("jinling")}; //vs是有一个元素的vector对象
};
定义类的静态成员
定义类的静态成员函数:与定义类的一般的成员函数形式相同。既可在类内部定义,也可在类外部定义。在类外部定义时,不能重复static关键字。注意该函数体不能访问非静态类数据成员
定义类的静态数据成员:静态数据成员要定义在任何函数之外。形式如下:
数据类型 类名::静态数据成员名=初始值;
string& Can::getFac(){return factory; }
string Can::factory="chongqing puling";
static constexpr 整型类型(int)的数据成员可以在类内初始化,初始值必须是常量表达式。同时,通常在类的外部定义一下该成员,注意定义时,不能再指定初始值。
class Can{
public:
static string& getFac();
private:
static string factory;
static constexpr int year=2015; //static const int year=2015
vector<string> vs{string("jinling")}; //vs是有一个元素的vector对象
};
string& Can::getFac(){return factory;}
使用类的静态成员
1.使用作用域运算符 Can::getFac();
2.虽然静态成员不属于类的某个对象,但是仍可以用类对象,引用和指针来访问静态成员
Can can;
Can *pCan;
string s;
s=can.getFac();
s=pCan->getFac():
同样,若factory是public的,则可用can.factory得到factory的值
静态数据成员能用于特殊场合
静态数据成员的类型可以是它所属的类的类型
class Can{
public:
static string& getFac();
private:
static Can prototype;
static string factory;
static constexpr int year=2015; //static const int year=2015
vector<string> vs{string("jinling")}; //vs是有一个元素的vector对象
};
静态数据成员可以在类的成员函数中做为默认实参,且可以先使用,再声明。
class Can{
public:
static string& getFac();
void f(int y=year){}
private:
static Can prototype;
static string factory;
static constexpr int year=2015; //static const int year=2015
vector<string> vs{string("jinling")}; //vs是有一个元素的vector对象
};