当前位置: 首页 > news >正文

C语言KR圣经笔记 5.3指针和数组 5.4地址运算

5.3 指针和数组

在 C 语言中,指针和数组有着非常强的关联,强到应当把两者同时拿出来讨论。任何可以通过数组下标来做到的操作,也都能用指针来做到。而指针的版本通常会更快,但至少对初学者来说会更难理解。

如下声明

int a[10];

定义了一个大小为 10 的数组 a,即由10个名为 a[0], a[1], ... a[9] 的连续对象所组成的块。

用 a[i] 来表示数组的第 i 个元素。如果 pa 是指向整数的指针,其声明为

int *pa;

则赋值语句

pa = &a[0];

使 pa 指向 a 的第 0 个元素;也就是说, pa 包含了 a[0] 的地址。

现在如果再赋值

x = *pa;

会将 a[0] 的内容拷贝给 x。

如果 pa 指向数组的某个特定元素,则根据定义,pa + 1 会指向下一个元素,pa + i 指向 pa 之后的第 i 个元素, pa - i 指向 pa 之前的第 i 个元素。因此,如果 pa 指向 a[0],则

*(pa+1)

表示 a[1] 的内容,而 pa + i 表示 a[i] 的地址,而 *(pa+i) 是 a[i] 的内容。

其实,不管数组 a 里面的变量是什么类型,占据多大空间,上述说法都是正确的。“将指针加1” 的含义是 pa + 1 指向下一个对象,由此扩展到所有指针运算,可得 pa + i 指向 pa 之后的第 i 个对象。

下标和指针运算之间有非常紧密的关联。根据定义,类型为数组的变量或表达式,其值为数组第 0 个元素的地址。因此,经过如下赋值之后

pa = &a[0];

pa 和 a 有相同的值。由于数组的名称就是其首个元素位置的同义词,赋值 pa = &a[0] 也能够写成

pa = a;

而更令人惊奇(至少在首次看到时)的事实是,a[i] 也能够写成 *(a+i)。在计算 a[i] 时,C会立即将其转换为 *(a+i);两种形式是等价的。将操作符 & 分别应用到两者,就能得到 &a[i] 和 a+i 也是等价的:a+i 是 a 之后第 i 个元素的地址。从另一个角度看,如果 pa 是指针,可以对它加下标来使用;pa[i] 等价于 *(pa+i)。简而言之, 数组+下标的表达式,等价于指针+偏移的表达式。

数组名称和指针有一个区别必须牢记。指针是一个变量,因此 pa=a 和 pa++ 都是合法的。但数组名不是变量;像 a=pa 和 a++ 这样的结构是非法的。

当数组名称被传递给函数时,传递的是数组首元素的位置。在被调函数中,该参数是一个局部变量,因此数组名参数是一个指针,即一个包含地址的变量。我们可以利用这个事实来写另一个版本的 strlen,计算字符串的长度:

/* strlen: 返回字符串s的长度 */
int strlen(char *s)
{int n;for (n = 0; *s != '\0'; s++)n++;return n;
}

由于 s 是一个指针,对其递增是完全合法的;s++ 对调用 strlen 的函数里面的字符串不起任何效果,它仅仅是对该指针在 strlen 中的私有拷贝进行递增。这意味着如下调用:

strlen("hello, world");    /* 字符串常量 */
strlen(array);             /* char array[100]; */
strlen(ptr);               /* char *ptr; */

都是可行的。

作为函数定义中的形参

char s[];

char *s;

是等价的;我们更偏向后者,因为它更显式地表明该参数是指针。当数组名称被传递给函数时,函数可以根据自己的意愿来认定它处理的是数组还是指针,并进行对应的操作。如果能够让代码看起来更恰当、更清晰,甚至可以使用两种表示法。

通过传递指向子数组开头的指针,可以将数组的一部分传递给函数。例如,如果 a 是数组,则

f(&a[2])

f(a+2)

都是把从 a[2] 开头的子数组地址传给函数 f 。在函数 f 中,参数声明可以写为

f(int arr[]) { ... }

或是

f(int *arr) { ... }

从 f 的角度而言,参数指向的是大数组的一部分还是数组真实的首地址,都无关紧要。

如果能保证元素的存在,可以将数组向前索引;p[-1] 和 p[-2] 等在语法上都是合法的,它们都指向 紧挨着 p[0] 的之前的元素。当然,引用数组边界之外的元素是非法的。

5.4 地址运算

如果 p 是指向数组中某个元素的指针,则 p++ 将 p 递增以指向下一个元素,而 p+=i 将 p 递增 i 以指向当前元素后的第 i 个元素。这些及其类似的结构,是指针或地址运算的最简单形式。

C 语言地址运算的方式是一致而且有规律的;对指针,数组和地址运算的集成是 C 语言的优势之一。我们写一个简单原始的内存管理器来说明。有两个例程。第一个是 alloc(n),返回一个指针 p, 指向 n 个连续字符的位置,alloc 的调用者可以用它来保存字符。第二个是 afree(p),释放通过alloc 获取到的内存,使这块内存后续能被重用。说它们是“简单原始”的,因为必须以 alloc 相反的顺序来调用 afree 。也就是说,alloc 和 afree 管理的内存是一个栈,或者叫后进先出队列。标准库提供的类似函数叫做 malloc 和 free ,没有这个限制;在8.7节会展示如何来实现它们。

最简单的实现是让 alloc 交出一个我们称之为 allocbuf 的大字符数组中的一小部分。这个数组是 alloc 和 free 私有的。因为它们使用指针而不是下标来处理,其他例程不需要知道数组的名字,因此在包含 alloc 和 free 的源文件中,该数组可以声明为 static,使之对外部不可见。在实际的内存管理器中,数组甚至都不需要有名字;它可能是通过调用 malloc 或请求操作系统,从而获取到的一个指向未命名内存块的指针。

另一个所需的信息是 allocbuf 用了多少。我们使用一个指针 allocp 来指向下一个空闲的元素。当 alloc 被要求 n 个字符时,它检查在 allocbuf 中是否存在足够的空间。如果是,它返回当前的 allocp 值(即空闲块的开头),并将其递增 n,以指向下一个空闲区域。如果没有足够空间,alloc 返回零。afree(p) 仅仅是将 allocp 设为 p,如果 p 在 allocbuf 内部的话。

#define ALLOCSIZE 10000    /* 可用空间 */static char allocbuf[ALLOCSIZE];    /* alloc所用空间 */
static char *allocp = allocbuf;     /* 下一个空闲位置 */char *alloc(int n)    /* 返回指向n个字符的指针 */
{if (allocbuf + ALLOCSIZE - allocp >= n) {    /* 空间足够 */allocp += n;return allocp - n;    /* 旧的指针 */} else {        /* 空间不足 */return 0;}
}void afree(char *p)    /* 释放p指向的空间 */
{if (p >= allocbuf && p < allocbuf + ALLOCSIZE)allocp = p;
}

通常,指针可以像其他变量一样初始化,不过正常情况下,有意义的初始值只有零,或者是包含之前定义过且类型匹配的地址的表达式。如下声明

static char *allocp = allocbuf;

将 allocp 定义为字符串指针,并将其初始化为指向 allocbuf 的开头,即程序启动时的下一个空闲位置。这也可以写成

static char *allocp = &allocbuf[0];

因为数组名称正是其第0个元素的地址。

如下判断

if (allocbuf + ALLOCSIZE - allocp >= n) { /* 足够 */

用来检查是否有足够的空间可满足分配 n 个字符的请求。如果有,则 allocp 的新值最多能到达的位置比 allocbuf 的末尾元素还超过一个【注意这个地址已经不属于allocbuf了,只可用来比较,不能分配】。如果请求能够满足,alloc 返回指向一块字符的起始位置的指针(注意alloc函数的声明)。如果不能,alloc 必须能够返回指示空间不足的信号。C 语言保证 0 永远不会是数据的合法地址,因此返回值零用来指示不正常的事件,此时为空间不足。

指针和整数是不可以相互交换使用的。零是唯一的例外:常量零可以被赋给指针,且指针可以与常量零比较。通常用符号常量 NULL 作为助记符来代替零,以更清晰地表示这是指针的特殊值。NULL 在 <stdio.h> 中定义。此后我们都将使用 NULL。

如下判断

if (allocbuf + ALLOCSIZE - allocp >= n) { /* 足够 */

以及

if (p >= allocbuf && p < allocbuf + ALLOCSIZE)

显示了指针运算的一些重要方面。首先,指针在某些环境下可以进行比较。如果 p 和 q 都指向同一数组的元素,则关系操作符如 ==,!=,<,>= 等等,都能正常使用。例如若要

p < q

为真,则 p 指向的元素在 q 指向的元素之前。任何指针都能与零进行相等或不等的比较,这是有意义的。但如果在不指向相同数组的指针之间进行运算或比较,其行为是未定义的。(有一个例外,数组末尾之后的第一个元素可以用于指针运算)

第二,我们已经观察到,指针和整数可以相加或相减。如下结构

p + n

表示 p 当前所指地址之后的第 n 个对象的地址。不管 p 指向何种类型的对象,这个说法总是正确的;n 会根据 p 指向的对象的大小进行放大,对象大小是由 p 的声明所决定的。例如,如果 int 占四个字节,则 int 会乘以四。【即如果p指向整数,则C编译器在计算 p + n 时会把n乘以4,例如 p=12345678 ,n=1,则 p+n = 12345678 + 1*4】

指针减法也是合法的:如果 p 和 q 指向同一数组内的元素,且 p<q,则 q-p+1 是 p 和 q 之间的元素个数(包含两端)。可用利用这个事实再写出另一个版本的 strlen:

/* strlen: 计算字符串s的长度 */
int strlen(char *s)
{char *p = s;while(*p != '\0')p++;return p - s;
}

在声明中,p 被初始化为 s,即字符串的首个字符。在 while 循环中,挨个检查每个字符,直到发现末尾的 '\0'。由于 p 指向的是字符,p++ 每次都将 p 移到下一个字符,而 p - s 表示移动过的字符数,即字符串的长度。(字符串中的字符数量可能太大,int 保存不下。头文件<stddef.h> 定义了一个类型 ptrdiff_t,它足够大,可以用来保存两个指针之间的有符号差值。然而,如果我们更仔细的话,会使用 size_t 来做 strlen 的返回值,以与标准库的版本相匹配。size_t 是 sizeof 操作符返回的有符号整型。)

指针运算是一致的:如果我们要处理比 char 占内存更多的 float,而 p 是指向 float 的指针,则 p++ 会指向下一个 float。这样,仅仅需要把 alloc 和 afree 中的所有 char 替换成 float,我们就能写出 alloc 的 float 版本 。所有的指针操作都会自动地考虑到所指向对象的大小。

合法的指针操作有:将指针赋给相同类型,一个指针与一个整数的加减,指向相同数组的两个指针的减法或比较,以及与零的赋值和比较。其他所有指针运算都是非法的。对两个指针相加是非法的,非法的还有相乘或相除,移位或者掩码,以及将指针与 float 或 double 相加,甚至,在没有强制类型转换的情况下,将一个类型的指针赋给另一个类型的指针。最后一种情况对 void * 是特例,它是可以不用强制类型转换。

相关文章:

C语言KR圣经笔记 5.3指针和数组 5.4地址运算

5.3 指针和数组 在 C 语言中&#xff0c;指针和数组有着非常强的关联&#xff0c;强到应当把两者同时拿出来讨论。任何可以通过数组下标来做到的操作&#xff0c;也都能用指针来做到。而指针的版本通常会更快&#xff0c;但至少对初学者来说会更难理解。 如下声明 int a[10]…...

设计模式:简单工厂模式、工厂方法模式、抽象工厂模式

简单工厂模式、工厂方法模式、抽象工厂模式 1. 为什么需要工厂模式&#xff1f;2. 简单工厂模式2.1. 定义2.2. 代码实现2.3. 优点2.4. 缺点2.5. 适用场景 3. 工厂方法模式3.1. 有了简单工厂模式为什么还需要有工厂方法模式&#xff1f;3.2. 定义3.3. 代码实现3.4. 主要优点3.5.…...

Could not load library libcudnn_cnn_infer.so.8

报错&#xff1a; Could not load library libcudnn_cnn_infer.so.8. Error: /root/miniconda3/lib/python3.10/site-packages/torch/lib/libcudnn_cnn_infer.so.8: undefined symbol: _ZNK10cask_cudnn14BaseKernelInfo18minorCCVCompatibleENS_8SafeEnumINS_47ComputeCapa…...

ELement UI时间控件el-date-picker误差8小时解决办法

一、问题描述&#xff1a; 在项目中引用了elementui中的date-picker组件&#xff0c;选中的时间跟实际相差八小时&#xff0c;且格式不是自己想要的格式 <el-date-pickertype"date"placeholder"选择日期"format"yyyy/M/d"v-model"form…...

Linux日志论转

系统日志、审计日志、诊断日志 日志系统rsyslog 日志管理基础: rsyslog 日志管理 logrotate日志轮转常见的日志文件 #tail -f /var/log/messages #动态查看日志文件的尾部&#xff0c;系统主日志文件#tail -f /var/log/secure #记录认证、安全的日志…...

第7课 利用FFmpeg将摄像头画面与麦克风数据合成后推送到rtmp服务器

上节课我们已经拿到了摄像头数据和麦克风数据&#xff0c;这节课我们来看一下如何将二者合并起来推送到rtmp服务器。推送音视频合成流到rtmp服务器地址的流程如下&#xff1a; 1.创建输出流 //初始化输出流上下文 avformat_alloc_output_context2(&outFormatCtx, NULL, &…...

Microsoft Visual Studio 2022 install Project 下载慢

1. 关闭Internet 协议版本6 2. 如果没有效果&#xff0c;打开Internet 协议版本4&#xff0c;更改DNS 3. 在浏览器中下载后安装&#xff0c;下载地址如下&#xff1a; Microsoft Visual Studio Installer Projects 2022 - Visual Studio Marketplace 4. 安装时注意关闭vs&…...

uniapp---安卓真机调试提示检测不到手机【解决办法】

最近在做APP&#xff0c;由于华为手机更新过系统&#xff0c;再次用来调试APP发现就不行了。下面给出具体的解决方法&#xff1a; 第一步&#xff1a;打开【允许开发人员选项】 找到【设置】点击【关于手机】找到【版本号】点击7次或多次&#xff0c;允许开发人员选项。 第二…...

Nginx(十四) 配置文件详解 - 负载均衡(超详细)

本篇文章主要讲ngx_http_upstream_module模块下各指令的使用方法。 1. upstream 上游服务器组/集群 Syntax: upstream name { ... } Default: — Context: http upstream指令定义了一个上游服务器组/集群&#xff0c;便于反向代理中的proxy_pass使用。服务器可以监听…...

大数据应用安全策略包括什么

大数据应用安全策略是为了保障大数据应用中的数据安全而采取的一系列措施&#xff0c;其重要性不容小觑。以下是大数据应用安全策略所包含的主要内容&#xff1a; 一、数据加密与安全存储 数据加密&#xff1a;对于敏感数据&#xff0c;应采用加密技术进行保护&#xff0c;包括…...

Ubuntu软件和vmware下载

https://cn.ubuntu.com/download/desktop VMware 中国 - 交付面向企业的数字化基础 | CN...

如何修改Anaconda的Jupyter notebook的默认启动路径

1.打开Anaconda控制台 2.输入下面的命令 jupyter notebook --generate-config 这个命令的作用是生成 Jupyter notebook 的配置文件。如果你是第一次运行&#xff0c;会直接生成这个文件。如果曾经运行过这个命令&#xff0c;就会像下图一样问你时候要覆盖原来的文件。这个时候…...

密码学:带密钥的消息摘要算法一数字签名算法

文章目录 前言手写签名和数字签名前置知识点&#xff1a;消息摘要算法数字签名算法数字签名算法的由来数字签名算法在实际运用的过程附加&#xff1a;签名和摘要值的解释 数字签名算法的家谱数字签名算法的消息传递模型经典数字签名算法-RSA实现 数字签名标准算法-DSA实现 圆曲…...

JVM中部分主要垃圾回收器的特点、使用的算法以及适用场景

JVM中部分主要垃圾回收器的特点、使用的算法以及适用场景&#xff1a; Serial GC&#xff08;串行收集器&#xff09; 特点&#xff1a;单线程执行&#xff0c;对新生代进行垃圾回收时采用复制算法&#xff08;Copying&#xff09;&#xff0c;在老年代可能使用标记-压缩或标记…...

vue保姆级教程----深入了解Vuex的工作原理

&#x1f4e2; 鸿蒙专栏&#xff1a;想学鸿蒙的&#xff0c;冲 &#x1f4e2; C语言专栏&#xff1a;想学C语言的&#xff0c;冲 &#x1f4e2; VUE专栏&#xff1a;想学VUE的&#xff0c;冲这里 &#x1f4e2; CSS专栏&#xff1a;想学CSS的&#xff0c;冲这里 &#x1f4…...

(JAVA)-(网络编程)-InetAddress(ip)

InetAddress类就表示ip地址&#xff0c;他是一个接口&#xff0c;有两个实现类&#xff1a;Inet4Address和Inet6Address&#xff0c;分别表示IPv4和IPv6. 创建对象&#xff1a;此类没有对外提供构造方法&#xff0c;创建ip对象要使用InetAddress类的静态方法getByName()。 st…...

手把手带你死磕ORBSLAM3源代码(二十二)Tracking.cc PrintTimeStats介绍

目录 一.前言 二.代码 2.1完整代码 一.前言 这段代码是一个C++函数,名为Tracking::PrintTimeStats(),它属于Tracking类。这个函数的主要目的是计算和打印出各种跟踪和本地映射任务所花费的平均时间和标准差,并将这些信息同时输出到控制台和一个名为ExecMean.txt的文本文件…...

【操作系统xv6】学习记录3-Wsl2 Ubuntu18.04图形化界面

不知道为啥&#xff0c;wls2和windows用vscode ssh的方式连接识别&#xff0c; 既然如此&#xff0c;那就装一个桌面版的&#xff0c;其实2年多前装过一次&#xff0c;后来pc机跑深度学习的任务&#xff0c;硬盘坏了~ 开干前再试一次 ref&#xff1a;https://zhuanlan.zhihu.…...

CCNP课程实验-03-Route_Path_Control_CFG

目录 实验条件网络拓朴需求 基础配置需求实现1.A---F所有区用Loopback模拟&#xff0c;地址格式为&#xff1a;XX.XX.XX.XX/32&#xff0c;其中X为路由器编号。根据拓扑宣告进对应协议。A1和A2区为特例&#xff0c;A1&#xff1a;55.55.55.0/24&#xff0c;A2&#xff1a;55.55…...

STM32 学习(二)GPIO

目录 一、GPIO 简介 1.1 GPIO 基本结构 1.2 GPIO 位结构 1.3 GPIO 工作模式 二、GPIO 输出 三、GPIO 输入 1.1 传感器模块 1.2 开关 一、GPIO 简介 GPIO&#xff08;General Purpose Input Output&#xff09;即通用输入输出口。 1.1 GPIO 基本结构 如下图&#xff0…...

EdgeRemover:终极指南 - 如何高效彻底移除Windows Edge浏览器

EdgeRemover&#xff1a;终极指南 - 如何高效彻底移除Windows Edge浏览器 【免费下载链接】EdgeRemover PowerShell script to remove Microsoft Edge in a non-forceful manner. 项目地址: https://gitcode.com/gh_mirrors/ed/EdgeRemover EdgeRemover是一个专业的Powe…...

LCDGraph:基于字符屏CGRAM的嵌入式轻量级实时绘图库

1. 项目概述LCDGraph 是一款专为嵌入式系统设计的轻量级图形绘制库&#xff0c;面向资源受限的微控制器平台&#xff08;如 Arduino 系列&#xff09;&#xff0c;核心目标是在标准字符型 LCD 显示屏上实现高效、低开销的实时线性数据可视化。它不依赖图形点阵驱动或外部显存&a…...

Linux用户管理全攻略:从创建到权限配置

1. Linux用户管理基础入门 刚接触Linux系统的朋友&#xff0c;经常会遇到这样的困惑&#xff1a;为什么有些命令普通用户不能执行&#xff1f;为什么新建的用户连基本的命令补全都没有&#xff1f;其实这些都是用户管理的问题。作为一个用了10年Linux的老鸟&#xff0c;今天我就…...

Arduino ESP平台MQTT固件空中升级(FUOTA)轻量库

1. 项目概述mqtt_fuota_duino是一个面向资源受限嵌入式物联网终端的轻量级固件空中升级&#xff08;Firmware Update Over-The-Air, FUOTA&#xff09;库&#xff0c;专为 Arduino 生态设计&#xff0c;深度适配 ESP8266 和 ESP32 平台。其核心使命并非替代标准 HTTP/HTTPS OTA…...

无线局域网安全(四)————CCMP加密实战与性能优化

1. CCMP加密的核心原理与AES算法特性 CCMP加密协议作为无线局域网安全的黄金标准&#xff0c;本质上是一套基于AES算法的"安全组合拳"。我常把它比作银行金库的三重门禁系统&#xff1a;第一道门用CTR模式确保数据保密性&#xff0c;第二道门通过CBC-MAC实现完整性校…...

团队知识协作平台:构建高效智能的文档管理系统

团队知识协作平台&#xff1a;构建高效智能的文档管理系统 【免费下载链接】outline Outline 是一个基于 React 和 Node.js 打造的快速、协作式团队知识库。它可以让团队方便地存储和管理知识信息。你可以直接使用其托管版本&#xff0c;也可以自己运行或参与开发。源项目地址&…...

AHT20温湿度传感器在STM32上的应用:从数据采集到OLED显示

AHT20温湿度传感器在STM32上的实战应用&#xff1a;从数据采集到OLED可视化 在物联网和智能硬件开发中&#xff0c;环境数据的实时监测与可视化是基础却关键的一环。AHT20作为新一代数字温湿度传感器&#xff0c;以其高精度、低功耗和I2C接口的便捷性&#xff0c;成为STM32开发…...

vLLM-v0.17.1一文详解:vLLM与MLC-LLM推理框架技术路线对比

vLLM-v0.17.1一文详解&#xff1a;vLLM与MLC-LLM推理框架技术路线对比 1. vLLM框架简介 vLLM是一个专注于大语言模型(LLM)推理和服务的高性能开源库。最初由加州大学伯克利分校的天空计算实验室开发&#xff0c;现已发展成为学术界和工业界共同维护的社区项目。这个框架以其出…...

掌控散热:OmenSuperHub开源风扇控制与性能优化工具深度解析

掌控散热&#xff1a;OmenSuperHub开源风扇控制与性能优化工具深度解析 【免费下载链接】OmenSuperHub 项目地址: https://gitcode.com/gh_mirrors/om/OmenSuperHub OmenSuperHub是一款专为惠普暗影精灵系列游戏本打造的开源控制软件&#xff0c;提供完全离线的硬件监控…...

从PERCLOS到‘三庭五眼’:聊聊疲劳检测算法里那些有趣的工程实现细节

从PERCLOS到‘三庭五眼’&#xff1a;疲劳检测算法的工程实现艺术 当算法工程师第一次看到"三庭五眼"这个美术概念被写入代码注释时&#xff0c;大概都会会心一笑——这正是工程实践中那些有趣的跨界融合时刻。疲劳检测系统看似是标准的计算机视觉任务&#xff0c;但…...