ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

cpp拾遗——类

2022-06-01 11:01:38  阅读:151  来源: 互联网

标签:48 子类 mov 拾遗 cpp 父类 public rax


1. 继承和访问控制

1.1 访问控制的管道

public成员 protected成员 private成员
public继承 public , public = 子类内外皆可访问 protected , public = 只能子类内和父类内访问 private , public = 只能父类内访问
protected继承 public , protected = protected protected | protected = protected private | protected = 父类private
private继承 public | public = 子类private protected | private = 子类private private | private = 父类private

1.2 继承的本质

属性继承

若有一下继承

class T{
    public:
        int a;
};
class TT:public T{
    public:
        int a;
};

则 TT tt 的内存结构如下

方法继承

class T{
    public:
        void func() {}
};
class TT:public T{
    public:
        void func() {}
};

TT tt;
TT *ptt;

ptt = &tt;
ptt->func();
ptt->T::func();
tt.func();
tt.T::func();
  1. 将class中定义函数,重命名函数名,定义全局函数
class T{
    public:
        void func() {}
};
class TT:public T{
    public:
        void func() {}
};
// 相当于定义为
void T_func() {}
void TT_func() {}

2) 根据调用方法时,对象的类型,进行静态链编

TT tt;
TT *ptt;

ptt = &tt;
ptt->func();     // TT_func(ptt);
ptt->T::func();  // T_func(&ptt->t);
tt.func();       // TT_func(&tt);
tt.T::func();    // T_func(&tt.t);

2. 类的兼容性原则

派生类对象可以当基类对象使用。
包括以下情况:
1)子类对象可以当父类对象使用
2)子类对象可以直接赋值给父类对象
3)子类对象可以直接初始化父类对象
4)父类指针可以指向子类对象
5)父类引用可以直接引用子类对象

3. 构造析构的调用顺序

构造:先父亲,再朋友,再自己
析构:先自己,再朋友,再父亲

3.1 继承中的构造本质

class F{
    public:
        F() { }
        ~F() { }
};
class F2{
    public:
        F2() { }
        ~F2() { }
};
class T{
    public:
        T() { }
        ~T() { }
};
class TT:public T{
    public:
        TT() { }
        ~TT() { }
        F2 f2; // 朋友的构造顺序又声明时顺序决定
        F f;
};

cpp编译器会添加代码

        TT() { T(); F2(); F(); /* 构造自己 */ }
        ~TT() { /* 析构自己 */ ~F(); ~F2(); ~T(); }

4. 同名成员继承

  1. 覆盖父类成员
    当父类和子类的成员同名时,子类成员会覆盖父类成员,但通过子类对象加上域作用符仍然可以访问父类属性。
  2. 重载只存在于同个类中
    父类和子类的成员函数即使 函数名相同,参数不同,但不构成重载,而是覆盖。
    原因很明显cpp编译器在重新命名函数名时,由于 类名不同,导致被命名后的函数名不同。

5. 继承和static

父类的static成员 被 子类继承,子类对象可以正常访问。

6. 多继承的二义性


由于D继承B和C,所以D有两份A的属性,那么D访问A的属性时会出现二义性(即不知道访问的哪个内存)。


通过加上virtual关键字,D继承B和C时只会继承A的一份属性,解决二义性问题。

7. 多态

7.1 多态构成条件

父类指针指向子类对象,访问成员函数

  1. 非多态
    cpp在编译阶段,静态链编,由于指针是父类对象,所以调用父类函数

  2. 多态
    当父类函数用virtual修饰,则使用动态链编,运行阶段,根据指针指向的对象为子类对象,调用子类对象的函数

7.2 多态的应用

  1. 虚析构函数
    需求:希望用父类指针,释放子类对象
    若没有使用virtual,则 delete 父类指针,在编译阶段,编译成 调用父类对象的 析构函数,则导致内存泄漏。
    若使用virtual,则 delete 父类指针,在运行阶段,根据指针指向的是子类对象,则调用子类对象的析构函数,子类对象的析构函数已经被cpp编译器修改,增加了调用父类析构和朋友析构的代码,所以会析构完所有继承和组合的对象,所以没有内存泄漏。

7.3 多态的本质

由于现象是运行时确定调用函数,所以一定是用函数指针实现,
而多个函数都可以设为多态,所以一定是将函数地址组织为表。

cpp编译器为 使用了 virtual 关键字的类添加了 vptr指针,当子类继承时,子类也就继承了 vptr指针。
并将 多态函数地址构成一张表,当对象构造时,vptr指针指向本构造函数对应类的 多态表,
所以 子类的vptr 先在 父类构造函数中,被设置为 指向父类的多态函数表,然后在自己的构造函数中被设置为指向自己的函数表,

        T() {}
    127a:   f3 0f 1e fa             endbr64
    127e:   55                      push   %rbp
    127f:   48 89 e5                mov    %rsp,%rbp
    1282:   48 89 7d f8             mov    %rdi,-0x8(%rbp)
    1286:   48 8d 15 d3 2a 00 00    lea    0x2ad3(%rip),%rdx        # 3d60 <_ZTV1T+0x10> # 设置vptr指针指向本类的多态函数表
    128d:   48 8b 45 f8             mov    -0x8(%rbp),%rax
    1291:   48 89 10                mov    %rdx,(%rax)


        TT() {}
    12c8:   f3 0f 1e fa             endbr64
    12cc:   55                      push   %rbp
    12cd:   48 89 e5                mov    %rsp,%rbp
    12d0:   48 83 ec 10             sub    $0x10,%rsp
    12d4:   48 89 7d f8             mov    %rdi,-0x8(%rbp)
    12d8:   48 8b 45 f8             mov    -0x8(%rbp),%rax
    12dc:   48 89 c7                mov    %rax,%rdi
    12df:   e8 96 ff ff ff          callq  127a <_ZN1TC1Ev>   # 调用父类构造函数
    12e4:   48 8d 15 4d 2a 00 00    lea    0x2a4d(%rip),%rdx        # 3d38 <_ZTV2TT+0x10>  # 设置vptr指针指向自己的函数表
    12eb:   48 8b 45 f8             mov    -0x8(%rbp),%rax
    12ef:   48 89 10                mov    %rdx,(%rax)

调用多态时,根据当前vptr指针的值为基地址,根据多态函数声明顺序获得偏移量(编译阶段获得),计算得到调用函数的地址,并调用。

1919     pt->func();
1920     11b8:   48 8b 45 f0             mov    -0x10(%rbp),%rax
1921     11bc:   48 8b 00                mov    (%rax),%rax
1922     11bf:   48 8b 10                mov    (%rax),%rdx
1923     11c2:   48 8b 45 f0             mov    -0x10(%rbp),%rax
1924     11c6:   48 89 c7                mov    %rax,%rdi
1925     11c9:   ff d2                   callq  *%rdx
1926     pt->func2();
1927     11cb:   48 8b 45 f0             mov    -0x10(%rbp),%rax
1928     11cf:   48 8b 00                mov    (%rax),%rax
1929     11d2:   48 83 c0 08             add    $0x8,%rax
1930     11d6:   48 8b 10                mov    (%rax),%rdx
1931     11d9:   48 8b 45 f0             mov    -0x10(%rbp),%rax
1932     11dd:   48 89 c7                mov    %rax,%rdi
1933     11e0:   ff d2                   callq  *%rdx
1934     pt->func3();
1935     11e2:   48 8b 45 f0             mov    -0x10(%rbp),%rax
1936     11e6:   48 8b 00                mov    (%rax),%rax
1937     11e9:   48 83 c0 10             add    $0x10,%rax
1938     11ed:   48 8b 10                mov    (%rax),%rdx
1939     11f0:   48 8b 45 f0             mov    -0x10(%rbp),%rax
1940     11f4:   48 89 c7                mov    %rax,%rdi
1941     11f7:   ff d2                   callq  *%rdx

7.4 多态的缺点

由于多态调用是计算地址,并调用,所以效率低,不应该把所有函数都声明为多态。

8. 父类和子类指针步长

虽然多态让 父类指针 能表现子类特性,但由于 子类通常定义了 成员属性,所以父类指针步长和子类指针步长不同。

9. 纯虚函数和抽象类

纯虚函数
没有函数体,只用于为派生类提供接口的函数。

virtual void func() = 0;

抽象类
具有纯虚函数的类为抽象类,由于抽象类定义不完整,所以抽象类不能定义对象,只能定义指针,用于实现多态。

标签:48,子类,mov,拾遗,cpp,父类,public,rax
来源: https://www.cnblogs.com/yangxinrui/p/16313117.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有