C++基础--第四部分:C++的模板

C++的模板

函数模板

// 定义一个函数模板
template<typename T>  // 类型参数列表
bool compare(T a, T b) // compare是一个函数模板,不是函数
{
	return a>b;   
}
int main()
{
	// compare<int> 才是一个函数
	// 函数调用点
	//compare<int>(10, 20);
	//compare<double>(10.5, 20.5);
	
	//compare(20, 30); // 模板实参推演,若符合参数类型的函数代码已经产生,就不用再产生一份相同的代码了
	
	// 模板实参推演
	// 对于某些类型来说,依赖编译器默认实例化的模板代码,代码处理逻辑是错误的
	compare("aaa", "bbb"); // 调用非模板函数,节省编译器处理的时间
	compare<const char*>("aaa", "bbb");
	return 0;
}
  • 函数名+类型参数,例如compare才构成一个函数名,在mian函数的函数调用点处,编译器会根据用户指定的参数的类型,从原模版中实例化一份函数代码,实例化后的代码称为模板函数

  • 函数模板是不进行编译的,但是实例化出的模板函数是要进行编译的

  • 模板的实参推演:根据传入的实参,推导出生产的模板函数的参数是什么类型

// 这里没有显式指定参数类型,编译器根据传入的实参类型,推演出T的类型,并进行模板的实例化
compare(20, 30);

当然,如果此前模板已经实例化出合适的模板函数compare,那么就不会再进行实例化

  • 模板的特例化—>不是编译器提供的模板函数,是开发者手动提供的

对于某些类型来说,依赖编译器默认实例化的模板代码,代码处理逻辑是错误的

template<>
bool compare<const char*>(const char* a, const char* b)
{
	cout << "compare<const char*>" << endl;
    // return a>b; 编译器默认实例化的模板代码的处理逻辑
	return strcmp(a, b) > 0;
}

int main()
{
    compare<const char*>("aaa", "bbb");
}

根据上面的代码,如果参数类型T是const char*类型,那么依赖编译器默认实例化的模板代码,将会执行的是两个字符串地址的大小比较,而这种比较对于字符串类型来说显然是错误的!!这里,我们就需要手动添加一份模板的实例化来进行相应的处理, 注意依然要写template,虽然是手动添加,但它依然是模板的实例化!!

  • 非模板函数—普通函数
// 1. 函数模板
template<typename T> // 定义一个模板参数列表, T是类型参数,用来接收类型
bool compare(T a, T b) // compare是一个函数模板
{//函数模板{}中的类型不进行编译,根本不知道实际的类型
	cout << "template compare" << endl;
	return a > b;
}

// 2. 模板的特例化
// 针对compare函数模板,提供const char* 类型的特例化版本
template<>
bool compare<const char*>(const char* a, const char* b)
{
	cout << "compare<const char*>" << endl;
	return strcmp(a, b) > 0;
}

// 3. 非模板函数---普通函数
bool compare(const char* a, const char* b)
{
	cout << "normal compare" << endl;
	return strcmp(a, b) > 0;
}

谈到模板函数以及非模板函数的重载关系时,严格来说,两者并不符合重载的条件,因为对于模板函数来说模板名+指定的类型参数才是函数名,例如compare才是函数名,而对于非模板函数来说,compare就是函数名,而函数重载指的是函数名相同,但是参数列表不同的

int main()
{
    compare("aaa", "bbb");// 不会进行实参推演
    compare<const char*>("aaa", "bbb");
    return 0;
}

此外,如果定义了非模板函数,当我们调用compare(“aaa”, “bbb”)函数时,编译器会优先找非模板函数,这样也就不用进行实参推演了,如果没有定义相应的非模板函数,则编译器会进行实参推演,然后看是否用户手动提供了该类型的模板特例化,如果还没有就要根据参数类型实例化模板函数。

  • 模板代码调用之前,一定要看到模板定义的地方,这样的话,模板才能进行正常实例化,产生能够被编译器编译的代码,所以,模板代码一般都是放在头文件当中的,然后在源文件当中直接进行#include导入头文件

main.cpp

// 模板的声明 
template<typename T>  
bool compare(T a, T b);  // compare<const char*> *UND*,  compare<int> *UND*, compare<double> *UND* 

bool compare(const char* a, const char* b);  // compare *UND*

int main()
{ 
	compare("aaa", "bbb"); // 调用非模板函数,节省编译器处理的时间
	compare<const char*>("aaa", "bbb");
	return 0;
}

test.cpp

template<typename T> // 定义一个模板参数列表, T是类型参数,用来接收类型
bool compare(T a, T b) // compare是一个函数模板
{//函数模板{}中的类型不进行编译,根本不知道实际的类型
	cout << "template compare" << endl;
	return a > b;
}

// 针对compare函数模板,提供const char* 类型的特例化版本
template<>
bool compare<const char*>(const char* a, const char* b)
{
	cout << "compare<const char*>" << endl;
	return strcmp(a, b) > 0;
}

// 非模板函数---普通函数
bool compare(const char* a, const char* b)
{
	cout << "normal compare" << endl;
	return strcmp(a, b) > 0;
}

根据上面的代码,将模板的定义放在test.cpp文件中,然后在main.cpp中进行模板的声明, 还有非模板函数的声明。

在编译的时候,非模板函数会生成对应的UND符号,在链接过程中进行定位,而在函数调用处compare<const char*>(“aaa”, “bbb”);,根据实参类型,编译器也会产生一份模板函数的声明,在编译时生成UND符号,在链接过程中由于模板的特例化是我们手动提供的,所以能够在test.cpp找到对应的定义,但是其他情况下在test.cpp中的模板并不会进行模板的实例化,所以如果我们在main函数中调用compare(10,20);就会在链接阶段出错了。

  • 模板的非类型参数都是常量,只能使用而不能修改(必须是整数/地址/引用类型)
template<typename T, int SIZE>
void sort(T* arr) 
{
	for (int i=0;i<SIZE-1;i++) 
	{
		for (int j=0;j<SIZE-1-i;j++) 
		{
			if (arr[j] > arr[j + 1]) 
			{
				int tmp = arr[j];
				arr[j] = arr[j + 1];
				arr[j + 1] = tmp;
			}
		}
	}
}

类模板

以下是我编写的一个顺序栈类模板

template<typename T=int> // 默认类型参数
class SeqStack  // SeqStack是模板名  模板名+类型参数列表才是类名
{
public:
	// 除了构造函数和析构函数可以不用写类型参数列表<T>,其他都要写上
	SeqStack<T>(int size=10) // C++开源代码中常用的风格
		:_pStack(new T[size])
		,_top(0)
		,_size(size)
	{}
	~SeqStack<T>() 
	{
		delete[]_pStack; // 释放占用的堆内存
		_pStack = nullptr; 
	}
	SeqStack<T>(const SeqStack<T>& stack)
		// 拷贝构造也有初始化列表
		:_top(stack._top)
		,_size(stack._size)
	{
		// 不要使用memcopy进行浅拷贝-->可能发生浅拷贝错误
		_pStack = new T[_size]; // 申请一块属于对象自己的堆内存
		for (int i=0; i<_top; i++) 
		{
			_pStack[i] = stack._pStack[i];
		}
	}
    
    // 赋值重载函数, 返回引用是为了能够实现连续赋值,比如a=b=c
	SeqStack<T>& operator=(const SeqStack<T>& stack)  
	{
		// 防止自赋值
		if (this == &stack) 
		{
			return *this;
		}
        // 释放当前对象占用的外部资源
		delete[]_pStack;
        
        // 深拷贝
		_top = stack._top;
		_size = stack._size;
		_pStack = new T[_size]; // 申请一块属于对象自己的堆内存
		for (int i = 0; i < _top; i++)
		{
			_pStack[i] = stack._pStack[i];
		}
		return *this;
	}
	void push(const T& val) 
	{
		if (full())
			expand();
		_pStack[_top++] = val;
	}
	void pop() 
	{
		if (empty())
			return;
		_top--;
	}
    
	// 查看栈顶元素
    // 对于只读操作的成员方法都写成const成员方法(普通成员方法,const对象无法调用)
	T top() const 
	{
		/*
		if (empty())
			throw "stack is empty!";  // 抛异常也代表函数逻辑结束
		*/
		return _pStack[_top-1];
	}
    
    // 判满函数
	bool full() const 
	{
		return _top == _size;
	}
    
    // 判空函数
	bool empty() const 
	{
		return _top == 0;
	}

private:
	T* _pStack;		// 指向动态开辟的内存空间的首地址
	int _top;		// 指向栈顶元素位置
	int _size;		// 顺序栈容量
private:
    // 扩容函数
    void expand() 	
	{// 按照原来栈底层数组大小的两倍扩容
		T* ptmp = new T[_size * 2];
		for (int i =0;i<_top;i++) 
		{
			ptmp[i] = _pStack[i];
		}
		delete[]_pStack;
		_pStack = ptmp;
		_size *= 2;
	}
};
  • 除了构造函数和析构函数可以不写类型参数列表,其他建议都要写上,毕竟模板名+类型参数列表才是类名

  • 编写类模板时,如果要在类外定义成员方法,需要注意:

// 1.加上类名作用域
// 2.类模板中的类型参数T仅作用于类的{}范围内,所以在类外实现成员方法时,还要加上template<typename=T>

template<typename T>
void SeqStack<T>::push(const T& val) 
{
    if (full())
    	expand();
    _pStack[_top++] = val;
}
  • 类模板的选择性实例化

    对于调用到的成员方法才会实例化。下面的代码中,编译器会根据指定的类型,实例化模板类SeqStack,并且只会去实例化会调用到的成员方法(构造、析构以及push和pop)

    int main() 
    {
    	// class Seqstack<int>{};这里只实例化了构造函数和析构函数
    	// 类模板的选择性实例化(对于调用到的方法才实例化,节省编译器的工作)
    	SeqStack<int> s1;
    	// SeqStack<> s1;  使用默认类型参数
    	s1.push(20);
    	s1.push(78);
    	s1.push(35);
    	s1.push(12);
    	cout << s1.top() << endl;
    	s1.pop();
    	return 0;
    }
    
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

下酒番陪绅士

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值