结构
相同或者不同类型的变量集合起来,组织在一个名字之下,有助于组织复杂的数据
基础
- 结构体类型声明
- 结构体变量声明
- 结构体变量定义
- 结构体成员引用
- 结构体嵌套
/* 结构体类型声明 */
struct point
{
int x;
int y;
};
/* 结构体变量声明/定义 */
struct point pt;
struct point maxpt = {320, 200};
/* 结构体成员引用 */
printf("%d,%d", pt.x, pt.y);
/* 结构体嵌套*/
struct rect
{
struct point pt1;
struct point pt2;
};
结构体与函数
- 结构体的合法操作:
- 整体赋值和复制
- &取地址
- 访问成员
/* makepoint函数:通过坐标x和y构造一个点 */
struct point makepoint(int x, int y)
{
struct point temp;
temp.x = x;
temp.y = y;
returm temp;
} /* 结构体作为函数的返回值,整体返回 */
/* canonrect函数:将矩形坐标规范化 */
sturct rect cannonrect(struct rect r) /* 函数的参数是结构体 */
{
struct rect temp;
temp.pt1.x = min(r.pt1.x, r.pt2.x);
temp.pt1.y = min(r.pt1.y, r.pt2.y);
temp.pt2.x = max(r.pt1.x, r.pt2.x);
temp.pt2.x = max(r.pt1.x, r.pt2.x);
return temp;
}
结构体指针
- 间接取址后访问成员
- 通过 -> 符号访问成员
- 注意这里容易出错,不能用指针直接访问成员,必须以上二者之一
struct point pt, *pp;
pp = &pt;
(*pp).x /* 正确 */
(*pp).y /* 正确 */
pp->x /* 正确 */
pp->y /* 正确 */
pp.x /* 错误 */
pp.y /* 错误 */
结构体数组
- 跟其他类型的数组没有太大区别
- 初值表初始化更复杂一些
/* 直接在{}中按照顺序写入数据 */
struct key
{
char *word;
int count;
}keytab[] = {
"auto", 0,
"break", 0,
"case", 0,
...
"while", 0
};
/* 没个结构成员,都单独在一个{}中初始化,这样比较清晰 */
struct key keytab[] = {
{"auto", 0},
{"break", 0},
{"case", 0},
...
{"while", 0}
}
结构体的长度
- sizeof,返回占用的字节数,类型位size_t,是编译时一元运算符,所以预编译的时候是不求值的,需要注意
- sizeof 对象
- sizeof(类型名)
/* 利用sizeof 获取结构体数组的成员数量 */
#define NKEYS (siezof keytab / sizeof(struct key))
#define NKEYS (sizeof keytab / sizeof(keytab[0]))
/* 第二种比较好,keytab的类型变了也没有关系 */
结构体的长度根据不同平台的对齐策略,可能会有不同,并不等于各成员长度的合
- 结构体对齐等找到比较好的说明文章的时候,再链接到这里
- 现在只要知道,结构体的准确长度,需要用sizeof返回,并不能自己简单计算出来即可
结构体的自引用
- 数据结构的基本链接方式,链表,树等
- 结构体内包含指向自身结构的指针
- 从这里开始C语言的理解和使用变得并不轻松了,熟练掌握常见的数据结构的操作方法是C语言登堂入室的标准
- 同时经常一起使用的还有递归函数,下面是一个二叉查找树的相关实现函数,不容易看懂,推荐自己编写和练习,阅读《数据结构和算法分析》
/* 二叉查找树,保存字符串和出现的次数 */
struct tnode
{
char *word;
int count;
struct tnode *left;
struct tnode *right;
};
struct tnode *talloc(void); /* 给一个新的结构体分配空间的函数 */
char *strdup(char*); /* 赋值字符串函数 */
/* addtree函数:在p的位置或者p的下方增加一个w节点 */
/* 这个函数核心有2个功能:1. 传入的节点指针p并不是NULL,那么在树中移动;2. 如果移动到NULL的节点,创建一个新的节点 */
/* 移动过程中函数内部p并无变化,原样返回 */
/* 创建节点的情况下,p从NULL变成一个已经分配了空间的节点的指针,返回给父节点 */
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;
}
/* treeprint函数:按照顺序打印树p */
void treeprint(struct tnode *p)
{
if (p != NULL)
{
treeprint(p->left);
printf("%4d %s\n", p->count, p->word);
treeprint(p->right);
}
}
#include <stdlib.h>
/* talloc函数:创建一个tnode*/
struct tnode *talloc(void)
{
return (struct tnode*)malloc(sizeof(struct tnode));
}
/* strdup函数:将字符串复制到一个新的安全位置 */
char *strdup(char *s)
{
char *p;
p = (char *)malloc(strlen(s) + 1);
if (p != NULL)
{
strcpy(p, s);
}
return p;
}
类型定义 typedef
建立新的数据类型名,原生的数据类型名有
- 基本数据类型 char short int float double 等
- 复合数据类型,比如结构体
- 枚举类型
typedef建立新的数据类型名,好处
- 方便移植,用typdef定义好u8, u16, u32之类的,移植的时候修改typedef就可以了
- 增强可读性
typedef int Length;
typedef char *String;
Length len, maxlen;
Length *lengths[];
String p, lineptr[MAXLINES], alloc(int);
int strcmp(String, String);
p = (String)malloc(100);
- typedef在复杂结构中的使用(这是更普遍和有意义的)
typdef struct tnode *Treeptr;
typedef struct tnode
{
char *word;
int count;
Treeptr left;
Treeptr right;
}Treenode;
Treeptr talloc(void)
{
return (Treeptr)malloc(sizeof(Treenode));
}
- typedef定义本身的复杂性
typedef int (*PFI)(char * , char *);
PFI strcmp, numcmp;
联合
- 用一个名字,保存不同长度和类型的成员(不同时刻),容量要足够宽
- 联合中成员的方式方式同结构一样,名称.成员 或者 指针->成员
- 联合的初始化,只能用第一个成员的类型进行
struct {
char *name;
int flags;
int utype;
union{
int ival;
float fval;
char *sval;
}u;
}symtab[NSYM];
symtab[i].u.ival
*symtab[i].u.sval
symtab[i].u.sval[0]
位字段
- 对位的操作可以通过2种方式
- 屏蔽码操作
- 位字段
/* 屏蔽码操作方式*/
#define KEYWORD 01
#define EXTERNAL 02
#define STATIC 04
flags |= EXTERNAL | STATIC; /* 设置某些位*/
flags &= ~(EXTERNAL | STATIC); /* 清掉某些位*/
if ((flags & (EXTERNAL | STATIC)) == 0) ...
/* 位字段操作方式*/
struct{
unsigned int is_keyword : 1;
unsigned int is_extern : 1;
unsigned int is_static : 1;
}flags;
flags.is_extern = flags.is_static = 1;
flags.is_extern = flags.is_static = 0;
if (flags.is_extern == 0 && flags.is_static == 0) ...
- 位字段可以没有名字,只是占个位置,起到填充作用
- 字段里边的成员不能取地址(&)