C++模板

本文详细介绍了C++中的模板概念,包括函数模板和类模板的语法、作用及使用方式。讨论了自动类型推导与显示指定类型的差异,以及在函数模板调用规则中的优先级。同时,阐述了类模板的特性,如无自动类型推导和默认参数,并探讨了类模板成员函数的创建时机、对象作为函数参数的传递方式以及与继承的关系。最后,通过实现一个通用数组类的案例,展示了类模板的实际应用。

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

模板

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

函数模板语法: 


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

利用函数模板 有两种交换方式

1.自动类型推导          2.显示指定类型

 函数模板注意事项

 注意事项:
自动类型推导,必须推导出一致的数据类型T,才可以使用
模板函数必须要确定T的数据类型才可以使用

函数模板案例-数组排序

函数模板案例:利用函数模板封装一个排序的函数,可以对不同数据类型数组进行排序
排序规则从小到大,排序算法为选择排序
分别利用char数组和int数组进行测试

普通函数与函数模板区别

普通函数调用时可以发生自动类型转换(隐式类型转换)
函数模板调用时,如果利用自动类型推导,不会发生隐式类型转换
如果利用显示指定类型的方式,可以发生隐式类型转换

 总结:建议使用显示指定类型的方式,使用函数模板,因为可以自己确定通用类型T

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

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

 总结:既然提供了函数模板,最好不要提供普通函数,否则容易出现二义性。

模板局限性

模板并不是万能的,有些特定数据类型,需要具体化方式做特殊实现

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

类模板语法

类模板作用:建立一个通用类,类中的成员 数据类型可以不具体制定,用一个虚拟的类型来代表。
template <class T>


总结:类模板和函数模板语法相似,在声明模板template后面加类,此类为类模板。

 类模板与函数模板区别

类模板与函数模板区别主要有两点:
1.类模板没有自动类型推导的使用方式
2.类模板在模板参数列表中可以有默认参数

 

总结:类模板使用只能用显示指定类型方式 类模板中的模板参数列表可以有默认参数

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


类模板中成员函数和普通类中成员函数创建时机是有区别的:
普通类中的成员函数一开始就可以创建
类模板中的成员函数在调用时才创建

总结:类模板中的成员函数并不是一开始就创建,在调用时才会去创建

类模板对象做函数参数


类模板实例化出的对象,向函数传参的方式
一共有三种传入方式:
1.指定传入的类型--直接显示对象的数据类型
2.参数模板化--将对象中的参数变为模板进行传递
3.整个类模板化--将这个对象类型 模板化进行传递

总结:通过类模板创建的对象,可以有三种方式向函数进行传参,使用比较广泛是第一种:指定传入类型。

类模板与继承

当类模板碰到继承时,需要注意以下几点:
 当子类继承的父类是一个类模板时,子类在声明的时候,要指定出父类中T的类型。
如果不指定,编译器无法分配内存给子类
 如果想灵活指定出父类中T的类型,子类也需要变为类模板

总结:如果父类是类模板,子类需要指定出父类中T的数据类型 

类模板成员函数类外实现

学习目标:能够掌握类模板中的成员函数类外实现

类模板分文件编写 

学习目标:掌握类模板成员函数分文件编写产生的问题以及解决方式
问题:类模板中成员函数创建时机是在调用阶段,导致分文件编写时链接不到
解决:
解决方式1:直接包含.cpp源文件
解决方式2:将声明和实现写到同一个文件中,并更改辍名为.hpp,hpp是约定的名称,并不是强制。

对上个案例进行编写

解决方式1代码:

 

 

解决方式2代码:

 

 

 总结:主流方法是第二种,将.h和.cpp中的内容写到一起,将后缀名改为.hpp文件。

类模板与友元

学习目标:掌握类模板配合友元函数的类内和类外实现
全局函数类内实现-直接在类内声明友元即可
全局函数类外实现-需要提前让编译器知道全局函数的存在

总结:建议全局函数做类内实现,用法简单,而且编译器可以直接识别。 

类模板案例:实现一个通用的数组类

案例描述:实现一个通用的数组类,要求如下:
可以对内置数据类型以及自定义数据类型的数据进行存储
将数组中的数据存储到堆区
构造函数中可以传入数组的容量
提供对应的拷贝构造函数以及operator=防止浅拷贝问题
提供尾插法和尾删除法对数组中的数据进行增加和删除
 可以通过下标的方式访问数组中的元素
可以获取数组中当前元素个数和数组的容量

类模板案例分析:

 

MyArray.hpp

#pragma once
#include <iostream>
using namespace std;
template <class T>
class MyArray
{
public:
	//有参构造函数
	MyArray(int Capacity) {
		//cout<<"有参构造函数被创建!" << endl;
		m_Capacity = Capacity;
		m_size = 0;
		pAddress = new T[this->m_Capacity];
	}
	//拷贝构造
	MyArray(const MyArray &arr) {//const只拷贝,不修改
		//cout<<"拷贝构造函数被创建" << endl;
		this->m_size = arr.m_size;
		this->m_Capacity = arr.m_Capacity;
		this->pAddress=new T[arr.m_Capacity];
		//将arr中的数据都拷贝过来
		for (int i = 0;i<this->m_size;i++) {
			this->pAddress[i] = arr.pAddress[i];
		}
	}
	//operator= 防止浅拷贝问题 a=b=c
	MyArray& operator=(const MyArray& arr) {
		//cout << "operator=重载函数被创建" << endl;
		if (this->pAddress != NULL) {
			delete[]this->pAddress;
			this->pAddress = NULL;
			this->m_size = 0;
			this->m_Capacity = 0;
		}
		//深拷贝操作
		this->m_size = arr.m_size;
		this->m_Capacity = arr.m_Capacity;
		this->pAddress = new T[arr.m_Capacity];
		for (int i = 0; i < this->m_size; i++) {
			this->pAddress[i] = arr.pAddress[i];
		}
		return *this;
	}
	//析构函数
	~MyArray() {
		//cout << "析构函数被创建!" << endl;
		if (pAddress != NULL) {
			delete[]this->pAddress;
			this->m_Capacity = NULL;
		}
	}
	//尾插法:
		void push_back(const T &val) {
			if (this->m_size==this->m_Capacity) {//判断容量是否等于大小
				return;
			}
			this->pAddress[this->m_size] = val;//在数组尾部插入数据
			this->m_size++;//更新数组大小
		}
	//尾删法:
		void pop_dele() {
			//让用户访问不到最后一个元素,即为尾删,逻辑删除。
			if (this->m_size == 0) {
				return;
			}
			this->m_size--;
		}
	//返回下标中的元素
		T& operator[](int index) {
			return this->pAddress[index];
		}
	//返回数组大小
		int getSize() {
			return this->m_size;
		}
	//返回数组容量
		int getCapacity() {
			return this->m_Capacity;
		}

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

};

 类模板案例.cpp

#include <iostream>
using namespace std;
#include "MyArray.hpp"
/*案例描述:实现一个通用的数组类,要求如下:
* 可以对内置数据类型以及自定义数据类型的数据进行存储
* 将数组中的数据存储到堆区
* 构造函数中可以传入数组的容量
* 提供对应的拷贝构造函数以及operator=防止浅拷贝问题
* 提供尾插法和尾删除法对数组中的数据进行增加和删除
* 可以通过下标的方式访问数组中的元素
* 可以获取数组中当前元素个数和数组的容量
*/

void printarr1(MyArray<int> &arr) {
	for (int i = 0;i<arr.getSize();i++) {
		cout<<arr[i]<<" ";
	}
	cout<<endl;
}
//内置数据类型定义
void test01() {
	MyArray<int> arr1(5);
	//利用尾插法进行数据定义:
	for (int i = 0;i<5;i++) {
		arr1.push_back(i);
	}
	cout<<"arr1数组中的数据元素有:" << endl;
	printarr1(arr1);
	cout << "arr1数组中的容量为:" <<arr1.getCapacity() << endl;
	cout << "arr1数组中的大小为:" << arr1.getSize() << endl;
	MyArray<int> arr2(arr1);
	cout << "arr2数组中的数据元素有:" << endl;
	printarr1(arr2);
	cout << "对arr2数组中的数据元素进行尾删" << endl;
	arr2.pop_dele();
	cout << "arr2数组中的数据元素有:" << endl;
	printarr1(arr2);
	cout << "arr2数组中的容量:" << arr2.getCapacity()<<endl;
	cout << "arr2数组中的大小:" << arr2.getSize()<<endl;
	/*MyArray<int> arr2(arr1);
	MyArray<int> arr3(100);
	arr3 = arr1;*/
}
//利用自定义数据类型
class person {
public:
	person() {};
	person(string name,int age) {
		m_name = name;
		m_age = age;
	}
	string m_name;
	int m_age;
};
void printarr2(MyArray<person>& arr) {
	for (int i = 0; i < arr.getSize(); i++) {
		cout << "name:" << arr[i].m_name << " age: " << arr[i].m_age << endl;
	}
	cout << endl;
}
void test02() {
	MyArray<person> arr(10);
	person p1("兰陵王", 18);
	person p2("孙悟空", 19);
	person p3("司马懿", 20);
	person p4("王昭君", 21);
	person p5("鲁班", 16);
	cout<<"王者五个英雄信息:" << endl;
	arr.push_back(p5);
	arr.push_back(p4);
	arr.push_back(p3);
	arr.push_back(p2);
	arr.push_back(p1);
	printarr2(arr);
	cout<<"数组容量为:"<<arr.getCapacity() << endl;
	cout << "数组长度为:" << arr.getSize() << endl;
	cout<<"进行尾删后为:" << endl;
	arr.pop_dele();
	printarr2(arr);
	cout << "数组容量为:" << arr.getCapacity() << endl;
	cout << "数组长度为:" << arr.getSize() << endl;
	

}

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

到这,类模板完结

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值