在C语言中指针:何时使用“&”和“*”?
在C语言中指针:何时使用“&”和“*”?
我刚开始学习指针,有些困惑。我知道&
表示变量的地址,并且可以在指针变量前使用*
来获得指针指向的对象的值。但是当你处理数组、字符串或者使用变量的指针副本调用函数时,情况又有所不同。在所有这些中,很难看到逻辑的模式。
什么时候应该使用&
和*
?
处理数组和函数有一种模式;只是一开始有点难以看清。
在处理数组时,记住以下有用的信息:当大多数情况下,数组表达式出现时,表达式的类型会从 "N元素的T数组" 隐式转换为 "指向T的指针",其值设置为指向数组中第一个元素。这个规则的例外是当数组表达式作为 &
或 sizeof
操作符的操作数,或当它是字符串文字在声明的初始化程序中使用时。
因此,当你将一个数组表达式作为参数调用函数时,函数会接收一个指针,而不是数组:
int arr[10]; ... foo(arr); ... void foo(int *arr) { ... }
这就是为什么不应该将与 scanf()
中的 "%s" 对应的参数使用 &
操作符:
char str[STRING_LENGTH]; ... scanf("%s", str);
由于隐式转换,scanf()
将接收指向 str
数组开头的 char *
值。这对任何函数调用一个数组表达式作为参数是有效的(任何一个 str*
函数,*scanf
和 *printf
函数等)。
在实际应用中,你可能从不会使用 &
操作符来调用一个数组表达式的函数,例如:
int arr[N]; ... foo(&arr); void foo(int (*p)[N]) {...}
这样的代码并不常见;你必须知道函数的声明中数组的大小,而且函数只能与特定大小的数组指针一起使用(指向10元素的T数组的指针与指向11元素的T数组的指针是不同的类型)。
当一个数组表达式作为 &
运算符的操作数时,结果表达式的类型是 "指向N元素的T数组",或者是 T (*)[N]
,这与指针数组 (T *[N]
) 和指向基本类型的指针 (T *
) 是不同的。
在处理函数和指针时,需要记住的规则是:如果你想改变一个参数的值,并使其反映在调用代码中,则必须传递一个指向要修改的对象的指针。再次说明,数组对这个规则有点问题,但我们会先处理正常的情况。
记住,C通过值传递所有的函数参数;形式参数接收实际参数的副本,对形式参数的任何更改不会反映在实际参数中。常见的例子是一个交换函数:
void swap(int x, int y) { int tmp = x; x = y; y = tmp; } ... int a = 1, b = 2; printf("before swap: a = %d, b = %d\n", a, b); swap(a, b); printf("after swap: a = %d, b = %d\n", a, b);
你将得到以下输出:
before swap: a = 1, b = 2 after swap: a = 2, b = 1
形式参数 x
和 y
是与 a
和 b
不同的对象,因此对 x
和 y
的更改不会反映在 a
和 b
中。由于我们要修改 a
和 b
的值,因此必须向交换函数传递它们的指针:
void swap(int *x, int *y) {int tmp = *x; *x = *y; *y = tmp; } ... int a = 1, b = 2; printf("before swap: a = %d, b = %d\n", a, b); swap(&a, &b); printf("after swap: a = %d, b = %d\n", a, b);
现在你的输出将会是:
交换前:a = 1,b = 2 交换后:a = 2,b = 1
请注意,在交换函数中,我们不改变 x
和 y
的值,而是改变了 x
和 y
指向的值。写入 *x
与写入 x
是不同的;我们没有更新 x
本身的值,而是从 x
得到一个位置并更新该位置的值。
如果我们想修改指针的值,情况同样适用;如果我们写入
int myFopen(FILE *stream) {stream = fopen("myfile.dat", "r"); } ... FILE *in; myFopen(in);
那么我们修改的是输入参数 stream
的值,而不是 stream
指向的值,因此改变 stream
不会影响 in
的值;为使此方法奏效,我们必须传递指向指针的指针:
int myFopen(FILE **stream) {*stream = fopen("myFile.dat", "r"); } ... FILE *in; myFopen(&in);
再次说明,数组在这方面有些棘手。当你将数组表达式传递给函数时,函数接收到的是一个指针。由于数组下标运算符的定义,你可以像对数组一样使用指针的下标运算符:
int arr[N]; init(arr, N); ... void init(int *arr, int N) {size_t i; for (i = 0; i < N; i++) arr[i] = i*i;}
请注意,数组对象可能无法赋值;也就是说,你不能这样做:
int a[10], b[10]; ... a = b;
因此,在处理指向数组的指针时要小心;像下面这样的代码是行不通的:
void (int (*foo)[N]) { ... *foo = ...; }
你有指针和值:
int* p; // variable p is pointer to integer type int i; // integer value
你可以使用*
将指针转换成值:
int i2 = *p; // integer i2 is assigned with integer value that pointer p is pointing to
你可以使用&
将值转换成指针:
int* p2 = &i; // pointer p2 will point to the address of integer i
编辑:
在数组的情况下,它们被处理得和指针非常相似。如果你将它们视为指针,你可以像上面所述一样使用*
来获取它们内部的值,但是还有另一种更常见的方法,使用[]
操作符:
int a[2]; // array of integers int i = *a; // the value of the first element of a int i2 = a[0]; // another way to get the first element
要获取第二个元素:
int a[2]; // array int i = *(a + 1); // the value of the second element int i2 = a[1]; // the value of the second element
因此,[]
索引操作符是*
操作符的一种特殊形式,它的工作方式如下:
a[i] == *(a + i); // these two statements are the same thing