600 likes | 769 Views
第 7 章 继承与派生. 7.1 类的继承与派生 7.2 类的继承方式 7.3 派生类的构造过程和析构过程 7.4 多继承. 第 7 章 继承与派生. 人类. 教师类. 管理人员类. 学生类. 教师管理人员类. 7.1 类的继承与派生. 人类 : 姓名、性别、年龄、身高、体重 教师类 : 姓名、性别、年龄、身高、体重、 专业、职称 管理人员类 : 姓名、性别、年龄、身高、体重、 职务
E N D
第7章 继承与派生 7.1 类的继承与派生 7.2 类的继承方式 7.3 派生类的构造过程和析构过程 7.4 多继承
第7章 继承与派生 人类 教师类 管理人员类 学生类 教师管理人员类 7.1 类的继承与派生 人类: 姓名、性别、年龄、身高、体重 教师类: 姓名、性别、年龄、身高、体重、专业、职称 管理人员类: 姓名、性别、年龄、身高、体重、职务 教师管理人员类: 姓名、性别、年龄、身高、体重、专业、职称、职务 学生类: 姓名、性别、年龄、身高、体重、学号、班级、专业 7.1.1 继承与派生的基本概念 在原有类的基础上派生出新的类,新类继承原有类的属性和方法,称原有的类为基类,新类称为派生类。 间接基类 直接基类 单继承:派生类 只有一个基类 多继承:派生类 有多个基类
第7章 继承与派生 7.1 类的继承与派生 7.1.2 派生类的声明 单继承派生类的声明语法为: class 派生类名: 继承方式基类名 { 派生类新增成员的声明; } 继承方式有三种(private,protected,public),在下一节中详细介绍。
第7章 继承与派生 例7.1 定义一个位置坐标类,属性有x坐标和y坐标,成员函数包括构造函数、获取x坐标的函数、获取y坐标的函数和移动位置到新的坐标点函数。然后定义派生类点类,除了继承基类(位置类)的成员外,又增加了新的数据成员颜色、获取颜色值的成员函数和显示数据成员值的函数。 #include <iostream.h> #include <string.h> class CLocation //位置坐标类 { private: int x; int y; public: CLocation(int x=0, int y=0); void MoveTo(int x, int y); int Getx(); int Gety(); };
第7章 继承与派生 例7.1 (续一) CLocation::CLocation(int x, int y) { CLocation::x = x; // 可以写成 this->x = x; CLocation::y = y; // 可以写成 this->y = y; } void CLocation::MoveTo(int x, int y) { CLocation::x = x; // 可以写成 this->x = x; CLocation::y = y; // 可以写成 this->y = y; } int CLocation::Getx() { return x; } int CLocation::Gety() { return y; } 可通过域运算符(::)访问类中的成员x和y,与使用this指针有同样作用
第7章 继承与派生 例7.1 (续二) class CPoint : public CLocation //从CLocation中公有继承 { private: char Color[10]; public: CPoint(char *c); void SetColor(char *c); void Show(); }; CPoint::CPoint(char *c) { strcpy(Color,c); } void CPoint::SetColor(char *c) { strcpy(Color,c); } void CPoint::Show() { cout << Getx() << "," << Gety() << " " << Color << endl ; } 调用从CLocation类中继承来的成员函数GetX()和GetY()。
第7章 继承与派生 返 回 例7.1 (续三) 位置坐标通过CLocation类构造函数的默认参数值设置为(0,0) void main(void) { CPoint p("Red"); p.Show(); p.MoveTo(7,8); p.Show(); p.SetColor("Green"); p.Show(); } 程序运行结果为: 0,0 Red 7,8 Red 7,8 Green
第7章 继承与派生 7.2 类的继承方式 类成员的访问权限: 私有成员(private):可以被类自身的成员和友元访问,但不能被包括派生类在内的其他任何类和任何普通函数访问 公有成员(public):可以被任何普通函数和任何类的成员函数访问 保护成员(protected):可以被类自身的成员和友元访问外,还可以被派生类的成员函数访问,但不能被任何非友元的普通函数访问 类的三种继承方式: 公有继承(public): 保护继承(protected): 私有继承(private):
第7章 继承与派生 7.2 类的继承方式 7.2.1 公有继承 公有继承的特点: (1) 基类的私有成员不能被派生类的函数成员访问 (2) 基类的公有成员和保护成员在派生类中的访问权限不变 分析例7.1 基类CLocation中的公有成员Getx()、Gety()和MoveTo(),在派生类CPoint中的访问权限仍然是公有的。 而x,y是基类CLocation的私有成员,在派生类CPoint的成员函数中不能访问。如果将CPoint类的成员函数Show()改写成下面的形式: void CPoint::Show() { cout << x << "," << y << " " << Color << endl ; } 错误信息:“不能访问CLocation类中的私有成员x,y”
第7章 继承与派生 7.2 类的继承方式 将x,y改为保护成员后,上面的show()就可以直接访问成员x和y了 7.2.1 公有继承(续一) 若将基类CLocation中的x,y改成保护成员,即: class CLocation { protected: int x; int y; public: int Getx(); int Gety(); void MoveTo(int x, int y); CLocation(int x=0, int y=0); };
第7章 继承与派生 7.2 类的继承方式 7.2.1 公有继承(续二) 分析例7.1CPoint类的部分成员属性: CPoint类的成员函数不能访问的成员有: int x; //继承基类的私有成员,类的成员函数不可访问 int y; //继承基类的私有成员,类的成员函数不可访问 CPoint类的私有成员有: char Color[10]; //自己定义的私有成员 CPoint类的公有成员有: int Getx(); //继承基类的公有成员,仍然是公有成员 int Gety(); //继承基类的公有成员,仍然是公有成员 void MoveTo(int x, int y); //继承基类的公有成员,仍然是公有成员 void SetColor(char *c); //自己定义的公有成员 void Show(); //自己定义的公有成员
第7章 继承与派生 7.2 类的继承方式 7.2.2 保护继承 保护继承的特点: (1) 基类的私有成员不能被派生类的函数成员访问 (2) 基类的公有成员和保护成员在派生类中的访问权限变为保护类型 例7.2 将例7.1的继承方式改为保护继承,其他代码保持不变 class CPoint :protected CLocation { private: char Color[10]; public: CPoint(char *c); void SetColor(char *c); void Show(); };
第7章 继承与派生 7.2 类的继承方式 错误:不能在主函数中访问CLocation类的函数MoveTo(),只能在派生类的成员函数中访问 7.2.2 保护继承(续一) 改为保护继承后,主函数不变编译时,是否正确? void main(void) { CPoint p("Red"); p.Show(); p.MoveTo(7,8); p.Show(); p.SetColor("Green"); p.Show(); }
第7章 继承与派生 7.2 类的继承方式 7.2.2 保护继承(续二) 为实现点的移动,可在CPoint类中再添加一个公有成员函数MoveTo(),如下: class CPoint:protected CLocation { private: char Color[10]; public: CPoint(char *c); void SetColor(char *c); void Show(); void MoveTo(int x, int y); }; void CPoint::MoveTo(int x, int y) { CLocation::MoveTo(x,y); } 用基类名和域运算符(::)可以访问基类的成员函数MoveTo(), 若不加限定,由于基类和派生类都有成员函数MoveTo(),将优先调用派生类本身的同名成员函数MoveTo()。
第7章 继承与派生 7.2 类的继承方式 7.2.2 保护继承(续三) 标识符的作用范围:成员函数作用域(如函数内部定义的局部变量及函数参数),类或者派生类作用域(如类中定义的数据成员或成员函数),基类作用域(如基类中定义的数据成员或成员函数),全局作用域(如定义的全局变量,及全局函数,即普通函数)。 标识符的访问原则:如果在某一程序段中有一个以上的同名标识符都有效,则标识符的作用范围越小,被访问到的优先级越高。 如果希望访问作用范围更大的标识符,则可以用类名和作用域运算符进行限定。如:CLocation::MoveTo(x,y)。
第7章 继承与派生 7.2 类的继承方式 7.2.2 保护继承(续四) 分析本例CPoint类的部分成员属性: CPoint类的成员函数不能访问的成员有: int x; //继承基类的私有成员,类的成员函数不可访问 int y; //继承基类的私有成员,类的成员函数不可访问 CPoint类的私有成员有: char Color[10]; //自己定义的私有成员 CPoint类的保护成员有: int Getx(); //继承基类的公有成员,成为保护成员 int Gety(); //继承基类的公有成员,成为保护成员 void MoveTo(int x, int y); //继承基类的公有成员,成为保护成员 CPoint类的公有成员有: void MoveTo(int x, int y); //自己定义的公有成员 void SetColor(char *c); //自己定义的公有成员 void Show(); //自己定义的公有成员
第7章 继承与派生 7.2 类的继承方式 7.2.3 私有继承 私有继承的特点: (1) 基类的私有成员不能被派生类的函数成员访问 (2) 基类的公有成员和保护成员在派生类中的访问权限变为私有类型 将例7.1的继承方式改为私有继承,其他代码保持不变 class CPoint :private CLocation { private: char Color[10]; public: CPoint(char *c); void SetColor(char *c); void Show(); };
第7章 继承与派生 7.2 类的继承方式 7.2.3 私有继承(续) 分析本例CPoint类的部分成员属性: CPoint类的成员函数不能访问的成员有: int x; //继承基类的私有成员,类的成员函数不可访问 int y; //继承基类的私有成员,类的成员函数不可访问 CPoint类的私有成员有: char Color[10]; //自己定义的私有成员 int Getx(); //继承基类的公有成员,成为私有成员 int Gety(); //继承基类的公有成员,成为私有成员 void MoveTo(int x, int y); //继承基类的公有成员,成为私有成员 CPoint类的公有成员有: void SetColor(char *c); //自己定义的公有成员 void Show(); //自己定义的公有成员
第7章 继承与派生 7.2 类的继承方式 派生类继承基类成员访问权限的变化表:
第7章 继承与派生 例7.3 定义一个钟表类,数据成员有时、分、秒,成员函数包括设置时间和显示时间。再从钟表类派生出闹钟类,新增数据成员有响铃时间,成员函数包括响铃、显示响铃时间和设置响铃时间。 #include <iostream.h> class Clock { public: Clock(int h=0, int m=0, int s=0); Clock(Clock &c); void SetTime(int h, int m, int s); void ShowTime(); private: int Hour; int Minute; int Second; };
第7章 继承与派生 例7.3 (续一) Clock::Clock(int h, int m, int s) //构造函数的参数用于初始化时、分、秒 { Hour = h; Minute = m; Second = s; } Clock::Clock(Clock &c) //拷贝构造函数将引用作为参数初始化新建对象 { Hour = c.Hour; Minute = c.Minute; Second = c.Second; } void Clock::SetTime(int h, int m, int s) //设置时间 { Hour = h; Minute = m; Second = s; } void Clock::ShowTime() //显示时间 { cout << Hour << ":" << Minute << ":" << Second << endl; }
第7章 继承与派生 例7.3 (续二) class AlermClock : public Clock { private: int AlermHour; int AlermMinute; int AlermSecond; public: AlermClock(int h=12, int m=0, int s=0); void Alerm(); void SetAlermTime(int h, int m, int s); void ShowAlermTime(); }; AlermClock::AlermClock(int h, int m, int s) { AlermHour = h; AlermMinute = m; AlermSecond = s; }
第7章 继承与派生 例7.3 (续三) void AlermClock::Alerm() // 转义字符‘\a’完成响铃 { cout << "\a\a\a\a\a\a\a"; } void AlermClock::SetAlermTime(int h, int m, int s) { AlermHour = h; AlermMinute = m; AlermSecond = s; } void AlermClock::ShowAlermTime() { cout << AlermHour << ":" << AlermMinute << ":" << AlermSecond << endl; }
第7章 继承与派生 返 回 例7.3 (续四) void main() { AlermClock c; c.ShowTime(); c.ShowAlermTime(); c.SetTime(10,30,40); c.SetAlermTime(6,30,0); c.ShowTime(); c.ShowAlermTime(); c.Alerm(); } 构造函数采用缺省参数值,基类继承的数据成员初始化为(0,0,0),将AlermClock类自己定义的数据成员响铃时间置为(12,0,0) 程序运行结果为: 0 : 0 : 0 12 : 0 : 0 10 : 30 : 40 6 : 30 : 0
第7章 继承与派生 7.3 派生类的构造过程和析构过程 基类的构造函数和析构函数都不被继承,需要在派生类中重新定义。由于派生类继承了基类的成员,在初始化时,也要同时初始化基类成员。可通过调用基类的构造函数对完成初始化。 7.3.1 派生类的构造过程 派生类构造函数定义的一般格式为: 派生类名::构造函数名(参数表):基类名(参数表),内嵌对象1(参数表1),内嵌对象2(参数表2),…,常量1(初值1),常量2(初值2),…,引用1(变量1),引用2(变量2)… { 派生类构造函数体; }
第7章 继承与派生 7.3 派生类的构造过程和析构过程 7.3.1 派生类的构造过程(续) 派生类构造函数的执行顺序: (1)先调用基类的构造函数 (2)然后按照数据成员(包括内嵌对象、常量、引用等必须初始化的成员)的声明顺序,依次调用数据成员的构造函数或初始化数据成员 (3)最后执行派生类构造函数的函数体 注意:构造函数的执行顺序只与成员声明的顺序有关,而与初始化表中各项的排列顺序无关。 常量成员、引用成员、内嵌对象,只能通过初始化表的方法初始化。
第7章 继承与派生 用初始化表的方式为a赋值,与函数体中使用 a=x 语句作用一样 用初始化表的方式为b、d赋值,为x和y初始化,调用基类A的构造函数为基类成员初始化 例7.4 派生类的构造过程 #include <iostream.h> class A { int a; public: A(int x):a(x) { cout << "construct A " << a << endl; } }; class B : public A { private: int b,c; const int d; A x,y; public: B(int v) : b(v), y(b+2), x(b+1), d(b), A(v) { c = v; cout << "construct B " << b <<" " << c << " " << d << endl; } };
第7章 继承与派生 例7.4 (续) void main(void) { B b1(10); } 执行顺序: 先调用基类的构造函数,即执行初始化表中的A(v); 然后按着成员声明的先后先后顺序,应首先初始化b,即执行初始化表中的b(v),接下来是成员d,即执行初始化表中的d(b); 接下来构造内嵌对象x,即执行初始化表中的x(b+1)。再构造内嵌对象y,即执行初始化表中的y(b+2); 最后执行类B自己的构造函数体。 程序运行结果为: construct A 10 construct A 11 construct A 12 construct B 10 10 10
第7章 继承与派生 7.3 派生类的构造过程和析构过程 7.3.2 派生类的析构过程 派生类析构函数执行时将自动调用基类及内嵌对象的析构函数,因此不必显式调用。 派生类构造函数的执行顺序: (1)先执行派生类的析构函数。 (2)然后按着内嵌对象声明的相反顺序,依次调用内嵌对象的析构函数。 (3)最后调用基类的析构函数。
第7章 继承与派生 例7.5 派生类的析构顺序 #include <iostream.h> class A { int a; public: A(int x):a(x) { cout << "construct A " << a << endl; } ~A() { cout << "destruct A " << a << endl; } };
第7章 继承与派生 例7.5 派生类的析构顺序 程序运行结果为: construct A 10 construct A 11 construct A 12 construct B 10 10 10 destruct B 10 10 10 destruct A 12 destruct A 11 destruct A 10 class B : public A { private: int b,c; const int d; A x,y; public: B(int v) : b(v), y(b+2), x(b+1), d(b), A(v) { c = v; cout << "construct B" << b <<" " << c << " " << d << endl; } ~B() { cout << "desstruct B" << b <<" " << c << " " << d << endl; } }; void main(void) { B b1(10); }
第7章 继承与派生 例7.6 定义一个点类CPoint,数据成员有点的坐标,再定义一个几何形状类CShape,数据成员只有颜色,以CShape类为基类派生出线段类CLine和圆类CCircle,其中线段类CLine的数据成员包括起点和终点(为CPoint类的内嵌对象),圆类CCircle的数据成员包括圆心(为CPoint类的内嵌对象)和半径。 #include <iostream.h> #include <string.h> class CPoint { private: int X; int Y; public: CPoint(int x=0, int y=0) { X=x; Y=y; } CPoint(Point &p) { X=p.X; Y=p.Y; } int GetX() { return X; } int GetY() { return Y; } };
第7章 继承与派生 例7.6 (续一) class CShape { private: char Color[10]; public: CShape(char *c) { strcpy(Color,c); } void Draw() { cout << "Draw a shape. The color is " << Color << endl; } void PrintColor() { cout << Color << endl; } };
第7章 继承与派生 输出颜色Color。因为基类的颜色Color是私有成员,在这里不能直接访问,只能通过它的公有函数PrintColor()访问 例7.6 (续二) class CLine:public CShape { private: CPoint Start; //线段的起点 CPoint End; //线段的终点 public: CLine(CPoint s, CPoint e, char *c):CShape(c),Start(s),End(e) {} void Draw() { cout << "Draw a Line from (" << Start.GetX() << "," << Start.GetY(); cout << ") to ("<< End.GetX() << "," << End.GetY() << "), with color "; PrintColor(); } };
第7章 继承与派生 例7.6 (续三) class CCircle:public CShape { private: CPoint Center; int Radius; public: CCircle(CPoint ctr, int r, char *c):CShape(c),Center(ctr) { Radius = r; } void Draw() { cout << "Draw a circle at center (" << Center.GetX() << "," ; cout << Center.GetY()<< ") with radius " << Radius << " and color "; PrintColor(); } };
第7章 继承与派生 例7.6 (续四) void main() { CShape s("Red"); CPoint p1(10,10), p2(100,100),p3(50,50); CLine l(p1,p2,"Green"); CCircle c(p3, 20, "Black"); s.Draw(); l.Draw(); c.Draw(); } 程序运行结果为: Draw a Shape. The color is Red Draw a Line from (10,10) to (100,100), with color Green Draw a Circle at center (50,50) with radius 20 and color Black
第7章 继承与派生 虽然父类的指针可以指向子类的对象,但调用的函数Draw()都是父类CShape的成员函数 返 回 例7.6 (续五) 父类与子类:如果派生类的派生控制为public,则这样的派生类称为基类的子类,而相应的基类则称为派生类的父类。C++允许父类指针直接指向子类对象,也允许父类引用直接引用子类对象。 void main() { CShape *ps[3]; CShape s("Red"); CPoint p1(10,10), p2(100,100),p3(50,50); CLine l(p1,p2,"Green"); CCircle c(p3, 20, "Black"); ps[0] = &s; ps[1] = &l; ps[2] = &c; for(int i=0; i<3; i++) ps[i]->Draw(); } 程序运行结果为: Draw a Shape. The color is Red Draw a Shape. The color is Green Draw a Shape. The color is Red Black
第7章 继承与派生 7.4 多继承 7.4.1 多继承的构造与析构 一个派生类可以有多于一个的基类,称之为多继承。 派生类构造函数的执行顺序: (1)先按着声明的顺序(从左至右)依次调用各基类的构造函数。 (2)然后按照数据成员(包括内嵌对象、常量、引用等必须初始化的成员)的声明顺序,依次调用数据成员的构造函数或初始化数据成员。 (3)最后执行派生类构造函数的函数体。 派生类析构顺序: (1)先执行派生类的析构函数。 (2)然后按着内嵌对象声明的相反顺序,依次调用内嵌对象的析构函数。 (3)最后按基类声明的相反顺序调用各基类的析构函数。
第7章 继承与派生 例7.7 多继承派生类的构造过程与析构过程。 #include <iostream.h> class CBase1 { protected: int b; public: CBase1(int x=0) { b=x; cout << "Construct CBase1! " << b <<endl; } ~CBase1() { cout << "Destruct CBase1! " << b <<endl; } };
第7章 继承与派生 例7.7 (续一) class CBase2 { protected: int b; public: CBase2(int x=0) { b=x; cout << "Construct CBase2! " << b <<endl; } ~CBase2() { cout << "Destruct CBase2! " << b <<endl; } };
第7章 继承与派生 例7.7 (续二) class CDerived : public CBase1,private CBase2 { protected: CBase1 b1; CBase2 b2; int d; public: CDerived(int x,int y, int z): :b1(y),CBase2(y),b2(z),CBase1(x) { d=z; cout << "Construct CDerived! " << d <<endl; } ~CDerived() { cout << "Destruct CDerived! " << d <<endl; } };
第7章 继承与派生 构造函数执行顺序: 先调用基类CBase1构造函数,再调用基类CBase2构造函数,然后调用内嵌对象b1构造函数,再调用内嵌对象b2的构造函数,最后执行CDerived类本身的构造函数体。 析构过程与构造过程恰好相反。 例7.7 (续三) void main() { CDerived d1(1,2,3); } 程序运行结果为: Construct CBase1 1 Construct CBase2 2 Construct CBase1 2 Construct CBase2 3 Construct CDerived! 3 Denstruct CDerived! 3 Denstruct CBase2! 3 Denstruct CBase1! 2 Denstruct CBase2! 2 Denstruct CBase1! 1
第7章 继承与派生 类中继承了两次CBase0的成员 CBase0 CBase1 CBase2 CDerived 7.4 多继承 7.4.2 多继承的二意性 1. 基类有同名成员引起的二意性 多继承时,不同基类可能有同名成员,这样派生类中就可能有从不同基类继承的同名成员,在引用时产生二意性。 2.从多个路径继承同一个基类引起的二意性 多重继承时,低层的派生类有可能从不同的路径继承同一个基类的成员多次,引用这样的成员时也会产生二意性。
第7章 继承与派生 例7.8 多继承派生产生的二意性。 class CBase2 { protected: int b; public: CBase2(int x=0) { b=x; } int GetB() { return b; } }; #include <iostream.h> class CBase1 { protected: int b; public: CBase1(int x=0) { b=x; } int GetB() { return b; } };
第7章 继承与派生 对象d1的数据成员 1 2 3 CBase1::b CBase2::b d 例7.8 (续) class CDerived : public CBase1,private CBase2 { protected: int d; public: CDerived(int x,int y, int z):CBase1(x),CBase2(y) { d=z; } void Output() { cout << d << b << endl; // Error: CDerived::b' is ambiguous } }; void main() { CDerived d1(1,2,3); int x = d1.GetB(); // Error: CDerived::GetB' is ambiguous d1.Output(); } 产生二意性,因为不知道访问哪一个基类继承来的b,可使用域运算符和基类名来限定,改为: CBase1::b或CBase2::b 产生二意性,改为:d1.CBase1::GetB()
第7章 继承与派生 例7.9 从多个路径继承同一个基类,引起的二意性。 class CBase1 : public CBase0 { public: CBase1(int x=0) : CBase0(x) { } }; class CBase2 : public CBase0 { public: CBase2(int x=0) : CBase0(x) { } }; #include <iostream.h> class CBase0 { protected: int b0; public: CBase0(int x=0) { b0=x; } int GetB0() { return b0; } };
第7章 继承与派生 b0 GetB0() CBase0 虽然可以通过域运算符与基类名解决 二意性问题,但一般情况下并不希望 派生类中有两份基类成员。 b0 GetB0() CBase1 b0 GetB0() CBase2 CBase1::b0 CBase2::b0 CBase1::GetB0() CBase2::GetB0() CDerived 例7.9 (续) class CDerived : public CBase1,public CBase2 { public: CDerived(int x,int y):CBase1(x),CBase2(y) { } }; void main() { CDerived d1(1,2); cout << d1.GetB0() <<endl; } 产生二意性,可使用域运算符和基类名来限定,改为: d1.CBase1::GetB0()或d1.CBase2::GetB0()
第7章 继承与派生 7.4 多继承 7.4.3 虚基类 为了避免在派生类中从不同路径继承间接基类多次,可以通过将间接基类声明为虚基类,虚基类的成员在它的间接派生类中只被继承一次。 1. 虚基类的声明 class 派生类名 :virtual继承方式 基类名
第7章 继承与派生 例7.10 虚基类的应用。 class CBase1 : virtual public CBase0 { public: CBase1(int x=0) : CBase0(x) { } }; class CBase2 : virtual public CBase0 { public: CBase2(int x=0) : CBase0(x) { } }; #include <iostream.h> class CBase0 { protected: int b0; public: CBase0(int x=0) { b0=x; } int GetB0() { return b0; } };
第7章 继承与派生 如果虚基类没有不带参数的构造函数,且有参数的 构造函数又没有默认参数值,则其所有的派生类(包 括间接派生类)的构造函数都必须为它的构造函数 提供参数。基类的数据成员b0得到的值是由最底层的 派生类CDerived的构造函数提供的。 例7.10 (续) class CDerived : public CBase1,public CBase2 { public: CDerived(int x,int y,int z):CBase0(x),CBase1(y),CBase2(z) { } }; void main() { CDerived d1(10,15,20); cout << d1.GetB0() <<endl; cout << d1.CBase1::GetB0() <<endl; cout << d1.CBase2::GetB0() <<endl; } 只从基类CBase0中继承了一次GetB0(),因此直接调用不会产生二意性,也可以使用 d1.CBase1::GetB0()或d1.CBase2::GetB0() 调用同一个GetB0()函数 程序运行结果为: 10 10 10