C语言入门系列:探秘二级指针与多级指针的奇妙世界

2024-06-24 12:20

本文主要是介绍C语言入门系列:探秘二级指针与多级指针的奇妙世界,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

文章目录

  • 一,指针的回忆杀
    • 1,指针的概念
    • 2,指针的声明和赋值
    • 3,指针的使用
      • 3.1 直接给指针变量赋值
      • 3.2 通过*运算符读写指针指向的内存
        • 3.2.1 读
        • 3.2.2 写
  • 二,二级指针详解
    • 1,定义
    • 2,示例说明
    • 3,二级指针与一级指针、普通变量的关系
      • 3.1,与一级指针的关系
      • 3.2,与普通变量的关系,
      • 示例说明
    • 4,二级指针的常见用途
    • 5,二级指针扩展到多级指针
  • 小结

C语言的学习之旅中,二级指针乃至多级指针往往是初学者感到“恶心”却又不得不面对的重要概念。

不过,一旦掌握了它们的精髓,你会发现它们其实并不那么可怕,反而在某些高级应用中显得尤为强大和灵活。

一,指针的回忆杀

1,指针的概念

指针就是内存地址,内存地址指向内存中的一个单元,这个单元就像一个个小房间,里面住着某种数据,可能是整数,可能是浮点数,可能是字符,也可能是另一个指针。

为了方便理解和记忆,可以把指针当作酒店房间号。

指针作为一种特殊的数据,也需要存储在内存中。

2,指针的声明和赋值

C语言通过指针变量为指针开辟一块内存。

int num = 10;
int* p;
p = #

如上代码,定义了指针变量p,声明变量时如果带有*号,说明这个变量是指针变量。

这个*号的作用是告诉编译器,这个指针变量p要存储的是变量num的值的内存地址,这个内存地址从形式上看也没什么特别,就是一个整数。

上面的代码中p = #,通过地址运算符&取出变量num的地址,赋给指针变量p

在这里插入图片描述

3,指针的使用

3.1 直接给指针变量赋值

上面的代码中p = # 所示,可以和普通变量一样,直接给指针变量赋值,只是这个值代表的是内存地址而已。

3.2 通过*运算符读写指针指向的内存

3.2.1 读

我们还可以通过* 运算符操作指针指向的内存中的值。

int num = 10;
int* p;
p = #int num2 = *p;

如上代码最后一行int num2 = *p;,运算符*的作用是告诉编译器,找到指针p指向的地址,从中读取数据。

3.2.2 写

当然,可以修改指针指向的内存中的数据。

int num = 10;
int* p;
p = #*p = 100;

上述代码*p = 100;,将指针p指向的内存中数据的值修改为100。注意,此时num的值也变为100了,原因是修改的就是num的内存中的值。

二,二级指针详解

1,定义

二级指针,顾名思义,是指向指针的指针,即一个变量,其类型是指向指针的地址。在C语言中,声明一个二级指针的基本形式如下:

数据类型** pointerName;

这里,**pointerName表示pointerName是一个指向指针的指针,它存储的是一个指针变量的地址。

int num = 10;
int* p;
p = #int** pp = &p;

如上,二级指针变量pp指向的是指针变量p的地址。
在这里插入图片描述

如上图所示,发现二级指针需要跳2级才能获取普通变量内存中的值,这也是二级指针名称的来源。

2,示例说明

以一个整型变量为例:

int a = 12;
int *p = &a;
int **pp = &p;

根据上述定义,我们可以得出以下计算结果:

p == &a
*p == 12
pp == &p
*pp == p
**pp == a

**pp就是获取a变量的值,因为pp指向的是p的地址,而p指向的又是a的地址,所以 **pp 就相当于a

3,二级指针与一级指针、普通变量的关系

3.1,与一级指针的关系

一级指针存储的是普通变量的地址,而二级指针存储的是一级指针的地址。
通过二级指针,我们可以间接访问到一级指针所指向的数据,实现更深层次的间接访问。

3.2,与普通变量的关系,

普通变量是数据存储的最直接形式,通过一级指针,我们增加了一层间接访问;二级指针则在此基础上再增加一层间接性,使得对普通变量的操作更为灵活。

示例说明

#include <stdio.h>
#include <stdlib.h>int main() {int value = 10;int *p = &value; // 一级指针,指向valueint **pp = &p;   // 二级指针,指向一级指针pprintf("Value: %d\n", value);      // 直接访问printf("Value via *p: %d\n", *p);  // 通过一级指针访问printf("Value via **pp: %d\n", **pp); // 通过二级指针访问return 0;
}

此例中,value是一个普通整型变量,p是一级指针指向value,而pp作为二级指针,则指向p。通过**pp,我们依然能最终访问到value的值,展示了指针间接访问的层级关系。

4,二级指针的常见用途

  • 动态内存管理:在动态分配数组时,可以使用二级指针来存储数组的首地址,便于管理和释放内存。
  • 函数参数传递:当希望函数能够修改外部指针变量(而非指针所指向的内容)时,需使用二级指针作为函数参数。
  • 指向指针的指针运算:在复杂数据结构操作中,二级指针提供了对指针进行指针运算的能力,如链表的节点交换、树结构的遍历等。

5,二级指针扩展到多级指针

多级指针的概念基于二级指针进一步扩展,即指针的指针的指针……依此类推。

一句话,多级指针存储的是上一级指针的地址。

虽然在日常编程中较为罕见,但在特定场景下,如复杂的内存管理、高度抽象的数据结构设计或底层系统编程中,多级指针可以提供极大的灵活性。

示例:三级指针

#include <stdio.h>void printViaTriplePointer(int ***triplePtr) {printf("Value via ***triplePtr: %d\n", ***triplePtr);
}int main() {int value = 7;int *p = &value;int **pp = &p;int ***triplePtr = &pp;printViaTriplePointer(triplePtr);return 0;
}

这个例子中,triplePtr是一个三级指针,通过它我们依然能够最终访问到最初定义的value。虽然看起来繁琐,但在特定逻辑或系统级操作中,这样的间接访问机制可能非常有用。

小结

总之,二级指针乃至多级指针虽初看复杂,实则是C语言中处理复杂数据结构和内存管理的强大工具。通过实践和理解,你将逐步揭开它们的神秘面纱,发现其背后的逻辑之美。

这篇关于C语言入门系列:探秘二级指针与多级指针的奇妙世界的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1090131

相关文章

深入理解Go语言中二维切片的使用

《深入理解Go语言中二维切片的使用》本文深入讲解了Go语言中二维切片的概念与应用,用于表示矩阵、表格等二维数据结构,文中通过示例代码介绍的非常详细,需要的朋友们下面随着小编来一起学习学习吧... 目录引言二维切片的基本概念定义创建二维切片二维切片的操作访问元素修改元素遍历二维切片二维切片的动态调整追加行动态

Go语言中make和new的区别及说明

《Go语言中make和new的区别及说明》:本文主要介绍Go语言中make和new的区别及说明,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录1 概述2 new 函数2.1 功能2.2 语法2.3 初始化案例3 make 函数3.1 功能3.2 语法3.3 初始化

从入门到精通MySQL联合查询

《从入门到精通MySQL联合查询》:本文主要介绍从入门到精通MySQL联合查询,本文通过实例代码给大家介绍的非常详细,需要的朋友可以参考下... 目录摘要1. 多表联合查询时mysql内部原理2. 内连接3. 外连接4. 自连接5. 子查询6. 合并查询7. 插入查询结果摘要前面我们学习了数据库设计时要满

从入门到精通C++11 <chrono> 库特性

《从入门到精通C++11<chrono>库特性》chrono库是C++11中一个非常强大和实用的库,它为时间处理提供了丰富的功能和类型安全的接口,通过本文的介绍,我们了解了chrono库的基本概念... 目录一、引言1.1 为什么需要<chrono>库1.2<chrono>库的基本概念二、时间段(Durat

Go语言中nil判断的注意事项(最新推荐)

《Go语言中nil判断的注意事项(最新推荐)》本文给大家介绍Go语言中nil判断的注意事项,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧... 目录1.接口变量的特殊行为2.nil的合法类型3.nil值的实用行为4.自定义类型与nil5.反射判断nil6.函数返回的

Go语言数据库编程GORM 的基本使用详解

《Go语言数据库编程GORM的基本使用详解》GORM是Go语言流行的ORM框架,封装database/sql,支持自动迁移、关联、事务等,提供CRUD、条件查询、钩子函数、日志等功能,简化数据库操作... 目录一、安装与初始化1. 安装 GORM 及数据库驱动2. 建立数据库连接二、定义模型结构体三、自动迁

解析C++11 static_assert及与Boost库的关联从入门到精通

《解析C++11static_assert及与Boost库的关联从入门到精通》static_assert是C++中强大的编译时验证工具,它能够在编译阶段拦截不符合预期的类型或值,增强代码的健壮性,通... 目录一、背景知识:传统断言方法的局限性1.1 assert宏1.2 #error指令1.3 第三方解决

Go语言代码格式化的技巧分享

《Go语言代码格式化的技巧分享》在Go语言的开发过程中,代码格式化是一个看似细微却至关重要的环节,良好的代码格式化不仅能提升代码的可读性,还能促进团队协作,减少因代码风格差异引发的问题,Go在代码格式... 目录一、Go 语言代码格式化的重要性二、Go 语言代码格式化工具:gofmt 与 go fmt(一)

从入门到精通MySQL 数据库索引(实战案例)

《从入门到精通MySQL数据库索引(实战案例)》索引是数据库的目录,提升查询速度,主要类型包括BTree、Hash、全文、空间索引,需根据场景选择,建议用于高频查询、关联字段、排序等,避免重复率高或... 目录一、索引是什么?能干嘛?核心作用:二、索引的 4 种主要类型(附通俗例子)1. BTree 索引(

Redis 配置文件使用建议redis.conf 从入门到实战

《Redis配置文件使用建议redis.conf从入门到实战》Redis配置方式包括配置文件、命令行参数、运行时CONFIG命令,支持动态修改参数及持久化,常用项涉及端口、绑定、内存策略等,版本8... 目录一、Redis.conf 是什么?二、命令行方式传参(适用于测试)三、运行时动态修改配置(不重启服务