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
继承方式 | 父类中的属性 | 子类中的属性 |
public | public | public |
private | 不可访问 | |
protected | protected | |
private | public | private |
private | 不可访问 | |
protected | private | |
protected | public | protected |
private | 不可访问 | |
protected | protected |
继承优点
概括来说,继承可以将功能比较相似的类中的公共成员抽离出来形成一个类,也就是父类,子类通过继承父类,可以使用父类成员,公共的成员在父类只需维护一份代码即可。若要添加公共成员,只需在父类进行添加即可,增加了代码的复用性、扩展性、灵活性,减少代码的冗余。
测试代码:
#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);
}