【C Plus Plus】基础篇(二)

继承,虚函数,多态

(一)继承
继承中派生类与基类的权限关系:
1.当基类以public方式被继承,那么基类的所有公有成员都会成为派生类的公有成员,基类protected属性的成员也会变成派生类的protected属性成员。
2.当基类以protected方式被继承,那么基类的public,protected属性的成员都会变成派生类的protected属性的成员。
3.当基类以private方式被继承,那么基类所有public,protected属性的成员都会变成派生类的private属性的成员。
4.从上面三条获取可以看出,不管采取哪种继承方式,基类private属性的变量永远没有被继承,所以派生类永远不能继承基类的private属性成员。

class Animal
{
public:
	int age;
protected:
	int height;
private:
	char gender;
};
//public继承
class Person:public Animal
{
public:
	int Age;
protected:
	int Height;
private:
	char Gender;
};
//protected继承
class Person:protected Animal
{
public:
	int Age;
protected:
	int Height;
private:
	char Gender;
};
//私有继承
class Person:private Animal
{
public:
	int Age;
protected:
	int Height;
private:
	char Gender;
};


(二)函数覆盖,函数隐藏,函数重载
在派生类继承基类的时候,如果派生类中拥有和基类同名的函数就会涉及到函数覆盖和函数隐藏的问题。下面我们就将覆盖,隐藏,重载三者区分一下以免。以后写程序的时候犯这些错误,debug半天后,拍着桌子叫道“我这个程序没错儿呀,是不是编译器的问题呀!!!”囧~~
函数重载特征:
(1)作用在同一个类中
(2)函数名相同,参数不同
(3)virtual关键字可有可无

class Animal
{
public:
	Animal(){}
	~Animal(){}
	void Eat(){
		cout<<"eat meat~~"<<endl;
	}
	void Eat(int i){
		cout<<"eat "<<i<<"kg meat~~"<<endl;
	}
	virtual void Eat(int i,int j){
		cout<<"eat "<<i<<"kg meat and "<<j<<"kg icecream~~"<<endl;
	}
};


函数覆盖特征:
(1)作用在不同类中(分别位于派生类与基类)
(2)函数名字相同,参数相同
(3)基类函数必须有virtual关键字

//此处生成Person对象时,Person对象的Eat函数将会覆盖继承过来的Eat函数
class Animal
{
public:
	Animal(){}
	~Animal(){}
	 virtual void Eat(){
		cout<<"eat meat~~"<<endl;
	}
};
class Person:public Animal
{
public:
	Person(){}
	~Person(){}
	void Eat(){
		cout<<"eat fish~~"<<endl;
	}
};
//要调用被覆盖的函数的话只能这样写
Person person;
person.Animal.Eat();


函数隐藏特征:
函数隐藏注意与重载和覆盖相区别,隐藏也是作用在派生类与子类之间,注意理解下面两个规则
(1)如果派生类与基类的函数同名,但是参数不同,此时不论有没有virtual关键字,基类的函数将被隐藏(与重载区分,重载是作用在同一类中)

class Animal
{
public:
	Animal(){}
	~Animal(){}
	 virtual void Eat(){
		cout<<"eat meat~~"<<endl;
	}
};
class Person:public Animal
{
public:
	Person(){}
	~Person(){}
	using Animal::Eat;//不加这句话,将无法调用从基类继承过来的Eat函数,它已经被隐藏
	void Eat(int i){
		cout<<"eat "<<i<<"kg fish~~"<<endl;
	}
};

(2)如果派生类与基类函数同名,并且参数也相同,此时基类函数没有virtual关键字,那么基类函数将被隐藏(与覆盖区分,覆盖必须有virtual关键字)

class Animal
{
public:
	Animal(){}
	~Animal(){}
	 void Eat(){
		cout<<"eat meat~~"<<endl;
	}
};
class Person:public Animal
{
public:
	Person(){}
	~Person(){}
	void Eat(){
		cout<<"eat fish~~"<<endl;
	}
};
//这边两个函数同名,参数也相同,利用上面的方法就不行了,必须采取和覆盖相同的方法才行
Person person;
person.Animal.Eat();

(三)构造函数和析构函数
1.在类的继承派生的时候,如果定义一个派生类对象总是先调用基类的构造函数,然后调用派生类的构造函数,析构函数的顺序与之相反。
2.如果派生类没有显示利用初始化列表来调用基类的构造函数,那么系统会首先查找基类的无参数构造函数,如果没有无参数构造函数便会调用默认构造函数,如果基类没有默认构造函数,那么将提示出错。如果派生类利用初始化列表来调用基类的构造函数,那么系统会到基类的构造函数里面去查找参数与之相匹配的,如果没有相匹配的那么系统同样提示出错。

class Animal
{
public:
	Animal(int i){
		cout<<"Animal Constructor"<<endl;
	}
	~Animal(){
		cout<<"Animal Destructor"<<endl;
	}
};
class Person:public Animal
{
public:
	Person():Animal(1){
		cout<<"Person Constructor"<<endl;
	}
	~Person(){
		cout<<"Person Destructor"<<endl;
	}
};

3.如果基类中定义了几个构造函数的版本,派生类在定义初始化列表的时候必须每个都满足要求,因为编译器会首先检查派生类构造函数和基类构造函数匹配情况。
4.派生类只能初始化它的直接基类
5.如果在派生类中定义了复制构造函数,不显示调用基类的复制构造函数,这时派生类的复制构造函数就会调用基类的默认构造函数(不是默认复制构造函数),如果基类没有默认构造函数就会出错。

class A
{
public:
	int a,a1;
	A(){
		a=a1=2;
		cout<<"Constructor A"<<endl;
	}
	A(int i){
		a=a1=11;
		cout<<"Constructor A1"<<endl;
	}
	~A(){
		cout<<"destructor"<<endl;
	}
};
class B:public A
{
public:
	int b,b1;
	B(){
		b=b1=2;
		cout<<"Constructor B"<<endl;
	}
	B(int i):A(2){
		b=b1=3;
		cout<<"Constructor B1"<<endl;
	}
	B(const B& K){           //这里调用基类的默认构造函数
		b=b1=5;
		cout<<"copy constructor"<<endl;
	}
	~B(){
		cout<<"destructor"<<endl;
	}
};
int main()
{
	B m(0);    cout<<m.a<<m.b<<endl;
	B m1(m); cout<<m1.a<<m1.b<<endl;
	system("pause");
}

运行结果:


6.如果再派生类中定义了复制构造函数,并且显示调用了基类的复制构造函数,而基类却没有定义基类的复制构造函数的时候,这时候将调用基类的默认复制构造函数初始化基类部分,调用派生类的复制构造函数初始化派生类部分。
.
(四)多重继承,虚基类
1.多重继承的构造函数和析构函数,如果一个类继承多个类,那么当定义一个派生类的时候,基类构造函数调用的顺序是根据继承时候那个顺序来的,与初始化列表无关,析构函数与构造函数顺序相反。
2.继承过程中的二义性问题
1)成员名重复:现在如果A继承B,C,B,C中分别都有f函数,当声明A的一个对象的时候,调用f函数的时候应该调用哪一个呢?
2)多个基类副本:类C,B都是从类D继承而来,这时A继承B,C,当申明一个A的对象的时候用该对象访问D类中的函数f,这时这个f函数是从B类继承过来的还是C类基础过来的呢?(这种情况在执行过程中会产生两个D类的副本)
解决方法:为了解决这种问题,我们可以采用域作用符(::)。还有为了解决第二种问题,我们可以采用虚基类,虚基类只允许有一个副本。

class A{
public:
	int a;
	A(int i){
		a=i;
		cout<<"A";
	}
};
class B:public A
{
public: 
	int b;
	B():A(4){
		cout<<"B";
	}
};
class C:public A
{
public:
	int c;
	C():A(5){
		cout<<"C";
	}
};
class D:public B,public C
{
public:
	int d;
	D():C(),B(){
		cout<<"D"<<endl;
	}
};
int main()
{
	D m;
	m.B::a=1;    cout<<m.B::a<<endl;
	m.A::a=3; 
	cout<<m.B::a<<m.A::a<<endl;//此处B和A享有相同的副本,所以值是一样的
	m.C::a=2;
	cout<<m.C::a<<endl;    //C与A,B两个副本不是同一个副本,所以改变C不影响A,B
	system("pause");
}

运行结果:


3.虚基类:当B,C以虚基类继承的方式从D继承,当A再继承B,C,当声明一个A的对象的时候,那么就只会产生一个副本

class B:public virtual D{};
class C:public virtual D{};
class A:public B,public C{}

4.虚基类的构造函数:正如上面所说,如果不加作为虚基类继承,那么将会产生两个副本,现在我们作为虚基类继承了,产生一个副本的原理是什么呢?当按照虚基类继承后,如果定义一个A的对象,那么就直接调用D的构造函数和A的构造函数,在这儿就不经过B,C的构造函数了,因此就会只有1个副本了。
.
(五)虚函数与多态
1)继承中的指针问题
1.当基类指针指向派生类对象时候,基类指针只能访问派生类从基类派生过来的变量。
2.基类指针可以指向派生类对象,派生类指针不能指向基类对象。
2)虚函数
1.为什么要使用虚函数:从上面我们就发现基类指针指向派生类对象不能访问派生类中的变量或函数,为了解决这个问题,我们引入了虚函数。
2.不能把成员变量声明为虚有的,virtual关键字不能用在成员变量前面。
3.一般以指针调用虚函数,不用运算符调用
4.如果声明一个基类指针,让它指向不同的派生类,这些派生类都从基类继承而来,如果从基类继承过来f虚函数,各自派生类又对此虚函数进行了重写,那么基类指针指向各自派生类调用f函数的时候,是调用的派生类重写的虚函数,这也就是上面标号1里面所说问题的解决方法。
5.虚函数必须在基类中使用virtual关键字,派生类中重写虚函数加或者不加virtual关键字都行。
6.包括虚函数的类被称为多态类,C++使用虚函数支持多态性。
7.在派生类中重定义虚函数的时候函数名和函数参数要一致,否则那就是重载了。
8.一旦函数为虚函数,那么不管多少重继承,它都是虚函数(好命苦~~)
9.隐藏虚函数:当我们重定义虚函数的时候我们不小心将它重载了,那么我们就只能把基类的虚函数覆盖了(隐藏,覆盖知识在上面讲解过)
10.带默认形参的虚函数:当基类的虚函数带有默认形参时,派生类对虚函数进行重定义后,用基类指针指向派生类对象调用虚函数时是调用的基类虚函数,而不是派生类冲定义的虚函数。(因为这边是默认形参,基类的默认形参会)
11.从上面一点你也许会为派生类打抱不平,好不容易重定义一下,你还不让我用,那么好吧,给你点自由,如果是派生类自己的对象,则可以调用自己重定义的。
12.析构函数可以为虚函数,但是构造函数不行
13.纯虚函数声明形式:virtual 类型 函数名(参数列表)=0;
14.如果类有纯虚函数,那么这个类就是抽象类。
15.抽象类不能有对象,只能作为其他类的基类。(因为抽象类中有一个或多个函数没定义)
16.虽然抽象类不能有对象,但是可以声明一个指针指向派生类对象。
17.虚函数虚拟特征是以层次结构方式来继承的。比如C继承了B,并从定义B中的虚函数,D继承C没有重定义虚函数,现在一个D的对象调用虚函数不是调用B中的,而是调用C中的。
啊呼,这貌似是我看黄邦勇帅写的东西里面最长的规则了,下面贴上一段代码,结合代码和运行结果好好理解上面这些规则吧~~

class A
{
public:
	int a;
	//virtual int b;    错误,不能把成员变量声明为虚有的
	virtual void f(){
		cout<<"Virtual Function f"<<endl;
	}
	virtual void h(int i=1,int j=2){
		cout<<"Virtual Function h~~"<<i<<" "<<j<<endl;
	}
	~A(){
		cout<<"Destroctor A"<<endl;
	}
};
class B:public A
{
public:
	int b;
	void f(int i){
		cout<<"重载 virtual function f"<<endl;
	}
	void f(){
		cout<<"重定义 virtual function f"<<endl;
	}
	void h(){
		cout<<"重载 virtual function h"<<endl;
	}
	void h(int i,int j=3){
		cout<<"重定义 virtual function h~~"<<i<<" "<<j<<endl;
	}
	~B(){
		cout<<"Destructor B"<<endl;
	}
};
int main()
{
	B m;
	A *p=&m;
	p->f();
	p->A::f();     //调用虚基类的虚函数
	p->h();
	m.h();
	m.h(1);
	m.A::h();
	system("pause");
}

运行结果:


3)虚析构函数
上面就有一条规则说,析构函数可以加virtual,但是构造函数不行,我们为什么要声明为虚析构函数呢?举个例子,如果我们现在用基类指针指向派生类对象,然后delete基类指针,在不声明为虚基类的情况下,只会调用基类的析构函数而不会调用派生类,为了解决此问题,我们声明为虚析构函数,这样我们在调用基类的析构函数的时候也会调用派生类的析构函数。

class Animal
{
public:
	virtual ~Animal(){     //如果不加virtual运行结果将只有Destructor Animal
		cout<<"Destructor Animal"<<endl;
	}
};
class Person:public Animal
{
public:
	~Person(){
		cout<<"Destructor Person"<<endl;
	}
};
int main()
{
	Animal *animal=new Person();
	delete animal;
	system("pause");
}

运行结果:


4)写到最后才发现没有添加一个多态的例子,从上面一些规则已经讲述了虚函数与多态的一些关系,下面举个例子
class A
{
public:
	virtual void fun(){
		cout<<"virtual function A"<<endl;
	}
};
class B:public A
{
	void fun(){
		cout<<"virtual function B"<<endl;
	}
};
class C:public A
{
	void fun(){
		cout<<"virtual function C"<<endl;
	}
};
int main()
{
	A *a=new A();
	a->fun();
	B b;
	C c;
	a=&b;
	a->fun();
	a=&c;
	a->fun();
	system("pause");
}

运行结果:

写到最后发现这篇文章里面也没有扩展多少原创作者的多少知识,基本上都是自己在按照他写的学习,自己又按照自己的理解写了一遍儿,所以这篇文章就作为自己的一篇读书笔记吧,原稿作者是黄邦勇帅。
这不是我的原创,转载请注明原稿作者

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值