C++提高编程——模板

本专栏记录C++学习过程包括C++基础以及数据结构和算法,其中第一部分计划时间一个月,主要跟着黑马视频教程,学习路线如下,不定时更新,欢迎关注

当前章节处于:

---------第1阶段-C++基础入门

---------第2阶段实战-通讯录管理系统,

---------第3阶段-C++核心编程,

---------第4阶段实战-基于多态的企业职工系统

=====>第5阶段-C++提高编程

---------第6阶段实战-基于STL泛化编程的演讲比赛

---------第7阶段-C++实战项目机房预约管理系统

文章目录

  • 一、概念
  • 二、函数模板
    • [2.1 基本语法](#2.1 基本语法)
    • [2.2 注意事项](#2.2 注意事项)
    • [2.3 普通函数和模板函数的区别](#2.3 普通函数和模板函数的区别)
    • [2.4 普通函数和函数模板调用规则](#2.4 普通函数和函数模板调用规则)
    • [2.5 模板的局限性](#2.5 模板的局限性)
  • 三、类模板
    • [3.1 基本语法](#3.1 基本语法)
    • [3.2 类模板中成员函数创建时机](#3.2 类模板中成员函数创建时机)
    • [3.3 类模板对象做函数参数](#3.3 类模板对象做函数参数)
    • [3.4 类模板与继承](#3.4 类模板与继承)
    • [3.5 类模板成员函数类外实现](#3.5 类模板成员函数类外实现)
    • [3.6 类模板分文件编写](#3.6 类模板分文件编写)
    • [3.7 类模板和友元](#3.7 类模板和友元)

一、概念

模板就是建立通用的模具,大大提高复用性。模板不可以直接使用,只是一个框架,并且模板的通用并不是万能的。使用模板的目的是提高复用性,将类型参数化。

二、函数模板

2.1 基本语法

template
函数声明或定义

  • template:声明创建模板
  • typename:表明其后面的符号是一种数据类型,可以用class代替
  • T:通用的数据类型,名称可以替换,通常为大写字母,可以定义多个
cpp 复制代码
#include <iostream>
using namespace std;


// 定义一个交换的函数模板
template <typename T>
void Myswap(T& a, T& b) {
	T temp = a;
	a = b;
	b = temp;
}

void test() {
	int a = 10;
	int b = 20;
	char c = 'c';
	char d = 'd';
	// 1. 自动推导型
	Myswap(a, b);
	cout << "a=" << a << endl;
	cout << "b=" << b << endl;
	// 2. 显示推导型
	Myswap<char>(c, d);
	cout << "c=" << c << endl;
	cout << "d=" << d << endl;

}

int main() {
	test();
	system("pause");
	return 0;

}
cpp 复制代码
a=20
b=10
c=d
d=c
请按任意键继续. . .

2.2 注意事项

  1. 自动类型推导,必须推导出一致的数据类型,才可以使用
  2. 模板必须要确定出T的数据类型,才可以使用
    第一个好理解,比如上面的Myswap,如果传入的两个实参类型不一致时,则会导致推导出来的T的类型不一致,则无法使用。下面对于第二点进行代码讲解:
cpp 复制代码
#include <iostream>
using namespace std;

// 定义模板
template <class T>
void func() {
	cout << "调用函数模板" << endl;
}


void test() {
	// func(); // 错误
	func<int>(); // 正确
}

int main() {
	test();
	system("pause");
	return 0;

}
cpp 复制代码
调用函数模板
请按任意键继续. . .

实战案例

  • 利用函数模板封装一个排序的函数,可以对不同数据类型数组进行排序
  • 排序规则从大到小,排序算法为选择排序
  • 分别利用char数组和int数组进行排序
cpp 复制代码
#include <iostream>
using namespace std;

// 排序函数模板
template<class T>
void func(T arr[],int length) {
	// 选择排序
	for (int i = 0; i < length; i++) {
		int max = i; // 认为当前的是最大值
		for (int j = i+1; j < length; j++) {
			if (arr[max] < arr[j]) {
				max = j;
			}
		}

		// 调换两个值
		T temp = arr[i];
		arr[i] = arr[max];
		arr[max] = temp;
	}
}

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

// 测试整型
void test01() {
	int arr[10] = { 1,4,3,2,6,7,5,9,8,0 };
	func(arr, 10);
	printArr(arr, 10);
}
void test02() {
	char arr[] = "acbdfeg";
	int length = sizeof(arr) / sizeof(arr[0]);
	func(arr,length);
	printArr(arr, length);
}
int main() {
	test01();
	test02();
	system("pause");
	return 0;

}
cpp 复制代码
9 8 7 6 5 4 3 2 1 0
g f e d c b a
请按任意键继续. . .

2.3 普通函数和模板函数的区别

  • 普通函数调用时可以发生自动类型转化(隐式类型转化)
  • 函数模板调用时,如果利用自动类型推导,不会发生隐式类型转换
  • 如果利用显示指定类型的方式,可以发生隐式类型转化
cpp 复制代码
#include <iostream>
using namespace std;

// 定义一个函数模板
template<class T>
T func1(T a, T b) {
	cout << "调用的是函数模板!" << endl;
	return a + b;
}



// 定义一个普通函数
int func2(int a,int b) {
	cout << "调用的是普通函数!" << endl;
	return a + b;
}

// 测试案例
void test() {
	int a = 10;
	int b = 20;
	char c = 'c';
	cout << func1(a, b) << endl;
	cout << func2(a, c) << endl;
	//cout << func1(a, c) << endl;// 报错
	cout << func1<int>(a, c) << endl;// 不报错
}
int main() {
	test();
	system("pause");
	return 0;

}
cpp 复制代码
调用的是函数模板!
30
调用的是普通函数!
109
调用的是函数模板!
109
请按任意键继续. . .

2.4 普通函数和函数模板调用规则

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

下面通过具体代码进行逐个讲解

cpp 复制代码
#include <iostream>
using namespace std;
// 定义普通函数
void MyPrint(int a,int b) {
	cout << "调用的是普通函数" << endl;
}

// 定义模板函数
template<class T>
void MyPrint(T a, T b) {
	cout << "调用的是模板函数" << endl;
}
// 重载模板函数
template<typename T>
void MyPrint(T a, T b, T c)
{
	cout << "调用重载的模板函数" << endl;
}

int main() {
	// 1. 如果函数模板和普通函数都可以实现,优先调用普通函数
	int a = 10; 
	int b = 20;
	MyPrint(a, b);
	// 2.可以通过空模板参数列表来强制调用函数模板
	MyPrint<>(a, b);
	// 3.函数模板也可以发生重载
	MyPrint(a, b, 30);
	// 4.如果函数模板可以产生更好的匹配,优先调用函数模板
	char c1 = 'a';
	char c2 = 'b';
	MyPrint(c1, c2);
	system("pause");
	return 0;

}
cpp 复制代码
调用的是普通函数
调用的是模板函数
调用重载的模板函数
调用的是模板函数
请按任意键继续. . .

总结:如果提供了函数模板,最好就不要再提供对应的普通函数,否则容易出现二义性。

2.5 模板的局限性

如果T的数据类型传入的是像Person这样的自定义数据类型,也无法正常进行比较运算,C++为了解决这种问题,提供模板的重载,为这些特定类型提供具体化模板。

cpp 复制代码
#include <iostream>
using namespace std;
class Person {
public:
	Person(string name,int age) {
		m_Name = name;
		m_Age = age;
	}
	string m_Name;
	int m_Age;
};
// 普通函数模板
template<class T>
bool myCompare(T& a, T& b) {
	return a == b;
}
// 具体化 优先于常规模板
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 test() {
	Person p1("Tom", 10);
	Person p2("Tom", 10);
	cout << myCompare(p1, p2) << endl;
}
int main() {
	test();
	system("pause");
	return 0;

}
cpp 复制代码
1
请按任意键继续. . .

利用具体化的模板,可以解决自定义类型的通用化,学习模板并不是为了写模板,而是再STL中能够运用系统提供的模板。

三、类模板

类似于函数模板,类有也对应的类模板,类模板的作用是建立一个通用类,类中的成员,数据类型可以不具体制定,用一个虚拟的类型来代表,语法为

template<typename T>

  • template --- 声明创建模板
  • typename --- 表面其后面的符号是一种数据类型,可以用class代替
  • T --- 通用的数据类型,名称可以替换,通常为大写字母

3.1 基本语法

cpp 复制代码
#include <iostream>
using namespace std;


// 定义一个交换的函数模板
template <typename T>
void Myswap(T& a, T& b) {
	T temp = a;
	a = b;
	b = temp;
}

void test() {
	int a = 10;
	int b = 20;
	char c = 'c';
	char d = 'd';
	// 1. 自动推导型
	Myswap(a, b);
	cout << "a=" << a << endl;
	cout << "b=" << b << endl;
	// 2. 显示推导型
	Myswap<char>(c, d);
	cout << "c=" << c << endl;
	cout << "d=" << d << endl;

}

int main() {
	test();
	system("pause");
	return 0;

}
cpp 复制代码
姓名:孙悟空  年龄:999
姓名:猪八戒  年龄:888
请按任意键继续. . .

3.2 类模板中成员函数创建时机

类模板中成员函数和普通类中成员函数创建时机是有区别的:

  • 普通类中的成员函数一开始就可以创建
  • 类模板中的成员函数再调用时才创建
cpp 复制代码
#include <iostream>
using namespace std;
class Person1
{
public:
	void showPerson1()
	{
		cout << "Person1 show" << endl;
	}
};

class Person2
{
public:
	void showPerson2()
	{
		cout << "Person2 show" << endl;
	}
};
// 定义类模板
template<class T>
class MyClass
{
public:
	T obj;

	//类模板中的成员函数,并不是一开始就创建的,而是在模板调用时再生成

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

};
void test01()
{
	MyClass<Person1> m;

	m.fun1();

	//m.fun2();//编译会出错,说明函数调用才会去创建成员函数
}
int main() {
	test01();
	system("pause");
	return 0;

}
cpp 复制代码
Person1 show
请按任意键继续. . .

3.3 类模板对象做函数参数

类模板实例化出的对象,向函数传参的方式

一共有三种传入方式:

  1. 指定传入的形式:直接显示对象的数据类型,这是用的最多的一种方式
  2. 参数模板化:将对象中的参数变为模板进行传递
  3. 整个类模板化:将这个对象类型,模板化进行传递

下面用代码进行讲解:

cpp 复制代码
#include <iostream>
using namespace std;
// 创建一个类模板
template<class NameType, class AgeType = int> // 默认参数为int
class Person
{
public:
	Person(NameType name, AgeType age)
	{
		this->mName = name;
		this->mAge = age;
	}
	void showPerson()
	{
		cout << "name: " << this->mName << " age: " << this->mAge << endl;
	}
public:
	NameType mName;
	AgeType mAge;
};
//1、指定传入的类型,最常用的一种方式
void printPerson1(Person<string, int>& p)
{
	p.showPerson();
}
void test01()
{
	Person <string, int >p("孙悟空", 100);
	printPerson1(p);
}

//2、参数模板化
template <class T1, class T2>
void printPerson2(Person<T1, T2>& p)
{
	p.showPerson();
	cout << "T1的类型为: " << typeid(T1).name() << endl;
	cout << "T2的类型为: " << typeid(T2).name() << endl;
}
void test02()
{
	Person <string, int >p("猪八戒", 90);
	printPerson2(p);
}

//3、整个类模板化
template<class T>
void printPerson3(T& p)
{
	cout << "T的类型为: " << typeid(T).name() << endl;
	p.showPerson();

}
void test03()
{
	Person <string, int >p("唐僧", 30);
	printPerson3(p);
}

int main() {

	test01();
	test02();
	test03();

	system("pause");

	return 0;
}
int main() {

	system("pause");
	return 0;

}
cpp 复制代码
name: 孙悟空 age: 100
name: 猪八戒 age: 90
T1的类型为: class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> >
T2的类型为: int
T的类型为: class Person<class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> >,int>
name: 唐僧 age: 30
请按任意键继续. . .

3.4 类模板与继承

当类模板碰到继承时,需要注意一下几点:

  • 当子类继承的父类是一个类模板时,子类在声明的时候,要制定出父类中T的类型
  • 如果不指定,编译器无法给子类分配内存
  • 如果想灵活指定出父类中T的类型,子类也需变成类模板
cpp 复制代码
#include <iostream>
// 父类类模板
using namespace std;
template<class T>
class Base
{
	T m;
};

// 子类继承父类
// class Son :public Base {}; // 错误,必须要确定父类中的T
class Son :public Base<int> {

};
void test01()
{
	Son c;// 实例化对象
}
template<class T2>
class Son2 :public Base<T2>
{
public:
	Son2()
	{
		cout << typeid(T2).name() << endl;
	}
};

void test02()
{
	Son2<char> child1;
}
int main() {
	test01();

	test02();
	system("pause");
	return 0;

}
cpp 复制代码
char
请按任意键继续. . .

3.5 类模板成员函数类外实现

cpp 复制代码
#include <iostream>
using namespace std;

template<class NameType, class AgeType = int> // 默认参数为int
class Person
{
public:
	Person(NameType name, AgeType age);
	void showPerson();
public:
	NameType mName;
	AgeType mAge;
};
// 构造函数 类外实现
template<class NameType, class AgeType>
Person<NameType, AgeType>::Person(NameType name, AgeType age)
{
	this->mName = name;
	this->mAge = age;
}
// 成员函数类外实现
template<class NameType, class AgeType>
void Person<NameType, AgeType>::showPerson()
{
	cout << "name: " << this->mName << " age: " << this->mAge << endl;
}

int main() {
	Person<string, int> p("张三", 13);
	p.showPerson();
	system("pause");
	return 0;

}
cpp 复制代码
name: 张三 age: 13
请按任意键继续. . .

3.6 类模板分文件编写

由于类模板中成员函数创建时机是在调用阶段,导致分文件编写时连接不到,有两种解决方案:

  • 直接包含cpp源文件
  • 将声明和实现写到同一个文件中,并更改后缀名为.hpp (更常用)

Person.hpp

cpp 复制代码
#pragma once
#include <iostream>
using namespace std;

template<class NameType, class AgeType = int> // 默认参数为int
class Person
{
public:
	Person(NameType name, AgeType age);
	void showPerson();
public:
	NameType mName;
	AgeType mAge;
};
// 构造函数 类外实现
template<class NameType, class AgeType>
Person<NameType, AgeType>::Person(NameType name, AgeType age)
{
	this->mName = name;
	this->mAge = age;
}
// 成员函数类外实现
template<class NameType, class AgeType>
void Person<NameType, AgeType>::showPerson()
{
	cout << "name: " << this->mName << " age: " << this->mAge << endl;
}

类模板分文件编写.cpp

cpp 复制代码
#include <iostream>
using namespace std;

#include "Person.hpp"

int main() {
	Person<string, int> p("张三", 13);
	p.showPerson();
	system("pause");
	return 0;

}
cpp 复制代码
name: 张三 age: 13
请按任意键继续. . .

3.7 类模板和友元

cpp 复制代码
#include <iostream>
using namespace std;
template <class T1,class T2>
class Person;

// 2. 全局函数类外实现
template <class T1, class T2>
void showPerson(Person<T1, T2> p) {
	cout << "姓名:" << p.m_Name << "  年龄:" << p.m_Age << endl;
}


template <class T1, class T2>
class Person {
	// 1. 全局函数类内实现
	// 加上friend变为全局函数,如果不加的话就是成员函数
	//friend void showPerson(Person<T1,T2> p) {
	//	cout << "姓名:" << p.m_Name << "  年龄:" << p.m_Age << endl;
	//}
	// 如果全局函数是类外实现,需要让编译器提前知道这个函数存在
	friend void showPerson<>(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;
};


int main() {
	Person<string, int> p("Tom", 12);
	showPerson(p);
	system("pause");
	return 0;

}
cpp 复制代码
姓名:Tom  年龄:12
请按任意键继续. . .

实战案例

实现一个通用的数组类

  1. 可以对内置数据类型以及自定义数据类型的数据进行存储
  2. 将数组中的数据存储到堆区
  3. 构造函数中可以川入数组的容量
  4. 提供对应的拷贝构造函数以及operator=防止浅拷贝问题
  5. 提供尾插法和尾删法对数组中的数据进行增加和删除
  6. 可以通过下标的方式访问数组中的元素
  7. 可以获取数组中当前元素个数和数组的容量

main.cpp

cpp 复制代码
#include <iostream>
using namespace std;
#include "MyArray.hpp"
class Person {
public:
	Person() {
	}
	Person(string name, int age) {
		m_name = name;
		m_age = age;
	}
	string getName() {
		return m_name;
	}
	int getAge() {
		return m_age;
	}
private:
	string m_name;
	int m_age=0;
};



void showPerson(Myarray<Person> array,int num) {
	for (int i = 0; i < num; i++) {
		cout << "姓名:" << array[i].getName() << "   年龄:"<<array[i].getAge()<<endl;
	}
}
void showInt(Myarray<int> arr, int num) {
	for (int i = 0; i < num; i++) {
		cout << arr[i] << " ";
	}
	cout << endl;
}

void test() {
	Myarray<int> arr(10);
	for (int i = 0; i < 10; i++) {
		arr.insert_Back(i);
	}
	showInt(arr, 10);
	//Myarray<int> arr2(arr);
	//Myarray<int> arr3(20);
	//arr3 = arr;
	//cout <<"arr3的容量为"<< arr3.get_Mcap() << endl;
	Myarray<Person> PersonArr(2);
	Person p1("张三", 12);
	Person p2("李四", 21);
	PersonArr.insert_Back(p1);
	PersonArr.insert_Back(p2);
	showPerson(PersonArr,2);
}
int main() {
	test();
	system("pause");
	return 0;

}

MyArray.hpp

cpp 复制代码
#pragma once
#include <iostream>
using namespace std;

template <class T>
class Myarray {
public:
	// 有参构造
	Myarray(int cap) {
		cout << "调用有参构造函数" << endl;
		//cout << "hello" << endl;
		this->m_cap = cap;
		this->pArray = new T[this->m_cap]; // 在堆区开辟
		this->size = 0;

	}
	// 拷贝构造
	Myarray(Myarray& arr) {
		//cout << "调用拷贝构造函数" << endl;
		this->size = arr.size;
		this->m_cap = arr.m_cap;
		this->pArray = new T[arr.m_cap];
		// 将arr中的数据拿过来
		for (int i = 0; i < arr.size; i++) {
			this->pArray[i] = arr.pArray[i];
		}
	}
	// 析构函数
	~Myarray() {
		if (this->pArray!= NULL) {
			cout << "调用析构函数" << endl;
			this->m_cap = 0;
			this->size = 0;
			// pArray是个数组  删除时要注意格式
			delete[] pArray;
			pArray = NULL;
			//cout << "删除完成" << endl;
		}
	}
	// 重载=操作运算符
	Myarray& operator=(const Myarray& arr) {

		//cout << "调用operator=函数" << endl;
		// 先判断原来栈区是否有数据
		if (this->pArray != NULL) {
			delete[] pArray;
			pArray = NULL;
			this->m_cap = 0;
			this->size = 0;

		}
		this->m_cap = arr.m_cap;
		this->size = arr.size;
		this->pArray = new T[arr.m_cap];
		return *this;
	}
	// 重载[]操作运算符
	T& operator[](int index) {
		return pArray[index];
	}
	// 获取数组容量
	int get_Mcap() {
		return this->m_cap;
	}
	// 获取数组大小
	int get_Size() {
		return this->size;
	}
	// 获取
	// 尾插法
	void insert_Back(T value) {
		// 判断还有无空间
		if (this->size == this->m_cap) {
			cout << "达到插入上限!" << endl;
			return ;
		}
		this->pArray[this->size] = value;
		this->size++;
		//return *this;
	}
	// 尾删法
	void pop_Back() {
		// 判断是否为0
		if (this->size == 0) {
			cout << "已达删除上限!" << endl;
			return;
		}
		this->size--;
	}
	// 通过下标方式访问元素
	T& get_index(int index) {
		if (index<0 || index>this.size) {
			cout << "索引有无!" << endl;
			return;
		}
		return this->pArray[index];
	}
private:
	T* pArray; // 头指针
	int m_cap; // 容量
	int size; // 大小
};
cpp 复制代码
调用有参构造函数
0 1 2 3 4 5 6 7 8 9
调用析构函数
调用有参构造函数
姓名:张三   年龄:12
姓名:李四   年龄:21
调用析构函数
调用析构函数
调用析构函数
请按任意键继续. . .
相关推荐
好奇龙猫14 分钟前
【学习AI-相关路程-mnist手写数字分类-win-硬件:windows-自我学习AI-实验步骤-全连接神经网络(BPnetwork)-操作流程(3) 】
人工智能·算法
霁月风1 小时前
设计模式——适配器模式
c++·适配器模式
sp_fyf_20241 小时前
计算机前沿技术-人工智能算法-大语言模型-最新研究进展-2024-11-01
人工智能·深度学习·神经网络·算法·机器学习·语言模型·数据挖掘
萧鼎1 小时前
Python并发编程库:Asyncio的异步编程实战
开发语言·数据库·python·异步
学地理的小胖砸1 小时前
【一些关于Python的信息和帮助】
开发语言·python
疯一样的码农1 小时前
Python 继承、多态、封装、抽象
开发语言·python
^velpro^1 小时前
数据库连接池的创建
java·开发语言·数据库
秋の花1 小时前
【JAVA基础】Java集合基础
java·开发语言·windows
香菜大丸1 小时前
链表的归并排序
数据结构·算法·链表
jrrz08281 小时前
LeetCode 热题100(七)【链表】(1)
数据结构·c++·算法·leetcode·链表