4.1 基本的使用方法

4.1.1 以函数返回值之外的方式来返回值

这种手法其实已经在 1.2.6 节中说明过,本节让我们回头再总结一下。

在 C 中,可以通过函数返回值。可是,函数只能返回一个值。

在大型的程序中,经常需要通过返回值返回程序处理的状态(比如是否成功,如果失败,还需要返回失败的原因)。

如果将指针作为参数传递给函数,此后在函数内部对指针指向的对象填充内容,就可以从函数返回多个值。

此时,假设需要返回的数据的类型为 T,参数的类型就成为“指向 T 的指针”。

代码清单 4-1 中,将指向 intdouble 的指针传递给函数,此后在函数内部对这两个指针指向的变量设定值。

要 点

如果需要通过函数返回值以外的方式返回值,将“指向 T 的指针”(如果想要返回的值的类型为 T)作为参数传递给函数。

 

代码清单 4-1 output_argument.c

  1. 1: #include <stdio.h>
  2. 2:
  3. 3: void func(int *a, double *b)
  4. 4: {
  5. 5: *a = 5;
  6. 6: *b = 3.5;
  7. 7: }
  8. 8:
  9. 9: int main(void)
  10. 10: {
  11. 11: int a;
  12. 12: double b;
  13. 13:
  14. 14: func(&a, &b);
  15. 15: printf("a..%d b..%f\n", a, b);
  16. 16:
  17. 17: return 0;
  18. 18: }

4.1.2 将数组作为函数的参数传递

本节的内容在 1.3.6 节中也进行过说明,这里再做一次总结。

在 C 语言中,数组是不能作为参数进行传递的。但可以通过传递指向数组初始元素的指针,使得在函数内部操作数组成为可能。

因此,在函数这一侧,通过

  1. 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. 1: #include <stdio.h>
  2. 2:
  3. 3: void func(int *array, int size)
  4. 4: {
  5. 5: int i;
  6. 6:
  7. 7: for (i = 0; i < size; i++) {
  8. 8: printf("array[%d]..%d\n", i, array[i]);
  9. 9: }
  10. 10: }
  11. 11:
  12. 12: int main(void)
  13. 13: {
  14. 14: int array[] = {1, 2, 3, 4, 5};
  15. 15:
  16. 16: func(array, sizeof(array) / sizeof(int));
  17. 17:
  18. 18: return 0;
  19. 19: }

要 点

想要将类型 T 的数组作为参数进行传递,可以考虑传递“指向 T 的指针”。可是,作为被调用方是不知道数组的元素个数的,所以在必要的情况下,需要使用其他方式进行参数传递。

4.1.3 可变长数组

一般情况下,C 语言在编译时必须知道数组的元素个数,但是也可以使用 malloc() 在运行时再为数组申请必要的内存区域。

这种数组,在本书中被称为可变长数组*

* 虽然我们经常这么叫,但是不能因此就认为这是最常见的称呼。

代码清单 4-3 中,首先让用户输入需要的内存大小(11~13 行),在第 15 行使用 malloc()分配数组所需的内存区域(省略对返回值的检查)。

第 17~19 行,给数组赋值,并且在 20~22 行输出数组的内容。

代码清单 4-3 variable_array.c

  1. 1: #include <stdio.h>
  2. 2: #include <stdlib.h>
  3. 3:
  4. 4: int main(void)
  5. 5: {
  6. 6: char buf[256];
  7. 7: int size;
  8. 8: int *variable_array;
  9. 9: int i;
  10. 10:
  11. 11: printf("Input array size>");
  12. 12: fgets(buf, 256, stdin);
  13. 13: sscanf(buf, "%d", &size);
  14. 14:
  15. 15: variable_array = malloc(sizeof(int) * size);
  16. 16:
  17. 17: for (i = 0; i < size; i++) {
  18. 18: variable_array[i] = i;
  19. 19: }
  20. 20: for (i = 0; i < size; i++) {
  21. 21: printf("variable_array[%d]..%d\n", i, variable_array[i]);
  22. 22: }
  23. 23:
  24. 24: return 0;
  25. 25: }

如果想要修改已经分配了的可变长数组的大小,你可以使用 realloc()

代码清单 4-4 中,每当用户输入一个 int 类型的值,程序都会使用 realloc() 扩展 variable_array 的内存区域(这里也省略了对返回值的检查)。

代码清单 4-4 realloc.c

  1. 1: #include <stdio.h>
  2. 2: #include <stdlib.h>
  3. 3:
  4. 4: int main(void)
  5. 5: {
  6. 6: int *variable_array = NULL;
  7. 7: int size = 0;
  8. 8: char buf[256];
  9. 9: int i;
  10. 10:
  11. 11: while (fgets(buf, 256, stdin) != NULL) {
  12. 12: size++;
  13. 13: variable_array = realloc(variable_array, sizeof(int) * size);
  14. 14: sscanf(buf, "%d", &variable_array[size-1]);
  15. 15: }
  16. 16:
  17. 17: for (i = 0; i < size; i++) {
  18. 18: printf("variable_array[%d]..%d\n", i, variable_array[i]);
  19. 19: }
  20. 20:
  21. 21: return 0;
  22. 22: }

必须要引起注意的是,在使用 malloc()实现可变长数组的时候,程序员必须自己来管理数组的元素个数。

这和在将数组作为参数进行传递时,被调用方无法知道数组长度的理由一样——malloc()得到的不是数组,而是指针。

要 点

在需要获得类型 T 的可变长数组时,可以使用 malloc()来动态地给“指向 T 的指针”分配内存区域。

但此时需要程序员自己对数组的元素个数进行管理。

 

补充 Java 的数组

本书是 C 的参考书,在这里提到 Java 的数组只是给大家做一个参考。

Java 中的数组只能使用内存堆区域。在 C 的函数中写成下面这样:

  1. int hoge[10];

在大多数处理环境中,数组本身是分配在栈中的。Java 不能写成上面这样,而应该写成下面这样:

  1. int[] hoge = new int[10];

new 相当于 C 的 malloc(),上面的 Java 语句和下面的 C 语句具有几乎相同的意义,

  1. int hoge = malloc(sizeof(int) 10);

因此,在 Java 中经常使用指针引用数组。比如,使用下面的方式进行数组的赋值:

  1. int[] hoge = new int[10];
  2. int[] piyo = hoge;

如图 4-1 所示,这里的 hogepiyo 都是指向数组实体的指针变量。

4.1 基本的使用方法 - 图1

图 4-1 将 Java 的数组赋给变量

可是,Java 的数组和在 C 中使用 malloc()分配的数组,有决定性的不同:Java 的数组是知道自身的长度的。因此,对于

  1. 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)提供的功能并不能完全满足需要。