C++基础语法:函数探幽(一)内联函数,默认参数,函数重载

前言
     

       "打牢基础,万事不愁" .C++的基础语法的学习."学以致用,边学边用",编程是实践性很强的技术,在运用中理解,总结.      

引入

       <C++ Prime Plus> 6th Edition(以下称"本书")第8章内容解读

内联函数

         1>本书P253--8.1节C++内联函数第二段:常规函数调用也使程序跳到另一个地址(函数的地址),并在函数结束时返回.下一段:对于内联代码,程序无需跳到另一个位置处执行代码,再跳回来。因此,内联函数的运行速度比常规函数稍快,但代价是需要占用更多内存。(黑体字是原话)

        ----非内联函数和内联函数在程序被编译后产生的机器指令不一样.

        调用非内联函数时,留下一个函数地址,CPU处理到函数地址时会跳到函数定义的位置执行代码,然后跳转回来(return)继续执行以下代码.编译后的内联函数,把代码一并放到当前位置,节省了CPU跳转执行和跳转返回的时间.见本书P254图8.1.所以,内联函数占内存多,但运行快.

        2>内联函数和寄存器变量register一样,是一种请求.即使定义成内联函数,编译器未必满足,见本书P254说明.

        3>内联函数不能递归

        4>内联函数优于宏定义

        C语言中的宏定义,不主动识别(),所以容易写错.内联函数没有这个顾虑.

        内联函数的使用场景

                代码内容少,调用次数不多,不用递归时可考虑使用内联函数

引用

        本书P255最后一段: 但引用变量的主要用途是用作函数的形参。通过将引用变量用作参数,函数将使用原始数据,而不是其副本.(黑体字是原话)

        引用已写过几篇文章,核心就在黑体字中,使用原值.

        引用主要用于表示单个变量的指针(不支持数组),多用于类对象引用做形参.如果想修改原值,用对象引用作形参,如果仅访问,对象引用前加const修饰.----代码的"潜在规则"

        本书P274中间8.2.7"何时使用引用参数"有详细使用说明 

        关于右值引用:本书截图如下

        测试代码如下

/*已测试*/
/*变量赋值左值引用,常量赋值右值引用*/
#include<iostream>
using namespace std;

int main(void) {
	int a = 3;	
	int&& b = 3;					//常量赋值给右值引用
	int& ref_c = a;					//变量赋值给左值引用
	int d = b;						//右值引用赋值给变量
	int e = ref_c;					//左值引用赋值给变量,相当于指针

	cout << "a的值是:" << a << endl;
	cout << "b的值是:" << b << endl;
	cout << "c的值是:" << ref_c << endl;
}

         区别左值引用和右值引用:左值引用不能接收常量,如int &f=3;//错误.右值引用可以接收常量.

         在什么情况下使用右值引用,暂时放一放.

默认参数

        概念:在定义函数时,设置一个(或多个)默认值.调用函数时,可以不传已设置为默认的值.

        函数回顾:

        函数的使用包括函数原型(声明),函数定义,函数调用三部分.

        函数原型由返回值类型,函数名,形参列表组成.

        非默认函数的定义和使用:       

void fun(Parameter pa);       //函数原型,函数声明

/*伪代码,函数定义*/
void fun(Parameter pa){       //函数定义,抬头和函数原型一样
    statement;                //语句,分号结束
    no return;                //返回值类型为void,无需return
}

fun(pa);                      //函数调用,pa为Parameter类型值或者变量

         默认函数的定义和使用:

void fun(Parameter pa=p);       //函数原型加入默认参数

/*伪代码,函数定义*/
void fun(Parameter pa=p){       //函数定义加入默认参数
    statement;                  
    no return;                  
}

fun();                          //函数调用之一,有了默认参数可以不传参
fun(pp);                        //函数调用之二,pp为Parameter类型值或者变量

         二者区别:在形参定义的时候传入默认参数,一套完整的数据表达:类型 变量=值;

         这个例子中表示为:Parameter pa=p; 分别对应了类型,形参变量和值;

         注意:形参列表中既有默认参数,又有非默认参数,默认参数应该顶右边写         

void fun(Parameter pa=p,Para p1);       //错误,默认参数应放右边
void fun(Para p1,Parameter pa=p);       //正确
         默认参数的使用场景

        当调用一个函数,经常给形参传入同一个值时,用默认参数将这个值设置为默认参数.

         如何使用默认参数

         和前面讲过的函数模板一样,(自用)关于程序的一些概念4:C++泛型初探-CSDN博客,使用默认参数属于"锦上添花"的操作.

        说明:函数模板是用来整合代码用的,能把相同逻辑的函数整合起来做成函数模板就做,整合不了就用本来的函数.函数的默认参数不用刻意去定义,当调用函数时发现经常传同一个参数时,修改原函数定义,使这个参数成为默认参数即可.

函数重载

        本书:函数多态是C++在C语言的基础上新增的功能。默认参数让您能够使用不同数目的参数调用同一个函数,而函数多态(函数重载)让您能够使用多个同名的函数。术语“多态”指的是有多种形式,因此函数多态允许函数可以有多种形式。类似地,术语“函数重载”指的是可以有多个同名的函数,因此对名称进行了重载。这两个术语指的是同一回事,但我们通常使用函数重载。可以通过函数重载来设计一系列函数——它们完成相同的工作,但使用不同的参数列表。

        函数重载的关键是函数的参数列表——也称为函数特征标 (function signature)。如果两个函数的参数数目和类型相同,同时参数的排列顺序也相同,则它们的特征标相同而变量名是无关紧要的。 C++允许定义名称相同的函数,条件是它们的特征标不同。如果参数数目和/或参数类型不同,则特征标也不同。 (黑体字是原话)

        ----解读:红色部分表示函数重载的概念:特征标不同的同名函数.

        "特征标相同"的概念:参数数目,类型,排列顺序相同,表示特征标相同.当三个特征中有任意一个不相同表示"特征标不相同",加上函数用相同名字(返回值类型无关),构成函数重载.注意:返回值类型不是特征标的一部分

         而变量名是无关紧要的

        解读:变量名对编译器来说是无关紧要的,前面提到过CPU不认识变量名(这里的变量名指函数名),只识别函数地址.但变量名对于编写函数和使用函数的人来说是很重要的,通过函数命名来明确要表达的逻辑.

        函数指针和函数重载的对比

        函数指针

/*伪代码*/
typedef ResultType (*p)(ParameterType1 pt1,ParameterType2 pt2); //声明函数指针

ResultType some_fun(ParameterType1 pt1,ParameterType2 pt2;      //p指向的函数

         上述代码声明了函数指针p指向这一类函数:返回值类型Resulttype,有类型ParameterType1和类型ParameterType2的形参.

/*伪代码*/
/*定义使用函数指针类型p做参数的函数fun*/
ResultType fun(p pfun),ParameterType1 pt1,ParameterType2 pt2){        
        return (*pfun)(pt1,pt2);                                //调用指针指向的函数
};    

===================================
/*调用fun*/
fun(some_fun,pt1,pt2);            

         函数重载

/*伪代码*/
ResultType1 fun_ol(ParameterType1 pt1,ParameterType2 pt2);      //重载形式1
ResultType2 fun_ol(ParameterType2 pt2,ParameterType1 pt1);      //重载形式2
ResultType1 fun_ol(ParameterType2 pt2);                         //重载形式3
ResultType2 fun_ol(ParameterType1 pt1);                         //重载形式4
ResultType1 fun_ol(ParameterType1* pt1);                        //重载形式5

        对于编译器来说,他们仍然是不同的函数,因为虽然变量名相同,但是函数地址不相同,编译后的程序找对应的函数进行调用; 但是对于程序员来说,他们是相同的函数,因为程序员只关心要实现的逻辑,不同的是实现的条件有所不同(传入的参数不同)

        对比函数指针和函数重载,函数指针是目的不同(函数名称不同),条件相同(特征标相同);函数重载是目的相同(函数名相同),条件不同(特征标不同).返回值类型函数指针必须相同,函数重载无要求.

        函数重载匹配 

        匹配原则1:和参数最接近的数据类型优先匹配.

        前面说过给函数传入参数必须满足的条件是:形参=传入的值       等式需成立 .那么问题来了,当传入的值同时可以传入两个函数时,该调用哪一个函数?举例:

void fun(int i);    //形参整型;
void fun(double d); //形参浮点型;

        fun(5)匹配void fun(int i);当没有定义void fun(int i)时,匹配void fun(double d)这是自动类型向上转换(整型值5自动转为浮点值5.0)而得来的.fun(5.0)匹配void fun(double d),不管有没有定义void fun(int i)都不会匹配到他,因为数据类型不会自动向下转换.

        匹配原则2:const值只能传给const形参.这是const修饰数据的特点,不懂的可以先复习.这并不是"匹配哪一个函数"的问题,而是是否能这样用的问题.举例:

void fun(const string& str);    //const引用作参数,记作函数A
void fun(string& str);          //引用作参数,记作函数B
============================
const string s="good";          //const值声明 
string s1="very good";          //非const值声明
============================
fun(s);                         //调用A,当未定义A时,不会调用B
fun(s1);                        //调用B,当未定义B时,调用A

       匹配原则3:和引用有关的匹配  ,截图自己看

        书上的例子:

函数重载互斥

        函数重载并不是随便定义的,当编译器不认识的时候不能定义函数重载,必须采用其他函数名定义函数.有几种情况会发生互斥:

        1.类型变量和类型引用互斥,不能形成函数重载

        

        您可能认为可以在此处使用函数重载,因为它们的特征标看起来不同。然而,请从编译器的角度来考虑这个问题。假设有下面这样的代码: 

        

        参数x与double x原型和double &x原型都匹配,因此编译器无法确定究竟应使用哪个原型。为避免这种混乱,编译器在检查函数特征标时,将把类型引用和类型本身视为同一个特征标(黑体字为本书原话)

        本着代码要多写的精神,果然发现问题.

        测试代码

#include<iostream>
using namespace std;

double cude(double d);		//二者只能出现一个
double cude(double& d);		//二者只能出现一个

int main(void) {
	double a = cude(5.0);	//没问题,可以调用非引用形参,5.0不能传给引用形参
	double& b = a;
	cout << a << endl;
	double c_val = cude(b);	//报错,有多个重载函数"cude"实例与参数列表匹配
}


double cude(double d) {
	return d;
}

double cude(double& d) {
	return d + 1;
};

         2.只有返回值类型不同,函数名和特征标相同的函数,不能形成函数重载.如前所述,返回值类型不是特征标的一部分.

测试代码

#include<iostream>
using namespace std;

int fun(int a);		//只能二选一定义
long fun(int a);	//错误定义,无法重载仅按返回类型区分的函数

         C++不允许以这种方式重载gronk( )。返回类型可以不同,但特征标也必须不同:         

         3.当函数重载和默认参数在一起使用时,默认参数不被看作特征标,有可能不形成函数重载. 

测试代码

#include<iostream>
using namespace std;

void print(int a, const string& str = "good");	//二选一
void print(int a);								//二选一

int main(void) {
	print(3);									//报错,有多个重载函数"print"实例与参数列表匹配
	print(3, "good");
}

void print(int a, const string& str = "good") {
	cout << "数字是:"<<a << "字符串是:" << str << endl;
	cout << "数字是:"<<a << "字符串是:" << str << endl;
}

void print(int a) {
	cout << "数字是:" << a << endl;
}

         函数重载互斥的解决办法:改变函数名称

如何使用函数重载 

        和前面的默认参数,函数模板一样,函数重载属于"锦上添花"的操作.

        实际应用中,类构造函数需要主动考虑函数重载. 

        此外只要使用了不同参数,就定义成不同名的函数,在优化代码的时候再考虑函数重载.

       

===================================内容分割线=============================  

题外话:函数重载是学习C++中又一个令人"烦躁"的地方.

            本来函数重载就是为了减少函数的数目,结果为了正确调用,要变得非常小心地编写程序.想减少工作量,反而增加了工作强度.学会了吧,实际用到的地方又不多.

===================================内容分割线============================   

后记 

        本书第8章后面还有函数模板的内容,这部分内容不少,特点和这篇帖子里的默认参数,函数重载一样,多数是用于优化代码,做"锦上添花"的事

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

jllws1

你的鼓励是我创作的动力,谢谢

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值