深入理解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++中能够实现灵活且深入的数据访问和操作,但同时也要求开发者对其有深刻的理解以避免潜在的错误和陷阱。在实际编程中,正确合理地使用指针可以大大提高代码效率,同时也能让程序逻辑更为清晰和紧凑。

相关推荐
凌云行者30 分钟前
OpenGL入门005——使用Shader类管理着色器
c++·cmake·opengl
凌云行者33 分钟前
OpenGL入门006——着色器在纹理混合中的应用
c++·cmake·opengl
~yY…s<#>1 小时前
【刷题17】最小栈、栈的压入弹出、逆波兰表达式
c语言·数据结构·c++·算法·leetcode
可均可可2 小时前
C++之OpenCV入门到提高004:Mat 对象的使用
c++·opencv·mat·imread·imwrite
白子寰2 小时前
【C++打怪之路Lv14】- “多态“篇
开发语言·c++
小芒果_012 小时前
P11229 [CSP-J 2024] 小木棍
c++·算法·信息学奥赛
XuanRanDev2 小时前
【每日一题】LeetCode - 三数之和
数据结构·算法·leetcode·1024程序员节
gkdpjj2 小时前
C++优选算法十 哈希表
c++·算法·散列表
代码猪猪傻瓜coding2 小时前
力扣1 两数之和
数据结构·算法·leetcode
王俊山IT2 小时前
C++学习笔记----10、模块、头文件及各种主题(一)---- 模块(5)
开发语言·c++·笔记·学习