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

相关文章

从基础到高级详解Go语言中错误处理的实践指南

《从基础到高级详解Go语言中错误处理的实践指南》Go语言采用了一种独特而明确的错误处理哲学,与其他主流编程语言形成鲜明对比,本文将为大家详细介绍Go语言中错误处理详细方法,希望对大家有所帮助... 目录1 Go 错误处理哲学与核心机制1.1 错误接口设计1.2 错误与异常的区别2 错误创建与检查2.1 基础

Go语言中json操作的实现

《Go语言中json操作的实现》本文主要介绍了Go语言中的json操作的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧... 目录 一、jsOChina编程N 与 Go 类型对应关系️ 二、基本操作:编码与解码 三、结构体标签(Struc

Rust 智能指针的使用详解

《Rust智能指针的使用详解》Rust智能指针是内存管理核心工具,本文就来详细的介绍一下Rust智能指针(Box、Rc、RefCell、Arc、Mutex、RwLock、Weak)的原理与使用场景,... 目录一、www.chinasem.cnRust 智能指针详解1、Box<T>:堆内存分配2、Rc<T>:

python语言中的常用容器(集合)示例详解

《python语言中的常用容器(集合)示例详解》Python集合是一种无序且不重复的数据容器,它可以存储任意类型的对象,包括数字、字符串、元组等,下面:本文主要介绍python语言中常用容器(集合... 目录1.核心内置容器1. 列表2. 元组3. 集合4. 冻结集合5. 字典2.collections模块

基于Go语言开发一个 IP 归属地查询接口工具

《基于Go语言开发一个IP归属地查询接口工具》在日常开发中,IP地址归属地查询是一个常见需求,本文将带大家使用Go语言快速开发一个IP归属地查询接口服务,有需要的小伙伴可以了解下... 目录功能目标技术栈项目结构核心代码(main.go)使用方法扩展功能总结在日常开发中,IP 地址归属地查询是一个常见需求:

GO语言短变量声明的实现示例

《GO语言短变量声明的实现示例》在Go语言中,短变量声明是一种简洁的变量声明方式,使用:=运算符,可以自动推断变量类型,下面就来具体介绍一下如何使用,感兴趣的可以了解一下... 目录基本语法功能特点与var的区别适用场景注意事项基本语法variableName := value功能特点1、自动类型推

GO语言中函数命名返回值的使用

《GO语言中函数命名返回值的使用》在Go语言中,函数可以为其返回值指定名称,这被称为命名返回值或命名返回参数,这种特性可以使代码更清晰,特别是在返回多个值时,感兴趣的可以了解一下... 目录基本语法函数命名返回特点代码示例命名特点基本语法func functionName(parameters) (nam

Go语言连接MySQL数据库执行基本的增删改查

《Go语言连接MySQL数据库执行基本的增删改查》在后端开发中,MySQL是最常用的关系型数据库之一,本文主要为大家详细介绍了如何使用Go连接MySQL数据库并执行基本的增删改查吧... 目录Go语言连接mysql数据库准备工作安装 MySQL 驱动代码实现运行结果注意事项Go语言执行基本的增删改查准备工作

Go语言使用Gin处理路由参数和查询参数

《Go语言使用Gin处理路由参数和查询参数》在WebAPI开发中,处理路由参数(PathParameter)和查询参数(QueryParameter)是非常常见的需求,下面我们就来看看Go语言... 目录一、路由参数 vs 查询参数二、Gin 获取路由参数和查询参数三、示例代码四、运行与测试1. 测试编程路

Linux查询服务器 IP 地址的命令详解

《Linux查询服务器IP地址的命令详解》在服务器管理和网络运维中,快速准确地获取服务器的IP地址是一项基本但至关重要的技能,下面我们来看看Linux中查询服务器IP的相关命令使用吧... 目录一、hostname 命令:简单高效的 IP 查询工具命令详解实际应用技巧注意事项二、ip 命令:新一代网络配置全