【C++ primer】第6章 函数 (1)


Part I: The Basics
Chapter 6. Functions


6.1 函数基础

函数定义的组成部分:返回类型、函数名字、由 0 个或多个形参(parameter)组成的列表以及函数体。

通过调用运算符(call operator,())来执行函数。调用运算符作用于一个表达式,该表达式是一个函数或指向一个函数。括号内是逗号分隔的实参(argument)列表。实参用于初始化函数的形参。调用表达式的类型是函数的返回类型。

函数调用完成两项工作:用相应的实参初始化函数的形参,并将控制权转移给该函数。
调用函数(calling function)的执行被挂起,被调用函数(called function)的执行开始。

void f1(){ /* ... */ }     // implicit void parameter list
void f2(void){ /* ... */ } // explicit void parameter list
int f3(int v1, v2) { /* ... */ }     // error 
int f4(int v1, int v2) { /* ... */ } // ok

因为无法使用未命名的形参,所以形参一般都有名字。偶尔,函数会有不被使用的形参,此类形参通常不命名,以表示该形参不会在函数体内使用。

函数的返回类型不能是数组类型或函数类型,但可以是指向数组或函数的指针。

局部对象

在 C++ 中,名字有作用域,对象有生命周期(lifetime)。

自动对象(automatic object):只存在于块执行期间的对象。
形参是自动对象。

局部静态对象(local static object):在第一次执行经过对象定义前进行了初始化。函数结束时不会被销毁;在程序终止时被销毁。
通过将局部变量定义为 static 来获得此类对象。

size_t count_calls()
{
	static size_t ctr = 0;  // value will persist across calls
	return ++ctr;
}
int main()
{
	for (size_t i = 0; i != 10; ++i)
		cout << count_calls() << endl;
	return 0;
}

这段程序输出从 1 到 10 的数字。

如果局部静态变量没有显式的初始化程序,则会对其进行值初始化,这意味着内置类型的局部静态变量将初始化为零。

函数声明

// parameter names chosen to indicate that the iterators denote a range of values to print 
void print(vector<int>::const_iterator beg, vector<int>::const_iterator end);

三要素——返回类型、函数名、形参类型——描述函数的接口,制定了调用函数所需的全部信息。

函数声明也称作函数原型(function prototype)。

在头文件中进行函数声明。含有函数声明的头文件应该被包含到定义函数的源文件中。

分离式编译

假设在 factMain.cc 文件中创建 main 函数,在 main 函数中调用 fact 函数,fact 函数定义在 fact.cc 文件中。编译过程如下:

$ CC factMain.cc fact.cc   # generates factMain.exe or a.out 
$ CC factMain.cc fact.cc -o main # generates main or main.exe

其中,$ 是命令提示符,CC 是编译器的名字,# 后面是注释语句。

如果修改了一个源文件内容,只需重新编译改动的文件。大多数编译器提供了分离式编译每个文件的方式。这个过程会生成一个包含对象代码(object code)的文件,文件后缀名为 .obj(Windows)或 .o(UNIX)。
编译器把对象文件链接(link)在一起形成可执行文件(executable file)。

$ CC -c factMain.cc     # generates factMain.o 
$ CC -c fact.cc         # generates fact.o 
$ CC factMain.o fact.o  # generates factMain.exe or a.out 
$ CC factMain.o fact.o -o main # generates main or main.exe

6.2 参数传递

传值参数(Passing Arguments by Value)

// function that takes a pointer and sets the pointed-to value to zero 
void reset(int *ip) 
{
	*ip = 0;  // changes the value of the object to which ip points    
	ip = 0;   // changes only the local copy of ip; the argument is unchanged 
}
int i = 42; 
reset(&i);                    // changes i but not the address of i 
cout << "i = " << i << endl;  // prints i = 0

熟悉 C 的程序员常常使用指针类型访问函数外部的对象。在 C++ 语言中,建议使用引用类型的形参替代指针。

传引用参数(Passing Arguments by Reference)

// function that takes a reference to an int and sets the given object to zero 
void reset(int &i)  // i is just another name for the object passed to reset 
{
	i = 0;  // changes the value of the object to which i refers 
}
int j = 42; 
reset(j);  // j is passed by reference; the value in j is changed 
cout << "j = " << j  << endl;  // prints j = 0
  • 使用引用避免拷贝
    如果函数无需改变引用形参的值,建议将其声明为常量引用。
  • 使用引用形参返回额外信息
    引用参数为函数返回多个结果提供了有效途径。

const 形参和实参

尽量使用常量引用
把函数中不会改变的形参定义为普通的引用,这是错误的,会误导函数调用者,即函数可能修改其实参的值。
使用引用而不是 const 引用,会极大地限制函数所能接受的实参类型。

// bad design: the first parameter should be a const string& 
string::size_type find_char(string &s, char c, string::size_type &occurs);

类似 find_char("Hello World", 'o', ctr); 这样的调用将在编译时发生错误。

数组形参

数组的两个特殊性质:
①不允许拷贝数组,所以,无法以值传递的方式使用数组形参;
②使用数组时通常会被转换成指针,所以,当向函数传递一个数组时,实际上传递的是指向数组第一个元素的指针。

// each function has a single parameter of type const int* 
void print(const int*);
void print(const int[]);   // shows the intent that the function takes an array 
void print(const int[10]); // dimension for documentation purposes (at best)

上面 3 个声明是等价的:每条语句声明了一个包含一个形参的函数,其形参类型为 const int*

int i = 0, j[2] = {0, 1}; 
print(&i); // ok: &i is int* 
print(j);  // ok: j is converted to an int* that points to j[0]

管理指针形参的 3 种常用技术:

1. 使用标记指定数组长度
该方法要求数组本身包含一个结束标记。示例:C风格字符串,它存储在字符数组中,最后的字符后面跟着一个空字符。

void print(const char *cp) {
	if (cp)          // if cp is not a null pointer
		while (*cp)  // so long as the character it points to is not a null character
			cout << *cp++; // print the character and advance the pointer 
}

2. 使用标准库规范
传递指向数组首元素和尾后元素的指针。

void print(const int *beg, const int *end) {
	// print every element starting at beg up to but not including end
	while (beg != end)
		cout << *beg++ << endl; // print the current element, and advance the pointer 
}

int j[2] = {0, 1}; 
// j is converted to a pointer to the first element in j 
// the second argument is a pointer to one past the end of j 
print(begin(j), end(j)); // begin and end functions, see § 3.5.3 (p. 118)

3. 显式传递一个表示数组大小的形参

// const int ia[] is equivalent to const int* ia 
// size is passed explicitly and used to control access to elements of ia
void print(const int ia[], size_t size) {    
	for (size_t i = 0; i != size; ++i)
		cout << ia[i] << endl;
}

int j[] = { 0, 1 };  // int array of size 2 
print(j, end(j) - begin(j));

数组引用形参

// ok: parameter is a reference to an array; the dimension is part of the type 
void print(int (&arr)[10]) {
	for (auto elem : arr)
		cout << elem << endl;
}
int i = 0, j[2] = {0, 1}; 
int k[10] = {0,1,2,3,4,5,6,7,8,9}; 
print(&i);   // error: argument is not an array of ten ints 
print(j);    // error: argument is not an array of ten ints 
print(k);    // ok: argument is an array of ten ints

因为数组的大小是构成数组类型的一部分,这限制了此版本 print 的可用性,该函数只能作用于大小为 10 的数组。

注意:&arr 两端的括号必不可少。

f(int &arr[10])   // error: declares arr as an array of references
f(int (&arr)[10]) // ok: arr is a reference to an array of ten ints

传递多维数组

多维数组是数组的数组。所以,多维数组的首元素是数组,传递的指针是一个指向数组的指针。
数组的第二维(以及后面所有维度)的大小都是数组类型的一部分,不能省略。

// matrix points to the first element in an array whose elements are arrays of ten ints 
void print(int (*matrix)[10], int rowSize) { /* . . . */ }
// equivalent definition 
void print(int matrix[][10], int rowSize) { /* . . . */ } 

注意:第一种定义中,*matrix 两端的括号必不可少。

int *matrix[10];   // array of ten pointers 
int (*matrix)[10]; // pointer to an array of ten ints

main: 处理命令行选项

有时需要将实参传递给 main,最常见的用法是让用户指定一组选项来指导程序的操作。
例如,假设 main 程序位于名为 prog 的可执行文件中,则可以按以下方式将选项传递给程序:

prog -d -o ofile data0

这些命令行选项通过两个(可选的)形参传递给 main 函数。

int main(int argc, char *argv[]) { ... }

第二个参数 argv 是指向 C样式字符串的指针的数组。第一个参数 argc 传递该数组中的字符串数。
因为第二个参数是一个数组,所以可以将 main 定义为

int main(int argc, char **argv) { ... }

当参数传递给 main 时,argv 中的第一个元素要么指向程序名称,要么指向空字符串。后续元素传递命令行上提供的参数。最后一个指针之后的元素保证为 0

给定上面的命令行,argc 将为 5,而 argv 将包含以下 C样式字符串:

argv[0] = "prog";   // or argv[0] might point to an empty string 
argv[1] = "-d"; 
argv[2] = "-o"; 
argv[3] = "ofile"; 
argv[4] = "data0"; 
argv[5] = 0;

注意:在使用 argv 中的实参时,记住可选参数以 argv[1] 开始;argv[0] 包含程序的名称,而不是用户输入。

含有可变形参的函数

initializer_list 形参(C++11标准)

使用情况:函数实参数量未知,但全部实参的类型都相同。
initializer_list 是一种标准库类型,表示某种特定类型的值的数组;定义在 initializer_list 头文件中。

表6.1 initializer_list 上的操作

操作含义
initializer_list<T> lst;默认初始化;T 类型元素的空列表
initializer_list<T> lst{a, b, c ...};lst 中的元素数量与初始值一样多;其元素是对应初始值的副本;列表中的元素是 const
lst2(lst)复制或赋值一个 initializer_list 对象,不会复制列表中的元素;复制后,原列表和副本共享元素
lst2 = lst同上
lst.size()列表中的元素数量
lst.begin()返回指向 lst 中首元素的指针
lst.end()返回指向 lst 中尾后元素的指针

initializer_list 是一个模板类型。定义对象时,必须指定列表包含的元素类型。

initializer_list<string> ls; // initializer_list of strings 
initializer_list<int> li;    // initializer_list of ints

initializer_list 中的元素总是 const 值;没有方式改变 initializer_list 中元素的值。

例,使用如下形式编写输出错误信息的函数,使其可作用于可变数量的形参:

void error_msg(ErrCode e, initializer_list<string> il) {
	cout << e.msg() << ": ";
	for (const auto &elem : il)
		cout << elem << " " ;
	//也可以使用迭代器访问元素。
	//for (auto beg = il.begin(); beg != il.end(); ++beg)
	//	cout << *beg << " " ;
	cout << endl;
}

// expected, actual are strings 
if (expected != actual)
	error_msg(ErrCode(42), {"functionX", expected, actual}); 
else
	error_msg(ErrCode(0), {"functionX", "okay"});

如果想要传递一个值的序列给 initializer_list 形参,那么必须把序列放在一对花括号内

省略符形参

C++中的省略符形参,允许程序连接到,使用名为 varargs 的 C库工具的 C代码。通常,省略符形参不应该用于其他目的。C编译器文档中描述了如何使用 varargs

省略符形参应该仅用于C和C++通用的类型。特别是,大多数类类型的对象在传递给省略符形参时均无法正确复制。

省略符形参只能出现在形参列表中的最后一个位置,有两种形式:

void foo(parm_list, ...); 
void foo(...);

第一种形式指定一些 foo 形参的类型,对与指定形参对应的实参进行类型检查,但不会对与省略符形参相对应的实参进行类型检查。在第一种形式中,形参声明后面的逗号是可选的。

类型 va_list:保存有关实参信息。
该类型用作 <cstdarg> 中定义的宏的形参,以检索函数的其他实参。
va_start 初始化该类型的对象,使用方式:随后调用 va_arg 按顺序检索传递给该函数的其他实参。
在用 va_start 初始化 va_list 对象的函数返回之前,必须调用 va_end 宏。

操作含义
va_startvoid va_start (va_list ap, paramN);初始化一个变量实参列表。初始化 ap 以获取形参 paramN 之后的其他实参。
va_endvoid va_end (va_list ap);结束使用变量实参列表。保证使​​用对象 ap 检索其他实参的函数正常返回。
va_argtype va_arg (va_list ap, type)检索下一个实参。该宏扩展为类型 type 的表达式,其具有由 ap 标识的变量实参列表中当前实参的值。
va_copyvoid va_copy (va_list dest, va_list src);复制变量实参列表。将 dest 初始化为 src 的副本(处于其当前状态)。
/* va_arg example */
#include <stdio.h>      /* printf */
#include <stdarg.h>     /* va_list, va_start, va_arg, va_end */

int FindMax(int n, ...) {
	int i, val, largest;
	va_list vl;
	va_start(vl, n);
	largest = va_arg(vl, int);
	for (i=1; i<n; i++) {
		val = va_arg(vl, int);
		largest = (largest > val) ? largest : val;
	}
	va_end(vl);
	return largest;
}

int main() {
	int m = FindMax(7, 702, 422, 631, 834, 892, 104, 772);
	printf("The largest value is: %d\n", m);
	return 0;
}

输出:

The largest value is: 892

va_list 类型的对象只能用作 va_startva_argva_endva_copy 宏或使用它们的函数的实参,例如 <cstdio> 中的变量实参函数(vprintfvscanfvsnprintfvsprintfvsscanf)。

/* vsprintf example */
#include <cstdio>
#include <cstdarg>

void PrintFError(const char *format, ...) {
	char buffer[256];
	va_list args;
	va_start(args, format);
	vsprintf(buffer, format, args);
	perror(buffer);
	va_end(args);
}

int main() {
	FILE * pFile;
	char szFileName[] = "myfile.txt";

	pFile = fopen(szFileName,"r");
	if (pFile == NULL)
		PrintFError("Error opening '%s'", szFileName);
	else {
		// file successfully open
		fclose(pFile);
	}
	return 0;
}

如果文件 myfile.txt 不存在,输出错误信息如下:

Error opening file 'myfile.txt': No such file or directory

6.3 返回类型和 return 语句

return 语句有两者形式:

return; 
return expression;

值是如何被返回的
返回值的方式与初始化变量和形参的方式完全相同:返回值用于在调用点上初始化一个临时变量,该临时变量是函数调用的结果。

不要返回局部对象的引用或指针
函数完成后,它所占用的存储空间也随之被释放掉。因此,函数终止意味着局部变量的引用将指向不再有效的内存区域,局部指针将指向不存在的对象:

// disaster: this function returns a reference to a local object 
const string &manip() {
	string ret;   // transform ret in some way
	if (!ret.empty())
		return ret;     // WRONG: returning a reference to a local object!
	else
		return "Empty"; // WRONG: "Empty" is a local temporary string 
}

引用返回左值
函数的返回类型决定函数调用是否是左值。调用一个返回引用的函数得到的是左值,其他返回类型得到右值。

char &get_val(string &str, string::size_type ix) {
	return str[ix]; // get_val assumes the given index is valid
}
int main() {
	string s("a value");
	cout << s << endl;   // prints a value
	get_val(s, 0) = 'A'; // changes s[0] to A
	cout << s << endl;   // prints A value
	return 0;
}

若返回类型是 const 引用,则不能给调用的结果赋值。

列表初始化返回值
C++11标准规定,函数可以返回花括号包围的值的列表。
若列表为空,则对函数返回的临时变量进行值初始化。否则,返回值取决于函数的返回类型。

vector<string> process() {
	// . . .    
	// expected and actual are strings
	if (expected.empty())
		return {};  // return an empty vector
	else if (expected == actual)
		return {"functionX", "okay"}; // return list-initialized vector
	else
		return {"functionX", expected, actual}; 
} 

如果函数返回一个内置类型,花括号列表中最多包含一个值,并且该值一定不需要进行缩小转换。
如果函数返回一个类类型,由类本身定义初始值如何使用。

主函数 main 的返回值
如果函数的返回类型不是 void,那么它必须返回一个值。
这条规则有个例外:main 函数允许没有 return 语句直接结束。
如果控制到达 main 函数的结尾处而没有 return 语句,编译器隐式插入 return 0;

main 函数不能调用它自己。

返回数组指针

因为数组不能被复制,所以函数不能返回数组。但是,函数可以返回一个指向数组的指针或引用。

要想定义一个返回数组的指针或引用的函数,有 4 种方法。

1. 最直接的方法是使用类型别名

typedef int arrT[10];  // arrT is a synonym for the type array of ten ints 
using arrtT = int[10]; // equivalent declaration of arrT; see § 2.5.1 (p. 68) 
arrT* func(int i);     // func returns a pointer to an array of five ints

2. 声明一个返回数组指针的函数

int arr[10];          // arr is an array of ten ints 
int *p1[10];          // p1 is an array of ten pointers 
int (*p2)[10] = &arr; // p2 points to an array of ten ints

如果想定义一个返回指针数组的函数,数组的维度必须跟在函数的名字之后。
返回数组指针的函数的形式如下:

Type (*function(parameter_list))[dimension]

具体实例:

int (*func(int i))[10];

3. 使用尾置返回类型(C++11)
任何函数定义都可以使用尾随返回,但这种方式对于具有复杂返回类型的函数最有用,比如指向数组的指针或引用。
尾随返回类型(trailing return type)在形参列表之后,并以 -> 开头。为了表明返回类型在参数列表的后面,在返回值类型通常出现的地方使用 auto

// fcn takes an int argument and returns a pointer to an array of ten ints 
auto func(int i) -> int(*)[10];

4. 使用 decltype

int odd[] = {1,3,5,7,9}; 
int even[] = {0,2,4,6,8}; 
// returns a pointer to an array of five int elements 
decltype(odd) *arrPtr(int i) {
	return (i % 2) ? &odd : &even; // returns a pointer to the array 
}

注意:必须记住 decltype 不会自动将数组转换为其相应的指针类型。decltype 返回的类型是数组类型,必须在其中添加 * 以表明 arrPtr 返回一个指针。


【C++ primer】目录

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值