面向对象技术强调软件的可重用性(softwarereusability)。C++语言提供了类的继承机制,解决了软件重用问题。C++的三大特点之一(封装,继承,多态)
本章讲解三个问题:
一. 继承方式(一共分为三种:公有继承、私有继承、保护继承)
二. 派生类的构造函数和析构函数
三. 虚基类
基础知识
继承的概念
类的继承指的是新的类从已有的类中得到已有的特性;派生指的是已有的类产生新类的过程。
已有的类称为:父类或者基类;
产生的新类称为:派生类或者子类。
继承可以分为单继承和多重继承。
单继承:比如父类学生—>派生类类研究生
多继承:比如父类狮子和父类老虎—>派生类狮虎兽
派生类的声明
C++中派生类声明的语法:
class 派生类名:继承方式 基类名1,继承方式 基类名2, ……,继承方式 基类名n
{
派生类成员声明;
}
派生类的生成过程:
1.吸收基类成员,但不包含构造函数和析构函数
2.改造基类成员,一是通过继承方式来控制,而是隐藏基类成员变量和成员函数,比如在派生类中声明一个与基类成员同名的成员,这时在派生类中或者在派生类对象中使用该成员名,就只能使用派生类中声明的同名成员。
3.增加新成员
一. 继承方式(一共分为三种:公有继承、私有继承、保护继承)
1.公有继承
这种继承方式,基类中的公有成员和保护成员在派生类中仍然是公有成员和保护成员,派生类的成员可以直接访问它,但是基类中的私有成员在派生类中不可访问!
注意:派生类的对象只能访问基类中的公有成员!
示例程序:
#include <iostream>
using namespace std;
class A
{
private:
int x;
protected:
int y;
public:
int z;
void setx(int i)
{
x = i;
}
int getx()
{
return x;
}
};
class B:public A
{
private:
int m;
protected:
int n;
public:
int p;
void setvalue(int a, int b, int c, int d, int e, int f)
{
setx(a); //基类的私有成员只能通过基类的函数访问,派生类的函数和对象都不能访问
y = b;
z = c;
m = d;
n = e;
p = f;
}
void display()
{
cout << "x=" <<getx()<< endl;
//cout << "x=" <<x<< endl; 基类中x为私有变量,派生类中成员函数不能直接访问
cout << "y=" << y << endl; //基类中y为保护变量,派生类中成员函数可以直接访问
cout << "m=" << m << endl;
cout << "n=" << n <<endl;
}
};
int main()
{
B obj;
obj.setvalue(1,2,3,4,5,6);
obj.display();
//cout << "y=" << obj.y << endl; 基类中y为保护成员,派生类的对象不可以直接访问
cout << "z=" << obj.z << endl; //基类中z为公有变量,派生类的对象可以直接访问
cout << "p=" << obj.p << endl;
return 0;
}
公有继承总结:
派生类对基类成员的访问无非就两种:内部访问(既函数访问)和对象访问。
内部访问:基类的公有成员、保护成员。
对象访问:基类的公有成员。
基类中的私有成员在派生类中不可直接访问!!!(不管是内部访问还是对象访问)
2.私有继承
这种方式下,基类中的public和protected成员在派生类中均为private身份出现在派生类中。派生类中的成员函数可以直接访问基类中的public成员和protected成员,但不能访问基类中的private成员!
注意:但是派生类的对象不能直接访问基类中的任何成员!
示例程序:
#include <iostream>
using namespace std;
class A
{
private:
int x;
protected:
int y;
public:
int z;
void setx(int i)
{
x = i;
}
int getx()
{
return x;
}
};
class B:private A
{
private:
int m;
protected:
int n;
public:
int p;
void setvalue(int a, int b, int c, int d, int e, int f)
{
setx(a);
y = b;
z = c;
m = d;
n = e;
p = f;
}
void display()
{
cout << "x=" <<getx()<< endl;
//cout << "x=" <<x<< endl; 基类中x为私有成员,派生类中成员函数不能直接访问
cout << "y=" << y << endl; //基类中y为保护成员,派生类中变为私有成员,派生类中成员函 数可以直接访问
cout << "m=" << m << endl;
cout << "n=" << n <<endl;
}
};
int main()
{
B obj;
obj.setvalue(1,2,3,4,5,6);
obj.display();
//cout << "y=" << obj.y << endl; 基类中y为保护成员通过私有继承变为派生类的私有成员,派生类的对象不可以直接访问
//cout << "z=" << obj.z << endl; 基类中z为公有成员通过私有继承变为派生类的私有成员 ,派生类的对象不可以直接访问
cout << "p=" << obj.p << endl;
return 0;
}
私有继承总结:
内部访问:基类的公有成员、保护成员。
对象访问:都不可访问
基类中的私有成员在派生类中不可直接访问!!!(不管是内部访问还是对象访问)
3.保护继承
这种方式下基类中的public和protected成员都以protected的方式出现在派生类中,派生类中的成员函数可以直接访问基类中的public和protected成员,但是不能直接访问基类中的private成员。注意:派生类中的对象不能访问基类中的任何成员!
保护继承与私有继承的差别主要体现在当前派生类进一步派生的子类中,而在当前派生类这一层次上,保护继承与私有继承没有差别!
比如:Circle私有继承了Point类,而Circle作为新的基类又派生出了Cylinder类,那么Cylinder的类的函数成员和对象都不能够访问Circle从Point类继承来的任何成员。
如果Circle是以保护方式继承了Point类,那么当Circle类再派生处Cylinder类时,Point类中的Public和Protected成员都会被Cylinder类继承为保护形式或者私有形式,但具体要看Cylinder对Circle的继承方式!
重点.类型兼容规则
指的是公有派生类继承了基类中除构造函数和析构函数之外的所有成员,这样凡是需要用基类对象的地方,都可以用公有对象来替代!
类型兼容规则中所指的替代报告以下几种情况:
1.派生类的对象可以赋值给基类的对象
2.派生类对象的地址可以赋值给指向基类的指针
3.派生类的对象可以初始化基类的引用
注意:在替代之后,派生类的对象就可以作为基类的对象使用,但是只能使用从基类继承的成员!(后面介绍的虚函数可以改变这种状况,即可以访问派生类中和基类同名的成员函数)
示例代码:
#include <iostream>
using namespace std;
class pet
{
public:
void speak()
{
cout << "How does a pet speak!" << endl;
}
};
class cat:public pet
{
public:
void speak()
{
cout << "miao ! miao!" << endl;
}
};
class dog:public pet
{
public:
void speak()
{
cout << "wang! wang!" << endl;
}
};
int main()
{
pet *p1, *p2, *p3, obj;
dog dog1;
cat cat1;
//1.派生类的对象可以赋值给基类的对象
obj = dog1; //派生类的对象可以复制给基类对象
obj.speak(); //派生类作为基类使用,只能使用基类里成员函数
//2.派生类对象的地址可以赋值给指向基类的指针
p1 = &cat1; //派生类的对象的地址可以复制给基类对象指针
p1->speak();//派生类对象作为基类使用,只能使用基类里的成员函数
//3.派生类的对象可以初始化基类的引用
pet &p4 = cat1; //派生类对象可以初始化基类的引用
p4.speak();//派生类对象作为基类使用,只能使用基类里的成员函数
dog1.speak();
cat1.speak();
return 0;
}
执行结果:
How does a pet speak!
How does a pet speak!
How does a pet speak!
wang! wang!
miao ! miao!
二. 派生类的构造函数和析构函数
1.派生类构造函数的基本语法:
派生类::派生类表(参数总表):基类名1(参数表1),……,基类名n(参数表n),内嵌对象名1(内嵌对象参数表1),……,内嵌对象名n(内嵌对象参数表n)
{
派生类新增成员的初始化语句;
}
派生类对象的执行次序如下:
1.调用基类构造函数,调用顺序按照他们被继承时声明的顺序(从左到右)
2.调用内嵌成员对象的构造函数,调用顺序按照他们在类中声明的顺序
3.调用派生类的构造函数体中的内容
执行程序:
#include <iostream>
using namespace std;
class B1
{
public:
B1(int i)
{
cout << "constructing B1 " << i << endl;
}
};
class B2
{
public:
B2(int j)
{
cout << "constructing B2 " << j << endl;
}
};
class B3
{
public:
B3()
{
cout << "constructing B3 *" << endl;
}
};
class C:public B2, public B1, public B3 //首先会按照继承的顺序从左到右调用基类构造函数
{
public:
C(int a, int b, int c, int d):B1(a), memberB2(d), memberB1(c), B2(b)
{
cout << "The last is me!" << endl;
}
private: //其次会按照内嵌成员对象在类中的声明顺序,其次调用他们的构造函数
B1 memberB1;
B2 memberB2;
B3 memberB3;
};
int main()
{
C obj(1,2,3,4);
return 0;
}
执行结果:
constructing B2 2
constructing B1 1
constructing B3 * 为什么B3会构造?
constructing B1 3
constructing B2 4
constructing B3 *
The last is me!
2.派生类析构函数
派生类的析构函数是在该对象消亡之前,做一些清理的工作!析构函数没有类型也没有参数,它的执行顺序和构造函数正好严格相反!
示例代码:
#include <iostream>
using namespace std;
class B1
{
public:
B1(int i)
{
cout << "constructing B1 " << i << endl;
}
~B1()
{
cout << "destructing B1" << endl;
}
};
class B2
{
public:
B2(int j)
{
cout << "constructing B2 " << j << endl;
}
~B2()
{
cout << "destructing B2" << endl;
}
};
class B3
{
public:
B3()
{
cout << "constructing B3 *" << endl;
}
~B3()
{
cout << "destructing B3" << endl;
}
};
class C:public B2, public B1, public B3 //首先会按照继承的顺序从左到右调用基类构造函数
{
public:
C(int a, int b, int c, int d):B1(a), memberB2(d), memberB1(c), B2(b)
{
cout << "The last is me!" << endl;
}
private: //其次会按照内嵌成员对象在类中的声明顺序,其次调用他们的构造函数
B1 memberB1;
B2 memberB2;
B3 memberB3;
};
int main()
{
C obj(1,2,3,4);
return 0;
}
执行结果:
constructing B2 2
constructing B1 1
constructing B3 *
constructing B1 3
constructing B2 4
constructing B3 *
The last is me!
destructing B3
destructing B2
destructing B1
destructing B3
destructing B1
destructing B2
三. 虚基类
1.二义性问题及消除
二义性问题一
在多重继承时,基类与基类之间容易出现二义性问题。
解决方法:可以使用同名隐藏规则和使用基类名限定来消除。
备注:
同名隐藏规则是指如果在内层中声明了与外层同名的标识符,则外层的标识符在内存不可见。在类的派生层次结构中,基类的成员和派生类的成员都有类的作用域,两者的作用范围不同,是相互包含的两个层,派生类在内层,基类在外层。当基类与派生类有相同名字的成员时,如果没有强行指定,通过派生类对象使用的是派生类中的同名成员!
基类之间出现同名时,如果要通过派生类对象访问基类中的同名成员,应使用基类名限定,比如下面程序中的
c1.A::f(); //使用基类名限定,消除了二义性
c1.B::f(); //使用基类名限定,消除了二义性
示例程序:
#include <iostream>
using namespace std;
class A
{
public:
void f()
{
cout << "调用class A的成员函数 f !" << endl;
}
};
class B
{
public:
void f()
{
cout << "调用class B的成员函数 f !" << endl;
}
void g()
{
cout << "调用class B的成员函数 g" << endl;
}
};
class C: public A, public B
{
public:
void g()
{
cout << "调用class C的成员函数 g" << endl;
}
void h()
{
cout << "调用class C的成员函数 h" << endl;
}
};
int main()
{
C c1;
c1.g(); //同名隐藏规则消除了二义性,调用派生类的成员函数
//c1.f(); 基类之间出现同名的f函数,出现了二义性:error C2385: 'C::f' is ambiguous
return 0;
}
消除二义性代码:
int main()
{
C c1;
c1.g(); //同名隐藏规则消除了二义性,调用派生类的成员函数
//c1.f(); 基类之间出现同名的f函数,出现了二义性:error C2385: 'C::f' is ambiguous
c1.A::f(); //使用基类名限定,消除了二义性
c1.B::f(); //使用基类名限定,消除了二义性
return 0;
}
二义性问题二
当某类的部分或者全部直接是从另一个共同基类派生而来时,这些共同基类从上一级基类中继承的成员函数就可能会有相同的名称!读起来挺绕,看下面的程序就明白了。
示例代码:
#include <iostream>
using namespace std;
class A
{
public:
void f()
{
cout << "class A 的成员函数 f" << endl;
}
};
class A1:public A
{
private:
int a1;
};
class A2:public A
{
private:
int a2;
};
class AA:public A1, public A2
{
public:
int aa;
};
int main()
{
AA aa_obj;
//aa_obj.f(); 出现二义性,不知道调用A1的f()函数还是A2的f()函数
//aa_obj.A::f();以类A基类名限定也扛不住! 不知道为什么???
return 0;
}
解决方案:
可以使用基类名限定或者虚基类的方式
解决方案一:采用基类名限定,修改main函数如下
int main()
{
AA aa_obj;
//aa_obj.f(); 出现二义性
//aa_obj.A::f(); 基类名限定也扛不住!
aa_obj.A1::f();
aa_obj.A2::f();
return 0;
}
解决方案二:虚基类
虚基类的语法格式:
class 派生类名:virtual 继承方式 基类名
声明了虚基类之后,虚基类的成员在进一步的派生过程中和派生类功能执行同一段内存单元!
示例代码:
#include <iostream>
using namespace std;
class A
{
public:
void f()
{
cout << "class A 的成员函数 f" << endl;
}
};
class A1:virtual public A //声明虚基类
{
private:
int a1;
};
class A2:virtual public A //声明虚基类
{
private:
int a2;
};
class AA:public A1, public A2
{
public:
int aa;
};
int main()
{
AA aa_obj;
aa_obj.f(); //二义性消除,怎么做到的看虚基类的实现机制!网上能搜到!
return 0;
}