(C++基础随笔) 05 C++模板

本文详细讲解了函数模板的基本写法、自动类型推导、显式指定类型,以及类模板的实例、继承、友元和成员函数创建时期。涵盖了数组排序、类型重载、类模板的多种传入方式和模板限制。适合理解泛型编程在C++中的实际运用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

模板

泛型编程

  • 函数模板
  • 类模板

函数模板

基本写法

这里的typename可以用class替换

template<typename T>
T func(T t1) {

}

示例

//交换两个数据
template<typename T>
void myswap(T &t1,T &t2) {
	T tmp = t1;
	t1 = t2;
	t2 = tmp;
}

void test01() {
	//int a = 10, b = 20;
	//myswap(a, b);

	double d1 = 1.0, d2 = 20.1;
	myswap(d1, d2);
	cout << "d1=  " << d1 << "  d2=  " << d2 << endl;
}
  • 函数模板会进行自动类型推导
  • 也可以显式指定类型
//在调用时显式指定
myswap<double>(d1, d2);

注意事项

  • 自动类型推导时必须推导出一致的数据类型T才能使用
	double d1 = 1.0, d2 = 20.1;
	myswap(d1, d2);

	char c = 'c';
	myswap(d1, c);//这样写错
  • 模板必须要确定出T的数据类型,才可以使用
template<class T>
void func() {
	cout << "func调用" << endl;

}
void test01() {
	func();//错误
}

数组排序案例

//交换两个数据
template<typename T>
void myswap(T& t1, T& t2) {
	T tmp = t1;
	t1 = t2;
	t2 = tmp;
}

//选择排序数组
template<typename T>
void selectSort(T arr[],int lens) {
	for (int i = 0; i < lens; i++) {
		//认定最大值的下标
		int max = i;
		for (int j = i + 1; j < lens; j++) {
			if (arr[max] < arr[j]) {
				max = j;
			}
		}
		if (max != i) {
			myswap(arr[max], arr[i]);
		}
	}
}


void test01() {
	int intArr[] = { 5,2,3,1,4 };
	char charArr[] = "baedc";

	int len = sizeof(charArr) / sizeof(char);
	selectSort(charArr, len);

	for (int i = 0; i < len; i++) {
		cout << charArr[i] << " ";
	}

	/*int len = sizeof(intArr) / sizeof(int);
	selectSort(intArr,len);

	for (int i = 0; i < len; i++) {
		cout << intArr[i] << " ";
	}*/
}

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

  • 普通函数调用时可以发生自动类型转换(隐式类型转换)
  • 函数模板在 使用自动类型推导时 不会发生隐式类型转换
  • 函数模板在 使用显式指定类型时 可以发生隐式类型转换
//普通函数的隐式类型转换
int func(int a,int b) {
	return a + b;
}

//如果是自动类型推导,不会发生隐式类型转换
template<typename T>
T func2(T a,T b) {
	return a + b;
}

void test01() {
	int a = 10, b = 20;
	char c = 'a';
	int result=func(a, c);//107
	cout << result << endl;

	//int result2 = func2(a, c);//会报错,因为没有推导出一致的数据类型
	int result2 = func2<int>(a, c);//显式指定类型时,可以发生隐式类型转换
	cout << result2 << endl;
}

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

  • 普通函数和函数模板可以重载
  • 优先调用普通函数
  • 可以通过空模板参数列表强制调用函数模板
myPrint<>(a,b);
  • 函数模板也可以重载
template<typename T>
void myPrint(T a,T b) {
	cout << "调用的模板" << endl;
}
template<typename T>
void myPrint(T a, T b,T c) {
	cout << "调用重载的模板" << endl;
}
  • 如果函数模板可以更好匹配,调用函数模板

模板的局限性

模板也不能直接赋值两个数组,或直接比较两个对象

提出了模板重载

class Person {
public:
	Person(int id, string name) {
		this->pid = id;
		this->pname = name;
	}

	int pid;
	string pname;

};

//对比两个数据是否相等
template<class T>
bool myCompare(T t1, T t2) {
	if (t1 == t2) {
		return true;
	}
	else {
		return false;
	}
}

//针对Person类型的函数模板重载,优先调用
template<> bool myCompare(Person& p1, Person& p2) {
	if (p1.pid == p2.pid && p1.pname == p2.pname) {
		return true;
	}
	else {
		return false;
	}
}
/
void test01() {
	//int a = 10, b = 10;
	//bool ret=myCompare(a, b);
	Person p1(1, "alex");
	Person p2(1, "alex");
	bool ret=myCompare(p1, p2);//直接调用不指定类型T的函数模板时会报错
	cout << ret << endl;
}

类模板

要给类模板形参传值

template<class NameType,class AgeType>
class Person {
public:
	Person(NameType name,AgeType age) {
		this->pname = name;
		this->page = age;
	}

	void showPerson() {
		cout << this->pname << "  " << this->page << endl;
	}

	NameType pname;
	AgeType page;
};

void test01() {
	//通过类模板初始化对象,<>里面是模板的参数列表,指定了NameType和AgeType
	Person<string,int> p1("alex",20);
	p1.showPerson();

}

类模板和函数模板区别

  • 类模板没有自动类型推导的使用方式
Person p1("alex",20);//就是不能写成这样
  • 类模板在模板参数列表中可以有默认参数
template<class NameType,class AgeType=int>//就是模板声明可以写成这样

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

  • 普通类的成员函数是一开始就创建
  • 类模板中的成员函数在被调用时创建(因为在被调用时才能确定数据类型)
class Person1 {
public:
	void showPerson1() {
		cout << "show person 1" << endl;
	}
};

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

template<class T>
class myClass {
public:
	T obj;
	void func1() {
		obj.showPerson1();
	}
	void func2() {
		obj.showPerson2();
	}
};

void test01() {
	myClass<Person1>m;
	m.func1();
	m.func2();//这条代码会出错,因为被调用时,才会创建Person1类的obj对象,而showPerson2不是Person1的成员函数
}

类模板对象做函数参数

三种传入方式

  • 指定传入的类型(最常用) 直接显示对象的数据类型
//指定传入的类型
void printPerson1(Person<string, int>& p) {
	p.showPerson();
}
  • 参数模板化 将对象中的参数变为模板进行传递
//参数模板化
template<class T1,class T2>
void printPerson2(Person<T1,T2> &p) {
	p.showPerson();
	//cout << "T1的类型:  " << typeid(T1).name() << endl;
}
  • 整个类模板化 将整个对象类型 模板化传递
//整个类都模板化
template<class T>
void printPerson3(T& p) {
	p.showPerson();
}
template<class T1,class T2>
class Person {
public:
	Person(T1 name,T2 age) {
		this->m_name = name;
		this->m_age = age;
	}
	void showPerson() {
		cout << this->m_name << "   " << this->m_age << endl;
	}

	T1 m_name;
	T2 m_age;

};

//指定传入的类型
void printPerson1(Person<string, int>& p) {
	p.showPerson();
}

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

//整个类都模板化
template<class T>
void printPerson3(T& p) {
	p.showPerson();
}

void test01() {
	Person<string, int>p("alex", 21);
	printPerson1(p);
}

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

void test03() {
	Person<string, int>p("Peter", 25);
	printPerson3(p);
}

类模板和继承

  1. 当没有指定父类中的T的类型时,无法确定子类应该申请多少内存
  2. 如果想灵活的指定父类中的T类型,子类也要写成类模板
template<class T>
class Base {
public:
	T t;
};

class Son :public Base<int> {
	//当没有指定父类中的T的类型时,无法确定子类应该申请多少内存
};

//如果想灵活的指定父类中的T类型,子类也要写成类模板
template<class T1>
class Son2 :public Base<T1> {

};

void test01() {
	Son s;
	Son2<int> s2;
}

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

  1. 需要类模板和函数模板相配合
  2. 需要模板参数列表
template<class T2>
void Base<T2>::printT(){}

示例

template<class T>
class Base {
public:
	Base(T t) {
		this->m_t = t;
	}
	void printT();
	T m_t;
};

template<class T2>
void Base<T2>::printT() {
	cout << this->m_t << endl;
}

void test01() {
	char a = 'a';
	Base<char> b1(a);
	b1.printT();
}

类模板分文件编写

问题

类模板中的成员函数的创建时期是在运行时,所以分文件链接阶段会出错

解决方法

  • 直接包含.cpp源文件
#include"Person.cpp"
  • 将声明和实现写道同一个文件中,约定名为.hpp
#include"Person.hpp"

Person.hpp

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

//原.h的内容
template<class T1, class T2>
class Person {
public:
	Person(T1 name, T2 age);
	void printPerson();

	T1 m_name;
	T2 m_age;
};

//原.cpp的内容
template<class T1, class T2>
Person<T1, T2>::Person(T1 name, T2 age) {
	this->m_name = name;
	this->m_age = age;
}
template<class T1, class T2>
void Person<T1, T2>::printPerson() {
	cout << this->m_name << "  " << this->m_age << endl;
}

类模板与友元

  • 类内实现:直接在类内声明友元
  • 类外实现:要提前让编译器知道全局函数的存在
#pragma once
#include <iostream>
using namespace std;


//编译器要先知道有printPerson2()这样一个全局函数存在
template<class T1,class T2>
class Person;

template<class T1, class T2>
void printPerson2(Person<T1, T2> p) {
	cout << p.m_name << "  " << p.m_age << endl;
}


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

	//全局函数的类外实现,要加上空模板参数列表!!
	friend void printPerson2<>(Person<T1, T2> p);

	T1 m_name;
	T2 m_age;
};


测试

//全局函数类内实现测试
void test01() {
	Person<string,int> p1("alex", 21);
	printPerson(p1);
}

//全局函数类外实现测试
void test02() {
	Person<string, int> p1("jelly", 25);
	printPerson2(p1);
}

一个通用的数组模板类

需求分析

在这里插入图片描述

MyArray.hpp

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

template<class T>
class MyArray {
public:
	MyArray(int cap) {
		cout << "有参构造调用" << endl;
		this->m_cap = cap;
		this->m_size = 0;
		this->pAddress = new T[this->m_cap];//按照m_cap把需要的堆区空间开辟出来
	}
	~MyArray() {
		cout << "析构调用" << endl;
		delete[] this->pAddress;//释放内存
		this->pAddress = NULL;//防止野指针
	}
	//拷贝构造
	MyArray(const MyArray& arr) {
		cout << "拷贝构造调用" << endl;
		this->m_cap = arr.m_cap;
		this->m_size = arr.m_size;

		//深拷贝
		this->pAddress = new T[arr.m_cap];

		//将arr中的数据都拷贝过来
		for (int i = 0; i < arr.m_size; i++) {
			this->pAddress[i] = arr.pAddress[i];
		}
	}

	//operator=防止浅拷贝问题
	MyArray& operator=(const MyArray& arr) {
		cout << "operator=  调用" << endl;
		//先判断原来堆区是否有数据,有要先释放
		if (this->pAddress != NULL) {
			delete[] this->pAddress;
			this->pAddress = NULL;
			this->m_cap = 0;
			this->m_size = 0;
		}

		//深拷贝
		this->m_cap = arr.m_cap;
		this->m_size = arr.m_size;
		this->pAddress = new T[arr.m_cap];
		for (int i = 0; i < arr.m_size; i++) {
			this->pAddress[i] = arr.pAddress[i];
		}

		return *this;
	}

	//尾插法
	void pushBack(const T& val) {
		//判断是不是满了
		if (this->m_size == this->m_cap) {
			return;
		}

		this->pAddress[this->m_size] = val;//在数组末尾插入数据
		this->m_size++;//更新size
	}

	//尾删法
	void popBack() {
		//判断是否为空
		if (this->m_size == 0) {
			return;
		}

		//让用户访问不到尾部元素
		this->m_size--;
	}

	//通过中括号加下标访问数组内的元素
	T& operator[] (int index) {
		return this->pAddress[index];
	}

	int getArrSize() {
		return this->m_size;
	}

	int getArrCap() {
		return this->m_cap;
	}


private:
	T* pAddress;//指针指向堆区开辟的真实数组
	int m_cap;//容量
	int m_size;//大小

};

测试

void test01() {
	//有参构造测试
	MyArray<int> arr1(5);

	arr1.pushBack(5);
	arr1.pushBack(4);
	arr1.pushBack(3);
	arr1.pushBack(2);
	arr1.pushBack(1);
	arr1.popBack();//  5 4 3 2

	int lens = sizeof(arr1) / sizeof(int);
	for (int i = 0; i < lens; i++) {
		cout << arr1[i] << endl;
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值