参考书目:《C++面向对象程序设计》—— 谭浩强 《C++程序设计:思想与方法》—— 翁惠玉
继承是自然界的一个普遍的重要特性。派生使新类在继承共性的同时,具有了更加丰富多彩的个性。在面向对象程序设计中,继承是C++实现软件重用的主要手段。一般采用层次分类方法来描述事物之间的关系。
类的层次结构关系总结:
类的层次结构图中,下层类是上层类的特殊类。
下层类自动具有上层类的特性,同时也具有自身新的特性。
越往层次结构图的下层,其特性越具体化。
这种从上到下的层次结构关系体现了继承与派生的过程。
C++面向对象技术也采用了这种继承机制。
注:1.由基类派生出新类的过程称为派生(派生类自身也可以作为基类派生出新的派生类)。
2.继承是指派生类自动拥有基类的属性和行为特征(派生类自动拥有基类的属性和行为,并表现出自身新的属性和行为特征 )。
类的继承和派生机制使程序员无须修改已有的类,只需在既有类的基础上,根据问题的实际需要,通过增加部分代码或修改少量代码而得到新的类(派生类),从而很好的解决了程序代码的重用问题。
C++的继承机制:
所谓继承是在已存在的类A 的基础上建立一个新类B。类A称为基类(base class)或父类,类B 称作派生类(derived class)或子类。子类从父类获得其已有的特性,这种现象称作类的继承。从另一个角度看从父类产生子类,称作类的派生。
一个基类可以派生出多个派生类,每个派生类又可以作为基类再派生出新的派生类。一个派生类只从一个基类派生,称作单继承。
具体过程如图所示:
一个派生类也可从多个基类派生,也就是说一个派生类可以有两个或多个基类。一个派生类有两个或多个基类的称作多重继承。
具体过程如图所示:
派生类声明的格式:class 派生类名: [继承方式] 基类名{ 派生类新增成员声明 } ;
继承方式包括:public、private、protected。如果省略,系统默认为private。
例:假定已经声明一个基类student,在它基础上通过单继承建立一个派生类student1。
class Student
{
private :
int num;
string name;
char sex;
public:
void display( )
{
cout<<"num: "<<num<<endl;
cout<<"name: "<<name<<endl;
cout<<"sex: "<<sex<<endl;
}
};
class Student1: public Student
{
private:
int age;
string addr;
public:
void display_1()
{
cout <<"age: "<<age<<endl;
cout <<"address: "<<addr<<endl;
}
};
派生类中的成员包括从基类继承过来的成员和自己增加的成员。继承基类成员体现了同一基类的派生类都具有的共性,而新增加的成员体现了派生类的个性。
构造一个派生类的几部分工作:
派生类中包含了基类成员和派生类成员,就产生了这两部分成员的关系和访问属性的问题。这个关系由基类成员的访问属性和派生类的继承方式组合决定。
从一个基类派生一个类的一般格式:
当派生类的继承方式为public(公有)属性时,在派生类中,基类的公有成员和保护成员在派生类中的访问属性没有变化,派生类的成员无法直接访问基类的私有成员。
class Point //基类Point类的声明
{
public: //公有函数成员
void InitP(float xx = 0, float yy = 0) { X = xx; Y = yy; }
void Move(float xOff, float yOff) { X += xOff; Y += yOff; }
float GetX() { return X; }
float GetY() { return Y; }
private: //私有数据成员
float X, Y;
};
class Rectangle : public Point //派生类声明
{
public: //新增公有函数成员
void InitR(float x, float y, float w, float h){
InitP(x, y); //调用基类公有成员函数
W = w; H = h;}
float GetH() { return H; }
float GetW() { return W; }
private: //新增私有数据成员
float W, H;
};
int main()
{
Rectangle rect;
rect.InitR(2, 3, 20, 10);
//通过派生类对象访问基类公有成员
rect.Move(3, 2);
cout << rect.GetX() << ','
<< rect.GetY() << ','
<< rect.GetH() << ','
<< rect.GetW() << endl;
return 0;
}
在派生类中,基类的公有成员和保护成员作为派生类的私有成员,派生类的成员可以直接访问它们,而派生类的成员无法直接访问基类的私有成员。私有继承之后,全部基类成员在派生类中都成为了私有成员或不可访问的成员,无法进一步派生。
当派生类的继承方式为protected继承属性时,在派生类中,基类的公有成员和保护成员均作为派生类的保护成员,派生类的成员可以直接访问它们,而派生类的成员无法访问基类的私有成员。保护继承可以进一步派生,而私有继承则不可以。
归纳总结:
多级派生关系如图所示:若类A 为基类,类B是类A的派生类,类C是类B的派生类,则类C也是类A的派生类。类B是类A的直接派生类,类C是类A的间接派生类。类 A是类B的直接基类,是类C的间接基类。
多级派生类的访问属性:
class A //基类
{
private:
int ka;
public:
int ia;
protected:
void fa( );
int ja;
};
class B: public A // public方式
{
private:
int mb;
public:
void fb1( );
protected:
void fb2( );
};
class C: protected B // protected方式
{
private:
int nc;
public:
void fc1( );
};
简单派生类只有一个基类,而且只有一级派生,在派生类的数据成员中不包含基类的对象(即子对象)。在定义派生类的构造函数时除了对自己的数据成员进行初始化外,还必须调用基类的构造函数初始化基类的数据成员。
构造函数格式如下:派生类名::派生类名(基类所需的形参,本类成员所需的形参):基类名(基类参数表){本类成员初始化赋值语句;};
基类参数表列出传递给基类构造函数的实参,是派生类构造函数总参数表中的参数。用派生类构造函数的形参做基类构造函数的实参。
简单派生类的构造函数
class B
{
private:
int b;
public:
B();
B(int i);
void Print() const;
};
class C:public B
{
private:
int c;
public:
C();
C(int i, int j);
void Print() const;
};
B::B()
{
b = 0; cout << "调用B的默认构造函数." << endl;
}
B::B(int i)
{
b = i; cout << "调用的构造函数." << endl;
}
void B::Print() const
{
cout << b << endl;
}
C::C()
{
c = 0;
cout << "调用C的默认构造函数." << endl;
}
C::C(int i, int j) :B(i)
{
c = j;
cout << "调用C的构造函数." << endl;
}
void C::Print() const
{
B::Print(); cout << c << endl;
}
void main()
{
C obj(5, 6);
obj.Print();
}
类的数据成员除了是标准类型或系统提供的类型如string外,还可以是类类型,如声明一个类时包含类类型的数据成员:Student s1 ;
派生类构造函数一般形式:派生类名::派生类名 (总参数表):基类名(实参表 ), 子对象名(参数表){派生类新增成员的初始化语句;}
派生类构造函数的任务包括:
对基类数据成员初始化
对子对象的数据成员初始化
对派生类的数据成员初始化
注:不能在声明派生类时对子对象初始化,系统在建立派生类对象时调用派生类构造函数对子对象进行初始化。
执行派生类构造函数的顺序是:
调用基类构造函数,初始化基类数据成员。
调用子对象构造函数,初始化子对象数据成员。
执行派生类构造函数,初始化派生类数据成员。
注:编译系统在此根据参数名(而不是参数的顺序)决定各参数表中参数之间的传递关系。如有多个子对象,要逐个列出子对象及其参数表。
#include
#include
using namespace std;
class Student //声明基类
{
public: //公用部分
Student(int n, string nam) //基类构造函数
{num = n; name = nam;}
void display()
{cout << "学号:" << num << endl << "姓名:" << name << endl;}
protected: //保护部分
int num;
string name;
};
class Student1 : public Student // public继承方式
{
private: // 派生类的私有数据
Student monitor; // 定义子对象(班长)
int age;
string addr;
public:
Student1(int n, string nam, int n1, string nam1, int a, string ad) :Student(n, nam), monitor(n1, nam1){age = a; addr = ad;}
void show()
{
cout << "这个学生是:" << endl;
display(); // 输出num和name
cout << "年龄: " << age << endl;
cout << "地址: " << addr << endl;
}
void show_monitor()
{
cout << endl << "班长是:" << endl;
monitor.display(); //调用基类成员函数
}
};
int main()
{
Student1 stud1(101, "王力",110,"李军",19,"上海市北京路115号");
stud1.show(); // 输出第一个学生的数据
stud1.show_monitor(); // 输出子对象的数据
return 0;
}
一个类可以派生出一个派生类,派生类还可以继续派生,形成派生的层次结构。
可以按照前面派生类构造函数的规则逐层写出各个派生类的构造函数。
基类的构造函数首部:
Student(int n, string nam );
派生类Student1的构造函数首部:
Student1(int n,string nam,int a):Student(n,nam);
派生类Student2的构造函数首部:
Student2(int n,string nam,int a,int s):Student1(n,nam,a);
写派生类构造函数的规则是,只须调用其直接基类的构造函数即可,不要列出每一层派生类的构造函数。
在声明Student2类对象时,调用Student2构造函数,在执行Student2构造函数时,先调用Student1构造函数,在执行Student1构造函数时,先调用基类Student构造函数。
初始化的顺序是:
①先初始化基类的数据成员num和name
②再初始化Student1的数据成员age
③最后初始化Student2的数据成员score
class Student //声明基类
{
public:
Student(int n, string nam) //基类构造函数
{num = n; name = nam;}
void display() //输出基类数据成员
{cout << "num:" << num << endl; cout << "name:" << name << endl;}
protected: //保护部分
int num;
string name;
};
class Student1 : public Student //声明公用派生类Student1
{
public:
Student1(int n, string nam, int a) :Student(n, nam){age = a;} //在此处只对派生类新增的数据成员初始化
void show() //输出num,name和age
{display(); cout << "age: " << age << endl;}
private: //派生类的私有数据
int age; //增加一个数据成员
};
class Student2 :public Student1 //声明间接公用派生类student2
{
public:
Student2(int n, string nam, int a, int s) :Student1(n, nam, a)
{score = s;}
void show_all() // 输出全部数据成员
{show(); cout << "score:" << score << endl;}
private:
int score; //增加一个数据成员
};
int main()
{
Student2 stud(10010, "李明", 17, 89);
stud.show_all(); //输出学生的全部数据
return 0;
}
class A
{
public:
A() { a = 0; cout << "A类无参构造函数被调用" << endl; }
A(int i) { a = i; cout << "A类有参构造函数被调用" << endl; }
void print() { cout << a << ","; }
int b;
private:
int a;
};
class B : public A
{
public:
B() { b1 = b2 = 0; }
B(int i) { b1 = 0; b2 = i; }
B(int i, int j, int k) :A(i), b1(j), b2(k) { }
void print(){A::print(); cout << b1 << "," << b2 << endl;}
private:
int b1, b2;
};
int main()
{
B b1, b2(5), b3(1, 2, 3); b1.print(); b2.print(); b3.print();
return 0;
}
类派生时,派生类不能继承基类的析构函数,撤销派生类对象时,需要派生类的析构函数去调用基类的析构函数。
析构顺序如下:先调用派生类的析构函数——>子对象的析构函数——>基类的析构函数。
class B
{
private:
int b;
public:
B() { b = 0; cout << "调用B的默认构造函数." << endl; }
B(int i) { b = i; cout << "调用B的构造函数." << endl; }
~B() { cout << "调用B的析构函数." << endl; }
void Print() const { cout << b << endl; }
};
class C :public B
{
private: int c;
public:
C() { c = 0; cout << "调用C的默认构造函数." << endl; }
C(int i, int j) :B(i) { c = j; cout << "调用C的构造函数." << endl; }
~C() { cout << "调用C的析构函数." << endl; }
void Print() const { B::Print(); cout << c << endl; }
};
void main()
{
C obj(5, 6);
obj.Print();
}
只有一个基类的派生方式称为单继承。当派生类同时具有两个或两个以上的基类时称为多继承。
class 派生类名:继承方式1 基类名1,继承方式2 基类名2,…{ 成员声明;}
注:每一个“继承方式”,只用于限制对紧随其后之基类的继承。若缺省,系统默认为私有继承方式。
class A
{
public:
void setA(int);
void showA();
private:
int a;
};
class B
{
public:
void setB(int);
void showB();
private:
int b;
};
void A::setA(int x) { a=x; }
void B::setB(int x) { b=x; }
class C : public A, private B
{
public:
void setC(int, int, int);
void showC();
private:
int c;
};
void C::setC(int x, int y, int z)
{
setA(x); setB(y); c=z;
}
int main()
{
C obj;
obj.setA(5);
obj.showA();
obj.setC(6,7,9);
obj.showC();
return 0;
}
派生类的构造函数形式:派生类构造函数名(总参数表):基类1构造函数(参数表),基类2构造函数(参数表),基类3构造函数(参数表)… { 派生类新增成员初始化语句 }
注:1.各基类的排列顺序不分先后,系统调用基类构造函数的顺序就是声明派生类时基类的出现顺序。2.多继承析构函数的执行顺序与多继承方式下构造函数的执行顺序完全相反,首先对派生类新增的数据成员进行清理,再对派生类对象成员进行清理,最后才对基类继承来的成员进行清理。
class Base1
{
int x;
public:
Base1(int a) { x = a; cout << "1的构造函数!\n"; }
~Base1() { cout << "1的析构函数!\n"; }
};
class Base2
{
int y;
public:
Base2(int a) { y = a; cout << "2的构造函数!\n"; }
~Base2() { cout << "2的析构函数!\n"; }
};
class Derived :public Base2, public Base1
{
int z; Base1 b1, b2;
public:
Derived(int a, int b) :
Base1(a), Base2(20), b1(200), b2(a + b){z = b; cout << "派生类的构造函数!\n";}
~Derived() { cout << "派生类的析构函数!\n"; }
};
void main(void)
{
Derived c(100, 200);
}
多重继承最常见的问题是派生类继承基类同名成员而产生的二义性问题。
注:1.在多重继承时,基类与派生类之间,或基类之间出现同名成员时,将出现访问时的二义性(不确定性)——采用虚函数或同名隐藏规则来解决。2.当派生类从多个基类派生,而这些基类又从同一个基类派生,则在访问此共同基类中的成员时,将产生二义性——采用虚基类来解决。
class B1
{
public:
int nV;
void fun(){cout << "Member of B1" << endl;}
};
class B2
{
public:
int nV;
void fun(){cout << "Member of B2" << endl;}
};
class D1 : public B1, public B2
{
public:
int nV; //同名数据成员
void fun(){cout << "Member of D1" << endl;}
};
void main()
{
D1 d1;
d1.nV = 1;
d1.fun();
d1.B1::nV = 2;
d1.B1::fun();
d1.B2::nV = 3;
d1.B2::fun();
}
同名隐藏规则——当派生类与基类中有相同成员时:
如果一个派生类有多个直接基类,而这些直接基类又有一个共同的基类,在派生类中会保留这个间接共同基类数据成员的多个同名成员。如果不希望在派生类中保留间接共同基类的多个同名成员,C++ 提供了虚基类的方法,使派生类在继承间接共同基类时只保留一份成员。
如果在虚基类中定义了带参数的构造函数,而且没定义默认构造函数,要求在它的所有派生类(直接和间接)中,通过构造函数的初始化表对虚基类进行初始化。
class A
{ A (int k) { } … … };
class B: virtual public A
{ B (int n ):A(n){ }… … };
class C: virtual public A
{ C (int n ):A(n){ } … … };
class D: public B,public C
{ D (int n ):A(n),B(n),C(n) { } … … };
class A
{
public:
int x;
A(int a = 0) { x = a; }
};
class B :public virtual A
{
public:
int y;
B(int a = 0, int b = 0) : A(a) { y = b; }
};
class C :public virtual A
{
public:
int z;
C(int a = 0, int c = 0) :A(a) { z = c; }
};
class D :public B, public C
{
public:
int dx;
D(int a1, int b, int c, int d, int a2) :B(a1, b), C(a2, c){dx = d;}
};
void main(void)
{
D d1(10, 20, 30, 40, 50);
cout << d1.x << endl;
d1.x = 400;
cout << d1.x << endl;
cout << d1.y << endl;
}
虚基类构造函数调用顺序的规定:
多继承构造函数调用顺序的规定:
class OBJ1
{
public: OBJ1() { cout << "调用OBJ1类构造函数" << endl; }
};
class OBJ2
{
public: OBJ2() { cout << "调用OBJ2类构造函数" << endl; }
};
class Base1
{
public: Base1() { cout << "调用Base1类构造函数" << endl; }
};
class Base2
{
public: Base2() { cout << "调用Base2类构造函数" << endl; }
};
class Base3
{
public: Base3() { cout << "调用Base3类构造函数" << endl; }
};
class Base4
{
public: Base4() { cout << "调用Base4类构造函数" << endl; }
};
class Derived :public Base1, virtual public Base2,public Base3, virtual public Base4
{
public:
Derived() :Base4(), Base3(), Base2(), Base1(), obj1(), obj2(){cout << "调用派生类构造函数成功!" << endl;}
protected:
OBJ1 obj1;
OBJ2 obj2;
};
int main()
{
Derived aa;
cout << "派生类对象 aa 构造成功,谢谢!" << endl;
return 0;
}
一个公有派生类的对象在使用上可以被当作基类的对象,反之则禁止,称为赋值兼容规则。
具体表现在:
如(1):
A a1; // 定义基类 A 对象 a1
B b1; // 定义类 A 的公用派生类 B 的对象 b1
a1=b1; // 用派生类 B 对象 b1 对基类对象 a1 赋值,在赋值时舍弃派生类自己的成员 。
注:赋值后不能企图通过对象 a1 去访问派生类对象 b1 的成员,因为 b1 的成员与 a1 的成员是不同的。
赋值兼容规则:
如(2):
A a1; // 定义基类 A 对象 a1
B b1; // 定义公用派生类 B 对象 b1
A&r=a1; // 定义基类 A 对象的引用,并用 a1 初始化。
注:1.可以用子类对象初始化引用变量 r,将最后一行改为: A& r=b1;2.保留上面第 3 行 “A& r=a1;” ,而对 r 重新赋值: r=b1;
如(3):
函数fun:
void fun(A& r) // 形参是类 A 的对象的引用
{cout<<r.num<<endl; }
由于子类对象与派生类对象赋值兼容,派生类对象能自动转换类型,在调用 fun 函数时可以用派生类 B 的对象 b1 作实参 :
fun(b1); 输出类 B 的对象 b1 的基类数据成员 num 的值。
如(4):指向基类对象的指针,也可以指向派生类对象。
class Student
{
public:
Student(int, string, float);
void display();
private:
int num;
string name;
float score;
};
Student::Student(int n, string nam, float s)
{num = n; name = nam; score = s;}
void Student::display()
{cout << endl << "num:" << num << endl << "name:" << name << endl << "score:" << score << endl;}
class Graduate :public Student
{
public:
Graduate(int, string, float, float);
void display();
private:
float pay;// 工资
};
Graduate::Graduate(int n, string nam, float s, float p) : Student(n, nam, s), pay(p) { }
void Graduate::display() { Student::display(); cout << "pay = "<< pay << endl; }
void main()
{
Student stud1(1001, "Li", 87.5);
Graduate grad1(2001, "Wang", 98.5, 563.5);
Student *pt = &stud1;
pt->display(); // 调用 stud1.display 函数
pt = &grad1; // 指针指向 grad1
pt->display(); // 调用 grad1.display 函数
}
在一个类中以另一个类的对象作为数据成员的,称为类的组合 (composition) 。类的组合和继承一样,是软件重用的重要方式。通过继承建立派生类与基类是一种“是”的关系。通过组合建立成员类与组合类是一种“有”的关系。继承是纵向的,组合是横向的。
class Teacher// 教师类
{
public: …
private:
int num;
string name;
char sex;
};
class BirthDate
{
public:
private:
int year;
int month;
int day;
};
class Professor: public Teacher
{
public: …
private:
BirthDate birthday;
};
Professor 类通过继承,从 Teacher 类得到了 num, name,age,sex 等数据成员。
通过组合,从 BirthDate 类得到了 year,month,day 等数据成员 。