C++面向对象核心-权限-多态

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;`
`}`

`
  1. 抽象类

如果基类只表达一些抽象的概念,并不与实际的对象相关联,这时候就可以使用抽象类。

如果一个类中有纯虚函数,这个类就是一个抽象类。

如果一个类是抽象类,则这个类中一定有纯虚函数。

纯虚函数是虚函数的一种,这种函数只有声明没有定义。不能实例化对象·

复制代码
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,函数体需要类外实现,会导致基类是抽象类。
相关推荐
奋斗的小花生3 分钟前
c++ 多态性
开发语言·c++
魔道不误砍柴功5 分钟前
Java 中如何巧妙应用 Function 让方法复用性更强
java·开发语言·python
pianmian16 分钟前
python数据结构基础(7)
数据结构·算法
闲晨8 分钟前
C++ 继承:代码传承的魔法棒,开启奇幻编程之旅
java·c语言·开发语言·c++·经验分享
老猿讲编程35 分钟前
一个例子来说明Ada语言的实时性支持
开发语言·ada
UestcXiye2 小时前
《TCP/IP网络编程》学习笔记 | Chapter 3:地址族与数据序列
c++·计算机网络·ip·tcp
Chrikk2 小时前
Go-性能调优实战案例
开发语言·后端·golang
幼儿园老大*2 小时前
Go的环境搭建以及GoLand安装教程
开发语言·经验分享·后端·golang·go
canyuemanyue2 小时前
go语言连续监控事件并回调处理
开发语言·后端·golang
杜杜的man2 小时前
【go从零单排】go语言中的指针
开发语言·后端·golang