六、结构
1、基础知识
概念:结构是一个或多个变量的集合,这些变量的类型可能不同,为方便而将它们并为一组用一个单独的名字操控。类似:薪资表。
表达:
struct 结构标记的可选名字
{
结构声明(成员)
}
如:
struct point(该标记命名了该类型的结构,并在此后可做为声明中花括号部分的缩写使用。)
{
int x;
int y;
};
注意点
:
- ① 结构成员、标记和普通变量(即非成员变量)可以拥有相同的名字而不冲突,因为能够通过上下文进行区分。且相同的成员名字可以出现在不同的结构中。
一个 struct 声明定义了一个类型。在标志成员列表结束的右花括号后可以带有一列变量
struct {
… } x, y, z;
int x, y, z;
在语法上类比:两者都将 x、y 和 z 声明为所命名的类型,并且为它们预留了存储空间
- ②不带变量列表的结构声明并不预留存储空间
-它只是描述了结构的模版(即一个结构的形态,然而如果结构声明已被加上标记,该标记则可用于此后结构实例的定义。
sturct point pt;
定义了一个 struct point 类型的结构变量 pt。
一个结构可通过在其定义之后紧随的初始值列表来进行初始化。
每一初始值都是赋给结构成员的常量表达式:
- ③ 对某个特定结构的成员进行引用:
结构名称.成员
——结构成员运算符“.”连接结构名称和成员名称。
例子:
printf(“%d, %d”, pt.x, pt.y)
- ④ 结构指针
p->结构成员
`
例子:
struct point origin, *pp;
printf(“origin is (%d, %d)\n”, pp->x, pp->y);
- ⑤结构运算符 . 和 -> 与用于函数调用的 () 和用于下标的 [] 一起,处于运算符优先等级的顶端
struct {
int len;
char *str;
} *p;
++p->len / /递增的是隐含: len++(p->len)
(++p)->len / /在访问 len 之前先递增 p
(p++)->len/ / 在之后才递增 p。
*p->str / / 获取 str 指向的内容
*p->str / /获取 str 指向的内容
(*p->str)++ / /递增 str 指向的内容
*p++->str / /在访问 str指向的内容之后将 p 递增
- ⑥结构嵌套
struct point {
int x;
int y;
};
struct rect {
struct point pt1;
struct point pt2;
};
struct rect screen;
screen.pt1.x
引用了 screen 的成员 pt1 的 x 坐标。
2、 结构和函数
① 结构操作(合法):
- 以结构为单元进行复制或赋值
- 用 & 获取结构地址
- 访问结构成员
- 复制和赋值也包括向函数传递参数以及从函数返回值
- 初始化:
-用一个成员常值列表初始化结构;
-一个自动结构变量也可以通过赋值进行初始化。
注意:结构不能进行比较(不合法)
② 编写方法:
- 传递结构的组件
- 传递整个结构
- 传递结构的指针
例子:
一、参数名和同名的成员之间没有冲突
struct point makepoint(int x, int y)
{
struct point temp;
temp.x = x;
temp.y = y;
return temp;
}
二、其测试一个点是否在一个矩形之内
int ptinrect(struct point p, struct rect r)
{
return p.x >= r.pt1.x && p.x < r.pt2.x
&& p.y >= r.pt1.y && p.y < r.pt2.y;
}
3、结构数组 &结构指针
①、结构数组
struct key {
char *word;
int count;
} keytab[NKEYS];
or
struct key {
char *word;
int count;
};
struct key keytab[NKEYS];
赋予初始值:
struct key {
char *word;
int count;
} keytab[] = {
“auto”, 0,
“break”, 0,
“case”, 0,
“char”, 0,
“const”, 0,
“continue”, 0,
“default”, 0,
/* … */
“unsigned”, 0,
“void”, 0,
“volatile”, 0,
“while”, 0,
};
关键字数组的项数:
keytab 的大小 / 结构 key 的大小
关键字的数目就是数组的大小除以其中一个元素的大小
#define NKEYS (sizeof keytab / sizeof(struct key))
or
#define NKEYS (sizeof keytab / sizeof keytab[0])
②、结构指针
struct key *binsearch(char *, struct key *, int);
比较:采用查找方法:二分法
4、自引用结构
㈠、在结构中引用自己,
struct tnode {
/* 树的节点 */
char *word; /* 指向单词的文本 */
int count; /* 出现的次数 */
struct tnode *left; /* 左孩子 */
struct tnode *right; /* 右孩子 */
};
一个结构包含它自己的实例是非法的,但
struct tnode *left;
将 left 声明为 tnode 的指针
,而不是 tnode 本身。
自引用结构的一种变体:两个结构相互引用。其构建方式如下:
struct t {
…
struct s *p; /* p 指向一个 s */
};
struct s {
…
struct t *q; /* q 指向一个 t */
};
㈡、例子:
解决统计输入中所以单词出现次数,单词列表无法先知道,无法方便排序并使用二分法查找。
方法一:在每个单词到来时将其按序排放到正确的位置,从而始终将目前已出现的单词保持有序。不通过在一个线性数组中平移单词来完成。
坏处:耗时。
方法二:二叉树
解决:
- 这颗树对于每个不同的单词都有一个“节点”;每个节点包含
一个指向单词文本的指针
一个出现次数的计数
一个指向左孩子节点的指针
一个指向右孩子节点的指针
节点按这样的形式组织:任一节点的左子树仅包含那些(按字母序)小于该节点单词的单词,而右子树则仅包含比其大的单词。
如句子“now is the time for all good men to come to the aid of their party”通过插入每个遇到的单词而构建成的树:
为了确认一个新碰到的单词是否已经存在于这棵树中,需要自根开始,将此新单词与该节点的单词相比较。假如匹配,问题的答案是肯定的。如果该新单词较树中的单词小,则到节点的左孩子继续查找,否则到其右孩子处查找。如果要查找的方向没有孩子,则该新单词不在树中,这个空位就是加入该新单词的恰当位置。
此过程为递归过程,
struct tnode {
/* 树的节点 */
char *word; /* 指向单词的文本 */
int count; /* 出现的次数 */
struct tnode *left; /* 左孩子 */
struct tnode *right; /* 右孩子 */
};
#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);
/* 单词频度统计 */
main()
{
struct tnode *root;
char word[MAXWORD];
root = NULL;
while (getword(word, MAXWORD) != EOF) // getword 读取单词
if (isalpha(word[0]))
root = addtree(root, word); // addtree 将它们放置到树中
treeprint(root);
return 0;
}
struct tnode *talloc(void);
char *strdup(char *);
/* addtree:递归函数,将一个包含 w 的节点加到 p 或 p 之下的位置 */
struct tnode *addtree(struct tnode *p, char *w)
{
int cond;
if (p == NULL) {
/* 出现了新单词 */
p = talloc(); /* 创建一个新节点 */
p->word = strdup(w);
p->count = 1;
p->left = p->right = NULL;
} else if ((cond = strcmp(w, p->word)) == 0)
p->count++; /* 重复出现的单词 */
else if (cond < 0) /* 小于则进入左子树 */
p->left = addtree(p->left, w);
else /* 大于则进入右子树 */
p->right = addtree(p->right, w);
return p;
}
void treeprint(struct tnode *p)
{
if (p != NULL) {
treeprint(p->left);
printf(“%4d %s\n”, p->count, p->word);
treeprint(p->right);
}
}
/* talloc:构建一个 tnode */
struct tnode *talloc(void)
{
return (struct tnode *) malloc(sizeof(struct tnode));
}
//将其参数给出的字符串复制到一个安全的地方
char *strdup(char *s) /* 构建一个 s 的副本 */
{
char *p;
p = (char *) malloc(strlen(s)+1); /* +1 为了存放‘\0’ */
if (p != NULL)
strcpy(p, s);
return p;
}
补充:存储空间分配器相关问题。
在一个程序中只期望存在一个存储空间分配器,即便它要分配不同类型的对象,如:对字符指针的请求和对 struct tnode 指针的请求。出现2个问题.
问题一
:如何满足多数实际机器都存在的要求,即某些类型的对象必须满足一些对齐限制?
ANS: malloc函数
问题二
:什么样的声明才能应对一个分配器必须按需返回不同类型的指针这一实际问题?
ANS: C中将malloc声明为返回一个void型指针,并显式地将指针强制转换为所期望的类型
5 表查询
哈希算法
6 类型定义(Typedef)
①、表达
typedef ——用于创建新的数据类型名
例子1:
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);
例子2:结构
typedef struct tnode *Treeptr;
typedef struct tnode {
/* 树节点 */
char *word; /* 指向单词的文本 */
int count; /* 出现次数 */
Treeptr left; /* 左孩子 */
Treeptr right; /* 右孩子 */
} Treenode;
创建了两个新的类型关键字 Treenode(一个结构)和 Treeptr(该结构的指针)
Treeptr talloc(void)
{
return (Treeptr) malloc(sizeof(Treenode));
}
②、强调
typedef 声明并没有创造任何意义上的新类型;它只是为现有的类型增加了一个新名字。
它也没有创造任何新的语义:用这种方式声明的变量与由显式拼写的类型所声明的变量的特性完全一致。
typedef int (*PFI)(char *, char *);
创建了类型 PFI,它表示“指向返回 int 的(且拥有两个 char * 参数的)函数的指针”
③、理由
- 将程序参数化以应对移植性问题。
- 为程序提供更好的说明性
7 联合(Union)
-
联合
是一个变量,它可以(在不同的时间)包含不同类型和大小的对象,并由编译器来追踪大小和对齐要求。联合提供了一种在单个存储区域操控不同类型的数据的途径,且不需要在程序中嵌入任何与具体机器相关的信息。 -
语法
-①存在结构中:int ival; float fval; char *sval; } u; 联合的成员获取: 联合名.成员 或 联合指针->成员 ```
-②存在在结构和数组
struct {
char *name;
int flags;
int utype;
union {
int ival;
float fval;
char *sval;
} u;
} symtab[NSYM];
对成员 ival 的引用通过 :
symtab[i].u.ival
对字符串 sval 的首字符的引用:
*symtab[i].u.sval
或
symtab[i].u.sval[0]
8 位域(Bit-fields)
①、目的:当存储空间极为宝贵
时,可能有必要将几个对象压缩到单个机器字
中;
②、解决方法:
方法一:需要使用位逻辑运算
#define KEYWORD 01
#define EXTERNAL 02
#define STATIC 04
or
enum {
KEYWORD = 01, EXTERNAL = 02, STATIC = 04 };
flags |= EXTERNAL | STATIC;
flags &= ~(EXTERNAL | STATIC);
if ((flags & (EXTERNAL | STATIC)) == 0) …
**VS**
方法二:位域(bit-field)为“字”的存储单元内的一组相邻的比特位。
struct {
unsigned int is_keyword : 1;
unsigned int is_extern : 1;
unsigned int is_static : 1;
} flags;
冒号之后的数代表了该域的比特位宽度。
这些域被声明为 unsigned int 以确保它们是无符号的量。
单个域的引用方式与结构同。
flags.is_extern = flags.is_static = 1;
flags.is_extern = flags.is_static = 0;
if (flags.is_extern == 0 && flags.is_static == 0)
注意点:
- 域可以仅用 int 进行声明;但为了移植性,
请显式地指定 signed 或者 unsigned
。 - 它们
不是数组
,也没有地址
,因此 & 运算符不能作用 于它们。
补充、常见查找法
二分查找(条件:必须排序)(Olog n)
二叉排序树(Onlogn)
哈希表