1. 函数
1.1 定义函数
其中包含三部分:返回值的数据类型(double),函数名power和在圆括号中的函数的参数列表。注意:在函数头的最后不需要加上分号。
一般形式:
返回类型 函数名(参数列表) |
如果函数没有返回值,返回类型就由void关键字指定。
函数也可以没有任何参数:
void MyFunction() 或者: void MyFunction(void) |
注意参数的类型不能是void
注意:由于用void指定返回类型的函数没有返回值,因此这种函数不能再调用程序的表达式中使用。此函数不等于任何值,测试其值或者给它赋值是没有意义的。以这种方式使用这类函数,编译器会产生错误消息。
1.2 函数体
代码:使用函数
#include <iostream>
#include <iomanip>
using namespace std;
double power(double x, int n)
{
double result = 1.0;
if (n >= 0)
for (int i = 0; i < n; i++)
result *= x;
else
for (int i = 0; i < -n; i++)
result /= x;
return result;
}
int main()
{
cout << endl;
for (int i = -3; i <= 3; i++)
cout << setw(10) << power(8.0, i);
cout << endl;
return 0;
}
power()函数调用了7次,每次调用时,第一个参数都是8.0,而第二个参数i的值,从-3到+3。输出为7个值,分别是8-3,、8-2、8-1、80、81、82和83。
1.3 参数和变元
在调用函数值,要通过指定的变元把信息传送给函数。在调用时,变元放在函数名后面的圆括号中。如:
cout << std::setw(10) << power(8.0, i); |
在调用函数式,指定的变元将代替在函数定义中使用的参数。函数中的代码在执行时,就使用变元值来初始化对应的参数。
1.4 返回值
一般情况下,在调用返回类型不是void的函数时,该函数必须返回一个值,其类型已在函数头中指定。因此在调用函数power()的表达式中,power()实际上是一个double类型的值。
return语句
一般形式:
return expression; |
其中,expreesion必须等于在函数头中为返回值指定的类型的值。该表达式还可以是任何表达式,只要其值是指定的类型即可。它可以包含函数调用,甚至可以包含同一函数的调用。
如果返回类型指定为void,return语句中不应有表达式:
return; |
2. 函数的声明
可以再使用之前声明函数,或者通过函数原型来定义函数。
函数原型
可以把power()函数的函数原型写为:
double power(double x, int n); |
注意有分号
3. 给函数传送参数
3.1 按值传送机制
代码:给函数传送变元——编写一个函数,它试图修改它的一个变元,但是不会成功
#include <iostream>
#include <iomanip>
using namespace std;
double change_it(double it);
int main()
{
double it = 5.0;
double result = change_it(it);
cout << "After function execution, it = " << it << endl
<< "Result returned is " << result << endl;
return 0;
}
double change_it(double it)
{
it += 10.0;
cout << endl
<< "Within function, it = " << it << endl;
return it;
}
从输出中,可以看出,在函数change_it()中给变量it加上10,对main()中的变量it没有任何影响。
函数change_it()中的变量it时该函数的局部变量,在调用该函数时,会引用所传送的变元值的副本。
当然,在返回change_it()的局部变量it的值时,会制作其当前值的副本,把该副本返回给调用程序。
给函数传送指针
代码:传送指针——如上修改变元的值,这次成功了
#include <iostream>
#include <iomanip>
using namespace std;
double change_it(double* pointer_to_it);
int main()
{
double it = 5.0;
double result = change_it(&it);
cout << "After function execution, it = " << it << endl
<< "Result returned is " << result << endl;
return 0;
}
double change_it(double* pit)
{
*pit += 10.0;
cout << endl
<< "Within function, it = " << *pit << endl;
return *pit;
}
double change_it(double* pointer_to_it); |
在main()中,声明并初始化变量it后,就再调用change_it()函数时,给它传送变量it的地址:
double result = change_it(&it); |
不需要创建一个指针变量来存储it的地址。只需要给函数传送的地址,可以再函数调用使用地址运算符。
给函数传送数组
代码:把数组作为函数的参数传送
#include <iostream>
#include <iomanip>
using namespace std;
double average(double array[], int count);
int main()
{
double values[] = {1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0};
cout << endl
<< " Average = "
<< average(values, (sizeof values)/(sizeof values[0]))
<< endl;
return 0;
}
double average(double array[], int count)
{
double sum = 0.0;
for (int i = 0; i < count; i++)
sum += array[i];
return sum/count;
}
double average(double array[], int count); |
第一个参数指定为double类型的数组,不能在方括号中指定数组的大小,即使指定也没有效果。这是因为数组第一维的大小不是其类型的一部分(在考虑多维数组的指针时,也会有类似的问题)。
代码:在传送数组时使用指针表示法
#include <iostream>
#include <iomanip>
using namespace std;
double average(double* array, int count);
int main()
{
double values[] = {1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0};
cout << endl
<< " Average = "
<< average(values, (sizeof values)/(sizeof values[0]))
<< endl;
return 0;
}
double average(double* array, int count)
{
double sum = 0.0;
for (int i = 0; i < count; i++)
sum += *array++;
return sum/count;
}
这里的for循环,很有意思:注意修改的地方,函数原型和函数定义的参数,不是带有方括号的数组名,而是*array。
sum += *array++; |
按值传送机制复制了原数组地址,并把副本传送给函数。我们修改的是副本,原数组地址并没有修改。一般情况下,只要给函数传送一维数组,就可以把所传送的值作为指针,以任何方式修改地址。(这一段不太懂)
代码:传送多维数组
#include <iostream>
#include <iomanip>
using namespace std;
double yield(double array[][4], int n);
int main()
{
double beans[3][4] = { {1.0, 2.0, 3.0, 4.0},
{5.0, 6.0, 7.0, 8.0},
{9.0, 10.0, 11.0, 12.0}
};
cout << endl
<< "Yield = " << yield(beans, sizeof beans/sizeof beans[0])
<< endl;
return 0;
}
double yield(double array[][4], int count )
{
double sum = 0.0;
for (int i=0; i<count; i++)
for (int j=0; j<4; j++)
sum += array[i][j];
return sum;
}
函数yield()的第一个参数定义为一个数组,它有任意多行,但是每行有4个元素。
3.2 按引用传送机制
要指定引用类型,只需在类型名的后面加上&。
引用只是另一个变量的别名,引用类型的变量存储对其他变量的引用。在把函数参数指定为引用类型时,函数就要使用按引用传送机制来传送参数。在调用函数时,对应于引用参数的变元不会复制,因为参数名就是调用程序中该变元值的别名。只要在函数体中使用参数名,它就会直接访问调用函数值中的变元值。
引用有风险:引用很高效,但是也有负面影响(安全性等)。
代码:引用参数
#include <iostream>
#include <iomanip>
using namespace std;
int larger(int& m, int& n);
int main()
{
int value1=10;
int value2=20;
cout<<endl<<larger(value1, value2)<<endl;
return 0;
}
int larger(int& m, int& n)
{
return m>n ? m : n;
}
使用常量引用
int larger(const int& m, const int& n); |
现在函数可以处理变量和常量了。只要不打算修改传送过来的参数,就总是可以把参数定义为const。
使用const引用参数,就可以获得引用参数的高性能和高效率,以及按值传送方法的安全性。
引用和指针
在大多数情况下,使用引用参数比使用指针更好。只要可能就应该把引用参数声明为const,因为这会为调用程序提供参数的安全性。
二者区别:指针和引用的一个重要区别是,指针可以为空,而引用总是要引用某个数据项——当然,只要它不是空指针的一个别名。如果允许参数为空,惟一的选择就是指针参数。指针参数可以为空,所以必须在解除对指针的引用之前测试它。如果试图解除对空指针的引用,程序就会崩溃。
声明引用
引用不会显示为函数的参数。引用可以以另一个变量的别名独立存在。假定有一个变量声明为:
long number = 0; |
类型long后面的宏&表示声明一个引用,等号后面的初始化值指定为变量名number。因此,rnumber是“引用long”类型。
同声明指针一样,&也可以放到变量名前面:
long &rnumber = number; |
一些约束:
1. 声明引用时,引用必须总是初始化为变量的一个别名。
2. 在声明引用时,决不能不对它进行初始化。(必须初始化)
3. 引用时固定的,在声明之后就不能修改,它总是同一个变量的别名。
使用指针和使用引用有一个显著区别:指针需要解除引用,才能获得或操作它指向的变量值,而引用不需要这一步。在某些方面,引用类似于已解除引用的指针,但引用不能修改为引用另一个变量。
引用是与引用的变量完全等价的。
3.3 main()的参数
函数main()可以有参数:
int main(int argc, char* argv[]){ //Code for main() } |
4. 默认的参数值
在许多情况下,给一个或多个函数参数赋予默认值是非常有用的。这意味着,仅需要在希望参数值不同于默认值时,为参数指定值。
例如:有一个函数要用于输出标准的错误消息。大多数情况下,使用默认的消息就足够了,但有时候需要指定另一个消息。为此,可在函数原型中为参数指定默认值。输出消息的函数的定义如下:
void show_error(const char* message) { cout<<endl<<message<<endl; } |
要指定默认消息,可以再这个函数的原型中编写一个字符串,用作默认的参数值,如下:
void show_error(const char* message=”Program Error”); |
如果要使用该函数输出默认的消息,在调用它可以不指定参数,如下:
show_error(); |
该函数会显示结果:
Program Error |
如果要提供某个消息,就可以指定函数的参数:
show_error(“Nothing works!”); |
下面使用string类型的参数定义show_error()函数,而不是C样式的字符串。
void show_error(const string message=”Program Error”); |
代码:使用多个默认参数值
#include <iostream>
#include <iomanip>
#include <string>
using namespace std;
void show_data(const int data[], int count=1, const string& title = "Data Values", int width = 10, int perLine = 5);
int main()
{
int samples[]={1,2,3,4,5,6,7,8,9,10,11,12};
int dataItem=99;
show_data(&dataItem);
dataItem=13;
show_data(&dataItem, 1, "Unlucky for some!");
show_data(samples, sizeof samples/sizeof samples[0]);
show_data(samples, sizeof samples/sizeof samples[0], "samples");
show_data(samples, sizeof samples/sizeof samples[0], "samples", 14);
show_data(samples, sizeof samples/sizeof samples[0], "samples", 14, 4);
return 0;
}
void show_data(const int data[], int count, const string& title, int width, int perLine)
{
cout<<endl<<title;
for(int i=0; i<count; i++)
{
if(i % perLine==0)
cout<<endl;
cout<<setw(width)<<data[i];
}
cout<<endl;
}
5. 从函数中返回值
5.1 返回一个指针
在从函数中返回一个指针时,必须确保它指向的地址是0,或者在调用函数仍旧有效的内存地址。换言之,在指针返回到调用函数中后,指针所指向的变量必须仍在其作用域中。其中的黄金规则:不要从函数中返回自动局部变量的地址。
例如:
int* larger(int* a, int* b) { if(*a>*b) return a; else return b; } |
可以用以下代码调试该函数:
*larger(&value1, &value2)=100; |
5.2 返回一个引用
引用的黄金规则:不要从函数中返回自动局部变量的引用。
例如:
int& larger(int& m, int& n) { return m>n?m:n; } |
返回类型是对int的引用,参数是非常量的引用。我们要返回某个引用参数,就不能把参数声明为const。
用以下语句来使用该函数修改两个参数中的较大值:
larger(value1,value2)=50; |
5.3 从函数中返回新变量
在函数中,可以再自由存储区中创建新变量,并通过指针返回值将它返回给调用程序。使用new运算符就可以为新变量分配内存空间,并返回其地址。
危险大,内存泄露的可能性非常高。每次调用这样的函数时,都要在自由存储区中分配更多的内存。调用函数应负责使用delete运算符来释放这些内存。
6. 内联函数
如果函数非常短,编译器为处理传送过来的参数以及返回结果的代码的系统开销,与进行实际计算的代码相比就非常多。这两类代码的执行时间非常相关。
在极端情况下,调用函数的代码占用的内存会比函数体中的代码还多。在这种情况下,编译器就应使用函数体中的实际代码替代函数的调用,并做适当的调整,已处理局部名称。这会使得程序更短更快。
为了让编译器完成此任务,可以在函数的定义中使用inline关键字。如:
inline int larger(int m, int n) { return m>n?m:n; } |
通过这个定义,编译器就会用内联代码替代调用。
但是这只是一个建议,它取决于编译器是否采纳这个建议。把函数声明为inline,函数的定义就必须可以再调用函数的每个源文件中使用。因此,内联函数的定义通常放在头文件中,而不是源文件中,该头文件包含在使用该函数的每个源文件中。
(不同的编译器采用不同的规则来确定定义为inline的函数是否用内联代码代替其调用。
有时编译器选择不按照请求把函数看做内联函数,这有个缺点:在这个情况下,函数调用会按照正常的函数调用来编译,但编译器一般还是会把该函数看做源文件的本地函数,所以每个使用它的源文件都要拥有该函数的已编译副本。结果是,如果函数在几个不同的源文件中使用,函数的代码就要进行不必要的重复。
7. 静态变量
前面编写的所有函数中,函数体在每次执行之后都不会保留任何信息。假定要计算某个函数的调用次数,怎么办?
一种方法:在文件作用域定义一个变量,然后再在函数中递增它。但是这个方法不好控制。
另一种方法:在函数体重把该变量声明为static,静态变量。
静态变量在定义它的语句中创建,在此之后,它一直存在,知道程序结束为止。
下面的函数示例声明了一个静态变量:
void nexInterger() { static int count=1; cout<<endl<<count++; } |
只要程序在执行,以后对函数的调用都会使用count的当前值。
代码:在函数中使用静态变量
#include <iostream>
#include <iomanip>
#include <string>
using namespace std;
long next_Fibonacci();
int main()
{
cout<<endl<<"The Fibonacci Series"<<endl;
for(int i=0; i<30; i++)
{
if(i%5==0)
cout<<endl;
cout<<setw(12)<<next_Fibonacci();
}
cout<<endl;
return 0;
}
long next_Fibonacci()
{
static long last=0;
static long last_but_one=1;
long next = last + last_but_one;
last_but_one = last;
last = next;
return last;
}
只要程序存在,静态变量就存在,但静态变量只能在声明它们的块中访问,所以变量last和last_but_one只能在next_Fibonacci()函数体中访问。