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

1、权限

1.1 权限修饰符

三种权限,一共对应九种场景。要做到心中有表,遇到任何一种场景都能直接反映出是否能访问。

类内

派生类中

全局

private

×

×

protected

×

public

#include 
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 

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 

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 

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 
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 

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 多态的原理

具有虚函数的类会存在一张虚函数表。这张表被当前类所有对象共用。每个类的对象内部都会有一个隐藏的虚函数指针成员,指向当前类的虚函数表。

C++面向对象核心-权限-多态_第1张图片

C++面向对象核心-权限-多态_第2张图片

在代码运行时,通过对象的虚函数指针找到对应虚函数表,在表中定位到虚函数的调用地址,执行对应的虚函数的内容。

因此使用多态会产生一些额外的开销。优点是代码编写更加的灵活高效,缺点是会降低代码的执行速度,代码可读性降低。

2.4 虚析构函数

(析构函数:当对象销毁时被调用,走完花括号“{}”,或delate释放堆空间)

如果不使用虚析构函数,且基类指针或引用指向派生类对象,使用delete销毁对象时,

析构函数不会被继承,但虚函数表会被继承,虚析构函数不会被覆盖,会自动生成一个新的对应派生类虚函数的虚析构函数,原虚析构函数对应基类虚函数。

只会触发基类的析构函数,如果在派生类中申请了内存资源,则会导致无法释放,出现内存泄漏的问题。

#include 

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 

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

C++面向对象核心-权限-多态_第3张图片

使用抽象类注意以下几点:

  • 抽象类的析构函数必须是虚析构函数
  • 抽象类支持多态,可以存在指针或引用的声明格式
  • 因为抽象类的作用就是指定算法框架,因此在一个继承体系中,抽象类的内容相对丰富且重要。

2.5、纯虚析构(熟悉)

纯虚析构函数的定义:

纯虚析构的本质:是析构函数,作用是各个类的回收工作。而且析构函数不能被继承。

必须要为纯虚析构函数提供一个函数体。

纯虚析构函数,必须在类外实现。

#include 

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,函数体需要类外实现,会导致基类是抽象类。

你可能感兴趣的:(C++,c++,算法,开发语言)