C++中的继承、多态和模板函数

一、继承

1、属性和方法的继承

继承可以更好的实现代码的重用性

#include <stdlib.h>
#include <iostream>

using namespace std;

//开发者
class Developer {
protected:
	char* language;
	char* ide;
	int age;
public:
	void say() {
		cout << "我是开发者" << endl;
	}
};

//Android开发者
class AndroidDeveloper : public Developer {
public:
	AndroidDeveloper() {
		this->language = (char*)"Android+Kotlin";
		this->ide = (char*)"Android Stuio";
	}
	//开发Android Application
	void createAndroidApp() {
		cout << "我使用" << this->ide << "开发了一款Android应用,使用了" << this->language << "语言" << endl;
	}
private:
	//Android 版本
	char* androidVersion;

};

//所有开发者都有开发工作
void work(Developer& d) {
	d.say();
}

void main() {
	AndroidDeveloper androidDev;
	androidDev.say();
	androidDev.createAndroidApp();
    //子类对象初始化父类类型的对象
	Developer d1 = androidDev;
	work(d1);
	//父类类型的指针
	Developer* d_p = &androidDev;
	d_p->say();
	//父类类型的引用
	Developer d2 = androidDev;
	d2.say();

	getchar();
}

2、通过子类给父类构造方法传参

父类的构造函数先调用;子类的析构函数先调用

//开发者
class Developer {
protected:
	char* language;
	char* ide;
	int age;
public:
	Developer(char * language, char* ide, int age) {
		this->language = language;
		this->ide = ide;
		this->age = age;
		cout << "Developer 构造函数" << endl;
	}
	~Developer() {
		cout << "Developer 析构函数" << endl;
	}
	void say() {
		cout << "我是开发者" << endl;
	}
};

//Android开发者
class AndroidDeveloper : public Developer {
public:
	AndroidDeveloper(char* language, char* ide, int age, char* androidVersion) : Developer(language, ide, age) {
		this->language = language;
		this->ide = ide;
		this->age = age;
		cout << "AndroidDeveloper 构造函数" << endl;
	}
	~AndroidDeveloper(){
		cout << "AndroidDeveloper 析构函数" << endl;
	}
	//开发Android Application
	void createAndroidApp() {
		cout << "我使用" << this->ide << "开发了一款Android应用,使用了" << this->language << "语言" << endl;
	}
private:
	//Android 版本
	char* androidVersion;

};

void work(Developer& d) {
	d.say();
}

//父类的构造函数先调用
//子类的析构函数先调用
void func() {
	AndroidDeveloper androidDev((char*)"Kotlin", (char*)"Android Studio", 5, (char*)"5.0.1");
	androidDev.say();
	androidDev.createAndroidApp();
}

void main() {
	func();
	getchar();
}

输出:

Developer 构造函数
AndroidDeveloper 构造函数
我是开发者
我使用Android Studio开发了一款Android应用,使用了Kotlin语言
AndroidDeveloper 析构函数
Developer 析构函数

3、继承中父类和子类的权限继承关系

基类中 继承方式 子类中
public & public继承 => public
public & protected继承 => protected
public & private继承 => private
protected & public继承 => protected
protected & protected继承 => protected
protected & private继承 => private
private & public继承 => 子类无权访问
private & protected继承 => 子类无权访问
private & private继承 => 子类无权访问

4、继承的二义性

4.1 继承的二义性定义

在某个类B同时继承另一个类A的两个或多个子类时(A1和A2),通过类B访问类A的成员时,会出现成员不明确的情况,即继承的二义性

class A {
public:
	char* name;
};

class A1 : public A {

};

class A2 : public A {

};

class B : public A1, public A2 {

};

void main() {
	B b;
	//报错,提示B::name不明确
	//b.name = (char*)"Jack";

	//指定父类显式调用
	b.A1::name = (char*)"Rose";
	getchar();
}

4.2 继承的二义性定义解决方案

再遇到继承的二义性时,可使用虚继承来解决继承的二义性问题
虚继承:不同路径继承来的同名成员只有一份拷贝

class A {
public:
	char* name;
};

class A1 : virtual public A {

};

class A2 : virtual public A {

};

class B : public A1, public A2 {

};

void main() {
	B b;
	//报错,提示B::name不明确
	//b.name = (char*)"Jack";

	//指定父类显式调用
	b.A1::name = (char*)"Rose";
	getchar();
}

二、多态

  • 多态是为了提高程序的扩展性
  • 动态多态:子类重写父类的函数,程序运行过程中,决定哪一个函数被调用
  • 静态多态:就是函数重载

1、虚函数

virtual 关键字修饰的函数叫虚函数,用来实现多态

例如:

Plane.h

#pragma once

class Plane {
public:
	virtual void fly();
	virtual void land();
};

Plane.cpp

#include "Plane.h"
#include <stdlib.h>
#include <iostream>

using namespace std;
void Plane::fly() {
	cout << "飞机起飞" << endl;
}

void Plane::land() {
	cout << "飞机降落" << endl;
}

Helicopter.h

#pragma once
#include "Plane.h"

class Helicopter : public Plane {
public:
	virtual void fly();
	virtual void land();
};

Helicopter.cpp

#include "Helicopter.h"
#include <stdlib.h>
#include <iostream>

using namespace std;
void Helicopter::fly() {
	cout << "直升飞机在原地起飞" << endl;
}

void Helicopter::land() {
	cout << "直升飞机降落在屋顶" << endl;
}

Test.cpp

#include <stdlib.h>
#include <iostream>
#include "Plane.h"
#include "Helicopter.h"
using namespace std;

//业务函数

void runPlane(Plane &p) {
	p.fly();
	p.land();
}

void main() {
	Plane p;
	
	runPlane(p);
	Helicopter h;
	//在 Plane.h 和 Helicopter.h 中的函数上不使用 virtual 修饰时,打印“飞机起飞”和“飞机降落”
	//使用 virtual 修饰时,打印“直升飞机在原地起飞”和“直升飞机降落在屋顶”,实现多态
	runPlane(h);
	getchar();
}

2、发生动态多态的条件

  • 使用继承
  • 父类的引用或指针指向子类的对象
  • 函数的重写

3、纯虚函数(抽象类)

  • 当一个类具有一个纯虚函数时,这个类就是抽象类
  • 抽象类不能被实例化
  • 子类继承抽象类,必须要实现纯虚函数,如果没有重新,子类也是抽象类
//形状
class Shape {
public:
	virtual void sayArea() = 0;
};

//圆
class Circle : public Shape {
private:
	int r;
public:
	Circle(int r) {
		this->r = r;
	}
	void sayArea() {
		cout << "圆的面积:" << 3.14 * r * r << endl;
	}
};

void main() {
	Circle c(5);
	c.sayArea();
	getchar();
}

4、接口

接口只是逻辑上的划分,语法上跟抽象类的写法没有区别

//可以看作一个接口
class Drawable{
    virtual void draw() = 0;
}

5、抽象类的作用

为了继承约束,子类必须按照约束实现

//可以看作一个接口
class Drawable{
    virtual void draw() = 0;
}

二、模板函数(泛型)

函数模板类似于泛型,用于在业务相同,参数类型不同时进行声明,在使用过程中,根据实际类型进行推导

template <typename T,typename Z>
//交换两个变量的值
void swap(T& a, Z& b){
    T tmp = 0;
    tmp = a;
    a = b;
    b = tmp;
}

void main(){
    int a = 10;
    int b = 25;
    swap(a,b);
    cout << a << "," << b << endl;
    
    char* x = (char*)"abc";
    char* y = (char*)"def";
    swap(x,y);
    cout << x << "," << y << endl;
    
    getchar();
}