1、权限
1.1 权限修饰符
三种权限,一共对应九种场景。要做到心中有表,遇到任何一种场景都能直接反映出是否能访问。
|-----------|----|------|----|
| | 类内 | 派生类中 | 全局 |
| private | √ | × | × |
| protected | √ | √ | × |
| public | √ | √ | √ |
#include <iostream>`
`using` `namespace std;`
`class` `Base`
`{`
`protected:`
`//类内 派生类内访问,`
` string s =` `"保护权限";`
`public:`
`Base()`
`{`
` cout << s << endl;`
`}`
`};`
`class` `Son:public Base`
`{`
`public:`
`Son()`
`{`
` cout << s << endl;`
`}`
`};`
`int` `main()`
`{`
`// Base b1;`
` Son s1;`
`// cout << s1.s << endl; // 错误 s是保护权限`
`return` `0;`
`}`
`
1.2 不同权限的继承
1.2.1 公有继承 public
上面的代码中一直使用的就是公有继承,公有继承也是使用的最多的一种继承方式。
在共有继承当中,派生类可以继承基类的成员,但是不可访问基类的私有成员,基类的公有成员与保护成员在派生类中权限不变。
#include <iostream>`
`using` `namespace std;`
`class` `Base`
`{`
`private:`
` string str1 =` `"私有成员";`
`protected:`
` string str2 =` `"保护成员";`
`public:`
` string str3 =` `"公有成员";`
`};`
`class` `Son:public Base`
`{`
`public:`
`Son()`
`{`
`// cout << str1 << endl; // 错误 str1为私有成员`
` cout << str2 << endl;`
` cout << str3 << endl;`
`}`
`};`
`int` `main()`
`{`
` Son s1;`
`return` `0;`
`}`
`
1.2.2 保护继承 protected
在保护继承中,派生类可以继承基类的成员,不可访问基类的私有成员,基类的公有成员与保护成员在派生类中的权限都是保护权限。(只能在基类与派生类中访问,外部无法访问)。
#include <iostream>`
`using` `namespace std;`
`class` `Base`
`{`
`private:`
` string str1 =` `"私有成员";`
`protected:`
` string str2 =` `"保护成员";`
`public:`
` string str3 =` `"公有成员";`
`};`
`// 保护继承`
`class` `Son:protected Base`
`{`
`public:`
`Son()`
`{`
`// cout << str1 << endl; // 错误 str1为私有成员`
` cout << str2 << endl;`
` cout << str3 << endl;`
`}`
`};`
`int` `main()`
`{`
` Son s1;`
`// s1.str3; //错误,str3在保护继承的派生类中为保护成员,外部无法访问`
`return` `0;`
`}`
`
1.2.3 私有继承 private
在私有继承中,派生类可以继承基类的成员,但是不可以访问基类的私有成员,基类的公有成员与保护成员在派生类中的权限都是私有权限。
#include <iostream>`
`using` `namespace std;`
`class` `Base`
`{`
`private:`
` string str1 =` `"私有成员";`
`protected:`
` string str2 =` `"保护成员";`
`public:`
` string str3 =` `"公有成员";`
`};`
`// 私有继承`
`class` `Son:private Base`
`{`
`public:`
`Son()`
`{`
`// cout << str1 << endl; // 错误 str1为私有成员`
` cout << str2 << endl;`
` cout << str3 << endl;`
`}`
`};`
`int` `main()`
`{`
` Son s1;`
`// s1.str3; //错误,str在私有继承的派生类中为私有成员`
`return` `0;`
`}`
`
2、多态
2.1 函数覆盖 override
函数覆盖与函数隐藏比较相似,但是函数隐藏不支持多态,而函数覆盖是多态的必备条件。
(函数覆盖是指在父类和子类之间存在继承关系时,子类定义了与父类中同名的函数,并且函数的参数类型、返回值类型必须与父类中的相应函数一致。当子类对象调用该同名函数时,会自动调用子类中的函数,而不是父类中的函数。这种机制就是函数覆盖。)
(函数隐藏是指在派生类中定义了与基类中同名的函数,从而隐藏了基类中的函数。当通过派生类的对象调用该函数时,实际上调用的是派生类中定义的函数,而不是基类中的函数。函数隐藏发生在不同作用域,即派生类的作用域中隐藏了基类的函数。)
在编程方式上,函数覆盖与函数隐藏有以下几点区别:
- 被覆盖的基类函数必须是虚函数。
- 在C++中,可以在派生类中新覆盖的函数上使用override 关键字验证覆盖是否成功。
一个函数使用virtual关键字修饰,就是虚函数。虚函数是函数覆盖的前提。在Qt Creator中函数名称使用斜体字。
虚函数具有以下性质:
- 虚函数具有传递性,基类中被覆盖的函数是虚函数,派生类中新覆盖的函数也是虚函数。
- 只有普通成员函数与析构函数可以声明为虚函数。
- 如果虚函数的声明与定义分离,virtual关键字只需要修饰到声明处。不能写到定义处。
#include <iostream>`
`using` `namespace std;`
`class` `Animal`
`{`
`public:`
`// 虚函数`
`virtual` `void` `eat();`
`void` `test()`
`{`
`}`
`};`
`void` `Animal::eat()`
`{`
` cout <<` `"动物爱吃饭"` `<< endl;`
`}`
`class` `Dog:public Animal`
`{`
`void` `eat()` `override`
`{`
` cout <<` `"狗爱吃骨头"` `<< endl;`
`}`
`// void test() override`
`// {`
`// }`
`};`
`int` `main()`
`{`
` Dog d1;`
`return` `0;`
`}`
`
2.2 多态的概念
多态可以理解为"一种接口,多种状态",只需要编写一个函数接口,根据传入的参数类型,执行不同的策略代码。
多态的使用需要具备三个前提条件:
- 公有继承
- 函数覆盖(在派生类内声明一个与基类公共成员函数函数名及参数一致的函数)
- 基类的引用/指针指向派生类对象
#include <iostream>`
`using` `namespace std;`
`class` `Animal`
`{`
`public:`
`// 虚函数`
`virtual` `void` `eat()`
`{`
` cout <<` `"动物爱吃饭"` `<< endl;`
`}`
`};`
`class` `Dog:public Animal`
`{`
`public:`
`void` `eat()` `override`
`{`
` cout<<"狗爱吃骨头"<<endl;`
`}`
`};`
`class` `Cat:public Animal`
`{`
`public:`
`void` `eat()`
`{`
` cout<<"猫爱吃鱼"<<endl;`
`}`
`};`
`void` `animal_eat(Animal *al)`
`{`
` al->eat();`
`}`
`void` `animal_eat2(Animal &al)`
`{`
` al.eat();`
`}`
`int` `main()`
`{`
`//堆区开辟`
` Dog *d1=new Dog;`
` Cat *c1=new Cat;`
`animal_eat(d1);`
`animal_eat(c1);`
`//栈区定义`
` Dog d2;`
` Cat c2;`
`animal_eat2(d2);`
`animal_eat2(c2);`
`return` `0;`
`}`
`
2.3 多态的原理
具有虚函数的类会存在一张虚函数表。这张表被当前类所有对象共用。每个类的对象内部都会有一个隐藏的虚函数指针成员,指向当前类的虚函数表。
在代码运行时,通过对象的虚函数指针找到对应虚函数表,在表中定位到虚函数的调用地址,执行对应的虚函数的内容。
因此使用多态会产生一些额外的开销。优点是代码编写更加的灵活高效,缺点是会降低代码的执行速度,代码可读性降低。
2.4 虚析构函数
(析构函数:当对象销毁时被调用,走完花括号"{}",或delate释放堆空间)
如果不使用虚析构函数,且基类指针或引用指向派生类对象,使用delete销毁对象时,
析构函数不会被继承,但虚函数表会被继承,虚析构函数不会被覆盖,会自动生成一个新的对应派生类虚函数的虚析构函数,原虚析构函数对应基类虚函数。
只会触发基类的析构函数,如果在派生类中申请了内存资源,则会导致无法释放,出现内存泄漏的问题。
#include <iostream>`
`using` `namespace std;`
`class` `Animal`
`{`
`public:`
`// 虚函数`
`virtual` `void` `eat()`
`{`
` cout <<` `"动物爱吃饭"` `<< endl;`
`}`
`~Animal()`
`{`
` cout <<` `"Animal 析构函数被调用了"` `<< endl;`
`}`
`};`
`class` `Dog:public Animal`
`{`
`public:`
`void` `eat()` `override`
`{`
` cout <<` `"狗爱吃骨头"` `<< endl;`
`}`
`~Dog()`
`{`
` cout <<` `"Dog 析构函数被调用了"` `<< endl;`
`}`
`};`
`int` `main()`
`{`
` Animal *al =` `new Dog;`
` al->eat();//狗爱吃骨头`
`delete al;//Animal 析构函数被调用了,只调用这一个`
`return` `0;`
`}`
`
解决方法是给基类的析构函数使用virtual关键字修饰为虚析构函数,通过传递性可以把各个派生类的析构函数都变为虚析构函数。因此建议给一个可能为基类的类中的析构函数设置为虚析构函数。
#include <iostream>`
`using namespace std;`
`class Animal`
`{`
` public:`
` // 虚函数`
` virtual void eat()`
` {`
` cout << "动物爱吃饭" << endl;`
` }`
` virtual ~Animal()`
` {`
` cout << "Animal 析构函数被调用了" << endl;`
` }`
`};`
`class Dog:public Animal`
`{`
`public:`
` void eat() override`
` {`
` cout << "狗爱吃骨头" << endl;`
` }`
` ~Dog()`
` {`
` cout << "Dog 析构函数被调用了" << endl;`
` }`
`};`
`int main()`
`{`
` Animal *al = new Dog;`
` al->eat();//狗爱吃骨头,函数执行完后执行该函数对应的析构函数。`
` //Dog 析构函数被调用了`
` delete al;//Animal 析构函数被调用了`
` return 0;`
`}`
`
- 抽象类
如果基类只表达一些抽象的概念,并不与实际的对象相关联,这时候就可以使用抽象类。
如果一个类中有纯虚函数,这个类就是一个抽象类。
如果一个类是抽象类,则这个类中一定有纯虚函数。
纯虚函数是虚函数的一种,这种函数只有声明没有定义。不能实例化对象·
virtual 返回值类型 函数名(参数列表)` `=` `0;`
`
不能直接使用抽象类作为类型声明,因为不存在抽象类类型的对象。
抽象类作为基类时,具有两种情况:
- 派生类继承抽象类,覆盖并实现所有的纯虚函数,此时派生类可以作为普通类使用,即不再是抽象类。
- 派生类继承抽象类,没有把抽象类中所有的纯虚函数覆盖并实现,此时派生类也变为了抽象类,等待他的派生类覆盖并实现剩余的纯虚函数。(需要完全覆盖基类所有抽象函数)
抽象类,无法实例化对象,但是可以创建指针和引用。
#include <iostream>`
`using` `namespace std;`
`// 抽象类:形状`
`class` `Shape`
`{`
`public:`
`// 纯虚函数`
`virtual` `void` `area()` `=` `0;` `// 面积`
`virtual` `void` `perimeter()` `=` `0;` `// 周长`
`};`
`// 圆形`
`class` `Circle:public Shape`
`{`
`public:`
`void` `area()`
`{`
` cout <<` `"圆形计算面积"` `<< endl;`
`}`
`void` `perimeter()`
`{`
` cout <<` `"圆形计算周长"` `<< endl;`
`}`
`};`
`// 多边形`
`class` `Polygon:public Shape`
`{`
`public:`
`void` `perimeter()`
`{`
` cout <<` `"多边形计算周长"` `<< endl;`
`}`
`};`
`// 矩形`
`class` `Rectangle:public Polygon`
`{`
`public:`
`void` `area()`
`{`
` cout <<` `"矩形计算面积"` `<< endl;`
`}`
`};`
`int` `main()`
`{`
`// Shape s; // 错误抽象类无法实例化对象`
` Circle c;`
` c.area();`
` c.perimeter();`
`// Polygon p; // 错误,没有完全覆盖基类抽象函数,抽象类无法实例化对象`
` Rectangle r;`
` r.area();`
` r.perimeter();`
`return` `0;`
`}`
`
使用抽象类注意以下几点:
- 抽象类的析构函数必须是虚析构函数
- 抽象类支持多态,可以存在指针或引用的声明格式
- 因为抽象类的作用就是指定算法框架,因此在一个继承体系中,抽象类的内容相对丰富且重要。
2.5、纯虚析构(熟悉)
纯虚析构函数的定义:
纯虚析构的本质:是析构函数,作用是各个类的回收工作。而且析构函数不能被继承。
必须要为纯虚析构函数提供一个函数体。
纯虚析构函数,必须在类外实现。
#include <iostream>`
`using` `namespace std;`
`class` `Animal`
`{`
`public:`
`virtual` `void` `speak()`
`{`
`}`
`Animal()`
`{`
` cout <<` `"基类的构造函数被调用了"` `<< endl;`
`}`
`// 纯虚析构`
`virtual` `~Animal()` `=` `0;`
`};`
`// 纯虚析构类外实现`
`Animal::~Animal()`
`{`
` cout <<` `"基类析构函数被调用了"` `<< endl;`
`}`
`class` `Dog:public Animal`
`{`
`public:`
`void` `speak()`
`{`
` cout <<` `"狗会汪汪汪"` `<< endl;`
`}`
`Dog()`
`{`
` cout <<` `"dog类构造函数被调用了"` `<< endl;`
`}`
`~Dog()`
`{`
` cout <<` `"Dog类的析构函数被调用了"` `<< endl;`
`}`
`};`
`int` `main()`
`{`
`// Animal al; // 错误基类是纯虚析构,无法实例化对象`
` Animal *al =` `new` `Dog();`
` al->speak();`
`delete al;`
`return` `0;`
`}`
`
虚析构与纯虚析构的区别:
- 虚析构:virtual关键字修饰,有函数体,不会导致基类为抽象类。
- 纯虚函数:virtual关键字修饰,结果=0,函数体需要类外实现,会导致基类是抽象类。