1、成员函数末尾带const : 不会修改该对象里任何成员变量的值
void noone() const {
Hour += 10; //错误,常量成员函数不可以修改稿成员变量的值
}
2、mutable 修饰符来修饰一个成员变量,这个成员变量处于可变状态,即使是以const 结尾的成员函数中也能修改。
mutable int Hour;
void noone() const {
Hour += 10; //可以修改成员变量Hour了。
}
3、static 修饰的成员变量(静态成员变量),在类中声明但没有定义,未分配内存,需要正在cpp源文件头定义来保证任何调用函数之前这个静态变量已经被初始化
// .h 文件中
public:
static int mystatic; //声明静态成员变量但没有定义
static void mystaticfunc(int testvalues); // 声明静态成员函数
// .cpp文件中
int Time::mystatic = 5; // 可以不给初始值,默认为0,定义时不要加static
4、= default : 编译器自动生成函数体,等价于 {}, 只适用于特殊的构造函数。 = delete :显式的禁用某个函数
5、拷贝构造函数:如果一个类的构造函数的第一个参数是所属的类类型引用,若有额外的参数,那么这些额外的参数都有默认值,如果定义了自己的拷贝构造函数,那么就取代了编译器合成的拷贝构造函数,这个时候就必须在自己的拷贝构造函数中给类中成员逐个赋值,第一个参数带const,不带explicit修饰。
Time myTime; //这会调用默认构造函数(不带参的)
Time myTime2 = myTime; //调用了拷贝构造函数
Time myTime3 ( myTime); //调用了拷贝构造函数
Time myTime4 { myrine }; //调用了拷贝构造函数
Time myTime5 = ( myTime}; //调用了拷贝构造函数
Time myTime6; //这会调用默认构造函数(不带参的)
myTime6 = myTime5; //没有调用拷贝构造函数
// 拷贝构造函数
Time::Time(const Time& timem, int a)
6、函数遮蔽:子类中如果有一个同名函数,那么父类中不管有几个同名函数,子类中都无法访问,如果我们确实想调用父类中的同名函数,有如下方法:
// (1)在子类的成员函数中用“父类::函数名”强制调用父类函数
// (2) 使用using,通过using让父类的同名函数在子类中可见
public:
using Human::sameNameFunc: // 只能指定函数名
7、基类中某个函数声明为虚函数,则所有子类中它都是虚函数。父类指针指向子类对象,调用子类同名同参函数,则需要把父类这个函数声明为虚函数(virtual)
Human *human = new Men;
human->eat(); //如果父类eat声明为virtual,则调用的是子类Men的eat函数
human->Human::eat(); // 如果函数前加了父类命名空间,则直接调用父类中的eat,即使父类声明为virtual。
8、为了避免在子类中写错虚函数,在C++11中,可以在子类函数声明中加override(虚函数专用),子类覆盖父类的同名同参函数,编译器进行纠错。
9、final 也是虚函数专用,用在父类中,如果我们在父类的函数声明中加了final,那么任何尝试子类覆盖父类的该函数都会失败
10、纯虚函数:在基类中声明的纯虚函数,要求子类中去实现。一旦一个类中有纯虚函数,就不能生成这个类的对象了,这个类成为了“抽象类”
// 1)抽象类不能生成对象,统一管理子类对象
// 2)子类中必须要实现该基类中定义的纯虚函数
virtual void eat() = 0; // 没有函数体,只有声明
11、当定义一个子类对象时,先执行的是父类的构造函数体,再行子类构造函数体,当对象被系统回收时,先执行子类的析构函数,再执行父类的函数体。
12、用基类指针new子类对象,在delete的时候系统不会调用子类的析构函数,需要把父类的析构函数声明为虚函数才行 - 虚析构函数
Human *human = new Men;
delete human; // 没有执行子类的析构函数
// 修改父类析构函数:
virtual ~ Human();
// 1)如果一个类想要做父类,务必要把这个类的析构写成virtual
// 2)普通类可以不写析构函数,但如果是基类,就必须写一个析构函数,必须是虚析构函数
// 3)虚函数会增加内存开销,类里面定义虚函数,编译器就会给这个类增加虚函数表,这个表中存放虚函数地址等信息
13、每个类都负责控制自己的友元类和友元函数,友元类关系的判断,最終还是要看类定义中有没有对应的friend 类声明:
(1)友元关系是不能被子类继承的。
(2)友元关系是单向的,比如类B是类A 的友元类,但这并不表示类 A是类B的友元类。
(3)友元关系也没有传递性,例如类B是类 A的友元类,类C是类 B的友元类,这并不代表类 C是类 A的友元类。
Class A
{
friend class B; // 不需要public、protected、private 修饰
private:
int data;
};
14、RTTI(Run Time Type identification):运行时类型识别
(1)dynamic_cast: 能够将父类指针或引用安全的转换为子类的指针或引用,帮助开发者做安全检查
Human *phuman = new Men;
Men *pman = dynamic_cast<Men *>(phuman);
if (pman != nullptr) {
cout<<"转换成功"<<endl;
} else {
cout<<"转换失败"<<endl;
}
// 对于引用,如果转换失败,则系统会抛出std::bad_cast异常,try...catch(){}
Human *phuman = new Men;
Human &q = *phuman;
try {
Men &pman = dynamic_cast<Men &>(q);
cout<<"转换成功"<<endl;
} catch(std::bad_cast) {
cout<<"转换失败"<<endl;
}
(2)typeid:返回指针或引用所指对象的实际类型,例如typeid(指针或引用),typeid(表达式),返回一个常量对象的引用(标准库类型(type_info))
Human *phuman = new Men;
const std::type_info& tp = typeid(*phuman);
cout<<tp.name()<<endl; // 父类有虚函数则结果为Class Men,否者为class Human
(3)要想RTTI两个运算符正常工作,那么基类中必须要有一个虚函数
// c++中,如果类里含有虚函数。编译器就会对该类产生一个虚函数表。
// 虚函数表里有很多项,每一项都是一个指针。每个指针指向的是这个类里的各个虚函数的入口地址。
// 虚函数表项里,第一个表项很特殊(有些编译器虚函数表第一项之前的内存位置存放指针,指向这个类所关联的type_info对彖),它指向的不是虚函数的入口地址,它指向的实际上是这个类所关联的type_info对彖
Human *phuman = new Men:
const std::type_info& tp = typeid(*phuman);
//phuman对象里有一个我们看不见的指针,这个指针指向的是这个对象所在的类Men里的虚函数表。
15、左值引用,一般不能绑定右值,只能绑定到左值上,const引用可以绑定到右值,const引用特殊。
char *p = nullptr ; // 指针有空指针说法,引用没有空引用说法,必须初始化,一般绑定左值。
int a = 1;
int &b{ a } //b 绑定到a,当然可以
int &c; //不可以,引用必须要初始化
int &c = 1; //不可以,c要绑定到左值上,不能绑定到右值上
const int &c = 1; //可以,const 引用可以绑定到右值上,所以const 引用可以说比较特殊
// 上面这段代码最后一行等价于下面这两行:
int tempvalue = 1;
const int &c = tempvalue //可以把 tempvalve 看成一个临时变量
16、右值引用,绑定到右值的引用,用“&&”,希望用右值引用来绑定到即将销毁的或是一些临时的对象上。C++ 11 引入目的是提高程序运行效率,将拷贝对象变成移动对象。
int a = 10;
int &c = 1; //不可以,c要绑定到左值上,不能绑定到右值上
int &&c = 1; //可以
int &&c = a; //不可以,不能将右值引用绑定到左值上
const int &c = 1; //可以
17、++i : 左值表达式, i++ : 右值表达式
++i 是直接给 i 变量加 1,然后返回i本身,因为i是变量,所以可以被赋值,因此是左值表达式。看看如下范例:
int i = 5;
(++i) = 20;
i++先产生一个临时变量来保存i的值用于使用目的,再给 i加 1,接着返回临时变量,之后系统再释放这个临时变量,临时变量被释放掉了,不能再被赋值,因此是右值表达式。看看如下范例:
int i = 5;
(i++) = 20; //语法错误,提示:表达式必须是可修改的左值
18、std::move : 把一个左值 强制 转换成右值
int i= 10;
int &&r = i; // 错误,不能将右值引用绑定到左值上
int &&r = std::move(i); // 正确
19、临时对象产生:
CTempValue sum;
sum = 1000; // 产生了一个临时对象
CTempValue sum = 1000; // 不会产生临时对象,为sum对象预留了空间,用1000构造sum对象,而且是直接构造在sum对象预留空间里。
20、移动构造函数:声明和实现习惯性加noexcept通知编译器该移动构造函数不抛出任何异常提高编译效率
A(A&& tempa) noexcept : m_pb(tempa.m_pb) {
tempa.m_pb = nullptr;
std::cout<< "移动构造函数执行" << std::endl;
}
21、虚继承离不开虚基类,虚基类的特点就是无论这个类在继承体系中出现多少次,派生类中都只会包含唯一一个共享的该类子对象
class A : virtual public B {...}
22、显式类型转换运算符
explicit operator int() const {
return m_i;
}