C++ Primer Plus 第八章 函数探幽

函数探幽

C++内联函数


程序编译之后由一组机器语言指令组成,运行程序时将其载入内存,运行函数时会跳转到函数所在的内存区域,执行完成后返回原来内存区域

该跳转过程会有一个时间,虽然很短

内联函数可以让函数就写在调用函数的位置,从而不需要跳转函数,但是可能会增加内存的占用。

当函数执行的时间小于跳转时间时,使用内联函数可以明显减少程序运行时间。否则减少的运行时间不明显。

如果要使用该特性,必须采取以下措施之一:

  1. 在函数声明前加上关键字inline;
  2. 在函数定义前加上关键字inline;

通常的做法是省略原形,将整个定义放在本应该提供原形的地方。

编译器不一定会满足内联函数的要求,因为它可能认为该函数过大,或者函数调用了自己(内联函数不能递归);更或者有些编译器没有启用该特性

可以代替c语言中的宏定义

引用变量


引用是已定义变量的别名。

创建引用变量

& 符号可以用的指示变量的地址。如果要讲rodents作为rats变量的别名,则:

    int rats;
    int & rodents = rats;

上述引用声明允许将rats和rodents互换————他们指向相同的值和内存单元。引用必须在声明引用时将其初始化,不能像指针那样。

    int rat;
    int &rodents = rats;
    int * const pr = &rats;

rodents 扮演的角色与表达式*pr相同。

将引用用作函数参数

这种传递方式被称为按引用传递。C语言中只能按值传递。

如果不想要程序修改引用的值

    double refcube(const double &ra);

但是这样的代码完全可以用按值传递代替,当数据比较大的时候(例如结构和类),引用参数会很有用。

如果实参和引用参数不匹配,C++将生成临时变量(仅当参数为const引用时)。

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

应尽可能使用const

将引用用于结构

引用非常适合用于结构和类。

假设有一个结构定义如下:

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

则可以定义以下的函数原型去引用他

    void set_pc(free_throws & ft);

引用可以作为函数声明,例如:

    free_throws & accumulate(free_throws & target,const free_throws & source);

返回引用时最重要的一点是:

  • 避免返回函数终止时不再存在的内存单元引用。即一下代码:
    const free_throws & clone2(free_throws & ft){
        free_throws newguy;
        newguy = ft;
        return newguy;
    }

令一种方法是使用new来分配新的存储空间。

    const free_throws & clone(free_throws & ft){
        free_throws *pt = new free_throws;
        *pt = ft;
        return newguy;
    }

不过这样的代码在后续的代码中很容易忘记对new的内存调用delete.

将引用用于类对象

可以加char*类型赋值给string &,如果如此传参,就会产生临时变量

默认参数


默认参数指的就是当函数调用中省略了实参时自动使用的一个值。

可以只在函数定义的时候写出:

    char *left(const char *str, int n = 1);

函数重载


又函数多态,是C++在c语言的基础上新增的功能。

函数重载的关键是函数列表————也称为函数特征标(function signature)。如果两个函数的参数数目和类型相同,同时参数的排列顺序也相同,
则它们的特征标相同,而变量名是无关紧要的。

C++允许定义名称相同的函数,条件是他们的特征表不同。如果参数数目和/或参数的类型不同,则特征标也不同。例如:

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

如果函数的调用没有原形匹配,那么C++会尝试使用标准类型转换强制进行匹配。(int x 和 int &x的函数标是相同的),编译器在检查函数特征标时,会吧类型引用和类型本身视为同一个特征标。

    void dribble(char *bits);
    void dribble(const char *cbits);

函数模板


函数模板允许使用泛型来编写程序。有时也称为通用编程。

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

代码的第一行表示创建一个模板,并将类型命名为AnyType。两个关键字是必须的,其中typename可以使用class,两者等价。

在使用函数时,如果传入的参数是int,则会将AnyType转换为int

如果写先声明后给出函数,则必须在声明之前和函数之前都加上模板。

模板同样可以重载。

显式具体化

  • 对于给定的函数名,可以有非模板函数、模板函数和显式具体化模板函数以及他们的重载版本。
  • 显式具体化的原型和定义应以template<>打头,并通过名称来指出类型。
  • 具体化优先与常规模板,而非模板函数优先于具体化和常规模板。
struct job{
    char name[40];
    double salary;
    int floor;
};
void Swap(job &,job &);

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

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

实例化和具体化

代码中包含函数模板本身并不会生成函数定义,它只是一个用于生成函数定义的方案。

编译器在使用模板为特定类型生成函数定义时,得到的是模板实例。

这种实例化的方式被称为隐式实例化。

显示实例化:

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

显示具体化:

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

区别在于templage后面是否有<>符号,显示具体化的意思是“不要使用Swap()模板来生成函数定义”。

在同一个文件中使用同一种类型的显式实例和显式具体化将出错。

  • 可以在程序中使用函数来创建显示实例化。
template <class T>
void Swap(T &,T &); //模板

template <> void Swap<job>(job &, job &); // job的显示具体化
int main(void){
    template void Swap<char(char &,char &)>;
    short a,b;
    ...
    Swap(a,b); //隐式实例化
    job n, m;
    ...
    Swap(n,m); //显示具体化后实例化
    char g, h;
    ...
    Swap(g, h); //显示实例化
    ...
}

三种具体化: 隐式实例化、显示具体化和显式实例化

函数版本的选择

对于函数重载、函数模板和函数模板重载,C++有一个策略。
该过程称为重载解析

  1. 建立函数候选列表
  2. 使用候选函数列表创建可行函数列表。
  3. 确定是否有最佳的可行函数。如果有,则使用,否则该函数调用出错。

选用函数的顺序:

  1. 完全匹配,常规函数优于模板。(具体化优于常规模板)
  2. 提升转换(例如char和shorts自动转换为int,float自动转换为double)
  3. 标准转换(int>char long>double)
  4. 用户定义的转换,如类声明中定义的转换。
完全匹配和最佳匹配

如果有两个函数完全匹配,是一种错误。此时编译器可以会出现“ambiguous”的错误。

但是有两个例外:

  1. 指向非const数据的指针和引用优先与非const指针和引用参数匹配。
    例如:

    void recycle(blot &); // #1
    void recycle(const blot &); // #2
    

    如果实参是blot类型的,则优先使用1(非引用和指针的const和非const之间的区别不适用)

  2. 如果一个是非模板函数,另一个不是。该情况下,非模板函数将优于模板函数。
    如果两个都是模板函数,则较具体的模板函数优先。

用于找出最具体的模板的规则被称为 函数模板的部分排序规则 这是C++98的特性

template < typename T>
void a1(T x);  //#1
template < typename T>
void a1(T * x);  //#2
...
int x;
a1(x); //调用#1
a1(&x); //调用#2
...

创建自定义选择

调用模板的时候在函数后跟上,优先使用模板函数

模板函数的反战

关键字decltype(C++11)

int x;
decltype(x) y; //创建一个y和x类型相同
decltype(expression) var;

步骤:

  1. 如果expression是一个没有用括号括起来的标识符,则var的类型与改标识符的类型相同,包括const等。

    double x = 5.5;
    double y = 7.9;
    double &rx = x;
    const double *pd;
    decltype(x) w; // w是double
    decltype(rx) u = y; //u是double &
    decltype(pd) v; // v是 const double *
    
  2. 如果expression是一个函数调用,则var的类型与函数的返回类型相同。

  3. 如果expression是一个左值,则var为指向其类型的引用。
    没有带括号的左值在第一步时已经被处理,所以引用需要带括号。

    double xx = 3.3;
    decltype((xx)) r2 = xx; // r2是double &
    

    括号并不会改变左值性

  4. 如果以上的标间都不满足,则var的类型与expression的类型相同(表达式或数值一类的)。

后置返回类型

template<class T1,classs T2>
?type? gt(T1 x,T2 y);

如果要是一个模板函数的返回值无法确定,则可以使用后置返回类型

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

上述两句等价,也可以如下定义:

template<class T1, class T2>
auto gt(T1 x, T2 y) -> decltype(x+y);
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值