C 中的指针 – 个人笔记
个人笔记,仅供学习【
注:Markdown 内的 # 号被认为是 Hashtag 的问题直到本文撰写时虽然修复了,但是暂未发布新版本(见此)。 Update: 已修复。
指针简要介绍
假设一个指针变量名是 ptr,可以编写如下语句:
ptr = &pooh; // 把 pooh 的地址赋给 ptr
对于这条语句,我们说 ptr “指向” pooh。ptr 和 &pooh 的区别是 ptr 是变量,而 &pooh 是常量。 还可以把ptr指向别处:
ptr = &bah; // 把 ptr 指向 bah,而不是 pooh
这样我们就可以使用间接运算符 *(indirection operator)找出储存在 bah 中的值:
val = *ptr; // 找出 ptr 指向的值
语句 ptr = &bah;
和 val = *ptr;
放在一起相当于下面的语句:
val = bah;
地址运算符 &,后跟一个变量名时,& 给出该变量的地址。 例如:&nurse 表示变量 nurse 的地址。
地址运算符 ,后跟一个指针名或地址时, 给出储存在指针指向地址上的值。
示例:
nurse = 22;
ptr = &nurse; // 指向 nurse 的指针
val = *ptr; // 把 ptr 指向的地址上的值赋给val
执行以上3条语句的最终结果是把22赋给val。
声明指针
int *p; // p 是一个返回整型数据的指针
首先从 p 处开始,先与 * 结合,所以说明 p 是一个指针。
然后再与 int 结合,说明指针所指向的内容的类型为int 型。
通常,程序员在声明时使用空格,在解引用变量时省略空格。
输出地址用 %p。
函数、数组与指针
total = sum(marbles); // 可能的函数调用
// ...
int sum(int * ar, int n) { // more general approach
int i;
int total = 0;
for( i = 0; i < n; i++) { // use n elements
total += ar[i]; // ar[i] the same as *(ar + i)
}
return total;
}
这里,第1个形参告诉函数该数组的地址和数据类型,第2个形参告诉函数该数组中元素的个数。
注意,只有在函数原型或函数定义头中,才可以用 int ar[]
代替 int * ar
:
int sum(int ar[], int n);
int *ar
形式和 int ar[]
形式都表示 ar 是一个指向 int 的指针。
(int ar[]
用于提醒读者:指针 ar 指向的不仅仅一个 int 类型值,还是一个 int 类型数组的元素。)
由于函数原型可以省略参数名,所以下面4种原型都是等价的:
int sum(int *ar, int n);
int sum(int *, int);
int sum(int ar[], int n);
int sum(int [], int);
在函数定义中不能省略参数名。下面两种形式的函数定义等价:
int sum(int *ar, int n) {
// code goes here
}
int sum(int ar[], int n); {
// code goes here
}
上例中,sum() 函数使用一个指针形参标识数组的开始,用一个整数形参表明待处理数组的元素个数(指针形参也表明了数组中的数据类型)。 还有一种方法是传递两个指针,第1个指针指明数组的开始处(与前面用法相同),第2个指针指明数组的结束处。
下面的程序表明了指针形参是变量,这意味着可以用索引表明访问数组中的哪一个元素。
/* sum_arr2.c -- sums the elements of an array */
#include <stdio.h>
#define SIZE 10
int sump(int * start, int * end);
int main(void) {
int marbles[SIZE] = {20,10,5,39,4,16,19,26,31,20};
long answer;
answer = sump(marbles, marbles + SIZE);
printf("The total number of marbles is %ld.\n", answer);
return 0;
}
/* use pointer arithmetic */
int sump(int * start, int * end) {
int total = 0;
while (start < end) {
total += *start; // add value to total 把数组元素的值加起来
start++; // advance pointer to next element 让指针指向下一个元素
}
return total;
}
指针 start 开始指向 marbles 数组的首元素,所以赋值表达式 total += *start
把首元素(20)加给 total。
然后,表达式 start++ 递增指针变量 start,使其指向数组的下一个元素。
因为 start 是指向 int 的指针,start 递增1相当于其值递增 int 类型的大小。
注意,前文的 sum() 函数把元素的个数作为第二个参数,把该参数作为循环测试的一部分。 而 sump() 函数则使用第二个指针来结束循环。
因为 while 循环的测试条件是一个不相等的关系,所以循环最后处理的一个元素是 end 所指向位置的前一个元素。 这意味着 end 指向的位置实际上在数组最后一个元素的后面。
C 保证在给数组分配空间时,指向数组后面第一个位置的指针仍是有效的指针。
这使得 while 循环的测试条件是有效的,因为 start 在循环中最后的值是 end(在最后一次while循环中执行完 start++;
后,start的值就是end的值)。
因为下标从 0 开始,所以 marbles + SIZE 指向数组末尾的下一个位置。
上述的循环体也可以写成一行代码:
total += *start++;
一元运算符 * 和 ++ 的优先级相同,但结合律是从右往左,所以 start++ 先求值,然后才是 *start
(指针 start 先把指针指向位置上的值加到 total 上后指向)。
如果使用 *++start
,顺序则反过来,先递增指针,再使用指针指向位置上的值。
如果使用 (*start)++
,则先使用 start 指向的值,再递增该值,而不是递增指针。
以下为一个实例:
/* order.c -- precedence in pointer operations */
#include <stdio.h>
int data[2] = {100, 200};
int moredata[2] = {300, 400};
int main(void) {
int * p1, * p2, * p3;
p1 = p2 = data;
p3 = moredata;
printf(" *p1 = %d, *p2 = %d, *p3 = %d\n", *p1 , *p2 , *p3);
printf("*p1++ = %d, *++p2 = %d, (*p3)++ = %d\n", *p1++ , *++p2 , (*p3)++);
printf(" *p1 = %d, *p2 = %d, *p3 = %d\n", *p1 , *p2 , *p3);
return 0;
}
输出结果是这样的:
*p1 = 100, *p2 = 100, *p3 = 300
*p1++ = 100, *++p2 = 200, (*p3)++ = 300
*p1 = 200, *p2 = 200, *p3 = 301
只有 (*p3)++
改变了数组元素的值,其他两个操作分别把 p1 和 p2 指向数组的下一个元素。
指针表示法和组表示法
C 语言中,ar[i]
和 *(ar+1)
这两个表达式都是等价的。
无论 ar 是数组名还是指针变量,这两个表达式都没问题。
但是,只有当 ar 是指针变量时,才能使用 ar++ 这样的表达式。
指针操作
如果编译器不支持 %p 转换说明,可以用 %u 或 %lu 代替 %p; 如果编译器不支持用 %td 转换说明打印地址的差值,可以用 %d 或 %ld 来代替。
// ptr_ops.c -- pointer operations
#include <stdio.h>
int main(void) {
int urn[5] = {100,200,300,400,500};
int * ptr1, * ptr2, *ptr3;
ptr1 = urn; // assign an address to a pointer 把一个地址赋给指针
ptr2 = &urn[2]; // ditto 把一个地址赋给指针
// dereference a pointer and take 解引用指针
// the address of a pointer 以及获得指针的地址
printf("pointer value, dereferenced pointer, pointer address:\n");
printf("ptr1 = %p, *ptr1 =%d, &ptr1 = %p\n", ptr1, *ptr1, &ptr1);
// pointer addition 指针加法
ptr3 = ptr1 + 4;
printf("\nadding an int to a pointer:\n");
printf("ptr1 + 4 = %p, *(ptr4 + 3) = %d\n", ptr1 + 4, *(ptr1 + 3));
ptr1++; // increment a pointer 递增指针
printf("\nvalues after ptr1++:\n");
printf("ptr1 = %p, *ptr1 =%d, &ptr1 = %p\n", ptr1, *ptr1, &ptr1);
ptr2--; // decrement a pointer 递减指针
printf("\nvalues after --ptr2:\n");
printf("ptr2 = %p, *ptr2 = %d, &ptr2 = %p\n", ptr2, *ptr2, &ptr2);
--ptr1; // restore to original value 恢复为初始值
++ptr2; // restore to original value 恢复为初始值
printf("\nPointers reset to original values:\n");
printf("ptr1 = %p, ptr2 = %p\n", ptr1, ptr2);
// subtract one pointer from another 一个指针减去另一个指针
printf("\nsubtracting one pointer from another:\n");
printf("ptr2 = %p, ptr1 = %p, ptr2 - ptr1 = %td\n", ptr2, ptr1, ptr2 - ptr1);
// subtract an integer from a pointer 一个指针减去一个整数
printf("\nsubtracting an int from a pointer:\n");
printf("ptr3 = %p, ptr3 - 2 = %p\n", ptr3, ptr3 - 2);
return 0;
}
输出结果是这样的:
pointer value, dereferenced pointer, pointer address:
ptr1 = 0x7fff5fbff8d0, *ptr1 =100, &ptr1 = 0x7fff5fbff8c8
adding an int to a pointer:
ptr1 + 4 = 0x7fff5fbff8e0, *(ptr4 + 3) = 400
values after ptr1++:
ptr1 = 0x7fff5fbff8d4, *ptr1 =200, &ptr1 = 0x7fff5fbff8c8
values after --ptr2:
ptr2 = 0x7fff5fbff8d4, *ptr2 = 200, &ptr2 = 0x7fff5fbff8c0
Pointers reset to original values:
ptr1 = 0x7fff5fbff8d0, ptr2 = 0x7fff5fbff8d8
subtracting one pointer from another:
ptr2 = 0x7fff5fbff8d8, ptr1 = 0x7fff5fbff8d0, ptr2 - ptr1 = 2
subtracting an int from a pointer:
ptr3 = 0x7fff5fbff8e0, ptr3 - 2 = 0x7fff5fbff8d8
指针变量的基本操作:
赋值:可以把地址赋给指针。例如,用数组名、带地址运算符(&)的 变量名、另一个指针进行赋值。 注意,地址应该和指针类型兼容。
解引用:* 运算符给出指针指向地址上储存的值。
本例中,*ptr1
的初值是 100,该值储存在编号为 0x7fff5fbff8d0 的地址上。
取址:和所有变量一样,指针变量也有自己的地址和值。对指针而言,& 运算符给出指针本身的地址。 本例中,ptr1 储存在内存编号为 0x7fff5fbff8c8 的地址上,该存储单元储存的内容是 0x7fff5fbff8d0,即urn的地址。 因此 &ptr1 是指向 ptr1 的指针,而 ptr1 是指向 utn[0] 的指针。
指针与整数相加:可以使用+运算符把指针与整数相加,或整数与指针相加。 无论哪种情况,整数都会和指针所指向类型的大小(以字节为单位)相乘,然后把结果与初始地址相加。
递增指针:递增指向数组元素的指针可以让该指针移动至数组的下一个元素。 注意!变量不会因为值发生变化就移动地址。
指针减去一个整数:可以使用 – 运算符从一个指针中减去一个整数。 指针必须是第 1 个运算对象,整数是第 2 个运算对象。
递减指针:当然,除了递增指针还可以递减指针。
指针求差:可以计算两个指针的差值。
比较:使用关系运算符可以比较两个指针的值,前提是两个指针都指向相同类型的对象。
注意,这里的减法有两种:可以用一个指针减去另一个指针得到一个整数,或者用一个指针减去一个整数得到另一个指针。
千万不要解引用未初始化的指针
考虑下面的例子:
int * pt; // 未初始化的指针
*pt = 5; // 严重的错误
第2行的意思是把5储存在pt指向的位置。但是pt未被初始化,其值是一个随机值,所以不知道5将储存在何处。 这可能不会出什么错,也可能会擦写数据或代码,或者导致程序崩溃。
因此,在使用指针之前,必须先用已分配的地址初始化它。
如果喜欢本文,欢迎点击下方的「鼓掌」按钮!
如果上面没有加载出任何东西,可以点击这里。