C语言KR圣经笔记 6.6 表查询 6.7 typedef

6.6 表查询

为了说明结构体的更多方面,本节我们来写一个表查询功能包的内部代码。在宏处理器或编译器的符号表管理例程中,这个代码是很典型的。例如,考虑 #define 语句,当遇到如下行

#define IN 1

时,名称 IN 与其对应的替换文本 1 都要存到一张表中。然后,当名称 IN 出现在如下语句中时,

state = IN;

它必须被替换成 1。

有两个例程用来操纵名称及其替换文本。install(s, t) 将名称 s 和 替换文本 t 记录到表格中,s 和 t 仅仅只是字符串。lookup(s) 在表格中搜索 s,并在找到时返回其指向的指针,若没找到则返回 NULL。

算法是哈希(hash)搜索——传入的名称被转换成一个小的非负整数,后续会用作指针数组的下标索引。一个数组元素指向一个链表的开头,该链表中所有元素名称的哈希值都等于该数组元素的下标值。如果没有一个名称的哈希值等于某个下标值,则该下标对应的数组元素值为NULL。

链表中的块(元素)是一个结构体,包含了:名称的指针,替换文本的指针,链表中下一个块的指针。若下一个指针为空,则标记链表结尾。

struct nlist {        /* 表项 */
    struct nlist * next;    /* 链表中的下一项 */
    char *name;             /* 定义的名称 */
    char *defn;             /* 替换文本 */
};

而指针数组就是

#define HASHSIZE 101
static struct nlist *hastab[HASHSIZE];    /* 指针表 */

lookup 和 install 都要使用的 hash 函数,会把字符串中每个字符值都加到对之前字符打散组合得到的值上,并返回对数组大小取模后得到的余数。这不是最好的hash函数,但是简短而高效。

/* hash:对字符串s生成哈希值 */
unsigned hash(char *s)
{
    unsigned hashval;

    for (hashval = 0; *s != '\0'; s++)
        hasval = *s + 31 * hashval;
    return hashval % HASHSIZE;
}

无符号算术运算保证哈希值为非负。

哈希过程在数组 hashtab 中生成了一个起始的下标索引;如果字符串能被找到,它会位于从此处开始的链表中。搜索是通过 lookup 来执行的。如果 lookup 发现该项已经存在,则返回指向它的指针,否则返回 NULL。

/* lookup:在hashtab中搜索s */
struct nlist *lookup(char *s)
{
    struct nlist *np;

    for (np = hashtab[hash(s)]; np != NULL; np = np->next)
        if (strcmp(s, np->name) == 0)
            return np;  /* 找到 */
    return NULL;        /* 没找到 */
}

lookup 中的 for 循环是遍历链表的标准用法:

for (ptr = head; ptr != NULL; ptr = ptr->next)
    ...

install 使用 lookup 来确定要加入的名称是否已经存在;如果是,则新定义会替换老的。否则,创建一个新的项。如果由于某种原因没有足够的空间来保存新的项,则 install 返回 NULL。

struct nlist *lookup(char *);
char *strdup(char *);

/* install:将(name, defn)存入 hashtab 中*/
struct nlist *install(char *name, char *defn)
{
    struct nlist *np;
    unsigned hashval;

    if ((np = lookup(name)) == NULL) {    /* 没找到 */
        np = (struct nlist *)malloc(sizeof(struct nlist));
        if (np == NULL || (np->name = strdup(name)) == NULL)
            return NULL;
        hashval = hash(name);
        np->next = hashtab[hashval];
        hashtab[hasval] = np;
    } else     /* 已经存在 */
        free((void *) np->defn);    /* 释放之前的defn */
    if ((np->defn = strdup(defn)) == NULL)
        return null;
    return np;
}

练习6-5、写个程序 undef, 从 lookup 和 install 维护的表中删除一个名称和定义。

练习6-6、基于本节的例程,写出一个适用于 C 程序的简单版本(即不带参数的)的 #define 预处理器。你会发现 getch 和 ungetch 也有用。

6.7 typedef

C语言提供了一种叫做 typdef 的机制,来创建新的数据类型名称。例如,声明

typedef int Length;

使 Length 这个名称成为 int 的同义词。类型 Length 可以用在声明,强制类型转换等等地方,与 int 类型可以使用的地方完全相同:

Length len, maxlen;
Length *lengths[];

类似地,如下声明

typedef char *String;

使 String 成为 char * 或字符指针的同义词,也能用于声明和强制类型转换:

String p, lineptr[MAXLINES], alloc(int);
int strcmp(String, String);
p = (String)malloc(100);

注意,在 typedef 中声明的类型出现在变量名的位置,而不是紧跟在 typedef 单词后面。在语法上,typedef 类似存储类型关键字如 extern, static 等。我们把 typedef 定义的类型名首字母大写,使其更醒目。

举个更复杂的例子,我们对本章前面例子中的树节点使用 typedef:

typedef struct tnode *Treeptr;

typedef struct tnode {             /* 树节点: */
    char *word;            /* 指向文本 */
    int count;             /* 出现次数 */
    struct tnode *left;    /* 左子节点 */
    struct tnode *right;   /* 右子节点 */
} Treenode;

这就创建了两个新的类型关键字: Treenode(结构体)和 Treeptr(结构体指针)。因此 talloc 例程可以写成:

Treeptr talloc(void)
{
    return (Treeptr) malloc(sizeof(Treenode));
}

必须强调,typedef 声明绝对没有创建新的类型;它仅仅是给某些已经存在的类型加了新的名字。typedef 也没有引入新的语义:用这种方式声明的变量,与不用它而显式写出的变量,两者属性完全相同。实际上, typedef 就像是 #define,区别在于它是由编译器解析的,因此能处理超出预处理器能力的文本替换。例如

typedef int (*PFI)(char *, char *);

创建了 PFI 类型,代表“返回 int 类型的函数的指针(有两个 char * 参数)”,它能用在对应的上下文,例如第五章的排序程序中:

PFI strcmp, numcmp;

除了纯粹的美学问题外,使用 typedef 还有两个主要原因。首先是将程序参数化以应对可移植性问题。如果把 typedef 用于可能依赖机器的数据类型,那么当程序需要移植时,就只有 typedef 需要修改。一种常见的情况是用 typedef 来命名各种不同的整数数量,然后为每种主机选择一套合适的 short、int 和 long。标准库中的 size_t 和 ptrdiff_t 就是这样的例子。

使用 typedef 的第二个目的是为程序提供更好的说明——名为 Treeptr 的类型,会比仅声明为指向复杂结构体类型的指针更好理解。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/359579.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

n-皇后-dfs

import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.IOException; import java.io.OutputStreamWriter; import java.util.Scanner;public class Main {static int n,N 20; //这里只会用到2 * n - 1的格子,开大点保险static char[][] g new c…

Makefile编译原理 makefile中的include关键字

一.makefile中的include关键字 类似C语言中的include 将其他文件的内容原封不动的搬入当前文件 make对include关键字的处理方式: 在当前目录搜索或指定目录搜索目标文件 搜索成功:将文件内容搬入当前makefile中 搜索失败:产生警告&…

聚观早报 | 360 AI搜索App上线;岚图汽车与京东达成合作

聚观早报每日整理最值得关注的行业重点事件,帮助大家及时了解最新行业动态,每日读报,就读聚观365资讯简报。 整理丨Cutie 1月30日消息 360 AI搜索App上线 岚图汽车与京东达成合作 三星电子在硅谷新设实验室 小米平板7系列参数曝光 Spa…

大创项目推荐 题目:基于深度学习的中文对话问答机器人

文章目录 0 简介1 项目架构2 项目的主要过程2.1 数据清洗、预处理2.2 分桶2.3 训练 3 项目的整体结构4 重要的API4.1 LSTM cells部分:4.2 损失函数:4.3 搭建seq2seq框架:4.4 测试部分:4.5 评价NLP测试效果:4.6 梯度截断…

代码随想录算法刷题训练营day20

代码随想录算法刷题训练营day20:LeetCode(654)最大二叉树、LeetCode(617)合并二叉树、LeetCode(700)二叉搜索树中的搜索、LeetCode(700)二叉搜索树中的搜索、LeetCode(98)验证二叉搜索 LeetCode(654)最大二叉树 题目 代码 import java.util.Arrays;/*** Definit…

MATLAB有限元应用-四边形八节点梁受力弯曲

MATLAB在处理平面有限元问题和梁弯曲问题上有很强的能力,主要体现在以下几个方面: 建模与网格划分 MATLAB内置了方便的图形界面工具(pdetoolbox等),可以快速对几何模型进行二维三维网格划分,生成有限元分析需要的网格。 求解器 MATLAB内置了多种求解偏微分方程的有限元求解器…

大模型重塑车载语音交互:赛道巨头如何引领新周期?

车载语音交互赛道正进入新一轮竞争周期。 高工智能汽车注意到,传统车载语音交互赛道当前基本已进入成熟期,主要为任务型助手,包括从单轮对话到多轮对话,单音区到多音区,从单一的导航、多媒体娱乐等座舱功能扩展智能驾…

钢材表面缺陷YOLOV8,OPENCV调用

【免费】钢材表面缺陷YOLOV8资源-CSDN文库 钢材表面缺陷YOLOV8NANO,训练得到PT模型,然后转换成ONNX,OPENCV的DNN调用,支持C,PYTHON,ANDROID

VScode中使用Xdebug调试PHP

君衍. 一、下载VScode与PHPstudy二、配置PHP环境变量三、PHPstudy中启用xdebug扩展四、打开php.ini,修改配置五、修改vscode配置六、VScode安装相关插件七、配置launch.json八、设置断点,开始调试 一、下载VScode与PHPstudy 首先我们自然是需要搭建环境…

C++ 数论相关题目 博弈论 Nim游戏

给定 n 堆石子,两位玩家轮流操作,每次操作可以从任意一堆石子中拿走任意数量的石子(可以拿完,但不能不拿),最后无法进行操作的人视为失败。 问如果两人都采用最优策略,先手是否必胜。 输入格式…

《统计学习方法:李航》笔记 从原理到实现(基于python)-- 第5章 决策树(代码python实践)

文章目录 第5章 决策树—python 实践书上题目5.1利用ID3算法生成决策树,例5.3scikit-learn实例 《统计学习方法:李航》笔记 从原理到实现(基于python)-- 第5章 决策树 第5章 决策树—python 实践 import numpy as np import pand…

Docusaurus 文档侧边栏增加 New 标识

在使用 Docusaurus 搭建文档站点的时候,我们经常要给某个侧边栏菜单增加一些醒目的标识,比如针对新创建的文档给它一个 New 的标识, 以提醒过来看文档的用户这是一个新增加项或者新特性(阅读的时候不要遗漏)。 然而这个…

C#: form 添加窗体最小化事件,添加系统托盘图标,点击后可以打开、最小软件窗口

说明: 1.实现窗体在最小化后触发一个事件,可以去实现需要的功能。 2.最小化后软件图标出现在系统右下角的托盘串口。 3.点击托盘口的图标可以实现软件弹出窗口和最小化的切换。 1.参考办法 以下是判断C#窗体最小化到状态栏的状态的方法:…

[AI]文心一言爆火的同时,ChatGPT带来了这么多的开源项目你了解吗

前言 前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家:https://www.captainbed.cn/z ChatGPT体验地址 文章目录 前言4.5key价格泄漏ChatGPT4.0使用地址ChatGPT正确打开方式最新功能语音助手存档…

Redis核心技术与实战【学习笔记】 - 7.Redis GEO类型 - 面向 LBS 应用的数据类型

前言 前面,介绍了 Redis 的 5 大基本数据类型:String、List、Hash、Set、Sorted Set,它们可以满足绝大多数的数据存储需求,但是在面对海里数据统计时,它们的内存开销很大。所以对于一些特殊的场景,它们是无…

计算机网络-物理层设备(中继器 集线器)

文章目录 中继器中继器的功能再生数字信号和再生模拟信号同一个协议 集线器(多口中继器)不具备定向传输的原因集线器是共享式设备的原因集线器的所有接口都处于同一个碰撞域(冲突域)内的原因 小结 中继器 中继器的功能 中继器的…

JVM 内存模型

1 什么是 JVM 内存模型 JVM 需要使用计算机的内存,Java 程序运行中所处理的对象或者算法都会使用 JVM 的内 存空间,JVM 将内存区划分为 5 块,这样的结构称之为 JVM 内存模型。 2 JVM 为什么进行内存区域划分 随着对象数量的增加&#xff…

基于二值化图像转GCode的单向扫描实现

基于二值化图像转GCode的单向扫描实现 什么是单向扫描单向扫描代码示例 基于二值化图像转GCode的单向扫描实现 什么是单向扫描 在激光雕刻中,单向扫描(Unidirectional Scanning)是一种雕刻技术,其中激光头只在一个方向上移动&a…

【Vue】二、Vue 组件展示控制的优雅解决方案

vue项目中展示的组件,我平常都是通过v-show进行展示控制,类似这样 通常情况下,一个正常展示组件的流程,是通过前端用户点击触发函数,在函数中对data数据进行操作,从而展示不同的页面 showWork: false, sho…

如何用Docker+jenkins 运行 python 自动化?

1.在 Linux 服务器安装 docker 2.创建 jenkins 容器 3.根据自动化项目依赖包构建 python 镜像(构建自动化 python 环境) 4.运行新的 python 容器,执行 jenkins 从仓库中拉下来的自动化项目 5.执行完成之后删除容器 前言 环境准备 Linux 服务器一台(我的是 CentOS7)…