cpp拾遗——类
作者:互联网
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();
- 将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. 同名成员继承
- 覆盖父类成员
当父类和子类的成员同名时,子类成员会覆盖父类成员,但通过子类对象加上域作用符仍然可以访问父类属性。 - 重载只存在于同个类中
父类和子类的成员函数即使 函数名相同,参数不同,但不构成重载,而是覆盖。
原因很明显cpp编译器在重新命名函数名时,由于 类名不同,导致被命名后的函数名不同。
5. 继承和static
父类的static成员 被 子类继承,子类对象可以正常访问。
6. 多继承的二义性
由于D继承B和C,所以D有两份A的属性,那么D访问A的属性时会出现二义性(即不知道访问的哪个内存)。
通过加上virtual关键字,D继承B和C时只会继承A的一份属性,解决二义性问题。
7. 多态
7.1 多态构成条件
父类指针指向子类对象,访问成员函数
-
非多态
cpp在编译阶段,静态链编,由于指针是父类对象,所以调用父类函数 -
多态
当父类函数用virtual修饰,则使用动态链编,运行阶段,根据指针指向的对象为子类对象,调用子类对象的函数
7.2 多态的应用
- 虚析构函数
需求:希望用父类指针,释放子类对象
若没有使用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