C++基础篇 Day8

1.继承

继承概念
定义: 被继承的类叫做基类(父类),继承的类叫做派生类(子类)。
使用方法: 通过继承关系,子类可以使用父类的成员。如果子类和父类有同名的成员,默认使用
子类的成员
,如果想使用父类成员,需要在成员名前面加上类名:: 用于显式的指定区分。

内存空间:成员在内存空间分布为先父类成员后子类成员,而每个类中成员分布与在类中声明顺序一致。

#include<iostream>
using namespace std;
//继承: 纵向关系,子类继承父类 可以使用父类的成员,也会包含父类
//表示:子类类名:继承方式 父类
class CFather
{
public:
	int m_fa;
	int m_money;
	void funFather()
	{
		cout << __FUNCTION__ << endl;

	}
	CFather():m_fa(4),m_money(200){}
};
class CSon:public CFather
{
public:
	int m_son;
	int m_money;
	CSon() :m_son(1), m_money(100)
	{
		m_fa = 2;
		funFather();
	}
};
int main()
{
	CSon son;
	cout << son.m_son << " " << son.m_fa << endl;
	son.funFather();
	son.m_money = 100;
	cout << son.CSon::m_money << " " << son.CFather::m_money << endl;
}

测试结果: 

 

继承方式

继承方式描述了父类成员在子类中的适用范围。继承方式访问修饰符共同决定了父类成员在子类中所表现的属性。

三种继承方式:public、private、protected

继承方式父类中的属性子类中的属性
publicpublicpublic
private不可访问
protectedprotected
privatepublicprivate
private不可访问
protectedprivate
protectedpublicprotected
private不可访问
protectedprotected

继承优点

概括来说,继承可以将功能比较相似的类中的公共成员抽离出来形成一个类,也就是父类,子类通过继承父类,可以使用父类成员,公共的成员在父类只需维护一份代码即可。若要添加公共成员,只需在父类进行添加即可,增加了代码的复用性、扩展性、灵活性,减少代码的冗余。 

测试代码:

#include<iostream>
using namespace std;
class CPeople
{
public:
	int m_money;
	CPeople() :m_money(10) {};
	void cost(int n)
	{
		m_money -= n;
		cout << "买吃的" << endl;
	}
	void walk()
	{
		cout << "跑" << endl;
	}
};
class CWhite :public CPeople
{
public:
	CWhite(){}
	void eat()
	{
		cout << "吃汉堡" << endl;
	}
};
class CYellow :public CPeople
{
public:
	CYellow(){}
	void eat()
	{
		cout << "吃米饭" << endl;
	}
};
class CBlack :public CPeople
{
public:
	CBlack(){}
	void eat()
	{
		cout << "吃手抓饭" << endl;
	}
};
int main() {
	CWhite white;
	white.cost(2);
	white.eat();
	white.walk();
	return 0;
}

内存空间

 测试代码:

#include<iostream>
using namespace std;

class CFather
{
public:
	int m_fa;
	int m_money;
	CFather() :m_fa(4), m_money(200)
	{
		cout << "..CFather()" << endl;
	}
	CFather(int a) :m_fa(a), m_money(200)
	{
		cout << "CFather()" << endl;
	}

};
class CSon :public CFather
{
public:
	int m_son;
	int m_money;
	CSon() :CFather(),m_son(1), m_money(100)
	{
		cout << "..CSon()" << endl;
	}
	CSon(int a) :CFather(), m_son(1), m_money(100)
	{
		cout << "CSon()" << endl;
	}
};
int main()
{
	cout << sizeof(CFather) << " " << sizeof(CSon) << endl;  //8 16
	CSon son;  //..CFather() ..CSon()
	cout <<"首地址 : "<<&son << endl;  //首地址 : 003EF844
	cout << &son.m_fa << endl;  //003EF844
	cout <<&son.CFather::m_money << endl;  //00AFFCD8
	cout << &son.m_son << endl;  //003EF84C
	cout << &son.CSon::m_money << endl;  //003EF850

}

2.构造-析构执行顺序

构造函数

执行顺序:父构造->子构造(先父后子)

说明:在子类创建对象的时候,会先跳转到子类的构造函数,(注意这里并不是先直接执行父类的析构函数),先执行构造的初始化参数列表,初始化顺序看在类中声明的先后顺序(内存排布的先后顺序),由于父类成员在前,子类成员在后,所以先初始化父类成员,调用父类的构造函数(默认调用父类的无参构造,如果父类只有带参的构造,在子类的初始化参数列表中必须显示的指定父类构造)。初始化完毕后,再回到子类初始化参数列表,按顺序继续初始化其他成员,待所有成员初始化结束后,再执行子类的构造函数体。

析构函数

 执行顺序:子析构->父析构(先子后父)

说明:子类对象生命周期结束后,自动调用子类析构,当析构执行完后,才会回收对象分配的内存空间(可理解成与构造顺序相反),这个空间包含创建的父类对象,那么回收父类对象前,会自动调用父类的析构,再进行回收。

测试父类、子类的构造、析构顺序:

#include<iostream>
using namespace std;

class CFather
{
public:
	CFather() { cout << "CFather" << endl; }
	~CFather() { cout << "~CFather" << endl; };

};
class Chand
{
public:
	Chand() { cout << "Chand" << endl; }
	~Chand() { cout << "~Chand" << endl; }

};
class CSon :public CFather
{
public:
	Chand m_hand;
	CSon() { cout << "CSon" << endl; }
	~CSon() { cout << "~CSon" << endl; };
};
int main()
{
	CSon son;

}

测试结果: 

 上三行输出为构造函数,下三行输出为析构函数。可见,构造函数与析构函数执行顺序相反。

3.隐藏

概念:对于父类和子类中,如果有同名的函数但是参数列表不同,则不能自动区分匹配,因为他们之间的关系并不是函数重载的关系(作用域不同),如果要调用,必须使用类名作用域去区分要调用哪个函数。这种关系称之为隐藏。

#include<iostream>
using namespace std;
class CFather
{
public:
	void fun()  //不是函数重载 
	{
		cout << "fun()" << endl;
	}
};
class CSon:public CFather
{
public:
	void fun(int a)
	{
		cout << "fun(int a) " << a << endl;
	}
};
int main()
{
	CSon tst;
	//tst.fun();  //匹配子类的(不能匹配父类的)
	tst.CFather::fun();
	tst.fun(10);
}

 4.父类指针指向子类对象
在继承关系下,允许父类指针指向子类对象,但是反过来却不行。这么做的好处是:父类指针可以统一多个类的类型,提高代码复用性、扩展性。

#include<iostream>
using namespace std;
class CPeople
{
public:
	int m_money;
	CPeople() :m_money(10) {};
	void cost(int n)
	{
		m_money -= n;
		cout << "买吃的" << endl;
	}
	void walk()
	{
		cout << "跑" << endl;
	}
};
class CWhite :public CPeople
{
public:
	CWhite(){}
	void eat()
	{
		cout << "吃汉堡" << endl;
	}
};
class CYellow :public CPeople
{
public:
	CYellow(){}
	void eat()
	{
		cout << "吃米饭" << endl;
	}
};
class CBlack :public CPeople
{
public:
	CBlack(){}
	void eat()
	{
		cout << "吃手抓饭" << endl;
	}
};
void fun(CPeople* p)
{
	p->cost(2);
	//p->eat();  // error: eat()不是CPeople中的成员
	p->walk();

}
int main() {

	fun(new CWhite);
	fun(new CYellow);
	fun(new CBlack);
	return 0;
}

测试结果:

 上面的代码有一个问题,就是不可以通过父类指针调用子类不统一的函数,即p->eat(),下面给出一个解决方案。

类成员函数指针:

定义类成员函数指针格式:类名 整体操作符(::*) 指针名

普通函数指针

void fun()
{
	cout << "fun" << endl;
}
	void(*p_fun)() = &fun;
	(*p_fun)();

 再用typedef进行优化:

	typedef void (*P_FUN)();
	P_FUN p_fun = &fun;
	(*p_fun)();

类成员函数指针

调用函数的两种方式:1.通过函数名直接调用  2.通过函数指针间接调用

上面提到 定义类成员函数指针格式:类名 整体操作符(::*) 指针名 ,下面通过代码感受一下。

#include<iostream>
using namespace std;
class CTest
{
public:
	void fun(/* CTest* const this */)
	{
		cout << "fun" << endl;
	}
};
int main()
{
	//1.
	CTest tst;
	void(CTest:: * p_fun)() = &CTest::fun;
	(tst.*p_fun)();
	//2.
	CTest* pTst = new CTest;
	void(CTest:: * p_fun)() = &CTest::fun;
	(pTst->*p_fun)();
}

上述代码中的 .*  和 ->* 都是C++提供的整体操作符

类成员函数与普通函数的区别:

1.所属作用域不同,类成员函数标识了所属的类,必须通过对象调用(可以是空指针对象,但必须得有)。

2.类成员函数编译器会默认加上一个隐藏的参数this指针,而普通函数没有。

举例:

先定义父类和子类:

class CFather {
public:

};
class CSon : public CFather {
public:
	void fun() {
		cout << __FUNCTION__ << endl;
	}
};

如果要通过父类指针指向并调用子类对象中特有的成员函数时,首先要创建一个类成员函数指针。

	CFather* pFa = new CSon;
	//pFa->fun();  //error

	//--------------- 通过类成员函数指针调用----------------
	//1.
	void(CFather ::* p_fun)() = (void(CFather ::*)()) &CSon::fun;
	(pFa->*p_fun)();  //CSon::fun
	//2.用typedef进行简化
	typedef void(CFather ::* P_FUN)();
	P_FUN p_fun2 = (P_FUN)&CSon::fun;
	(pFa->*p_fun2)();  //CSon::fun

注意:我们创建的是父类成员函数指针,指向的是子类成员函数的地址,由于两边的类型不同,所以要将右边的子类成员函数地址强转为父类成员函数指针类型

下面我们用类成员函数指针解决 p->eat() 的问题。

#include<iostream>
using namespace std;
class CPeople
{
public:
	int m_money;
	CPeople() :m_money(10) {};
	void cost(int n)
	{
		m_money -= n;
		cout << "买吃的" << endl;
	}
	void walk()
	{
		cout << "跑" << endl;
	}
};
class CWhite :public CPeople
{
public:
	CWhite(){}
	void eat()
	{
		cout << "吃汉堡" << endl;
	}
};
class CYellow :public CPeople
{
public:
	CYellow(){}
	void eat()
	{
		cout << "吃米饭" << endl;
	}
};
class CBlack :public CPeople
{
public:
	CBlack(){}
	void eat()
	{
		cout << "吃手抓饭" << endl;
	}
};
typedef void(CPeople ::* P_FUN)();
void fun(CPeople*p, P_FUN p_fun)
{
	p->cost(2);
	//p->eat();  //error
	(p->*p_fun)();
	p->walk();

}
int main()
{
	fun(new CWhite, (P_FUN)&CWhite::eat);
	fun(new CYellow, (P_FUN)&CYellow::eat);
	fun(new CBlack, (P_FUN)&CBlack::eat);
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值