在讲指针变量前先明确&,%p等符号的含义,&变量名:获取该变量的地址(指针),%p:输出地址.
指针变量是存储内存地址的变量,它的值是另一个变量的地址,而不是具体的数据值。
1. 指针变量的定义
在C/C++中,定义指针变量的基本格式为:
数据类型 *指针变量名;
例如:
int *p; // 定义一个指向 int 类型的指针变量 p
这里 p 不是整数变量,而是一个存储整数变量地址的指针。
2. 取地址符(&)与解引用符(*)
• &(取地址符):获取变量的地址。
• *(解引用符):访问指针指向的内存地址中的值。
示例:
#include <stdio.h>
int main() {
int a = 10;
int *p = &a; // p 存储 a 的地址
printf("a 的值: %d\n", a);
printf("a 的地址: %p\n", &a);
printf("p 的值(即 a 的地址): %p\n", p);
printf("p 指向的值(即 *p): %d\n", *p); // 访问 a 的值
return 0;
}
输出示例(地址可能不同):
a 的值: 10
a 的地址: 0x7ffeefbff5a4
p 的值(即 a 的地址): 0x7ffeefbff5a4
p 指向的值(即 *p): 10
3. 指针的作用
(1) 通过指针修改变量值
*p = 20; // 修改 p 指向的变量 a
示例:
#include <stdio.h>
int main() {
int a = 10;
int *p = &a;
*p = 20; // 通过指针修改 a 的值
printf("a 的新值: %d\n", a); // 输出 20
return 0;
}
int*p=&a;表示p储存a的地址,对应前面的定义.p获得a的地址,*p解引用,访问a的值(因为p指向的是a的地址).
(2) 指针与数组
指针可以用于操作数组
核心思想是:指针可以存储数组的地址,并通过指针访问数组的元素。由于数组在内存中是连续存储的,所以指针可以通过地址偏移来访问数组中的不同元素
明确一点:数组名字其实就是数组的起始地址
int arr[5] = {10, 20, 30, 40, 50};
printf("%p\n", arr); // 输出数组的首地址
printf("%p\n", &arr[0]); // 同样是数组的首地址
上面的arr和&arr[0]是相同的,说明数组名本质上是一个指向数组首元素的指针常量。
用指针访问数组
数组元素存储在内存中的方式如下(假设arr的地址是0x1000,每个int占4个字节):
变量 地址 值
arr[0] 0x1000 10
arr[1] 0x1004 20
arr[2] 0x1008 30
arr[3] 0x100C 40
arr[4] 0x1010 50
我们可以使用指针来访问数组:
int arr[5] = {10, 20, 30, 40, 50};
int *p = arr; // p 指向数组的第一个元素(arr[0])
printf("%d\n", *p); // 输出 10,等价于 arr[0]
printf("%d\n", *(p+1)); // 输出 20,等价于 arr[1]
printf("%d\n", *(p+2)); // 输出 30,等价于 arr[2]
解释:
• p 存储的是 arr[0] 的地址,即 0x1000。
• p + 1 让指针移动到 0x1004,即 arr[1] 的位置,解引用 *(p + 1) 取出 20。
• p + 2 让指针移动到 0x1008,即 arr[2],解引用 *(p + 2) 取出 30。
等价关系:
• arr[i] 等价于 *(arr + i).
• p[i] 也等价于 *(p + i).
示例:
printf("%d\n", arr[3]); // 输出 40
printf("%d\n", *(arr+3)); // 同样输出 40
printf("%d\n", p[3]); // 依然输出 40
printf("%d\n", *(p+3)); // 依然输出 40
指针遍历数组
既然 p + i 可以访问 arr[i],那么我们可以用指针遍历数组:
#include <stdio.h>
int main() {
int arr[5] = {10, 20, 30, 40, 50};
int *p = arr; // 指针指向数组首元素
for(int i = 0; i < 5; i++) {
printf("%d ", *(p + i)); // 依次访问数组元素
}
return 0;
}
//输出: 10 20 30 40 50
此外,我们还可以用指针的自增运算遍历数组:
int *p = arr;
while(p < arr + 5) { // 当 p 还没有超出数组范围
printf("%d ", *p);
p++; // 让指针指向下一个元素
}
• arr + 5 代表超出数组最后一个元素的下一个地址。
• p++ 让指针不断向后移动,每次指向数组的下一个元素
int arr[] = {1, 2, 3};
int *p = arr; // 指针指向数组首元素
printf("%d\n", *(p+1)); // 输出 2
指针修改数组元素
既然指针能访问数组元素,那么也可以修改数组中的值:
int arr[5] = {10, 20, 30, 40, 50};
int *p = arr;
*p = 100; // 修改 arr[0] 为 100
*(p+2) = 300; // 修改 arr[2] 为 300
p[4] = 500; // 修改 arr[4] 为 500
printf("%d %d %d\n", arr[0], arr[2], arr[4]); // 输出 100 300 500
等价关系:
• arr[0] = 100; 等价于 *p = 100;
• arr[2] = 300; 等价于 *(p+2) = 300;
• arr[4] = 500; 等价于 p[4] = 500;
指针与二维数组
对于二维数组 int arr[3][4](三行四列),它的存储结构如下:
arr[0] -> [ 10 20 30 40 ]
arr[1] -> [ 50 60 70 80 ]
arr[2] -> [ 90 100 110 120 ]
arr 实际上是一个 指向第一行的指针,arr[i] 是指向第 i 行的 指针,所以:
• arr[i][j] 等价于 *(*(arr + i) + j),下面有解释
示例:
#include <stdio.h>
int main() {
int arr[3][4] = {
{10, 20, 30, 40},
{50, 60, 70, 80},
{90, 100, 110, 120}
};
int *p = &arr[0][0]; // 指向二维数组的首地址
printf("%d\n", *(p + 5)); // 访问 arr[1][1],即 60
printf("%d\n", *(*(arr + 1) + 1)); // 访问 arr[1][1],即 60
return 0;
}
看完这段代码可能会有以下疑问:
1. 为什么 *(p + 5) 会访问 arr[1][1]?
2. 为什么 *(*(arr + 1) + 1) 会访问 arr[1][1]?
详细解释:
二维数组的存储方式
在C语言中,二维数组在内存中是按行存储的(行优先存储),比如:
int arr[3][4] = {
{10, 20, 30, 40},
{50, 60, 70, 80},
{90, 100, 110, 120}
};
它在内存中的排列方式如下(假设 arr[0][0] 的地址是 0x1000,每个 int 占 4 字节):
元素 地址(假设) 值
arr[0][0] 0x1000 10
arr[0][1] 0x1004 20
arr[0][2] 0x1008 30
arr[0][3] 0x100C 40
arr[1][0] 0x1010 50
arr[1][1] 0x1014 60
arr[1][2] 0x1018 70
arr[1][3] 0x101C 80
arr[2][0] 0x1020 90
arr[2][1] 0x1024 100
arr[2][2] 0x1028 110
arr[2][3] 0x102C 120
观察:
• arr[0][0] 地址是 0x1000,arr[0][1] 地址是 0x1004(因为 int 占 4 字节)。
• arr[1][0] 地址是 0x1010,arr[1][1] 地址是 0x1014,以此类推。
2. int *p = &arr[0][0]; 解释
&arr[0][0] 代表 二维数组首元素的地址,所以 p 直接指向了 arr[0][0],即 p = 0x1000。
换句话说,p 现在可以当作一个一维数组的指针,它指向的是整个二维数组在内存中的首地址。
3. *(p + 5) 解析
printf("%d\n", *(p + 5));
等价于:
• p 是 &arr[0][0],即 p 指向 arr[0][0](地址 0x1000)。
• p + 5 让指针向后移动 5 个 int 类型的位置:
• p + 1 指向 arr[0][1](0x1004)
• p + 2 指向 arr[0][2](0x1008)
• p + 3 指向 arr[0][3](0x100C)
• p + 4 指向 arr[1][0](0x1010)
• p + 5 指向 arr[1][1](0x1014)
• *(p + 5) 取出 arr[1][1] 的值 60。
4. *(*(arr + 1) + 1) 解析
printf("%d\n", *(*(arr + 1) + 1));
这个语法更接近二维数组的标准访问方式,我们一步步解析它:
(1) arr 是什么?
• arr 代表 二维数组的首地址,它的类型是 int [4](即 指向含 4 个 int 的数组)。
• arr + 1 代表 跳过一整行,即 arr[1](arr[1] 其实是 &arr[1][0])。
• *(arr + 1) 取出 arr[1],也就是 arr[1] 这一行的首地址(&arr[1][0])。
(2) *(arr + 1) + 1 解析
• *(arr + 1) + 1 代表 在 arr[1] 这行上再向右移动 1 列,即 arr[1][1]。
(3) *(*(arr + 1) + 1) 取值
• *(*(arr + 1) + 1) 取出 arr[1][1] 的值,即 60。
小结
*(p + 5)
访问的元素 : arr[1][1]
计算过程 :
p 指向 arr[0][0],加 5 后指向 arr[1][1]
*(*(arr + 1) + 1)
访问的元素: arr[1][1]
计算过程:
arr + 1 代表 arr[1],*(arr + 1) + 1 代表 arr[1][1]
1. 二维数组在内存中是按行存储的,即 arr[0] 在前,arr[1] 紧随其后。
2. int *p = &arr[0][0]; 让 p 变成了指向所有元素的指针,p + 5 访问 arr[1][1]。
3. arr 本身是一个指向数组的指针,arr + 1 让指针移动到下一行,*(arr + 1) + 1 访问 arr[1][1]。
4. 这两种方式本质上都访问了相同的内存位置,所以输出的值相同。
大小结
1. 数组名本质是指向首元素的指针,即 arr 等价于 &arr[0]。
2. 指针可以像数组一样使用,如 p[i] == *(p + i)。
3. 指针可以修改数组元素,如 *(p+2) = 300 等价于 arr[2] = 300。
4. 指针遍历数组可以用 p++ 来不断访问下一个元素。
5. 二维数组的指针操作:arr[i][j] == *(*(arr + i) + j)。
(3) 指针与动态内存分配
int *p = (int*)malloc(sizeof(int)); // 在堆区分配内存
*p = 100;
free(p); // 释放内存
4. 指针变量的类型
指针类型必须匹配数据类型:
int *p1; // 指向 int 类型
double *p2; // 指向 double 类型
char *p3; // 指向 char 类型
不同类型的指针所占的字节数可能相同,但解引用时必须匹配数据类型。
总结
1. 指针存储变量的地址,而不是变量的值。
2. 通过 & 获取变量地址,通过 * 访问指针指向的值。
3. 指针可以用来修改变量值、操作数组、动态分配内存等。
4. 指针类型必须与数据类型匹配。