面向对象
特性
C++面向对象的三大特性:封装、继承和多态。
封装:将数据和方法写进类中,避免外界干扰和保护数据,使得代码模块化。
继承:一个类(派生类)继承另一个类(基类)的所有属性和方法,同时它还可以有另一个类不具备的方法和属性(可拓展)。
多态:C++多态性是通过虚函数来实现的,虚函数允许子类重新定义成员函数(覆盖或者称为重写)。
构造函数
构造函数:在对象定义时调用。构造函数与类同名,不具有返回值。
如果类中没有定义构造函数,编译器将自动生成默认构造函数;如果自定义了构造函数,编译器将不再自动生成默认构造函数,但可以根据需要自行定义。
初始化列表:在构造函数函数名后可以使用初始化列表对类成员数据进行初始化,初始化顺序由声明顺序决定,与列表中的顺序无关。初始化列表只能使用在构造函数中。
class className { int A; double B; // 默认构造函数 className(){ } // 使用初始化列表 className():A(0), B(0.0) { } }
拷贝构造函数:构造函数的参数是自身类型的引用,且额外参数都有默认值,实现对象的复制。
涉及到指针类型的变量复制时,存在两种拷贝方式:深拷贝和浅拷贝
深拷贝:将指针所包含的内存空间中的内容也进行拷贝
浅拷贝:仅将指针的值(也就是所指内存空间的首地址)拷贝
C++提供的默认拷贝构造函数和赋值都是浅拷贝操作,涉及指针操作时进行重写,避免产生空指针异常。
移动构造函数:与拷贝构造函数相似,区别在于第一个参数是右值引用。移动构造函数不分配任何新内存,它接管给定的对象中的内存。在接管内存后,它将给定对象中的指针都置为nullptr。移动源对象会被销毁,意味着将在其上运行析构函数。
class className { int A; double B; // 拷贝构造函数 className(const className& c); // 移动构造函数 className(className&& c); }
析构函数
析构函数:对象被销毁时自动被调用,先执行函数体,然后销毁成员,成员按初始化顺序的逆序进行销毁。析构函数不接受参数,一个类有唯一一个析构函数。
class className { ... // 析构函数 ~className(); }
赋值
拷贝赋值运算符:对类对象进行赋值操作,将一个对象的值复制给另一个存在的对象。
如果类未定义自己的拷贝赋值运算符,编译器会生成一个。
移动赋值运算符:参数为该类类型的右值引用,将给定对象的值复制给另一个存在的对象。与移动构造函数相同,新对象接管给定对象的内存,移动源对象会被销毁。
class className { ... // 赋值运算符 className& operator=(const className&); // 赋值运算符 className& operator=(className&&); }
运算符重载
重载的运算符是带有特殊名称的函数,函数名是由关键字 operator 和其后要重载的运算符符号构成的。与其他函数一样,重载运算符有一个返回类型和一个参数列表。
class className { ... // 赋值运算符就是一个重载 className& operator=(const className&); }
虚函数
使用关键字virtual声明虚函数。虚函数是实现多态的关键,派生类可以重写(覆盖)基类的虚函数。
重写条件:派生类的函数名称与基类的虚函数名称相同,参数列表也要相同,返回值也相同(若基类的函数返回基类类类型的指针或 者引用,派生类可以返回相应的派生类型)
虚函数指针:指向虚函数的指针,具体是在派生类里的实现,当派生类调用虚函数的时候,实际上是通过调用该虚函数指针从而找到接口。虚函数指针对外部是完全不可见。
虚函数列表:每个拥有虚函数的类的虚函数指针按照一定的顺序排列起来构成列表。派生类重写的虚函数会以新的虚函数指针覆盖旧指针,派生类新声明的虚函数会跟在表后。多重继承会以继承顺序排列循函数指针。
静态联编与动态联编
将源代码中的函数名解释为执行特定的函数代码块称为联编。在编译过程中完成的联编称为静态联编(早期联编)。虚函数需要在程序运行时才能正确地选择虚函数代码,称为动态联编(晚期联编)。
继承
创建一个类时,可以指定新建的类继承了一个已有的类的数据成员和成员函数。已有的类称为基类,新建的类称为派生类。
派生类不继承基类的构造函数、析构函数、拷贝构造函数、重载运算符和友元函数。
继承允许我们依据另一个类来定义一个类,这使得创建和维护一个应用程序变得更容易,也达到了重用代码功能和提高执行效率的效果。
继承中可以使用基类的指针(引用)来指向(引用)派生类的对象,反之不行。可以将派生类的指针或引用转换为基类的指针或引用。
为了能够使用基类的指针指向派生类的对象,并通过基类指针销毁派生类对象,可以在基类中使用虚析构函数,此后通过基类指针delete派生类对象时,会调用派生类的析构函数,再调用基类的析构函数。
class A { ... virtual ~A(); } class B : public A { ... ~B(); }
访问控制
public | protected | private | |
---|---|---|---|
基类本身 | T | T | T |
派生类 | T | T | F |
外部 | T | F | F |
继承类型
继承类型有公有继承(public)、保护继承(protected)和私有继承(private),通常使用公有继承。
成员\继承方法 | public | protected | private |
---|---|---|---|
public | public | public | private |
protected | protected | public | private |
private | X | X | X |
抽象基类
纯虚函数:通过在虚函数声明处分号之前添加=0将虚函数定义为纯虚函数,无需提供函数的定义。
含有或未经覆盖直接继承纯虚函数的类称为抽象基类,抽象基类负责定义接口,故抽象基类不能被实例化,使用它来创建派生类,在派生类中对纯虚函数进行重写。
// 抽象基类 class A { ... virtual void method() = 0; } class B : class A { virtual void method() { ... } }