C++:函数————函数重载与函数模板
来源:互联网 发布:千里眼淘宝版下载 编辑:程序博客网 时间:2024/06/05 22:32
一、函数重载
1.概念
C语言中不允许定义多个函数名相同的函数,但是在C++中,只要函数的特征标(即函数的参数列表)不同,即使函数名相同也是可以定义的,这种方式被称为“函数重载”。(C++允许通过函数重载设计一系列的函数——它们完成相同的工作但是使用不同的参数列表。)
2.函数重载的规则
>1:定义时要注明参数的类型:
void print(const char * str);void print(const double value);void print(const long value);
>2.如果调用的函数没有对应的原型的时候,C++不会停止调用,而是尝试自动转换
#include <iostream>#include <string>using namespace std;void print(const char * str);void print(const double value);void print(const long value);int main(){ unsigned int year = 2015; print(year); return 0;}
在这段代码中,year是无符号整型,它没有对应的函数原型,于是系统将其强制转换。
但是不巧的是,它既可以被强制转换为double型,也能被强制转换为long型,因此编译器判定这是:
error: call of overloaded 'print(unsigned int&)' is ambiguous|(二义性的)
但是如果print(double)与print(long)去掉任意一个,编译器将unsigned int提升转换,编译可以通过。
>3.类型的引用和类型本身视为同一种特征标
void print(const long value);void print(const double & value);这种情况下无法共存,因为当存在形如print(5.0)的调用时,编译器不知道到底调用的哪个print,同样是二义性的定义错误。
>4.存在const限定符的特征标
匹配函数时,并不区分const与非const变量
void print1(const int value);void print2(int value);int main(){ const int value1 = 50; int value2 = 30; print1(value1); print1(value2); print2(value1); print2(value2); return 0;}上述代码是可以通过编译的,但是如果const修饰的是指针或者是引用时,上述代码就无法通过。
void print1(const char * value);void print2(char * value);int main(){ const char* value1 = "testone"; char* value2 = "testtwo"; print1(value1); print1(value2); print2(value1); print2(value2); return 0;}
这种情况下编译器会报错,print2(char*)只能与非const类型匹配,但是print1(const char * )既能与const类型匹配也可以和非const类型的匹配。造成这种不同的原因在于,将非const(指针或者引用)值赋值给const是合法的,但是将const值赋给非const的值是非法的(这个地方有点绕,但的确是合乎规范的)。
>5.重载引用参数
类设计和STL中经常用到引用,因此知道不同引用类型的重载很有必要。
二、函数模板
函数的模板是通用函数描述,使用泛型定义一个模板,其中的泛型可以用具体的类型进行替换。下面给出函数模板的创建与使用方法。
1.建立一个模板
template <typename AnyType>void Swap(AnyType &a,AnyType &b){ AnyType temp = a; a = b; b = temp;}这里建立了一个Swap模板,再次强调它不是一个具体的函数,使用这个模板可以交换基本上所有类型的数据。
第一行,声明:
template <typename AnyType>
表示建立一个模板并将类型的名称定义为Anytype,其中template和typename都是关键字必须的。不过更一般的情况下,我们使用下面这种方式。
template<class T>
那么上述代码可改写为:
template <class T>void Swap(T &a,T &b){ T temp = a; a = b; b = temp;}在程序中,可能有不同类型的数据来调用Swap()这个模板,每当具体的数据类型的变量调用该模板时,编译器自动地生成Swap()函数的相应数据类型的版本。
2.函数模板的重载:
同函数重载一样,只要特征标不同即可实现对模板的重载,如Swap()模板,想要对数据进行交换的换,可以重载为以下形式:
template <class T>void Swap(T a[],T b[],int n){ T temp; for(int i = 0 ; i < n ; ++i){ temp = a[i]; a[i] = b[i]; b[i] = temp; }}再次说明,模板的参数可以是明确的数据类型而非泛型,如int n
3.函数模板的显示具体化
之前编写的模板可能无法处理某些类型的数据(比如说比较大小的模板,当比较结构体时可能就会力不从心),我们能想到的是,为特定的类型提供具体化的模板定义,也就是这里所说的显示具体化。当编译器找到与函数调用匹配的具体化定义时,不再寻找模板。
为了更方便的说明,我们举一个例子:
假如说存在一个结构体:
typedef struct{ string name; string ID; int grade;}Student;我们只想交换两个学生的ID数据域,那么可以对template进行如下具体化实现:
template <> void Swap<Student>(Student &a,Student &b){ string temp = a.ID; a.ID = b.ID; b.ID = temp;}其中Swap<Student>中的<Student>是可选的,因为函数的参数类型表明,这是Student类型的一个具体化,所以也可以这样编写:
template <> void Swap(Student &a,Student &b){ string temp = a.ID; a.ID = b.ID; b.ID = temp;}所以,该例的完整程序可如下编写:
#include <iostream>#include <string>using namespace std;typedef struct{ string name; string ID; int grade;}Student;template <class T> void Swap(T &a,T &b);//如同函数一样先声明,不定义,在main函数的后面定义,方便定义多个模板template <> void Swap(Student &a,Student &b);int main(){ Student s1 = {"Zhangsan","100000",90}; Student s2 = {"Lisi","200000",70}; Swap(s1,s2); cout << "s1:" << s1.name << " " << s1.ID << " " << s1.grade << endl; cout << "s2:" << s2.name << " " << s2.ID << " " << s2.grade << endl; return 0;}template <class T>void Swap(T &a,T &b){ T temp = a; a = b; b = temp;}template <>void Swap(Student &a,Student &b){ string temp = a.ID; a.ID = b.ID; b.ID = temp;}PS:更加推荐这种编写方式,简单规范。
需要强调的是,必须首先定义模板才可以对模板进行显式具体化。
4.函数模板的实例化与具体化
为了更好的理解模板,必须理解术语实例化与具体化。
再次强调(因为很重要要说好几遍!):定义模板的时候并不会生成函数的定义,只是提供一个生成函数定义的方案。
编译器使用模板为特定的数据类型生成函数定义时,得到的是模板实例。
这种借由编译器自动生成函数定义的方式,被称为隐式实例化。(因为没有手动的去给定实例化的规则所以叫隐式)
简而言之,模板+自动匹配的具体的数据类型 = 隐式实例化
除了隐式实例化,C++还提供显式实例化,这意味着可以直接命令编译器创建特定的实例。显式实例化的创建规则如下:
template void Swap<Student>(Student &a,Student &b);这个格式同显式具体化的格式十分相似:
template <> void Swap(Student &a,Student &b);//或下行template <> void Swap<Student>(Student &a,Student &b);因此很容易被混淆。
>1.显式实例化与显式具体化的不同
不同之处在于,编译器看到某个具体类型的显式实例化后,将利用模板定义生成Swap()函数的具体类型的版本而不需要为此重新定义Swap()的规则。显式具体化则是为了某些原来模板不能处理的类型重新定义了Swap()的规则。
>2.显式实例化与隐式实例化的不同
那么已经存在隐式实例化的机制下我们为什么要使用显式实例化呢?让编译器自己找不就可以了么?
第一点不同:当程序调用模板函数时,如果存在显式实例化的类型匹配,那么编译器优先遵循显式实例化的规则。因此编译速度稍快一些。
第二点不同:
如果遇到以下情况,那么显式实例化就起到了很大的作用:
template <class T> T Add(T a,T b){ return a + b;}int main(){ int x = 10; double y = 8.2; cout << Add<double>(x,y) << endl; return 0;}这里的模板实际上与函数调用的Add(x,y)是不匹配的,因为x、y一个为int一个为double,但是模板定义的Add中的两个参数要求类型一样(均为泛型T),此时就可以用Add<double>(x,y)的形式将其强制为double类型实例化。
但是如果对Swap()类型做类似的处理时不行的,即:
template <class T> void Swap(T &a,T &b);int main(){ int x = 10; double y = 8.2; cout << Swap<double>(x,y) << endl; return 0;}同样强制生成一个double类型的实例,但是这是没有用处的,编译器如下提示:
error: no matching function for call to 'Swap(int&, double&)'
第一个形参的类型为double,不可以指向int变量x(归根结底是因为使用了引用或者指针,Add的那个实例中用的是值传递)
总结:隐式实例化、显式实例化、显式具体化都称为具体化。它们的相同之处在于,均表示的是使用具体类型的函数定义而不是通用的描述。
那么在程序中,有模板有函数又有重载的时候,到底调用的是哪一个?
优先级:常规函数 > 具体化模板函数 > 常规模板函数。
且,有以下规则:
cout << Add(x,y) << endl;//优先使用常规函数 cout << Add<>(x,y) << endl;//优先使用模板函数 cout << Add<int>(x,y) << endl;//这是要求编译器进行显式实例化____________________________________________________________________________________________________________________________________
- C++:函数————函数重载与函数模板
- 函数的模板2——函数模板重载
- C++ Template学习笔记之函数模板(7)——重载函数模板
- C++ Template学习笔记之函数模板(7)——重载函数模板
- 函数重载与函数模板
- 重载函数与函数模板
- 函数模板与函数重载
- 函数模板与函数重载
- 函数模板——函数重载-多组数求和
- 函数重载与模板
- 函数重载与模板
- C++学习笔记五——函数重载(多态)、函数模板及函数模板重载和完全匹配与最佳匹配
- C++基础篇—函数重载与Extern C
- CPP 6th——运算符重载+函数重载+函数模板
- 函数模板 /函数重载
- 函数重载 函数模板
- C++编程思想学习—函数重载与默认函数
- 函数重载与函数模板的区别
- Swift之GCD(一)
- SLF4J和Logback日志框架
- cocos2dx 中实现再按一次退出效果
- 杭电ACM-The Snail
- 11种经典软件滤波的原理
- C++:函数————函数重载与函数模板
- docker命令之build
- CustomIOS7AlertView 适配 iOS 8
- CentOS集群自动同步时间的一种方法
- Using R — Calling C Code ‘Hello World!’
- Apache (支持IPv6) 安装及常见错误
- log4j:WARN No appenders could be found for logger
- Google 地理信息反解析
- 实现HTTP断点续传下载工具(附源代码)