C++ Primer Plus 学习笔记(五)

第八章. 函数探幽

1. C++ 内联函数

正常创建函数后,程序调用该函数时,程序跳转到函数地址。使用内联函数时,编译器在编译时,会直接将函数代码替换函数调用,而省去程序跳转到函数地址的时间,加快运行速度。但是内联函数每次调用时,都会创建函数副本,调用十次就创建十个。因此要创建内联函数,代码行数不宜多,而且内联函数无法递归。使用内联函数,要在函数声明和定义前加上关键字 inline。相比于宏定义,内联函数可以更好的实现按值传递参数的目的。内联函数与宏定义类似的事,它可以定义在头文件中,被多个源文件包含时,不会违反单定义规则,它的作用域好像是内部链接的。

inline double squar(double x) { return x * x; }  // 内联函数
#define SQUAR(X) X * X  // 宏
int a = aquar(3+2);  // a = 5 * 5 = 25
int b = SAUAR(3+2);  // b = 3 + 2 * 3 + 2 = 11

2. 引用变量

引用相当于给变量起别名,引用和原变量名都指同一个变量。引用定义为:

int rats;
int & rodents = rats;  // rodents是rats的引用,&不是取地址符,而是指向int的引用

引用必须在声明时就初始化,不能先声明再赋值,就像声明 const 常量一样。

C++ 中,引用的重要作用是作为函数参数,引用作为参数,可以改变原始变量(给原始变量起别名,函数直接对原始变量操作),函数调用用实参初始化形参:

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

理解引用可以把它想成指针,直接对内存上的原始数据进行修改。

引用是变量的别名,因此在形参是引用的函数比形参是一般变量的函数,实参要求更严格:

int cube(int a);
int recube(int &b);
int x = 2;
int m = cube(x + 2);  // 合法
int z1 = cube(4.13);  // 合法
int n = recube(x + 2);  // 不合法
int z2 = recube(4.13);  // 不合法

而当形参是 const 的引用时,

int con_recube(const int& c);
int num1 = con_recube(x + 2);  // 合法
int num2 = con_recube(4.13);  // 合法

函数实参的类型正确,但不是左值;或者类型与形参不匹配但可以转换成匹配类型,函数将创建临时变量,将其初始化为形参对应的类型的值,然后函数形参为该临时变量的引用,函数调用结束后释放该临时变量。形参是指针时,与引用的效果相同。也就是说,如果接受引用或指针的函数的意图是修改参数,那么创建临时变量就会修改临时变量而不是参数,则在创建临时变量时会遭到编译器阻止报错,对于不修改参数却想使用引用或指针的函数,参数可由 const 限定,会消除这种副作用而创建临时变量,不修改形参。

3. 默认参数

声明默认参数时,只能从右向左添加默认值,函数定义时,省略默认值。

int harpo(int n, int m = 4, int j = 5);  // 合法
int harpo(int n, int m, int j)  // 定义时省略默认值
{
    ...
}
int chico(int n, int m = 6, int j);  // 非法

调用默认函数时,不能跳过参数

int beeps = harpo(3, , 8)  // 非法

4. 函数重载

函数重载只跟参数的特征标,即参数的数目、类型或者顺序有关,与函数返回值无关。

在调用重载的函数时,没有完全匹配实参的函数时,将使用标准类型强制转换,若有多个重载都可以强制转换匹配时会报错。引用并不会重载,而 const 会。

void dribble(const char*)
void dribble(char*)  // 重载

重载函数的参数是左值、const及右值时,将调用最匹配的函数。

void stove(double & r1);
void stove(const double & r2);
void stove(double && r3);
double x = 55.3;
const double y = 32.2;
stove(x);  // 调用void stove(double & r1)
stove(y);  // 调用void stove(const double & r2)
stove(x+y);  // 调用void stove(double && r3)

5. 函数模板

函数模板的格式:

template <typename T>  // typename可以用class替换
void Swap(T & a, T & b);  // 函数声明
    ...
template <typename T>
void Swap(T & a, T & b)  // 函数定义
{
    T temp;
    temp = a;
    a = b;
    b = temp;
}

T 表示任意标准类型,关键字 template、typename以及尖括号不能省略,在声明和定义时都要有。函数模板在每次调用模板函数时自动生成相应的函数定义,并不缩短程序。

函数模板也可以重载,且重载函数的形参也可以有其他类型,并不只有T类型。

template <typename T>
void Swap(T a[], T b[], int n);

对于模板函数定义中,不适用的类型,如 a = b 中,T 是数组形式,或者 if (a > b),而 T 是结构等。可以用显示具体化处理这种问题。

struct job
{
    ...
};
template <> void Swap<job>(job &, job &);  // 函数模板显示具体化声明,第一个尖括号里没有参数
    ...                                    // Swap<job>的<job>是可选的

template <> void Swap<job>(job &, job &)  // 函数定义
{
    ...
}

编译器使用模板生成的函数定义,即为模板实例,这种实例的方式称为隐式实例化。c++ 允许显示实例化,在调用模板函数时,采用:

int main()
{
    ...
    Swap<int>(m, n);  // 显示实例化,告诉编译器生成T是int的函数定义,并将
                      // m, n的类型强制转换成int
    std::cout << m << n;
}

也可以在主函数中声明一个显示实例化的模板函数

int main()
{
    template void swap<char, char>(char& a, char& b);
    char g, h;
    swap(g, h);  // 使用声明的显示实例化模板
}

显示实例化声明时, template 后面没有尖括号,而显示具体化函数声明和定义时有。在同一文件中,同时使用同一类型的显示实例化和显示具体化,将会报错。

int m = 5;
double x = 14.3;
Swap<double>(m, x);  // 将会报错,m,x 都是非const引用,不会创建副本。
                     // m的引用不能强制转换成double

如果重载或模板等有多个匹配的原型,将会报错。匹配原型的函数有优先级,形参是指针或引用时,实参是非const的,那么形参是非const的优先于形参是const的函数。非模板函数优先于模板函数。显式具体化优先于隐式实例化。多个匹配的函数,会选择更具体的函数。

在函数调用时,可以用尖括号告诉编译器使用模板:

template <typename T>
T lesser(T a, T b);

int lesser(int a, int b);

int main()
{
    int m = 4;
    int n = 8;
    double x = 15.3;
    double y = 21.4;    
    
    lesser(m, n);  // 调用非模板函数
    lesser(x, y);  // 调用模板函数
    lesser<>(m, n);  // 显示告诉编译器使用模板函数,尖括号里面为空
    lesser<int>(x, y)  // 显示实例化
    return 0;
}

decltype关键字可以推断变量类型:

//decltype(exp) var; 
// exp没括号,var的类型与exp相同
double x = 5.5;
double y = 7.9;
double & rx = x;
const double *pd;
decltype(x) w;  // w是double类型
decltype(rx) u = y;  // rx是double &类型
decltype(pd) v;  // pd是const double *类型

// exp是函数调用,var的类型与函数返回类型相同
long indeed(int); 
decltype (indeed(3)) m;  // m是long类型,不会实际调用函数,编译器只查看
                         // 函数返回类型
// exp有括号,var的类型是指向exp的引用
double xx = 4.4;
decltype((xx)) r2 = xx;  // r2的类型是double &
decltype(xx) w = xx;  // w的类型是double

对于下面的情况:

template<class T1, class T2>
decltype(x+y) gt(T1 x, T2 y);  // 不可以这样推断返回类型

x 和 y并没有声明,编译器无法得知 x y 是什么类型。

可以采用返回类型后置的方式:

template <class T1, class T2>
auto gt(T1 x, T2 y) -> decltype(x+y);  // 声明

// 定义
template <class T1, class T2>
auto gt(T1 x, T2 y) -> decltype(x+y)
{
    ...
}

这样 decltype 在参数 x y 的声明后面。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值