C++ Primer 学习笔记_22_函数(续一) -return语句、递归调用、函数声明
函数
--return语句、递归调用、函数声明
三、return语句
1、没有返回值的函数
在返回值类型为void的函数中,return返回语句不是必需的,隐式的return发生在函数的最后一个语句完成时。
一般情况下,返回类型是void的函数使用return语句是为了引起函数的强制结束,这种return的用法类似于循环结构中的break语句的作用。
void do_swap(int &v1,int &v2) { int tmp = v1; v1 = v2; v2 = tmp; } void swap(int &v1,int &v2) { if (v1 == v2) return; return do_swap(v1,v2); }
2、在含有return语句的循环后没有提供return语句是很危险的,因为大部分的编译器不能检测出这个漏洞,运行时会出现什么问题是不确定的。
bool str_subrange(const string &str1,const string &str2) { if (str1.size() == str2.size()) return str1 == str2; string::size_type size = (str1.size() > str2.size() ? str1.size() : str2.size()); string::size_type i = 0; while (i != size) { if (str1[i] != str2[i]) return; //Error ++i; } //return true; //Warning }
3、主函数main的返回值:为了使返回值独立于机器,cstdlib头文件定义了两个预处理变量,分别用来表示程序运行成功和失败:
int main() { if (some_failure) return EXIT_FAILURE; else return EXIT_SUCCESS; }
4、返回非引用类型:如果函数返回非引用类型,则其返回值可以是局部对象,也可以是求解表达式的结果:
string make_plural(size_t ctr,const string &word,const string &ending) { return (ctr == 1 ? word : word + ending); }
这个函数要么返回其形参word的副本,要么返回一个未命名的临时string对象,这个临时对象是由字符串word和 ending的相加而产生的。这两种情况下,return都在调用该函数的地方复制了返回的string对象。
5、返回引用:当函数返回引用类型时,并没有复制返回值,相反,函数返回的是对象本身!
const string &shorterString(const string &str1,const string &str2) { return (str1.size() < str2.size() ? str1 : str2); }
形参和返回类型都是指向conststring 对象的引用,调用函数和返回结果时,都没有复制这些string对象。
6、在函数执行完毕时,将释放分配给局部对象的存储空间,此时,对局部对象的引用就会指向不确定的内存!
const string &manip(const string &str) { string s = str; return s; //Warning }
因此,千万不要返回:
1)局部对象的引用
2)指向局部对象的指针(悬垂指针!)
7、返回引用左值
char &get_val(string &str,string::size_type index) { return str[index]; } int main() { string s("a value"); cout << s << endl; for (string::size_type index = 0; index != s.size(); ++index) { s[index] = toupper(get_val(s,index)); } cout << s << endl; }
由于函数返回的是一个引用,因此这是正确的,该引用是被返回元素的同义词。如果不希望引用返回值被修改,返回值应该声明为const:
const char &get_val(...
//P215 习题7.19 int &get(int *arr,int index) { return arr[index]; } int main() { int ia[10]; for (size_t i = 0; i != 10; ++i) { get(ia,i) = i; //OK } for (size_t i = 0; i != 10; ++i) { cout << ia[i] << endl; } }
四、递归调用
直接或间接调用自己的函数称为递归函数。
1、两个简单例子
//1 int factorial(int val) { if (val > 1) return val * factorial(val - 1); return 1; }
//2 int rgcd(int v1,int v2) { if (v2 != 0) return rgcd(v2,v1 % v2); return v1; }
递归函数必须定义一个终止条件:否则,函数就会“永远”递归下去,直到程序栈耗尽!
//P217 习题7.20 int factorial(int val) { int res = 1; for (int i = 2; i <= val; ++i) { res *= i; } return res; }
五、函数声明
在函数声明中的形参名会被忽略,如果在声明中给出了形参的名字,它应该只用作辅助说明文档:
void print(int *arr,size_t size);
1、应该在头文件中提供函数声明:
在源文件中声明函数的方法比较呆板而且容易出错。解决的方法是把函数声明放在头文件中,这样可以确保对于指定函数其所有声明保持一致。如果函数接口发生变化,则只要修改其唯一的声明即可。
定义函数的源文件应该包含声明该函数的头文件。将提供函数声明头文件包含在定义该函数的源文件中,可使编译器能检查该函数的定义和声明时是否一致。
2、默认实参
如果有一个形参具有默认实参,那么,它后面所有的形参都必须有默认实参。因此,设计带有默认实参的函数,其中部分工作就是排列形参,应该使最少使用默认实参的形参排在最前面,最可能使用默认实参的形参排在最后。
3、指定默认实参的约束
既可以在函数声明也可以在函数定义中指定默认实参。但是,在一个文件中,只能为一个实参指定默认实参一次:
//ff.h int ff(int = 0); //ff.cc #include "ff.h" int ff(int a = 0) //Error { //... }
通常,应该在函数声明中指定默认实参,并将该声明放在合适的头文件中。
//P219 习题7.26 string make_plural(int cnt,const string &word,const string &ending = "s") { return (cnt == 1 ? word : word + ending); } int main() { string word = "Book"; string ending = "s"; cout << make_plural(1,word,ending) << endl; cout << make_plural(2,word) << endl; cout << make_plural(2,word,ending) << endl; }