本章内容包括:

  • 内联函数
  • 引用变量
  • 如何按引用传递函数参数
  • 默认 参数
  • 函数重载
  • 函数模板
  • 函数模板具体化

8.1 C++内联函数

内联函数是C++为提高程序运行速度所做的一项改进。常规函数和内联函数之间的主要区别不在于编写方式,而在于C++编译器如何将它们组合到程序中。要了解内联函数与常规函数之间的区别,必须深入到程序内部。

编译过程的最终产品是可执行程序——由一组机器语言指令组成。运行程序时,操作系统将这些指令载入到计算机内存中,因此每条指令都有特定的内存地址。计算机随后将逐步执行这些指令。有时(如有循环或分支语句时),将跳过一些指令,向前或向后跳到特定地址。常规函数调用也使程序跳到另一个地址(函数的地址),并在函数结束时返回。更详细的介绍这一过程的典型实现:

执行到函数调用指令时,程序将在函数调用后立即执行该指令的内存地址,并将函数复制到堆栈,跳到标记函数起点的内存单元,执行函数代码,然后跳回到地址被保存的指令处。来回跳跃并记录跳跃位置意味着以前使用函数时,需要一定的开销。

C++ 内联函数提供了另一种选择。内联函数的编译代码与其他程序代码“内联”起来了。也就是说,编译器将使用相应的函数代码替换函数调用。对于内联代码,程序无需跳到另一个位置处执行代码,再调回来。因此,内联函数的运行速度比常规函数稍快,但代价是需要占用更多内存。如果程序在10个不同的地方调用同一个内联函数,则该程序将包含该函数代码10个副本。

应有选择的使用内联函数,如果执行代码的时间比处理函数调用机制的时间长,则节省的时间将只占整个过程很小一部分。如果代码执行的时间很短,则内联调用就可以节省非内联调用使用的大部分时间。另一方面,由于这个过程相当快,因此尽管节省了该过程的大部分时间,但节省的时间绝对值并不大,除非该函数经常被调用。

内联函数的特性,

  • 在函数声明前加上inline;
  • 在函数定义前加上关键字inline。

通常的做法是省略原型,将整个定义(即函数头和所有函数代码)放在本应提供原型的地方。

程序员请求将函数作为内联函数时,编译器并不一定会满足这种请求。它可能认为该函数过大或注意到函数调用了自己(内联函数不能递归),因此不将其作为内联函数;而有些编译器没有启用或实现这种特性。

#include <iostream>inline double square(double x)
{return x*x;
}int main()
{using namespace std;double a,b;double c =13.0;a=square(5.0);b=square(4.5+7.5); //can pass expressionscout <<"a= "<<a<<",b="<<b<<endl;cout <<"c= "<< c <<",c squared = "<< square(c++)<<endl;cout <<"Now c= "<<c<<endl;return 0;
}

程序输出如下,输出表明,内联函数和常规函数一样,也是按值来传递参数的如果参数为表达式,如4.5+7.5,则函数将传递表达式的值。

内联与宏

inline工具是C++新增的特性。C语言使用预处理器语句#define来提供宏——内联函数的原始实现。

如下,并不是通过传递参数实现,而是通过文本替换来实现的,X 是“参数”的符号标记。只有第一个a能正常工作。

8.2 引用变量

8.2.1 创建引用变量

前面讲过,C和C++使用&符号来指示变量的地址。C++给&符号赋予了另一个含义,将其用来声明引用。例如将rodents作为rats变量的别名,你可以这样做:

int rats;
int & rodents =rats;

其中,&不是地址运算符,而是类型标识符的一部分。就像声明中的char* 指的是指向char的指针一样, int &指的是指向int的引用。

引用有些类似于指针,但是还是不同于指针,除了表示法不同外,还有其他的差别,例如必须在声明引用时将其初始化,而不能像指针那样,先声明,再赋值:

int rats =101;
int & rodents =rats; //rodents a reference
int * prats =&rats; //prats a pointerint rat;
int & rodent;
rodent = rat;  //No, you can't do this

引用更接近const指针,必须在创建时进行初始化,一旦与某个变量关联起来,就将一直效忠于它。

8.2.2 将引用用作函数参数

引用经常被用作函数参数,使得函数中的变量名成为调用程序中的变量的别名。这种传递参数的方法称为按引用传递。按引用传递允许被调用的函数能够访问调用函数中的变量。C++新增的这项特性是对C语言的超越,C语言只能按值传递。按值传递导致被调用函数使用调用程序的值的拷贝。当然,C语言也允许避开按值传递的限制,采用按指针传递的方式。

#include <iostream>using namespace std;
void swapr(int &a, int &b);  // a,b are aliases for ints
void swapp(int *p, int *q);  // p,q are addresses of ints
void swapv(int a, int b);   // a,b are new variablesvoid swapr(int &a, int &b)
{int temp;temp=a;a=b;b=temp;
}
void swapp(int *p, int *q)
{int temp;temp=*p;*p=*q;*q=temp;
}
void swapv(int a, int b)
{int temp;temp=a;a=b;b=temp;
}int main()
{int wallet1 =300;int wallet2 =350;cout<<"wallet1 = $"<<wallet1<<endl;cout<<"wallet2 = $"<<wallet2<<endl;cout<<"using references to swap contents:"<<endl;swapr(wallet1,wallet2);cout<<"wallet1 = $"<<wallet1<<endl;cout<<"wallet2 = $"<<wallet2<<endl;cout<<"using pointers to swap contents:"<<endl;swapp(&wallet1,&wallet2);cout<<"wallet1 = $"<<wallet1<<endl;cout<<"wallet2 = $"<<wallet2<<endl;cout<<"Trying to use passing by value:"<<endl;swapv(wallet1,wallet2);cout<<"wallet1 = $"<<wallet1<<endl;cout<<"wallet2 = $"<<wallet2<<endl;return 0;}

如上是按引用,指针,值作为参数传递的方式,引用和指针都成功地交换了两个钱夹的内容,而按值传递的方法没能完成这项任务。

8.2.3 引用的属性和特别之处

引用传递时候,不允许引用函数内改变引用地址的值。

#include<iostream>
using namespace std;double cube(double a);
double refcube(double &ra);double cube(double a)
{a *=a*a;return a;
}
double refcube(double &ra)
{ra *=ra *ra;return ra;
}int main()
{double x =3.0;cout<<cube(x)<<" = cube of "<<x<<endl;cout<<refcube(x)<<" = cube of "<<x<<endl;return 0;
}

临时变量、引用参数和const

如果实参与引用参数不匹配,C++将生成临时变量。当前,仅当参数为const引用时,C++才允许这样做。

尽可能的使用const

  • 使用const可以避免无意中修改数据的编程错误;
  • 使用const使函数能够处理const和非const实参,否则只能接受非const数据;
  • 使用const引用使函数能够正确生成并使用临时变量

8.2.4 将引用用于结构

引用非常适合用于结构和类。使用结构引用参数的方式与使用基本变量引用相同,只需要在声明结构参数时使用引用运算符&即可。

struct free_throws
{std::string name;int made;int attempts;float percent;} void set_pc(free_throws &ft); //use a reference to a structure

返回引用时,要避免返回函数终止时不再存在的内存单元引用。

8.2.5 将引用用于类对象

将类对象传递给函数时,C++通常的做法时使用引用。

8.2.6 对象、继承和引用

ostream和ofstream类凸显了一个有趣属性,ofstream对象可以使用ostream类的方法,这使得文件输入/输出的格式与控制台输入/输出相同。使得能够将特性从一个类传递给另一个类的语言特性称为继承。ostream是基类,ofstream是派生类。派生类继承了基类的方法。

继承的另一个特征是, 基类引用可以指向派生类对象,而无需进行强制类型转换。这种特征的一个实际结果是,可以定义一个接受基类引用作为参数的函数,调用该函数时,可以将基类对象作为参数,也可以将派生类对象作为参数。

8.2.7 何时引用参数

使用引用参数的主要原因有两个。

  • 程序员能够修改调用函数中的数据对象
  • 通过传递引用而不是整个数据对象,可以提高程序的运行速度

当数据对象较大时(如结构和类对象),第二个原因最重要。这些也是使用指针参数的原因。因为引用参数实际上是基于指针的代码的另一个接口。

对于使用传递的值而不作修改的函数。

  • 如果数据对象很小,如内置数据类型或小型结构,则按值传递
  • 如果数据对象是数组,则使用指针,因为这是唯一的选择,并将指针声明为指向const的指针。
  • 如果数据对象是较大的结构,则使用const指针或const引用,以提高程序的效率。这样可以节省复制结构所需的时间和空间。
  • 如果数据对象是类对象,则使用const引用。类设计的语义常常要求使用引用,这是C++新增这项特性的主要原因。因此,传递类对象的标准方式是按引用传递。

对于修改调用函数中数据的函数:

  • 如果数据对象是内置数据类型,则使用指针。
  • 如果数据对象是数组,则只能使用指针。
  • 如果数据对象是结构,则使用引用或指针。
  • 如果数据对象是类对象,则使用引用。

8.3 默认参数

对于带参数列表的函数,必须从右向左添加默认值。也就是说,要为某个参数设置默认值,则必须为它右边的所有参数提供默认值。

实参按从左到右顺序依次被赋给相应的形参,而不能跳过任何参数。

8.4 函数重载

函数多态是C++在C语言的基础上新增的功能。默认参数能让您使用不同数目的参数调用同一个函数,而函数多态(函数重载)让您能够使用多个同名的函数。

函数重载的关键是函数的参数列表——也称为函数特征标。如果两个函数的参数数目和类型相同,同时参数的排列顺序也相同,则它们的特征标相同,而变量名是无关紧要的。C++允许定义名称相同的函数,条件是它们的特征标不同。如果参数数目或参数类型不同,则特征标也不同。

void print(const char *str, int width); // #1
void print(double d, int width);        // #2
void print(long l, int width);          // #3
void print(int i, int width);           // #4
void print(const char *str);            // #5print("Pancakes",15); // #1
print("Syrup");       // #2
print(1999.0,10);     // #3
print(1999, 12);      // #4
print(1999L,15);      // #5

是特征标,而不是函数类型使得可以对函数进行重载。例如,下面的两个声明是互斥的:

long gronk(int n, float m);
double gronk(int n, float m);  //hence not allowedlong gronk(int n, float m);
double gronk(float n, float m);  //hence allowed

8.4.1 何时使用函数重载

虽然函数重载很吸引人,但也不要滥用。仅当函数基本上执行相同的任务,但使用不同形式的数据时,才应采用函数重载。另外您可能还想知道,是否可以通过使用默认参数来实现同样的目的。例如,可以用两个重载函数来代替面向字符串的left()函数:

char * left(const char *str, unsigned n);
char * left(const char *str);

使用一个带默认参数的函数要简单些。只需编写一个函数(而不是两个函数),程序也只需为一个函数(而不是两个)请求内存;需要修改函数时,只需要修改一个。然而,如果需使用不同类型的参数,则默认参数不管用了,在这种情况下,应该使用函数重载。

8.5 函数模板

现在的C++编译器实现了C++新增的一项特性——函数模板。函数模板是通用的函数描述,也就是说它们使用泛型来定义函数,其中的泛型可用具体的类型(如int,double)替换。由于类型是用参数表示的,因此模板特性有时也被称为参数化类型。

之前定义了一个交换两个int值得函数,假设要交换两个double值,则一种方法是复制原来的代码,并用double替换所有int。但是这种方法容易出错,如用全局查找和替换,将double替换int,可能将:

int x;
short interval;

转换为:

double x;
short doubleerval; //unintended change of variable name

C ++的函数模板功能能自动完成这一过程,可以节省时间,且更可靠。

函数模板允许以任意类型的方式来定义函数。例如,可以建立一个交换模板:

template <typename AnyType>
void Swap(AnyType &a, AnyType &b)
{AnyType temp;temp =a;a=b;b=temp;
}

第一行指出,要建立一个模板,并将类型命名为AnyType。关键字template和typename是必需的,除非可以使用关键字class代替typename。另外,必须使用尖括号。类型名可以任意选择(这里为AnyType)。只要遵守C++命名规则即可,许多程序员都使用简单的名称,如T。余下的代码描述了交换两个AnyType值得算法。模板并不创建任何函数,而只是告诉编译器如何定义函数。需要交换int得函数时,编译器将按模板模式创建这样得函数,并用double代替AnyType。

在标准C++98添加关键字typename之前,C++使用关键字class来创建模板。也即是说,可以这样编写模板定义,typename关键字使得参数AnyType表示类型这一点更为明显。

template <class AnyType>
void Swap(AnyType &a, AnyType &b)
{AnyType temp;temp =a;a=b;b=temp;
}

8.5.1 重载的模板

需要多个对不同类型使用同一算法的函数时,可使用模板。然而,并非所有的类型都使用相同的算法。为满足这种需求,可以像重载常规函数定义那样重载模板定义。和常规重载一样,被重载的模板的函数特征标必须不同。并非所有的模板参数都必须时模板参数类型。

#include <iostream>
using namespace std;
template <typename T>
void Swap(T &a, T &b);template <typename T>
void Swap(T *a, T *b,int n);void Show(int a[]);
const int Lim=8;int main()
{   int i=10, j=20;cout<<"i,j = "<<i<<", "<<j<<endl;cout <<"Using compiler-generated int swapper:"<<endl;Swap(i,j);cout<<"Now i,j="<<i<<", "<<j<<endl;int d1[Lim]={0,7,0,4,1,7,7,6};int d2[Lim]={0,7,2,0,1,9,6,9};cout<<"Original arrays:"<<endl;Show(d1);Show(d2);Swap(d1,d2,Lim);cout<<"Swapped arrays:"<<endl;Show(d1);Show(d2);return 0;
}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 temp;for(int i=0;i<n;i++){temp=a[i];a[i]=b[i];b[i]=temp;}
}void Show(int a[])
{cout<<a[0]<<a[1]<<"/";cout<<a[2]<<a[3]<<"/";for(int i=4;i<Lim;i++){cout<<a[i];}cout<<endl;} 

8.5.2 模板的局限性

假设有如下模板函数:

template <class T>
void f(T a, T b)
{...}

通常,代码假定可执行哪些操作。例如,下面代码假定定义了赋值,但如果T为数组,这种假设将不成立:

a = b;

同样,下面语句假设定义了 <,但如果T为结构,该假设便不成立

if (a > b)

另外,为数组名定义了运算符 >, 但由于数组名为地址,因此它比较的是数组的地址,而这不是你所希望的。下面的语句假定为类型T定义了乘法运算符,但如果T为数组,指针或结构,这种假设便不成立:

T c= a*b

总之,编写的模板函数很可能无法处理某些类型。另一方面,有时候通用化是有意义的,但C++语法不允许这样做。例如,将两个包含位置坐标的结构相加是有意义的,虽然没有为结构定义运算符+。一种解决方案是,C++允许您重载运算符+,以便能够将其用于特定的结构或类。另一种解决方案是,为特定类型提供具体化的模板定义。

8.5.3 显式具体化

8.5.4 实例化和具体化

8.5.5 编译器选择使用哪个函数版本

8.5.6 模板函数的发展

在C++发展早期,大多数人都没有想到模板函数和模板类会有这么强大而有用,但是聪明而专注的程序员挑战模板技术的极限,阐述了各种可能性。根据熟悉模板的程序员提供的反馈,C++98标准做了相应的修改,并添加了标准模板库。

1,什么是类型

template <class T1, class T2>
void ft(T1 x, T2 y)
{...?type? xpy =x+y;...
}

在C++中,编写模板函数时,一个问题是并非总能知道应在声明中使用哪种类型。

2,关键字decltype(C ++11)

c++11新增关键字decltype提供了解决方案。可以使用如下代码:

int x;
decltype(x) y; //make y the same type as x

所以可以这样修复前面的模板函数ft()

template <class T1, class T2>
void ft(T1 x, T2 y)
{...decltype(x+y)  xpy =x+y;...
}

3,另一种函数声明语法(C++11后置返回类型)

有一个相关的问题是decltype本身无法解决的。请看下面不完整模板函数:

template <class T1, class T2>
?type? gt(T1 x, T2 y)
{...return x+y;
}

同样无法预先知道将x和y相加得到的类型。C++新增了一种声明和定义函数的语法。下面使用内置类型来说明这种语法的工作原。对于下面的原型:

double h(int x, float y);

使用新增的语法可编写成这样:

auto h(int x, float y) ->double;

这将返回类型移到了参数声明后面。 ->double被称为后置返回类型。其中auto是一个占位符,表示后置返回类型提供的类型,这是C++11给auto新增的一个角色。这种语法也可以用于函数定义:

auto h(int x, float y) ->double
{/* function body*/
};

8.6 总结

第 8 章 函数探幽相关推荐

  1. 《C++ Primer Plus》第8章 函数探幽 学习笔记

    C++ 扩展了 C 语言的函数功能.通过将 inline 关键字用于函数定义,并在首次调用该函数前提供其函数定义,可以使得 C++ 编译器将该函数视为内联函数.也就是说,编译器不是让程序跳到独立的代码 ...

  2. 《C++ Primer Plus 6th》读书笔记 - 第8章 函数探幽

    1. 摘录 默认参数指的是当函数调用中省略了实参时自动使用的一个值. 默认参数并非编程方面的重大突破,而只是提供了一种便捷的方式.使用默认参数,可以减少要定义的析构函数.方法以及方法重载的数量. 试图 ...

  3. c语言函数与编译预处理教学视频,C语言课程第6章 函数及编译预处理.ppt

    C语言课程第6章 函数及编译预处理 6.1模块化程序设计与函数 在设计较复杂的程序时,我们一般采用的方法是:把问题分成几个部分,每部分又可分成更细的若干小部分,逐步细化,直至分解成很容易求解的小问题. ...

  4. Python 精要参考(第二版) 第六章 函数与函数编程

    1. 第六章 函数与函数编程 为便于代码维护,绝大多数子程序都被分解并重新组织为函数以使代码模块化. 在 Python中定义一个函数很简单,Python从其它函数编程语言中借鉴了很多有用的思路用来简化 ...

  5. Swift 1.1语言第7章 函数和闭包

    Swift 1.1语言第7章  函数和闭包 在编程中,随着处理问题的越来越复杂,代码量飞速增加.其中,大量的代码往往相互重复或者近似重复.如果不采有效方式加以解决,代码将很难维护.为了解决这个问题,人 ...

  6. 【数理知识】《数值分析》李庆扬老师-第3章-函数逼近与快速傅里叶变换

    第2章 回到目录 第4章 第3章-函数逼近与快速傅里叶变换 3.1 函数逼近的基本概念 3.2 正交多项式 3.3 最佳平方逼近 3.4 曲线拟合的最小二乘法 3.5 有利逼近 3.6 三角多项式逼近 ...

  7. 《BOOST程序库完全开发指南》 第11章 函数与回调

    第11章  函数回调 #include <iostream> #include <boost/assign.hpp> #include <boost/ref.hpp> ...

  8. python编程设计大学ppt_Python程序设计-清华大学出版社-董付国第5章-函数的设计和使用PPT...

    PPT内容 这是Python程序设计-清华大学出版社-董付国第5章-函数的设计和使用PPT下载,主要介绍了函数定义:斐波那契数列:形参与实参:参数类型:默认值参数:关键参数:可变长度参数:参数传递的序 ...

  9. c语言程序设计函数6,C语言程序设计》第6章函数-XiamenUniversity.PDF

    厦门大学非计算机专业本科生公共课 (2012-2013第2学期) <C语言程序设计> 第6章 函数 林子雨 厦门大学计算机科学系 E-mail: ziyulin@ 个人主页:/linziy ...

最新文章

  1. torch.set_num_threads
  2. 适配器模式(Adapter)和外观模式(Facade)
  3. oracle 根据spid查sql,探讨:Oracle数据库查看一个进程是如何执行相关的实际SQL语句...
  4. java面试题十八 switch一个考题
  5. android分层测试,Android视图层次和性能的检测(官方)
  6. Python读取Json字典写入Excel表格的方法
  7. WCF Testing Tool(转)
  8. 最高效的进(线)程间通信机制--eventfd
  9. 转----ASP.NET中常用的优化性能方法
  10. conda如何升级pytorch_第一节 PyTorch简介及环境配置
  11. python integer怎么用_Python core.integer方法代码示例
  12. gflags.lib(gflags.obj) : error LNK2001: 无法解析的外部符号 __imp_PathMatchSpecA
  13. 小米路由器3 保姆级救砖回忆录及实操技巧演示
  14. java swing 常见错误和使用
  15. CPLEX运行出错-OPL标记问题
  16. golang生成随机数
  17. 页面最上方的搜索和logo叫什么_网页顶部导航栏设计总结
  18. 云安全将来时 | 云安全的发展与未来趋势
  19. apmserv mysql5.1启动失败,win10系统无法正常运行apmserv显示apache和mysql启动失败怎么办...
  20. ResourceDictionary

热门文章

  1. MAC 开启root权限
  2. 解决电脑双网卡上网问题
  3. oam 纯度_我编程中的纯度:Go中的函数
  4. xd怎么做页面滑动_XD教程|如何在Adobe XD中创建滑动手势
  5. 科研学习过程中的收获
  6. Mybatis逆向工程、Quartz框架的定时任务管理详解、Cron表达式
  7. 一叠资料怎么快速打印复印?
  8. enspac启动失败代码2_eNSP华为路由交换设备模拟器AR设备启动失败“错误代码40”问题解决方案...
  9. android动态脱壳教程,使用 FRIDA 为 Android 应用进行脱壳的操作指南
  10. springboot连接微信公众号服务以及给前端签名