C++ Primer 学习笔记_22_函数(续一) -return语句、递归调用、函数声明

C++ Primer 学习笔记_22_函数(续1) --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对象,这个临时对象是由字符串wordending的相加而产生的。这两种情况下,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;
}