第八章 函数探幽(P254-297)

本章之后就是c++与c最大的不同,也就是c++独有的内容。

内联函数

常规函数与内联函数的区别主要不在于编写方式,而是c++编译器如何将它们组合到程序中。
常规函数:执行到函数调用指令时,程序将在函数调用后立即存储该指令的内存地址,并将函数参数复制到堆栈,跳到标记函数起点的内存单元,执行函数代码,然后跳回到地址被保存的指令出,这样来回跳跃位置意味使用函数需要一定的开销。
内联函数:编译器将使用相应的函数代码替换函数调用,无需跳到另一个位置执行代码,所以内联函数运行速度比常规函数快,但需要占用更多的内存。(内联函数不能递归)

内联函数的声明:不需要函数原型,将定义放在前面,函数头前加inline

//常规函数
int fuction(int); //原型
int fuction(int n) //定义
{
	...
}

//内联函数
inline int fuction(int n) //无需函数原型
{
	...
}

如果一个程序调用一个常规函数十次,程序需要十次保存地址与来回跳转位置,时间过长。如果是调用内联函数十次,程序中将包含内联函数10个副本,不用来回跳转,节约时间,但因为有10个副本,程序占用内存过多。因此在使用内联函数时要考虑时间与内存的比值,来决定是否采用内联函数,如果一个函数代码很短,且需要多次调用,用内联函数再适合不过了。

内敛与宏
c语言使用预处理语句#define来提供宏 ----内敛代码的原始实现。
inline工具是c++新增的特性。
如果计算一个数的平方宏:

#define SQUARE(X) X*X

这并不是函数,不是传递参数得结果的,而是通过文本替换得到的。

SQUARE (5) -------------5*5
SQUARE(4.5+5.5)------------4.5+5.5*4.5+5.5

很明显,在使用宏的时候,根据X不同,有时会出现错误

引用变量

c++新增的一种复合类型----引用变量,是已定义的变量的别名。目前的主要用途是作为函数的形参,通过引用变量作为参数,函数将使用原始数据,而不是副本,之前的指针可以使用原始数据,现在引用变量也可以。

使用&(取址运算符)声明引用:

int numbers;
int & nu = =numbers;

这样,nu与numbers有着相同的含义-------指向相同的值和内存单元。这一点要与指针区分,在值与址上有着相同的含义,只是表示方法不同,除此之外,还有一个差别,声明引用时必须将其初始化,而指针可以先声明,再赋值。这一点与const指针相似,必须在创建时进行初始化,一旦与某个变量关联起来,就将一直表示它,不能改变。
这里只需要注意,引用变量值改变,其关联的变量也将改变。
其次,要尽可能使用const:

  • 使用const可以避免无疑中修改数据的编程错误
  • 使用const使函数能够处理const和非const实参,否则只能接受非const实参
  • 使用const引用使函数能够正确生成并使用临时变量
    对最后一点做些说明,引用变量作为函数参数时,传入的实参必须是一个变量,而不能是一个值:function(x),而不能是function(5),但是使用const引用变量作为函数参数,则可以接受值,此时,函数会建立相应的临时变量。

可以声明一个返回引用变量的函数,传统的返回值的函数,程序会将返回值传递一个临时变量,然后再将这个临时变量拷贝给要赋值的变量,如果函数直接返回引用变量,则无需中间过程,直接赋值给变量,效率更高。

#include<iostream>
using namespace std;
string version1(string s1,const string &s);
string &version2(string &s1,const string &s);
int main()
{
	string s1,nu,nu2;
	cout<<"please enter a string: ";
	getline(cin,s1);
	cout<<"now,your string is :"<<s1<<endl;
	cout<<"-----------------------------"<<endl;
	nu = version1(s1,"***");
	cout<<"now,your string is :"<<nu<<endl;
	cout<<"the ordinay string is :"<<s1<<endl;cout<<"-----------------------------"<<endl;
	nu2 = version2(s1,"###");
	cout<<"now,your string is :"<<nu2<<endl;
	cout<<"the ordinay string is :"<<s1<<endl;
	
	return 0;
}
string version1(string s1,const string &s)
{
	string temp;
	temp = s + s1 + s;
	return temp;
}//传入常规变量

string &version2(string &s1,const string &s)
{
	s1 = s +s1+ s;
	return s1;
}//传递的是引用,并且返回引用,所以原始值s1也改变。

上述代码结果最后补充一个内容,左值与右值:

a = 10;

上述代码中,a为左值,10是右值,通常来说,左值是可以取地址的,而右值不可以。我们前面说的都是左值引用,如果想要使用左值引用对一个右值进行引用操作,前面需要加上const:

const int &x = 10;

但此时,x仅只读,不能修改x的值,也成为常引用。
c++11中提供了另一种引用-------右值引用:两个&&

int &&x =10;

此时x可修改,用法与左值引用相同,但不常用,仅会在之后的移动相关问题中使用。

函数默认形参

函数定义是可以给参数增加默认值,如果调用时没有传递参数,则使用默认值,定义时必须从右至左定义默认参数,也就是说,一个默认参数的右边参数都应该是默认参数。实参按照从左往右的顺序赋给形参。

函数重载

函数重载就是同一个函数名,但函数参数的类型不同,可以传递一个int或者double等等,这与函数默认参数不同,默认参数是同一个类型参数,传递的参数数目不同。
函数重载的关键是函数的参数列表------也称函数特征标,如果两个函数的参数数目和类型相同,同时参数的排列顺序也相同,则他们的特征标相同。

void index(int x, const char *str);
void index(double y,const char *str);

只有一个函数时,如果函数声明是int类型,但传入double数据,此时会强制转换,但如果是函数重载,传入其他类型参数,因为程序不知道应该转换为哪个类型的,因此会直接报错。
函数重载是特征标的不同,跟返回值类型无关
函数重载是区分const的,但不区分引用,也就是说,单独加一个const是函数重载,但加一个引用并不是函数重载。

函数模板

函数重载需要定义多个函数,实现功能相同,但参数类型不同的函数,这样有点繁琐,因此提出函数模板,只需要定义一个函数,但函数参数类型用自定义字符代替,同时利用template<typename ***>声明该字符:

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

....

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

要注意,函数原型和定义前都需要加上template<typename ***>,其中的typename可以用class代替,但防止与类声明混淆,建议使用typename。
这样利用函数模板定义,无论传入参数是那种类型,都可以实现其功能。

重载的模板,如果传入的是两个数组,那么之前的函数模板就不能发挥其作用,需要利用函数重载,但可以使用重载模板,也就是我们传入的数组可以是int,double等不同数据类型的。

函数模板是有一定的局限性的,例如,此时两个同种结构体的变量,如果只是交换内容,那么函数模板是可以实现的,因为结构体支持整体赋值。但如果,结构体中有三个变量,我们只想交换两个结构体变量中的两个元素,这时,普通的函数模板就不能实现该操作。这时就需要用到,显式具体化:表示,遇到指定的变量类型,不能按照普通的函数模板处理,应该使用指定的函数内容

template <> void Swap<job>(job &a, job &b)
{
	...
}

job是我们声明的一种结构体,该函数表示,如果Swap函数调用时传入的参数时job结构体类型,则执行这个特殊的代码,这样就与声明的普通函数Swap区分开。

如果将Swap函数重载三种,一个常规函数,一个函数模板,一个函数显式具体化:

void Swap(int a, int b) //普通函数

template<typename T>
void Swap(T a,T b); //函数模板

template<> void Swap<job>(job &a, job &b); //函数具体化

三个优先级是普通函数>函数具体化>函数模板,如果传入两个int类型参数,程序将调用普通函数而不执行模板函数,如果函数传入两个结构体变量,程序将调用具体化函数而不执行模板函数。

这里要补充一个知识点,前面函数重载时讲到,增加引用不是函数重载,即下面函数原型不是函数重载,是错误:

void index(int a, int b);
void index(int &a, int  &b);  //不是函数重载

但是,如果其中一个是函数模板,则算是函数重载:

template<typename T>
void index(T a, T b);
void index(int &a, int  &b);  //此时是属于函数重载的

最后要区分一些概念,首先要明确,代码中包含函数模板本身并不会生成函数定义,也就是说,函数模板并不是函数定义,它只是一个用于生成函数定义的方案,根据调用时传入的参数类型不同,才会生成不同的函数定义,也就是生成一个该函数的实例化,这种实例化方式称作隐式实例化,即在函数被调用时才会实例化,例如,一个模板函数被调用了十次,那么每调用一次,函数就是实例化一次,即程序中该函数实例化了五次。 但是,c++还允许显式实例化,意味着直接命令编译器创建特定的实例,其语法是:

template void Swap<int>(int, int);  //显式实例化

经过上述的声明,将使用Swap()模板生成一个使用int类型的实例,也就是“使用Swap()模板生成int类型的函数定义”,这是才是真正的函数定义。这里要跟前面的显式具体化区分(template后面有无<>):

template<> void Swap<job>(job &a, job &b);  //显式具体化

隐式实例化,显式实例化,显式具体化统称为具体化,区别是,隐式实例化是只有在函数被调用时才会生成相应类型的函数定义,显式实例化则是提前就生成了相应参数类型的函数定义,直接调用即可,最后的显式具体化是,遇到指定的特殊类型参数,不使用正常的函数模板,而使用指定的特殊函数,但也是在被调用是才会生成函数定义。

对于函数重载,函数模板,函数模板重载,程序需要决定调用使用哪一个函数定义,这个过程称作重载解析,这里详细的过于复杂,调用函数符合完全匹配,在其基础上选择最佳匹配的函数,这是通过传递参数使程序自己决定选择哪一个函数。也可以自己选择:

void function(int a, int b);   //常规函数
void function(T a, T b);  //函数模板


function<>(m,n);  //加上<>表示调用函数模板
function<int>(m,n);  //<>中加变量类型

<>中加变量类型,表示使用该类型的函数模板,即使传入参数不符合该类型,也会强制转换为该类型。

最后一个问题,有时候不清楚参数类型具体是哪个,比如:一个函数模板,两个不同类型数据相加,那他们和的类型呢?根据参数不同,无法具体表示和的类型:

template<class T1, class T2>
void index(T1 a, T2 b)
{
	...
	?type? t = a + b;
	...
}

因此此时t的类型无法确定,c++11新增的关键字decltype可以解决该问题:

int a;
decltype(a) b;

表示生成一个与a类型相同的变量b,因此,我们可以这样:

decltype(a+b) t = a + b;

这样可以声明各种类型,包括引用,指针,常数指针等等。如果括号中的是一个函数调用,那么表示该函数返回值的类型(并不会调用该函数,通过查看函数原型获取返回值类型)。如果括号中变量有另一个括号,则表示该类型的引用:

double z = 10;
decltype( (z) ) a = z;

a是一个double类型的引用。

上述表示在函数中不知道类型的情况下的解决方法,但是函数的返回值类型未知呢?则不适用,但可以这样:

auto index(int a, double b)  ->double
//所以可以这样:
auto index(int a, double b)  ->decltype(a+b)

将返回类型移到了参数声明后,被称为后置返回类型,auto是一个占位符,表示后置返回类型提供的类型。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值