C++梁哥笔记day14 | 我的日常分享

C++梁哥笔记day14

类与对象

一、继承和派生

继承和派生其实分别是站在父类和子类两个角度来说的,本质上是同一个东西

图片

1.1继承的基本概念:
C++最重要的特征是代码重用,通过继承机制可以利用已有的数据来定义新的数据类型,新的类不仅拥有旧类的成员,还拥有新定义的成员。一个B类继承与A类,或者说从A类派生出B类。这样的话,类A称为基类(父类),类B称为派生类(子类)。派生类中的成员包含两大部分:一类是从基类继承过来的,一类是自己新增加的成员。从基类继承过来的表现其共性,而新增的成员体现了其个性。
图片

1.2派生类定义
派生类定义格式

1
2
3
4
5
6
7
class 派生类名 : 继承方式 基类名{
//派生类新增的数据成员和成员函数
}
即:
class 子类名 : 继承方式 父类名{
//派生类新增的数据成员和成员函数
}

1.3继承方式分:

  • public公有继承,
  • protected保护继承,
  • private私有继承

1.4继承源个数上分:

  • 单继承,指每个派生类只直接继承了一个基类
  • 多继承,指多个基类派生出一个派生类的继承关系,多继承的派生类直接继承了不止一个基类

二、父类在派生类的访问权限控制

派生类继承基类,派生类拥有基类中全部成员变量和方法(除了构造方法和析构函数),但是在派生类中,继承的成员并不一定能直接访问,不同的继承方式会导致不同的访问权限。

2.1公有继承

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
//定义一个基类
class Base{
public:
int a;
protected:
int b;
private:
int c;
public:
Base(){
cout<<"Base的无参构造"<<endl;
}
};
//定义一个派生类
class Derived:public Base{
public:
Derived(){
cout<<"Derived的无参构造"<<endl;
}
void fun(){
a = 100;
b = 200;
//c = 300;//error: 'int Base::c' is private
}
};
void test01(){
Derived ob1;
ob1.a = 100;
cout<<ob1.a<<endl;

//ob1.b = 200;
//cout<<ob1.b<<endl;//error: 'int Base::b' is protected

//ob1.c = 300;
//cout<<ob1.c<<endl;//error: 'int Base::c' is private
}

运行结果:
图片

总结:

  • 父类中的public数据在子类中也是public
  • 父类中的private数据在子类中是不可见的
  • 父类在中的protected数据在子类中是protected的

2.2保护继承

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Derived1:protected Base{
public:
Derived1(){
cout<<"Derived1的无参构造"<<endl;
}
void fun(){
a = 100;
b = 200;
//c = 300;//error: 'int Base::c' is private
}
};
void test02(){
Derived1 ob1;
//ob1.a = 100;//error:'int Base::a' is inaccessible
//ob1.b = 200;//error:'int Base::b' is protected
//ob1.c = 300;//error:'int Base::c' is private
}

总结:

  • 父类中的public数据在子类中是protected
  • 父类中的private数据在子类中是不可见的
  • 父类在中的protected数据在子类中是protected的

2.3私有继承

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Derived2:private Base{
public:
Derived2(){
cout<<"Derived2的无参构造"<<endl;
}
void fun(){
a = 100;
b = 200;
//c = 300;//error: 'int Base::c' is private
}
};
void test03(){
Derived2 ob1;
//ob1.a = 100;//error:'int Base::a' is inaccessible
//ob1.b = 200;//error:'int Base::b' is protected
//ob1.c = 300;//error:'int Base::c' is private
}

总结:

  • 父类中的public数据在子类中是private
  • 父类中的private数据在子类中是不可见的
  • 父类在中的protected数据在子类中是private的

2.4三种继承方式总结:

图片

如果继承方式的权限高于或等于父类中数据的权限,父类中数据在子类中的权限不变;如果继承方式的权限低于父类总的数据权限,父类数据在子类中的权限将降为继承方式的权限。另外父类中的private私有数据在任何继承方式下,其在子类中都是不可见的,无法访问。


三、子类的内层布局

利用vs studio查看子类的布局

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Base {
public:
int a;
protected:
int b;
private:
int c;
};

class Derived :public Base {
public:
int d;
protected:
int e;
};

图片

图片
图片


四、子类中构造和析构的顺序

4.1继承中的对象模型

在C++编译器的内部可以理解为结构体,子类是由父类成员叠加子类新成员而成

4.2继承中对象构造和析构的调用规则

子类对象在创建时会首先调用父类的构造函数,父类构造函数执行完毕后,才会调用子类的构造函数,当父类构造函数有参数时,需要子类初始化列表(参数列表)中显示调用父类的构造函数,析构函数的调用顺序和构造函数相反。
图片

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//定义一个基类
class Base{
public:
Base(){
cout<<"Base的无参构造"<<endl;
}
~Base(){
cout<<"Base的析构"<<endl;
}
};
//定义一个派生类
class Derived:public Base{
public:
Derived(){
cout<<"Derived的无参构造"<<endl;
}
~Derived(){
cout<<"Derived的析构函数"<<endl;
}
};
void test01(){
Derived ob1;
}

运行结果:
图片

构造顺序:父类构造函数 —> 子类构造函数
析构顺序:子类析构函数 —> 父类析构函数

五、子类中有其他类的对象成员时构造和析构顺序

父类的构造和析构 对象成员的构造和析构 子类自身的构造和析构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
//定义一个基类
class Base{
public:
Base(){
cout<<"Base的无参构造"<<endl;
}
~Base(){
cout<<"Base的析构"<<endl;
}
};
//定义一个其他类
class Other{
public:
Other(){
cout<<"Other的无参构造"<<endl;
}
~Other(){
cout<<"Other的析构函数"<<endl;
}
};
//定义一个派生类
class Derived:public Base{
private:
Other ob;
public:
Derived(){
cout<<"Derived的无参构造"<<endl;
}
~Derived(){
cout<<"Derived的析构函数"<<endl;
}
};
void test01(){
Derived ob1;
}

运行结果:
图片

构造:父类构造 —> 其他类构造 —> 子类构造
析构:子类析构 —> 其他类析构 —> 父类析构

总结:

图片


六、子类中的父类构造详解

子类默认调用父类的无参构造函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
//定义一个基类
class Base{
private:
int a;
public:
Base(){
cout<<"Base的无参构造"<<endl;
}
~Base(){
cout<<"Base的析构"<<endl;
}
Base(int a){
this->a = a;
cout<<"Base的有参构造"<<endl;
}
};
//定义一个派生类
class Derived:public Base{
private:
int b;
public:
Derived(){
cout<<"Derived的无参构造"<<endl;
}
~Derived(){
cout<<"Derived的析构函数"<<endl;
}
Derived(int b){
this->b = b;
cout<<"Derived有参构造函数"<<endl;
}
};
void test01(){
Derived ob1(10);
}

运行结果:
图片

如果要调用父类的有参构造就必须使用初始化列表显示调用父类的有参构造函数

6.1 子类使用初始化列表显示调用父类的有参构造函数

调用形式:

1
2
3
4
5
6
7
Derived(int a,int b):Base(a),b(b){

}
//调用父类两个参数的有参函数
Derived(int a,int b,int c):Base(a,c),b(b){

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
//定义一个基类
class Base{
private:
int a;
public:
Base(){
cout<<"Base的无参构造"<<endl;
}
~Base(){
cout<<"Base的析构"<<endl;
}
Base(int a){
this->a = a;
cout<<"Base的有参构造"<<endl;
}
};
//定义一个派生类
class Derived:public Base{
private:
int b;
public:
Derived(){
cout<<"Derived的无参构造"<<endl;
}
~Derived(){
cout<<"Derived的析构函数"<<endl;
}
Derived(int b){
this->b = b;
cout<<"Derived有参构造函数"<<endl;
}
Derived(int a,int b):Base(a),b(b){
cout<<"Derived两个参数有参构造函数"<<endl;
}
};
void test02(){
Derived ob2(10,30);
}

运行结果:
图片

七、继承中同名成员的处理方法

当子类成员和父类成员同名时,子类依然从父类继承同名成员,子类访问其成员默认访问子类的成员(本作用域,就近原则)在子类中通过作用域::进行同名成员区分(在派生类中使用基类的同名成员,显示使用类名限定符)

7.1 子类和父类成员变量同名

  • 当父类和子类成员变量同名时,在子类中会就近原则,取用子类中的成员变量
  • 如果非要使用父类中的同名就必须使用父类作用域限定::,或者使用父类中的公有方法

案例1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
class Base{
public:
int num;
public:
Base(){
cout<<"Base无参构造方法"<<endl;
}
Base(int num){
this->num = num;
cout<<"Base有参构造方法 int"<<endl;
}
~Base(){
cout<<"Base析构方法"<<endl;
}
};
class Derived:public Base{
public:
int num;
public:
Derived(){
cout<<"Derived无参构造方法"<<endl;
}
Derived(int a,int b):Base(a){
this->num = b;
cout<<"Derived有参构造方法 int int"<<endl;
}
~Derived(){
cout<<"Derived析构方法"<<endl;
}
void showNum(){
cout<<"父类中的num:"<<Base::num<<endl;
cout<<"子类中的num:"<<this->num<<endl;
}
};
void test01(){
Derived ob1(10,30);
ob1.showNum();
}

运行结果:
图片

  • 如果父类中的数据时private时,在子类中不可见,要使用友元???友元好像用不了,可以使用父类中的公有方法

案例2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
class Base{
private:
int num;
public:
Base(){
cout<<"Base无参构造方法"<<endl;
}
Base(int num){
this->num = num;
cout<<"Base有参构造方法 int"<<endl;
}
~Base(){
cout<<"Base析构方法"<<endl;
}
int getNum(){
return this->num;
}
};
class Derived:public Base{
public:
int num;
public:
Derived(){
cout<<"Derived无参构造方法"<<endl;
}
Derived(int a,int b):Base(a){
this->num = b;
cout<<"Derived有参构造方法 int int"<<endl;
}
~Derived(){
cout<<"Derived析构方法"<<endl;
}
void showNum(){
//cout<<"父类中的num:"<<Base::num<<endl;//error: 'int Base::num' is private
//可以在父类中定义一个方法来访问父类中的num
cout<<"父类中的num:"<<getNum()<<endl;
cout<<"子类中的num:"<<this->num<<endl;
}
};
void test01(){
Derived ob1(10,30);
ob1.showNum();
}

运行结果:
图片

7.2 子类和父类成员方法(函数)同名

注意: 与java中子类重写重载父类方法的区别。在C++中一旦子类重写重载的父类的某个成员函数,将屏蔽父类中这个成员函数的所有重写重载函数。(于2020.1.7 晚9:39修改 重写与重载的概念混淆了 C++中的重写利用虚函数的动态绑定见day16)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class Main {
public static void main(String[] args) {
Derived ob = new Derived();
ob.fun();
ob.fun(10);
}
}
class Base{
public void fun(){
System.out.println("父类中的fun void");
}
public void fun(int a){
System.out.println("父类中的fun int");
}
}
class Derived extends Base{
public void fun(){
System.out.println("子类中的fun void");
}
}

运行结果:
图片

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
class Base
{
public:
Base() {
cout<<"无参构造函数"<<endl;
}
~Base(){
cout<<"析构函数"<<endl;
}
void fun(){
cout<<"父类的fun void"<<endl;
}
void fun(int a){
cout<<"父类中的fun int"<<endl;
}
void show(){
cout<<"父类中的show void"<<endl;
}
};
class Derived:public Base{
public:
void fun(){
cout<<"子类的fun void"<<endl;
}
};
void test01(){
Derived ob;
ob.fun();
//子类中重写了父类中fun,则父类中所有的fun将被屏蔽
//ob.fun(10);//error:no matching function for call to 'Derived::fun(int)'
ob.show();
}

运行结果:
图片

如果用户必须要调用父类被屏蔽的方法,必须加作用域

1
2
3
4
5
6
7
8
void test02(){
Derived ob;
ob.fun();
//加父类作用域
ob.Base::fun();
ob.Base::fun(10);
ob.show();
}

运行结果:
图片