目录
一、基础概念
二、指针基础操作
1、声明指针变量
2、基本操作
3、void * 与 NULL
三、使用场景
1、作为函数参数传递
2、指针与数组
3、内存的动态分配
一、基础概念
C语言区别于其他高级语言的一个特点是其拥有指针的概念,指针变量将内存地址作为数据值。下面代码中所示,定义了 num 整数变量,并给其赋值为 10。计算机执行该语句时,会在内存开辟一个大小为 4 字节的空间用于存放数值 10,使用 num 可以访问该内存空间中的数据。而指针变量 p 存放了 num 变量在内存中存放的第一个字节的地址。
int num = 10;
int *p = #
示意图如下所示,假如变量 num 存放数据的内存地址为 1000 ~ 1003,指针 p 的内存地址为1004~1011(8个字节)。变量 num 内存中存放了数据 10,而指针 p 中记录了 num 内存的第一个字节的地址。
指针存放内存地址,所以指针变量的大小和 CPU 寻址有关,在 64bit 机器中指针变量占 8 字节,32位机器中指针变量占 4 字节。
二、指针基础操作
1、声明指针变量
int *p1, *p2;
int *p1, p2;
声明指针变量需要注意两点,首先需要指明指针变量的类型,其次 * 符号也是作为变量名的一部分。如上代码所示,第一行声明了两个 int 类型的指针变量 p1 和 p2;而第二行声明了一个指针变量 p1 和一个整型变量 p2。因为计算机中不同类型占用的内存空间大小不同,而指针变量只记录了变量数据在内存中的第一个字节的地址,所以只有指明指针变量的类型,才能明确变量数据的读取范围。
2、基本操作
指针变量主要包含两个操作:* 与 &。
& 为取址运算符号,通过该操作符可以获取变量的内存地址。
* 为解引用运算符,该运算符可以获取指针指向的数据。
int num = 10;
int *p = #
printf("%d\n", *p);
3、void * 与 NULL
int *p1 = NULL;...
if (p1 != NULL)
{...
}
将一个指针变量赋值为 NULL,表明该指针变量不指向任何内存地址,可以用来初始化一个指针变量。对值为 NULL的变量进行解引用是没有任何意义的,目前的编译器也不会进行检查,所以在使用前需要进行判断。
int *p1 = (int *)malloc(sizeof(int) * 4);
void *p2 = (void *p1);
int *p3 = (int *)p2;
malloc 动态分配堆内存空间,其返回值为 void 类型的指针变量,void 类型的指针变量可以和任意类型的指针变量进行强制类型转换后赋值。
三、使用场景
1、作为函数参数传递
当指针作为函数实参传递时,在函数中修改其值后会影响调用函数中变量的值,比较有名的就是swap 函数的例子,如下代码所示,swap1 可以实现交换两个参数的值,而 swap2 无法实现。
#include <stdio.h>
void swap1(int *a, int *b)
{int tmp = *a;*a = *b;*b = tmp;
}
void swap2(int a, int b)
{int tmp = a;a = b;b = tmp;
}
int main(void)
{int a = 2, b = 3;swap1(&a, &b);print("%d %d\n", a, b);int a = 2, b = 3;swap2(a, b);print("%d %d\n", a, b);
}
我们知道 C 中函数只能有一个返回值,那么对于一些需要多个返回值得场景如何编写函数呢?指针为我们提供了一个思路。如下图所示,将剩余 time 分钟 转换为剩余 hours 小时 minutes 分钟,例如剩余 90分钟 输出为 剩余 1小时30分钟。函数实现如下:
void timeToHM(int time, int *hours, int *minutes)
{*hours = time / 60;*minutes = time % 60;
}
2、指针与数组
数组是线性结构,声明一个数组会在内存中申请一个连续的空间。声明的数组名是指向数组中第一个元素的指针的同义词。我们可以将其赋值给指针,通过指针完成对数组的读写。
如下所示,数组赋给指针存在如下两种方式,这两种方式作用相同。
int array[5] = {0};
// 指向数组的第一个元素
int *p1 = array;
int *p2 = &array[0];
// 指向数组的第二个元素
int *p3 = array + 1;
int *p4 = &array[1];
指向数组的指针可以进行 + 和 - 两种操作,也支持自增(++)与自减(--)操作符。需要注意的是当数组指针指向加减操作时,并不是以一个字节作为单位,而是以数组类型所占内存空间为一个单位进行加减。在下面代码中,p2指向了数组的第二个元素,p3指向了数组的第三个元素,在内存中p2位于p1四个字节后,p3位于p2四个字节后。
int array[5] = {0};
// 指向数组第一个元素
int *p1 = array;
// 指向数组第二个元素
int *p2 = p1 + 1;
// 指向数组第三个元素
int *p3 = ++p2;
// 指向数组第二个元素
int *p4 = --p3;
// 指向数组第一个元素
int *p5 = p4 - 1;
还需要注意二维数组与指针之间的联系,二维数组不能使用二级指针进行赋值。二维数组需要配合数组指针进行使用。
int array[5][10];
// array变量名指向 array[0] 的地址,类型是int[10],
// 所以只有声明(*p1)[10]才可以接收二维数值指针
int (*p1)[10] = array;
// p2 等同于 &array[1][1];
int *p2 = *(p1 + 1) + 1;
指针还可以在函数中作为接收数组变量的形参。
// 接收一维数组
int sort(int *array, int len)
{...
}
// 接收二维数组,注意必须写清楚列数
int solve(int (*array)[10], int row, int col)
{...
}
还需要区分 数组指针 和 指针数组 这两个概念。指针数组,即数组中每个成员都是指针,数组指针即数组的指针。具体示例如下代码所示,需要将二维数组指针与指针数组的格式进行区分。
// 指针数组,该数组中每个元素都是指针
int *p1[10];
// 一维数组指针
int array[10] = {0};
int *p2 = array;
// 二维数组指针
int array[10][10] = {0};
int (*p3)[10] = array;
还需要注意的是 sizeof 在指针与数组之间的取值不一样,sizeof参数为数组名时可以获取该数组占用的字节数大小,而如果参数为指针,即使该指针指向数组,sizeof返回的值也只是该指针占用的内存空间。。
int array[9] = {1, 2, 3, 4, 5, 6, 7, 8, 9};
int *p = array;
// 输出36,因为数组中包含9个成员,每个成员都是int类型的,4*9=36字节
printf("%d\n", sizeof(array));
// 输出 8 , 因为 64 bit机器中指针占8个字节
printf("%d\n", sizeof(p));
3、内存的动态分配
指针还经常用于动态分配堆中的内存,如下代码所示。malloc 函数返回 void * 指针,需要根据接收指针类型进行强制类型转换。
#include <malloc.h>
// 声明长度为10的一维整形数组
int *p = (int *)malloc(sizeof(int) * 10);
if (NULL != p)
{...
}
free(p);
p = NULL;
根据具体的内存动态分配可以看之前写的博客:
C 内存的动态分配怎么用?有啥建议吗?内存分配中栈与堆到底有啥不同啊?_我要出家当道士-CSDN博客https://blog.csdn.net/qq_37437983/article/details/122720401?spm=1001.2014.3001.5502