目录
前言
简单来说,指针是一个变量,其值为另一个变量的地址。通过指针,我们可以直接访问、修改其他变量的值,并且可以动态地进行内存管理和数据操作。
指针的重要概念
-
指针变量:
指针变量是用来存储内存地址的变量。通过指针,我们可以直接访问和操纵其他变量所在的内存地址。 -
内存地址:
在计算机中,每个变量都存储在内存中的某个位置,每个位置都有一个唯一的地址。指针存储的就是这个地址。 -
指针的声明和使用:
在C和C++等语言中,指针的声明使用*
来表示。例如,int *ptr;
表示声明了一个指向整数的指针变量。指针的使用一般包括取地址操作&
和解引用操作*
。取地址操作用于获取变量的地址,解引用操作用于访问指针所指向的地址中存储的值。 -
指针和数组:
数组名本身就是数组第一个元素的地址。因此,指针和数组紧密相关,可以通过指针来遍历数组。此外,可以通过指针进行数组元素的动态分配和释放。 -
指针和函数:
函数参数可以是指针,通过指针参数,可以实现对函数外部变量的修改,或者动态传递数据。另外,可以通过指针返回动态分配的内存。 -
指针和动态内存分配:
通过指针,我们可以使用malloc()
、calloc()
和realloc()
等函数在堆上动态分配内存。同时,需要负责及时释放动态分配的内存,以防止内存泄漏。 -
指针和指针算术:
指针的值是内存地址,因此可以进行指针加法和指针减法。这在涉及数组、字符串和内存操作时非常有用。 -
指针的安全性:
使用指针需要特别注意指针的安全性,比如空指针、野指针、指针溢出等问题都可能导致程序崩溃或不可预测的行为。
剖析
题目一
&a取的是整个数组的地址,他的类型是int(*)[5],权限是20个字节,强制类型转换为 int*后,它的权限变为了4个字节即一个整形。不强制类型转换,ptr操作时也只会访问4个字节,取决于自己的指针类型,而不取决于所赋给自己的类型,但是会报警告
int a[5] = {1, 2, 3, 4, 5};
int *ptr = (int *)(&a + 1);
printf("%d,%d\n", *(a + 1), *(ptr - 1)); // 2 5
题目二
struct Test
{
int Num;
char *pcName;
short sDate;
char cha[2];
short sBa[4];
} *p = (struct Test *)0x100000;
// 假设p 的值为0x100000。 如下表表达式的值分别为多少?
// 已知,结构体Test类型的变量大小是20个字节
/* 0x100000 加上20字节后--> 0x100014 */
printf("%p\n", p + 0x1);
/* 1048,576 +1 --> 1028,577 */
printf("%p\n", (unsigned long)p + 0x1);
/* 0x100000 + 4 --> 0x100004 */
printf("%p\n", (unsigned int *)p + 0x1);
题目三
int a[4] = {1, 2, 3, 4};
int *ptr1 = (int *)(&a + 1);
int *ptr2 = (int *)((int)a + 1);
printf("%x,%x\n", ptr1[-1], *ptr2); // 4 2000000
题目四
初始化二维数组a时,用的是(),并不是{},这里是逗号表达式,最右边为准,因此实际初始化的a是 a[3][2] = {1, 3, 5, 0, 0, 0}
a[0]是第一行的数组名,表示首元素的地址,即p == a[0][0]的地址
int a[3][2] = {(0, 1), (2, 3), (4, 5)};
int *p;
p = a[0];
printf("%d\n", p[0]); // 1 p[0] == *(arr + 0)
题目五
%d形式输出 &p[4][2] - &a[4][2]
int(*p)[4] ——> p是一个指向有4个整形的数组的指针
p = a;
p的类型是 int (*) [4]
a的类型是 int (*) [5]
等号两边类型不相同,编译器会报警告,有下
a一次访问5个整形,p按照自己的权限进行访问,+1一次访问4个整形
则 p[4][2] ——> * ( * ( p + 4 ) + 2 ) 取地址最终指向 第4行第3列的地址 (蓝色位置)
则 a[4][2] 再取地址最终指向 第5行第2列的地址(红色位置)
两个地址相减 得到的是两个指针之间的元素个数
再有地址由低到高,低地址减去高地址,得到负数
最终&d形式输出 &p[4][2] - &a[4][2] = -4
%p形式输出 &p[4][2] - &a[4][2]
10000000000000000000000000000100 -4的源码
111111111111111111111111111111111011 -4的反码
111111111111111111111111111111111100 -4的补码
再有 %p打印会把补码看作为地址
FF FF FF FC -4的补码作为地址输出
int a[5][5];
int(*p)[4];
p = a;
printf("%p,%d\n", &p[4][2] - &a[4][2], &p[4][2] - &a[4][2]); // fffffffffffffffc -4
题目六
int aa[2][5] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
int *ptr1 = (int *)(&aa + 1); // 跳过整个aa数组
int *ptr2 = (int *)(*(aa + 1)); // 得到第二行的数组名
printf("%d,%d", *(ptr1 - 1), *(ptr2 - 1)); // 10 5
题目七
char **pa = a
将指针数组 a 的首元素的地址赋值给了指针 pa。
由于数组的元素是指针类型,因此需要使用指向指针的指针 char ** 类型来接收首元素的地址。
就比如 int a = 1; int *p = &a; p指针指向a的地址,所指的是int类型,那么就需要int修饰p,*代表p是个指针
pa++
pa++ 将指针 pa 自增,指向了指针数组的下一个元素的地址。因此,pa 指针现在指向了数组 a 的第二个元素地址的地址。
%s 格式符,它会根据指针指向的地址找到对应的字符串,并将该字符串打印出来。
char *a[] = {"work", "at", "alibaba"};
char **pa = a;
pa++;
printf("%s\n", *pa); // at
题目八
char **cp[] = {c + 3, c + 2, c + 1, c};
cp 是一个二级指针数组。如下,cp 的类型是 char **cp[],这表示它是一个指针的指针数组。每个 cp 数组的元素都是一个 char ** 类型的指针(也就是二级指针)。这意味着每个元素本身是一个指向指针的指针。在这种情况下,每个元素存储了 c 数组中对应字符串指针的地址。
char ***cpp = cp;
cpp指针变量存放cp二级指针首元素的地址,而首元素 c+3 的地址,所以cpp是c+3的指针的指针cpp 存放的是 cp 的地址。而 cp 本身是一个指向指针的指针数组的首元素的地址。
在代码片段中,cp 的首元素是 c + 3 的地址,因此可以说 cpp 存放着 c + 3 的地址。
**++cpp
*--*++cpp + 3
*cpp[-2] + 3
cpp[-1][-1] + 1
char *c[] = {"ENTER", "NEW", "POINT", "FIRST"};
char **cp[] = {c + 3, c + 2, c + 1, c};
char ***cpp = cp;
printf("%s\n", **++cpp); // POINT
printf("%s\n", *--*++cpp + 3); // ER *的优先级大于+ ++的优先级大于*
printf("%s\n", *cpp[-2] + 3); // ST cpp[-2] ——> *(cpp - 2) 最终——> *(*(cpp - 2)) + 3
printf("%s\n", cpp[-1][-1] + 1); // EW *(*(cpp - 1) - 1) + 3