1.定义在类内部的函数是隐式的inline函数。
2.因为this的目的总是指向“这个”对象,所以this是一个常量指针,我们不允许改变this中保存的地址。
3.常量成员函数:允许把const关键字放在成员函数的参数列表之后,此时紧跟在参数列表后面的const表示this是一个指向常量的指针。因为this是指向常量的指针,所以常量成员函数不能改变调用它的对象的内容。
4.常量对象,以及常量对象的引用或指针都只能调用常量成员函数。
5.编译器分两步处理类:首先编译成员的声明,然后才轮到成员函数体。因此成员函数体可以随意使用类中的其他成员而无须在意这些成员出现的次序。
6.构造函数不能被声明成const,当我们创建类的一个const对象时,直到构造函数完成初始化过程,对象才能真正取得其“常量”属性。因此,构造函数在const对象的构造过程中可以向其写值。
7.当类没有声明任何构造函数时,编译器才会自动的生成默认构造函数,默认构造函数按照如下规则初始化类的数据成员:
如果存在类内的初始值,用它来初始化成员。
否则,默认初始化该成员。
8.我们可以使用default来要求编译器生产构造函数。
class test { public: test() = default; test(int i) {} };
9.使用class和struct定义类唯一的区别就是默认访问权限,class默认是private,struct默认是public。
10.类允许其他类或者函数访问它的非公有成员,方法是另其他类或者函数成为它的友元,友元声明只能出现在类定义的内部,也不受访问控制符限制。
class test { friend int GetTestCount(const test& t); public: test() = default; private: int m_count; }; int GetTestCount(const test& t) { return t.m_count; }
11.尽管当类的定义发生改变时无需更改用户代码,但是使用了该类的源文件必须重新编译。
12.一个可变数据成员永远不会是const,即使它是const对象的成员。因此一个const成员函数可以改变一个可变成员的值。
class test { public: test() = default; void change_count() const { m_count++; } private: mutable int m_count; };
13.一个const成员函数如果以引用的形式返回*this,那么它的返回类型将是常量引用。
class test { public: test() = default; const test& change_count() const { return *this; } };
14.每个类负责控制自己的友元类或者友元函数。
class test { friend class best; private: int m_count; }; class best { public: int get_test_count() { return m_t.m_count; } private: test m_t; };
class test; class best { public: int get_test_count(const test& t); }; class test { friend int best::get_test_count(const test& t); private: int m_count; }; // 注意这个定义必须在test定义之后,否则不知道test类里的细节会报错 int best::get_test_count(const test& t) { return t.m_count; }
15.和其它局部作用域不同,在类中不能重复定义外层作用域中已经定义过的类型名字,即使定义相同。
typedef int my_int; class test { private: typedef int my_int; // 错误,重复定义,但是有的编译器可以编译过 my_int m_count; };
16.成员函数中使用名字时的查找顺序是:成员函数->类->类外作用域。
int count; class test { private: int count; int add_count(int count) { this->count += count; // this->count指的是类的成员,count指的是参数 ::count++; // ::count指的是外层作用域的count } };
17.如果没有在构造函数的初始化列表中初始化成员,则在构造函数体之前执行默认初始化。
18.如果类的成员是const或者引用或者没有默认构造函数的类类型的话,必须在定义时或者初始化列表里完成初始化。
class test { public: test(){}; // 错误,m_const没有初始化 private: const int m_count; };
19.构造函数的初始化列表中成员的出现顺序并不代表初始化的顺序,而是按照在类中的定义顺序来初始化。所以如果一个成员靠另一个成员来初始化的时候要注意。
class test { public: test() :m_base(0) // m_base的值是0 ,m_count(m_base) // m_count的值是未定义的 {}; private: int m_count; int m_base; };
20.如果一个构造函数为所有的参数都定义了默认实参,则这个类实际上定义了默认构造函数。
21.委托构造函数指的是一个构造函数调用其它构造函数来执行自己初始化的过程。
class test { public: test(int i,int j) { } // 非委托构造函数 test(int i) :test(i, 0) {} // 委托构造函数,委托了test(int,int)构造函数 test(int i) :test() {} // 委托构造函数,委托了默认构造函数 };
22.当已有的构造函数中没有完全初始化所有变量的时候会执行默认构造函数。
class test { public: test(int i) {} private: int m_count; }; struct A { test t; }; A a; // 错误,不能为A合成默认构造函数 struct B { B() {} // 错误,t没有初始值 test t; };
class test { public: test(int i) {} }; test t(); // 正确,t是声明的一个函数 test t1; // 错误,test没有默认构造函数
23.如果构造函数之接受一个实参,则它实际上定义了转换为此类类型的转换构造函数,但是这种转换只允许一步类类型转换。
class test { public: test(std::string str) {} }; void get_test(test t){} get_test("123"); // 错误,"123"既不是test类型,也不是string类型 get_test(std::string("123")); // 正确,string类型被转换为test类型
但是其实上述操作完成转换后我们并不能使用它,如果不想这种转换可以用explicit
class test { public: explicit test(std::string str) {} }; void get_test(test t){} get_test(std::string("123")); // 错误,不允许这种隐式转换
24.聚合类必须满足以下条件:
- 所有成员都是public
- 没有定义任何构造函数
- 没有类内初始值
- 没有基类,也没有virtual函数
struct test { std::string name; int count; }; test t = { "test",1 };
25.数据成员都是字面值类型的聚合类是字面值常量类,或者符合下列条件的也是字面值常量类:
- 数据类型都必须是常量类型
- 类必须至少含有一个constexpr构造函数
- 如果一个数据成员含有类内初始值,则内置类型成员的初始值必须是一条常量表达式;或者如果成员属于某种类类型,则初始值必须使用成员自己的constexpr构造函数
- 类必须使用析构函数的默认定义,该成员负责销毁类的对象
因为构造函数没有返回语句,而constexpr函数的唯一可执行语句就是返回语句,可以推断出constexpr构造函数其实是空语句。
constexpr构造函数必须初始化所有数据成员,初始值或者使用constexpr构造函数,或者是一条常量表达式。
26.类的静态成员存在于任何对象之外,对象中部包含任何与静态数据成员有关的数据。类似的,静态成员函数也不与任何对象绑定在一起,也不包含this指针。作为结果,静态成员函数不能声明成const的。
27.一般来说我们不能在类内部初始化静态成员,在类的外部定义静态成员的时候,不能重复static关键字,而且一旦被定义,将一直存在于程序的整个生命周期中。
28.静态成员和普通成员的区别:
class test { test(int i = m_count); // static成员可以做默认实参,普通成员不可以 private: static int m_count; static test m_t; // 正确,静态成员可以是不完全类型 test* m_t1; // 正确,指针可以是不完全类型 test m_t2; // 错误,数据成员必须是完全类型 };