在C++程序中完成某些任务的主要方式就是调用函数。你若想描述如何进行某个操作,把它定义成函数是标准方式。注意,函数必须先声明后调用。
一个函数声明需要给出三部分信息:函数的名字、函数的返回值类型(如果有的话)以及调用该函数必须提供的参数数量和类型。例如:
Elem* next_elem(); //无参数,返回一个指向Elem的指针(一个Elem*) void exit(int); //接受一个int参数,不返回任何值 double sqrt(double); //接受一个double参数,返回的也是一个double类型
在一个函数声明中,返回类型位于函数名之前,参数类型位于函数名之后,并用括号包围起来。
参数传递的语义与初始化的语义是相同的。即,编译器会检查参数的类型,并且在必要时执行隐式参数类型转换。例如:
double s2 = sqrt(2); //用参数double{2}调用sqrt()函数 double s3 = sqrt(“three”); //错误:sqrt()函数要求参数类型是double
我们不应低估这种编译时检查和类型转换的价值。
函数声明可以包含参数名,这有助于读者理解程序的含义。但实际上,除非该声明同时也是函数的定义,否则编译器会简单忽略参数名。例如:
double sqrt(double d); //返回d的平方根 double square(double); //返回参数的平方结果
返回类型和参数类型属于函数类型的一部分。例如:
double get(const vector<double>& vec, int index); //函数类型:double(const vector<double>&, int)
函数可以是类的成员。对这种成员函数(member function),类名也是函数类型的一部分,例如:
char& String::operator[](int index); //函数类型:char& String::(int)
我们都希望自己的代码易于理解,因为这是提高代码可维护性的第一步。而令程序易于理解的第一步,就是将计算任务分解为有意义的模块(用函数和类表达)并为它们命名。这样的函数就提供了计算的基本词汇,就像类型(包括内置类型和用户自定义类型)提供了数据的基本词汇一样。C++标准算法(如find、sort和iota)提供了一个良好开端,接下来我们就能用这些表示通用或者特殊任务的函数组合出更复杂的计算模块了。
代码中错误的数量通常与代码的规模和复杂程度密切相关,多使用一些更短小的函数有助于降低代码的规模和复杂度。例如,通过定义函数来执行一项专门任务,在其他代码中我们就不必再为其编写一段对应的特定代码,将任务定义为函数促使我们为这些任务命名并明确它们的依赖关系。
如果程序中存在名字相同但参数类型不同的函数,则编译器会为每次调用选择最恰当的版本。例如:
void print(int); //接受一个整型参数 void print(double); //接受一个浮点型参数 void print(string); //接受一个字符串型参数 void user() { print(42); //调用print(int) print(9.65); //调用print(double) print(“Barcelona”); //调用print(string) }
如果存在两个可供选择的函数且它们难分优劣,则编译器认为此次调用具有二义性并报错。例如:
void print(int, double); void print(double, int); void user2() { print(0, 0); //错误:二义性调用 }
定义多个具有相同名字的函数就是我们所熟知的函数重载(function overloading),它是泛型编程的一个基本部分。当重载函数时,应保证所有同名函数都实现相同的语义。print()函数就是一个这样的例子:每个print()都将其实参打印出来。