前言
"打牢基础,万事不愁" .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章后面还有函数模板的内容,这部分内容不少,特点和这篇帖子里的默认参数,函数重载一样,多数是用于优化代码,做"锦上添花"的事