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';
这样看是不是正常很多
列表初始化返回值
可以使用初始化列表对调用点的临时变量进行初始化。
这里我的理解是,调用点的临时变量的类型为返回值的类型,然后返回值类型使用初始化列表进行初始化,这样一来就和平时用初始化列表初始化变量的形式一样了。
如果是空列表{},则临时变量使用值初始化。
内置类型初始化列表中只能由一个值
类类型的列表初始化,由类类型自己决定。
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)
int factorial(int val){
if(val!=0){
return factorial(val-1)*val;
return 1;
那么如果传入的值尾负值,则递归会一致调用下去,直到栈溢出。
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“);会报错
同样,如果代码中有变量的名字和函数的名字一样,变量的名字也会屏蔽掉函数的名字。