三、继承与多态:
1.基类与派生类:
(1)继承:
面对对象程序设计中最重要的一个概念是继承。
继承允许我们根据一个类去定义另一个类,这使得创建和维护一个应用程序变得更容易。这样做也达到了代码复用功能和提高执行效率的效果。当创建一个类时,不需要重新编写新的数据成员和成员函数,只需指定新建的类继承了一个已有的类的成员即可。这个已有的类称为基类,新建的类称为派生类。
(2)继承的基本语法:
class 子类 :继承方式 父类
2.继承方式:
(1)公有继承(public):
class Base
{
public:
int m_A;
protected:
int m_B;
private:
int m_C;
};
class Son1 :public Base
{
public:
void func()
{
m_A;//可访问,为public权限
m_B;//可访问,为protected权限
//m_C;//不可访问
}
};
void test01()
{
Son1 s1;
s1.m_A;
//s1.m_B;//错误,protected
//s1.m_C;//错误,父类private
}
子类Son1以公共的方式继承了Base类,在其内部可以访问m_A和m_B,不能访问父类中的私有成员m_C。类外的全局函数test01()中创建了Son1的对象s1,但只能访问m_A。
(2)保护继承(protected):
class Base
{
public:
int m_A;
protected:
int m_B;
private:
int m_C;
};
class Son2 :protected Base
{
public:
void func()
{
m_A;//可访问,为protected权限
m_B;//可访问,为protected权限
//m_C;//不可访问
}
};
void test02()
{
Son2 s2;
//s2.m_A;//错误,protected
//s2.m_B;//错误,protected
//s2.m_C;//错误,父类private
}
子类Son2以保护的方式继承了Base类,在其内部可以访问m_A和m_B且它们都是保护成员,不能访问父类中的私有成员m_C。类外的全局函数test01()既不能访问保护成员m_A和m_B,也不能访问m_C。
(3)私有继承(private):
class Base
{
public:
int m_A;
protected:
int m_B;
private:
int m_C;
};
class Son3 :private Base
{
public:
void func()
{
m_A;//可访问,为private权限
m_B;//可访问,为private权限
//m_C;//不可访问
}
};
class GrandSon3 :public Son3
{
public:
void func()
{
//m_A;//不可访问,为private权限
//m_B;//不可访问,为private权限
//m_C;//不可访问,为private权限
}
};
子类Son3以私有的方式继承了Base类,在其内部可以访问m_A和m_B且它们都是私有成员,不能访问父类中的私有成员m_C。
子类Son3的子类GrandSon3(姑且叫它孙子类吧)以公共的方式继承了Son3类,但由于Son3类中的成员均为私有权限,因此m_A和m_B以及m_C都不可访问。
综上,我们可以做出如下总结:
①子类无法访问父类的private成员。
②公有继承方式会保持父类成员的权限等级。
③保护继承方式会将父类的public权限升级为protected权限。
④私有继承方式会将父类的public和protected权限升级为privated权限。
3.继承中的构造与析构的调用原则:
(1)子类对象在创建时会首先调用父类的构造函数。
(2)父类构造函数执行结束后,执行子类的构造函数。
(3)当父类的构造函数有参数时,需要在子类的初始化列表中显式的调用。
(4)析构函数调用的先后顺序与构造函数相反。
#include
using namespace std;
class Base
{
public:
Base()
{
cout<<"Base构造函数"<
运行结果:
4.多态:
(1)基本概念:
多态(即“多种形态”)面向对象的基本特征之一。当类之间存在层次结构,并且类之间是通过继承关联时,就会用到多态,意味着调用成员函数时,会根据调用函数对象的类型来执行不同的函数。
(2)多态的好处:
1)应用程序不必为每一个派生类编写功能调用,只需要对抽象基类进行处理即可,大大提高了程序的可复用性。
2)派生类的功能可以被基类的方法或引用变量所调用,这叫向后兼容,提高可扩展性和可维护性。
(3)示例:
#include
#include
using namespace std;
class Animal
{
public:
virtual void speak()
{
cout<<"动物在说话"<
运行结果:
C++ 中父类的指针或者引用可以指向子类对象,因此Cat对象传入DpSpeak()函数编译器不会报错。
Animal类中的成员函数speak()前加virtual表示其是虚函数,编译器在编译阶段不能确定函数调用。
多态使得真正调用的函数地址在运行的时候才能确定,称为动态联编。
多态的使用可用于许多不同的Animal子类,DoSpeak()函数只需写一次即可。
多态满足条件:①有继承关系;②子类重写父类中的虚函数。
四、模板:
1.模板介绍:
模板是泛型编程的基础,泛型编程是以一种独立于任何特定类型的方式编写代码。
我们需要开发一个函数用于不同的数据类型,且不希望重复做类似的工作,比如下面这个例子:
//交换整型数
void swapInt(int& a, int& b)
{
int temp=a;
a=b;
b=temp;
}
//交换浮点型数
void swapDouble(double& a, double& b)
{
double temp=a;
a=b;
b=temp;
}
模板可以很好地解决这个问题,它可以将数据类型作为参数传递。
2.函数模板:
(1)语法:
template
template:声明创建模板
typename:表明其后面的符号是一种数据类型,可以用class代替
T:通用的数据类型(这里的T可以替换为其它大写字母)
(2)示例1:
#include
using namespace std;
template
void mySwap(T& a, T& b)
{
T temp=a;
a=b;
b=temp;
}
void test01()
{
int a=10;
int b=20;
mySwap(a,b);
cout<<"a="<
运行结果:
函数模板用字母T来代表一种类型,可以根据实际调用时传入的变量类型自动推导出T所代表的类型。无论传入的变量类型是int,double还是char都能够被自动推导出来。
(3)示例2:
调用时也可以显式地指定类型。
#include
using namespace std;
template
void mySwap(T& a, T& b)
{
T temp=a;
a=b;
b=temp;
}
void test02()
{
double c=10.1;
double d=20.2;
//mySwap(c,d);
mySwap(c,d);
cout<<"c="<
(4)总结:
1)关键字template。
2)使用函数模板有两种方式:自动推导类型和显式指定类型。
3)模板参数化的对象是类型。
4)自动推导类型必须推导出一致的数据类型T才能使用。
5)模板必须要确定出T的数据类型才能使用。
3.类模板:
(1)语法:
template
(2)示例:
#include
using namespace std;
template
class Person
{
public:
Person(NameType name, AgeType age)
{
this->mName=name;
this->mAge=age;
}
void showPerson()
{
cout<<"name:"<mName<<"age:"<mAge<p("孙悟空",1000);
p.showPerson();
}
void test02()
{
Personp("猪八戒",999);
p.showPerson();
}
int main()
{
test01();
test02();
return 0;
}
类模板不能自动进行类型推导,必须显式指定类型。类模板中的模板参数可以有默认参数。
类模板中的成员函数也可以类内声明类外实现,但是在类外必须加上模板参数列表。