本文最后更新于:2023年8月30日 下午
函数指针
获取函数的地址:函数的名字就是地址
函数指针声明与使用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
| double pam(int);
double (*pf)(int); pf=pam; double (*pf)(int)=pam; auto pf=pam;
pf(123); (*pf)(123); pam(123);
---------------------------------------------------------------------------------------
double f1(int); double f2(int); double f3(int);
double (*pfa[3])(int); pfa[0]=f1; double (*pfa[3])(int)={f1,f2,f3};
---------------------------------------------------------------------------------------
const double *(*(*pd)[3])(const double *,int);
---------------------------------------------------------------------------------------
double pam(int);
typedef double (*p_func)(int);
p_func pf;
|
非静态类成员函数指针
与类的非静态数据成员变量不同,类的非静态成员函数不会在运行时为每个类的实例对象生成一份专有的机器语言代码,类的非静态成员函数在内存中只有一份,即类的非静态成员函数的地址也是唯一的。那么为什么不同的实例调用非静态成员函数的时可以访问到属于实例本身的数据呢?这是因为非静态成员函数默认的第一个参数就是this指针,只是没有明写出来罢了
类的非静态成员函数默认的第一参数就是this
受this指针的影响, 非静态类成员函数指针 的声明和使用与
一般的函数指针(含静态成员函数指针) 都不同
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| //类的非静态成员函数指针 声明、赋值、使用 都跟普通情况不同 //声明时需要添加类名 int (classA::*pFun)(int); //注意类名放置的位置,类名是用来指明this指针的类型的 //赋值时需要添加类名和& pFun=&classA::Fun; //使用时需要加上实例对象 (classAInstance->*pFun)(12345); //注意一个'('、')','*'都不能少,格式就这样死死固定住了; //如果这个语句出现在非静态成员函数中,不要认为classAInstance不写系统会自动加上this;如果你是想调用this的pFun就自己手动加上,不然会报错,
//类的静态成员函数指针 声明、赋值、使用 //声明时 int (*pFun)(int); //赋值时需要添加类名 pFun=classA::Fun; //使用时 pFun(12345); //或 (*pFun)(12345);
|
函数模板
函数模板将类型作为参数传递给函数模板,使编译器生成该类型的函数;又叫通用编程或参数化类型;
由函数模板对应的具体函数称为称为模板实例(instantiation)
隐式实例化、显式实例化、显式具体化统称为具体化(specialization)
类型参数不能有默认值,但是非类型参数可以有默认值
函数模板的声明格式
1 2 3
| template<typename T> void Swap(T &a,T &b);
|
函数模板的定义格式
这只是一个函数模板,并不是函数;函数模板用于告诉编译器如何定义函数;只有在编译时检测到代码中需要用到这个模板,编译器才会依据具体类型按照模板格式生成模板实例,这个过程称为隐式实例化(implicit
instantiation)
编译结束之后机器码中并没有函数模板,只可能存在模板实例的机器码
模板函数的定义可以放在使用到函数模板的函数后面
1 2 3 4 5 6 7 8
| template<typename T> void Swap(T &a,T &b) { T temp; temp=a; a=b; b=temp; }
|
模板函数的使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| //使用时都不用指明类型,编译器会自动检测参数列表的数据类型来确定函数的类型 int main() { int i=10; int j=20; Swap(i,j); //隐式实例化 Swap<>(i,j); //不带参数的显式实例化,确保编译器使用函数模板的实例化版本
double x=1.3; double y=3.6;
Swap(x,y); //隐式实例化 Swap<>(x,y); //不带参数的显式实例化,确保编译器使用函数模板的实例化版本
//疑问: //Swap(i,x); //会怎样?
return 0; }
|
函数模板的重载
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| template<typename T> void Swap(T &a,T &b) { T temp; temp=a; a=b; b=temp; }
template<typename T> void Swap(T *a,T *b,int n) { T temep; for(int i=0;i<n;++i) { temp=a[i]; a[i]=b[i]; b[i]=temp; } }
int main() { int i=10; int j=20; Swap(i,j);
int array1[3]={1,2,3}; int array2[3]={4,5,6};
Swap(array1.array2);
return 0; }
|
函数模板的局限性
1 2 3 4 5 6 7
| template <typename T> void f(T a,T b) { //a=b; //原本是想做内容赋值操作的,但如果T类型是数组指针,那么最终只是a变成了b的指针,a并没有获得b数组的内容;又或者当前T类型并没有'='操作; //a>b; //原本是想做内容比较操作的,但如果T类型是数组指针,那么最终只是比较了a指针和b指针,并没有进行内容比较;又或者当前T类型并没有'>'操作; //以后注意T只处理非数组类型,T*才是用来处理数组类型的; }
|
函数模板的显式具体化(explicit
specialization)
所谓的显式具体化就是手动写一个函数模板的模板实例
函数模板显式具体化的声明格式:
1 2 3 4 5 6 7 8 9 10 11
| template <typename T> void Swap(T &,T &);
template <> void Swap<job>(job &,job &);
template <> void Swap(job &,job &);
|
如果一个名称f同时存在非模板函数、
显式具体化函数、常规模板,将遵循第三代函数使用优先顺序(C++98标准):非模板函数
> 显式具体化函数 > 常规模板
实例化和具体化
- 实例化和具体化是不同的概念:
- 实例化是由编译器自动生成模板实例的过程
- 具体化是手动写出一个模板实例的过程
- 实例化 template 后面没有 <>
- 具体化 template 后面有 <>
显式实例化(explicit
instantiation)的方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| template <typename T> void Swap(T &,T &);
template void Swap<int>(int,int);
...主函数中 int i=10; double x=1.3; Swap<double>(i,x); ...
|
ps:同一个文件中如果同时出现同一类型函数的 显式实例化 和 显式具体化
,将出错
编译器选择使用哪个函数版本
重载解析(overloading
resolution):面对函数重载、函数模板、函数模板重载时,C++决定使用哪一个函数的过程
详细解释这个策略需要将近一章的篇幅= =,下面只讲大致步骤
函数调用的步骤(假设函数调用为may('B'))
- 先找出所有名称为may的函数
- 再从这些函数中找出可以只接受一个参数'B'的may函数(实参类型可强转为形参类型的也算)
- 再从这些函数中选择一个最佳函数,优先级为: 常规函数完全匹配 >
模板函数完全匹配 >
提升转换(例:char和short自动转为int,float自动转为double) >
标准转换(例:int转换为char,long转换为double) > 用户定义的转换
完全匹配:实参类型与形参类型完全等价,等价表如下所示:
ps:倒数第二列是不是错了???
- 如果使用以上优先级选择之后还是剩下一个以上的完全匹配函数,那么将要使用以下规则继续筛选:
- 非模板函数优先于模板函数
- 非const的指针和引用的实参优先与非const的指针和引用的形参配对
- 如果剩下的都是完全匹配的模板函数,则使用函数模板的部分排序规则(partial
ordering
rules)(C++98特性,主要是处理指针与非指针的问题)来选择最具体的
如果使用以上规则选择之后还是剩下一个以上的候选函数,那么编译器将报错;
例子:调用函数map('B');
1 |
void may(int); |
T |
T |
4 |
2 |
void may(double,double); |
T |
F |
|
3 |
float may(float,float = 3); |
T |
T |
5 |
4 |
void may(char); |
T |
T |
1 |
5 |
char * may(const char *); |
T |
F |
|
6 |
char may(const char &); |
T |
T |
1 |
7 |
template< typename T > void
may(const T &); |
T |
T |
3 |
8 |
template< typename T > void may(T
*); |
T |
F |
|
ps:整数类型是不会隐式转换成指针的
以上只是介绍了单参函数调用的函数原型匹配问题,若是有多个参数,情况将非常复杂;这里不再介绍
模板函数的发展
问题一
在C++98标准下的函数模板存在着很多不完美的地方,有时候由于不知道具体的类型是什么,导致无法正常编写函数模板定义,例:
1 2 3 4 5 6 7
| template<typename T1,typename T2> void ft(T1 x,T2 y) { ... ?type? xpy = x + y; ... }
|
为了解决上面的问题,C++11引入了一种新的声明数据类型方式,使用关键字decltype
decltype是在运行时确定变量类型的
decltype确定类型的方式,满足以下哪个形式就用哪个形式的判定方法
1 2 3 4 5 6 7 8 9 10 11 12 13
| ... int x; decltype{x} y;
int j = 3; int &k = j; int &n = j; decltype(j+6) i1; decltype(100L) i2; decltype(k+n) i3; ...
|
1 2 3 4 5 6
| ... long indeed(int); decltype{indeed(3)} y; ...
|
1 2 3 4 5 6 7 8 9 10 11 12
| //形式三: ... double xx = 4.4; decltype{(xx)} y; //判定方法:y的类型将为{}中变量类型的引用;这里y的类型将会是double & //注意:这里的括号不能省略且括号中必须是变量(左值)
//这里引入一个和函数模板话题不沾边的小概念: //xx = 98.6; //(xx) = 98.6; //以上的两条表达式的作用是一样的,就是赋值,无差别 ...
|
于是上面的问题就可以解决:
1 2 3 4 5 6 7 8 9 10 11
| template<typename T1,typename T2> void ft(T1 x,T2 y) { ... decltype{x + y} xpy = x + y; ...
typrdef decltype{x + y} xpytype; }
|
问题二
但是即使有了decltype还是存在问题,有时函数返回类型不能确定,例:
1 2 3 4 5 6
| template<typename T1,typename T2> ?type? gt(T1 x,T2 y) //返回类型无法确定,无法将?type?改为decltype{x + y},因为那时x和y还没有声明 { ''' return x + y; }
|
为了解决上面的问题,C++11引入了一个新语法后置返回类型,格式为:
1 2
| auto h(int x,float y) -> double;
|