C程序设计语言之第6章 结构

本文详细介绍了C语言中的结构体,包括结构体的基本知识、结构体与函数的交互、结构体数组、指向结构的指针的使用、自引用结构实现二叉树、表查找中的哈希表应用以及类型定义typedef和联合的概念。文中还讨论了结构体的内存对齐和空穴现象,以及结构体长度与成员长度的关系。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1、结构的基本知识
关键字struct引入结构声明,结构声明由包含在花括号内的一系列声明组成,关键字struct后面的名字是可选的,成为结构标记。结构成员、结构标记和普通变量(即非成员)可以采用相同的名字。

2、结构与函数
结构的合法操作只有几种:作为一个整体复制和赋值,通过&运算符取地址,访问其成员。
在所有运算符中,下面4个运算符的优先级最高(C++中作用域解析运算符优先级最高,其次是这4个):结构运算符“.”和“->”、用于函数调用的“()”以及用于下标的“[]”。这里举一个例子:假设p是结构体类型的指针变量,str是其中的指针类型的成员,那么*p+±>str先读取指针str指向的对象的值,然后再将p加1。首先结合的是->,其次是++,最后是*。但++在这是后缀,所以是先执行,再加1。

3、结构数组
这里先提一下本节中介绍的sizeof。
文中说的是“编译时(compile-time)一元运算符”,可以用来计算任一对象的长度。看了一下网上的说明,sizeof既是一个运算符,也是一个关键字,或者说是一个非常特殊的,能作为运算符的关键字。严格地说,sizeof的返回值是无符号整型值,其类型为size_t(可以参考下https://www.zhihu.com/question/24773728),该类型在头文件<stddef.h>中定义。
文中有句话:“条件编译语句#if中不能使用sizeof,因为预处理器不对类型名进行分析,但预处理器并不计算#define语句中的表达式,因此,在#define中使用sizeof是合法的。”我想它大概意思就是,预编译(预处理)在编译过程的早期进行, 此时尚未对类型名称进行分析,所以不能理解sizeof要处理的类型名,而define只是文本替换,所以无所谓?

这里针对结构数组举的程序例子是统计输入中各个C语言关键字出现的次数。
这里使用结构数组,数组中的每个结构元素包括一对变量。声明为

struct key{
	char *word;//用来存放关键字名
	int count;//用来存放相应关键字的出现次数
} keytab[NKEYS];

初始化的方式为

struct key{
	char *word;
	int count;
} keytab[] = {
	{"auto", 0},
	{"break", 0},
	{"case", 0},
	/* ... */
	{"while", 0}
};

这是比较精确的做法,如果初值是简单变量或字符串,并且其中的任何值都不为空,则内层的花括号可以省略。
通过这一节的这个程序再次对getchar这个函数的机制充满了疑问。?

4、指向结构的指针
这里再次用到了5.4节中允许指向相同数组中元素的两个指针进行减法运算,以及C保证数组末尾之后第一个元素的指针运算符可以正确运行这两个规则,来将上一节中的程序改成使用结构的指针来完成。
有一个需要注意的点:结构的长度可能不等于各成员长度的和。
网上一个说法是:

  1. C语言的编译过程是从上到下的。
  2. 结构体某成员的地址偏移量必须为该成员的整数倍,如果不是整数倍,则前面成员变量需要填充。
  3. 结构体最后一个成员所占字节的长度=max(所有的成员长度)。

在这里,某成员的地址偏移量,应该就是它前面的成员已经占据的长度。那么,如果第一个成员是char,第二个是int,如果第一个成员占据一个字节长度,那第二个int型成员的地址偏移量将会是1,显然不是int类型长度的整数倍,因此第一个成员需要填充相应的的字节。以此类推,后面的成员也要满足此条件。这就是书上提到的“空穴”(hole)。当然使用sizeof可以得到正确的对象长度。

5、自引用结构
给出了二叉树的实现,以统计输入中所有单词的出现次数。
首先是树的定义:

struct tnode { /* the tree node: */
	char *word; /* points to the text */
	int count; /* number of occurrences */
	struct tnode *left; /* left child */
	struct tnode *right; /* right child */
 };

然后是主函数部分:

#include <stdio.h>
#include <ctype.h>
#include <string.h>

#define MAXWORD 100
struct tnode *addtree(struct tnode *, char *);
void treeprint(struct tnode *);
int getword(char *, int);

/* word frequency count */
int main()
{
	struct tnode *root;
	char word[MAXWORD];
	root = NULL;
	while (getword(word, MAXWORD) != EOF)
		if (isalpha(word[0]))
			root = addtree(root, word);
	treeprint(root);
	return 0;
 }

函数定义:

#include <stdlib.h>

struct tnode *talloc(void);
char *strdup(char *);

/* addtree: add a node with w, at or below p */
struct treenode *addtree(struct tnode *p, char *w)
{
	int cond;
	if (p == NULL) { /* a new word has arrived */
 		p = talloc(); /* make a new node */
		p->word = strdup(w);
 		p->count = 1;
		p->left = p->right = NULL;
 	} 
	else if ((cond = strcmp(w, p->word)) == 0)
		p->count++; /* repeated word */
	else if (cond < 0) /* less than into left subtree */
		p->left = addtree(p->left, w);
	else /* greater than into right subtree */
		p->right = addtree(p->right, w);
 	return p;
}

/* talloc: make a tnode */
struct tnode *talloc(void)
{
	return (struct tnode *) malloc(sizeof(struct tnode));
}

/*strdup把通过其参数传入的字符串复制到某个安全的位置*/
char *strdup(char *s) /* make a duplicate of s */
{
	char *p;
	p = (char *) malloc(strlen(s)+1); /* +1 for '\0' */
	if (p != NULL)
		strcpy(p, s);
	return p;
}

/* treeprint: in-order print of tree p */
void treeprint(struct tnode *p)
{
	if (p != NULL) {
		treeprint(p->left);
		printf("%4d %s\n", p->count, p->word);
		treeprint(p->right);
	}
}

之前定义过的函数:

/* getword: get next word or character from input */
int getword(char *word, int lim)
{
  	int c, getch(void);
	void ungetch(int);
	char *w = word;
	while (isspace(c = getch()))
		;
	if (c != EOF)
		*w++ = c;
	if (!isalpha(c)) {
		*w = '\0';
		return c;
	}
	for ( ; --lim > 0; w++)
		if (!isalnum(*w = getch())) {
			ungetch(*w);
			break;
		}
	*w = '\0';
	return word[0];
}


 
 //第4章的部分
#define BUFSIZE 100
char buf[BUFSIZE]; /* buffer for ungetch */
int bufp = 0; /* next free position in buf */
int getch(void) /* get a (possibly pushed-back) character */
{
	return (bufp > 0) ? buf[--bufp] : getchar();
}
void ungetch(int c) /* push character back on input */
{
	if (bufp >= BUFSIZE)
		printf("ungetch: too many characters\n");
	else
		buf[bufp++] = c;
}

6、表查找
这里使用的散列,或者说哈希表。
文中用的应该是哈希链表,当出现哈希值冲突时,沿着相应哈希值的链表一直查下去。程序操作有点看不太懂啊。

7、类型定义(typedef)
typedef类似于#define语句,但前者是由编译器解释的,因此它的文本替换功能要超过预处理器的能力(PS:预处理就是简单的文本替换、宏展开、删除注释等工作,不涉及语法检查,而编译器要把高级语言翻译成机器语言,所以说能力更强?)。给出的例子是:

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

该语句定义了类型PFI是“一个指向函数的指针,该函数具有两个char *类型的参数,返回值类型为int”(可以回顾下5.11:指向函数的指针)。

8、联合
联合的目的:一个变量可以合法地保存多种数据类型中任何一种类型的对象。声明与访问成员的方式与结构相同。
实际上,联合就是一个结构,它的所有成员相对于基地址的偏移量都为0,此结构空间要达到足够容纳最“宽”的成员,也就是说大家共用一个存储空间,此空间大小为所有成员类型中最大的那个。联合只能用其第一个成员类型的值进行初始化,而且任何时候读取的类型必须是最近一次存入的类型。

9、位字段
这个,先不看了吧。。。

总结:
累啊。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值