C程序设计语言(四)结构

结构

相同或者不同类型的变量集合起来,组织在一个名字之下,有助于组织复杂的数据

基础

  • 结构体类型声明
  • 结构体变量声明
  • 结构体变量定义
  • 结构体成员引用
  • 结构体嵌套
/* 结构体类型声明 */
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) ...
  • 位字段可以没有名字,只是占个位置,起到填充作用
  • 字段里边的成员不能取地址(&)
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值