<C++>10 模板

本文最后更新于:2022年6月22日 下午

10 模板

C++ 另一种编程思想称为:泛型编程。主要利用的技术就是模板

C++ 提供两种模板:函数模板、类模板

10.1 函数模板

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

语法:

template<typename T>		// <———— 声明模板
T met(T obj) {				// <———— 这里是一处函数的声明或定义
    return obj;
}

template:声明创建模板

typename:其后面的符号是一种数据类型。也能写成 class

T:通用数据类型。名字随便起

template<class HOMU>
HOMU mosiMita(HOMU homu) {
    cout << "HomuHomu MiniDomu!" << endl;
    return homu;
}

注意事项:

  • 自动类型推导,必须推导出一致的泛型才能使用

  • 模板必须确定泛型的数据类型才能使用。

    不能推导的场合,应该显式地指定泛型类型

    template<typename T>
    void biat() {				// <———— 不能进行类型推导
        cout << "Ye dada!" << endl;
    }
    
    void heli() {
        biat<int>();			// <———— 因不能进行类型推导,必须显式指定那个泛型类型
    }

普通函数与函数模板的区别:

  • 函数模板在编译时不生成任何目标代码。仅当通过模板生成具体的函数实例时才生成目标代码。

  • 函数指针只能指向模板的实例,而不能指向模板本身

  • 被多个源文件引用的函数模板,应当连同函数体一起放在头文件中

  • 普通函数调用时能发生自动类型转换

    函数模板调用时,如果利用自动类型推导,则不发生自动类型转换

    如果显式指定泛型类型,则仍会发生自动类型转换

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

  • 函数模板可以发生重载

  • 如果普通函数和函数模板都能实现,则优先调用普通函数

    可以通过空模板参数列表来强制调用函数模板

  • 如果函数模板可以产生更好的匹配,则优先调用函数模板

    template<typename T>
    void act(T t) {
    	cout << "模板函数" << endl;
    }
    
    void act(int n) {
    	cout << "普通函数" << endl;
    }
    
    int main() {
        act(1);				// <—————— 调用普通函数
    	act<>(1);			// <—————— 调用函数模板(空模板参数)
    	act('1');			// <—————— 调用函数模板(更好的匹配)
    }

模板的局限性:

  • 模板的通用性不是万能的。C++ 提供模板的重载,可以为特定类型实现更好的操作

    template<typename T>
    void act(T t) {						// <—————— 函数模板
    	cout << "T" << endl;
    }
    
    template<> void act(string s) {		// <—————— 函数模板的重载
    	cout << s << endl;
    }
    
    int main() {
        string s = "Heruin";
        act(s);							// <—————— 此处调用重载模板
    }

10.2 类模板

声明模板 template 后加一个类即构成类模板

语法:

template<class T, class Y>				// <—————— class 也可以写成 typename,一样的
class Lumine {							// <—————— 类的声明或定义
public:
    T lum_bro;
    Y lum_wep;
    
    Lumine(T t, Y y) {					// <—————— 构造器
        this->lum_bro = t;
        this->lum_wep = y;
    }
    
    void introduce() {					// <—————— 一个函数。模板类型变化时也可能出问题
        cout << lum_bro << endl;
        cout << lum_wep << endl;
    }
};

int main() {
    Lumine<int,int> lumine(1, 1);		// <—————— 要这样去实例化
}

与函数模板的区别:

  • 类模板不能自动类型推导,所以必须显式指定类型

  • 类模板在模板列表中可以有默认参数

    template<class T = string, class Y>		// <———— 添加了默认参数。这样,不显示指定就用默认类型
    class Lumine;

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

  • 普通类的成员函数在一开始就能创建

  • 类模板的成员函数在调用时才能创建

    因为数据类型不确定,该类在模板被调用前不会被创建

类模板继承问题:

  • 子类继承的父类是一个模板的场合,子类声明时要指明那个父类的泛型类型

    不指定的场合,编译器不能给子类分配内存

  • 想要灵活指定父类泛型类型的场合,子类也要称为模板

    template<class TAR>
    class DF17 {};
    
    template<class TAR>
    class HX8 : private DF17<TAR> {};		// <—————— [优质答案:“我不知道。”]

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

template<class ELE>
class Cryo {
public:
	void act(ELE t);
	Cryo();
};

template<class R>
Cryo<R>::Cryo() {}					// <—————— 类模板构造器类外实现

Cryo<char>::Cryo() {}

template<typename E>
void Cryo<E>::act(E t) {}			// <—————— 类模板成员函数类外实现

10.2.1 类模板对象做函数参数

(下例中的)模板类如下:

template<class T>
class Gro{};			// <—————— 随便写的一个空类

三种方式:

  • 指定传入类型:

    void act1(Gro<int>& g) {		// <———— “都说了是 Gro<int> 啦,你怎么听的呀(怒)”
        cout << "指定传入类型" << endl;
    }
  • 参数模板化:

    template<typename T>
    void act2(Gro<T>& g) {			// <———— “回去告诉你们那个参数,他是个模板朕也是个模板”
        cout << "参数模板化" << endl;
    }
  • 整个类模板化:

    template<class T>
    void act3(T& t) {				// <———— “啊?什么模板类,没看到啊”
        cout << "整个类模板化" << endl;
    }

10.2.2 类模板分文件编写

类模板分文件编写的场合,在运行时可能报错

这是因为,分文件编写的场合,包含的是头文件。而类模板成员函数是在调用时创建,这会导致编译器不能读取源文件中的实现。

头文件 hydro.h

template<class ELE>
class Hydro {				// <———— 随便写的,意思一下
public:
	void act(ELE t);
	Hydro();
};

源文件 hydro.cpp

template<class R>
Hydro<R>::Hydro() {}			// <———— 随便写的,意思一下

template<typename E>
void Hydro<E>::act(E t) {}		// <———— 随便写的,意思一下

解决方法:

  • 不包含头文件,而是包含源文件(一般不这样):

    #include<iostream>
    using namespace std;
    #include "hydro.cpp"		// <———— 包含的不是头文件而是源文件
    
    int main() {
        Hydro h;
    }
  • 将头文件和源文件合并为 hydro.hpp

    头文件 hydro.hpp

    template<class ELE>
    class Hydro {
    public:
    	void act(ELE t);
    	Hydro();
    };
    
    template<class R>
    Hydro<R>::Hydro(){}
    
    template<typename E>
    void Hydro<E>::act(E t) {}

    之后,包含该 hpp 文件即可

    #include<iostream>
    using namespace std;
    #include "hydro.hpp"
    
    int main() {
        Hydro h;
    }

    后缀名 hpp 是约定俗成的名称。非要变也可以

10.2.3 类模板做友元

template<class L>
class Anemo;							// <———— [5]

template<typename L>
void act2(Anemo<L> a);					// <———— [4]

template<class L>
class Anemo {
    friend void act1(Anemo<L> a) {}		// <———— [1]
    friend void act2<>(Anemo<L> a);		// <———— [3]
private:
    L val = 1;
};

template<typename L>
void act2(Anemo<L> a) {}				// <———— [2]
  1. 全局函数类内实现的场合,可以直接包含友元

    [1]

    friend void act1(Anemo<L> a) {}

  2. 全局函数的类外实现,需要添加模板声明

    [2]

    template<typename L>
    void act2(Anemo<L> a) {}

  3. 全局函数类外实现的场合,包含友元时要加入空模板参数列表,以示其为模板函数而非普通函数

    [3]

    friend void act2<>(Anemo<L> a);

  4. 由于类内使用了此前未声明过的全局函数,所以还要将全局函数在类实现前进行声明,以向编译器提示该函数的存在

    [4]

    template<typename L>
    void act2(Anemo<L> a);

  5. 由于该全局函数参数是一个此前未声明过的类,故该类也要在全局函数声明前声明,以向编译器提示该类的存在

    [5]

    template<class L>
    class Anemo;

附录

可变数组类

template<class C>
class OArray {
public:
	C* cs = NULL;
	OArray() {
		m_size = 5;
		cs = new C[m_size];
	}

	OArray(int n) {
		if (n <= 0) n = 5;
		m_size = n;
		cs = new C[m_size];
	}

	void add(const C& obj) {
		if (cs == NULL) cs = new C[m_size];
		if (m_length >= m_size) {
			C* temp = new C[m_size * 2];
			for (int i = 0; i < m_size; i++) {
				temp[i] = cs[i];
			}
			delete[] cs;
			cs = NULL;
			cs = temp;
			m_size *= 2;
		}
		cs[m_length++] = obj;
	}

	void deleteLast() {
		if (m_length <= 0) return;
		m_length--;
		cs[m_length] = NULL;
	}

	C& operator[](int n) {
		return cs[n];
	}

	void operator=(OArray<C>& o) {
		this->m_size = o.m_size;
		if (this->cs != NULL) delete[] this->cs;
		this->cs = NULL;
		this->m_length = 0;
		this->cs = new C[o.getSize()];
		for (int i = 0; i < o.getLength(); i++) {
			add(o.cs[i]);
		}
	}

	int getLength() {
		return m_length;
	}

	int getSize() {
		return m_size;
	}

	void print() {
		for (int n = 0; n < m_length; n++) {
			cout << cs[n] << '\t';
		}
		cout << endl;
	}

	~OArray() {
		if (this->cs != NULL) delete[] this->cs;
		this->cs = NULL;
	}
private:
	int m_size = 0;
	int m_length = 0;
};

<C++>10 模板
https://i-melody.github.io/2022/04/30/C++/入门阶段/10 模板/
作者
Melody
发布于
2022年4月30日
许可协议