行业知识
C语言之指针详解
文章目录
- 1 指针
- 1.1 简介
- 1.2 什么是指针
- 1.2.1 定义
- 1.2.2 指针表示
- 1.2.3 为什么*p=&a不正确
- 1.3 使用指针
- 1.3.1 简单使用
- 1.3.2 NULL 指针
- 1.3.3 指针算术运算
- 1.3.3.1 定义
- 1.3.3.2 遍历数组:递增一个指针
- 1.3.3.3 遍历数组:递减一个指针
- 1.3.3.4 指针的比较
- 1.3.4 指针数组
- 1.3.4.1 简单示例
- 1.3.4.2 字符串指针数组问题
- 1.3.5 指向数组的指针
- 1.3.6 指向指针的指针
- 1.4 函数和指针关系
- 1.4.1 传递指针给函数
- 1.4.2 从函数返回指针
- 1.4.3 函数指针
- 1.4.4 回调函数
1 指针
1.1 简介
当初学C语言,指针是最头疼的地方,难以理解,不过时间长了就自然明白了
通过指针,可以简化一些 C 编程任务的执行,还有一些任务,如动态内存分配,没有指针是无法执行的。
每一个变量都有一个内存位置,每一个内存位置都定义了可使用 & 运算符访问的地址,它表示了在内存中的一个地址。
注意:用register修饰的变量是没法取到地址的,没有内存位置
1.2 什么是指针
1.2.1 定义
指针也就是内存地址,指针变量是用来存放内存地址的变量。就像其他变量或常量一样,必须在使用指针存储其他变量地址之前,对其进行声明。指针变量声明的一般形式为: type *var_name;
- type 是指针的基类型,它必须是一个有效的 C 数据类型
- var_name 是指针变量的名称。用来声明指针的星号 * 与乘法中使用的星号是相同的。但是,在这个语句中,星号是用来指定一个变量是指针。
所有实际数据类型,不管是整型、浮点型、字符型,还是其他的数据类型,对应指针的值的类型都是一样的,都是一个代表内存地址的长的十六进制数。
不同数据类型的指针之间唯一的不同是,指针所指向的变量或常量的数据类型不同。
1.2.2 指针表示
请看下面的实例,它将输出定义的变量地址:
#include int main (){ int var_runoob = 10; int *p; // 定义指针变量 p = &var_runoob; printf("var_runoob 变量的地址: %p\n", p); return 0;}结果:var_runoob 变量的地址: 0x7ffeeaae08d81.2.3 为什么*p=&a不正确
int *p=&a是对的,但是*p=&a;却不对?
int *p=&a;这一句定义了一个变量p,p是一个指向int型的指针,即p的数据类型是int*,而&a的数据类型也是int*。这条语句可以分写为 int *p;p=&a;所以这一句没问题。
*p=&a; 这一句是对(*p)进行赋值操作,(*p)的数据类型是int型,而&a的数据类型是int*,两者类型不同,无法进行赋值,所以语句是错的。
*p=a和p=&a是正确的,*p=a的意思:将a的值赋给p指针指向的地址的值;p=&a的意思是:将a的地址赋给指针p;
区别:*p是一个值;p是一个地址;两者完全不相同,*代表着p指向的地址的值,简单来说就是取值;&是取地址符号,取的是地址;p是指针,可以理解为所指向的值的地址,*p就是取p指针指向的地址的值,&a就是取a的地址。
1.3 使用指针
1.3.1 简单使用
使用指针时会频繁进行以下几个操作:定义一个指针变量、把变量地址赋值给指针、访问指针变量中可用地址的值。这些是通过使用一元运算符 * 来返回位于操作数所指定地址的变量的值。下面的实例涉及到了这些操作:
#include int main (){ int var = 20; /* 实际变量的声明 */ int *ip; /* 指针变量的声明 */ ip = &var; /* 在指针变量中存储 var 的地址 */ printf("var 变量的地址: %p\n", &var ); /* 在指针变量中存储的地址 */ printf("ip 变量存储的地址: %p\n", ip ); /* 使用指针访问值 */ printf("*ip 变量的值: %d\n", *ip ); return 0;}结果:var 变量的地址: 0x7ffeeef168d8ip 变量存储的地址: 0x7ffeeef168d8*ip 变量的值: 201.3.2 NULL 指针
在变量声明的时候,如果没有确切的地址可以赋值,为指针变量赋一个 NULL 值是一个良好的编程习惯。
赋为 NULL 值的指针被称为空指针。
NULL 指针是一个定义在标准库中的值为零的常量
#include int main (){ int *ptr = NULL; printf("ptr 的地址是 %p\n", ptr ); return 0;}结果:ptr 的地址是 0x0在大多数的操作系统上,程序不允许访问地址为0 的内存,因为该内存是操作系统保留的。然而,内存地址 0 有特别重要的意义,它表明该指针不指向一个可访问的内存位置。但按照惯例,如果指针包含空值(零值),则假定它不指向任何东西。
如需检查一个空指针,可以使用 if 语句,如下所示:
if(ptr) /* 如果 p 非空,则完成 */if(!ptr) /* 如果 p 为空,则完成 */
1.3.3 指针算术运算
1.3.3.1 定义
C 指针是一个用数值表示的地址。因此,可以对指针执行算术运算。可以对指针进行四种算术运算:++、--、+、-
-
整型指针:假设 ptr 是一个指向地址 1000 的整型指针,是一个 32 位的整数,让我们对该指针执行下列的算术运算:ptr++,在执行完上述的运算之后,ptr 将指向位置 1004,因为 ptr 每增加一次,它都将指向下一个整数位置,即 当前位置往后移 4 字节。这个运算会在不影响内存位置中实际值的情况下,移动指针到下一个内存位置。
-
字符型指针:如果 ptr 指向一个地址为 1000 的字符,上面的运算会导致指针指向位置 1001,因为下一个字符位置是在 1001。
-
指针的每一次递增,它其实会指向下一个元素的存储单元。
指针的每一次递减,它都会指向前一个元素的存储单元。
指针在递增和递减时跳跃的字节数取决于指针所指向变量数据类型长度,比如 int 就是 4 个字节。
1.3.3.2 遍历数组:递增一个指针
我们喜欢在程序中使用指针代替数组,因为变量指针可以递增,而数组不能递增,数组可以看成一个指针常量。
下面的程序递增变量指针,以便顺序访问数组中的每一个元素:
#include const int MAX = 3; int main (){ int var[] = {10, 100, 200}; int i, *ptr; /* 指针中的数组地址 */ ptr = var; for ( i = 0; i1.3.3.3 遍历数组:递减一个指针
同样地,对指针进行递减运算,即把值减去其数据类型的字节数,如下所示:
#include const int MAX = 3; int main (){ int var[] = {10, 100, 200}; int i, *ptr; /* 指针中最后一个元素的地址 */ ptr = &var[MAX-1]; for ( i = MAX; i > 0; i--) { printf("存储地址:var[%d] = %p\n", i-1, ptr ); printf("存储值:var[%d] = %d\n", i-1, *ptr ); /* 指向下一个位置 */ ptr--; } return 0;}结果:存储地址:var[2] = 518a0ae4存储值:var[2] = 200存储地址:var[1] = 518a0ae0存储值:var[1] = 100存储地址:var[0] = 518a0adc存储值:var[0] = 101.3.3.4 指针的比较
指针可以用关系运算符进行比较,如 ==、。如果 p1 和 p2 指向两个相关的变量,比如同一个数组中的不同元素,则可对 p1 和 p2 进行大小比较。
下面的程序修改了上面的实例,只要变量指针所指向的地址小于或等于数组的最后一个元素的地址 &var[MAX - 1],则把变量指针进行递增:
#include const int MAX = 3; int main (){ int var[] = {10, 100, 200}; int i, *ptr; /* 指针中第一个元素的地址 */ ptr = var; i = 0; while ( ptr printf("存储地址:var[%d] = %p\n", i, ptr ); printf("存储值:var[%d] = %d\n", i, *ptr ); /* 指向上一个位置 */ ptr++; i++; } return 0;}结果:存储地址:var[0] = 0x7ffeee2368cc存储值:var[0] = 10存储地址:var[1] = 0x7ffeee2368d0存储值:var[1] = 100存储地址:var[2] = 0x7ffeee2368d4存储值:var[2] = 200 int var[] = {10, 100, 200}; int i; for (i = 0; i1.4.4 回调函数
函数指针作为某个函数的参数,函数指针变量可以作为某个函数的参数来使用的,回调函数就是一个通过函数指针调用的函数。
简单讲:回调函数是由别人的函数执行时调用你实现的函数。
实例中 populate_array() 函数定义了三个参数,其中第三个参数是函数的指针,通过该函数来设置数组的值。
实例中我们定义了回调函数 getNextRandomValue(),它返回一个随机值,它作为一个函数指针传递给 populate_array() 函数。
populate_array() 将调用 10 次回调函数,并将回调函数的返回值赋值给数组。
#include #include void populate_array(int *array, size_t arraySize, int (*getNextValue)(void)){ for (size_t i=0; i return rand();} int main(void){ int myarray[10]; /* getNextRandomValue 不能加括号,否则无法编译,因为加上括号之后相当于传入此参数时传入了 int , 而不是函数指针*/ populate_array(myarray, 10, getNextRandomValue); for(int i = 0; i
-