作为C语言的核心概念,指针常常让初学者感到困惑。本文将从数组与指针的关系入手,逐步揭开指针在数组操作、函数传参以及多级指针中的神秘面纱,帮助你建立系统的指针知识体系。

 

一、数组名的双重身份:首地址与整体标识

 

在C语言中,数组名就像一个"双面特工",大多数时候它代表数组首元素的地址,但在特定场景下又会化身整个数组的标识。我们先通过一段简单的代码验证:

 

 

#include <stdio.h>

int main() {

    int arr[10] = {1,2,3,4,5,6,7,8,9,10};

    printf("&arr[0] = %p\n", &arr[0]);

    printf("arr = %p\n", arr);

    return 0;

}

 

 

运行结果会发现 &arr[0] 和 arr 的输出地址完全相同,这说明数组名默认就是首元素的地址。但接下来的代码却让人产生疑惑:

 

 

#include <stdio.h>

int main() {

    int arr[10] = {1,2,3,4,5,6,7,8,9,10};

    printf("%d\n", sizeof(arr)); // 输出40(假设int为4字节)

    return 0;

}

 

 

如果数组名是地址, sizeof(arr) 应该输出4或8(指针长度),但实际输出的是整个数组的大小。这是因为数组名有两个特殊情况:

 

-  sizeof(数组名) :此时数组名代表整个数组,计算的是数组总字节数

-  &数组名 :获取整个数组的地址,与首元素地址数值相同但含义不同

 

通过下面的代码可以直观看到地址偏移的差异:

 

 

printf("&arr[0] = %p\n", &arr[0]); // 首元素地址

printf("&arr[0]+1 = %p\n", &arr[0]+1); // 偏移4字节(int类型)

printf("arr = %p\n", arr); // 首元素地址

printf("arr+1 = %p\n", arr+1); // 偏移4字节

printf("&arr = %p\n", &arr); // 数组地址

printf("&arr+1 = %p\n", &arr+1); // 偏移40字节(整个数组大小)

 

 

关键点总结:除了 sizeof 和取地址符 & 的特殊情况,其他场景下数组名都表示首元素地址。

 

二、指针与数组的交互:访问数组的新方式

 

掌握了数组名的本质后,我们可以用指针更灵活地操作数组。下面是一个通过指针输入输出数组的例子:

 

 

#include <stdio.h>

int main() {

    int arr[10] = {0};

    int i = 0;

    int sz = sizeof(arr)/sizeof(arr[0]);

    int* p = arr; // 指针指向数组首元素

    

    // 输入数组元素

    for(i=0; i<sz; i++) {

        scanf("%d", p+i); // 等价于arr+i

    }

    

    // 输出数组元素

    for(i=0; i<sz; i++) {

        printf("%d ", *(p+i)); // 方式1:解引用+偏移

    }

    return 0;

}

 

 

更有趣的是,我们还可以直接用 p[i] 来访问元素,这是因为 p[i] 本质上等价于 *(p+i) 。就像 arr[i] 等价于 *(arr+i) 一样,C语言在编译时会将数组下标访问转换为指针偏移操作。

 

重要结论:指针和数组在访问元素时具有高度一致性, p[i] 和 *(p+i) 是完全等价的表达。

 

三、数组传参的真相:为什么函数内算不出数组长度?

 

初学者常遇到的一个困惑是:为什么在函数内部无法通过 sizeof 获取数组长度?看下面的例子:

 

 

#include <stdio.h>

void test(int arr[]) {

    int sz2 = sizeof(arr)/sizeof(arr[0]);

    printf("sz2 = %d\n", sz2); // 输出1(或2,取决于指针大小)

}

int main() {

    int arr[10] = {1,2,3,4,5,6,7,8,9,10};

    int sz1 = sizeof(arr)/sizeof(arr[0]);

    printf("sz1 = %d\n", sz1); // 输出10

    test(arr);

    return 0;

}

 

 

这里的关键在于:数组传参时,实际传递的是首元素的地址,而不是数组本身。因此函数形参的 int arr[] 本质上等价于 int* arr ,此时 sizeof(arr) 计算的是指针变量的大小(4或8字节),而不是数组长度。

 

正确的做法是在传数组时同时传递长度参数:

 

 

void bubble_sort(int arr[], int sz) {

    // 使用sz作为循环条件

    for(int i=0; i<sz-1; i++) {

        // ...排序逻辑

    }

}

 

 

总结:一维数组传参的本质是传递指针,函数内部必须通过额外参数获取数组长度。

 

四、冒泡排序的优化:指针思维的实际应用

 

冒泡排序是理解指针操作的经典场景,我们先看基础实现:

 

 

void bubble_sort(int arr[], int sz) {

    for(int i=0; i<sz-1; i++) {

        for(int j=0; j<sz-i-1; j++) {

            if(arr[j] > arr[j+1]) {

                int tmp = arr[j];

                arr[j] = arr[j+1];

                arr[j+1] = tmp;

            }

        }

    }

}

 

 

用指针思维优化后,我们可以添加"有序标记"来减少不必要的比较:

 

 

void bubble_sort_optimized(int arr[], int sz) {

    for(int i=0; i<sz-1; i++) {

        int flag = 1; // 假设当前已排序

        for(int j=0; j<sz-i-1; j++) {

            if(arr[j] > arr[j+1]) {

                flag = 0; // 发生交换,标记为无序

                int tmp = arr[j];

                arr[j] = arr[j+1];

                arr[j+1] = tmp;

            }

        }

        if(flag == 1) break; // 若未发生交换,提前结束

    }

}

 

 

这种优化思路体现了指针操作的核心思想:通过地址偏移访问数据,并利用标记位减少无效操作。

 

五、从一级到二级:指针的指针

 

指针变量本身也是变量,也有地址,这就引出了二级指针的概念。看下面的示例:

 

 

#include <stdio.h>

int main() {

    int a = 10;

    int *pa = &a; // 一级指针,指向int变量

    int **ppa = &pa; // 二级指针,指向一级指针

    return 0;

}

 

 

二级指针的运算可以分解为两步解引用:

 

-  *ppa :通过二级指针获取一级指针的地址,等价于 pa 

-  **ppa :再解引用一次,获取原始变量的值,等价于 a 

 

比如:

 

 

int b = 20;

*ppa = &b; // 等价于 pa = &b,让一级指针指向b

**ppa = 30; // 等价于 *pa = 30,等价于 b = 30

 

 

形象比喻:一级指针是"指向房间的钥匙",二级指针就是"指向钥匙的盒子",要拿到房间里的东西,需要先打开盒子取出钥匙,再用钥匙开门。

 

六、指针数组:存储指针的容器

 

类比整型数组(存放int值)和字符数组(存放char值),指针数组就是存放指针的数组,定义形式为:

 

 

int *ptr_arr[5]; // 5个指向int的指针组成的数组

 

 

每个元素都是一个指针,可以指向不同的内存区域:

 

c

int a = 10, b = 20, c = 30;

int *ptr_arr[3] = {&a, &b, &c}; // 初始化指针数组

 

 

指针数组的价值在于可以灵活管理多个地址,尤其是在处理字符串时非常有用:

 

char *strs[] = {"hello", "world", "c", "pointer"};

 

 

七、用指针数组模拟二维数组

 

虽然C语言有真正的二维数组,但在某些场景下可以用指针数组模拟二维效果:

 

#include <stdio.h>

int main() {

    int arr1[] = {1,2,3,4,5};

    int arr2[] = {2,3,4,5,6};

    int arr3[] = {3,4,5,6,7};

    

    // 指针数组存储三个一维数组的首地址

    int *parr[3] = {arr1, arr2, arr3};

    

    // 模拟二维数组访问

    for(int i=0; i<3; i++) {

        for(int j=0; j<5; j++) {

            printf("%d ", parr[i][j]);

        }

        printf("\n");

    }

    return 0;

}

 

 

这种模拟的本质是:

 

-  parr[i] 获取第i个一维数组的首地址

-  parr[i][j] 等价于 *(parr[i[ij) ,访问一维数组的第j个元素

 

注意:这与真正的二维数组不同,指针数组模拟的"二维数组"各行内存不一定连续,而真正的二维数组在内存中是连续存储的。

 

总结:指针思维的核心

 

理解指针的关键在于建立"地址操作"的思维模式:

 

1. 数组名本质是首地址,除了 sizeof 和 & 的特殊情况

2. 指针+偏移是访问数组元素的底层实现

3. 函数传数组本质是传指针,需额外传递长度

4. 多级指针是指针的嵌套,解引用次数等于指针级别

5. 指针数组是存储地址的容器,可灵活管理多个内存区域

 

通过这些知识,我们不仅掌握了指针的核心概念,也理解了C语言内存操作的底层逻辑,这对后续学习动态内存分配、数据结构等内容至关重要。

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/diannao/87732.shtml
繁体地址,请注明出处:http://hk.pswp.cn/diannao/87732.shtml
英文地址,请注明出处:http://en.pswp.cn/diannao/87732.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Windows PPT/word怎么pdf不降低分辨率,插入可编辑

Windows PPT/word怎么pdf不降低分辨率 下载软件Inkscape&#xff1a;Inkscape - Draw Freely. | Inkscape 然后使用Inkscape将你的PDF转为svg, 然后用office的PPT打开&#xff0c;将svg复制进PPT/word&#xff0c;然后保存就可以了 插入可编辑的&#xff08;只能通过Mac的才可…

vue3 select 选中值时,即获得id,也获得name值并且输入框正确选中

1.获取 name和id 直接绑定对象 将 value 绑定为整个对象&#xff0c;通过 change 事件获取完整数据 value-key 绑定唯一标识 value 绑定为整个对象&#xff0c;通过 change 事件获取完整数据 <el-select v-model"selectedItem" change"handleChange"…

什么是Seata

Seata的实现原理主要围绕其核心架构&#xff08;TC/TM/RM&#xff09;和事务模式&#xff08;如AT、TCC等&#xff09;展开&#xff0c;通过协调全局事务与分支事务的协作保证数据一致性。以下是核心实现原理的详细解析&#xff1a; ⚙️ ​​一、核心架构协作机制​​ Seata通…

linux ARM64架构用户空间和内核空间的区分

一、ARM64 架构地址空间的「黄金分割」 ARM64&#xff08;ARMv8-A&#xff09;采用 48 位虚拟地址&#xff08;Linux 默认配置&#xff09;&#xff0c;总空间为 256TB&#xff0c;分为高低两个 128TB 区域&#xff1a; 1. 地址空间整体布局 虚拟地址空间&#xff08;48位&a…

51单片机重要知识点1

1. 在读IO口状态前必须先写该IO口1. 即让下拉MOS高阻断路。如&#xff1a; P221; KEYP22; 复位状态各IO口1的。另外大多数情况都不会IO口即做输入又做输出的。设计思想永远不要复杂化高难度编程&#xff0c;而要简单明了。 2.

【解析法与几何法在阻尼比设计】自控

解析法与几何法在阻尼比设计中的详细对比 一、解析法&#xff1a;基于数学方程的定量求解 核心思想&#xff1a;通过特征方程与根轨迹条件建立代数关系&#xff0c;直接求解满足阻尼比要求的系统参数。 1. 适用场景 二阶系统或可简化为二阶系统的高阶系统&#xff08;主导极…

搭建pikachu靶场

文章目录 一、pikachu是什么?二、搭建环境1.下载链接三、安装教程phpstudy安装配置pikachu 靶场安装配置总结一、pikachu是什么? Pikachu 靶场是一个专门为网络安全爱好者和学习者设计的 Web 安全靶场。它旨在帮助用户学习和实践常见的 Web安全漏洞和攻击手法。Pikachu 靶场通…

OpenStack Dashboard在指定可用域(Availability Zone)、指定节点启动实例

通过OpenStack Dashboard在指定可用域&#xff08;Availability Zone&#xff09;中创建实例的完整配置&#xff0c;涵盖可用域创建、节点管理、关系与限制的详细步骤&#xff1a; 一、可用域&#xff08;Availability Zone&#xff09;的概念与关系 0.指定域、指定节点、指定…

6.时间序列预测的模型部署

6.1实验设置和python版的Azure机器学习SDK介绍 6.1.1 WorkSpace 6.1.2 Experiment 6.1.3 Run 6.1.4 Model 6.1.5 ComputeTarget、RunConfiguration和ScriptRunConfig 6.1.6 Image 和 Webservice 6.2 机器学习模型部署 6.3 时间序列预测的解决方案体系结构部署示例 6.3.1 训练…

加密货币:比特币

比特币&#xff08;Bitcoin&#xff0c;简称BTC是一种去中心化的数字货币&#xff0c;由中本聪&#xff08;Satoshi Nakamoto&#xff09;在2008年提出&#xff0c;并于2009年正式推出。它是首个基于区块链技术的加密货币&#xff0c;旨在实现点对点的价值传输&#xff0c;无需…

【Dv3Admin】应用WSGI启动配置文件解析

在 Django 项目部署中&#xff0c;WSGI 是连接 Web 服务器与应用的标准接口。它负责接收请求、交由 Django 处理并返回响应&#xff0c;是系统上线运行的基础组件。理解其作用&#xff0c;有助于掌握项目的启动流程与部署逻辑。 本文解析 application/wsgi.py 模块的结构与功能…

aws各类服务器编号

在 AWS 中&#xff0c;服务器实例编号通常由一个字母和数字组合而成&#xff0c;每个字母代表不同的实例系列&#xff0c;数字则表示该系列的不同版本或规格。以下是对常见实例系列编号的解释&#xff1a; T 系列&#xff08;突发性能型&#xff09; 特点&#xff1a;T 系列实例…

Netty实战:从核心组件到多协议实现(超详细注释,udp,tcp,websocket,http完整demo)

目录 前言 一、为什么选择Netty&#xff1f; 二、Netty核心组件解析 三、多协议实现 1. TCP协议实现&#xff08;Echo服务&#xff09; 2. UDP协议实现&#xff08;广播服务&#xff09; 3. WebSocket协议实现&#xff08;实时通信&#xff09; 4. HTTP协议实现&#x…

MCP出现的意义是什么?让 AI 智能体更模块化

AI 智能体现在能做的事情真的很厉害&#xff0c;可以思考、规划&#xff0c;还能执行各种复杂任务&#xff0c;而且代码量并不大。这让开发者看到了一个机会&#xff1a;把那些庞大复杂的代码库和 API 拆解成更实用的模块。 不过要让这些智能变成现实世界里真正能用的东西&…

【深度剖析】领信卓越:福耀玻璃的数字化转型(下篇3:阶段成效3-打造从功能部件到数据终端跃迁的智能化产品)

在数字经济持续发展的背景下,企业数字化转型方案成为实现转型的关键。不同行业内的企业因转型动机和路径的差异,其转型成效也各异。福耀玻璃自1983年创立以来,从一家濒临破产的乡镇水表玻璃厂蜕变为全球汽车玻璃行业的领军企业,其发展历程堪称中国制造业的典范。创始人曹德…

告别停机烦恼!AWS EC2实例升级的“零中断”实战方案

引言&#xff1a; “服务器要升级了&#xff0c;今晚得停机维护...” —— 这句话曾是多少运维工程师的“噩梦”&#xff0c;也是业务部门最不愿听到的通知。在追求极致用户体验和7x24小时业务连续性的今天&#xff0c;停机窗口已成为难以承受之重。尤其是在云时代&#xff0c…

奇葩的el-checkbox-group数组赋值

背景。自定义表单。多选组件封装。当选项被多选后&#xff0c;el-checkbox-group中v-model的值以数组形式存储了选中的内容。奇葩问题。存储的值时label属性。而渲染时需要使用插值单独将选项的名称渲染出来。而在el-checkbox标签中:label要赋值option.value很别扭。 <temp…

【Python系列PyCharm实战】ModuleNotFoundError: No module named ‘sklearn’ 系列Bug解决方案大全

【Python系列Colab实战】ModuleNotFoundError: No module named ‘sklearn’ 系列Bug解决方案大全 一、摘要 在使用 Jupyter、PyCharm 或 Google Colab 进行机器学习开发时&#xff0c;导入 sklearn&#xff08;scikit-learn&#xff09;相关模块时&#xff0c;常会遇到一系列…

小白的进阶之路系列之十六----人工智能从初步到精通pytorch综合运用的讲解第九部分

从零开始学习NLP 在这个由三部分组成的系列中,你将构建并训练一个基本的字符级循环神经网络 (RNN) 来对单词进行分类。 你将学习 如何从零开始构建循环神经网络NLP 的基本数据处理技术如何训练 RNN 以识别单词的语言来源。从零开始学自然语言处理:使用字符级 RNN 对名字进行…

MySQL在ubuntu下的安装

前言&#xff1a; 安装与卸载中&#xff0c;用户全部进行切换为root ,一旦安装&#xff0c;普通用户也是可以进行使用 初期联系mysql时不进行用户的管理&#xff0c;直接使用root 进行即可&#xff0c;尽快适应mysql语句&#xff0c;后面进行学了用户管理再考虑新建普通用户。&…