3 c++编程-提高篇-模版

 重新系统学习c++语言,并将学习过程中的知识在这里抄录、总结、沉淀。同时希望对刷到的朋友有所帮助,一起加油哦!

 

 生命就像一朵花,要拼尽全力绽放!死磕自个儿,身心愉悦!

 系列文章列表:

1 c++编程-基础

2 c++编程-核心

 

写在前面,本篇章主要针对泛型编程学习。

1 模版

1.1 模版的概念

模版就是建立通用模具,减少重复工作量。

生活中也有很多使用模版的例子。例如:

        ppt模版:  年度总结ppt模版

        报表模版:  月度数据报表模版

模版的特点:

  1. 模版不可以直接使用,它只是一个框架。使用时需要自己填充业务特性的东西。
  2. 模版的通用并不是万能的,只适用于某些特定类型的场景。

 

1.2 函数模版

c++另一种编程思想称为 泛型编程,主要利用的技术就是模版。

c++提供两种模版机制: 函数模版    和   类模版

 

1.2.1 函数模版语法

函数模版的作用:

        给定一个通用函数,其函数返回值类型和形参类型都可以不具体给定,用一个虚拟的类型来代替。

语法:

        template<typename T>

        函数声明或定义

解释:

  • template:声明要创建模版
  • typename:表明其后的符号是一种数据类型。告诉编译器不要报错。typename可用class代替
  • T:通用数据类型。可以写成其他,通常为大写字母,例如 X或Y等。

调用使用模版函数的方式:

  1. 自动类型推导
  2. 显式指定数据类型

 

示例:


#include <iostream>
#include <string>

using namespace std;
// 交换两个整型数据
void swapInt(int& a, int& b) {
	int tmp = a;
	a = b;
	b = tmp;
}

// 交换两个浮点型数据
void swapDouble(double& a, double& b) {
	double tmp = a;
	a = b;
	b = tmp;
}

// 使用模版函数
template<typename T>
void mySwap(T& a, T& b) {
	T tmp = a;
	a = b;
	b = tmp;
}

void test() {
	int a = 1;
	int b = 2;
	swapInt(a, b);
	cout << "a = " << a << endl;
	cout << "b = " << b << endl;

	double c = 1.1;
	double d = 2.2;
	swapDouble(c, d);
	cout << "c = " << c << endl;
	cout << "d = " << d << endl;

}

void test2() {
	cout << "------------使用模版函数--------------" << endl;
	int a = 1;
	int b = 2;

	// 两种调用模版函数的方式:
	// 1 自动类型推导
	//mySwap(a, b);

	// 2 显式指定数据类型
	mySwap<int>(a, b);
	cout << "a = " << a << endl;
	cout << "b = " << b << endl;

}


int main() {
	test();

	test2();

	system("pause");
	return 0;
}

 

1.2.2 函数模版注意事项

注意事项:

  1. 自动类型推导,必须推导出一致的数据类型T,才可以使用
  2. 模板必须要确定出T的数据类型,才可以使用

示例:


#include <iostream>
#include <string>

using namespace std;

// 使用模版函数
template<typename T>
void mySwap(T& a, T& b) {
	T tmp = a;
	a = b;
	b = tmp;
}

template<typename T>
void func() {
	cout << "func调用" << endl;
}

void test() {
	int a = 1;
	int b = 2;
	double c = 3.3;

	mySwap(a, b);// 正确
	// mySwap(a, c);// 错误,必须推导出一致的数据类型T
}

void test2() {
	// func();  // 错误,无法确定出T的数据类型
	func<int>();  // 正确
	func<string>();  // 正确
}

int main() {
	test();
	test2();

	system("pause");
	return 0;
}

 

1.2.3 函数模版案例

案例:

  • 利用模版的技术封装一个排序函数,可以对不同数据类型进行排序;
  • 排序规则从大到小,排序算法为选择排序
  • char数组和int数组进行测试;

示例:


#include <iostream>
#include <string>

using namespace std;

// 交换函数
template<class T>
void mySwap(T& a, T& b) {
	T tmp = a;
	a = b;
	b = tmp;
}

// 选择排序算法
template<class T>
void mySort(T arr[], int len) {
	for (int i = 0; i < len; i++)
	{
		// 选定i下标的元素值为当前轮排序的最大值下标
		int max = i;
		for (int k = i + 1; k < len; k++) {
			// 若认定的最大值 比 遍历出的值 小,说明k下标的元素才是最大值
			if (arr[max] < arr[k]) 
			{
				max = k;  // 更新最大值下标
			}
		}
		if (max != i) {
			mySwap(arr[max],arr[i]);  // 交换max和i元素值
		}
	}
}

// 打印数组
template<class T>
void printArr(T arr[], int len) {
	for (int i = 0; i < len; i++) {
		cout << arr[i] << " ";
	}
	cout << endl;
}

// 测试char数组
void test() {
	char arr[] = { 'd','e','a','b','c' };
	int len = sizeof(arr) / sizeof(char);
	mySort(arr, len);
	printArr(arr, len);
}

// 测试int数组
void testInt() {
	int arr[] = { 4,2,3,1,6,5,7 };
	int len = sizeof(arr) / sizeof(int);
	mySort(arr, len);
	printArr(arr, len);
}

int main() {
	test();
	testInt();

	system("pause");
	return 0;
}

 

1.2.4 普通函数与函数模版的区别

区别:

  • 普通函数 调用可以发生隐式类型转换
  • 函数模版 用自动类型推导,不可以发生隐式类型转换
  • 函数模版 用显式指定类型,可以发生隐式类型转换

示例:

# include <iostream>
# include <string>

using namespace std;

// 普通函数
int add1(int a, int b) {
	return a + b;
}

// 模版函数
template<class T>
T add2(T a, T b) {
	return a + b;
}

void test1() {
	int a = 10;
	int b = 20;
	char c = 'c'; // a-97  c-99

	cout << add1(a, b) << endl;
	cout << add1(a, c) << endl;
}

void test2() {
	int a = 10;
	int b = 20;
	char c = 'c';
	cout << add2(a, b) << endl;

	// 报错, 用自动类型推导,不可以发生隐式类型转换
	// cout << add2(a, c) << endl; 

	// 用显式指定类型,可以发生隐式类型转换
	cout << add2<int>(a, c) << endl; 
}

int main() {
	test1();
	test2();

	system("pause");
	return 0;
}

 

1.2.5 普通函数与函数模版的调用规则

调用规则:

  1. 如果普通函数和函数模版都有实现,优先调用普通函数;
  2. 可以通过空模版参数列表来强制调用函数模版;
  3. 函数模版也可以发生重载;
  4. 如果模版函数可以产生更好的匹配,优先调用函数模版;

示例:

#include <iostream>
#include<string>

using namespace std;

void myPrint(int a) {
	cout << "调用普通函数" << endl;
}

template<class T>
void myPrint(T a) {
	cout << "调用模版函数" << endl;
}

// 函数模版也可以发生重载;
template<class T>
void myPrint(T a, T b) {
	cout << "调用重载的模版函数" << endl;
}

void test() {
	int a = 10;

	// 如果普通函数和函数模版都有实现,优先调用普通函数;
	myPrint(a);

	// 可以通过空模版参数列表来强制调用函数模版;
	myPrint<>(a);

	// 如果模版函数可以产生更好的匹配,优先调用函数模版;
	// 因为普通函数需要先做隐式类型转换,模版函数不需要,所以编译器认为模版函数更匹配
	char c = 'c';
	myPrint(c);
}

int main() {
	test();

	system("pause");
	return 0;
}

 

1.2.6 模版的局限性

模版并不是万能的,有些特定的数据类型,需要用具体化模版方式来实现。

例如:

template<class T>
void f(T a,T b)
{
    a=b;
}

在上述a=b;的赋值代码中,如果传入的a和b是数组,就无法实现,编译会报错。

示例:


#include <iostream>
#include <string>

using namespace std;

class Person {
public:
	Person(string name, int age) {
		m_name = name;
		m_age = age;
	}

	bool operator==(Person &p) {
		if (this->m_name == p.m_name) {
			return true;
		}
		else
		{
			return false;
		}
	}

	string m_name;
	int m_age;
};

template<class T>
bool myCompare(T &a, T &b) {
	if (a == b) {
		return true;
	}
	else {
		return false;
	}
}

// 利用具体化Person的版本实现,具体化会优先调用
// template<> 模版定义成空模版列表
// 模版函数参数列表T需要换成具体化的数据类型Person
template<> bool myCompare(Person& p1, Person& p2) {
	if (p1.m_name == p2.m_name && p1.m_age == p2.m_age) {
		return true;
	}
	else {
		return false;
	}
}

void test01() {
	int a = 10;
	int b = 20;
	bool ret = myCompare(a, b);
	if (ret) {
		cout << "a == b" << endl;;
	}
	else
	{
		cout << "a != b" << endl;
	}
}


void test02() {
	Person p1 = Person("echo", 18);
	Person p2 = Person("echo", 18);
	bool ret = myCompare(p1, p2);
	if (ret) {
		cout << "p1 == p2" << endl;;
	}
	else
	{
		cout << "p1 != p2" << endl;
	}
}

int main() {
	test01();
	test02();

	system("pause");
	return 0;
}

 

1.3 类模版

1.3.1 类模版语法

作用:建立一个通用类,类中的成员数据类型可以不固定,用虚拟类型来指定。

语法:

template<typename T>
class 类名{
    ……
}

示例:


#include <iostream>
#include <string>

using namespace std;

//也可以这样定义 template<typename NameType, typename AgeType>
template<class NameType,class AgeType>
class Person {
public:
	Person(NameType name, AgeType age) {
		this->m_name = name;
		this->m_age = age;
	}

	void PrintPerson() {
		cout << "name:" << m_name << ",age:" << m_age << endl;
	}

	NameType m_name;
	AgeType m_age;
};

void test() {
	Person<string,int> p1("echo", 18);
	p1.PrintPerson();

	Person<string, string> p2("bala", "不知道");
	p2.PrintPerson();
}

int main() {
	test();

	system("pause");
	return 0;
}

1.3.2 类模版与函数模版的区别

  1. 类模版没有自动堆导使用方式
  2. 类模版中的模版参数列表可以有默认数据类型

示例:


#include <iostream>
#include <string>

using namespace std;

template<typename NameType,typename AgeType = int>
class Person {
public:
	Person(NameType name, AgeType age) {
		this->m_name = name;
		this->m_age = age;
	}

	void PrintPerson() {
		cout << "name:" << m_name << ",age:" << m_age << endl;
	}

	NameType m_name;
	AgeType m_age;
};

void test() {
	// 1 无法用自动类型推导,只能用显式指定数据类型
	// Person p1("echo", 18); // 无法编译通过,错误
	Person<string, int> p1("echo", 18);
	p1.PrintPerson();

	// 2 类模版的模版参数列表可以指定默认数据类型
	Person<string> p2("tom", 30);
	p2.PrintPerson();
}

int main() {
	test();

	system("pause");
	return 0;
}

 

1.3.3 类模版中成员函数创建时机

普通类和类模版中成员函数的创建时机不同:

  1. 普通类中的成员函数在 一开始 就创建;
  2. 类模版中的成员函数在 调用时  才创建;

示例:


#include <iostream>
#include <string>

using namespace std;

class Person1 {
public:
	void showPerson1() {
		cout << "show Person1" << endl;
	}
};

class Person2 {
public:
	void showPerson2() {
		cout << "show Person2" << endl;
	}
};

template<typename T>
class MyPerson {
public:
	void fun1() {
		obj.showPerson1();
	}

	void fun2() {
		obj.showPerson2();
	}

	T obj;
};

void test1() {
	MyPerson<Person1> p;
	p.fun1();
	//p.fun2(); // 错误 因为:调用时确认obj是Person1数据类型,fun2成员未创建
}

void test2() {
	MyPerson<Person2> p;
	//p.fun1();  //错误 因为:调用时确认obj是Person2数据类型,fun1成员未创建
	p.fun2();
}

int main() {
	test1();
	test2();

	system("pause");
	return 0;
}

 

1.3.4 类模版对象做函数参数

类模版对象做函数参数共有三种形式:

  1. 指定传入类型--直接显式的使用数据类型做函数参数   (最常用的形式)
  2. 参数模版化--将类对象中的参数模版化来做函数参数
  3. 整个类模版化--将类对象模版化来做函数参数

示例:


#include <iostream>
#include <string>

using namespace std;
template<class T1,class T2>
class Person {
public:
	Person(T1 name, T2 age) {
		this->m_name = name;
		this->m_age = age;
	}

	void showPerson() {
		cout << "name:" << m_name << ",age:" << m_age << endl;
	}

	T1 m_name;
	T2 m_age;
};

void showPerson1(Person<string, int> &p) {
	p.showPerson();
}
//1 指定传入类型--直接显式的使用数据类型做函数参数
void test1() {
	Person<string, int> p("echo1", 18);
	showPerson1(p);
}

template<class T1,class T2>
void showPerson2(Person<T1, T2>& p) {
	p.showPerson();
}
//2 参数模版化--将类对象中的参数模版化来做函数参数
//3 整个类模版化--将类对象模版化来做函数参数
void test2() {
	Person<string, int> p("echo2", 19);
	showPerson2(p);
}

template<class T>
void showPerson3(T& p) {
	p.showPerson();
}
//3 整个类模版化--将类对象模版化来做函数参数
void test3() {
	Person<string, int> p("echo3", 20);
	showPerson3(p);
}

int main() {
	test1();
	test2();
	test3();

	system("pause");
	return 0;
}

 

1.3.5 类模版与继承

要继承类模版,需要注意:

  1. 当子类继承的父类是一个类模版时,子类在声明时,需要指定父类中T的数据类型。——如果不指定父类T的数据类型,编译器无法给子类分配内存,会编译不通过。
  2. 如果想灵活继承父类中T的数据类型,子类也需要定义成类模版

示例:


#include <iostream>
#include <string>

using namespace std;
template<class T>
class Base {
	T b;
};

// 错误写法
// 子类继承父类是类模版时,子类在声明时,需要指定父类中T的类型
//class Son :public Base {
//};

// 正确写法如下,指定T为int
class Son :public Base<int> {
};

void test1() {
	Son s;
}

// 如果想灵活指定父类中T的数据类型,子类也需变为模版
// T 是base中数据类型,Y是Son2中数据类型
template<class T,class Y>
class Son2 :public Base<T> {
	Y y;
};

void test2() {
	Son2<int, string> s;
}

int main() {
	test1();
	test2();

	system("pause");
	return 0;
}

 

1.3.6 类模版成员函数类外实现

示例:

#include <iostream>
#include <string>

using namespace std;

// 类模版中的成员函数在类外实现
template<class T1, class T2>
class Person {
public:
	Person(T1 name, T2 age);

	void PrintPerson();

	T1 m_name;
	T2 m_age;
};

// 类模版中的成员函数在类外实现
template<class T1,class T2>
Person<T1,T2>::Person(T1 name, T2 age)
{
	m_name = name;
	m_age = age;
}

template<class T1, class T2>
void Person<T1, T2>::PrintPerson()
{
	cout << "name:" << m_name << ",age:" << m_age << endl;
}

void test() {
	Person<string, int> p1("echo", 18);
	p1.PrintPerson();
}

int main() {
	test();

	system("pause");
	return 0;
}

 

1.3.7 类模版分文件编写

问题:

        普通类分文件编写,会直接将类分成头文件a.h文件和源文件a.cpp文件。然后在调用程序中添加#include "a.h",程序就直接调用类成员函数。但是,如果是类模版,该方式分文件编写及调用会出现编译问题。

原因:

        类模版中成员函数是在调用时才创建,直接添加 .h会找不到函数。

解决办法:

        方法1:直接包含.cpp源文件。

        方法2:将声明和实现写在一起,不要分文件编写,并将文件名后缀写成.hpp。.hpp是大家约                      定的类模版文件名写法,不是强制的。——常用方式

 

1.3.8 类模版与友元

全局函数类内实现:

        直接在类内声明是友元即可;

全局函数类外实现:

        (1) 需要提前让编译器知道类模版;

        (2) 需要提前让编译器知道全局函数存在;

示例:


#include <iostream>
#include <string>

using namespace std;

// 要提前让编译器知道Person类存在
template<class T1, class T2>
class Person;

// 全局函数类外实现
template<class T1, class T2>
void showPerson2(Person < T1, T2>& p) {
	cout << "类外实现--name:" << p.m_name << ",age:" << p.m_age << endl;
}

template<class T1,class T2>
class Person {
	// 全局函数类内实现
	friend void showPerson(Person < T1, T2>& p ){
	cout << "类内实现--name:" << p.m_name << ",age:" << p.m_age << endl;
	}

	// 全局函数类外实现
	// 注意:要加空模版参数列表
	// 如果全局函数是类外实现,需要让编译器提前知道这个函数存在,不然编译不通过
	friend void showPerson2<>(Person < T1, T2>& p);

public:
	Person(T1 name, T2 age) {
		this->m_name = name;
		this->m_age = age;
	}

private:
	T1 m_name;
	T2 m_age;
};

void test() {
	Person<string, int> p("echo", 18);
	showPerson(p);
}

void test2() {
	Person<string, int> p("echo", 18);
	showPerson2(p);
}

int main() {
	test();
	test2();

	system("pause");
	return 0;
}

 

 


posted on 2022-11-24 11:33  爱学习的小灵子  阅读(31)  评论(0编辑  收藏  举报