美文网首页
《C++ Primer Plus》:函数探幽

《C++ Primer Plus》:函数探幽

作者: Dragon_boy | 来源:发表于2020-07-28 17:15 被阅读0次

本章内容概览

  • 内联函数
  • 引用变量
  • 按引用传递函数参数
  • 默认参数
  • 函数重载
  • 函数模板

内联函数

内联函数是C++为提高运行速度的改进,和常规函数编写一样,只是编译器组合到程序的方式不同。常规函数调用时,编译器会根据函数地址跳转,执行代码,函数结束后跳转回来,而内联函数是编译器使用相应的函数代码替换函数调用,程序不会跳到另一处执行代码,所以内联函数运行速度快,但相比之下占用内存大。

要使用内联函数,应在声明和定义前加上inline关键字,不过通常会省略原型。注意,内联函数不能递归。

如计算平方的函数:

inline double square(double x) {return x * x;}

引用变量

引用是已定义的变量的别名。引用变量的主要用途是用作函数的形参,通过将引用变量用作参数,函数将使用原始数据,而不是其副本。

C++使用&运算符来声明引用:

int rats;
int& rodents = rats;

上述引用声明允许将rats和rodents互换,它们指向相同的值和内存单元。

引用和指针看起来很像,但还是有差别的,其中之一是声明引用时必须初始化:

int rat;
int& rodent;
rodent = rat;  //invalid

引用更像是const指针,都必须在声明时初始化。

函数可以将引用用作函数参数:

void swap(int& a, int& b);

调用后,函数体中使用的相当于是真实的值,而不是拷贝值,这和指针,按地址传递参数很像。

同样,还可以使用const限定符修饰形参,表明不能修改引用:

void refcube(const double& ra);

如果实参和引用参数不匹配,C++将生成临时变量,如使用表达式作为实参传入。当前,仅当参数为const引用时,C++才能这么做。

如果引用参数是const,编译器将在下面两种情况下生成临时变量:

  • 实参的类型正确,但不是左值
  • 实参的类型不正确,但可以转换为正确的类型。

左值是可被引用的数据对象,如变量、数组元素、结构成员、引用和解除引用的指针都是左值,非左值包含字面常量和包含多项式的表达式。但常规变量和const变量都可以视为左值。

C++11还新增了右值引用,使用&&声明:

double&& rref = std::sqrt(36.00);
double j = 15.0;
double&& jref = 2.0 * j + 18.5;

引用的引入其实是为了方便将结构和类用作参数:

struct free_throws
{
    std::string name;
    int made;
    int attempts;
    float percent;
};

void set_pc(free_throw& ft);

同时返回值可以返回一个引用,如果不是的话,就是一个拷贝:

free_throw& set_pc(free_throw& ft);

默认参数

默认参数位于非默认参数后定义,调用函数时,要么不设置默认值,要么全部设置:

int harpo(int n, int m = 4, int j = 5);

函数重载

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

void print(const char* str, int width);
void print(double d, int width);
void print(long l, int width);
void print(int i, int width);
void print(const char* str);

不过这种时候如果传入了不正确的类型作为参数,C++会拒绝进行强制转换。

同时,下面两个函数原型的特征标相同,所以不是函数重载:

double cube(double x);
double cube(double& x);

类型引用和类型本身是同一个特征标。

同时,匹配函数时并不区分const和非const变量:

void dribble(char* bits);  // 可以传入const和非const
void dribble(const char* bits);  //只能传入const

总之注意,函数重载必须是特征标不同,如果要返回类型不同的话,特征标也必须不同。

函数模板

比如定义一个交换模板函数:

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

模板并不创建函数,只是告诉编译器如何定义函数。比如:

int a = 1;
int b = 2;
Swap(a,b);

编译器会用int替换AnyTyoe,创建一个int版本的函数。

模板重载和常规函数重载类似,只要特征标不同即可。

显式具体化,对于常规模板函数定义,对于某些操作可能无意义,比如,整型可以直接在if语句中比较,但数组不行,同样结构体不能单独交换成员变量的值。

我们可以这么定义:

template <typename T>
void Swap(T&, T&);

template <> void Swap<job> (job&, job&);

job是一个结构体。如果传入非job类型值,则会使用普通版本模板函数,如果是job类型值,就会使用特定的模板。

实例化和具体化

在代码中包含函数模板本身并不会生成函数定义,只是一个用于生成函数定义的方案,编译器使用模板为特定类型生成函数定义时,得到的是模板实例,这种实例化方式是隐式实例化,C++还允许显示实例化,比如int类型:

template void Swap<int>(int, int);

这可以直接让编译器创建特定的实例。

而显示具体化:

template <> void Swap<int> (int&, int&);

区别在于<>,同时显示具体化是要求编译器遇到对应参数时使用该模板创建定义,而显示实例化是直接创建对应类型的实例。

还可以通过使用函数来创建显示实例化:

template <typename T>
T Add(T a, T b)
{
    return a + b;
}
...
int m = 6;
double x = 10.2;
cout << Add<double>(x,m) << endl;

<double>表明将非double参数转换为double参数。不过对于Swap不行:

int m =5;
double x = 14.3;
Swap<double>(m,x);

第一个参数是double&,不能指向int m。

对于函数重载、函数模板和函数模板重载,C++确定使用哪一个函数定义的策略是重载解析:

  • 创建候选函数列表
  • 使用候选函数列表创建可行函数列表
  • 确定是否有最佳的可行函数

有时候我们可以自定义引导编译器使用哪种函数,比如可以:

lesser<>(m,n)

<>表明使用模板函数。

模板函数发展

比如下面的模板函数:

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

我们不知道xpy是什么类型的,因为x和y可以是任何类型。

C++提供了关键字decltype:

int x;
decltype(x) y;

这样,y的类型就和x类型一致,decltype的参数可以是表达式,上述模板函数可以变为:

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

不过decltype不能处理返回语句的值的类型:

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

编译器无法预知x+y的类型,所以无法使用decltype,我们可以使用auto:

template<typename T1, typename T2>
auto ft(T1 x, T2, y)
{
    ...
    return x + y;
}

相关文章

网友评论

      本文标题:《C++ Primer Plus》:函数探幽

      本文链接:https://www.haomeiwen.com/subject/qjnfrktx.html