C语言KR圣经笔记 5.1指针和地址 5.2指针和函数参数

2023-12-28 20:04

本文主要是介绍C语言KR圣经笔记 5.1指针和地址 5.2指针和函数参数,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

第五章 指针和数组

指针是包含变量地址的变量。在 C 语言中,指针被大量使用,部分原因是有时只能用指针来表达某种计算,而部分原因是相比其他方式,指针通常能带来更紧凑和高效的代码。指针和数组是紧密关联的;本章也讲探讨它们的关系,并演示如何利用这个关系。

指针曾经和 goto 语句一起,被归结为用于创建“让人不可能理解”的程序的绝妙方式。如果粗心大意地使用指针,这个说法当然是对的,而且很容易创建指向不可预料位置的指针。然而,通过训练和规范约束,指针可以用来得到清晰性和简洁性。这正是我们在本书中尽量展示的方面。

ANSI C 的主要改动是把如何操纵指针的规则给明确了,实际上就是把优秀程序员已有的实践,和优秀编译器已加强的地方,做成了强制要求。另外,void * 类型(指向 void 的指针)取代了 char * ,作为通用指针的正确类型。

5.1 指针和地址

我们先从内存组织的简化图开始。一台典型的机器有一组连续编号或者叫编址的内存单元,这些内存单元可以被各自独立操作,或者是按连续分组进行操作。通常的情况是任一个字节可以是一个 char, 一对单字节的内存单元可以被当作是 short 整型,而四个连续的字节组成一个 long。指针是一组(通常是2个或4个)可以保存地址的单元。这样,如果 c 是一个char 而 p 是指向它的指针,则我们可以用下图来表示这种情况:

一元操作符 & 给出一个对象的地址,因此如下语句

p = &c;

把 c 的地址赋给 p,而 p 被称作“指向” c。操作符 & 只能用于内存中的对象:变量和数组元素。它不能用于表达式、常量 或者寄存器(register)变量。

一元操作符 * 是 间接 或者叫 解引用 操作符;当用于指针时,它访问指针指向的对象。假定 x 和 y 是整数而 ip 是指向 int 的指针。下面这段人为构造(但没有实际用途)的代码,演示了如何声明指针,以及如何使用 & 和 *:

int x = 1, y = 2, z[10];
int *ip;        /* ip是指向int的指针 */ip = &x;        /* ip现在指向x */
y = *p;         /* y现在是1 */
*p = 0;         /* x现在是0 */
ip = &z[0];     /* ip现在指向z[0] */

x,y,z的声明不需要多说了。指针 ip 的声明为

int *ip;

这种形式旨在助记;它表示表达式 *ip 是一个 int。变量声明的语法,模仿了变量可以在其中出现的表达式的语法。这个推理也适用于函数声明。例如

double *p, atof(char *);

表示,在一个表达式中,*p 和 atof(s) 都有着 double 类型的值,而 atof 的参数是 char 的指针。

你还应当注意到有个隐含的约束:指针被限制指向一种特定的对象类型。(有一个例外,“指向 void 的指针”被用来保存任意类型的对象,但不能对它自身解引用。我们会在5.11节说明)

如果 ip 指向整数 x,则 *ip 可以出现在任何 x 可以出现的上下文中,因此

*ip = *ip + 10;

将 *ip 增加10。

一元操作符 & 和 * 比算术操作符的结合更为紧密,因此赋值表达式

y = *ip + 1

首先取出 ip 所指向的对象,将其加1,并把结果赋给 y。而

*ip += 1

会把 ip 指向的对象递增,正如

++*ip

(*ip)++

最后一个例子中的括号是必须的;如果没有括号,则表达式会对 ip 递增,而不会对它指向的内容递增,因为一元操作符 * 和 ++ 是从右至左结合。

最后,由于指针是变量,那么不解引用也可以使用它们。例如,如果 iq 是另一个 int 的指针,

iq = ip

将 ip 的内容拷贝给 iq,这样就使 iq 也指向 ip 所指的对象了。

5.2 指针和函数参数

由于C将参数传给函数时是值传递,因此没有直接的方法可以使被调函数修改调用者函数中的变量。例如,一个排序例程可能会用一个 swap 函数来交换两个无序的元素。如果写

swap(a, b);

其中 swap 函数的定义为

void swap(int x, int y)    /* 错误 */
{int temp;temp = x;x = y;y = temp;
}

但这样是不行的。因为所有调用都是值传递,swap 不能影响调用它的例程中的参数 a 和 b。上面的函数只是交换了 a 和 b 的拷贝

要得到想要的效果,方法是让调用程序传递要修改的值的指针

swap(&a, &b);

由于操作符 & 得到了变量的地址,&a 是 指向 a 的指针。在 swap 之中,参数被声明为指针,而通过它们来间接访问操作数。

void swap(int *px, int *py)    /* 交换 *px和 *py */
{int temp;temp = *px;*px = *py;*py = temp;
}

用图来表示就是:

指针参数使一个函数能够访问并改变调用它的函数中的对象。举个例子,考虑实现一个函数 getint ,该函数执行自由格式的输入转换:将一个字符流拆分成多个整数值,每次调用得到一个整数。getint 必须返回它所找到的值,也必须在没有更多输入时指示文件结束。这两个值必须通过不同的路径返回,因为不管EOF使用什么值,都有可能是一个输入整数的值。

一种解决方案是让 getint 把文件结束状态作为函数返回值,而同时使用一个指针参数来保存转换后的整数,传递回给它的调用者函数。这也是 scanf 使用的模式,详见 7.4节。

下面的循环通过调用 getint 来填充整型数组:

int n, array[SIZE], getint(int *);for (n = 0; n < SIZE && getint(&array[n]) != EOF; n++);

每次调用都将 array[n] 设置为从输入中找到的下一个整数,并对 n 递增。注意,必须将 array[n] 的地址传给 getint 。否则,getint 没有办法将转换后的整数传给调用者。

我们这个版本的 getint 在文件结尾时返回 EOF,在下一个输入不是数字时返回0,而在输入包含合法的数字时返回一个正值:

#include <ctype.h>int getch(void);
void ungetch(int);/* getint: 将下一个整数从输入存入 *pn */
int getint(int *pn)
{int c, sign;while (isspace(c = getch()))    /* 跳过空白字符 */;if (!isdigit(c) && c != EOF && c != '+' && c != '-') {ungetch(c);        /* 非数字 */return 0;}sign = (c == '-') ? -1 : 1;if (c == '+' || c =='-')c = getch();for (*pn = 0; isdigit(c); c = getch())*pn = 10 * *pn + (c - '0');*pn *= sign;if (c != EOF)ungetch(c);return c;
}

在 getint 内, *pn 自始至终 都被当作普通的 int 变量来使用。我们还使用了 getch 和 ungetch(见4.3节),因此多读但又不得不读的一个字符,就能被推回给输入。

练习5-1、在我们的getint版本中,后面没有跟数字的单独的 + 和 - 号,也被当作是合法的数字,其值为 0。修改这个问题,将这种字符推回给输入。

练习5-2、模仿 getint,写一个浮点数版本的 getfloat 函数。 getfloat 用什么类型作为其返回值? 

这篇关于C语言KR圣经笔记 5.1指针和地址 5.2指针和函数参数的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



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

相关文章

C语言中位操作的实际应用举例

《C语言中位操作的实际应用举例》:本文主要介绍C语言中位操作的实际应用,总结了位操作的使用场景,并指出了需要注意的问题,如可读性、平台依赖性和溢出风险,文中通过代码介绍的非常详细,需要的朋友可以参... 目录1. 嵌入式系统与硬件寄存器操作2. 网络协议解析3. 图像处理与颜色编码4. 高效处理布尔标志集合

Go语言开发实现查询IP信息的MCP服务器

《Go语言开发实现查询IP信息的MCP服务器》随着MCP的快速普及和广泛应用,MCP服务器也层出不穷,本文将详细介绍如何在Go语言中使用go-mcp库来开发一个查询IP信息的MCP... 目录前言mcp-ip-geo 服务器目录结构说明查询 IP 信息功能实现工具实现工具管理查询单个 IP 信息工具的实现服

C 语言中enum枚举的定义和使用小结

《C语言中enum枚举的定义和使用小结》在C语言里,enum(枚举)是一种用户自定义的数据类型,它能够让你创建一组具名的整数常量,下面我会从定义、使用、特性等方面详细介绍enum,感兴趣的朋友一起看... 目录1、引言2、基本定义3、定义枚举变量4、自定义枚举常量的值5、枚举与switch语句结合使用6、枚

go 指针接收者和值接收者的区别小结

《go指针接收者和值接收者的区别小结》在Go语言中,值接收者和指针接收者是方法定义中的两种接收者类型,本文主要介绍了go指针接收者和值接收者的区别小结,文中通过示例代码介绍的非常详细,需要的朋友们下... 目录go 指针接收者和值接收者的区别易错点辨析go 指针接收者和值接收者的区别指针接收者和值接收者的

Python中使用正则表达式精准匹配IP地址的案例

《Python中使用正则表达式精准匹配IP地址的案例》Python的正则表达式(re模块)是完成这个任务的利器,但你知道怎么写才能准确匹配各种合法的IP地址吗,今天我们就来详细探讨这个问题,感兴趣的朋... 目录为什么需要IP正则表达式?IP地址的基本结构基础正则表达式写法精确匹配0-255的数字验证IP地

Go 语言中的select语句详解及工作原理

《Go语言中的select语句详解及工作原理》在Go语言中,select语句是用于处理多个通道(channel)操作的一种控制结构,它类似于switch语句,本文给大家介绍Go语言中的select语... 目录Go 语言中的 select 是做什么的基本功能语法工作原理示例示例 1:监听多个通道示例 2:带

利用Python快速搭建Markdown笔记发布系统

《利用Python快速搭建Markdown笔记发布系统》这篇文章主要为大家详细介绍了使用Python生态的成熟工具,在30分钟内搭建一个支持Markdown渲染、分类标签、全文搜索的私有化知识发布系统... 目录引言:为什么要自建知识博客一、技术选型:极简主义开发栈二、系统架构设计三、核心代码实现(分步解析

Java Optional避免空指针异常的实现

《JavaOptional避免空指针异常的实现》空指针异常一直是困扰开发者的常见问题之一,本文主要介绍了JavaOptional避免空指针异常的实现,帮助开发者编写更健壮、可读性更高的代码,减少因... 目录一、Optional 概述二、Optional 的创建三、Optional 的常用方法四、Optio

C语言函数递归实际应用举例详解

《C语言函数递归实际应用举例详解》程序调用自身的编程技巧称为递归,递归做为一种算法在程序设计语言中广泛应用,:本文主要介绍C语言函数递归实际应用举例的相关资料,文中通过代码介绍的非常详细,需要的朋... 目录前言一、递归的概念与思想二、递归的限制条件 三、递归的实际应用举例(一)求 n 的阶乘(二)顺序打印

C语言中的数据类型强制转换

《C语言中的数据类型强制转换》:本文主要介绍C语言中的数据类型强制转换方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录C语言数据类型强制转换自动转换强制转换类型总结C语言数据类型强制转换强制类型转换:是通过类型转换运算来实现的,主要的数据类型转换分为自动转换