C++之函数模板

1、什么是模板?模板有什么作用?
模板分为函数模板和类模板。函数模板是对函数功能框架的描述,具体功能由实际传递的参数决定。
有了函数模板,编译器就会根据模板自动生成多个函数名相同,参数列表不同的函数,不需要手动写;
例:求一个矩形面积
当传入的长、宽均是正整数时,我们需要这样写函数:

void Rectangle(int a,int b)
{
   int s;
   s=a*b;
   cout<<"s="<<s<<endl;
}

但如果传入的长、宽都是带小数的时候,我们就需要这样写:

void Rectangle(double a,double b)
{
   double s;
   s=a*b;
   cout<<"s="<<s<<endl;
}

可以发现,这样写实在繁琐,形式都是一样的,还需要写两边。为了解决这个问题,就有了函数模板。
2、函数模板该怎样使用?
函数模板的写法如下:

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

对于上边的程序,我们可以定义这样一个函数模板:

template<typename T>
void Rectangle(T&x,T&y)
{
  T s;
  s=x*y
  cout<<"s"<<s<<endl;
}
T是类型参数,代表传入参数的类型。函数在由模板生成函数时,会自动根据实参的类型对模板中的类型参数进行替换。

3、函数模板和模板函数有什么区别?
有模板实例化的到的具体函数叫做模板函数(实例化:编译器由模板自动替换生成具体函数的过程叫做实例化);
函数模板会在编译期根据使用情况生成对应的函数
模板不编译;
但是模板生成的函数指令会编译;
模板中的语法错误,会在生成对应的指令时候被编译出错误;
函数模板有类型自推的能力,使用函数模板可以不用传模板类型参数;
对于上边的程序:

template<typename T>
void Rectangle(T&x,T&y)
{
  T s;
  s=x*y
  cout<<"s"<<s<<endl;
}

这是函数模板

void Rectangle(int a,int b)
{
   int s;
   s=a*b;
   cout<<"s="<<s<<endl;
}
void Rectangle(double a,double b)
{
   double s;
   s=a*b;
   cout<<"s="<<s<<endl;
}

这是模板函数

下边我们来看一段小代码:

template<typename T>
bool compare(T a, T b)
{
	cout << "template bool compare(T a, T b)" << endl;
	cout << typeid(T).name() << endl;
	return a > b;
}
int main()
{
	compare(10, 20);
	compare(10.2, 20.3);
	//compare(10, 20);
	//compare(10.2, 20.3);
}

在编译器对模板进行实例化时,模板的类型参数并非只能通过传入实参来判断,还可以直接在传参的时候就指明类型参数该实例化为哪种类型。(上边程序屏蔽的部分)。
此处实例化的模板函数是:

bool Compare(double a,double b);

还需要说明的是:在指定类型的时候,可以指定多个类型,如:

compare<doublefloat>(10.2, 20)

即使不指定,编译器也会替换为相应的类型,即:

compare<double>(10.2, 20)//20的类型为int

同样,如果不指定,还是根据实参进行识别的话,也会识别出不同的类型,即:

  compare(10.2, 20);//10.2 为double,20为int

示例:写一个冒泡排序的模板

template<typename T>
void swap1(T& a, T& b)
{
	T tmp = a;
	a = b;
	b = tmp;
}
template<typename T>
void sort(T* arr, int len)
{
	cout << "void sort(T *arr,int len)" << endl;
	cout << typeid(T).name() << endl;
	if (NULL == arr)
	{
		return;
	}
	for (int i = 0; i < len-1; i++)
	{
		for (int j = 0; j < len - i - 1; j++)
		{
			if (!(arr[j] > arr[j + 1]))
			{
				swap1(arr[j], arr[j + 1]);
			}
		}
	}
}

int main()
{
	int arr[] = { 23,45,34,11,67,89,4,3,90,21 };
	double arr[] = { 23,45,34,11,67,89,4,3,90,21 };
	int len = sizeof(arr) / sizeof(arr[0]);
	sort(arr, len);
	return 0;
}

冒泡排序函数模板在编译期会自动生成以下两种函数:

void sort(int *arr,int len);
void sort(double *arr,int len);

4、函数模板的特例化
问题:特例化是什么?为什么要特例化?
答:特例化顾名思义就是函数模板的特殊实例化,它是为了完成一半模板函数不能完成的特殊任务。例:
还是刚才那个代码:

template<typename T>
bool compare(T a, T b)
{
	cout << "template bool compare(T a, T b)" << endl;
	cout << typeid(T).name() << endl;
	return a > b;
}
int main()
{
	if (compare("aaa", "sss"))//会直接比较地址,比较结果不可信
	{
		cout << "aaa > sss" << endl;
	}
	else
	{
		cout << "sss > aaa" << endl;
	}
	进行一般的数值比较是,上边的模板函数就可以了,但如果两个实参都是字符串的话,它们的实参类型就位`const char*`类型。
	众所周知,进行字符串比较时,是按照字符一个一个进行比较的,这样的结果才可信,但是如果此时我们还是调用上边的模板函数来比较的话,它就不是比较的字符串了,它比较的是整个字符串的地址。这样造成的结果就是,左边的永远比右边的大
}

上述代码一眼就能看出来sss大,正确答案应该是sss > aaa,打印结果如下:
C++之函数模板_第1张图片
我们发现,这个结果明显不正确。
在这里插入图片描述
通过观察地址可以发现,aaa的地址比sss大,这就说明了对于字符串这种特殊的实参,我们不能用普通的模板函数来比较,必须另写一个模板函数的特例化来解决这些问题,即:

template<>
bool compare(const char* a, const char* b)
{
	cout << "bool compare(const char* a, const char* b)" << endl;
	return strcmp(a, b) > 0;
}

执行结果如下:
在这里插入图片描述
我们发现结果正确了。

解释:特例化并不是对函数模板进行重载,相反,它也只是函数模板实例化的特殊模板函数。这种特例化的模板函数在写时,需要直接在形参参数列表中写明参数类型,不能再使用类型参数。
特例化分为部分特例化和全部特例化。全部特例化就是上边这种,将两个参数都写明参数类型。部分特例化就是只将一部分的参数写明参数类型,如:

template<T>
bool compare(T a,int b);

5、类模板
还是先明确一个定义,类模板的实例化就是模板类。
类模板,实际上是建立一个通用类,内部的数据成员,成员函数的返回值类型和参数类型不具体说明,用类型参数来代表。当使用类模板定义对象时,系统会根据实参的类型去替换模板中的类型参数,从而实例化出一个模板类,用以实现具体的功能。
其格式如下:

template<typename 类型参数>
class 类名
{
   类成员说明
};

当需要实现具体功能时,就需要建立具体的类模板,如:求矩形面积

template<typename T>
class Rectangle
{ 
publicRectangle(T a,T b)
    { 
        x=a;
        y=b;
    }
    T Area()
    {
        return a*b;
    }
private:
    int x,y;
}
int main()
{
	Rectangle<int>a(10, 20);
}

注意:类模板
类模板不编译
会在编译期根据使用方式,生成对应的类代码
类模板中没有使用到的成员方法不会在编译器生成对应的指令
类模板使用时必须加上模板类型参数,类模板无法自己推导类型参数
为什么类模板的成员方法不能再其它cpp文件中实现?
类模板需要在编译时期将使用到的成员方法生成对应的指令
编译器是指针对单文件的
如果将类模板的成员方法实现在其他文件,编译期使用到该方法的文件不可见,就无法生成对应的指令----就会报无法解析的外部符号

再看一个代码:

template <typename T>
class Arr
{
public:
	Arr(T x,T y)
	{
	    a=x;
	    b=y;
		cout << "Arr()" << endl;
	}
	T Area();
private:
    T a,b;
};

//类模板的成员方法类外实现
template<typename T>
void Arr<T>::Area()
{
     return x*y;
	cout << "void Arr::Area()" << endl;
}

int main()
{
    Arr<int>A(1,2);
    Arr<float>B(2,3);
    Arr<double>C(3,4);
    return 0;
}

上述代码中,类模板进过实例化之后产生了三个模板类,并产生了A、B、C三个对象。
注意:类的成员函数在类外实现时需要在函数定义之前进行模板声明,在成员函数名前写上“类名::”;

你可能感兴趣的:(笔记,c++,1024程序员节,开发语言)