函数
基础知识
定义函数
没有返回值的函数1
2
3
4
5void functionName(parameterList)
{
statement(s)
return; // optional
}
其中,parameterList指定了传递给函数的参数类型与数量。
有返回值的函数将生成一个值,并将它返回给调用函数。1
2
3
4
5typeName functionName(parameterList)
{
statements
return value; // value is type cast to type typeName
}
如果函数包括多条返回语句,则在执行第一条返回语句时结束。
函数和数组
考虑如下函数:1
int sum_arr(int arr[], int n) // arr = array name, n = size
这看起来非常合理。方括号指出arr是一个数组,而方括号为空表明,可以将任意长度的数组传递给此函数。但实际上,arr并不是一个数组,而是一个指针。不过,在编写函数时,可以将arr看作是数组。
函数如何使用指针处理数组
在大多数情况下,C++和C语言一样,将数组名视为指针。C++将数组名解释为第一个元素地址。
1 | cookies == &cookies[0]; // array name is address of first element |
注意:只有当用于函数头或者函数原型时,int *arr和 int arr[]的含义才是相同的
下列的恒等式值得记忆:
1 | arr[i] == *(arr + i) |
示例
填充数组
用于接受数组名参数的函数访问的是原始数组,而非其副本,因此可以通过调用该函数将值赋给数组元素。该函数的一个参数是要填充的数组名称。通常,不能在函数中设置数组长度,而要将数组长度作为第二个参数传递。由于用户输入的元素数目可能少于数组长度,因此函数应该返回实际输入的元素数目。因此,函数原型如下:1
int fill_array(double ar[], int limit);
可以使用循环连续地将值读入到数组中,但如何提早结束呢?一种方法是,使用一个特殊值来指出输入结束。由于所有属性非负,可以使用负数来指出输入结束。另外,该函数应对错误输入作出反应,如停止输入等。这样,函数如下所示:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23int fill_array(double ar[], int limit)
{
using namespace std;
double temp;
int i;
for (i = 0; i < limit; i++)
{
cout << "Enter value #" << (i + 1) << ": ";
cin >> temp;
if (!cin) // bad input
{
cin.clear();
while (cin.get() != '\n')
continue;
cout << "Bad input; input process terminated.\n";
break;
}
else if (temp < 0)
break;
ar[i] = temp;
}
return i;
}
显示数组及用const保护数组
为防止函数无意中修改数组的内容,可在声明形参时使用关键字const:1
void show_array(const double ar[], int n);
该声明表明,指针ar指向的是常量数据。这意味着不能使用ar修改该数据,也就是说,可以使用像ar[0]这样的值,但不能修改。下面是show_array()函数的代码:1
2
3
4
5
6
7
8
9void show_array(const double ar[], int n)
{
using namespace std;
for (int i = 0; i < n; i++)
{
cout << "Property #" << (i + 1) << ": $";
cout << ar[i] << endl;
}
}
修改数组
在这个例子中,对数组进行的第三项操作是将每个元素与同一个重新评估因子相乘。需要给函数传递3个参数:因子、数组和元素数目。该函数不需要返回值,因此其代码如下:1
2
3
4
5void revalue(double r; double ar[], int n)
{
for (int i = 0; i < n; i++)
ar[i] *= r;
}
由于此函数将修改数组的值,因此在声明ar时,不能使用const。
指针和const
可以用两种不同方式将const关键字用于指针。第一种方法是让指针指向一个常量对象,这样可以防止使用该指针来修改所指向的值。第二种方法是将指针本身声明为常量,这样可以防止改变指针指向的位置。下面来看细节。首先,声明一个指向常量的指针pt:1
2int age = 39;
const int * pt = &age;
该声明指出,pt指向一个const int(这里为39),因此不能使用pt来修改这个值。换句话来说,*pt的值为const,不能被修改:1
2*pt +=1; // INVALID because pt points to a const int
cin >> *pt; // INVALID for the same reason
现在有一个微妙的问题。pt的声明并不意味着它指向的值实际上就是一个常量,而只是意味着对pt而言,这个值是常量。例如,pt指向age,而age不是const。可以直接通过age变量来修改age的值,不能使用pt指针来修改它:1
2*pt = 20; // INVALID because pt points to a const int
age = 20; // VALID because age is not declared to be const
以前我们将常规变量的地址赋给常规指针,而这里将常规变量的地址赋给const的指针。因此还有两种可能:将const变量的地址赋给const的指针、将const的地址赋给常规指针。这两种操作都可行吗?第一种可行,但第二种不可行:1
2
3
4
5const float g_earth = 9.80;
const float * pe = &g_earth; // VALID
const float g_moon = 1.63;
float *pm = &g_moon; // INVALID
对于第一种情况来说,既不能使用g_earth来修改值9.80,也不能使用pe来修改。C++禁止第二种情况的原因很简单——如果将g_moon的地址赋给pm,则可以使用pm来修改g_moon的值,这使得g_moon的const状态很荒谬,因此C++禁止将const的地址赋给非const指针。如果非要这样做,可以使用强制类型转换来突破限制,详情在之后讨论运算符const_cast是会提到。
如果将指针指向指针,则情况将更复杂。前面讲过,假如涉及的是一级间接关系,则将非const指针赋给const指针是可以的:1
2
3int age = 39; // age++ is a valid operation
int * pd = &age; // *pd = 41 is a valid operation
const int * pt = pd; // *pt = 42 is an invalid operation
然而,进入两级间接关系时,与一级间接关系一样,将const和非const混合的指针赋值方式将不再安全。如果允许这样做,则可以编写这样的代码:1
2
3
4
5
6const int **pp2;
int *p1;
const int n = 13;
pp2 = &p1; // not allowed, but suppose it were
*pp2 = &n; // valid, but sets p1 to point at n
*p1 = 10; // valid, but changes const n
上述代码将非const地址(&p1)赋给了const指针(pp2),因此可以使用p1来修改const数据。因此,当且仅当只有一层间接关系(如指针指向基本数据类型)时,才可以将非const地址或指针赋给const指针。
注意:如果数据类型本身并不是指针,则可以将const数据或非const数据的地址赋给指向const的指针,但只能将非const数据的地址赋给非const指针。
假设有一个由const地址组成的数组:1
const int months[12] = {31,28,31,30,31,30,31,31,30,31,30,31};
则禁止将常量数组的地址赋给非常量指针将意味着不能将数组名作为参数传递给使用非常量形参的函数:1
2
3int sum(int arr[], int n); // should have been const int arr[]
...
int j = sum(months, 12);
上述函数调用试图将const指针(months)赋给非const指针(arr),编译器将禁止这种函数调用。
尽可能使用const
将指针参数声明为指向常量数据的指针有两条理由:
- 这样可以避免由于无意间修改数据而导致的编程错误;
- 使用const使得函数能够处理const和非const实参,否则将只能接受非const数据。如果条件允许,则应将指针形参声明为指向const的指针。
为说明另一个微妙之处,请看下面的声明:1
2int age = 39;
const int * pt = &age;
第二个声明中的const只能防止修改pt指向的值(这里为39),而不能防止修改pt的值。也就是说,可以将一个新地址赋给pt:1
2int sage = 80;
pt = &sage; // okay to point to another location
但仍然不能使用pt来修改它指向的值(现在为80)。
第二种使用const的方式使得无法修改指针的值:1
2
3int sloth = 3;
const int * ps = &sloth; // a pointer to const int
int * const finger = &sloth; // a const pointer to int
在最后一个声明中,关键字const的位置与以前不同。这种声明格式使得finger只能指向sloth,但允许使用finger来修改sloth的值。中间的声明不允许使用ps来修改sloth的值,但允许将ps指向另一个位置。简而言之,finger和*ps都是const,而*finger和ps不是。
如果愿意,还可以声明指向const对象的const指针:1
2double trouble = 2.0E30;
const double * const stick = &trouble;
其中,stick只能指向trouble,而stick不能用来修改trouble的值。简而言之,stick和*stick都是const。
通常,将指针作为函数形参来传递时,可以使用指向const的指针来保护数据。例如:1
void show_array(const double ar[], int n);
在该声明中使用const意味着show_array()不能修改传递给它的数组中的值。只要只有一层间接关系,就可以使用这种技术。例如,这里的数组元素是基本类型,但如果它们是指针或指向指针的指针,则不能使用const。
