zl程序教程

您现在的位置是:首页 >  后端

当前栏目

黑马C++笔记——模板(CPP)

C++模板笔记 黑马 CPP
2023-09-14 09:14:58 时间

模板的概念

模板就是建立通用的模具,这样可以极大地提高复用性

特点:

  • 模板不可以直接使用,他只是一个架子
  • 模板的通用并不是万能的,他依然有局限性

1.函数模板

  • C++的另一种编程思想被称为泛型编程,其主要利用的技术就是模板
  • C++提供两种模板机制 函数模板类模板

1.函数模板的语法

作用:可以建立一个通用的函数,其函数返回值类型 和 形参类型可以不具体指定,用一个虚拟的类型 T 来代表

template<typename T>
函数声明或者定义

template<class T>
函数声明或者定义

//上面两者是一样的

举例:实现一个交换两个元素的函数模板

#include<iostream>
using namespace std;

template<typename T>
void mySwap(T& a, T& b) {
	T temp = a;
	a = b;
	b = temp;
}

int main() {
	int a = 20;
	int b = 10;
	//自动类型推导
	mySwap(a, b);

	cout << "a = " << a << endl; //a = 10
	cout << "b = " << b << endl; //b = 20

	double c = 1.1;
	double d = 2.2;
	//显式指定类型
	mySwap<double>(c, d);

	cout << "c = " << c << endl; // c = 2.2
	cout << "d = " << d << endl; // d = 1.1
	system("pause");
	return 0;
}

2.函数模板注意事项

一般使用函数模板有两种方式:

  • 自动类型推导
  • 显式指定类型
#include<iostream>
using namespace std;

template<typename T>
void myswap(T& a, T& b) {
	T temp = a;
	a = b;
	b = temp;
}

template<typename T>
void fun() {
	cout << "感觉不如原神...画质" << endl;
}



int main() {
	int a = 10;
	int b = 20;
	char c = 'c';
	//myswap(a, b);  成功执行


	//myswap(a, c); 报错。自动类型推导 这里要求两个参数的类型必须是一致的

	//fun(); 报错。这里只是普通函数调用

	fun<int>(); //成功执行。这里是显式指定类型

	cout << "a = " << a << endl;
	cout << "b = " << b << endl;


	system("pause");
	return 0;
}

案例:用模板实现对不同内置类型数组的排序(以下代码只测试了char数组 和 int数组)

#include<iostream>
using namespace std;

template<typename T>
void myswap(T& a, T& b) {
	T temp = a;
	a = b;
	b = temp;
}

template<typename T>
void mysort(T arr[], int len) {
	for (int i = 0; i < len; i++) {
		int pos = i;
		for (int j = i; j < len; j++) {
			if (arr[j] < arr[pos]) pos = j;
		}
		if (i != pos) myswap(arr[i], arr[pos]);
	}
}

template<typename T>
void printArray(T arr[], int len) {
	for (int i = 0; i < len; i++) {
		cout << arr[i] << " ";
	}
	cout << endl;
}

void test01() {
	char arr[] = "fghbnabcdffaa";
	int len = sizeof(arr) / sizeof(char);
	mysort(arr, len);
	printArray(arr, len); // a a a b b c d f f f g h n
}

void test02() {
	int arr[] = { 2,12,41,3,6,7,90,11,1,2,3,4,5 };
	int len = sizeof(arr) / sizeof(int);
	mysort(arr, len);
	printArray(arr, len); // 1 2 2 3 3 4 5 6 7 11 12 41 90
}

int main() {
	test01();
	//test02();
	system("pause");
	return 0;
}

3.普通函数与函数模板的区别

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

template<typename T>
T myadd(T a, T b) {
	return a + b;
}
int add(int a, int b) {
	return a + b;
}

int main() {
	int a = 10;
	int b = 20;
	char c = 'c';

	//1.普通函数调用时是可以发生自动类型转换的
	cout << add(a, b) << endl; //30
	cout << add(a, c) << endl; //答案是 c 的ascii码值 + a = 109

	//2.模板函数调用时,如果利用自动类型推导,是不会发生隐式类型转换的
	cout << myadd(a, b) << endl; //30
	//cout << myadd(a, c) << endl;   报错

	//3.如果使用显式指定类型的方式,可以发生隐式类型转换
	cout << myadd<int>(a,c) << endl; //答案是 c 的ascii码值 + a = 109
	system("pause");
	return 0;
}

4.普通函数与函数模板的调用规则

  • 如果函数模板 和 普通函数都能匹配,优先调用普通函数
  • 可以通过添加空模板参数列表强制使用函数模板
  • 函数模板也可以发生重载
  • 如果函数模板可以更好的匹配,优先使用函数模板
#include<iostream>
using namespace std;

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

template<typename T>
void myadd(T a, T b) {
	cout << "函数模板调用" << endl;
}

template<typename T>
void myadd(T a, T b,T c) {
	cout << "重载函数模板调用" << endl;
}

int main() {
	//1.如果普通函数和函数模板都能实现,优先使用普通函数
	int a = 10;
	int b = 20;
	myadd(a, b);//普通函数调用

	//2.可以通过空模板参数列表来强制调用函数模板
	myadd<>(a, b);//函数模板调用

	//3.函数模板也可以发生重载
	myadd(a, b, 100);//函数模板重载

	//4.如果函数模板可以更好地匹配,优先调用函数模板
	char c = 'c';
	char d = 'd';
	myadd(c, d);//函数模板调用。因为调用普通函数会发生自动类型转换(char -> int),所以编译器选择了函数模板

	system("pause");
	return 0;
}

5.函数模板的局限性

  • 针对复杂的自定义类型,函数模板就很难处理

解决方案:

可以利用具体化的模板,来适用自定义类型(不推荐)

#include<iostream>
#include<string>
using namespace std;

class Person {
public:
	int age;
	string name;
	Person(int age, string name) {
		this->age = age;
		this->name = name;
	}
};
//1
template<typename T>
bool mycmp(T& a, T& b) {
	if (a == b) return true;
	else return false;
}

//2
//利用具体化的模板解决自定义类型的比较
template<>bool mycmp(Person& p1, Person& p2) {
	if (p1.age == p2.age && p1.name == p2.name) return true;
	else return false;
}

int main() {
	Person p1(20, "wurusai");
	Person p2(20, "wurusai");
    //在调用mycmp(p1, p2)时,不会调用1,只会调用2这个函数模板
	if (mycmp(p1, p2)) cout << "p1 == p2" << endl; //输出 p1 == p2
	else cout << "p1 != p2" << endl;
	system("pause");
	return 0;
}

2.类模板

  • 建立一个通用的类,类中成员的数据类型可以不具体指定,先用一个虚拟的类型 T 指定。

示例:

#include<iostream>
using namespace std;

template<typename NameType,typename AgeType>
class Person {
public:
	NameType name;
	AgeType age;

	Person(NameType name, AgeType age) {
		this->name = name;
		this->age = age;
	}

	void show() {
		cout << "name : " << this->name << " age : " << this->age << endl;
	}
};

void test() {
	Person<string, int> p1("wurusai", 20);
	p1.show();
}

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

1.类模板与函数模板的区别

  • 类模板没有自动类型推导
  • 类模板在参数列表中可以有默认参数
#include<iostream>
using namespace std;

template<typename NameType,typename AgeType>
class Person {
public:
	NameType name;
	AgeType age;

	Person(NameType name, AgeType age) {
		this->name = name;
		this->age = age;
	}

	void show() {
		cout << "name : " << this->name << " age : " << this->age << endl;
	}
};

void test01() {
	//1.类模板没有自动类型推导
	Person p1("wurusai", 20);//报错
}

int main() {
	test01();
	system("pause");
	return 0;
}
#include<iostream>
using namespace std;

template<typename NameType = string,typename AgeType = int>
class Person {
public:
	NameType name;
	AgeType age;

	Person(NameType name, AgeType age) {
		this->name = name;
		this->age = age;
	}

	void show() {
		cout << "name : " << this->name << " age : " << this->age << endl;
	}
};

void test01() {
	//1.类模板没有自动类型推导
	//Person p1("wurusai", 20);//错误

	//2.类模板在参数列表中可以有默认参数
	Person<>p2("wurusai", 20);//运行成功
	p2.show();
}

int main() {
	test01();
	system("pause");
	return 0;
}

2.类模板中成员函数的创建时机

  • 普通类的成员函数在一开始的时候就被创建
  • 类模板中的成员函数在调用时才被创建(类似懒加载)
#include<iostream>
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 Person {
public:
	T obj;
	//obj 要么是Person1 要么是Persona2
	//不可能 既能调用showPerson1 又能调用showPerson2
	//但是编译没有报错,说明这两个成员函数都还没有被创建
	void fun1() {
		obj.showPerson1();
	}

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

void test() {
	//1.类模板中的成员函数在调用的时候才会创建(类似于懒加载)
	Person<Person1> p;
}


int main() {
	test();
	system("pause");
	return 0;
}
#include<iostream>
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 Person {
public:
	T obj;

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

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

void test() {
	//1.类模板中的成员函数在调用的时候才会创建(类似于懒加载)
	Person<Person1> p;

	p.fun1();
	//p.fun2(); 报错


	Person<Person2> q;
	//q.fun1(); 报错
	q.fun2();
}


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


3.类模板对象作为函数参数

  • 传入指定的类型(直接显示对象的数据类型)推荐使用
  • 参数模板化(将对象中参数变为模板进行传递)
  • 将整个类模板化(将这个对象类型模板化进行传递)
#include<iostream>
using namespace std;

template<typename T1,typename T2>
class Person {
public:
	T1 name;
	T2 age;
	Person(T1 name, T2 age) {
		this->name = name;
		this->age = age;
	}

	void showPerson() {
		cout << "name : " << this->name << " age : " << this->age << endl;
	}
};

//1.直接指定类对象的数据类型 这一种方式最常用
void fun1(Person<string, int> &p) {
	p.showPerson();
}

void test01() {
	Person<string, int> p("wurusai", 20);
	fun1(p);
}

//2.将参数模板化
template<typename T1,typename T2>
void fun2(Person<T1,T2>& p) {
	p.showPerson();
	cout << "T1 的类型为 : " << typeid(T1).name() << endl;
	cout << "T2 的类型为 : " << typeid(T2).name() << endl;
}

void test02() {
	Person<string, int> p("wurusai", 20);
	fun2(p);
}

//3.直接将整个类模板化
template<typename T>
void fun3(T &p) {
	p.showPerson();
	cout << "T 的类型为 : " << typeid(T).name() << endl;
}

void test03() {
	Person<string, int> p("wurusai", 20);
	fun3(p);
}

int main() {
	//test01();
	//test02();
	test03();
	system("pause");
	return 0;
}

4.类模板与继承

  • 当子类继承的父类是一个类模板时,子类在声明的时候,需要指定出父类T的类型,否则编译器无法给子类分配内存,报错
  • 如果想灵活指定出父类中的 T 类型,子类也需要变为类模板
#include<iostream>
using namespace std;

template<typename T>
class Base {
public:
	T m;
};

//class Son1 : public Base { 父类是类模板时,子类必须指定 T 的类型,否则编译器无法给子类分配内存,报错
class Son1 : public Base<int>{

};

//2.如果想灵活的指定父类中 T 的类型,子类也需要变为类模板
template<typename T1,typename T2>
class Son2 : public Base<T2> {
public:
	T1 b;
	Son2() {
		cout << "T1 的类型为 : " << typeid(T1).name() << endl;
		cout << "T2 的类型为 : " << typeid(T2).name() << endl;
	}
};

void test() {
	Son2<string, int> s2;
}

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

5.类模板成员函数的类外实现

#include<iostream>
using namespace std;

template<typename T1,typename T2>
class  Person {
public:
	T1 name;
	T2 age;
	Person(T1 name, T2 age);
	void showPerson();
};

//类模板成员函数的类外实现
template<typename T1,typename T2>
Person<T1, T2>::Person(T1 name, T2 age) {
	this->name = name;
	this->age = age;
}

template<typename T1, typename T2>
void Person<T1, T2>::showPerson() {
	cout << "name : " << this->name << " age: " << this->age << endl;
}

void test() {
	Person<string, int> p("wurusai", 20);
	p.showPerson();
}

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

6.类模板分文件编写

问题:

类模板中成员函数被创建时是在调用阶段,这可能会导致分文件编写后链接不到

解决方案:

  • 直接include .cpp源文件 不推荐
  • 将模板的声明和实现写到同一个文件中,文件的后缀名为.hpp。(hpp是约定的规范命名推荐使用

person.h

#pragma once
#include<iostream>
using namespace std;

template<typename T1, typename T2>
class Person {
public:
	T1 name;
	T2 age;
	Person(T1 name, T2 age);
	void showPerson();
};

person.cpp

#include"person.h"
using namespace std;

template<typename T1,typename T2>
Person<T1, T2>::Person(T1 name, T2 age) {
	this->name = name;
	this->age = age;
}

template<typename T1, typename T2>
void Person<T1, T2>::showPerson() {
	cout << "name : " << this->name << " age: " << this->age << endl;
}

主函数

#include"person.h"
using namespace std;

void test() {
    //运行会报错
	//类模板中成员函数是在调用时才会创建。分文件编写会导致链接时出问题

	Person<string, int> p("wurusai", 20);
	p.showPerson();
}

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

主函数1

#include"person.cpp"
using namespace std;

void test() {
	//引入.cpp源文件 运行成功

	
	Person<string, int> p("wurusai", 20);
	p.showPerson();
}

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

person.hpp

#pragma once
#include<iostream>
using namespace std;

template<typename T1,typename T2>
class Person {
public:
	T1 name;
	T2 age;
	Person(T1 name, T2 age);
	void showPerson();
};

template<typename T1, typename T2>
Person<T1, T2>::Person(T1 name, T2 age) {
	this->name = name;
	this->age = age;
}

template<typename T1, typename T2>
void Person<T1, T2>::showPerson() {
	cout << "name : " << this->name << " age: " << this->age << endl;
}

主函数3(最终版)

#include"person.hpp"
using namespace std;

void test() {
	//类模板中成员函数是在调用时才会创建。分文件编写会导致链接时出问题

	/*
	* 解决方案:
	* 1.直接包含 .cpp文件(不推荐)
	* 2.将模板声明 和 实现都写到头文件中,并更改为.hpp文件(这是约定俗成的规范)
	*/
	Person<string, int> p("wurusai", 20);
	p.showPerson();
}

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

7.类模板与友元

  • 全局函数类内实现——直接在类内部声明实现友元即可
  • 全局函数类外实现——需要提前让编译器知道全局函数的存在(比较麻烦)
#include<iostream>
using namespace std;

//提前声明
template<typename T1,typename T2>
class Person;

template<typename T1, typename T2>
void showPerson2(Person<T1, T2> &p) {
	cout << "全局友元函数(类外)实现—— name : " << p.name << " age: " << p.age << endl;
}


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


	//全局友元函数 类外实现
	friend void showPerson2<>(Person<T1, T2> &p);


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

private:
	T1 name;
	T2 age;
};


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

//类外实现
void test2() {
	Person<string, int> p("tom", 20);
	showPerson2(p);
}

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

8.案例实现一个通用的数组类

功能:

在这里插入图片描述

myArray.hpp

#pragma once
#include<iostream>
using namespace std;

template<typename T>
class MyArray {
public:
	MyArray(int capacity) {
		cout << "MyArray 构造函数调用。。。" << endl;
		this->m_capacity = capacity;
		this->m_size = 0;
		this->pAddress = new T[capacity];
	}

	MyArray(const MyArray& arr) {
		cout << "MyArray 拷贝构造函数调用。。。" << endl;
		this->m_capacity = arr.m_capacity;
		this->m_size = arr.m_size;
		this->pAddress = new T[this->m_capacity];
		for (int i = 0; i < this->m_size; i++) {
			this->pAddress[i] = arr.pAddress[i];
		}
	}

	//尾插法
	void push_back(const T& value) {
		if (this->m_capacity == this->m_size) {
			cout << "数组容量已经满了" << endl;
			return;
		}
		this->pAddress[this->m_size] = value;
		this->m_size++;
	}

	//尾删法
	void pop_back() {
		if (this->m_size == 0) {
			cout << "数组已经为空" << endl;
			return;
		}
		this->m_size--;
	}

	//返回元素个数
	int getSize() {
		return this->m_size;
	}

	//返回容量
	int getCapacity() {
		return this->m_capacity;
	}


	//利用下标返回元素
	T& operator[] (int index) {
		return this->pAddress[index];
	}

	MyArray& operator= (const MyArray& arr) {
		cout << "MyArray operator = 函数调用。。。" << endl;
		if (this->pAddress != nullptr) {
			delete[] this->pAddress;
			this->pAddress = nullptr;
			this->m_capacity = 0;
			this->m_size = 0;
		}

		this->m_capacity = arr.m_capacity;
		this->m_size = arr.m_size;
		this->pAddress = new T[this->m_capacity];
		for (int i = 0; i < this->m_size; i++) {
			this->pAddress[i] = arr.pAddress[i];
		}
		return *this;
	}

	~MyArray() {
		if (this->pAddress != nullptr) {
			cout << "MyArray 析构函数调用。。。" << endl;
			delete[] this->pAddress;
			this->pAddress = nullptr;
		}
	}
private:

	//指向数组
	T* pAddress;
	//容量
	int m_capacity;
	//元素个数
	int m_size;
};

主函数

#include<iostream>
#include"myArray.hpp"
using namespace std;

class Person {
public:
	string name;
	int age;
	Person() {

	}
	Person(string name, int age) {
		this->name = name;
		this->age = age;
	}
};

void printArray(MyArray<int> &a) {
	for (int i = 0; i < a.getSize(); i++) {
		cout << a[i] << " ";
	}
	cout << endl;
}

//测试内置数据类型
void test1() {
	MyArray<int> a(5);

	/*MyArray<int> b(a);
	MyArray<int> c(20);*/

	//尾插法
	for (int i = 0; i < 5; i++) {
		a.push_back(i + 1);
	}

	cout << "a 的容量为 :" << a.getCapacity() << endl;
	cout << "a 的元素个数为 :" << a.getSize() << endl;
	printArray(a);

	//尾删法
	a.pop_back();
	a.pop_back();

	cout << "删除之后的情况为:" << endl;

	cout << "a 的容量为 :" << a.getCapacity() << endl;
	cout << "a 的元素个数为 :" << a.getSize() << endl;
	printArray(a);
}


void printPersonArray(MyArray<Person>& p) {
	for (int i = 0; i < p.getSize(); i++) {
		cout << "name : " << p[i].name << "\t age : " << p[i].age << endl;
	}
}

//测试自定义数据类型
void test2() {
	MyArray<Person> p(10);

	/*MyArray<int> b(a);
	MyArray<int> c(20);*/

	//尾插法
	Person p1("wurusai", 20);
	Person p2("zed", 40);
	Person p3("yassuo", 40);
	Person p4("fizz", 20);
	Person p5("jax", 50);
	Person p6("leesin", 30);

	p.push_back(p1);
	p.push_back(p2);
	p.push_back(p3);
	p.push_back(p4);
	p.push_back(p5);
	p.push_back(p6);

	cout << "a 的容量为 :" << p.getCapacity() << endl;
	cout << "a 的元素个数为 :" << p.getSize() << endl;
	printPersonArray(p);

	//尾删法
	p.pop_back();
	p.pop_back();

	cout << "删除之后的情况为:" << endl;

	cout << "a 的容量为 :" << p.getCapacity() << endl;
	cout << "a 的元素个数为 :" << p.getSize() << endl;
	printPersonArray(p);
}


int main() {
	//test1();
	test2();
	system("pause");
	return 0;
}