代码改变世界

C 语言编程 — 高级数据类型 — 结构体与位域

2020-04-03 23:00  云物互联  阅读(519)  评论(0编辑  收藏  举报

目录

前文列表

程序编译流程与 GCC 编译器
C 语言编程 — 基本语法
C 语言编程 — 基本数据类型
C 语言编程 — 变量与常量
C 语言编程 — 运算符
C 语言编程 — 逻辑控制语句
C 语言编程 — 函数
C 语言编程 — 高级数据类型 — 指针
C 语言编程 — 高级数据类型 — 数组
C 语言编程 — 高级数据类型 — 字符串
C 语言编程 — 高级数据类型 — 枚举

结构体

结构体用来自定义一个新的数据类型。与枚举类型的枚举值都是整型不同,结构体由一系列具有相同或不同数据类型的变量组成。我们可以使用结构体表示更加复杂的数据类型。

注意,我们应该将结构体放在所有用到它的函数的上方。这个类型和内建的基本数据类型的用法没有任何区别。

定义结构体

使用 struct 关键字来类型结构体数据类型:

struct tag { 
    member
    member
    member
    ...
} variable;
  • tag 是结构体的标识(名字)。
  • member 是几结构体的成员,为标准的变量定义语句,比如:int i
  • variable-list 结构体变量,可以一次性指定一个或多个结构体变量。

e.g.

struct Books {
   char  title[50];
   char  author[50];
   char  subject[100];
   int   book_id;
} book;

结构体的定义有多种方式,比较灵活。通常的,tag、member、variable-list 这 3 部分至少要出现 2 个:

  1. 直接创建:在声明定义结构体类型的同时创建结构体变量

struct {
    int a;
    char b;
    double c;
} s1;
  1. 间接创建:先声明定义结构体类型,再另外创建结构体变量
struct SIMPLE
{
    int a;
    char b;
    double c;
};

struct SIMPLE t1, t2[20], *t3;

注意,在上面的声明中,第一个和第二声明被编译器当作两个完全不同的类型,即使他们的成员列表是一样的。

  1. 无名创建:当使用直接创建方式时,可以省略结构体类型名,所以称为无名创建,我们可以同时使用 structtypedef 来声明一个无名结构体。
typedef struct
{
    int a;
    char b;
    double c; 
} Simple2;

// 现在可以用 Simple2 作为类型,声明新的结构体变量
Simple2 u1, u2[20], *u3;

结构体的成员可以是其他结构体,也可以包含指向自己结构体类型的指针,这种指针的应用通常是为了实现一些更高级的数据结构如链表和树等。

struct COMPLEX {
    char string[100];
    struct SIMPLE a;
};

struct NODE {
    char string[100];
    struct NODE *next_node;
};

如果两个结构体互相包含,则需要对其中一个结构体进行声明,同时还要注意语句的顺序:

struct B;

struct A{
    struct B *partner;
    //other members;
};

struct B {
    struct A *partner;
    //other members;
};

初始化结构体变量

#include <stdio.h>

struct Books {
    char    title[50];
    char    author[50];
    char    subject[100];
    int     id;
} book = {"is book", "fanguiju", "C", 123};

int main() {
    printf("Book's title: %s\nauthor: %s\nsubject: %s\nid: %d\n", book.title, book.author, book.subject, book.id);
    return 0;
}

访问结构体成员

使用成员访问运算符 . 对结构体的成员进行访问。

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

struct Books {
    char    title[50];
    char    author[50];
    char    subject[100];
    int     id;
};

int main() {
    struct Books book1;
    strcpy(book1.title, "C Programming");
    strcpy(book1.author, "Nuha Ali");
    strcpy(book1.subject, "C Programming Tutorial");
    book1.id = 123;
    printf("Book's title: %s\nauthor: %s\nsubject: %s\nid: %d\n", book1.title, book1.author, book1.subject, book1.id);
    return 0;
}

将结构体作为实参传入函数

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

struct Books {
    char    title[50];
    char    author[50];
    char    subject[100];
    int     id;
};

void printBook(struct Books book) {
    printf("Book's title: %s\nauthor: %s\nsubject: %s\nid: %d\n", book.title, book.author, book.subject, book.id);
}
int main() {
    struct Books book1;
    strcpy(book1.title, "C Programming");
    strcpy(book1.author, "Nuha Ali");
    strcpy(book1.subject, "C Programming Tutorial");
    book1.id = 123;

    printBook(book1);
    return 0;
}

指向结构体变量的指针

定义基类为结构体 Books 的指针类型变量:

struct Books *struct_pointer;

现在,就可以在上述定义的指针变量中存储结构体变量的内存地址了:

struct_pointer = &book1;

在使用指向该结构体变量的指针访问结构体成员时,必须使用 -> 运算符,如下所示:

struct_pointer->title;

因为结构体指针变量 struct_pointer 本质是一个内存地址,跟结构体变量不同,不可以直接使用成员访问运算符 .,而是使用 -> 运算符。

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

struct Books {
    char    title[50];
    char    author[50];
    char    subject[100];
    int     id;
};

void printBook(struct Books *book) {
    printf("Book's title: %s\nauthor: %s\nsubject: %s\nid: %d\n", book->title, book->author, book->subject, book->id);
}
int main() {
    struct Books book1;
    strcpy(book1.title, "C Programming");
    strcpy(book1.author, "Nuha Ali");
    strcpy(book1.subject, "C Programming Tutorial");
    book1.id = 123;

    printBook(&book1);
    return 0;
}

位域

在某些场景中,需要存储的数据值并不需要占用一个完整的字节(Byte),而只需占几个或一个二进制位(Bit)。例如:存放一个开关量,只有 0 和 1 两种状态,使用到 1 位二进位即可。

为了节省存储空间,并使处理简便,C 语言又提供了一种数据结构 —— 位域。

所谓 “位域” 就是把一个字节中的二进位划分为几个不同的区域,并说明每个区域的位数以及标识(域名),允许在程序中按域名进行操作。这样就可以把几个不同的变量用同一个字节总的不同二进制位域来表示。

定义位域

位域的定义与结构体定义类型,本质是一种特殊的结构体:

struct 位域结构体名 {
    类型说明符 [位域名]: 位域长度(Bit)
    ...
};

在这里插入图片描述

e.g.

struct bs {
    int a:8;
    int b:2;
    int c:6;
} data;

上例位域结构体变量 data 占用了 2 个字段(16 位)。

  • 一个位域存储在同一个字节(单元)中,当一个字节所剩的空间不够存放另一位域时,则会从下一单元起存放该位域。也可以有意使某位域从下一单元开始:
struct bs {
    unsigned a:4;
    unsigned  :4;    /* 空域,填 0 表示不使用 */
    unsigned b:4;    /* 刻意从下一单元开始存放 */
    unsigned c:4
}
  • 由于位域不允许跨字节,因此位域的长度不能大于一个字节的长度(8 Bit)。如果最大长度大于计算机的整数字长,一些编译器可能会允许域的内存重叠,另外一些编译器可能会把大于一个域的部分存储在下一个字中。视乎于编译器的实现,这也是 C 语言的特点之一。

  • 位域可以是无名位域,这时它只用来作填充或调整位置。无名的位域是不能使用的。如上例子中的空域。

使用位域结构体的成员

#include <stdio.h>

int main() {
    struct BS {
        unsigned a:1;
        unsigned b:3;
        unsigned c:4;
    };

    struct BS bit;
    struct BS *pbit;

    bit.a = 1;    /* 给位域赋整型值 1,数值没有超过位域 a 的 1bit */
    bit.b = 7;    /* 给位域赋整型值 7,数值没有超过位域 b 的 3bit */
    bit.c = 15;   /* 给位域赋整型值 15,数值没有超过位域 c 的 4bit */

    printf("%d, %d, %d\n", bit.a, bit.b, bit.c);

    pbit = &bit;   /* 把位域结构体变量 bit 的地址赋给位域结构体指针变量 pbit */
    pbit->a = 0;   /* 结构体变量访问结构体成员 */
    pbit->b &= 3;  /* 与赋值运算 */
    pbit->c |= 1;  /* 或赋值运算 */
    printf("%d, %d, %d\n", pbit->a, pbit->b, pbit->c);

    return 0;
}

运行:

$ ./main
1, 7, 15
0, 3, 15