C++的模板template

本文介绍了C++中的模板,包括类模板和函数模板的原理,以及为何引入模板以减少代码重复。通过实例化展示了如何根据不同类型自动适应模板。
摘要由CSDN通过智能技术生成

一、什么是模板

        C++中的模板分为类模板函数模板,并不是一个实际的类或函数,这指的是编译器不会自动为其生成具体的可执行代码。只有在具体执行时编译器才帮助其实例化

二、为什么引入模板

        拿我们最常见的交换函数来举例子,如果我们编写一个交换int类型变量的函数,如下:

bool Swap(int &a, int &b) {
    int c = a;
    a = b;
    b = c;

    return true;
}

        但如果我们想要编写一个通过函数来交换float的函数,我们又要写另一个函数,如下:

bool Swap(float &a, float &b) {
    float c = a;
    a = b;
    b = c;

    return true;
}

         那接下来如果需要交换double,char,bool,甚至一些结构体或者类的交换函数,那么我们的工作量就会很大。细心观察会发现,其实上边两个函数唯一不同的便是交换的变量类型不同,如果我们把变量类型设置为一个抽象的X,在具体需要交换的时候,再去根据具体的类型去自动适应,这便是C++的template模板的巧妙之处。

三、使用方式

        C++模板采用template关键字修饰

3.1 函数模板

        具体写法如下:

template <typename 参数类型1, typename 参数类型2,...>
返回值类型 函数名称(形参1, 形参2,...) {
    函数体
}

        上边代码的typename关键字也可以用class代替。下边给出一个交换函数模板的例子:

template <typename T>
bool Swap(T &a, T &b) {
    T c = a;
    a = b;
    b = c;

    return true;
}

  3.2 类模板

          具体写法如下:

template <typename 模板名称1, typename 模板名称2,...>
class 类名称{
    类成员
}

        注意,在实例化类模板对象时,需要在类的结尾加上<具体模板名称>。 比如以下例子:

template <typename T1, typename T2>
class NNUM {
	private :
		T1 num1;
		T1 num2;
		
	public :
		NNUM(T1 num1, T1 num2) {
			this->num1 = num1;
			this->num2 = num2; 
		}
		
		void print_NNUM(T2 str) {
			cout << "str : [" << str << "]" << endl;
			cout << "num1 : <" << this->num1 << ">, " << "num2 : <" << num2 << ">" << endl;
		}
};

       这里是一个用了2个模板类型(T1和T2)的类模板,那么我们在实例化时候就要按照以下代码,将具体需要使用的T1与T2类型体现出来:

NNUM<int, string> n1 = NNUM<int, string>(123, 456);

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

        区别在于是否会发生隐式类型转换。例子如下:

/*函数模板*/
template <typename T>
T fun1(T num1, T num2) {
	return num1 * num2;
}

/*普通函数*/
float fun2(float num1, float num2) {
	return num1 * num2;
}


int main() {
	int x = 222;
	float y = 3.0;

	/*函数模板*/
	//以下这条语句报错,因为函数模板无法隐式地将int类型的x转换为float类型
	//cout << fun1(x, y) << endl; //语句1
	/*普通函数*/
	//这条语句正确 222.0 * 3.0 = 666.0
	cout << fun2(x, y) << endl; //语句2 

	//以下这条语句正确,因为显示调用函数模板,可以实现自动类型转换
	cout << fun1<float>(x, y) << endl; //语句3

	return 0;
}

        以上代码中,fun1为函数模板,fun2为普通函数。

        当使用函数模板传入的参数为int和double时,语句1报错,因为函数模板无法隐式地将int类型的x转换为float类型,无法完成隐式自动转换。同样的使用放入fun2的普通函数中就可以正常运行和输出。语句3采用显示调用的方式,同样也可以实现自动类型转换

        建议:当使用模板时,最好显示调用

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

        当函数模板普通函数同时存在时,程序会选择哪一个呢

//函数模板
template<typename T>
void funct(T a, T b) {
	cout << "函数模板" << endl;
}

//普通函数
void funct(int a, int b) {
	cout << "普通函数" << endl;
}

5.1 情况一: 二者均可调用

        当函数模板和普通函数均可调用时,程序会优先调用普通函数

        程序测试示例和运行结果如下所示:

//函数模板
template<typename T>
void funct(T a, T b) {
	cout << "函数模板" << endl;
}

//普通函数
void funct(int a, int b) {
	cout << "普通函数" << endl;
}

int main() {
	funct(1, 2);

	return 0;
}

---------------------------------------------------------------------------------------------------------------------------------

☆特别注意

        当普通函数只有声明,没有定义的时候,程序同样会优先调用普通函数。示例如下:

//函数模板定义
template<typename T>
void funct(T a, T b) {
	cout << "函数模板" << endl;
}

//普通函数声明
void funct(int a, int b);

int main() {
	funct(1, 2);

	return 0;
}

        上述代码中,普通函数funct只有声明而未定义,因此编译后程序会报错误信息函数未定义

5.2 情况二:使用空模板参数列表

        在使用空模板参数列表的情况下,程序会强制调用函数模板。示例与运行结果如下

//函数模板
template<typename T>
void funct(T a, T b) {
	cout << "函数模板" << endl;
}

//普通函数
void funct(int a, int b) {
	cout << "普通函数" << endl;
}

int main() {
	funct<>(1, 2);//这里使用了“空模板参数列表”

	return 0;
}

5.3 情况三:函数模板的重载

        函数模板允许重载

        重载的作用在于:代码在编译阶段对模板代码编译。而在代码的执行阶段,才会判断具体调用时是否发生错误。例子如下:

//复数类
class Complex {
	int real;//实部
	int vir;//虚部

	public:
		Complex(int r, int v) : real(r), vir(v){}
};

//模板比较函数
template<typename T>
bool myCompare(T &t1, T &t2) {
	if (t1 == t2) {
		return true;
	}
	else {
		return false;
	}
}

int main() {
	Complex c1(1, 2);
	Complex c2(3, 4);
	Complex c3(3, 4);

	cout << "c1 == c2 : " << boolalpha << myCompare(c1, c2) << endl;
	cout << "c2 == c3 : " << boolalpha << myCompare(c2, c3) << endl;

	return 0;
}

        这样的程序编译是没有错误的,但运行时会报错,错误信息如下:

       原因:当执行时,模板匹配到了我们的Complex类,但Complex类型并没有相应的==运算符

解决方式:

 1. 重载运算符==

                利用全局函数重载Complex的==运算符。

                        关于运算符的重载,请参考我之前写的《C++运算符重载》

// ==运算符的重载
bool operator==(Complex& c0, Complex& c1) {
	cout << "执行的是:运算符重载" << endl;
	if (c0.real == c1.real && c0.vir == c1.vir) {
		return true;
	}
	else {
		return false;
	}
}
2. 模板重载
//模板的重载
template<>
bool myCompare(Complex& c0, Complex& c1) {
	if (c0.real == c1.real && c0.vir == c1.vir) {
		return true;
	}
	else {
		return false;
	}
}

                在函数执行时,会优先匹配这个函数的内容进行执行,因为它不需要适配T类型,直接就是Complex类型的,更容易和函数调用适配。执行结果如下:

 3. 二者的优先级

        如果二者同时存在:模板重载 > 运算符重载

六、模板类和普通类的函数创建时机

        模板类和普通类创建的时间是不同的:

普通类的成员函数在程序编译时一开始就被创建
类模板的成员函数调用时创建

       也就是说,当类模板使用出现错误的时候,在编译阶段,程序可能不会报错。例子如下:

class Dog {
	public :
		Dog& showDog() {
			cout << "汪" << endl;

			return *this;
		}
};

class Cat {
	public :
		Cat& showCat() {
			cout << "喵" << endl;

			return *this;
		}
};

template<typename T>
class ShowAnimal {
    public:
	    T animal;

	    void show1() {
	    	animal.showDog();
	    }
    
	    void show1() {
	    	animal.showCat();
	    }
}

        以上代码编译不会报错。以上例子中,DogCat分别拥有showDog()showCat()成员函数。当类模板ShowAnimal创建它的show1()show2()成员函数时,其实并没有在编译阶段真正创建这两个函数,因此在编译阶段是没有错误的。

        但是,执行时候会报错。因为如果T为Cat,则show1()成员函数出错,因为没有animal没有showDog();如果T为Dog,则show2()成员函数出错,因为没有animal没有showCat()

        我们再看一下普通函数的例子:

class Cat {
public:
	Cat& showCat() {
		cout << "喵" << endl;

		return *this;
	}
};

class Show {
	public :
		Cat cat;

		void show() {
			cat.showDog();//这句话会直接报错,无法通过编译
		}
};

        在ShowShow类show()成员函数中,因为cat没有showDog()

                系统在未创建对象时,就已经创建了这个函数,因此在编译阶段系统会报错

七、类模板作为函数的形参

        (※小知识※:如果想查看某一类型的名称,请使用typeid(XXX).name(),返回的是字符串)

                (比如: cout << typeid(int).name() << endl; 输出的是int。当然,类型也可以是模板T)

        当需要将类模板作为形参类型时,总共有一下三种方式:

7.1 直接指定模板的类型

        这个很简单,就是在< >声明模板的真实类型,示例如下:

<template T>
class Animal {
    public :
        T type; //假设在调用时,指定的是string类型
    
        Animal(T t) : type(t){}
        
        void show() {
            cout << type << endl;
        }
} 

//测试函数:参数为一个Animal类型的对象
void test(Animal<string> animal) {
    animal.show();
}

//主函数
int main() {
    Animal<string> dog("狗");

    test(dog);//指明string类型的调用

    return 0;
}

 7.2 将模板类的参数模板

        在形参的模板类型处依旧使用模板,将整个函数变成一个模板函数。例子如下:

<template T>
class Animal {
    public :
        T type; //假设在调用时,指定的是string类型
    
        Animal(T t) : type(t){}
        
        void show() {
            cout << type << endl;
        }
} 

//测试函数:参数为一个Animal类型的对象
template<typename T>
void test(Animal<T> animal) {
    animal.show();
}

//主函数
int main() {
    Animal<string> dog("狗");

    test(dog);//指明string类型的调用

    return 0;
}

7.3 使用模板引用类型

        将形参类型整体设置为模板的引用,依靠编译器执行时自动识别。例子如下:

<template T>
class Animal {
    public :
        T type; //假设在调用时,指定的是string类型
    
        Animal(T t) : type(t){}
        
        void show() {
            cout << type << endl;
        }
} 

//测试函数:参数为一个Animal类型的对象
template<typename T>
void test(T& animal) {
    animal.show();
}

//主函数
int main() {
    Animal<string> dog("狗");

    test(dog);//指明string类型的调用

    return 0;
}

 八、继承模板类

        1. 当子类企图集成一个类模板父类时,父类必须指定具体的模板类型。原因:如果不指定类型,编译器无法确定父类具体大小,也就无法集成给子类。(无论父类访问权限如何,子类都将全部继承,只是 是否可见和可访问罢了)

        2. 如果父类未指定模板具体类型,则子类必须也一个类模板

为了便于直观理解,例子如下:

//模板类父类
template <typename T>
class Animal {
	public :
		T type;

		Animal(T t) : type(t) {}
};

//指定父类模板的类型,子类可以正常继承
class Dog : public Animal<string> {
	public :
		Dog(string t) : Animal(t) {}
};

//未指定父类模板类型,子类也必须是模板类
template <typename T>
class Cat : public Animal {
	public :
		Cat(T t) : Animal(t) {}
};

九、类模板 成员函数的类外定义

        类外定义时,不光需要声明域,同时也需要:

1将这个函数声明为函数模板
2在域名声明具体的模板类型

     拿上边的Animal类举一个简单的例子:

//模板类父类
template <typename T>
class Animal {
	public :
		T type;

		Animal(T t);//构造函数
        void showType();//展示type函数
};

//构造函数的类外定义
template<typename T>
Animal<T>::Animal(T t) : type(t) {} 

//展示type函数的类外定义
template<typename T> 
void Animal<T>::showType() {
    cout << T << endl;
}

十、类模板 多文件编写

        由于类模板是在运行时才创建的,因此有时会出现声明(.h)与源码(.cpp)对应不上的情况。

解决方式有以下两个:(1)直接包含.cpp文件  

                                    (2)声明与定义写到同一个尾缀为.hpp的文件(hpp不是强制的,是习惯)

        

  • 10
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值