c++_note_5_继承和多态

5.继承

继承方式\父类级别publicprotectedprivate
publicpublicprotectedprivate
protectedprotectedprotectedprivate
privateprivateprivateprivate

class Child : protected Parent
class Child : private Parent
class Child : public Parent

派生类继承了基类的全部成员变量,以及除了构造和析构之外的成员方法。
private成员在子类中依然存在,但无法访问。

父类的static成员,将被所有子类共享。
static成员访问级别遵循继承访问控制规则。

判断能否访问:

  • 调用位置在子类内部还是外部
  • 如何继承
  • 父类中的访问级别

子类对象可以作为父类对象使用
子类对象可以赋值给父类对象
子类对象可以初始化父类对象

#include<iostream>
using namespace std;

class P{
private:
	int priv;
public:
	int pub;
	void set(int a, int b, int c){
		pub = a;
		prot = b;
		priv = c;
	}
protected:
	int prot;
	
};

class C : public P{
public:
	void func(){
		cout<<pub<<endl;
		cout<<prot<<endl;
	//	cout<<priv<<endl;
	}
};

int main(int argc, char *argv[])
{
	C c;
	P p = c; 
	c.set(1,2,3);
	c.func();
	cin.get();
	return 0;
}

关于子类构造析构

Child() : Parent() , GrandP()
先构造父类,再构造成员变量,最后构造自己。
先析构自己,再析构成员变量,最后构造父类。

#include<iostream>
using namespace std;

class GrandP{
private:
	int gp; 
public:
	GrandP(){
		cout<<"construct GrandP"<<endl;
	}
protected:

};

class P : public GrandP{
private:
	int a,b;
	GrandP gp;
public:
	P(int a, int b) : GrandP(){
		this->a = a;
		this->b = b;
		cout<<"construct P"<<endl;
	}
	P(const P &obj){
		cout<<"copy"<<endl;
	}
	~P(){
		cout<<"distruct P"<<endl;
	}
protected:

};

class C : public P{
private:
	int c;
public:
	C(int a, int b, int c):P(a,b){
		this->c = c;
		cout<<"construct C"<<endl;
	}
	~C(){
		cout<<"distruct C"<<endl;
	}
protected:

};

void func(P p){
	cout<<"func"<<endl;
}

int main(int argc, char *argv[])
{
	/*
	P p1(1,2);
	C c1(2,3,4);
	P p2 = c1;
	*/
	C c(1,2,3);
	cin.get();
	return 0;
}
/*
construct GrandP
construct GrandP
construct P
construct C
*/

父子同名成员

若父子有变量同名,该变量还是会继承,分别这样调用:
c.P::foobar
c.foobar // or c.C::foobar
函数也一样。

#include<iostream>
using namespace std;

class P{
private:
	 
public:
	void print(){
		cout<<"P print"<<endl;
	}
protected:

};
class P2{
private:
	 
public:
	void print(){
		cout<<"P2 print"<<endl;
	}
protected:

};

class C : public P,public P2{
private:
	
public:
	void print(){
		cout<<"C print"<<endl;
	}
protected:

};

int main(int argc, char *argv[])
{
	C c;
	c.print(); 
	c.P::print();
	c.P2::print();
	cin.get();
	return 0;
}
/*
C print
P print
P2 print
*/

虚继承

当一个类C有多个父类,这些父类P又有同一父类GP时,GP会构造两次,C中访问GP的变量时就会出现二义性
因为继承关系本应形成,这种多继承则会形成,使程序更复杂。
虚继承解决了二义性问题。

添加virtual后,类的size会变大,这部分编译器添加的属性就是实现虚继承的方式。

#include<iostream>
using namespace std;

class GP{
private:
	int a;
public:
	GP(){
		cout<<"construct GP"<<endl;
	} 
	void print(){
		cout<<a<<endl;
	} 
protected:

};

class P1 : virtual public GP{
private:
	int b;
public:

protected:

};

class P2 : public GP{
private:
	int c;
public:

protected:

};

class C : public P1,public P2{
private:

public:

protected:

};

int main(int argc, char *argv[])
{
	C c;
	//c.print();
	cout<<sizeof(GP)<<endl;
	cout<<sizeof(P1)<<endl;
	cout<<sizeof(P2)<<endl;
	cin.get();
	return 0;
}
/*
construct GP
construct GP
4
12
8
*/

6.多态

通过继承可以复用以前的代码;
通过多态还可以使用将来的代码,同样的调用语句有不同的表现。

实现效果

同样的调用语句,不同的表现形态。

意义

多态是软件行业追求的目标,是设计模式和框架的基础。

实现条件

实现多态有3个条件(对比间接赋值):

  1. 有继承
  2. 有虚函数重写
  3. 用父类指针或父类引用指向子类对象

虚函数表

#include<iostream>
#include<windows.h>
using namespace std;

class Base1
{
public:
	int a = 0x11111111;
	virtual void func1(){}
	virtual void func2(){}
};



class Child : public Base1
{
public:
	virtual void func1() 
	{
		cout << "1" << endl;
	}
	virtual void func2()
	{
		cout << "2" << endl;
	}
};

typedef void (*PFUN)();

int main(int argc, char *argv[])
{
	Child c;

	PFUN *pvtable = (PFUN*)*(PFUN*)&c;
	PFUN pfun = (PFUN)pvtable[0];
	pfun();
	pfun = (PFUN)pvtable[1];
	pfun();
	cin.get();
	return 0;
}

内存中找到虚函数表,+0处为func(),汇编窗口中会被解析为指令,在汇编窗口中显示该地址,显示jmp C::func

子类重写虚函数后,表中函数会被覆盖。

继承多个虚函数类,会有多个虚函数表指针。

多态实现原理

c++的多态是通过父类中声明virtual,然后使用指针引用实现的。
virtual则是静态联编,编译阶段决定函数调用。
virtual则是动态联编(迟绑定),运行时表现为多态。

//重定义
#include <iostream>
using namespace std;

class Base 
{
public :
	void func() {
		cout << "Base" << endl;
	}
};

class Child : public Base
{
public:
	void func() {
		cout << "Child" << endl;
	}
};

int main()
{
	Base * pBase = nullptr; //静态联编,绑定函数
	
	Base base;
	Child child;
	pBase = &base;
	pBase->func();
	pBase = &child;
	pBase->func();
	return 0;
}
/*
Base
Base
*/

c函数指针和迟绑定是c++多态的理论基础,

类中声明虚函数时,编译器在类中生成虚函数表,而且是父类子类各一个;
虚函数表是存储类成员函数指针的数据结构,由编译器生成和维护;
虚函数会被编译器放入虚函数表;

若存在该表,每个对象创建时都会初始化一个指向该类虚函数表的指针vptr
对象构造结束时vptr的指向才确定;

执行函数时,编译器不需要区分父类或子类对象,而是确定函数是否为虚函数,若是,则根据对象的vptr查找函数入口地址并调用。
查找和调用都是运行时完成,即动态联编。普通成员函数在编译时就确定了调用函数,所以虚函数效率低。

关于vptr分步初始化

执行父类构造函数时,vptr指向父类虚函数表;

父类构造函数完成后,vptr指向子类,即本类的虚函数表;

#include<iostream>
using namespace std;

class P{
private:

public:
	virtual void print(){
		cout<<"P print"<<endl;
	}
protected:

};

class C : public P{
private:

public:
	virtual void print(){  //virtual 可不写 
		cout<<"C print"<<endl;
	}
protected:

};

void func(P &p){
	p.print();
}

int main(int argc, char *argv[])
{
	P *obj = NULL;
	P p;
	C c;
	
	obj = &p;
	obj->print();
	obj = &c;
	obj->print();
	
	func(p);
	func(c); 
	
	cin.get();
	return 0;
}
/*
P print
C print
P print
C print
*/

虚析构函数

对于父类:

  • 构造函数不能是虚函数,建立子类时,必须从根类逐层调用构造函数;
  • 析构函数要写为虚函数,用于指引delete运算符正确析构动态对象,把所有子类析构函数执行一遍,否则导致内存泄漏。
#include<iostream>
using namespace std;

class P{
private:

public:
	P(){
		cout<<"construct P"<<endl;
	}
 	virtual ~P(){
		cout<<"destruct P"<<endl;
	}
protected:

};

class C : public P{
private:

public:
	C(){
		cout<<"construct C"<<endl;
	}
	~C(){
		cout<<"destruct C"<<endl;
	} 
protected:

};

int main(int argc, char *argv[])
{
	{
		C c;
	}
	cout<<endl;
	P *p = new C();
	delete p;
    
	cin.get();
	return 0;
}
/*
construct P
construct C
destruct C
destruct P

construct P
construct C
destruct C
destruct P  若没有virtual,则不会释放父类内存.
*/

重载\重写\重定义

重载:

  • 同一类中;
  • 编译期间根据参数类型和个数决定函数调用;
  • 子类无法重载父类的函数,父类同名函数将被覆盖。

重写:

  • 父类与子类之间;
  • 父类与子类的函数必须有完全相同的原型;
  • 两类:声明virtual能够产生多态,不声明则是重定义
#include<iostream>
using namespace std;

class P{
private:

public:
	void print1(){
		cout<<"P print1()"<<endl;
	}
	void print1(int a){
		cout<<"P print1(int)"<<endl;
	} 
	
protected:

};

class C : public P{
private:

public:
	//redefinition
	void print1(){
		cout<<"C print1"<<endl;
	}
	
protected:

};

int main(int argc, char *argv[])
{
	P p;
	C c;
	
	//overload
	p.print1();
	p.print1(1);
	//c.print1();
	//c.print1(1);
	
	c.P::print1();
	c.print1();
	
	cin.get();
	return 0;
}
/*
P print1()
P print1(int)
P print1()
C print1
*/

父类和子类指针的步长

父类和子类大小不同,指针自加自减的步长也不同。

#include<iostream>
using namespace std;

class P{
private:

public:
	virtual void print(){
		cout<<"print P"<<endl;
	}
protected:

};
class C : public P{
private:
	int a;
public:
	virtual void print(){
		cout<<"print C"<<endl;
	}
protected:

};

int main(int argc, char *argv[])
{
	C childs[3] = {C(),C(),C()};
	P *pP = NULL;
	C *pC = NULL;
	
	pP = childs;
	pC = childs;
	pC->print();
	pP->print();
	
	pP++;
	pC++;
	pC->print();
	pP->print();
	
	cin.get();
	return 0;
}

纯虚函数和抽象类

纯虚函数是在基类中声明但没有定义的虚函数,相当于一个接口,并指定参数,每个派生类都要有自己的版本。
一个含有纯虚函数的类叫做抽象类
virtual void func( int ) = 0;

抽象类不能建立对象,不能作为返回和参数类型。
抽象类可以声明指针和引用。

抽象类可以降低程序耦合度。

在工程上,多继承对代码维护性的影响是灾难性的,如二义性和同名成员,所以几乎不使用。

#include<iostream>
using namespace std;

class Figure{
private:

public:
	virtual void printArea() = 0;
protected:

};
class Square : public Figure{
private:
	int a;
public:
Square(int a){
		this->a = a;
	}
	void printArea(){
		cout<<a*a<<endl;
	}
protected:

};
class Rectangle : public Figure{
private:
	int a,b;
public:
	Rectangle(int a , int b){
		this->a = a;
		this->b = b;
	}
	void printArea(){
		cout<<a*b<<endl;
	}
protected:

};

void showArea(Figure *p){
	p->printArea();
}

int main(int argc, char *argv[])
{
	Figure *p;
	Rectangle r(2,3);
	Square s(2);
	showArea(&r);
	showArea(&s);
	cin.get();
	return 0;
}

/*
   抽象类模拟接口
*/

#include<iostream>
using namespace std;

class Interface1{
private:

public:
	virtual void add(int a, int b) = 0;
protected:

};

class Interface2{
private:

public:
	virtual void mult(int a, int b) = 0;
protected:

};

class C : public Interface1,public Interface2{
private:

public:
	void add(int a, int b){
		cout<<a+b<<endl;
	}
	void mult(int a, int b){
		cout<<a*b<<endl;
	}
protected:

};

int main(int argc, char *argv[])
{
	C c;
	
	Interface1 *it1 = &c;
	it1->add(1,2);
	Interface2 *it2 = &c;
	it2->mult(2,3);
	
	cin.get();
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值