C++ Primer Plus学习笔记-第八章:函数探幽

第八章:函数探幽

内联函数是C++为提高程序运行速度而做的一项改进,它的原理是:像C语言中的宏替换那样直接将函数代码插入调用它的位置并直接编译,好处是不需要像常规函数调用一样来回切换执行位置,缺点是一个内联函数需要拷贝多个副本;当函数调用相当频繁时使用内联函数效果更明显;

使用内联函数的条件:

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

注意:内联函数不能递归

引用是已定义变量的别名,主要用途是作为被调用函数的形参,这样被调用的函数就可以直接使用原始数据;

C和C++使用符号&来指示变量的地址,而C++中&符号也用来声明引用;比如int & abc是指向int类型的引用;

引用的初始化必须在定义时完成,指针则可以在随后的使用过程中修改;

让我们自己创建一个引用:

int & rodents = rars;

可以通过初始化来设置引用的对象,但是不能通过赋值;

按引用传递和按值传递看起来相同,只能通过原型或函数定义才能知道传递方式;区别是:

void swapr(int & a,int & b);//按引用传递
void swapv(int a, int b);//按值传递

如果将对象按引用的方式传递给函数却不希望被修改,可以使用const限定符:

double refcube(const double &ra);

传递引用的限制更加严格,当传入的引用和函数原型中定义的不一致时,程序会先创建一个临时引用对象,把传入对象转换成符合格式的临时对象,再把临时对象传入;

这两种情况会导致创建临时对象:

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

左值参数是可被引用的数据对象,非左值包括字面变量和包含多项的表达式;

C++11新增了右值引用,符号是&&,像这样:

double && rref = std::sqrt(36.00);
//以前的引用&现在称为左值引用;

使用结构引用参数的方式与使用基本变量相同,只需要在声明结构参数时使用引用运算符&即可;比如:

void set_pc(free_throws & ft);

注意:返回值为引用对象的函数实际上就是引用的别名;

应避免返回函数终止时不再存在的内存单元引用;也应避免指向临时变量的指针;最简单的方法是:返回一个作为参数传递给函数的引用;另一种方法是用new来分配新的存储空间(记得在不使用它的时候用delete释放);

赋值表达式中等式左边必须标识一个可修改的内存块,常规(非引用)返回值的类型是右值,不能通过地址访问,只能出现在赋值表达式的右边而不能出现在左边;

假设要使用返回值,但是不允许修改返回值,那么可以在函数原型的最前面加上const修饰符;

将类传递给函数时,C++通常的做法是引用;

注意其实string对象是可以用C风格字符串来初始化的,这是这个类自带的转换功能

使得能够将特性从一个类传递给另一个类的语言特性被称为继承,比如ostream继承给了ofstream,对于ostream来说ofstream是派生类;这意味着继承类可以使用基类的特性,比如在ofstream中就可以使用ostream中的setf()和precision()方法;基类引用可以指向派生类;也就是说,虽然在函数定义中是使用了针对基类的引用,但是传入派生类的引用是完全可行的;

方法setf()可以用于设置各种格式化的状态,方法调用setf(ios_base::fixed)将对象置于使用定点表示法的模式;setf(ios_base::showpoint)将对象置于显示小数点的模式,即使小数部分全部是0;方法precision(x)可以指定显示多少位小数(前提是已经处于定点模式下),所有设置都将不变直到下一次设置它们;默认的字段宽度为0,恰好可以装下所输出的内容;

方法setf()返回调用它之前有效的所有格式化设置,ios_base::fmtflags是存储这种信息所需要的数据类型名称,可以用变量来存储这个返回值;

每个对象都存储了自己的格式化设置,因此当程序将cout传递给另一个函数时,接收到的cout的设置是重置过的;

使用引用参数的原因:

程序员能够修改调用函数中的数据对象‘
通过传递引用而不是整个数据对象可以提升程序效率

引用参数实际上是基于指针的代码的另一个接口;

对于使用传递的值而不做修改的函数:

如果数据对象很小则按值传递
如果数据对象是数组则使用指针(这是唯一的选择)
对象是较大的结构则使用const的指针或引用
对象是类对象,使用const引用

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

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

对于基本类型,cin也使用引用,比如:cin>>n;

默认参数指的是当函数调用中省略了实参时自动使用的一个值;让我们来实现它:

char * left(const char * str, int n = 1);
//只有函数原型指定了默认值,函数定义与无默认值相同
//任何一个默认参数右侧都只能出现默认参数

默认参数能够使用不同数目的参数来调用同一个函数,而函数多态(函数重载)可以使用多个同名的函数;

函数重载的关键是使用函数的参数列表,也称函数特征标;如果两个函数的参数数目和类型相同,同时参数的排列顺序也相同,则它们的特征标相同;没有匹配成功的原型时,并不会停止调用其中的一个函数,C++将尝试进行类型转换后再进行匹配;

注意编译器在检查函数特征标时,将把类型引用和类型本身视为同一个特征标,因此不能通过这两者来进行函数重载;

注意将非const值赋值给const变量是合法的,反之则是非法的;

要记住是特征标让函数可以重载,而不是函数类型使得函数可以重载;

一个小技巧:对于一个数字来说,除以十就可以得到去掉最后一位的数字;可以这样获取数字位数:

unsigned digits = 1;
while (n /= 10)
    digits++;
//举个例子,比如有一个五位数要获取前三位
//那么将这个数除以一百就可以了

函数重载的触发条件:函数基本上执行相同的任务,但使用不同形式的数据;

函数重载的原理:C++根据参数数目和类型私下对函数名称进行矫正,称为名称修饰或者函数矫正,使用一些奇怪的符号对函数名进行编码,这样程序员眼中的函数重载实际上在编译器眼中就是在使用不同的函数;

函数模板是通用函数的描述,也就是说它们使用泛型来定义函数,其中的泛型可用具体的类型(比如int或double)替换;

让我们来简单的创建一个基于模板的交换函数:

template <typename AnyType>
void swap(AnyType &a, AnyType &b)
{
    AnyType temp;
    temp = a;
    a = b;
    b = temp;
}
//typename关键字可以使用class替换
//class是曾经的实现方式

注意:函数模板不能缩短可执行程序,最终的代码不包含任何模板,只包含了为程序生成的实际函数;

需要对多个类型使用不同的算法时,可使用模板;被重载的模板的函数特征标必须不同;

显式具体化是提供的函数的具体化定义,其中包含所需的代码;当编译器找到与函数调用匹配的具体化定义时,将使用该定义,而不再寻找模板;

具体化方法是:

对于给定的函数名可以有非模板函数,模板函数,显式具体化模板函数以及它们的重载版本


显式具体化的原型和定义应以template<>打头,并通过名称来指出类型


具体化优先于常规模板,而非模板函数优先于具体化和常规模板

举个例子:

void swap(job &,job &);
//常规函数,优先级最高

template <typename T>
void swap(T &, T&);
//重载函数,优先级最低

template <> void swap(job &, job &);
//具体化函数,优先级中等

在代码中包含函数模板本身并不会生成函数定义,它只是一个用于生成函数定义的方案;编译器使用模板为特定的类型生成函数定义时,得到的是模板实例;

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

隐式实例化,显式实例化和显式具体化统统称为具体化;

编译器决定调用那个函数定义来重载的过程称为重载解析;而重载解析的过程是:

  1. 创建候选函数列表,其中包含与被调用函数的名称相同的函数和模板函数;
  2. 使用候选函数列表创建可行函数列表;
  3. 确定是否有最佳可行函数,有则使用那个函数,没有则表示发生错误;
  4. 上述过程只考虑特征标不考虑返回类型

可选函数最佳到最差的标准是:

  1. 完全匹配,但常规函数优于模板
  2. 提升转换
  3. 标准转换
  4. 用户定义的转换

一般来讲同时出现两个甚至更多最佳函数时代表着错误的发生,但是有例外;

两个匹配结果但仍可以完成解析的条件:

  1. 指向const的指针和引用优先于指向非const的指针和引用
  2. 两个完全匹配的函数都是模板函数时较具体的模板函数优先,这意味着显式具体化将优先于使用模板隐式生成的具体化

术语"最具体"并不一定意味着显式具体化,而是指编译器推断使用哪种类型时执行的转换最少

简而言之,重载解析将寻找最匹配的函数,如果只存在一个这样的函数则选择它,如果存在多个这样的函数但其中只有一个是非模板函数,则选择该函数,如果存在多个适合的函数,且它们都为模板函数,但没有一个函数比其它函数更具体,则函数调用将是不确定的,因此是错误的;当然,如果不存在匹配的函数,则也是错误的;

如果函数定义是在使用函数前定义的,它将充当函数原型;

会遇到这样的情况:有时候模板函数中新定义的数据类型是未知的,为了能在函数中定义某些变量的数据类型,我们可以这样做:

decltype (x+y) xpy;
//这将把xpy的类型限制为和x+y的结果相同

对于使用关键字decltype变量类型的确认,具体过程分为以下三步:

  1. 如果exoression是一个没有用括号扩起的标识符,则var的类型与该标识符的类型相同,包括const等限定符;
  2. 如果expression是一个函数调用,则var的类型与该函数的返回值相同;
  3. 如果前面的条件都不满足,则var的类型与expression相同;

注意使用decltype关键字有这样的问题,它没有办法确定函数原型中返回值的类型,这是解决的方法:

template<class T1,class T2>
autu gt(T1 x, T2 y) ->decltype(x+y)
{
    ...
    return x+y;
}

你可能感兴趣的:(笔记,c++,编程语言)