从本篇文章开始将要讲解C++里面最重要的内容:继承与多肽。为什么要把继承和多肽放在一块讲是因为多肽只能发生在父类和子类之间(也就是没有继承就没有多肽)。
继承与多肽我准备花费三个篇幅来讲解:
1.继承与多肽-基础入门
2.继承与多肽-项目实战
3.继承与多肽-实现原理
关于上面的三个篇幅我将采用三篇文章来讲解,今天这篇文章先讲解.继承与多肽的基础入门篇。
基础入门篇我主要是先讲解下和多肽有关的几个核心概念:函数重写、赋值兼容性原则、多肽
1.函数重写
说到函数重写大多数人肯定会和函数重载相混淆,如果有混淆的请看我的 “函数重载和函数重写的本质区别” 。函数重写就是子类定义了和父类一模一样的成员函数(包括参数个数、参数顺序、返回值都一模一样),函数体可以不一样。例如下面的代码:
#include
using namespace std;
class Parent
{
protected:
int nMoney;
public:
// 父类构造函数
Parent()
{
nMoney = 10;
}
// 老爸炫富方法
void showMoney()
{
cout << "Parent::showMoney() = " << nMoney << endl;
}
};
class Child : public Parent
{
protected:
int nMoney;
public:
// 子类的构造函数
Child()
{
nMoney = 100;
}
// 孩子也有炫富的方法 和老爸一模一样 只是孩子挣的钱比较多了 老爸10万 孩子是老爸的十倍
void showMoney()
{
cout << "Child::showMoney() = " << nMoney << endl;
}
};
int main()
{
Child c;
c.showMoney();
return 0;
}
程序运行结果:Child::showMoney() = 100
我们可以看到父类和子类都有相同的属性 int nMoney(只是老爸是10万,而孩子却又100万),并且还有相同的炫富方法void showMoney()。上一节我们已经说过当父类和子类有相同的成员变量时,默认(在不适应作用于分辨符的情况)会访问子类那个。其实如果父类和子类有相同的成员函数,虽然父类的那个成员函数已经继承给子类,但是在用子类的对象去调用这个成员函数时,默认还是会使用子类的那个重写函数,也就是我们的c.showMeney()为什么会打印 Child::showMoney() = 100的原因,因为默认是使用子类的成员变量、子类的成员函数。这个在子类和父类都有的showMoney()函数,就是函数重写。简单的说,函数重写就是定义了一个和父类一样的函数,为了下面方便理解,我讲的比较多。
2.赋值兼容性原则
赋值兼容性原则概念:子类的对象可以当做父类使用。
赋值兼容性原则就是父类的指针和引用可以操作子类的对象,这一句话看似很简单,但是真的要是可以灵活用到你的工程上,你的工程将会变得非常容易管理。例如根据上面的代码我们在main函数中有如下定义:
Child c1;
Child c2;
Parent *p1 = &c1;
Parent &p2 = c2;
C++中什么样的指针或者引用必须指向什么样的对象,但是由于赋值兼容性原则,子类可以当做父类使用,所以一个父类的指针可以指向子类的对象,因为编译器会把子类当做父类。假设一下,如果编译器把c1和c2当做Child类型的对象,程序必然会报错,因为一个Parent指针指向了一个不是Parent类型的对象。所以编译器在这里是把c1和c2当做Parent类型的对象。这就是我们平时说的赋值兼容性原则。为了验证上面所说的,我们来看一个程序:
#include
using namespace std;
class Parent
{
protected:
int nMoney;
public:
// 父类构造函数
Parent()
{
nMoney = 10;
}
// 老爸炫富方法
void showMoney()
{
cout << "Parent::showMoney() = " << nMoney << endl;
}
};
class Child : public Parent
{
protected:
int nMoney;
public:
// 子类的构造函数
Child()
{
nMoney = 100;
}
// 孩子也有炫富的方法 和老爸一模一样 只是孩子挣的钱比较多了 老爸10万 孩子是老爸的十倍
void showMoney()
{
cout << "Child::showMoney() = " << nMoney << endl;
}
};
int main()
{
Child c1;
Child c2;
Parent *p1 = &c1; // 父类的指针指向子类的对象1
Parent &p2 = c2; // 父类的引用指向子类的对象2
p1->showMoney();
p2.showMoney();
return 0;
}
程序输出结果:
Parent::showMoney() = 10
Parent::showMoney() = 10
疑问:
Parent *p1 = &c1; 这句代码明明是引用了c1,为什么还是调用了父类的炫富函数?
Parent &p2 = c2; 这句代码明明值指向了c2,为什么还是调用了父类的炫富函数?
答案:这个就是复制兼容性原则在搞鬼,因为编译器会把c1和c2当做父类的对象,所以当然会去使用父类的炫富函数。这个往往不是我们需要的,我们需要的往往是指向子类对象就使用子类的函数,指向父类对象就使用父类的函数。这样哦我们呢就可以引出多肽的概念了。
3.多肽:多肽就是一个调用语句可以实现多种状态。
下面我把上面的代码实现多肽,再看看运行结果,大家就可以理解多肽是个什么样的东西了。
#include
using namespace std;
class Parent
{
protected:
int nMoney;
public:
// 父类构造函数
Parent()
{
nMoney = 10;
}
// 老爸炫富方法, 使用 virtual 关键字可以实现多台机制
virtual void showMoney()
{
cout << "Parent::showMoney() = " << nMoney << endl;
}
};
class Child : public Parent
{
protected:
int nMoney;
public:
// 子类的构造函数
Child(int money)
{
nMoney = money;
}
// 孩子也有炫富的方法和老爸一模一样 只是孩子挣的钱比较多了 老爸10万 孩子是老爸的十倍
// 这里的关键字不使用也可以,因为当父类中使用了virtual,子类重写的函数自动的也会变成虚函数
virtual void showMoney()
{
cout << "Child::showMoney() = " << nMoney << endl;
}
};
int main()
{
Child child1(10); // 定义了一个 Child 类型的对象 child1,炫富值:10
Child child2(20); // 定义了一个 Child 类型的对象 child2,炫富值:20
Parent parent; // 定义了一个 Parent 类型的对象 parent,炫富值:10
Parent * p1; // 定义了一个父类的指针,用来操作父类和子类对象使用
p1 = &child1; // 指向child1
p1->showMoney();
p1 = &child2; // 指向child2
p1->showMoney();
p1 = &parent; // 指向parent
p1->showMoney();
return 0;
}
程序运行结果:
Child::showMoney() = 10
Child::showMoney() = 20
Parent::showMoney() = 10
看似不起眼的结果背后却蕴藏了一个程序设计中非常常用的设计方法,下面我先讲解下这段代码。
大家有没有发现两个类除了子类的构造函数发生变化以外还有什么发生了变化?父类中被重写的函数加了一个 virtual 关键字,子类重写的那个函数也加了关键字 virtual 。这样就可以实现我们所说的多肽。mian函数里面定义了两个子类对象,炫富值分别是10和20,并且定义了一个父类的对象和指针。
我们可以发现虽然使用了同样调用语句p1->showMoney(),却产生了不同的结果,这就是上面说的一个调用语句可以实现多种状态。
到这里多肽已经讲完,总结下要实现多肽所必须的几个条件:
1.多肽是发生在父类和子类之间的,单一的一个类不可能实现多肽。
2.父类和子类必须发生重写函数,并且父类被重写的函数前面必须用 virtual 关键字进行修饰。
3.在使用多肽特性时,必须定义一个父类的指针或者引用。(这个在下一节 “继承与多肽-项目实战” 会详细讲解为什么要这样做)
在进入下一节之前,我们可以思考一个问题:
现在有一个父类P1,有50个子类 C1-C50继承了它,这50个子类中都重写了父类P1的某个函数(比如说showMoney()函数),50个子类每个子类都有对应的一个对象,总共也就是50个不同类型对象,现在要对这50个不同类型的对象全体炫富一次用程序怎么实现?
说明:我们上面的例子只是一个子类,用这个子类定义了两个同类型的对象,而上面问题说的是50个子类,每个子类定义1个对象,然后对每个对象炫下富。
下一节我们会用一个比较典型的小项目来深入讲解怎么在工程里面巧妙的使用多肽并且解决上面的问题。