C++ Primer 第六章 函数 6.5~6.6 练习和总结

6.5 特殊用途语言特性

6.5.1默认实参

我们可以给形参列表中的形参添加默认的实参。
如下形式

int func(int height,int width=123,char color='')

1。注意给一个形参赋予默认实参之后,其后面的所有形参都要赋予默认实参,所以在设计函数的时候,尽可能的需要手动传入参数的形参放在形参列表的前面。

2.默认实参不可以是局部变量,表达式只要能够转化为形参所定义的类型,都可以作为默认实参。

int w = 123;
int h = 123;
int func(int height=h,int width=1+5,char color=''){
}

以上赋予默认实参的方式都是可以的。如果变量是全局变量,可以调用函数之前修改变量的值,调用函数之后,默认实参的取值为全局变量最后一次修改的值。(大白话就是,如果默认实参是一个全局变量,那么全局变量变化,默认实参也会变化,因为形参的赋值,只发生在调用的时候,所以在这之前全局变量可以一直变化。)

3.C++中建议在头文件中写函数的声明,在源文件中写函数的定义。我们可以在声明的时候为函数赋予默认的实参,需要注意的是,如果同一个函数声明了多次,那么后续只能给没有赋予默认实参的形参赋予默认实参

int func(int a,int b ,int c=1);
int func(int a,int b,int c =1);//错误c修改了默认实参
int func(int a,int b=1,int c=1)// 错误,c修改了默认实参

从上的代码可以看到,就算是一模一样的代码同时声明两次,也算修改了之前已经赋予默认实参的形参。

练习
6.40
b。因为有默认实参的形参后面所有的形参都要有默认形参

6.41
a。是非法的,因为ht没有默认实参,所以调用函数需要一个实参
b。合法
c。合法,但是和初衷不符,因为调用函数时,实参是从左往右给形参赋值,所以init(14,’*’)是分别给ht,和wd赋值。

6.42

string make_plural(size_t ctr,const string &word,const string &ending="s") {
	return (ctr > 1) ? word + ending : word;
}

make_plural(2,"success","es");
make_plural(2,"failure");

6.5.2 内联函数和constexpr函数

内联函数

把重复使用的代码封装成为函数,可以提高我们的开发效率,调用函数相对于直接使用表达式,调用函数的效率相对要低一些。

所以C++提供了inline关键字,inline可以将函数声明为内联函数,内联函数会在调用的地方展开成为表达式,以此提高运行效率。

扫描二维码关注公众号,回复: 9460921 查看本文章

但是什么时候将函数定义为内联函数是个值得商讨的问题,C++ Primer中并没说明,内联和普通函数之间的区别。

而且如果一个函数太长,声明为inline,编译器可以选择忽略,如果没有记错的话,有些编译器会将较短的普通函数变为内联函数

constexpr函数

我们可以用constexpr来修饰函数,表示函数会返回常量表达式,需要注意的是,constexpr函数不一定返回常量表达式.

只有当函数的返回值类型和所有形参类型都是字面值类型时时,返回常量表达式。

但是我在测试时,局部变量不是字面值,直接返回局部变量的结果,也可以作为常量表达式返回,我使用的编译器为vs2017。

constexpr int func(){
return 1;//返回的是常量表达式
}
constexpr int func(){
	int a = 1;
	return a;//返回的常量表达式
}
constexpr int func(int a){
return a;
}

func(1);// 是常量表达式
int a= 10;
func(a);//不是常量表达式

constexpr函数会被隐式的指定为inline函数

constexpr函数和inline函数可以被定义多次,但是他们通常在头文件中定义

*为什么这样,结合书上的原因就是,我个人觉得是这样的,constexpr函数和inline函数需要被嵌入为表达式。所以明确的需要知道函数体中的内容,而通常我们在头文件中定义函数,在源文件中修改函数。如果包含了头文件,编译的时候代码实际上只是调用了这个函数的声明,具体函数体的内容需要等到链接的时候才知道。
而声明在头文件中的话,我们在编译的时候就能将函数体的内容嵌入到调用处。
*

以上纯属个人猜测

练习

6.43
a。声明和定义都在头文件,因为它是内联函数,内联函数在编译时会在调用点替换为函数体的内容。
b。声明在头文件,定义在源文件。

6.44


inline bool isShorter(const string &s1,const string &s2) {
	return s1.size() < s2.size();
}

6.45
一般函数体结构较为简单的函数都可以声明为内联

6.46
不可以,因为返回值类型不是字面值类型

6.5.3 调试帮助

C++提供了一些便于调试的功能

一个是assert,其实就是断言,使用方法如下

assert(experssion);

如果表达式为真则不会发生任何事情,如果表达式为假,则会报错。

assert的行为依赖于预处理变量NDEBUG,如果开启了NDEBUG,则所有的assert都不会被触发。

我们可以通过定义宏,来手动的写一些调试

#ifndef NDEBUG
cout<<123<<endl;
#endif

除此之外,C++还预定义了一些变量,便于我们调试。


int func() {
	cout << __func__ << endl;//函数名
	cout << __LINE__ << endl;//当前行号
	cout << __TIME__ << endl;//文件编译时间
	cout << __FILE__ << endl;//文件名
	cout << __DATE__ << endl;//文件编译日期
	return 1;
}

可以利用这些东西为我们的代码,提供更多的错误信息。

练习
6.47

vs2017中定义了NDEBUG也没有反应。。。。

6.48

不正确,cin是一个对象,永远为true

6.6函数匹配

同名的函数可以有多个,在调用的时候就必然需要匹配一个合适函数。

匹配的流程如下:

1.首先是选取候选函数,候选函数是同调用的函数同名且可见的函数。因为如果在局部作用域中,声明了和调用函数一样的名字的函数或者变量,那么外部作用域的同名函数会被屏蔽。

2.从候选函数中,根据调用函数的实参数量和类型确定可行函数。可行函数的特征是形参类型和实参的类型一样或者可以相互转换,形参和实参的数量一样,如果有默认实参,那么默认实参的数量是可以忽略的。

3.从可行的函数中选出最佳匹配函数。

最佳的匹配函数一般是可行函数中形参和实参的类型完全一样的函数。

但是有时实参的类型并没有那么理想,实参需要经过转换才能变为形参的类型。所以这个时候就涉及到了函数匹配的机制问题。

这个机制总的来说就是,

如果形参只有一个
实参转换为形参的类型时,优先挑选挑选那些类型转换后精度不会出现损失的函数

void func(double i) {
	cout<<"double"<<endl;
}
void func(int i) {
	cout<<"int"<<endl;
}

float i = 1.0f;
func(i);//输出double

如果形参有多个。那么有且只有一个函数满足下面的条件,才算匹配成果。

1.该函数的每一个实参的匹配都不劣于其他可行函数需要的匹配
2.至少有一个实参的匹配,优于其他可行函数的匹配。

总结起来一句话就是,每一个实参的匹配都要>=其他函数的匹配,且至少有一个实参的匹配要>其他的函数的实参匹配。

所以下面的代码具有二义性,最佳匹配的函数不唯一

1.int func(int,int);
2.int func(double,double);


func(2,3.14);

从第一个参数来看,函数1更加匹配,但是从参数2来看,函数2更加匹配,没有任何一个函数可以完全胜过其他的函数,所以具有二义性。

练习

6.49
在之前的总结中已经写了,不再赘述

6.50
除了a不合法,其余的都合法,是因为匹配就有二义性。有多个函数可以匹配

6.6.1 实参类型转换

之前只是大概的说了一下匹配的规则,实参类型转换提供了实参类型转化的优先级。
在这里插入图片描述
我觉得第1点的第三小点和第二点有点像。

类型提升是,在计算时,小整型都会转化为int或者比int大的整型

算术类型转化是,在计算算术表达式的时候,运算对象会进行转换,
比如

char a  = 'a';
double b=  3.14;
int c = a+b;

a首先被提升为int,然后再转化为double,右侧表达式计算的结果再转化为int。

void func(int );
void func(short);
char a='a''
func(a);//调用的是int,因为char整型提升之后变成了int
void func(long);
void func(float);
func(4.14);//有二义性,因为doulbe可以转为float和long

关于算术类型的转化这里比较难理解,我的猜想是,双精度转化为单精度是会损失精度,double转long也会损失精度,所以这两个函数都可以转,但是没有最优。

函数匹配和const实参

这个之前就说过了,如果函数有引用和常量引用两种,

void func(const int &i);
void func(int &i);

这两个函数在调用的时候选谁,却决于实参是不是const,如果实参是const则选择将const形参的函数。如果实参不是常量,则选择普通函数。

void f(int& ,int &) {

}
void f(const int &,const int &){

}

const int a = 1;
	int b = 1;
f(a, b);

如果是实参既有const又有非const类型,那么选择形参声明为常量的函数,其实这里b已经通过类型转化转化为const类型了。

练习

6.52
a。等级3 类型提升
b。等级4, 算术类型转换

6.53
a。如果掉函数时,实参有常量类型,则调用第二条语句
b。如果调用函数时,实参有常量类型指针,则调用第二条语句
c。重复定义,指针的顶层const,不算重载。

发布了54 篇原创文章 · 获赞 6 · 访问量 3346

猜你喜欢

转载自blog.csdn.net/zengqi12138/article/details/104101982