C++继承分为公有继承(public)、私有继承(private)、保护继承(protected)是常用的三种继承方式。
在C++语言中,一个派生类可以从一个基类派生,也可以从多个基类派生。从一个基类派生的继承称为单继承;从多个基类派生的继承称为多继承。
基类和派生类的关系:
派生类是基类的具体化,而基类是派生类的抽象化。
1.派生类的定义格式:
(1)单继承的定义格式如下:
class <派生类名>:<继承方式> <基类名>
{
<派生类新定义成员>
};
其中,class是关键词,<派生类名>是新定义的一个类的名字,它是从<基类名>中派生的,并且按指定的<继承方式>派生的。
<继承方式>常使用如下三种关键字给予表示:
public 表示公有继承;
private 表示私有继承;
protected 表示保护继承;
class Base
{
public:
~Base()
{
cout<<"~Base"<<endl;
}
};
class Derived:public Base //单继承
{
public:
~Derived()
{
cout<<"~Derived"<<endl;
}
};
多继承的定义格式如下:
class <派生类名>:<继承方式1> <基类名1> <继承方式2><基类名2> ,…
{
<派生类新定义成员>
};
class Base
{
public:
~Base()
{
cout<<"~Base"<<endl;
}
};
class Base1
{
public:
~Base1()
{
cout<<"~Base1"<<endl;
}
};
class Derived:public Base, public Base1 //多继承
{
public:
~Derived()
{
cout<<"~Derived"<<endl;
}
};
由程序实例可见,多继承与单继承的区别从定义格式上看,主要是多继承的基类多于一个。
如果省略继承方式,对’class’将采用私有继承,对’struct’将采用公有继承。
也就是说
class Base1{};
struct Base2{};
class Derive:Base1,Base2{}; // 那么,Derive类将私有继承Base1,公有继承Base2。相当于:class Derive:private Base1,public Base2{};
2.派生类的构成:
派生类的成员包括从基类继承过来的成员和自己增加的成员两大部分。从基类继承过来的成员体现了派生类从基类继承获得的共性,而新增加的成员体现了派生类的个性。
构造一个派生类包括以下三部分工作:
(1)从基类接收成员。
· (2)调整从基类接收的成员。
(3)声明派生类增加的成员。
声明派生类时,应当自己定义派生类的构造函数和析构函数,因为析构函数和构造函数不能从基类继承。
3.继承方式:
公有继承(public)、私有继承(private)、保护继承(protected)是常用的三种继承方式。
(1). 公有继承(public)
公有继承的特点是基类的公有成员和保护成员作为派生类的成员时,它们都保持原有的状态,而基类的私有成员仍然是私有的,不能被这个派生类的子类所访问。
(2). 私有继承(private)
私有继承的特点是基类的公有成员和保护成员都作为派生类的私有成员,并且不能被这个派生类的子类所访问。
(3). 保护继承(protected)
保护继承的特点是基类的所有公有成员和保护成员都成为派生类的保护成员,并且只能被它的派生类成员函数或友元访问,基类的私有成员仍然是私有的。
下面列出三种不同的继承方式的基类特性和派生类特性。
public protected private
公有继承 public protected 不可见
私有继承 private private 不可见
保护继承 protected protected 不可见
(1) 基类成员对其对象的可见性:
公有成员可见,其他不可见。这里保护成员同于私有成员。
(2) 基类成员对派生类的可见性:
公有成员和保护成员可见,而私有成员不可见。这里保护成员同于公有成员。
(3) 基类成员对派生类对象的可见性:
公有成员可见,其他成员不可见。
所以,在公有继承时,派生类的对象可以访问基类中的公有成员;派生类的成员函数可以访问基类中的公有成员和保护成员。这里,一定要区分清楚派生类的对象和派生类中的成员函数对基类的访问是不同的。
class Base //基类
{
public: //基类公有成员
void get_value()
{
cin>>num>>name>>sex;
}
void display()
{
cout<<"num:"<<num<<endl;
cout<<"name:"<<name<<endl;
cout<<"sex:"<<sex<<endl;
}
private: //基类私有成员
int num;
string name;
char sex;
};
class Derived:public Base
{
public:
void display1()
{
cout<<”num:”<
class Base //基类
{
public: //基类公有成员
void get_value()
{
cin>>num>>name>>sex;
}
void display()
{
cout<<"num:"<<num<<endl;
cout<<"name:"<<name<<endl;
cout<<"sex:"<<sex<<endl;
}
private: //基类私有成员
int num;
string name;
char sex;
};
class Derived:private Base
{
public:
void display1()
{
cout<<"age:"<<age<<endl; //引用派生类私有成员,正确
cout<<"addr:"<<addr<<endl; //引用派生类私有成员,正确
}
private:
int age;
string addr;
};
int main()
{
Derived b1; //定义一个Derived类的对象
b1.display(); //错误,私有基类的公用成员函数在派生类中是私有函数
b1.display1(); //正确,display1函数是Derived类的公用函数
b1.age = 18; //错误,类外不能引用派生类的私有成员
system("pause");
return 0;
}
因此,(1)不能通过派生类对象引用私有基类继承过来的任何成员。
(2)派生类的任何成员不能访问私有基类的私有成员,但可以访问私有基类的公有成员。
保护继承:
这种继承方式与私有继承方式的情况相同。两者的区别仅在于对派生类的成员而言,对基类成员有不同的可见性。
上述所说的可见性也就是可访问性。关于可访问性还有另的一种说法。这种规则中,称派生类的对象对基类访问为水平访问,称派生类的派生类对基类的访问为垂直访问。
class Base //基类
{
public: //基类公有成员
void get_value()
{
cin>>num>>name>>sex;
}
void display()
{
cout<<"num:"<<num<<endl;
cout<<"name:"<<name<<endl;
cout<<"sex:"<<sex<<endl;
}
protected: //基类保护成员
int num;
string name;
char sex;
};
class Derived:protected Base
{
public:
void display1()
{
cout<<"age:"<<num<<endl; //引用基类保护成员,正确
cout<<"age:"<<name<<endl; //引用基类保护成员,正确
cout<<"age:"<<sex<<endl; //引用基类保护成员,正确
cout<<"age:"<<age<<endl; //引用派生类私有成员,正确
cout<<"addr:"<<addr<<endl; //引用派生类私有成员,正确
}
private:
int age;
string addr;
};
int main()
{
Derived b1; //定义一个Derived类的对象
b1.display1(); //正确,display1函数是Derived类的公用函数
b1.num = 18; //错误,类外不能引用保护成员
system("pause");
return 0;
}
总结:
(1). 基类的private成员在派生类中是不能被访问的,如果基类成员不想在类外直接被访问
但需要在派生类中能访问,就定义为protected。可以看出保护成员限定符是因继承才出现的。
(2). public继承是一个接口继承,保持is-a原则,每个父类可用的成员对子类也可用,因为每个子类对象也都是一个父类对象(子类中继承父类的成员)。
(3).protetced/private继承是一个实现继承,基类的部分成员并非完全成为子类接口的一部分,是 has-a 的关系原则,所以非特殊情况下不会使用这两种继承关系,在绝大多数的场景下使用的都是公有继承。私有继承以为这is-implemented-in-terms-of(是根据……实现的)。通常比组合(composition)更低级,但当一个派生类需要访问基类保护成员或需要重定义基类的虚函数时它就是合理的。
(4). 不管是哪种继承方式,在派生类内部都可以访问基类的公有成员和保护成员,基类的私有成员存在但是在子类中不可见(不能访问)。
(5). 使用关键字class时默认的继承方式是private,使用struct时默认的继承方式是public,不过最显示的写出继承方式。
(6). 在实际运用中一般使用都是public继承,极少场景下才会使用protetced/private继承.派生类的默认成员函数在继承关系里面,在派生类中如果没有显示定义这六个成员函数,编译系统则会默认合成这六个默认的成员函数。
4.派生类的构造函数和析构函数:
派生类构造函数形式:
派生类构造函数名(总参数表列):基类构造函数名(参数表列),子对象名(参数表列)
{
派生类只能够新增成员初始化语句;
}
执行派生类构造函数的顺序:
(1)调用基类构造函数,对基类数据成员初始化;
(2)调用子对象构造函数,对子对象数据成员初始化;
(3)执行派生类构造函数本身,对派生类数据成员初始化;
#include<stdio.h>
using namespace std;
#include <string>
class Student //声明基类
{
public:
Student(int n, string name) //基类构造函数
{
_num = n;
_name = name;
}
void display() //成员函数,输出基类数据成员
{
cout<<"num:"<<_num<<endl;
cout<<"name:"<<_name<<endl;
}
protected: //保护成员
int _num;
string _name;
};
class Student1: public Student
{
public:
Student1(int n, string name, int n1, string name1, int age, string address)
:Student(n, name),Monitor(n1, name1) //派生类构造函数
{
_age = age;
_address = address;
}
void show()
{
cout<<"This student is:"<<endl;
display(); //输出num和name
cout<<"age:"<<_age<<endl; //输出age
cout<<"address:"<<_address<<endl; //输出address
}
void show_monitor() //成员函数,输出子对象
{
cout<<"Class monitor is:"<<endl;
Monitor.display(); //调用基类成员函数
}
private:
Student Monitor;
int _age;
string _address;
};
int main()
{
Student1 stud1(10010, "wang-li", 10001, "li-sun", 19, "115 BEIJING Road, shanghai");
stud1.show(); //输出学生的数据
stud1.show_monitor(); //输出子对象的数据
system("pause");
return 0;
}
运行结果:
继承关系中构造函数调用顺序:
派生类中对象构造函数—>初始化列表调用基类构造函数—>派生类构造函数体
【说明】
基类没有缺省构造函数,派生类必须要在初始化列表中显式给出基类名和参数列表。
基类没有定义构造函数,则派生类也可以不用定义,全部使用缺省构造函数。
基类定义了带有形参表构造函数,派生类就一定定义构造函数。
继承关系中析构函数调用过程:
class Test1
{
public:
Test1( int data)
{
cout <<"Test1()"<<endl;
}
~Test1 ()
{
cout<< "~Test1()"<<endl ;
}
};
class Test2
{
public:
Test2( int data)
{
cout <<"Test2()"<<endl;
}
~Test2 ()
{
cout<< "~Test2()"<<endl ;
}
};
class Base1
{
public:
Base1( int data)
: _data(data ) //基类构造函数
{
cout <<"Base1()"<<endl;
}
~Base1() //基类析构函数
{
cout<< "~Base1()"<<endl ;
}
protected:
int _data;
};
class Base2
{
public:
Base2( int data)
: _data2(data ) //基类构造函数
{
cout <<"Base2()"<<endl;
}
class Test1
{
public:
Test1( int data)
{
cout <<"Test1()"<<endl;
}
~Test1 ()
{
cout<< "~Test1()"<<endl ;
}
};
class Test2
{
public:
Test2( int data)
{
cout <<"Test2()"<<endl;
}
~Test2 ()
{
cout<< "~Test2()"<<endl ;
}
};
class Base1
{
public:
Base1( int data)
: _data(data ) //基类构造函数
{
cout <<"Base1()"<<endl;
}
~Base1() //基类析构函数
{
cout<< "~Base1()"<<endl ;
}
protected:
int _data;
};
class Base2
{
public:
Base2( int data)
: _data2(data ) //基类构造函数
{
cout <<"Base2()"<<endl;
}
~Base2 () //基类析构函数
{
cout<< "~Base2()"<<endl ;
}
protected:
int _data2;
};
class Derive: public Base1, public Base2 //多继承
{
public:
Derive() //派生类构造函数
: t2 (3)
, t1 (4)
, Base2(0)
, Base1(1)
{
cout <<"Derive()"<<endl;
}
~Derive () //派生类析构函数
{
cout<< "~Derive()"<<endl ;
}
protected:
Test1 t1;
Test2 t2;
};
void Test()
{
Derive d;
}
int main()
{
Test();
system("pause");
return 0;
}
运行结果:
由结果可以看出来,构造函数是先调用两个基类Base1,Base2的构造函数,在调用Test1,Tsae2的构造函数,最后调用派生类Derived构造函数,而析构函数顺序则和构造函数的调用顺序相反,是先调用派生类Derived析构函数,在调用Test2,Tsae1的析构函数,最后调用两个基类Base2,Base1的析构函数。
综上:先构造的后析构,先析构的后构造。
5.继承体系中的作用域
(1). 在继承体系中基类和派生类是两个不同作用域。
(2). 子类和父类中有同名成员,子类成员将屏蔽父类对成员的直接访问。
(在子类成员函数中,可以使用 基类::基类成员 访问)
(3). 注意在实际中在继承体系里面最好不要定义同名的成员。
6.继承与转换–赋值兼容规则(public继承)
(1). 子类对象可以赋值给父类对象
class A
{
public:
void display();
protected:
int _a;
};
class B:public A
{
public:
void display1();
protected:
int _b;
};
int main()
{
A a1; //定义基类A对象
B b1; //定义A的派生类B的对象b1
a1 = b1; //用派生类B对象b1对基类对象a1赋值
system("pause");
return 0;
}
在赋值时时舍弃派生类自己的成员,即为“大材小用”。
注意:赋值后不能通过对象a1访问派生类对象b1的成员,因为b1和a1的成员不同。
(2). 父类对象不能赋值给子类对象–>程序会崩溃(基类对象不包含派生类的对象,无法对派生类成员赋值)
(3). 父类的指针/引用可以指向子类对象
(4). 子类的指针/引用不能指向父类对象(可以通过强制类型转换完成)
#include <iostream>
#include <time.h>
using namespace std;
class B0 //基类
{
public:
void display()
{
cout<<"B0::display()"<<endl;
}
};
class B1:public B0 //派生类
{
public:
void display()
{
cout<<"B1::display()"<<endl;
}
};
class B2:public B0 //派生类
{
public:
void display()
{
cout<<"B2::display()"<<endl;
}
};
void fun(B0 *ptr)
{
ptr->display();
}
int main()
{
B0 b0;
B1 b1;
B2 b2;
fun(&b0);
b0 = b1;
fun(&b0);
b0 = b2;
fun(&b0);
system("pause");
return 0;
}
(5) 如果函数的参数是父类对象或基类对象的引用,相应的实参可以用子类对象。
例如:
class A
{
public:
int num;
};
class B:public A
{
public:
int age;
};
void fun(A &r) //形参是类A的对象的引用变量
{
cout<<r.num<<endl;
}
int main()
{
B b1;
fun(b1);
system("pause");
return 0;
}
7、友元与继承
友元关系不能继承,也就是说基类友元不能访问子类私有和保护成员。
class B0 //基类
{
friend void display();
public:
B0()
:data(10)
{}
private:
int data;
};
void display()
{
cout<<"data:"<<B0::data<<endl;
}
class B1:public B0 //派生类
{
public:
void display1()
{
cout<<"B1::display()"<<endl;
}
};
int main()
{
B1 b;
b.display(); //错误,display函数是基类的友元函数,友元函数不可继承
b.display1();
}
8、继承与静态成员
基类定义了static成员(可以被继承),则整个继承体系里面只有一个这样的成员。无论派生出多少个子类,都只有一个static成员实例。
class Person
{
public :
Person()
{
++ _count;
}
protected :
string _name;
public :
static int _count; // 统计人的个数。
};
int Person::_count = 0;
class Student : public Person //公有继承
{
protected :
int _stuNum ; // 学号
};
class Graduate :public Student
{
protected:
string _seminarCourse; // 研究科目
};
void TestPerson1()
{
Student s1;
Student s2;
Student s3;
Graduate s4;
cout<<"人数:"<<Person::_count<<endl; //4,创建了四个对象,所以计数器为4
Student ::_count = 0;
cout<<"人数:"<<Person::_count<<endl; //0,Student是由Person派生来的类,当派生类的计数器清0时,他其实就是访问的基类里的计数器,因此基类里的计数器也会受影响,从而输出0
}
9.菱形继承:
(1)两个基类有同名成员
class A
{
public:
int a;
void display();
};
class B
{
public:
int a;
void display();
};
class C:public A, public B
{
public:
int b;
void show();
};
void Test()
{
C c1;
c1.a = 3; //错误,基类A和B都有a,无法知道调用的是哪个
c1.display(); //错误,基类A和B都有display函数,无法知道调用的是哪个
c1.A::a = 3; //正确,明确知道调用的是基类A
c1.A::display(); //正确,明确知道调用的是基类A的display函数
}
int main()
{
Test();
system("pause");
return 0;
}
(2)两个基类和派生类三者都有同名函数。
class A
{
public:
int a;
void display();
};
class B
{
public:
int a;
void display();
};
class C:public A, public B
{
public:
int a;
void display();
};
void Test()
{
C c1;
c1.a = 3; //正确,访问派生类C的成员a
c1.display(); //正确,访问派生类C的成员函数
//基类的同名函数在派生类中被屏蔽,成为“不可见”的,即就是派生类的新增的同名函数覆盖了基类中的同名成员。
//同名覆盖:不同的成员函数,只有在函数名和参数个数相同,类型相匹配的条件下才发生函数重载。
}
int main()
{
Test();
system("pause");
return 0;
}
(3)派生类A和派生类B从同一个基类class N
class N
{
public:
int a;
void display()
{
cout<<"A::a = "<<a<<endl;
}
};
class A:public N
{
public:
int a1;
};
class B:public N
{
public:
int a2;
};
class C:public B
{
public:
int a3;
void show()
{
cout<<"a3 = "<<a3<<endl;
}
};
怎样访问派生类A从基类N中继承下来的成员呢?
c1.a = 3;
c1.display(); //错误,访问不明确
c1.N::a = 3;
c1.N::display(); //错误,无法判断类A中从基类N继承下来的成员,还是类B中从基类N继承下来的成员
c1.A::a = 3;
c1.A::display(); //正确,访问类N的派生类A中的基类成员
这样导致了继承中的二义性,为了避免二义性的继承,引入了虚继承(虚基类)
虚基类:
(1)作用:使继承在间接共用基类时只保留一份成员
声明虚基类的一般形式:
class 派生类名 :virtual 基类名
即在声明派生类时,将关键字virtual加在继承方式前面。经过这样的声明后, 当基类通过多条派生路径被一个派生类
继承时,该派生类只继承该基类一次,即就是说,基类成员只保留一次。
class A
{
public:
void fun()
{}
protected:
int _data;
};
class B:virtual public A
{
protected:
int data_b;
};
class C:virtual public A
{
protected:
int data_c;
};
class D:public A, public B
{
public:
void fun_d();
protected:
int data_d;
};
注意:为了保证虚基类在派生类中只继承一次,应当在该基类的所有直接派生类中声明为虚基类。
不声明为虚基类会出现对基类的多次继承,模版如下:
声明为虚基类,模版如下:
(2)虚基类的初始化:
如果虚基类中定义了带参数的构造函数,而且没有定义默认构造函数,则在其所有派生类中,通过构造函数的初始
化列表对虚基类进行初始化。
class A
{
public:
void fun()
{}
A(int i)
{}
protected:
int _data;
};
class B:virtual public A
{
public:
B(int n)
:A(n)
{}
protected:
int data_b;
};
class C:virtual public A
{
public:
C(int n)
:A(n)
{}
protected:
int data_c;
};
class D:public A, public B
{
public:
void fun_d();
D(int n)
:A(n)
,B(n)
,C(n)
{}
protected:
int data_d;
};
(3)虚基类的简单应用实例
#include <iostream>
using namespace std;
#include <string>
//公共基类Preson
class Person
{
public:
Person(string nam, char s,int a) // 构造函数
{
name = nam;
sex = s;
age = a;
}
protected: // 保护成员
string name;
char sex;
int age;
};
//直接派生类Teacher
class Teacher:virtual public Person //声明Preson为公用继承的虚基类
{
public:
Teacher(string nam, char s,int a,string t) //构造函数
:Person(nam, s, a)
{
title = t;
}
protected: //保护成员
string title; //职称
};
//直接派生类Student
class Student:virtual public Person
{
public:
Student(string nam, char s, int a, float sco)
:Person(nam, s, a)
,score(sco)
{}
protected:
float score;
};
//多继承派生类Graduate
class Graduate:public Teacher, public Student
{
public:
Graduate(string nam, char s, int a, string t, float sco, float w)
:Person(nam, s, a)
,Teacher(nam, s, a, t)
,Student(nam, s, a, sco)
,wage(w)
{}
void show()
{
cout<<"name:"<<name<<endl;
cout<<"age:"<<age<<endl;
cout<<"sex:"<<sex<<endl;
cout<<"score:"<<score<<endl;
cout<<"title:"<<title<<endl;
cout<<"wage:"<<wage<<endl;
}
private:
float wage; //工资
};
int main()
{
Graduate grad1("Wang-Li", 'f', 24, "assistant", 89.5, 1234.5);
grad1.show();
system("pause");
return 0;
}
运行结果图: