继承是面向对象的特性之一,可减少重复代码。
语法: class 子类: 继承方式 父类
父类也称作基类,子类也称作派生类
派生类(子类)成员包含两大部分:
一类是从基类(父类)继承过来的,一类是自己增加的成员。从基类继承过来的表现其共性,而新增的成员体现其个性。
- #include
- using namespace std;
-
- //基类(父类)
- class Base
- {
- public:
- void printBase()
- {
- cout << "父类成员函数" << endl;
- }
- };
-
- //派生类(子类)
- class Sub : public Base
- {
- public:
- void printSub()
- {
- cout << "子类成员函数" << endl;
- }
- };
-
- int main()
- {
- //类和对象 - 继承 - 基本语法
- Sub s1;
- s1.printSub();
- s1.printBase();
-
- system("pause");
-
- return 0;
- }
输出结果
子类成员函数
父类成员函数
Go语言中没有class,继承的实现是通过在struct结构体内置匿名成员来实现的。
- package main
-
- import "fmt"
-
- //基类(父类)
- type Base struct {
- }
-
- func (base *Base) printBase() {
- fmt.Println("父类成员函数")
- }
-
- //派生类(子类)
- type Sub struct {
- Base //内置匿名父类结构体
- }
-
- func (sub *Sub) printSub() {
- fmt.Println("子类成员函数")
- }
-
- func main() {
- sub := Sub{}
- sub.printSub()
- sub.printBase()
- }
输出结果
子类成员函数
父类成员函数
三种继承方式:
- public 公共继承:子类继承父类成员时,不能访问父类私有成员,父类中public成员继承后还是public,父类中protected成员继承后还是protected
- protected 保护继承:子类继承父类成员时,不能访问父类私有成员,父类中public成员继承后变成protected,父类中protected成员继承后还是protected
- private 私有继承:子类继承父类成员时,不能访问父类私有成员,父类中public成员与protected成员继承后变成private成员
- #include
- using namespace std;
-
- class Base
- {
- public:
- int a;
- protected:
- int b;
- private:
- int c;
- };
-
- //公有继承
- class Sub : public Base
- {
- public:
- void setVal()
- {
- a = 1;
- b = 2;
- //c = 3; //子类中不能访问父类的私有成员,报错:成员Base::c不可访问
- }
- };
-
- int main()
- {
- //类和对象 - 继承方式 - 公有继承
- //public 公共继承:子类继承父类成员时,不能访问父类私有成员,父类中public成员继承后还是public,父类中protected成员继承后还是protected
- Sub s1;
- s1.a;
- //s1.b; //子类对象不能访问父类的保护成员,报错:成员Base::b不可访问
-
- system("pause");
-
- return 0;
- }
- #include
- using namespace std;
-
- class Base
- {
- public:
- int a;
- protected:
- int b;
- private:
- int c;
- };
-
- //保护继承
- class Sub : protected Base
- {
- public:
- void setVal()
- {
- a = 1; //在子类中,已变为保护成员,子类能够访问,子类对象不能访问
- b = 2;
- //c = 3; //子类中不能访问父类的私有成员,报错:成员Base::c不可访问
- }
- };
-
-
- //继承子类
- class Grand : public Sub
- {
- public:
- void setVal()
- {
- a = 3; //子类中可以访问父类的保护成员
- b = 5; //子类中可以访问父类的保护成员
- }
- };
-
- int main()
- {
- //类和对象 - 继承方式 - 保护继承
- //protected 保护继承:子类继承父类成员时,不能访问父类私有成员,父类中public成员继承后变成protected,父类中protected成员继承后还是protected
- Sub s1;
- //s1.a; //保护继承后,子类对象不能访问父类成员中的公有成员,因为在子类继承中已变成protected,报错:成员Base::a不可访问
- //s1.b; //对象不能访问保护成员,报错:成员Base::b不可访问
-
- Grand g1;
- //g1.a; //对象不能访问保护成员,报错:成员Base::b不可访问
- //g1.b; //对象不能访问保护成员,报错:成员Base::b不可访问
-
- system("pause");
-
- return 0;
- }
- #include
- using namespace std;
-
- class Base
- {
- public:
- int a;
- protected:
- int b;
- private:
- int c;
- };
-
- //私有继承
- class Sub : private Base
- {
- public:
- void setVal()
- {
- a = 1; //在子类中,已变为私有成员,不能子类访问
- b = 2; //在子类中,已变为私有成员,不能子类访问
- //c = 3; //子类中不能访问父类的私有成员,报错:成员Base::c不可访问
- }
- };
-
- //继承子类
- class Grand : public Sub
- {
- public:
- void setVal()
- {
- //a = 1; //子类中不能访问父类的私有成员,报错:成员Base::a不可访问
- }
- };
-
- int main()
- {
- //类和对象 - 继承方式 - 私有继承
- //private 私有继承:子类继承父类成员时,不能访问父类私有成员,父类中public成员与protected成员继承后变成private成员
- Sub s1;
- //s1.a; //私有继承后,子类对象不能访问父类成员中的公有成员,因为在子类继承中已变成private,报错:成员Base::a不可访问
- //s1.b; //私有继承后,子类对象不能访问父类的保护成员,因为在子类继承中已变成private,报错:成员Base::b不可访问
-
- system("pause");
-
- return 0;
- }
Go语言中,没有像C++有public、protected、private的修饰访问权限,是通过首字母大小写来确定访问权限的,方法名、常量、变量名、结构体名等,同包内首字母大小写都可访问(理解为公有的),不同包时只有首字母大写才能被访问(理解为公有的),不同包内首字母小写不能被访问(理解为私有的)。
在base包中定义一个父类结构体
- package base
-
- //基类(父类) - 结构体名首字母大写
- type Base2 struct {
- Name string //首字母大写
- age int //首字母小写
- }
-
- //结构体名首字母小写
- type base3 struct {
- Name string //首字母大写
- age int //首字母小写
- }
在main包中继承base包中的父类结构体
- package main
-
- import (
- "fmt"
- base "testProject/CPlus/2-类/继承方式"
- )
-
- //基类(父类)
- type Base struct {
- Name string //
- age int //
- }
-
- //派生类(子类) - 继承同包中的父类
- type Sub struct {
- Base //内置匿名父类结构体(同包内)
- }
-
- //派生类(子类) - 继承不同包中的父类 - 结构体名首字母大写
- type Sub2 struct {
- base.Base2 //内置匿名父类结构体(不同包)
- }
-
- //派生类(子类) - 继承不同包中的父类 - 结构体名首字母小写
- type Sub3 struct {
- // base.base3 //不能访问不同包下首字母小写的结构体,error: Unexported type "base3" usage
- }
-
- func main() {
- fmt.Println("---- 子类继承同包中的父类 ----")
-
- sub := Sub{}
- sub.Name = "Hello"
- sub.age = 2
- fmt.Printf("Name = %s, age = %d\n", sub.Name, sub.age)
-
- fmt.Println("---- 子类继承不同包中的父类 ----")
-
- sub2 := Sub2{}
- sub2.Name = "World"
- //sub2.age = 23 //error: Unexported field "age" usage
- fmt.Printf("Name = %s, 不能访问小写字母开头的属性 age\n", sub2.Name)
- }
---- 子类继承同包中的父类 ----
Name = Hello, age = 2
---- 子类继承不同包中的父类 ----
Name = World, 不能访问小写字母开头的属性 age
- #include
- using namespace std;
-
- class Base
- {
- public:
- int a;
- protected:
- int b;
- private:
- int c;
- };
-
- class Sub : public Base
- {
- public:
- int d;
- };
-
- int main()
- {
- //类和对象 - 继承 - 对象模型
- cout << "子类占用空间大小:" << sizeof(Sub) << endl;
-
- system("pause");
-
- return 0;
- }
输出结果
子类占用空间大小:16
命令: cl /d1 reportSingleClassLayout类名 "文件名"
reportSingleClassLayout后面连接的是要查看的类名
注意:/d1 此处是数字1,不是小写字母l
F:\CPlus\>cl /d1 reportSingleClassLayoutSub "rs.cpp"
用于 x86 的 Microsoft (R) C/C++ 优化编译器 19.32.31332 版
版权所有(C) Microsoft Corporation。保留所有权利。rs.cpp
class Sub size(16):
+---
0 | +--- (base class Base)
0 | | a
4 | | b
8 | | c
| +---
12 | d
+---
E:\Program\VisualStudio\VC\Tools\MSVC\14.32.31326\include\ostream(301): warning C4530: 使用了 C++ 异常处理程序,但未启用展开语义。请指定 /EHsc
E:\Program\VisualStudio\VC\Tools\MSVC\14.32.31326\include\ostream(294): note: 在编译 类 模板 成员函数“std::basic_ostream> &std::basic_ostream >::operator <<(unsigned int)”时
rs.cpp(23): note: 查看对正在编译的函数 模板 实例化“std::basic_ostream> &std::basic_ostream >::operator <<(unsigned int)”的引用
rs.cpp(23): note: 查看对正在编译的 类 模板 实例化“std::basic_ostream>”的引用
Microsoft (R) Incremental Linker Version 14.32.31332.0
Copyright (C) Microsoft Corporation. All rights reserved./out:rs.exe
rs.obj
- package main
-
- import (
- "fmt"
- "unsafe"
- )
-
- //基类(父类)
- type Base struct {
- a int32
- b int32
- c int32
- }
-
- //派生类(子类)
- type Sub struct {
- Base
- d int32
- }
-
- func (s *Sub) print() {
- fmt.Println(s)
- }
-
- func main() {
- sub := Sub{}
- fmt.Printf("子类占用空间大小 : %d\n", unsafe.Sizeof(sub))
- }
输出结果
子类占用空间大小 : 16
- 构造函数顺序:父类先构造,子类再构造
- 析构函数顺序:子类先析构,父类再析构;与构造函数顺序相反
- #include
- using namespace std;
-
- class Base
- {
- public:
- Base()
- {
- cout << "父类构造函数" << endl;
- }
-
- ~Base()
- {
- cout << "父类析构函数" << endl;
- }
- };
-
- class Sub : public Base
- {
- public:
- Sub()
- {
- cout << "子类构造函数~" << endl;
- }
-
- ~Sub()
- {
- cout << "子类析构函数~" << endl;
- }
- };
-
- void test()
- {
- Sub s1;
- }
-
- int main()
- {
- //类和对象 - 继承 - 构造函数与析构函数顺序
- test();
-
- system("pause");
-
- return 0;
- }
父类构造函数
子类构造函数~
子类析构函数~
父类析构函数
子类与父类出现同名的成员时:
- 访问子类同名成员,直接访问即可
- 访问父类同名成员,需要加作用域
子类中出现和父类同名的成员函数,子类的同名成员会隐藏掉父类中所有的同名成员函数(成员函数重载时会有多个同名函数),如果子类想访问父类中被隐藏掉的同名成员函数,需要加作用域。
- #include
- using namespace std;
-
- class Base
- {
- public:
- int a;
- Base() {
- a = 3;
- }
-
- void f1()
- {
- cout << "父类中同名成员函数f1()" << endl;
- }
-
- void f1(int _a)
- {
- cout << "父类中同名成员函数f1(int), 参数值 = " << _a << endl;
- }
- };
-
- class Sub : public Base
- {
- public:
- int a; //成员属性与父类同名
- Sub()
- {
- a = 5;
- }
-
- void f1() //成员函数与父类同名
- {
- cout << "子类中同名成员函数f1()" << endl;
- }
-
- };
-
- int main()
- {
- //类和对象 - 继承 - 继承同名成员处理方式
- /*
- 访问子类同名成员,直接访问即可
- 访问父类同名成员,需要加作用域
- */
- Sub s1;
- cout << "访问子类同名成员属性, a = " << s1.a << endl;
- cout << "访问父类同名成员属性, a = " << s1.Base::a << endl;
-
- s1.f1();
- s1.Base::f1();
- s1.Base::f1(8);
-
- system("pause");
-
- return 0;
- }
输出结果
访问子类同名成员属性, a = 5
访问父类同名成员属性, a = 3
子类中同名成员函数f1()
父类中同名成员函数f1()
父类中同名成员函数f1(int), 参数值 = 8
注:Go语言中没有重载函数
- package main
-
- import (
- "fmt"
- )
-
- //基类(父类)
- type Base struct {
- a int
- }
-
- func (base *Base) print() {
- fmt.Println("父类同名函数中,父类同名属性 a = ", base.a)
- }
-
- //派生类(子类)
- type Sub struct {
- Base //内置匿名父类结构体
- a int //成员属性与父类同名
- }
-
- //成员函数与父类同名
- func (sub *Sub) print() {
- fmt.Println("子类同名函数中,子类同名属性 a = ", sub.a)
- }
-
- func main() {
- fmt.Println("---- 子类成员与父类同名 ----")
-
- sub := Sub{}
- sub.a = 3
- sub.print()
- sub.Base.a = 5 //访问父类成员属性
- sub.Base.print() //访问父类成员函数
- }
输出结果
---- 子类成员与父类同名 ----
子类同名函数中,子类同名属性 a = 3
父类同名函数中,父类同名属性 a = 5
继承中同名的静态成员在子类对象中的访问方式,与非静态成员访问方式一样:
- 访问子类同名成员,直接访问即可
- 访问父类同名成员,需要加作用域
可通过对象访问,也可通过类名访问
- #include
- using namespace std;
-
- class Base
- {
- public:
- static int a;
-
- static void f1()
- {
- cout << "父类中同名成员函数f1()" << endl;
- }
-
- static void f1(int _a)
- {
- cout << "父类中同名成员函数f1(int),参数值 = " << _a << endl;
- }
- };
- int Base::a = 5;
-
- class Sub : public Base
- {
- public:
- static int a; //成员属性与父类同名
-
- static void f1() //成员函数与父类同名
- {
- cout << "子类中同名成员函数f1()" << endl;
- }
- };
-
- int Sub::a = 3;
-
- int main()
- {
- //类和对象 - 继承 - 继承同名成员处理方式 - 静态成员
- /*
- 访问子类同名成员,直接访问即可
- 访问父类同名成员,需要加作用域
- */
-
- Sub s1;
- cout << "--- 通过对象访问 ---" << endl;
- cout << "访问子类同名成员属性, a = " << s1.a << endl;
- cout << "访问父类同名成员属性, a = " << s1.Base::a << endl;
- s1.f1();
- s1.Base::f1();
- s1.Base::f1(8);
-
- cout << endl << "--- 通过类名访问 ---" << endl;
- cout << "访问子类同名成员属性, a = " << Sub::a << endl;
- cout << "访问父类同名成员属性, a = " << Sub::Base::a << endl;
- Sub::f1();
- Sub::Base::f1();
- Sub::Base::f1(12);
-
- system("pause");
-
- return 0;
- }
输出结果
--- 通过对象访问 ---
访问子类同名成员属性, a = 3
访问父类同名成员属性, a = 5
子类中同名成员函数f1()
父类中同名成员函数f1()
父类中同名成员函数f1(int),参数值 = 8
--- 通过类名访问 ---
访问子类同名成员属性, a = 3
访问父类同名成员属性, a = 5
子类中同名成员函数f1()
父类中同名成员函数f1()
父类中同名成员函数f1(int),参数值 = 12
C++ 允许一个类继承多个类,多继承可能会引发父类中有同名成员出现,需要加作用域区分。
语法: class 子类: 继承方式 父类1, 继承方式 父类2 ......
- #include
- using namespace std;
-
- //父类
- class Base1
- {
- public:
- int a;
- int b;
-
- Base1()
- {
- a = 11;
- b = 12;
- }
- };
-
- //父类
- class Base2
- {
- public:
- int a; //同名成员属性
- int c;
-
- Base2()
- {
- a = 21;
- c = 22;
- }
- };
-
- //子类 - 多继承
- class Sub : public Base1, public Base2
- {
- public:
- int d;
- int e;
-
- Sub()
- {
- d = 31;
- e = 32;
- }
- };
-
- int main()
- {
- //类和对象 - 继承 - 多继承
-
- Sub s1;
- cout << "访问子类自己的成员属性: d = " << s1.d << endl;
- cout << "访问父类Base1的成员属性: b = " << s1.Base1::b << endl;
- cout << "访问父类Base2的成员属性: c = " << s1.Base2::c << endl;
- cout << "访问父类Base1的同名成员属性: a = " << s1.Base1::a << endl;
- cout << "访问父类Base2的同名成员属性: a = " << s1.Base2::a << endl;
-
- system("pause");
-
- return 0;
- }
输出结果
访问子类自己的成员属性: d = 31
访问父类Base1的成员属性: b = 12
访问父类Base2的成员属性: c = 22
访问父类Base1的同名成员属性: a = 11
访问父类Base2的同名成员属性: a = 21
Go语言可以通过匿名组合方式实现多继承(组合继承)。
- package main
-
- //基类(父类)
- type Base1 struct {
- a int
- b int
- }
-
- //基类(父类)
- type Base2 struct {
- a int //同名成员属性
- c int
- }
-
- //派生类(子类)
- type Sub struct {
- Base1 //内置匿名父类结构体
- Base2
- d int
- }
-
- func main() {
- sub := Sub{}
- sub.d = 31 //访问子类自己的成员属性
- sub.b = 12 //访问父类Base1的成员属性
- sub.c = 22 //访问父类Base2的成员属性
- sub.Base1.a = 11 //访问父类Base1的同名成员属性
- sub.Base2.a = 21 //访问父类Base2的同名成员属性
- }
两个派生类继承同一个基类,又有某类同时继承了两个派生类,这种继承被称为菱形继承,或钻石继承。如下图所示,像个菱形一样的继承:

举个栗子,如下图所示:

- #include
- using namespace std;
-
- //基类 - 动物
- class Animal
- {
- public:
- int age;
- };
-
- //派生类 - 羊 (继承动物类)
- class Sheep : public Animal
- {
-
- };
-
- //派生类 - 骆驼 (继承动物类)
- class Camel : public Animal
- {
-
- };
-
- //派生类 - 羊驼 (继承羊类 与 骆驼类)
- class Alpaca : public Sheep, public Camel
- {
-
- };
-
- int main()
- {
- //类和对象 - 菱形继承
- Alpaca alpaca;
- alpaca.Sheep::age = 18;
- alpaca.Camel::age = 20;
-
- //在菱形继承中,两个父类拥有相同的数据,需要加以作用域区分
- //在实际中,羊驼的年龄记录一个就够了,不需要两份数据,导致数据不一致,也导致资源浪费
- cout << "在父类Sheep中,羊驼的年龄 age = " << alpaca.Sheep::age << endl;
- cout << "在父类Camel中,羊驼的年龄 age = " << alpaca.Camel::age << endl;
-
- system("pause");
-
- return 0;
- }
输出结果
在父类Sheep中,羊驼的年龄 age = 18
在父类Camel中,羊驼的年龄 age = 20
在实际中,羊驼的年龄记录一个就够了,不需要两份数据,导致数据不一致,也导致资源浪费。
查看内存对象模型命令:cl /d1 reportSingleClassLayoutAlpaca "文件名.cpp"

利用虚继承解决菱形继承问题。
虚继承:在派生类前加上关键字 virtual
由于派生类为虚继承,所以基类变为虚基类
- #include
- using namespace std;
-
- //基类 - 动物 - 由于派生类为虚继承,所以基类变为虚基类
- class Animal
- {
- public:
- int age;
- };
-
- //派生类 - 羊 (继承动物类) - 虚继承
- class Sheep : virtual public Animal
- {
-
- };
-
- //派生类 - 骆驼 (继承动物类) - 虚继承
- class Camel : virtual public Animal
- {
-
- };
-
- //派生类 - 羊驼 (继承羊类 与 骆驼类)
- class Alpaca : public Sheep, public Camel
- {
-
- };
-
- int main()
- {
- //类和对象 - 解决菱形继承问题 - 虚继承
- //菱形继承问题:在实际中,羊驼的年龄记录一个就够了,不需要两份数据,导致数据不一致,也导致资源浪费
- Alpaca alpaca;
- alpaca.Sheep::age = 18;
- alpaca.Camel::age = 20;
-
- cout << "在父类Sheep中,羊驼的年龄 age = " << alpaca.Sheep::age << endl;
- cout << "在父类Camel中,羊驼的年龄 age = " << alpaca.Camel::age << endl;
- cout << "在自己类Alpaca中,羊驼的年龄 age = " << alpaca.age << endl;
-
- system("pause");
-
- return 0;
- }
输出结果
在父类Sheep中,羊驼的年龄 age = 20
在父类Camel中,羊驼的年龄 age = 20
在自己类Alpaca中,羊驼的年龄 age = 20
命令: cl /d1 reportSingleClassLayoutAlpaca "文件名.cpp"

vbptr 虚基类指针,v - virtual, b - base, ptr - pointer
vbptr指向一个vbtable 虚基类表

- package main
-
- import "fmt"
-
- //基类 - 动物
- type Animal struct {
- age int
- }
-
- //派生类 - 羊 (继承动物类)
- type Sheep struct {
- Animal
- }
-
- //派生类 - 骆驼 (继承动物类)
- type Camel struct {
- Animal
- }
-
- //派生类 - 羊驼 (继承羊类 与 骆驼类)
- type Alpaca struct {
- Sheep
- Camel
- }
-
- func main() {
- //类和对象 - 菱形继承
- alpaca := Alpaca{}
- alpaca.Sheep.age = 18
- alpaca.Camel.age = 20
-
- fmt.Println("在父类Sheep中,羊驼的年龄 age = ", alpaca.Sheep.age)
- fmt.Println("在父类Camel中,羊驼的年龄 age = ", alpaca.Camel.age)
- }
输出结果
在父类Sheep中,羊驼的年龄 age = 18
在父类Camel中,羊驼的年龄 age = 20
同C++中抛出的问题一样:在实际中,羊驼的年龄记录一个就够了,不需要两份数据,导致数据不一致,也导致资源浪费。
Go语言中没有virtual来表示虚继承,可以通过 interface + struct 来实现虚基类的用法,需要实现interface中定义的方法。
- package main
-
- import "fmt"
-
- //接囗(虚基类) - 动物
- type IAnimal interface
- {
- setAge(age int)
- getAge() int
- }
-
- //接囗实现 - 动物
- type AnimalImpl struct {
- age int
- }
-
- func (m *AnimalImpl) setAge(age int) {
- m.age = age
- }
- func (m *AnimalImpl) getAge() int {
- return m.age
- }
-
- //派生类 - 羊 (继承接囗)
- type Sheep struct {
- IAnimal
- }
-
- //派生类 - 骆驼 (继承接囗)
- type Camel struct {
- IAnimal
- }
-
- //派生类 - 羊驼 (继承羊类 与 骆驼类)
- type Alpaca struct {
- Sheep
- Camel
- }
-
- func main() {
- //实现IAnimal接囗动物变量
- var animalImpl = AnimalImpl{}
-
- //羊驼变量
- var alpaca = Alpaca{
- Sheep{&animalImpl},
- Camel{&animalImpl},
- }
-
- alpaca.Sheep.setAge(18)
- alpaca.Camel.setAge(20)
-
- fmt.Println("在父类Sheep中,羊驼的年龄 age = ", alpaca.Sheep.getAge())
- fmt.Println("在父类Camel中,羊驼的年龄 age = ", alpaca.Camel.getAge())
- }
输出结果
在父类Sheep中,羊驼的年龄 age = 20
在父类Camel中,羊驼的年龄 age = 20