目录
继承是面向对象三大特性之一。
一、继承的基本语法
继承的好处:减少重复的代码
语法: class 子类:继承方式 父类{};
特点:
- 子类继承父类的特性,并拥有自己特有的成员,子类也称为派生类
- 父类的特性其所有子类均能够继承,父类也称为基类
#include <iostream>
using namespace std;
#include<string>
//创建班级父类
class Class {
public:
//构造函数
Class(string name, string adress) :CName(name), Adress(adress) {}
string CName;
string Adress;
};
//创建学生子类,继承班级类,继承时指定班级类的构造函数的参数值
class Student :public Class {
public:
//构造函数
Student(string name, int age) :Class("5(1)班","求是楼") { //当父类的构造函数不是默认构造而是有参构造时,子类在继承时便确定了父类的构造函数参数值
SName = name;
SAge = age;
}
string SName;
int SAge;
};
void test() {
//实例化学生类,其继承了班级类,能够调用班级类的成员
Student S1("s1", 18);
cout << "班级:" << S1.CName << "\t地址:" << S1.Adress << "\t学生姓名:" << S1.SName << "\t学生年龄" << S1.SAge << endl;
}
int main() {
test();
system("pause");
return 0;
}
班级:5(1)班 地址:求是楼 学生姓名:s1 学生年龄18
请按任意键继续. . .
二、继承方式
1. 公共继承
语法:class 子类:public 父类{};
使用公共继承时,父类的public权限成员在子类中为public权限成员,父类的protected权限成员在子类中为protected权限成员,父类的private权限成员子类继承但不可访问。
2. 保护继承
语法:class 子类:protected 父类{};
使用保护继承时,父类的public权限成员和protected权限成员在子类中为protected权限成员,父类的private权限成员子类继承但不可访问。
3. 私有继承
语法:class 子类:private 父类{};
使用私有继承时,父类的public权限成员和protected权限成员在子类中为private权限成员,父类的private权限成员子类继承但不可访问。
三、继承中的对象模型
子类继承父类时,会继承父类的包括private权限的所有成员,因此其在内存中占用内存空间为父类的所有非静态成员变量和子类特有的所有非静态成员变量所占内存之和。
#include <iostream>
using namespace std;
#include<string>
class A {
public:
int a;
protected:
int b;
private:
int c;
};
class B :public A {
public:
int d;
};
void test() {
cout << "类B所占的内存空间为:" << sizeof(B) << endl; //父类A的私有成员变量c也被继承,因此为a,b,c,d四个int所占内存空间
}
int main() {
test();
system("pause");
return 0;
}
类B所占的内存空间为:16
请按任意键继续. . .
四、继承中的构造函数与析构函数
在子类继承父类时,实例化子类会先进行父类的实例化,因此在子类实例化过程中,编译器会先调用父类的构造函数,再调用子类的构造函数。在释放子类对象时,则先调用子类的析构函数,再调用父类的析构函数。
注:若父类的构造函数为有参构造函数,则在子类的构造函数中需要对父类构造函数的参数赋具体值(见一中的代码段)。
#include <iostream>
using namespace std;
#include<string>
class A {
public:
//构造函数
A() {
cout << "父类A的构造函数。" << endl;
}
//析构函数
~A() {
cout << "父类A的析构函数。" << endl;
}
int a;
protected:
int b;
private:
int c;
};
class B :public A {
public:
//构造函数
B() {
cout << "子类B的构造函数。" << endl;
}
//析构函数
~B() {
cout << "子类B的析构函数。" << endl;
}
int d;
};
void test() {
B b1;
}
int main() {
test();
system("pause");
return 0;
}
父类A的构造函数。
子类B的构造函数。
子类B的析构函数。
父类A的析构函数。
请按任意键继续. . .
五、继承中的同名成员处理
当子类与父类具有同名成员时,父类的同名成员会被隐藏,子类的同名成员可直接使用"."进行调用,若想调用父类的同名成员,需要添加作用域:父类名::同名成员。
1. 非静态同名成员处理
对于非静态同名成员,只需在调用父类同名成员时添加父类作用域即可实现。
#include <iostream>
using namespace std;
#include<string>
class A {
public:
//构造函数
A() {
a = 100;
b = 100;
c = 100;
}
void fun() {
cout << "父类的fun函数。" << endl;
}
int a;
int b;
int c;
};
class B :public A {
public:
//构造函数
B() {
a = 200;
}
void fun() {
cout << "子类的fun函数。" << endl;
}
int a;
};
void test() {
B b1;
b1.fun(); //直接调用fun函数调用的是子类的成员函数
b1.A::fun(); //添加作用域,实现父类同名成员函数fun的调用
cout << "调用子类同名成员:" << b1.a << endl; //直接调用成员变量a调用的是子类的成员变量
cout << "调用父类同名成员:" << b1.A::a << endl; //添加作用域,实现父类同名成员变量a的调用
}
int main() {
test();
system("pause");
return 0;
}
子类的fun函数。
父类的fun函数。
调用子类同名成员:200
调用父类同名成员:100
请按任意键继续. . .
2. 静态同名成员处理
静态成员的访问方式分为1.使用对象名进行访问;2.使用类名进行访问。在子类和父类具有同名静态成员时,直接使用"."调用的是子类的静态同名成员。若想调用父类静态同名成员,则在静态成员前添加作用域即可实现。
#include <iostream>
using namespace std;
#include<string>
class A {
public:
//构造函数
A() {
b = 100;
c = 100;
}
static void fun() {
cout << "父类的static void fun()函数。" << endl;
}
static int a;
int b;
int c;
};
int A::a = 100;
class B :public A {
public:
static void fun() {
cout << "子类的static void fun()函数。" << endl;
}
static int a;
};
int B::a = 200;
void test() {
B b1;
cout << "通过对象访问静态成员" << endl;
b1.fun(); //直接调用fun函数调用的是子类的成员函数
b1.A::fun(); //添加作用域,实现父类同名成员函数fun的调用
cout << "调用子类同名成员:" << b1.a << endl; //直接调用成员变量a调用的是子类的成员变量
cout << "调用父类同名成员:" << b1.A::a << endl; //添加作用域,实现父类同名成员变量a的调用
cout << "-----------------------------------------" << endl;
cout << "通过类名访问静态成员" << endl;
B::fun(); //直接调用fun函数调用的是子类的成员函数
B::A::fun(); //添加作用域,实现父类同名成员函数fun的调用
// A::fun(); //等价于 B::A::fun();
cout << "调用子类同名成员:" << B::a << endl; //直接调用成员变量a调用的是子类的成员变量
cout << "调用父类同名成员:" << A::a << endl; //添加作用域,实现父类同名成员变量a的调用
}
int main() {
test();
system("pause");
return 0;
}
通过对象访问静态成员
子类的static void fun()函数。
父类的static void fun()函数。
调用子类同名成员:200
调用父类同名成员:100
-----------------------------------------
通过类名访问静态成员
子类的static void fun()函数。
父类的static void fun()函数。
调用子类同名成员:200
调用父类同名成员:100
请按任意键继续. . .
六、多继承
1. 多继承语法
语法:class 子类:继承方式 父类1,继承方式 父类2,. . .{};
注:由于多继承容易出现多个父类中具有同名成员的现象,因此在实际开发中一般不使用多继承。
#include <iostream>
using namespace std;
#include<string>
class A1 {
public:
//构造函数
A1() {
a = 100;
}
int a;
};
class A2 {
public:
//构造函数
A2() {
b = 200;
}
int b;
};
class B :public A1, public A2 { //多继承
public:
//构造函数
B() {
c = 300;
}
int c;
};
void test() {
B b1;
cout << "调用子类成员:" << b1.c << endl;
cout << "调用父类1成员:" << b1.b << endl;
cout << "调用父类2成员:" << b1.a << endl;
}
int main() {
test();
system("pause");
return 0;
}
调用子类成员:300
调用父类1成员:200
调用父类2成员:100
请按任意键继续. . .
2. 菱形继承
有四个类A,B1,B2,C,其中B1和B2分别继承A,C继承B1和B2,则A,B1,B2,C形成菱形继承,也称钻石继承。
- 使用作用域实现调用不同类间可能出现的同名成员
- 由于C继承B1和B2,导致C会继承两份来自于A的成员,但是C只需一份A中成员,此时使用虚继承使C中只继承一份A的成员
虚继承:class 子类:virtual 继承方式 父类{};
虚继承中会创建vbptr指针(虚基类指针),该指针指向每个类的vbtable(虚基类表),vbtable中存储的是vbptr指针到继承的成员属性内存的偏移量。因此,通过vbptr指针和vbtable,在只创建一份类成员的情况下,便可使所有子类拥有父类属性。
注:虚继承使所有子类使用vbptr指针访问父类成员,这使得所有子类与父类共用同一内存存储的父类成员,因此在任意一个子类或父类中修改该成员,会使其它类的该成员的值同步发生变化。
#include <iostream>
using namespace std;
#include<string>
class A {
public:
//构造函数
A() {
a = 100;
}
int a;
};
/*
//非虚继承,C中有两个成员a
class B1 :public A {};
class B2 :public A {};
class C1 : public B1, public B2 {}; //A,B1,B2,C1构成菱形继承
void test01() {
C1 c1;
//使用作用域区分同名成员
c1.B1::a = 10;
c1.B2::a = 100;
//cout << c1.a << endl; //无法使用c1.a访问c1的成员a,因为c1中有两个成员a
}
*/
//虚继承,形成虚基类
class B1 :virtual public A {}; //虚基类从基类A中继承的是指针vbptr
class B2 :virtual public A {};
//虚继承,C2中仅有一个成员a
class C2: public B1, virtual public B2 {}; //A,B1,B2,C1构成菱形继承
void test02() {
//虚继承
C2 c2;
c2.B1::a = 10;
c2.B2::a = 100;
cout << "成员a的值为:" << c2.a << endl; //虚继承,成员a只有一个,在任意类中修改会改变其它类的值
}
int main() {
test02();
system("pause");
return 0;
}
成员a的值为:100
请按任意键继续. . .