4.1 基本的使用方法
4.1.1 以函数返回值之外的方式来返回值
这种手法其实已经在 1.2.6 节中说明过,本节让我们回头再总结一下。
在 C 中,可以通过函数返回值。可是,函数只能返回一个值。
在大型的程序中,经常需要通过返回值返回程序处理的状态(比如是否成功,如果失败,还需要返回失败的原因)。
如果将指针作为参数传递给函数,此后在函数内部对指针指向的对象填充内容,就可以从函数返回多个值。
此时,假设需要返回的数据的类型为 T,参数的类型就成为“指向 T 的指针”。
代码清单 4-1 中,将指向 int
和 double
的指针传递给函数,此后在函数内部对这两个指针指向的变量设定值。
要 点
如果需要通过函数返回值以外的方式返回值,将“指向 T 的指针”(如果想要返回的值的类型为 T)作为参数传递给函数。
代码清单 4-1 output_argument.c
- 1: #include <stdio.h>
- 2:
- 3: void func(int *a, double *b)
- 4: {
- 5: *a = 5;
- 6: *b = 3.5;
- 7: }
- 8:
- 9: int main(void)
- 10: {
- 11: int a;
- 12: double b;
- 13:
- 14: func(&a, &b);
- 15: printf("a..%d b..%f\n", a, b);
- 16:
- 17: return 0;
- 18: }
4.1.2 将数组作为函数的参数传递
本节的内容在 1.3.6 节中也进行过说明,这里再做一次总结。
在 C 语言中,数组是不能作为参数进行传递的。但可以通过传递指向数组初始元素的指针,使得在函数内部操作数组成为可能。
因此,在函数这一侧,通过
array[i]
这种方式,就可以引用数组的内容。因为在本质上,array[i]
只不过是*(array + i)
的语法糖。
代码清单 4-2 中,将数组 array
传递给 func()
,之后在 func()
内部将 array
的内容输出。
func()
还以参数 size
来接收数组 array
的元素个数。这是因为 array
只是一个指针,所以 func()
并不知道调用方数组的元素个数。
main()
中 array
的类型是“int
的数组”,因此,在 16 行可以用 sizeof
运算符取得数组元素的个数。
可是,在 func()
中,参数 array
的类型是“指向 int
的指针”,即使使用 sizeof(array)
,取出来的也只是指针自身的大小。
当然,我们可以模仿字符串的做法——在数组的末尾都加上'\0'
,通过在函数内部检索'\0'
,就可以计算出字符串中的字符个数。
代码清单 4-2 将数组作为参数传递
- 1: #include <stdio.h>
- 2:
- 3: void func(int *array, int size)
- 4: {
- 5: int i;
- 6:
- 7: for (i = 0; i < size; i++) {
- 8: printf("array[%d]..%d\n", i, array[i]);
- 9: }
- 10: }
- 11:
- 12: int main(void)
- 13: {
- 14: int array[] = {1, 2, 3, 4, 5};
- 15:
- 16: func(array, sizeof(array) / sizeof(int));
- 17:
- 18: return 0;
- 19: }
要 点
想要将类型 T 的数组作为参数进行传递,可以考虑传递“指向 T 的指针”。可是,作为被调用方是不知道数组的元素个数的,所以在必要的情况下,需要使用其他方式进行参数传递。
4.1.3 可变长数组
一般情况下,C 语言在编译时必须知道数组的元素个数,但是也可以使用 malloc()
在运行时再为数组申请必要的内存区域。
这种数组,在本书中被称为可变长数组*。
* 虽然我们经常这么叫,但是不能因此就认为这是最常见的称呼。
代码清单 4-3 中,首先让用户输入需要的内存大小(11~13 行),在第 15 行使用 malloc()
分配数组所需的内存区域(省略对返回值的检查)。
第 17~19 行,给数组赋值,并且在 20~22 行输出数组的内容。
代码清单 4-3 variable_array.c
- 1: #include <stdio.h>
- 2: #include <stdlib.h>
- 3:
- 4: int main(void)
- 5: {
- 6: char buf[256];
- 7: int size;
- 8: int *variable_array;
- 9: int i;
- 10:
- 11: printf("Input array size>");
- 12: fgets(buf, 256, stdin);
- 13: sscanf(buf, "%d", &size);
- 14:
- 15: variable_array = malloc(sizeof(int) * size);
- 16:
- 17: for (i = 0; i < size; i++) {
- 18: variable_array[i] = i;
- 19: }
- 20: for (i = 0; i < size; i++) {
- 21: printf("variable_array[%d]..%d\n", i, variable_array[i]);
- 22: }
- 23:
- 24: return 0;
- 25: }
如果想要修改已经分配了的可变长数组的大小,你可以使用 realloc()
。
代码清单 4-4 中,每当用户输入一个 int
类型的值,程序都会使用 realloc()
扩展 variable_array
的内存区域(这里也省略了对返回值的检查)。
代码清单 4-4 realloc.c
- 1: #include <stdio.h>
- 2: #include <stdlib.h>
- 3:
- 4: int main(void)
- 5: {
- 6: int *variable_array = NULL;
- 7: int size = 0;
- 8: char buf[256];
- 9: int i;
- 10:
- 11: while (fgets(buf, 256, stdin) != NULL) {
- 12: size++;
- 13: variable_array = realloc(variable_array, sizeof(int) * size);
- 14: sscanf(buf, "%d", &variable_array[size-1]);
- 15: }
- 16:
- 17: for (i = 0; i < size; i++) {
- 18: printf("variable_array[%d]..%d\n", i, variable_array[i]);
- 19: }
- 20:
- 21: return 0;
- 22: }
必须要引起注意的是,在使用 malloc()
实现可变长数组的时候,程序员必须自己来管理数组的元素个数。
这和在将数组作为参数进行传递时,被调用方无法知道数组长度的理由一样——malloc()
得到的不是数组,而是指针。
要 点
在需要获得类型 T 的可变长数组时,可以使用
malloc()
来动态地给“指向 T 的指针”分配内存区域。但此时需要程序员自己对数组的元素个数进行管理。
补充 Java 的数组
本书是 C 的参考书,在这里提到 Java 的数组只是给大家做一个参考。
Java 中的数组只能使用内存堆区域。在 C 的函数中写成下面这样:
- int hoge[10];
在大多数处理环境中,数组本身是分配在栈中的。Java 不能写成上面这样,而应该写成下面这样:
- int[] hoge = new int[10];
new
相当于 C 的malloc()
,上面的 Java 语句和下面的 C 语句具有几乎相同的意义,
- int hoge = malloc(sizeof(int) 10);
因此,在 Java 中经常使用指针引用数组。比如,使用下面的方式进行数组的赋值:
- int[] hoge = new int[10];
- int[] piyo = hoge;
如图 4-1 所示,这里的
hoge
和piyo
都是指向数组实体的指针变量。
图 4-1 将 Java 的数组赋给变量
可是,Java 的数组和在 C 中使用
malloc()
分配的数组,有决定性的不同:Java 的数组是知道自身的长度的。因此,对于
- int[] hoge = new int[10];
使用
hoge.length
,可以知道数组的长度为 10。这里为什么不是
hoge.length()
,也是 Java 的一个谜团,明明可以通过length()
获得String
的长度。此外,尽管 Java 的数组是保存在堆中的,但却不能改变长度,它没有类似于 C 的
realloc()
这样的函数。在很多 Java 语法的疑团中,这一点尤其让我感到费解。真是太不方便了!倘若要实现
array.setSize(newSize)
这样的功能,一旦调用realloc()
,地址就会发生变化,因此,通过使用指针或者句柄进行间接引用,来实现 VM 以外的 VM,是一件难度很高的事情……莫非是因为这个原因?特别是在通用性不太好的 Java 中,集合类库(Collection Library)提供的功能并不能完全满足需要。