搞懂C++的虚拟继承(升级)

简介: 搞懂C++的虚拟继承(升级)

4edc953e2c684bbe819ffa954c899c08.png

作者:良知犹存

转载授权以及围观->欢迎添加Wx:Allen-Iverson-me-LYN

前言:本文为C++使用过程中的一些细节知识补充,碎片化的知识.其中有韦东山老师和其他博主一些知识笔记,在此谢谢韦老师以及各博主的文章博客,受益良多.

   一、多重定义会使程序进行基类调用,用virtual定义基类的程序函数,然后派生类(子类)进行虚拟函数继承调用,调用的时候内存占用以及编译执行情况是如何呢?          

联编机制:联编是指一个程序自身彼此关联的一个过程,按照联编所进行的阶段不同分为静态联编和动态联编.

1.静态联编,对于一般类的成员函数(非虚函数),在编译的时候就确定好了调用哪一个。而编译时所进行的联编又称为静态束定(束定是指确定所调用的函数与执行该函数代码之间的关系).

2.动态联编,在运行过程中解决选择调用函数与执行该函数代码之间的关系,又被成为动态束定。具体为:有一个指针,指向一个虚函数表;在调用的时候通过指针找到表,调用虚函数,进而将虚函数连接到当前类下。所以试下动态联编首先要有继承性,并且要求建立子类型关系,其次,一个重要条件是虚函数,继承是动态联编的基础,虚函数是动态联编的关键.

普通继承:静态联编

4edc953e2c684bbe819ffa954c899c08.png

虚拟继承:动态联编

4edc953e2c684bbe819ffa954c899c08.png

   一般函数名字相同返回值不同的时候只能做重载不能进行虚拟函数,但是如果返回值是本次定义的类的指针或者引用时,虚拟函数才成立。

    二、虚拟析构函数,将析构函数定义为虚函数,但是需要注意,如果我们派生类分配了内存空间.但是基类的析构函数不是虚析构函数的情况下,类执行析构函数的时候只执行基类析构函数,派生类则没有,造成内存泄漏.

class D{......};
class B: public D{......};
int main(int argc, char **argv)
{
    D* d = new b;
    return 0;
}

4edc953e2c684bbe819ffa954c899c08.png

using namespace std;
class Human {
private:
  int a;
public:
  virtual void eating(void) { cout<<"use hand to eat"<<endl; }
  virtual ~Human() { cout<<"~Human()"<<endl; }
    virtual Human* test(void) { 
        cout<<"Englishman test()"<<endl; 
        return this;
        }
};
class Englishman : public Human {
public:
  void eating(void) { cout<<"use knife to eat"<<endl; }
  virtual ~Englishman() { cout<<"~Englishman()"<<endl; }
    virtual Englishman* test(void) { 
        cout<<"Englishman test()"<<endl; 
        return this;
        }
};
class Chinese : public Human {
public:
  void eating(void) { cout<<"use chopsticks to eat"<<endl; }
  virtual ~Chinese() { cout<<"~Chinese()"<<endl; }/*析构函数也应该为虚拟函数*/
    virtual Chinese* test(void) { 
        cout<<"Chinese test()"<<endl; 
        return this;
        }
};
void test_eating(Human h){
  h.eating();
}
void test_return(Human h){
  h.test();
}
int main(int argc, char **argv)
{
    Human h;
    Englishman e;
    Chinese c;
    test_return(h);
    test_return(e);
    test_return(c);
  return 0;
}

4edc953e2c684bbe819ffa954c899c08.png

     动态类型转化中经常使用指针,一般不适用引用,因为引用在被转换判断的时候,实体可能不存在,引用指向也就没有意义,程序就会执行失败。

/*类的定义和上面范例定义相同,此处就不多做赘述*/
void test_eating(Human &h)
{
    #if 0/*有隐患*/
    Englishman &pe = reinterpret_cast<Englishman&>(h);/*静态显性强制转化*/
    Chinese    &pc = reinterpret_cast<Chinese&>(h);
    Guangximan &pg = reinterpret_cast<Guangximan&>(h);    
    #else
    // Englishman &pe = dynamic_cast<Englishman&>(h);/*error*/
    Chinese    &pc = dynamic_cast<Chinese&>(h); /*动态转换*/
    Guangximan &pg = dynamic_cast<Guangximan&>(h);
    #endif
  h.eating();
    /*想分辨哪个国家的人*/
}
void test_return(Human h)
{
  h.test();
}
int main(int argc, char **argv)
{
    Human h;
    Guangximan g;
    Englishman *pe;
    pe = static_cast<Englishman*>(&h);
    // Englishman *pe2 = static_cast<Englishman*>(&g);
    Chinese *pc = static_cast<Chinese*>(&g);
    test_eating(g);
  return 0;
}

4edc953e2c684bbe819ffa954c899c08.png

四、类与派生类之间:函数名相同,参数与返回值也同样相同的函数叫做覆写函数,函数名相同参数不同叫做重载函数。

   覆写函数通常应用于子类继承父类时,重写父类中的函数,覆写的函数不能在父类中为private,否则只是相当于在子类中新写了一个函数.

       C++允许在同一作用域中的某个函数和运算符指定多个定义,分别成为重载函数和重载运算符.

namespace C
{ 
using namespace std;
class Point
{
private:
    int x;
    int y;    
public:
    Point()
    {
        std::cout << "/* Point() */" << std::endl;
    }
    Point(int x,int y) : x(x),y(y)
    {
        std::cout << "/* Point(int x,int y) */" << std::endl;
    }
    Point(const Point& p)
    {
        std::cout << "Point(const Point& p)" << std::endl;
        x = p.x;
        y = p.y;
    }    
    ~Point()
    {
        std::cout << "/* ~Point() */" << std::endl;
    }    
    int getX(){return x;}
    int getY(){return y;}
    int setX(int x){this->x = x;}
    int setY(int y){this->y = y;}
    void printInfo()
{
        cout<<"("<<x<<","<<y<<")"<<endl;
    }
    friend Point add(Point &p1,Point &p2);
    friend Point operator+(Point &p1,Point &p2);
    friend Point operator++(Point &p,int a);
    friend Point operator++(Point &p);
};
Point add(Point &p1,Point &p2)
{
    Point  n ;
    n.x=(p1.x+p2.x);    
    n.y=(p1.y+p2.y);
    return n ;
}
Point operator+(Point &p1,Point &p2)
{
    cout<<"Point operator+(Point &p1,Point &p2)"<<endl;
    Point  n ;
    n.x=(p1.x+p2.x);    
    n.y=(p1.y+p2.y);
    return n ;
}
/*Point p(1,2); ++p 函数重载 通过参数的不一致进行区分*/
Point operator++(Point &p)
{
    std::cout << "++p" << std::endl;
    p.x += 1;
    p.y += 1;
    return p;
}
/*Point p(1,2); p++ */
Point operator++(Point &p,int a)
{
    std::cout << "p++" << std::endl;
    Point n;
    n = p;
    p.x += 1;
    p.y += 1;
    return n;    
}
}
#include "point.h"
using namespace C;
int Person::cnt = 0; /*定义和初始化 此处并不是全局变量*/
int main(int argc,char** argv)
{
    Point p1(1,2);
    Point p2(2,3);
    std::cout << "begin" << std::endl;
    ++p1;
    p1.printInfo();
    std::cout << "***********" << std::endl;
    p2++;
    p2.printInfo();    
    std::cout << "end" << std::endl;
   return 0;
}

4edc953e2c684bbe819ffa954c899c08.png

目录
相关文章
|
2月前
|
编译器 C++ 开发者
【C++】继承
C++中的继承是面向对象编程的核心特性之一,允许派生类继承基类的属性和方法,实现代码复用和类的层次结构。继承有三种类型:公有、私有和受保护继承,每种类型决定了派生类如何访问基类成员。此外,继承还涉及构造函数、析构函数、拷贝构造函数和赋值运算符的调用规则,以及解决多继承带来的二义性和数据冗余问题的虚拟继承。在设计类时,应谨慎选择继承和组合,以降低耦合度并提高代码的可维护性。
35 1
【C++】继承
|
3月前
|
Linux 编译器 测试技术
【C++】CentOS环境搭建-快速升级G++版本
通过上述任一方法,您都可以在CentOS环境中高效地升级G++至所需的最新版本,进而利用C++的新特性,提升开发效率和代码质量。
209 64
|
3月前
|
Linux 编译器 测试技术
【C++】CentOS环境搭建-快速升级G++版本
通过上述任一方法,您都可以在CentOS环境中高效地升级G++至所需的最新版本,进而利用C++的新特性,提升开发效率和代码质量。
259 63
|
3月前
|
安全 程序员 编译器
【C++篇】继承之韵:解构编程奥义,领略面向对象的至高法则
【C++篇】继承之韵:解构编程奥义,领略面向对象的至高法则
96 11
|
3月前
|
C++
C++番外篇——对于继承中子类与父类对象同时定义其析构顺序的探究
C++番外篇——对于继承中子类与父类对象同时定义其析构顺序的探究
68 1
|
3月前
|
C++
C++番外篇——虚拟继承解决数据冗余和二义性的原理
C++番外篇——虚拟继承解决数据冗余和二义性的原理
50 1
|
3月前
|
安全 编译器 程序员
C++的忠实粉丝-继承的热情(1)
C++的忠实粉丝-继承的热情(1)
23 0
|
3月前
|
编译器 C++
C++入门11——详解C++继承(菱形继承与虚拟继承)-2
C++入门11——详解C++继承(菱形继承与虚拟继承)-2
42 0
|
3月前
|
程序员 C++
C++入门11——详解C++继承(菱形继承与虚拟继承)-1
C++入门11——详解C++继承(菱形继承与虚拟继承)-1
46 0
|
4月前
|
C++
C++(二十)继承
本文介绍了C++中的继承特性,包括公有、保护和私有继承,并解释了虚继承的作用。通过示例展示了派生类如何从基类继承属性和方法,并保持自身的独特性。此外,还详细说明了派生类构造函数的语法格式及构造顺序,提供了具体的代码示例帮助理解。