深入理解C/C++指针:从基本操作到复杂表达式

目录

代码展示:

示例1:指向数组结束位置之后的地址

示例2:结构体大小对指针运算的影响

示例3:访问数组元素的不同方式

示例4:逗号表达式在数组初始化中的应用

示例5:计算多维数组元素间的地址差值

示例6和7:指针与二维数组及字符串数组的操作

示例8:复杂指针表达式与字符串输出


指针是一个强大且灵活的工具,它们允许我们以低级视角直接操作内存。本文将通过一系列实例,深入解析指针如何与数组、结构体以及字符串相互作用,并演示一些复杂的指针表达式。

代码展示:

cpp 复制代码
// 指针操作与数组、结构体相关示例

// 示例1:指针指向数组结束位置之后的地址
int main() {
    int a[5] = { 1, 2, 3, 4, 5 };
    int* ptr = (int*)((&a + 1)); // 指针ptr指向数组a最后一个元素之后的位置
    printf("%d,%d\n", *(a + 1), *(ptr - 1)); // 输出数组第二个元素和最后一个元素的值
    return 0;
}

// 解释:
// 在C/C++中,`&a`获取数组a的起始地址。由于数组在内存中是连续存储的,加1后相当于指向数组结尾后的一个位置。
// `*(a + 1)` 访问数组a的第二个元素(索引为1)。
// `*(ptr - 1)` 回溯到数组a的最后一个元素的地址并获取其值。

// 示例2:结构体Test大小为20字节时,不同类型的指针运算结果
struct Test {
    int Num;         // 通常占用4个字节
    char* pcName;    // 指针类型在32位系统上占用4个字节
    short sDate;     // short 类型占用2个字节
    char cha[2];     // 字符数组 cha 占用2个字节
    short sBa[4];    // 短整型数组 sBa 占用8个字节
}*p = (struct Test*)0x10000;

int main() {
    printf("%p\n", (p + 0x1)); // 结果:0x100014 -- 结构体指针+1相当于增加一个结构体大小(20字节)
    printf("%p\n", (unsigned long)p + 0x1); // 结果:0x100001 -- 转换成无符号长整数后直接数字加1
    printf("%p\n", (unsigned int*)p + 0x1); // 结果:0x100004 -- 转换成无符号整数指针后,指针加1相当于增加4/8字节
    return 0;
}

// 示例3:访问数组元素的不同方式
int main()
{
	int a[4] = { 1,2,3,4 };
	int* ptr1 = (int*)(&a + 1);//4 --  (ptr1[-1] == *(ptr1-1)
	int* ptr2 = (int*)((int)a + 1);//0x200000 -- 0x00 000005 -> 5 + 1 = 6 -> 0x00 00 00 06
	printf("0x%x\n0x%x", ptr1[-1], *ptr2);
	return 0;
}

// 示例4:逗号表达式在初始化多维数组中的应用
int main() {
    int a[3][2] = { (0,1),(2,3),(4,5) }; // 使用逗号表达式分别初始化每一行的元素
    int* p = a[0];
    printf("%d", p[0]); // 输出:1
    return 0;
}

// 示例5:计算多维数组元素之间的地址差值
int main()
{
	int a[5][5];//int(*)[5] -- 首元素
	int(*p)[4];//int(*)[4]  
	 p = a;  //将a的地址赋值给p

	 //指针-指针取中间元素的个数
	printf("%p,%d\n", &p[4][2] - &a[4][2], &p[4][2] - &a[4][2]); //p[4][2] == *(*(p+4)+2)
	return 0;		
}

// 示例6:指向二维数组的指针操作
int main() {
    int aa[2][5] = { 1,2,3,4,5 , 6,7,8,9,10 };
    int* ptr1 = (int*)(&aa + 1);
    int* ptr2 = (int*)(*(aa + 1));
    printf("%d,%d\n", *(ptr1 - 1), *(ptr2 - 1)); // 输出:10, 5
    return 0;
}

// 示例7:指向字符数组的指针递增和字符串输出
int main() {
    char* a[] = { "Work","at","alibaba" };
    char** pa = a;
    pa++;
    printf("%s\n", *pa); // 输出:"at"

    // 示例8:复杂指针表达式与字符串输出
    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"
    printf("%s\n", cpp[-1][-1] + 1); // 输出:"EW"
    
    return 0;
}

示例1:指向数组结束位置之后的地址

cpp 复制代码
int main() {
    int a[5] = { 1, 2, 3, 4, 5 };
    int* ptr = (int*)((&a + 1)); // 指针ptr指向数组a最后一个元素之后的位置
    printf("%d,%d\n", *(a + 1), *(ptr - 1)); // 输出数组第二个和最后一个元素的值
}

在这个示例中,我们首先创建了一个包含5个整数的数组a。然后,我们将一个指向整型的指针ptr初始化为数组a的结束位置之后的一个地址。由于数组是连续存储的,因此*(ptr - 1)能够访问数组的最后一个元素。同时,*(a + 1)访问数组的第二个元素。

示例2:结构体大小对指针运算的影响

cpp 复制代码
struct Test {
    // ...
}*p = (struct Test*)0x10000;

int main() {
    printf("%p\n", (p + 0x1)); // 结果反映了结构体大小
    // ...
}

此示例展示了结构体类型指针在进行算术运算时,加1实际上意味着增加结构体所占用的字节数。在本例中,根据Test结构体的定义,其大小为20字节,因此(p + 0x1)的结果会增加相应的字节数。

示例3:访问数组元素的不同方式

cpp 复制代码
int main() {
    int a[4] = { 1,2,3,4 };
    int* ptr1 = (int*)(&a + 1);
    int* ptr2 = (int*)((int)a + 1);
    printf("0x%x,0x%x\n", ptr1[-1], *ptr2);
}

这里我们展示了两种不同的方法来计算数组结束后的地址,并回溯至数组最后一个元素。这有助于理解指针如何基于数组首地址和类型大小进行移动。

示例4:逗号表达式在数组初始化中的应用

cpp 复制代码
int main() {
    int a[3][2] = { (0,1),(2,3),(4,5) };
    // ...
}

通过逗号表达式可以分别初始化多维数组的每一行元素。例如,在这个2D数组初始化中,每个括号内的逗号表达式都用于设置该行的两个元素。

示例5:计算多维数组元素间的地址差值

cpp 复制代码
int main() {
    int a[5][5];
    // ...
    printf("%p,%d\n", &p[4][2] - &a[4][2], &p[4][2] - &a[4][2]);
}

示例6和7:指针与二维数组及字符串数组的操作

在这两个示例中,我们进一步探讨了指针如何遍历和访问二维数组及字符串数组的元素。通过对指针进行递增或利用复杂的指针表达式,我们可以实现对数组深层次元素的灵活访问和操作。

cpp 复制代码
// 示例6:指向二维数组的指针操作
int main() {
    int aa[2][5] = { 1,2,3,4,5 , 6,7,8,9,10 };
    int* ptr1 = (int*)(&aa + 1);
    int* ptr2 = (int*)(*(aa + 1));
    printf("%d,%d\n", *(ptr1 - 1), *(ptr2 - 1)); // 输出:10, 5
    return 0;
}

// 示例7:指向字符数组的指针递增和字符串输出
int main() {
    char* a[] = { "Work","at","alibaba" };
    char** pa = a;
    pa++;
    printf("%s\n", *pa); // 输出:"at"

示例8:复杂指针表达式与字符串输出

在最后一个示例中,我们进一步展示了如何利用多级指针和复杂的指针表达式来访问和操作字符数组(即字符串)。例如:

cpp 复制代码
//cpp-1和cpp--(一个会改变cpp的位置,但是cpp-1不会改变cpp当前的指向,只是返回了一个对应的值)
int main()
{
	char* c[] = { "ENTER","NEW","POINT","FIRST" };
	char** cp[] = { c + 3,c + 2,c + 1,c };
	char*** cpp = cp;
	printf("%s\n", **++cpp);//POINT -- c+2
	printf("%s\n", *-- * ++cpp + 3);//ER -- c+2 => c+1
	printf("%s\n", *cpp[-2] + 3);//st -- **(cpp-2)+2
	printf("%s\n", cpp[-1][-1] + 1);//EW  -- *(*(cpp-1)-1 )+1
	return 0;
}

在这个例子中,我们首先创建了一个包含多个字符串的字符指针数组c。然后定义了二级指针数组cp,其元素是c数组中的不同起始位置。最后,通过三级指针cpp指向cp数组。

  • 第一条语句将三级指针cpp递增后解引用两次,得到的是cp[1]所指向的字符串,即"POINT"。
  • 第二条语句先递增cpp并解引用一次,然后对结果进行自减操作再解引用,接着加上偏移量3,得到的是"POINT"字符串从第四个字符开始的部分,即"ER"。
  • 第三条语句直接使用cpp[-2]获取到cp数组中倒数第二个元素,并在其基础上加3,因此输出为"NEW"字符串从第三个字符开始的部分,即"st"。
  • 最后一条语句通过cpp[-1][-1]获取到cp数组中最后一个元素所指向的字符串"FIRST"的最后一个字符'F',然后加1,输出为"EW"。

通过这些复杂的指针表达式示例,我们可以看到指针在C/C++中能够实现灵活且深入的数据访问和操作,但同时也要求开发者对其有深刻的理解以避免潜在的错误和陷阱。在实际编程中,正确合理地使用指针可以大大提高代码效率,同时也能让程序逻辑更为清晰和紧凑。

相关推荐
IT 青年6 分钟前
数据结构 (11)串的基本概念
数据结构
萧萧玉树16 分钟前
分布式在线评测系统
前端·c++·后端·负载均衡
bbppooi23 分钟前
堆的实现(完全注释版本)
c语言·数据结构·算法·排序算法
FFDUST30 分钟前
C++ 优先算法 —— 无重复字符的最长子串(滑动窗口)
c语言·c++·算法·leetcode
shiming887931 分钟前
C/C++链接数据库(MySQL)超级详细指南
c语言·数据库·c++
前端白袍32 分钟前
C语言:C语言实现对MySQL数据库表增删改查功能
c语言·数据库·mysql
m0_738054561 小时前
【leetcode】全排列 回溯法
c++·算法·leetcode·回溯法
ZZZ_O^O1 小时前
【贪心算法第五弹——300.最长递增子序列】
c++·学习·算法·leetcode·贪心算法
码农多耕地呗1 小时前
哈希表—acwing
数据结构·散列表
Koishi_TvT1 小时前
蓝桥杯c++算法秒杀【6】之动态规划【下】(数字三角形、砝码称重(背包问题)、括号序列、异或三角:::非常典型的必刷例题!!!)
c语言·c++·算法·性能优化·蓝桥杯·动态规划·c