c++泛型编程之函数模板与类模板

本文详细介绍了C++中的函数模板和类模板,包括模板的实现原理、函数模板的重载、类模板的声明与定义实例。通过myAddFunc和MyArray模板展示了如何根据不同类型参数创建不同版本的函数和类型化的类。

1 模板介绍

  • C++泛型编程(Generic Programming)是一种编程范式,它允许你编写与类型无关的代码,这些代码可以在不改变的情况下被多种类型使用。这种类型的编程主要通过模板(Templates)机制来实现。
  • 模板是C++中泛型编程的基础。一个模板就是一个创建类或函数的蓝图或者公式。

2 函数模板

2.1 函数模板的实现

  • 函数模板形式如下,typename可用class代替。
    •   template <typename T>
        T func(T data, ...){
        	...
        }
      
      • template: 声明创建模板
      • typename: 表面其后面的符号是一种数据类型,可以用class代替
      • T: 通用的数据类型,名称可以替换,通常为大写自读
  • 测试代码
    •   	#include <iostream>
        	
        	template <typename T>
        	T myAddFunc(T data1, T data2) {
        		return data1 + data2;
        	}
        	
        	int main() {
        		// 自动类型推导使用
        		int iSum = myAddFunc(100, 50);
        		float fSum = myAddFunc(3.6, 8.8);
        		std::cout << "iSum = " << iSum << std::endl;
        		std::cout << "fSum = " << fSum << std::endl;
        	
        		// 显示指定类型
        		int iiSum = myAddFunc<int>(200, 90);
        		float ffSum = myAddFunc<float>(12.8, 13.1);
        		std::cout << "iiSum = " << iiSum << std::endl;
        		std::cout << "ffSum = " << ffSum << std::endl;
        		return 0;
        	}
      

2.2 函数模板与普通函数区别

  • 普通函数调用时可以发生隐式类型转换
  • 函数模板调用时,如果利用自动类型推导,不会发生隐式类型转换
  • 如果利用显式指定类型的方式,可以发生隐式类型转换
  • 可以通过以下例子理解这三项规则
    •   #include <iostream>
      
        template <class T>
        T myAddFunc01(T data1, T data2) {
        	return data1 + data2;
        }
        
        int myAddFunc02(int data1, int data2) {
        	return data1 + data2;
        }
        
        
        int main() {
        	int data1 = 10010;
        	float data2 = 100.8;
        
        	// 无法正常调用
        	// myAddFunc01(data1, data2);
        
        	// 可以正常调用
        	myAddFunc01<int>(data1, data2);
        	
        	// 可以正常调用
        	myAddFunc02(data1, data2);
        
        	return 0;
        }
      

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

  • 如果函数模板和普通函数都可以实现,优先调用普通函数
  • 可以通过空模板参数列表来强制调用函数模板
  • 如果函数模板可以产生更好的匹配,优先调用函数模板
  • 示例如下
    •   #include <iostream>
      
        template <class T>
        void myPrint(T data) {
        	std::cout << "函数模板,data:" << data << std::endl;
        }
        
        void myPrint(int data) {
        	std::cout << "普通函数,data:" << data << std::endl;
        }
        
        
        int main() {
        	int data = 10010;
        	float fData = 12.88;
        
        	// 会优先调用普通函数
        	myPrint(data);
        
        	// 通过空模板参数列表,强制调用函数模板
        	myPrint<>(data);
        
        	// 产生了更好的匹配,会优先调用函数模板
        	myPrint(fData);
        
        
        	return 0;
        }
      
  • 打印结果
    •   普通函数,data:10010
        函数模板,data:10010
        函数模板,data:12.88
      
  • 函数模板也可以发生重载
  • 示例如下
    •   #include <iostream>
        
        template <typename T>
        T myAddFunc(T data1, T data2) {
        	return data1 + data2;
        }
        
        template <typename T>
        T myAddFunc(T data1, T data2, T data3) {
        	return data1 + data2 + data3;
        }
        
        int main() {
        	int sum1 = myAddFunc(10, 20);
        	int sum2 = myAddFunc(10, 20, 30);
        
        	std::cout << "sum1 = " << sum1 << std::endl;
        	std::cout << "sum2 = " << sum2 << std::endl;
        	return 0;
        }
      

2.4 自定义数据类型使用函数模板

  • 示例如下
    •   #include <iostream>
      
        struct Person {
        public:
        	Person(int code, std::string name) {
        		mCode = code;
        		mName = name;
        	}
        
        	int mCode;
        	std::string mName;
        };
        
        template <class T>
        bool myCompare(T data1, T data2) {
        	if (data1 == data2) {
        		return true;
        	}
        	else {
        		return false;
        	}
        }
        
        // 指定模板的比对规则
        template<> bool myCompare(Person p1, Person p2) {
        	if (p1.mCode == p2.mCode) {
        		return true;
        	}
        	else {
        		return false;
        	}
        }
        
        int main() {
        	Person p1(10010, "Tom");
        	Person p2(10020, "Mary");
        
        	if (myCompare(p1, p2)) {
        		std::cout << "p1 == p2" << std::endl;
        	}
        	else {
        		std::cout << "p1 != p2" << std::endl;
        	}
        
        
        	return 0;
        }
      

2.5 函数模板实现原理

  • 当我们调用一个函数模板时,编译器通常用函数实参来为我们推断模板实参。即当我们调用myAddFunc时,编译器使用实参的类型来确定绑定到模板参数T的类型。
  • 例如我们调用myAddFunc(100, 50)时,实参类型为int,编译器会推断出模板实参为int,并将它绑定到模板参数T
  • 编译器用推断出的模板参数来为我们实例化一个特定版本的函数。

3 类模板

  • 类模板实际上是函数模板的推广。类模板用于实现所需数据的类型参数化

3.1 类模板的声明与定义

  • 语法
    	template <类型形参表>
    	class 类名{
    		//类成员
    	};
    	
    	template <类型形参表>
    	返回类型 类型<类型名>::成员函数1(形参表){
    		//函数体
    	}
    	
    	template <类型形参表>
    	返回类型 类型<类型名>::成员函数n(形参表){
    		//函数体
    	}
    
  • 模板类声明与定义
    	template <typename T>
    	
    	class MyArray
    	{
    	public:
    		MyArray(int size);
    		~MyArray();
    	
    		T& get(int index);
    		void set(int index, T value);
    	
    	private:
    		int mSize;
    		T *mData;
    	};
    	
    	template <typename T>
    	void MyArray<T>::set(int index, T value) {
    		mData[index] = value;
    	}
    	
    	template <typename T>
    	MyArray<T>::MyArray(int size)
    	{
    		mSize = (size > 1) ? size : 1;
    		mData = new T[mSize];
    	}
    	
    	template <typename T>
    	MyArray<T>::~MyArray()
    	{
    		delete[]mData;
    	}
    	
    	template <typename T>
    	T& MyArray<T>::get(int index) {
    		return mData[index];
    	}
    
  • 由于类模板中的函数一开始并不会创建,只有在调用的时候才创建。因此如果类模板的定义放在.h文件中,实现放在.cpp文件中,在链接的时候会报错。因此类模板的定义和实现,都要放在头文件中,一般将头文件定义为.hpp,使用时包含.hpp文件就可以了。
  • 调用
    	#include <iostream>
    	#include "MyArray.hpp"
    	
    	int main() {
    		MyArray<int> intArray(10);
    	
    		intArray.set(0, 12);
    		intArray.set(1, 24);
    		intArray.set(2, 34);
    		intArray.set(3, 55);
    	
    		std::cout << "intArray[0] = " << intArray.get(0) << std::endl;
    		std::cout << "intArray[1] = " << intArray.get(1) << std::endl;
    		std::cout << "intArray[2] = " << intArray.get(2) << std::endl;
    		std::cout << "intArray[3] = " << intArray.get(3) << std::endl;
    	
    		MyArray<float> floatArray(10);
    		floatArray.set(0, 1.1);
    		floatArray.set(1, 19.5);
    		floatArray.set(2, 20.0);
    		floatArray.set(3, 8.8);
    	
    		std::cout << "floatArray[0] = " << floatArray.get(0) << std::endl;
    		std::cout << "floatArray[1] = " << floatArray.get(1) << std::endl;
    		std::cout << "floatArray[2] = " << floatArray.get(2) << std::endl;
    		std::cout << "floatArray[3] = " << floatArray.get(3) << std::endl;
    	
    		return 0;
    	}
    

3.2 函数模板与类模板的区别

  • 函数模板可以进行自动类型推导,类模板无法进行自动类型推导
  • 示例如下
    •   #include <iostream>
      
        template <typename TCode, typename TName>
        class Person {
        public:
        	Person(TCode code, TName name) {
        		mCode = code;
        		mName = name;
        	}
        
        	TCode mCode;
        	TName mName;
        };
        
        template <typename TCode, typename TName>
        void funcPerson(TCode code, TName name) {
        	
        }
        
        
        int main() {
        	// 函数模板可以进行自定类型推导
        	funcPerson(10010, "Tom");
        
        	// 类模板无法进行类型推导,会报错
        	// Person p1(10020, "Jack");
        
        	// 类模板必须显式指定类型
        	Person<int, std::string> p1(10020, "Jack");
        
        	return 0;
        }
      
  • 类模板在模板参数列表中可以有默认参数
  • 示例如下
    •   #include <iostream>
      
        // 指定默认参数类型
        template <typename TCode, typename TName = std::string>
        class Person {
        public:
        	Person(TCode code, TName name) {
        		mCode = code;
        		mName = name;
        	}
        
        	TCode mCode;
        	TName mName;
        };
        
        int main() {
        
        	// 有默认参数类型时可不显式指定类型
        	Person<int> p1(10020, "Jack");
        
        	return 0;
        }
      

3.3 类模板对象作函数参数

  • 类模板对象作函数参数时,有三种传入类型,分别为 指定传入类型参数模板化类模板化
  • 使用比较广泛的是指定传入类型
  • 示例如下
    •   #include <iostream>
        #include <string>
        
        template <typename T1, typename T2>
        class Person {
        public:
        	Person(T1 code, T2 name) {
        		mCode = code;
        		mName = name;
        	}
        	void PersonShow() {
        		std::cout << "code: " << mCode << std::endl;
        		std::cout << "name: " << mName << std::endl;
        	}
        
        public:
        	T1 mCode;
        	T2 mName;
        };
        
        
        // 指定传入类型
        void printPerson1(Person<int, std::string> &p) {
        	p.PersonShow();
        }
        
        // 参数模板化
        template <typename T1, typename T2>
        void printPerson2(Person<T1, T2> &p) {
        	p.PersonShow();
        	std::cout << "T1 type: " << typeid(T1).name() << std::endl;
        	std::cout << "T2 type: " << typeid(T2).name() << std::endl;
        }
        
        // 整个类模板化
        template <typename T>
        void printPerson3(T &p) {
        	p.PersonShow();
        	std::cout << "T type: " << typeid(T).name() << std::endl;
        }
        
        
        int main() {
        	// 指定传入类型
        	Person<int, std::string> p1(10010, "Tom");
        	printPerson1(p1);
        
        	// 参数模板化
        	Person<int, std::string> p2(10020, "Jack");
        	printPerson2(p2);
        
        	// 整个类模板化
        	Person<int, std::string> p3(10030, "Lucy");
        	printPerson3(p3);
        
        
        	return 0;
        }
      
  • 执行结果
    •   code: 10010
        name: Tom
        code: 10020
        name: Jack
        T1 type: int
        T2 type: class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> >
        code: 10030
        name: Lucy
        T type: class Person<int,class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> > >
      

3.4 类模板与继承

  • 父类是一个类模板时,子类在声明的时候,要指定处父类中T的类型。如果不指定,编译器无法给子类分配内存。如果想灵活指定出父类中T的类型,子类也需要变为类模板。
  • 示例如下
  •   #include <iostream>
      #include <string>
      
      template <typename T>
      class Base {
      public:
      	T mData;
      };
      
      // 继承时必须显式指定父类的类型
      class subA : public Base<int> {
      
      };
      
      // 子类为类模板时
      template <typename T1, typename T2>
      class subB : public Base<T2> {
      public:
      	T1 mSubData;
      };
      
      
      int main() {
      	
      	subA sA;
      
      	subB<int, std::string> sb;
      
      	return 0;
      }
    

3.5 类模板与友元

  • 友元函数类内实现:直接在类内声明友元即可
  • 友元函数类外实现:需要提前让编译器知道全局函数的存在
  • 示例如下
    •   #include <iostream>
        #include <string>
        
        template <typename T1, typename T2>
        class Person;
        
        // 友元函数类外实现
        template <typename T1, typename T2>
        void personShow2(Person<T1, T2> p) {
        	std::cout << "code: " << p.mCode << std::endl;
        	std::cout << "name: " << p.mName << std::endl;
        }
        
        template <typename T1, typename T2>
        class Person {
        	// 友元函数类内实现
        	friend void personShow(Person<T1, T2> p) {
        		std::cout << "code: " << p.mCode << std::endl;
        		std::cout << "name: " << p.mName << std::endl;
        	}
        
        	// 友元函数类外实现, 要加一个空模板参数列表
        	friend void personShow2<>(Person<T1, T2> p);
        
        public:
        	Person(T1 code, T2 name) {
        		mCode = code;
        		mName = name;
        	}
        
        	T1 mCode;
        	T2 mName;
        };
        
        
        int main() {
        	Person<int, std::string> p1(10010, "Tom");
        	personShow(p1);
        
        	Person<int, std::string> p2(10020, "Lucy");
        	personShow2(p2);
        
        	return 0;
        }
      

4 可变参数模板

  • C++11语法中,支持可变参数模板,这意味着模板可以接受不确定数量的参数。示例如下
  • 实现
    •   #ifndef VARIADICS_H
        #define VARIADICS_H
        #include <iostream>
        
        class Variadics{
        public:
        	Variadics() {}
        public:
        	std::string getMaxString(std::string data); 
        
        	template<typename... Args>
        	std::string getMaxString(std::string data, Args... rest);
        private:
            std::string mBaseStr;
        };
        
        std::string Variadics::getMaxString(std::string data) {
            std::cout << "data: " << data << std::endl;
        
            if(data.size() > mBaseStr.size()){
                mBaseStr = data;
            }
            std::string tmpBaseStr = mBaseStr;
            mBaseStr.clear();
        	return tmpBaseStr;
        }
        
        template<typename... Args>
        std::string Variadics::getMaxString(std::string data, Args... rest) {
        	std::cout << "data: " << data << std::endl;
            if(data.size() > mBaseStr.size()){
                mBaseStr = data;
            }
        
        	return getMaxString(rest...);
        }
        
        #endi
      
  • 调用
    •   Variadics vsria;
        std::string strMax;
        strMax = vsria.getMaxString("hello", "wor555ld", "12345", "1");
        std::cout << "strMax: " << strMax << std::endl;
      
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

大草原的小灰灰

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

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

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

打赏作者

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

抵扣说明:

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

余额充值