C++ Primer 第六章 函数 6.3~6.4 练习和总结

6.3 返回类型和return语句

返回值类型分为两种,一种是有返回值类型,还有一直是无返回值类型,

无返回值类型的函数,可以只写一个return;也可以不写return

有返回值类型的函数,必须返回一个值,该值为返回的类型或者可以隐式的转化为返回值类型

无返回值

return;

有返回值

return experssion;//返回一个表达式,表达式由一个或者多个求值对象组成。

值是如何被返回的?

返回的值会在调用点初始化一个临时量,这个临时量就是函数调用的结果。

返回值类型可以是引用类型也可以是指针类型。需要注意的是,不要返回局部变量的引用以及指针类型。因为局部变量在函数调用结束之后,声明周期就结束了,内存被回收,所以返回局部变量的引用或者指针将产生未定义行为

int & f(){
	int num=1;
	return num;
}

引用返回左值

返回值类型为引用的话,返回的是左值,既然是左值就拥有左值的属性,如果返回的是一个非常量的引用,那么函数可以放在赋值语句的左边


char& get_value(string &str,string::size_type index){
	return str[index];	
}

string str="123";
get_value(str,1)='c';
cout<<str<<endl;
>>> 1c3

看起来有点别扭,但是试想以下,如果是类的成员函数这样使用,看起来就正常很多了

String str="123";
str.get_value(1)='c';

这样看是不是正常很多

列表初始化返回值

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

可以使用初始化列表对调用点的临时变量进行初始化。

这里我的理解是,调用点的临时变量的类型为返回值的类型,然后返回值类型使用初始化列表进行初始化,这样一来就和平时用初始化列表初始化变量的形式一样了。

如果是空列表{},则临时变量使用值初始化。

内置类型初始化列表中只能由一个值

类类型的列表初始化,由类类型自己决定。

main函数的返回值

main函数可以不写返回值,如果不写的话,默认就是return 0;return 0表示程序执行成功,如果return 其他的值,含义则和具体的机器有关。

函数可以在函数体中调用自己,无论是直接调用还是间接调用,这样调用的方式叫做递归。。递归最终要有终止条件,不然递归会一直进行下去直到调用栈满了,然后报错。

练习

6.30

bool str_subrange(const string& str1,const string &str2) {
	if (str1.size()==str2.size()) {
		return str1 == str2;
	}
	auto size = str1.size() < str2.size() ? str1.size() : str2.size();
	for (decltype(size) i = 0; i < size;++i ) {
		if (str1[i]!=str2[i]) {
			return false;//这里会报错,提示没有返回值
		}
	}
	//return false;这里不写只会提示警告
}

6.31
当返回的是局部变量的引用时,返回的引用是无效的。

同样返回的是局部变量的常量引用是无效的。

6.32
是正确的,该函数返回数组,对应index下标所对应的元素的引用

6.33


void print(vector<int>::iterator beg,vector<int>::iterator end) {
	if (beg==end) {//要有终止条件
		return;
	}
	else {
		cout<<*beg<<endl;
		print(beg+1,end);
	}
}

6.34

如果把终止条件加上 if(val!=0)

if factorial(int val){
	if(val>1){
	return factorial(val-1)*val;
}
if(val!=1){
return 1;
}

可见,当val等于1
的时候,并没有对应的return语句。 在vs2017中会编译器会发出警告,执行之后,得到一个不可预料的值。

6.35

因为val–,返回的是val-1之前的副本,这样程序永远都不会结束。

6.3.3 返回数组指针

之前说了,形参数组,返回值也可以是数组。但是数组是不能够拷贝的,而且数组在使用时变成了指向其列表第一个元素的指针,所以我们不能直接返回数组,但是可以返回数组的引用 或者指针。

有四种方式可以返回数组的指针或者引用

1.最原始的方法,这个方法和声明数组引用很像。
只不过变量名变成了函数名()

string (&get_string_arr())[10] {

}

2.使用类型别名

using string_arr_10 = string[10];
string_arr_10& get_string_arr() {

}

这种方式非常的直观,可读性也很高

3.尾置返回类型

auto get_string_arr()->string(&)[10]{
	
}

在平时填返回值类型的地方,使用auto关键字,使用->来写上真的返回类型。

这种方式看起来也非常的直观,直到返回的就是大小为10的string数组的引用。不需要额外语句。

4.使用decltype

string arr[10];
decltype(arr)& get_string_arr() {
}

这种方式需要先定义一个变量,我觉得最不好用。。

下面这种方式看起来也是很清晰,然而这种方式不错误的,编译不了。

string(&)[10] get_string_arr() {
}

练习
6.36
已经写在上面了

6.37
尾置返回类型,因为尾置返回类型不需要额外的定义语句,而且可读性很高

6.38
把*换成&就可以了。

函数重载

什么是函数重载,C++ Primer中说的很明白,如果同一个作用域内的几个函数名字相同但是形参列表不同,我们称之为重载函数。

所以下面的类型都是重载函数

int func(int i);
int func(double i);
int func(string i);

注意,main函数不能重载,同时main函数也不也能递归调用。

我们定义的多个重载函数,在调用时由实参的类型来确定,要注意的是,某些函数我们认为是重载的,但是其实它们就是同一个函数

1.返回值类型不同,不是重载函数,这个从重载函数的定义就可以看到,定义中说的是形参列表不同,函数名相同

double func(int i);
int func(int i);

2.变量名字不同,不是重载函数 ,这些定义的其实是一个函数。形参的名字并不作为重载函数的判断条件。

int func(int);
int func(int a);
int func(int b);

3.类型别名,定义的形参列表和原来的类型不构成重载函数,INT本质上还是int,所以不构成重载函数。

using INT = int;
int func(INT i);
int func(int i);

4.形参是顶层const的形参(不是复合类型)无法和非const形参(不是复合类型)区分开来,不是重载函数。

int func(const int i);
int func(inti i);

int fun(int * i);
int func(int * const i)

以上的两种情况都不是重载函数。

5.形参如果是某种类型的引用或者指针,定义了const的函数和没有定义const的函数可以区分开来,构成重载函数。

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

int func(int* i);
int func(const int* i);

复合类型的const和非const形参还是可以区分开来的。

但是在调用的时候,实参如果是非const,那么会优先调用非const的函数。

之前在介绍函数的返回值类型时,写了函数可以返回引用。但是有这样一个情况。

const string& get_short_string(const string &s1,const string &s2){
return s1.size()<=s2.size()?s1:s2;
}

上面这个函数获取较短的字符串,但是返回的结果为const string& 类型,这就意味着无法修改它内部的值(当然使用const_cast 可以修改,但是这意味着我们的程序设计有问题)。

所以为了满足可以修改其内部的值,我们可以定义一个函数

string & get_short_string(string &s1,string &s2){
	auto &r = get_short_string(const_cast<const string&>(s1),const_cast<const string7>(s2));
	return const_cast<string&>(r);
}

使用这个函数得到的string类型是可以修改的,这样如果我们传入的类型是非常量的类型,那么调用的就是这个函数,返回的类型可以修改,如果我们传入的实参是常量类型,那么调用的就是返回常量的版本

调用函数的重载

我们定义了多个重载函数,那么编译器该怎么决定调用哪一个呢。
之前说了是按照实参的类型和数量来确定。

但是有些时候,不同函数的形参类型可以相互转化时就不太好确定了,这里怎么确定还没看

目前需要i记住的是。
1。编译器会选择一个实参最匹配的函数来调用
2.如果能够匹配多个函数,则编译器报错
3.如果没有任何一个函数可以匹配,则编译器报错

练习

a。两个函数的形参不构成重载
b。返回值类型不同,不构成重载
c。没问题

6.4.1 重载和作用域

函数的声明和定义一般都会定义在所有的作用域之外,但是也可以定义在局部作用域内。

和之前变量在局部作用域中的属性一样,局部作用域中的变量会屏蔽到外层的同名函数。

int func(double);
int func(string)
int main(){
	int func(int);
	func(123);//调用int
	func(123.3);//double转化为int
	func("123");//报错
}


int main(){
int func;
func(1231);
}

上面的代码中,局部定义域中声明了函数func,屏蔽了外部定义的函数。所以func(“123“);会报错

同样,如果代码中有变量的名字和函数的名字一样,变量的名字也会屏蔽掉函数的名字。

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

猜你喜欢

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