c++(24)STL引入:函数模板、类模板

我们在C语言的常规编程工作中,经常会遇到因为形参数据类型,而定义多个函数。比如功能交换A和B的值

//int 类型数据交换
void MySwap(int &a, int &b)
{
	int temp = a;
	a = b;
	b = temp;
}

//double 类型数据交换
void MySwap(double &a, double &b)
{
	double temp = a;
	a = b;
	b = temp;
}

只要A和B 这两个数据的类型不同,我就要重定义一个新函数,而这两个函数除了形参数据类型不一样,其他的逻辑都是一样的。这样就造成了代码的重复,增加维护成本。

为了解决这个问题,c++引入了 模板。

1、函数模板的基本语法

函数模板使形参类型化,实现定义的时候不关心具体的数据类型,只关心功能的实现。

编译器为了与普通函数区分,使用关键字template。

template  
template 这两种使用方式表达的意思一样,根据习惯喜欢用哪个就用哪个。如果说需要使用多个类型的参数,那么也可以增加定义

template

#include 
#include 
#include 
using namespace std;

//template  // 
template //这两种方式都可以  //如果需要多个类型参数的时候,可以增加定义 
//template
//使用template模板函数的时候,当出现template关键字的时候,只对紧接着出现的函数名生效
void MySwap(T &a, T &b)
{
	T temp = a;
	a = b;
	b = temp;
}
//上面template的作用域消失

int main(void)
{
	int a = 10, b = 20;
	cout <<"a="<#include 
#include 
#include 
using namespace std;

template 
T MyAdd(T a, T b)
{
	cout<<"函数模板"<

c++(24)STL引入:函数模板、类模板_第1张图片

 3、函数模板和普通函数在一起调用的规则

(1)、函数模板可以像普通函数那样被重载

template
void Print(T a)
{
}

template
void Print(T a, T b)
{
}

(2)、c++编译器优先考虑普通函数   

                 MySwap(T a, T b);    MySwap(int a, int b); 当形参都是int时,优先考虑普通。

(3)、如果函数模板可以产生一个更好的匹配,那么选择模板

(4)、可以通过空模板实参列表的语法限定编译器只能通过模板匹配 

                 MySwap<>(a, b);限定只调用函数模板   

4、c++编译模板机制剖析,分析函数模板是如何实现的

c++(24)STL引入:函数模板、类模板_第2张图片 当我们需要编译一个test.cpp的时候,他的一个编译过程大概是这样的。首先预编译器会先将宏定义进行展开,生成test.i文件,此时的这个 .i 文件我们还能看得懂。(通过编辑器打开,可以在最下面发现 宏定义展开后的代码)

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

之后编译器会将.i文件,进行翻译编译,生成汇编文件test.s,这个时候已经不太能 看得懂了,当然ABCD的字母还是看得懂。

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

在之后汇编器,会将.s文件变成二进制文件,也就是目标文件,也就是test.o文件。(在windows下是.obj文件)

 g++ -c test.s -o test.o

最后到链接器,将很多.o文件和链接库文件,最后都合在一起,生成可执行文件。(linux可执行文件,文件名可自定义。windows下是.exe文件)。

g++ test.s -o a.out

 函数模板的机制,其实是编译器在编译阶段,对函数模板进行了翻译。将整个工程中,被调用的函数模板翻译成了普通函数。调用了N种数据类型的模板,就会生成N个不同名称的普通函数。

c++(24)STL引入:函数模板、类模板_第3张图片

 下面我们来验证一下

#include 
#include 
#include 
using namespace std;

template  //注意没有分号
T MyAdd(T a, T b)
{
	return (a+b);
}

int main(void)
{
	int nVal1 = 10, nVal2 = 20;
	char chVal1 = 30, chVal2 = 40;
	float fVal1 = 6.66, fVal2 = 9.99;
	
	MyAdd(nVal1, nVal2);
	MyAdd(chVal1, chVal1);
	MyAdd(fVal1, fVal2);
	
	MyAdd(nVal1, nVal1);
	
	return 0;
}


上面这个例子一共调用了四次MyAdd模板,我们对源文件直接进行编译g++ -S test.cpp -o test.s,生成 .s 文件。然后直接打开.s文件进行分析

c++(24)STL引入:函数模板、类模板_第4张图片

.s文件还是能让我们稍微看懂一点,但是对与理解原来有很大帮助。我们看到里面有一个熟悉的main函数,在这里面call了四次 MyAdd,我们在.s里也发现了熟悉的MyAdd,只不过名字不太一样,多了一点后缀。

分别叫c++(24)STL引入:函数模板、类模板_第5张图片 细心点可以发现,里面不同的i、c、f、i就是  对应的数据类型的缩写,int、char、float、int。跟我们实际的调用情况也吻合。

那么我们分析函数模板是如何实现的结论:

编译器并不是把函数模板处理成能够处理任何类型的函数,而是在编译阶段,函数模板通过具体类型产生的不同函数,编译器对函数模板进行二次编译,在声明的地方对模板函数代码本身进行编译,在调用的地方对参数替换后的代码进行编译。

5、函数模板案例char,int,float数据排序

#include 
#include 
#include 
using namespace std;

template  
void MySort(T *a, int len)
{
	int i=0, j=0;
	cout<<"排序之前的数据:"< a[j])
			{
				T temp = a[i];
				a[i] = a[j];
				a[j] = temp;
			}
		}
	}
	
	cout<<"排序之后的数据:"<

c++(24)STL引入:函数模板、类模板_第6张图片

6、类模板

类模板和函数模板很相似,有一点区别。函数模板在调用的时候,可以自动类型推导。但是类模板必须显示的指定类型。

要注意在类模板派生普通类的时候,子类的定义也要指定数据类型。

类模板派生类模板的时候,如果不指定类型。那么需要再使用template关键字,定义模板类型。

#include 
#include 
#include 
using namespace std;

template  //注意没有分号
//类模板
class people
{
public:
	people(T age)
	{
		this->m_Age = age;
	}
public:
	T m_Age;
};

//类模板派生普通类
class student : public people
{
public:
};

//类模板派生类模板
template  //注意没有分号
class adult : public people
{
public:
	//类模板的,类内实现,需要指定模板类people
	adult(T age):people(age)
	{
	}
	
	void show();
};

//类模板的,类外实现,也需要指定模板类people
template  
void adult::show()
{
	//在类模板中使用成员变量,必须要加上this,或者指定类模板作用域,否则编译报错
	// adult::m_Age
	cout<<"i am adult, my age is "<m_Age< a1(30);
	a1.show();
	
	adult a2("30岁");
	a2.show();
	return 0;
}


这里一定要注意,使用类模板的时候,类内定义实现和类外定义实现的区别。其实如果对上面编译器的编译机制理解的比较好的话,就知道在分配内存的时候必要要指定类型,来区分内存大小。

当他是模板的时候,类型不明确。当变量不指定类型或者模板的时候,就更不知道具体的数据大小了。

 7、类模板重载操作符

#include 
#include 
#include 
using namespace std;

//还有一种使用比较多的方式,就是下面这种,先声明模板类,但是不常于友元
template class people;
template void print(people &p);
template ostream & operator<<(ostream &os, people &p);

template
class people
{
public:
	people(T1 name, T2 age)
	{
		this->m_name = name;
		this->m_age = age;
	}
	
	//声明类外友元函数的时候,编译器并不认识T1,T2,所以也要加上template关键字
	//template  //windows下可以直接通过,但是linux下编译不通过
	//friend ostream & operator<<(ostream &os, people &p);
	//template//linux下要重新修改T1 和 T2的名称
	//friend ostream & operator<<(ostream &os, people &p);
	
	//普通友元的使用方法
	//template
	//friend void print(people &p);
public:
	T1 m_name;
	T2 m_age;
};

//类外友元函数,<<操作符重载
template
ostream & operator<<(ostream &os, people &p)
{
	os<<"name:"<
void print(people &p)
{
	cout<<"name:"< a("li4", 18);
	cout<

警告:我们在实际使用模板的时候,千万要避免使用友元!!!

因为一旦友元碰上模板,就会特别麻烦,破坏类的封装性就算了,不同的编译器处理还不同。非常不建议使用!!!

8、linux环境中多文件分离编译

我们先了解一下c++编译机制  还有 模板的实现机制 

c++(24)STL引入:函数模板、类模板_第7张图片

模板定义在编译的时候,不会生成对应的实现函数。只有在调用的时候才会根据数据类型生成对应的具体实现函数。

所以当我们使用make将几个文件放一起编译的时候,只要main文件中使用了people就会报错。因为编译器在main函数中,没有找模板函数的实现,(类模板的实现)。

比如people p("zhang3", 18);的时候,编译器在构造函数定义在当前的文件中没有找到,编译就会认为这个函数在其他的文件中。会让链接器在链接的时候,去找这个函数的具体位置。

源文件  4-template.cpp

#include "4-template.h"

//函数模板  经过两次编译
//并没有生成具体的函数,因为在此文件中并没有具体使用
template
people::people(T1 name, T2 age)
{
	this->m_name = name;
	this->m_age = age;
}

template
void people::show()
{
	cout<<"name:"<m_name<<", age:"<m_age<

 main.cpp

#include 
using namespace std;

//编译器在main函数中,调用people的构造,会由于模板的编译实现机制,而找不到具体数据类型的函数实现。
//会通知链接器在链接的时候 去找实现,而我们的类实现cpp,由于是分开编译的。具体函数实现,是在调用的时候才会生成。
//为了解决这个问题,我们其实就把cpp文件放进来 一起编译就行了


//可以直接放在这里,include
#include "4-template.cpp"

//还有一种方式,就是我们在使用类模板的时候,直接把模板的实现cpp和h文件合成一个,叫做hpp文件
//就相当于将声明和实现  放在一起
//#include "4-template.hpp"

int main(void)
{	
	people p("li4", 18);
	p.show();
	
	return 0;
}

4-template.h

#pragma once
#include 
#include 
#include 
using namespace std;

template
class people
{
public:
	people(T1 name, T2 age);
	void show();
	
public:
	T1 m_name;
	T2 m_age;
};


makefile

TOP := ..
COMM_DIR := .
SRC_DIR := .
INC_DIR := .

APP_TARGET  := template

## Object files that compose the target(s)
COMPILE_FILE := $(SRC_DIR)/4-template \
				$(SRC_DIR)/main

PROJ_FILES := $(foreach obj,$(COMPILE_FILE),$(obj).cpp)
PROJ_OBJS  := $(notdir $(COMPILE_FILE))
PROJ_OBJS  := $(foreach obj,$(PROJ_OBJS),$(obj).o)

## include and lib path in shared object file
INC_PATH += $(INC_DIR)

##rules
CPP := g++

all:$(APP_TARGET)

$(APP_TARGET):
	$(CPP) $(CFLAGS) -c $(PROJ_FILES)
	$(CPP) -o $(APP_TARGET) $(PROJ_OBJS)
	
clean:
	rm -f $(PROJ_OBJS) *.pdb *.map $(APP_TARGET)

上面就是使用模板类,当cpp文件h文件分开编译的时候的解决方案。其实我们在实际工作中,很罕见直接include cpp文件的。当我们使用类模板的时候,会直接把模板的实现cpp和h文件合成一个,叫做hpp文件,就相当于将声明和实现  放在一起。在使用的时候,直接包含这个hpp文件,

这样也方便其他人阅读代码,知道你在这个里面使用了模板
#include "4-template.hpp"

源文件4-template.hpp

#pragma once
#include 
#include 
#include 
using namespace std;

template
class people
{
public:
	people(T1 name, T2 age);
	void show();
	
public:
	T1 m_name;
	T2 m_age;
};

template
people::people(T1 name, T2 age)
{
	this->m_name = name;
	this->m_age = age;
}

template
void people::show()
{
	cout<<"name:"<m_name<<", age:"<m_age<

9、当类模板遇上static关键字

一句话,类模板中的static 变量,归具体类所有。

c++(24)STL引入:函数模板、类模板_第8张图片

 使用方法

c++(24)STL引入:函数模板、类模板_第9张图片

 

p1 p2 p3 共享int a;

pp1  pp2  pp3共享int a;且跟上面的a是两个地址空间 

10、MyArray类模板案例

写一个自定义数组的模板案例。比较简单,测试程序中使用了常规数据类型int,和自定义数据类型people类。这样的案例,看起来,就比较像STL提供的动态数组了。

其中还涉及到c++11中关于对右值取引用的内容,可以看代码的注释。下面是源码,有兴趣的可以跟着我的代码敲一遍,加深关于类模板的理解。

#include 
#include 
#include 
using namespace std;

//people元素
class people
{
public:
	people()
	{
		this->m_name = "无名氏";
		this->m_age = 0;
	}
	
	people(string name, int age)
	{
		this->m_name = name;
		this->m_age = age;
	}
	
	people &operator=(const people &another) //=号操作符重载
	{
		if (this == &another)
		{
			return *this;
		}
	
		this->m_name = another.m_name;
		this->m_age = another.m_age;
		return *this;
	}
	
	//MyArray模板的show使用cout
	friend ostream &operator<<(ostream &os, const people &p);
public:
	string m_name;
	int m_age;
};

ostream &operator<<(ostream &os, const people &p)
{
	os<<"name:"<
class MyArray
{
public:
	MyArray(int nCap)
	{
		cout<<"构造:MyArray(int nCap)..."<m_nCap = nCap;
		this->m_nSize = 0;
		this->pAddr = new T[this->m_nCap];
	}
	
	MyArray(const MyArray &another)//拷贝构造
	{
		cout<<"构造:MyArray(const MyArray &another)..."<m_nSize = another.m_nSize;
		this->m_nCap = another.m_nCap;
		
		this->pAddr = new T[this->m_nCap];
		for (int i=0; im_nSize; i++)
		{
			this->pAddr[i] = another.pAddr[i];
		}			
	}
	
	~MyArray()
	{
		cout<<"析构:~MyArray()..."<pAddr != NULL)
		{
			delete[] this->pAddr;
			this->pAddr = NULL;
			this->m_nSize = 0;
			this->m_nCap = 0;
		}
	}
	
	T& operator[](int nIndex)//[]操作符重载
	{
		return this->pAddr[nIndex];
	}
	
	MyArray operator=(const MyArray &another) //=号操作符重载
	{
		cout<<"=号操作符重载:MyArray &operator=(const MyArray &another)..."<pAddr == another.pAddr)
		{
			return *this;
		}
		
		if (this->pAddr != NULL)
		{
			delete[] this->pAddr;
			this->pAddr = NULL;
			this->m_nSize = 0;
			this->m_nCap = 0;
		}
	
		this->m_nSize = another.m_nSize;
		this->m_nCap = another.m_nCap;
		this->pAddr = new T[this->m_nCap];
		for (int i=0; im_nSize; i++)
		{
			this->pAddr[i] = another.pAddr[i];
		}
		return *this;
	}


	void PushBack(T &data)
	{
		if (this->m_nSize >= this->m_nCap)
			return ;
		
		this->pAddr[this->m_nSize++] = data;
	}

	//这是c++11的新标准,对T &&对右值取引用
	void PushBack(T &&data)
	{
		if (this->m_nSize >= this->m_nCap)
			return ;
		
		this->pAddr[this->m_nSize++] = data;
	}
	
	void show()
	{
		for (int i=0; im_nSize; i++)
		{
			cout<pAddr[i]<<" ";
		}
		cout< arr(20);
	int a=10, b=20, c=30, d=40;
	arr.PushBack(a);
	arr.PushBack(b);
	arr.PushBack(c);
	arr.PushBack(d);
	
	//思考:为什么不能直接arr.PushBack(10);arr.PushBack(20);
	//因为声明时是 void PushBack(T &data);形参是引用,是一个左值,而常量不能作为左值。
	//解决方案:重载 void PushBack(T &&data);   这是c++11的新标准,对T &&对右值取引用
	arr.PushBack(100);
	arr.PushBack(200);
	arr.PushBack(300);
	arr.PushBack(400);
	for (int i=0; i arr1 = arr;
	arr1.show();
	cout<<"-----------------------------"< arr2(10);
	arr2 = arr;  //这里有一个值拷贝动作,会有一份临时变量被构造和析构,因为我们的=号操作符重载是返回值,而非引用
	arr2.show();
	cout<<"-----------------------------"< ap(5);
	ap.PushBack(p1);
	ap.PushBack(p2);
	ap.PushBack(p3);
	ap.show();
	cout<<"-----------------------------"< ap1(5);
	ap1 = ap;
	ap1.show();
	cout<<"-----------------------------"<

执行结果

c++(24)STL引入:函数模板、类模板_第10张图片

你可能感兴趣的:(c++,c++,数据结构,开发语言)