C++内联函数
内联函数是一种用空间换时间的技术,是C++提高程序运行速度做的改进。运行程序时操作系统将指令载入计算机内存中,并逐条执行这些指令,遇到循环或分支时向前或向后跳转到特定的地址(每条指令都有特定的内存地址)。常规函数也是如此,在调用常规函数时立即存储该指令的地址,并跳转到函数的地址,在函数执行结束后返回到跳转之前的地址继续往下执行。来回的跳跃并记录跳跃位置需要花费一定的开销。
与常规函数不同,如果使用内联函数,编译器会使用相应的函数代码替换函数调用,此时程序无需跳到另一个位置执行代码,再跳回来。因此内联函数运行速度比常规函数快。但相应的需要占用更多的内存,所以内联函数适合代码量较小的函数,且不能是递归函数。
1 inline double square(double x) { return x * x; }
inlien是C++新增的特性,C使用预处理语句#difine来提供宏,以此来达到内联函数的功能。但是宏是单纯的文本替换,不能按值传递。
引用变量
引用变量是C++新增的一种复合类型。引用变量的主要作用是作为函数的形参,此时函数将使用原始数据,而不是副本。提高函数处理大型数据结构的效率(常用于结构、类)。引用和指针很像,表达式 b 和 *p 都可以与 a 互换, &b 和 p 可以和 &a 互换。但在声明引用时必须初始化,指针可以先声明,后赋值。
1 int a = 10; 2 int& b = a; //声明int类型引用,并将b作为a的别名, int* const b = &a; 3 int* p = &a;
使用引用作为函数参数时,函数参数中的变量名成为调用程序中的变量别名。按引用传递可以让被调用函数直接访问调用函数中的原始数据,而不是其副本。函数调用使用实参初始化形参。如果想使用引用参数而又不想对原始数据造成影响,则使用常量引用。
1 double refcube(const double& x);
如果传递的实参与形参类型不匹配,但是可以转换为正确的类型,或者实参的类型正确,但不是左值。C++将生成临时变量(前提形参为const引用),将实参的值传递给临时变量,并将const引用指向临时变量。且临时变量只在该函数调用期间存在。因为如果是使用参数const引用的函数,表明该函数不对原始数据进行修改,而是使用,此时生成临时变量不会造成不利的影响。
传统的返回机制与按值传递参数类似,计算 return 后面表达式的值,概念上来说,这个值被复制到一个临时位置,然后调用程序使用这个值(编译器可能会优化)。返回引用时直接复制,效率更高。同时要避免返回临时变量的引用,简单的做法是返回作为参数传递给函数的引用。或者使用 new 来分配新的存储空间。
将返回类型声明为 const 引用可以避免将函数调用作为左值。
当引用的对象是类,且该类的派生类采用公有继承时,基类的引用可以指向基类或派生类对象(指针同理)。如果数据对象时数组,则使用指针是唯一的选择
默认参数
通过函数原型设置函数参数默认值,同时必须从右向左提供默认值,中间不能跳过任何参数。
1 char* left(const char* str, int n = 1);
函数重载
默认参数使得可以使用不同数目的参数调用同一个函数,函数重载允许使用多个同名的函数——完成相同工作,但使用不同的参数列表。通过上下文确定要使用的函数重载版本。要定义多个同名的函数,它们的特征标(参数数目、参数类型)必须不同,变量名无关紧要。
如果传递的参数列表不匹配任何函数原型,C++将尝试使用标准类型转换强制进行匹配,此时若有多个函数原型可以转换匹配,C++将拒绝这中函数调用,并将其视为错误。同时,C++将类型和类型引用视为同一个特征标。匹配函数时,不区分const和非const变量。
1 double cube(double x); 2 double cube(double& x); 3 4 count << cube(x); //同时匹配 double x 原型和 double& x 原型
C++如何跟踪每个重载函数呢?C++对函数的编码方式不同,C++编译器把函数原型中的形参对函数名进行修饰,以此来区别函数名相同的函数。修饰时使用的约定根据编译器而异。
函数模板
使用泛型来定义函数,泛型可用具体的类型替换,将类型作为参数传递给模板,编译器根据模板和传递的参数生成相关的函数定义。注意函数模板不是函数。
template告诉编译器要定义一个模板,后面跟着一对尖括号代表模板参数列表,typename(或class)指出 T 是一个接收类型的类型参数,这里的 T 起着占位符的作用,如果传给 T 的类型为 int, 则所有 T 替换为 int。模板不创建任何函数,只是告诉编译器如何定义函数。
在第 11 行代码中,编译器会检查使用的参数类型,并生成相应的函数,此处生成 int 版本的Swap函数。注意函数模板不能缩短可执行程序,而且模板的定义放在头文件中。
1 template <typename T> 2 void Swap(T& a, T& b) { 3 T temp; 4 temp = a; 5 a = b; 6 b = temp; 7 } 8 9 10 int i = 10, j = 20; 11 Swap(i, j);
当然模板也支持重载,模板参数列表中也并非必须为类型参数,如下面第四个模板声明。
1 template <class T> 2 void Swap(T& a, T& b); 3 4 template <class T> 5 void Swap(T& a, T& b, T& c); 6 7 template <typename T> 8 void Swap(T* a, T* b); 9 10 template <typename T> 11 void Swap(T a[], T b[], int n);
模板也有一些局限性,可能无法处理某些类型。如果 T 为数组,则下面模板中的赋值、if条件语句、T c = a*b语句都不成立。解决的一种办法是为特定类型提供具体化的模板定义。
1 template<typename T> 2 void func(T a, T b) { 3 a = b; 4 if (a > b) { 5 ... 6 } 7 8 T c = a * b; 9 }
提供具体化模板函数的定义称为显示具体化。对于给定的函数名,可以有非模板函数、模板函数和具体化模板函数以及函数的重载版本。显示具体化模板函数的原型和定义以 template<> 开头,通过名称指出类型。下面分别为非模板函数、模板函数、具体化模板函数,编译器在选择原型时,优先级为:非模板函数 > 显示具体化模板函数 > 模板函数
1 struct job { 2 char name[40]; 3 double salary; 4 int floor; 5 }; 6 7 void Swap(job a, job b); 8 9 template<typename T> 10 void Swap(T&, T&); 11 12 template<> void Swap<job>(job&, job&); //Swap<job>(job&, job&)中的<job> 可选,因为函数的参数类型已经表明这是 job 的一个具体化
代码中包含模板本身不会生成函数定义,编译器使用模板为特定类型生成函数定义时,得到模板实例。模板不是函数定义,但是使用 int的模板实例是函数定义(隐式实例化)。显式具体化和显式实例化的区别在于,显式具体化声明在template后包含<>, 而显式实例化没有。可以在文件中使用函数来创建显式实例化,如 std::cout << Swap<double>(a, b) << std::endl; 。在同一个文件中,同一类型显式实例化和显示具体化不能同时存在。
对于函数重载、函数模板和函数模板重载,C++通过重载解析来确定为函数调用哪个定义。首先,编译器创建候选函数列表,从候选函数列表创建可行的函数列表,再从可行的函数列表中选择最佳的可行函数。最佳到最差的顺序:完全匹配 > 提升转化 > 标准转化 > 用户自定义转化。如果有多个匹配的原型,则编译器可能无法完成重载解析的过程。但有时候,即使两个函数都完全匹配,仍然可以完成重载解析,比如 const 和 非const 之间的区别(只适用于指针和引用指向的数据,如下所示)。
1 void func(int); 2 void func(const int); //编译器报错,出现二义性错误 3 4 void func(int&); 5 void func(const int&); //const 和 非const的区别只适用于指针和引用
术语“最具体”并不一定意味着显示具体化,而是指编译器推断使用哪种类型时,执行的转换最少。
1 template<class T> void recycle(T t); #1 2 template<class T> void recycle(T* t); #2 3 4 recycle(&link); //recycle(&link)调用会和 #2 模板匹配,因为在生成过程中,它需要进行的转换更少
在使用模板时,可能会出现变量的类型不确定的情况。C++11新增关键字decltype可以解决这种情况。
1 template<class T1, class T2> 2 void ft(T1 x, T2 y) { 3 ... 4 ?xpy? = x + y; //变量xpy的类型是? 解决办法是使用C++11新增的关键字decltype 5 //decltype(x + y) xpy = x + y; 6 ... 7 }
decltype的处理实际上更复杂一些。
1 /* 2 decltype(expression) var; 3 */ 4 5 //如果exression是一个没有用括号括起的标识符。 6 double x = 5.5; 7 double y = 7.9; 8 double& rx = x; 9 const double* pd; 10 11 decltype(x) w; // w的类型为double 12 decltype(rx) u = y; // u的类型为double& 13 decltype(pd) v; // v的类型为const double* 14 15 //如果expression是一个函数调用 16 long indeed(int); //声明indeed函数 17 decltype(indeed(3)) m; //m的类型为indeed函数的返回类型(long),实际上编译器不会调用函数,只是会查看函数的原型 18 19 //如果expression是用括号括起的标识符且是一个左值,则var为指向其类型的引用 20 double xx = 4.4; 21 decltype ((xx)) r2 = xx; //r2的类型为double& 22 decltype (xx) w = xx; //w的类型为double 23 24 //如果前面的条件都不满足,var的类型与expression的类型相同 25 int j = 3; 26 int& k = j; 27 int& n = j; 28 decltype(j + 6) i1; //i1的类型为int 29 decltype(100L) i2; //i2的类型为long 30 decltype(k + n) i3; //i3的类型为int, k和n虽然为引用,但是表达式 k+n 是两个int类型的和
解决了函数模板中函数体的变量类型问题后,还有函数模板返回类型的问题。如下所示,无法直接使用 decltype(x + y),因为此时还未声明参数x和y,必须在声明参数x,y后才能使用decltype。
1 template<class T1, class T2> 2 ?type? func(T1 x, T2 y) { //函数的返回类型是? 3 return x + y; 4 }
解决方法是使用后置返回类型。此时decltyep在参数声明后面,x和y位于作用域内,可以使用它们。
1 double h(int x, int y); 2 auto h(int x, int y) -> double; //后置返回类型,auto是一个占位符 3 4 template<class T> 5 auto func(T x, T y) -> decltype(x + y) { 6 return x + y; 7 }
标签:const,函数,int,double,笔记,C++,类型,探幽,模板 From: https://www.cnblogs.com/owmt/p/17861175.html