STL学习小计(一)——template模板

5 篇文章 0 订阅

一、模板

1.1 函数模板

//main.cpp
template<class T>// 需要声明template,其中class可以替换为typename
int MyAdd(T a, T b)//模板函数
{
    return a + b;
}

int MyAdd(int a,char b)//普通函数
{
    return a + b;
}

int main()
{
    int a=10,b=20;
    double da = 1.23,db=2.45;
    MyAdd(a,b); //第一次调用
    MyAdd(da,db); //第二次调用
    MyAdd(b,a);//第三次调用
    return 0;
}

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

1、模板函数与普通函数同时出现时,编译器优先调用普通函数。** 可以使用MyAdd<>()显式指定类型,强制调用模板 **
2、模板函数不允许自动类型转换,普通函数可以。例如执行MyAdd(char,int)时候会调用普通函数,执行MyAdd(int,int)或者MyAdd(char,char)时候才会调用模板函数。
3、函数模板可以重载

1.2 函数模板的编译实现

在main.cpp文件到可执行文件中,有这几步,此处建议动手试一试,总结为E->S->C(戏称ESC):
1、预编译器------->main.i;宏展开,引入头文件

g++ -E main.cpp -o main.i

2、编译器------->main.s(汇编文件),此时还是能看懂的;

g++ -S main.i -o main.s

3、汇编器------->main(目标文件,Windows为.obj,Linux为.o),为二进制,此时已经看不懂;

g++ -C main.cpp -o main.o

4、链接器------->main.exe;(可执行文件)将各个.o文件合起来

g++ main.s -o main

函数模板无法直接被调用,它会在编译环节会根据不同的参数类型,被翻译成不同的具体函数。
main.cpp中,所调用的三次MyAdd(),在编译中被翻译如下,可以看出,第一个和第三个是同一个函数,即他们的具体函数一样。

	call	_Z5MyAddIiET_S0_S0_
....
	call	_Z5MyAddIdET_S0_S0_
....
	call	_Z5MyAddIiET_S0_S0_

两次编译:
编译器会对函数模板进行两次编译:
1、对模板代码本身进行编译,可以理解为先看一下模板代码有无语法错误;
2、在调用模板的地方,针对不同的参数,生成不同的具体函数;进行参数替换后,再对具体函数进行编译。

1.3 函数模板应用——同时对int数组和char数组排序

template<class T>//将正常的排序中,int和char换成T即可
void SortArray(T *arr, int len)
{
	T temp;
	for (int i = 0; i < len - 1; i++)
	{
		for (int j = 0; j < len - 1 - i; j++)
		{
			if (arr[j] > arr[j + 1])
			{
				temp = arr[j];
				arr[j] = arr[j + 1];
				arr[j + 1] = temp;
			}

		}
	}
}

1.4 类模板

在类里面使用模板:

template<class T>
class Person
{
public:
	T mId;
	T mAge;
	
	Person(T id, T age)
	{
		this->mAge = age;
		this->mId = id;
	}
	void show()
	{
		cout << "ID: " << mId << "  Age : " << mAge << endl;
	}
};
int main()
{
	Person<int> p(10,20);//类模板必须显式指定,函数模板可以自动推导
	p.show();
	return 0;
}

注意:类模板在使用的时候必须显式指定,这不同函数模板可以自动推导

1.5 类模板派生

1.5.1类模板派生普通类
template<class T>
class Person
{
public:
	Person()
	{
		mAge = 0;
	}
	T mId;
};

//派生普通类,此处需要显式指定类型,例如Person<int>
class SubPerson :public Person<int>{};

注意:派生普通类时候,需要显式指定类型。可以理解为:在定义对象时候,编译器需要预分配内存空间,只有确定了类型,才可以确当预分配的空间大小

1.5.2 类模板派生类模板
template<class T>
class Person
{
public:
	Person()
	{
		mAge = 0;
	}
	T mId;
};

//派生模板类,此处不需要显式指定类型,可以拿T来用
template<class T>
class SubPerson :public Person<T>{};

模板类派生总结:
需不需要显式指定,关键看编译器是否能知道需要分配多大空间,即编译器是否知道参数的类型。在派生模板类里面,由于引入了模板T,则编译器可以从模板T处获取参数类型,因此不需要显式指定,可以改用T代替

1.6模板类的写法

1.6.1 防止头文件重复引用

在c中

#ifndef PERSON_H
#define PERSON_H
#endif // !PERSON_H

在C++中

#pragma once
1.6.2 .cpp和.h文件分离的写法

即在头文件里声明类,在cpp文件里实现类。
头文件Person.h 如下:

//Person.h
#pragma once
#include <iostream>
#include <string>
using namespace std;

template<class T1,class T2>
class Person
{
public:
	Person(T1 name,T2 age);
	void show();
public:
	T1 mName;
	T2 mAge;
};


源文件Person.cpp 如下:

//Person.cpp
#include "Person.h"

template<class T1, class T2>
Person<T1, T2>::Person(T1 name, T2 age)//此处也需要<T1,T2>显式指定
{
	this->mAge = age;
	this->mName = name;
}

template<class T1, class T2>
void Person<T1, T2>::show() 
{
	cout << "Name:" << this->mName << " Age:" << this->mAge << endl;
}

注意!这样的Person类,在main.cpp中使用的时候,使用不当会出现错误,是因为模板的二次编译机制和c++的单独编译机制导致的,详见我的另一篇博客:
C++使用template模板类,h和cpp分离,编译能通过但是无法运行----报错:无法解析的外部符号

(错误的用法)main.cpp文件如下:

//main.cpp,这种写法能编译通过,但是无法正常运行
#include <iostream>
#include "Person.h"
int main()
{
	Person<string, int> p("小明", 12);
	p.show();
    std::cout << "Hello World!\n";
}

(正确的用法)main.cpp文件如下:

//main.cpp
#include <iostream>
#include "Person.cpp"
//此处引入cpp文件而不是h文件
//并且按照惯例,会将Person.cpp的后缀cpp改成hpp,再引入Person.hpp,以作区分
int main()
{
	Person<string, int> p("小明", 12);
	p.show();
    std::cout << "Hello World!\n";
}

1.6.3 .cpp和.h文件不分离的写法

即在同一个文件里声明和实现函数,又可以分为类内实现和类外实现
1、类内实现如下:

//类内实现,即函数实现在类内
template<class T1,class T2>
class Person
{
public:
	Person(T1 name, T2 age)
	{
		this->mName = name;
		this->mAge = age;
	}
	void show()
	{
		cout << "Name:" << this->mName << " Age:" << this->mAge << endl;
	}

public:
	T1 mName;
	T2 mAge;
};

int main()
{
	Person<string,int> p("小明",21);//此处需要显式指定类型,编译器才能预分配内存空间
	p.show();
}

2、类外实现
类模板的类外实现较为复杂,主要原因在于:实现在类外的函数无法访问到类的私有成员private,或者是保护成员protected,可以用友元函数来解决,但不建议使用,原因后面会解释。

环境:Window 10 + VS2017 + C++17标准,以下代码可以运行:

//类外实现,友元函数
#include <iostream>
#include<string>
using namespace std;

template<class T1, class T2>
class Person
{
public:
	Person(T1 name, T2 age);
	//此处声明友元函数,注意需要使用<T1,T2>来显式指定类型,原因上面讲过了
	template<class T1,class T2>
	friend void show(Person<T1,T2> p);
	
private:
	T1 mName;
	T2 mAge;
};

template<class T1,class T2>
Person<T1,T2>::Person(T1 name,T2 age)
{
	this->mAge = age;
	this->mName = name;
}

template<class T1, class T2>
void show(Person<T1,T2> p)
{
	cout << "Name:" << p.mName << " Age:" << p.mAge << endl;
}

int main()
{
	Person <string, int> p("小明", 13);
	show(p);
}

但是这无法在Ubuntu + C++11下运行,需要做一些修改

#include <iostream>
#include<string>
using namespace std;

//此处加入类外声明
template<class T1, class T2> class Person;
template<class T1, class T2> void show(Person<T1,T2> p);


template<class T1, class T2>
class Person
{
public:
	Person(T1 name, T2 age);

	//template<class T1,class T2> 此处不用声明T1 T2
	friend void show<T1,T2>(Person<T1,T2> p);
	
private:
	T1 mName;
	T2 mAge;
};

template<class T1,class T2>
Person<T1,T2>::Person(T1 name,T2 age)
{
	this->mAge = age;
	this->mName = name;
}

template<class T1, class T2>
void show(Person<T1,T2> p)
{
	cout << "Name:" << p.mName << " Age:" << p.mAge << endl;
}

int main()
{
	Person <string, int> p("小明", 13);
	show(p);
}

这样改完之后就可以了,显而易见,友元函数会十分麻烦,因此不建议使用

1.7 类模板碰到static成员

static成员是所有类实例共有的,是整个类共有的,但模板并不是类。换言之,当类模板被显式指定为不同的具体类时候,不同的具体类有各自不同的static成员
参考代码:

#include <iostream>
using namespace std;

template<class T>
class Person
{
public:
	static int a;
};

//类外初始化
template<class T>
int Person<T>::a = 0;

int main()
{
	Person<int> p1, p2, p3;
	Person<char> ps1, ps2, ps3;

	p1.a = 10;

	cout << "p1:" << p1.a << " p2:" << p2.a << " p3:" << p3.a << endl;
	cout << "ps1:" << ps1.a << " ps2:" << ps2.a << " ps3:" << ps3.a << endl;
}

输出如下:
p1:10 p2:10 p3:10
ps1:0 ps2:0 ps3:0

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值