C++语法之继承

本文首发于个人博客

继承

  • 继承,可以让子类拥有父类的所有成员(变量\函数)

默认私有继承

例如下面的代码中,类Cat继承类Animal

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Animal{
public:
int age;
void eat(){
cout<< "Animal::eat()"<<endl;
}
};


class Cat : Animal{
public:
int height;
void run(){
cout<< "Cat::run()"<<endl;
}
};

如下调用的时候,发现设置父类的成员变量,访问父类的函数,会报错。

1
2
3
4
5
6
7
8
9
10
int main(){

Cat cat;
cat.age =3; //编译报错
cat.height = 4;
cat.run();
cat.eat(); //编译报错

return 0;
}

修改为public继承

那是因为C++中默认是私有继承。如果子类想访问父类的函数等,需要public继承,改成如下就可以了。

1
2
3
4
5
6
7
class Cat : public Animal{
public:
int height;
void run(){
cout<< "Cat::run()"<<endl;
}
};

c++中没有类似Java中的java.lang.Object 或者ObjectC中的NSObject的基类

对象的内存布局

  • 父类的成员变量在前,子类的成员变量在后

上面的代码反汇编

1
2
3
4
5
6
7
8
9
10
11
12
13
14
  0x100001160 <+0>:  pushq  %rbp
0x100001161 <+1>: movq %rsp, %rbp
0x100001164 <+4>: subq $0x10, %rsp
0x100001168 <+8>: movl $0x0, -0x4(%rbp)
0x10000116f <+15>: movl $0x3, -0x10(%rbp) //cat.age =3
0x100001176 <+22>: movl $0x4, -0xc(%rbp)//cat.height =4
-> 0x10000117d <+29>: leaq -0x10(%rbp), %rdi
0x100001181 <+33>: callq 0x1000011a0 ; Cat::run at Main.cpp:42
0x100001186 <+38>: leaq -0x10(%rbp), %rdi
0x10000118a <+42>: callq 0x1000011e0 ; Animal::eat at Main.cpp:33
0x10000118f <+47>: xorl %eax, %eax
0x100001191 <+49>: addq $0x10, %rsp
0x100001195 <+53>: popq %rbp
0x100001196 <+54>: retq

从上面的汇编可以看到ageheight的地址前面,而且是连续的内存。

多继承

  • C++允许一个类可以有多个父类(不建议使用,会增加程序设计复杂度)

如下所示:GoodStudent同时继承PersonStudent

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
class Person{
public:
int age;
void run(){
cout << "Person::run()" << endl;
}
};

class Student{
public:
int height;
void play(){
cout << "Student::play()" << endl;
}
};

class GoodStudent:public Person,public Student{
public:
int score;
void study(){
cout << "GoodStudent::study()" << endl;
}
};

int main(){
GoodStudent stu;
stu.age = 1;
stu.height = 2;
stu.score = 3;


return 0;
}

对应汇编如下,可知是把父类的成员变量放在了子类中,并且内存是连续的

1
2
3
4
5
6
7
    0x100000f90 <+0>:  pushq  %rbp
0x100000f91 <+1>: movq %rsp, %rbp
0x100000f94 <+4>: xorl %eax, %eax
0x100000f96 <+6>: movl $0x0, -0x4(%rbp)
0x100000f9d <+13>: movl $0x1, -0x10(%rbp)
0x100000fa4 <+20>: movl $0x2, -0xc(%rbp)
-> 0x100000fab <+27>: movl $0x3, -0x8(%rbp)

多个父类同样的函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Person{
public:
int age;
void run(){
cout << "Person::run()" << endl;
}
};

class Student{
public:
int height;
void play(){
cout << "Student::play()" << endl;
}
void run(){
cout << "Student::run()" << endl;
}
};

子类调用的时候需要区分命名空间

1
2
stu.Person::run(); //调用Person的run()
stu.Student::run();//调用Student的run()

菱形继承

  • 如果存在类A,同时类B和类C都继承A,有类D继承B和C。则称为菱形继承

  • 一般在开发中,不会使用菱形继承。

    • 最底下子类从基类继承的成员变量冗余、重复
    • 最底下子类无法访问基类的成员,有二义性

例如如下的代码中,就是菱形继承的例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Person{
public:
int age;
};

class Student:public Person{
public:
int height;

};

class Work:public Person{
public:
int salary;
};

class GoodStudent:public Student,public Work{
public:
int score;

};

在X86架构下,一个GoodStudent对象占用内存20字节,是因为,Student和Work都从Person继承了age,那么GoodStudent中就有2个age了。同时加上score,height, salary就是5个成员变量。

1
2
GoodStudent stu;
cout<< sizeof(stu) <<endl; //输出20

虚继承

为了解决菱形继承的问题,我们可以使用虚继承。

如下代码 Person类被称为虚基类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Person{
public:
int age;
};

class Student:virtual public Person{
public:
int height;

};

class Work:virtual public Person{
public:
int salary;
};

class GoodStudent:public Student,public Work{
public:
int score;

};

这样一个GoodStudent对象里面只有一个age成员变量。当然这样做增加了虚函数表。这里不做展开。

成员访问权限

  • 成员访问权限、继承方式有3种
    • public:公共的,任何地方都可以访问(struct默认)
    • protected:子类内部、当前类内部可以访问
    • private:私有的,只有当前类内部可以访问(class默认)
      • 子类内部访问父类成员的权限,是以下2项中权限最小的那个
    • 成员本身的访问权限
    • 上一级父类的继承方式
      • 开发中用的最多的继承方式是public,这样能保留父类原来的成员访问权限
      • 访问权限不影响对象的内存布局

初始化列表

  • 一种便捷的初始化成员变量的方式
  • 只能用在构造函数中
  • 初始化顺序只跟成员变量的声明顺序有关

下面两种写法是等价的

1
2
3
4
5
6
7
8
9
10
11
12
class Person{
int m_age;
Person(int age){
this->m_age = age;
}
};

class Person{
int m_age;
Person(int age): m_age(age){
}
};

初始化列表与默认参数配合使用

例如下面的代码

1
2
3
4
5
6
7
8
class Person{
public:
int m_age;
int m_height;
Person(int age = 0, int height =0):m_age(age),m_height(height){

}
};

使用的时候如下三种都可以

1
2
3
Person person1;
Person person2(18);
Person person3(18,188);