this
指针- 在普通成员函数是
type const*
- 遵循初始化规则,意味着不能把
this
绑定到一个常量对象上,因此不能在常量对象上调用普通的成员函数
- 在普通成员函数是
const
修饰成员函数- 即常量成员函数,表示函数不会修改对象的内容,并且
const
成员函数不能调用非const
成员函数。 const
修饰的成员函数使得this
指针的类型变成了const type const*
,这样常量对象将可以调用常量成员函数。const
修饰的成员函数无法通过赋值运算符改变对象的成员内容。因为点运算符的左值右值性是根据成员所属对象的左值右值性来决定的
class T { int a; public: int b(int c) { this->a = c; } int bc(int c) const{ // 报错 this->a = c; } };
- 即常量成员函数,表示函数不会修改对象的内容,并且
- 构造函数
- 默认构造函数,没有参数的构造函数
- 合成的默认构造函数,如果存在类内的初始值则用它来初始化成员,否则执行默认初始化该
- 既生成合成的默认构造函数,也使用其他非默认构造函数。可以使用
default
关键字
class T(){ T()=default; }
- 合成的默认构造函数总是被声明为
inline
的,=default
如果出现在类内部,那么也是inline
的,如果除外在类的外部,那么默认不是inline
的。这个和成员函数的特点是一样的,成员函数在类内也将默认为inline
的 - 部分编译器支持类内初始化(类似于
java
的行为) - 如果不支持类内初始化,那么可以使用构造函数初始值列表
- 构造函数初始值列表,负责为新创建的对象的一个或几个数据成员赋初始值。赋值过程发生在一旦调用构造函数后
class T{ int one; char two; long three; double four=3.14; // one被显示初始化,two被列表初始化,three使用默认初始化,four使用类内初始化 T(int one):one(one),two('1'){} }
- 当具有
const
引用和常量对象的时候,成员必须被初始化,并且随着构造函数体一开始执行初始化就完成了。唯一初始化这样的值的机会只能是初始值列表,否则是不能在构造函数体中执行复制函数的 - 成员初始化顺序与成员在类中定义的顺序一致。而和初始化成员列表中初始的顺序无关。(部分编译器可能会警告和成员声明顺序不一致)
- 委托构造函数(
c++11
)
class Sale { private: int id; string name; public: Sale(int id) :Sale(id, nullptr) {}; Sale(int id, string name) :id(id), name(name) {}; Sale(); }; Sale::Sale() :Sale(0, nullptr) {};
- 拷贝函数
- 发生在初始化变量
- 值方式传递或者返回一个对象(函数)
- 使用赋值运算符发生对象赋值行为,对象不存在时执行销毁操作,局部对象将会在块结束时被销毁
- 访问限定符没有限定要出现多少次,每个限定符的影响力是到下一个限定符或者类的结尾
class
和struct
关键字的默认访问权限不一样,类可以在第一个访问限定符之前定义成员,struct
访问说明符之前的成员是public
,class
则是private
friend
关键字友元- 只能出现在类的内部,无需形参名,友元函数不是类的成员,不受访问限定符的控制级别的约束。有些编译器允许在尚无
friend
函数的声明情况下就调用它,但有些不支持这种行文,所以最好在此类的头文件上加上友元函数的声明 - 友元函数可以定义在类的内部,这样的函数隐式是内联的
- 友元函数可以是其他类的成员函数。并且声明必须具有顺序
// 1. 先声明并且定义友元成员函数所在类,并且不能定义友元成员函数(只能且必须声明) class WindowMgr{ void clear(ScreenIndex); } // 2. 接下来定义要被共享的类包括友元声明 class Screen{ friend void WindowMgr::clear(ScreenIndex); } // 3. 最后才能定义友元函数
- 友元类,友元类的成员函数可以访问此类所有成员
class Screen{ // 友元类 friend class WindowMgr; }
- 友元类不具有传递性
- 友元函数的声明并不是
friend
那句声明的(即友元函数并非真的声明,只是影响权限),必须要在使用的地方进行声明才能调用到这个函数
- 只能出现在类的内部,无需形参名,友元函数不是类的成员,不受访问限定符的控制级别的约束。有些编译器允许在尚无
- 类可以自定义某种类型在类中别名,由类定义得名字和其他成员一样存在访问限制,可以是
public
或者private
中的一种。- 用来定义类型的成员必须先定义后使用,待解释原因
- 可变数据成员
const
成员函数时也希望修改某个数据成员,可以通过在成员声明中加入mutable
关键字做到这一点。
- 返回
this
的成员函数- 普通返回
this
的成员函数
inline Screen &Screen::set(char c){ contents[cursor]=c; return *this; }
- 从
const
返回this
的引用形式那么返回类型将是常量引用,并且也必须是常量引用。因为this
是const type const*
struct MyStruct { public: MyStruct& a() { return *this; } // 这里如果没有const会编译报错 const MyStruct& b() const{ return *this; } };
- 普通返回
- 类的声明
- 前向声明
forward declaration
,在定义之前是不完全类型incomplete type
class MyClass; // 不完全类型可以定义指针 MyClass *a; // 不完全类型可以定义引用 MyClass &b; // 不完全类型可以声明函数 void c(MyClass); // 不完全类型可以声明函数 MyClass d();
- 前向声明
- 类的作用域
- 类的作用域是遇到类名之后才属于类的作用域内,因此类外定义函数的时候也需要限定符来引用类内作用域的东西。具体原因是因为函数的返回类型通常出现在函数体的外面,因为返回类型中使用的名字都位于类的作用域之外,因此返回类型必须指明它是哪个类的成员
// 这事一个函数,返回的是MyClass::index MyClass::index MyClass::test(){ }
- 类的名字查找
name lookup
是直到类全部声明完成之后才编译函数体
typedef double Money; string bal; class Account{ public: // 这里最终返回的是字段bal而不是string定义的bal,原因也是因为类的名字查找是直到类全部声明完成之后才编译函数体 Money balance(){return bal}; private: Money bal; }
- 类中的类型名有特殊的处理。一般来说内层作用域可以重新定义外层作用域中的名字,即使该名字已经在内层作用域中使用过,但是在类中如果成员使用了外层作用域中的某个名字该名字代表一种类型,则类不能在之后重新定义名字。注意只是类型名而不是变量命
typedef double Money; string bal; class Account{ public: Money balance(){return bal}; private: // 错误不能重新定义Money typedef double Money; }
- 成员中定义的普通块作用域的名字查找,成员函数中的查找名字的规则如下
- 首先在成员函数内查找该名字的声明
- 然后如果在成员函数中没有找到则在类中继续查找,这时类的所有成员都可以被考虑
- 类中也没找到则去该成员函数定义之前的作用域内继续查找
int height=0 class Screen{ int height; public int size(int height){ // 此时的height是函数参数的height return height; // 此时的height也是类成员height return Screen::height; // 此时的height也是类成员height return this->height; // 此时的height不是类成员height,而是最外层的全局height return ::height; } }
- 隐式的类类型转换
- 如果构造函数只接收一个实参则它实际上定义了转换成此类类型的隐式转换机制,这种构造函数也被称为转换构造函数
converting constructor
class B { public: B(int a) { cout << "调用构造函数" << endl; } }; void print(B bb) { cout << "调用函数" << endl; } int main() { B bb = static_cast<B>(1); print(static_cast<B>(1)); getchar(); } // 调用构造函数 // 调用构造函数 // 调用函数
- 只允许一步隐式类型转换,不允许多次隐式类型转换
- 抑制构造函数定义的隐式转换。讲构造函数声明为
explicit
即可。只能在类内部声明的时候使用explicit
关键字,在类外部定义时不应重复。并且explicit
构造函数只能用于直接初始化而不能使用拷贝(复制)初始化
- 如果构造函数只接收一个实参则它实际上定义了转换成此类类型的隐式转换机制,这种构造函数也被称为转换构造函数
aggregate class
聚合类
使得用户可以直接访问其成员并且具有特殊的初始化语法形式。具有如下特点的类则是聚合的- 所有成员都是
public
的 - 没有定义任何构造函数
- 没有类内初始值
- 没有基类,也没有
virtual
函数
- 这样的聚合类可以使用花括号括起来的成员初始值列表来初始化。初始值的顺序必须和声明的顺序一致。如果初始值列表个数少于成员的数量,则后面的成员执行值初始化。一定不能超过成员的数量
- 所有成员都是
literal type
字面值常量类- 数据成员都是字面值类型的聚合类是字面值常量类
- 非聚合类但是符合如下要求的也是字面值常量类
- 数据成员都必须是字面值类型
- 类必须至少含有一个
constexpr
构造函数 - 如果一个数据成员含有类内初始值,则内置类型成员的初始值必须是一条常量表达式。或者如果成员属于某种类类型,则初始值必须使用成员自己的
constexpr
构造函数 - 类必须使用析构函数的默认定义,该成员负责销毁类的对象
- 字面值常量类的构造函数可以是
constexpr
函数,此函数必须初始化所有的数据成员。必须使用初始值或者constexpr
构造函数或者是常量表达式来初始化成员 constexpr
构造函数可以声明为=default
或者=delete
- 类的静态成员
- 类的静态成员可以是
public
或者private
的,可以是常量,引用,指针,类类型等 - 静态成员函数不能声明为
const
。在类的外部定义静态成员时,不能重复static
关键字 - 必须在类的外部定义(文件作用域内)和初始化每个静态成员,和其他对象一样,一个静态对象只能定义一次。这点和
java
并不一致
class Test { public: static int num; }; int Test::num = 0;
- 类内初始化。只能适用于
static constexpr
的地方 - 静态成员可以就是本身,而非静态只能是指针或者引用
- 静态成员可以作为默认实参,非静态成员则不行
- 类的静态成员可以是