C++类和类的继承、多态,虚函数使用等知识点总结

一、C++继承

c++的一个很大的特点是面向对象编程,而继承机制是面向对象程序设计使代码可以复用的最重要手段;它可以让程序员在保留原有类特性的基础上进行功能拓展,在类的基础上衍生功能模块,使得设计的代码更加有层次感,更加的简洁,冗余性大大降低;
类的继承分为基类(也称父类)和派生类(也称子类),举个例子,一般形式为:

基类:

class Operator
{
public:
    Operator(){std::cout << "======Operator build !" << std::endl;}
    virtual ~Operator(){std::cout << "======Operator release !" << std::endl;}

    virtual void print_op_name();

    virtual int op_func_(int a, int b) = 0;

};

子类:

class Add : public Operator
{
public: 
    Add(){std::cout << "======Add build !" << std::endl;}
    ~Add(){std::cout << "======Add release !" << std::endl;}

    virtual void print_op_name();

    virtual int op_func_(int a, int b);

};

类Add继承了类Operator,所以可以去复用Operator中的成员变量和成员函数;
(1)继承方式:继承方式有public、private、protected,如果在继承时没有指定继承方式,例如:

class Add : Operator
{
.........
};

则默认是private方式,这种私有继承方式 基类指针无法指向派生类 ,正常我们选择继承方式都是public显式继承。
(2)类中的访问权限
类中的访问修饰符有private、public 和 protected,其决定了基类的哪些成员将被派生类获取和访问,以及上面说的派生类继承基类的方式;类中的定义通常形式为:


class base {
public:
   //
protected:
   //
private:
   //
};

在类的继承中:
在基类中,private修饰的变量,无法在派生类中访问,只能被基类成员函数访问;
在基类中,被public和protected修饰的变量,在派生类中也会对应的会变成public和protected;

不管在基类还是派生类中,变量被private修饰的,都只能被成员函数或者友元函数访问,不能在类外部访问,举个例子:

class Base
{  
    public:   
        void print() {   
            std::cout << num << std::endl;
        }
   private:
   		int num;    
};
int main()
{  
    Base P;
    P.num = 2;
    P.print();
}

上述代码编译错误,因为被private修饰的变量num无法在类的外部被访问,而在成员函数print()中就可以访问;

这些修饰符修饰类的成员时,总结如下:
public修饰的成员可以被任意实体访问,这和c语言中的结构体成员相似;
private修饰的成员不能直接被类的实体访问,也不能被子类的实体访问,但是可以被类的成员函数访问;
protected修饰的成员不能直接被类的实体访问,但是可以被子类访问,也可以被类的成员函数访问;

二、C++类中的虚函数

c++类中的虚函数一般形式为:

virtual 返回值类型 函数名(参数表) 
{ 函数体 }

和正常的函数相比,就是在开头加了个virtual修饰;类中的虚函数一个主要的作用是实现C++多态的机制(第三节详细介绍);

(1)纯虚函数
一般形式为:

virtual 返回值类型 函数名(参数表)= 0
{ 函数体 }

在类的继承中,如果基类中的某个成员函数定义为纯虚函数,则在其派生类中就应该重写这个成员函数的实现方法;
和虚函数不同的是,纯虚函数不需要在基类中定义实现的方法,而必须在派生类中实现;
虚函数则需要在基类中定义实现方法,也可以在派生类中定义实现方法;

有纯虚函数的类叫做抽象类,或者接口类。抽象类无法实例化出对象。抽象类的子类也无法实例化出对象,除非重写父类的虚函数。

(2)虚析构
虚析构一般在类中指的是virtual去修饰类的析构函数,一般形式为:

virtual ~Base();

析构函数的重要作用是为了防止内存泄漏!!

具体一点说就是,当一个基类指针指向一个派生类的对象时,当删除这个基类指针时,此时基类中的析构函数为虚析构情况下,会自动调用派生类的析构函数,释放后派生类的内存;而如果基类中的析构函数是普通析构函数时,就不会释放派生类的内存,造成内存泄漏。

举个例子:

class Operator   // 基类
{
public:
    Operator(){std::cout << "======Operator build !" << std::endl;}
    virtual ~Operator(){std::cout << "======Operator release !" << std::endl;}
    virtual void print_op_name();
    virtual int op_func_(int a, int b);
public:
    std::string op_name;
};

class Add : public Operator  // 派生类
{
public: 
    Add(){std::cout << "======Add build !" << std::endl;}
    ~Add(){std::cout << "======Add release !" << std::endl;}
    virtual void print_op_name();
    virtual int op_func_(int a, int b);
};
// 主函数
int main()
{
    int number = 0;
    Operator *op =  new Add;
    op->op_name = "Add";
    op->print_op_name();
    number = op->op_func_(6, 3);
    std::cout << "number = " << number << std::endl;
    delete op;
}

若Operator类中的析构函数加了virtual 修饰(即为虚析构函数),输出结果:
在这里插入图片描述
若Operator类中的析构函数没有加virtual 修饰(即为普通析构函数),输出结果:
在这里插入图片描述
可以看到不是虚析构的情况下,派生类Add只做了构造的操作,而没有调取析构函数进行内存释放,因此造成内存泄漏。
所以,当一个类作为基类的时候,尽量加上virtual关键字!! 使之成为虚析构函数;如果此类没有派生类,可以不加。另外,子类中的虚函数也不用加。
但是有一点是,让基类的析构函数编程虚析构函数,会增加这个类的内存的占用(系统会分配一个虚函数表),如果不需要使用基类对派生类的对象操作时,可以不用使之成为虚析构函数;

三、C++多态

C++多态,即多种形态;类比一下,可以理解为我们不同的人去做同一件事情,完成同一个目的,每个人会有每个人的方法;

上面已经介绍了类中的虚函数的使用方法,多态的构成条件之一就是用到虚函数;
举个例子说明多态的实现方式:

class Operator   // 基类
{
public:
    Operator(){std::cout << "======Operator build !" << std::endl;}
    virtual ~Operator(){std::cout << "======Operator release !" << std::endl;}
    virtual void print_op_name(){
        std::cout << "[Operator] print op name !" << std::endl;
    }
    virtual int op_func_(int a, int b){
        std::cout << "[Operator] op_func_ !" << std::endl;
        return a + b;
    }
public:
    std::string op_name;
};

class Add : public Operator  // 派生类
{
public: 
    Add(){std::cout << "======Add build !" << std::endl;}
    ~Add(){std::cout << "======Add release !" << std::endl;}
    virtual void print_op_name(){ 
        std::cout << "[Add] op name : " << op_name << std::endl; 
    }
    virtual int op_func_(int a, int b) {
        std::cout << "[Add] op_func_ !" << std::endl;
        return a + b;
    }
};
// 主函数
int main()
{
    int number = 0;
    Operator *op =  new Add;
    op->op_name = "Add";
    op->print_op_name();
    number = op->op_func_(6, 3);
    std::cout << "number = " << number << std::endl;
    delete op;
}

执行:
在这里插入图片描述
可以看到实际调用的op_func_函数为Add子类中op_func_的实现;
如果把其中的op_func_函数前的virtual去掉:

class Operator   // 基类
{
public:
    Operator(){std::cout << "======Operator build !" << std::endl;}
    virtual ~Operator(){std::cout << "======Operator release !" << std::endl;}
    virtual void print_op_name(){
        std::cout << "[Operator] print op name !" << std::endl;
    }
    int op_func_(int a, int b){
        std::cout << "[Operator] op_func_ !" << std::endl;
        return a + b;
    }
public:
    std::string op_name;
};

class Add : public Operator  // 派生类
{
public: 
    Add(){std::cout << "======Add build !" << std::endl;}
    ~Add(){std::cout << "======Add release !" << std::endl;}
    virtual void print_op_name(){ 
        std::cout << "[Add] op name : " << op_name << std::endl; 
    }
    int op_func_(int a, int b) {
        std::cout << "[Add] op_func_ !" << std::endl;
        return a + b;
    }
};
// 主函数
int main()
{
    int number = 0;
    Operator *op =  new Add;
    op->op_name = "Add";
    op->print_op_name();
    number = op->op_func_(6, 3);
    std::cout << "number = " << number << std::endl;
    delete op;
}

执行:
在这里插入图片描述
可以看到op_func_调用的实现是基类Operator中的实现方式了,而print_op_name依旧是派生类中的实现;
这里派生类中的函数和基类中的函数拥有相同的函数名,参数,返回值,可以称之为派生类对基类的函数进行了重写。
综合以上,可以总结一下多态的构成条件:

  1. 派生类必须对基类的函数进行了重写,并且这个函数必须为虚函数;
  2. 必须通过基类的指针或者引用去调虚函数;

四、友元类

C++中的友元类是指一个类可以访问另一个类的私有成员,举例说明更直观:

class A
{
    friend class B;
public:
    A(){}
    ~A(){}

    void print()
    {
        std::cout << "A name : " << a_name << std::endl;
    }
private:
    string a_name;
};

class B 
{
public:
    B() {}
    ~B(){}
    void creat()
    {
        _p.a_name = "jack";
    }
    void print()
    {
        std::cout << "A name : " << _p.a_name << std::endl;
        std::cout << "B name : " << b_name << std::endl;
    }
public:
    string b_name;
    A _p;
};

int main()
{
    B p;
    p.b_name = "marry";
    p.creat();
    p.print();
    return 0;
}

例中,友元类的定义形式为在A类中定义friend class B; 也就是B是A的友元类。
那么B可以访问A的私有成员。例如上面例子中,B中定义了A类的对象,在creat()函数中就可以给A类私有成员a_name赋值,print()函数中就可以打印a_name。
应用场景:在不公开自身私有成员的情况下,将这些私有成员暴露给其他类,既想保留Private属性,又想使用其他类来修改或者调用其中的参数;
使用友元类可以增加代码的灵活性,使得我们可以在不破坏封装性的情况下,让其他类或函数访问私有成员。比如,如果一个类需要访问另一个类的私有成员,但是这两个类之间没有继承关系,那么就可以将其中一个类声明为另一个类的友元类。

五、类占用的内存

c++类占用的内存计算方法:
(1)类的内存空间占用只包含变量,不包含函数;
(2)静态变量或者静态函数不占类内存空间;
(3)有虚函数的类,有一个指向虚函数表的指针,无论类中有多少虚函数,内存计算只按照一个虚函数计算,因为只有一个虚表指针(32位系统中占4字节,64位系统中占8字节);
(4)类和结构体都有内存对齐原则;
(5)空的类占一个字节;

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值