c/docs/notes/02_c-leap/07_xdx/index.md

23 KiB
Raw Blame History

第一章:枚举(

1.1 回顾 C 语言中的变量

  • C 语言中的变量,按照数据类型划分,如下所示:

Note

构造类型也会被人称为自定义数据类型

  • C 语言中的变量,按照声明位置划分,如下所示:

  • C 语言中的变量,按照存储方式分类,如下所示:

1.2 什么是枚举?

  • 在实际生活中,我们经常会遇到一些数据的值是有限的,如:

    • 星期Monday (星期一)、......、Sunday (星期天)。
    • 性别Man (男)、Woman (女)。

    • 季节Spring (春节)......Winter (冬天)。

    • 支付方式Cash现金、WeChatPay微信、Alipay (支付宝)、BankCard (银 行卡)、CreditCard (信用卡)。

    • 就职状态Busy、Free、Vocation、Dimission。

    • 订单状态Nonpayment未付款、Paid已付款、Delivered已发货、 Return退货、Checked已确认Fulfilled已配货

    • ...

  • 类似上述的场景,我们就可以使用 C 语言提供的一种构造类型 --- 枚举Enumeration ,其用于定义一组相关的整型常量。它提供了一种更具可读性和可维护性的方式来定义常量集合

1.3 定义枚举

  • 语法:
enum 枚举类型 {
    枚举元素1, // 枚举常量1
    枚举元素2, // 枚举常量2
    ...
}

Note

枚举元素也称为枚举成员枚举常量,具有如下的特点:

  • ① 枚举元素的值必须在同一枚举中是唯一的。
  • ② 枚举元素的值必须是整数类型,通常是 int 。
  • ③ 如果没有为枚举元素指定值,编译器会自动为它们进行分配,从 0 开始,自动递增。
  • ④ 定义枚举的时候,也可以为枚举元素自定义值,但是需要保证唯一性和整数类型。

Important

CLion 中选中枚举元素并使用快捷键 Ctrl + Q,或将鼠标悬浮在枚举元素上,就会自动显示枚举元素对应的值,如下所示:

  • 示例:每个枚举常量的值默认为从 0 开始递增的整数
#include <stdio.h>

/**
 * 定义枚举
 */
enum Color {
    RED,   // 0
    GREEN, // 1
    BLUE   // 2
};

int main() {

    // 禁用 stdout 缓冲区
    setbuf(stdout, nullptr);

    return 0;
}
  • 示例:定义带有显式值的枚举,如果给定一个常量的值,后续的常量会依次递增
#include <stdio.h>

/**
 * 定义枚举
 */
enum Color {
    RED = 1,
    GREEN, // 2
    BLUE   // 3
};

int main() {

    // 禁用 stdout 缓冲区
    setbuf(stdout, nullptr);

    enum Color color = GREEN;
    printf("color = %d\n", color);

    return 0;
}

1.4 枚举变量

1.4.1 概述

  • 定义变量时所指定的类型是我们自定义的枚举类型,那么该变量就称为枚举变量。

1.4.2 定义枚举变量

  • 可以使用定义好的枚举类型来声明枚举变量。
  • 语法:
enum 枚举名 变量名;
  • 示例:
#include <stdio.h>

/**
 * 定义枚举
 */
enum Color {
    RED = 1,
    GREEN,
    BLUE
};

int main() {

    // 禁用 stdout 缓冲区
    setbuf(stdout, nullptr);
    
    // 定义枚举变量
    enum Color color ; // [!code highlight]

    return 0;
}

1.4.3 给枚举变量赋值

  • 枚举变量的值应该是枚举类型中的任意一个枚举元素(没有常量),不能是其他的值。
  • 语法:
枚举变量 = 枚举常量;
  • 示例:
#include <stdio.h>

/**
 * 定义枚举
 */
enum Color {
    RED = 1,
    GREEN,
    BLUE
};

int main() {

    // 禁用 stdout 缓冲区
    setbuf(stdout, nullptr);

    // 给枚举变量赋值
    enum Color color = BLUE; // [!code highlight]
    
    printf("color = %d\n", color);

    return 0;
}

1.5 枚举的本质到底是什么?

  • 尽管枚举的定义语法看起来像一种新类型,但它的底层实际上是一个整型(通常是 int 类型。C 语言并不强制要求枚举使用特定的整型类型,但编译器通常会选择使用 int 来表示枚举。

  • 在 C 语言中,枚举类型和整数类型是兼容的。你可以在需要整数的地方使用枚举值,也可以将枚举值赋给整型变量。这是因为枚举成员在编译时就被替换为其对应的整数值。

  • 示例:

#include <stdio.h>

/**
 * 定义枚举
 */
enum Color {
    RED = 1,
    GREEN, // 2
    BLUE   // 3
};

int main() {

    // 禁用 stdout 缓冲区
    setbuf(stdout, nullptr);

    enum Color color = 0;

    printf("sizeof(color) = %zu \n", sizeof(color)); // sizeof(color) = 4
    printf("sizeof(RED) = %zu \n", sizeof(RED));     // sizeof(RED) = 4
    printf("sizeof(GREEN) = %zu \n", sizeof(GREEN)); // sizeof(GREEN) = 4
    printf("sizeof(BLUE) = %zu \n", sizeof(GREEN));  // sizeof(BLUE) = 4
    printf("sizeof(int) = %zu \n", sizeof(int));     // sizeof(int) = 4

    return 0;
}

1.6 应用示例

  • 如果枚举常量的值是连续的,我们可以使用循环遍历;如果枚举常量的值不是连续的,则无法遍历。

  • 示例:

#include <stdio.h>
// 定义枚举类型
enum Weekday {
    MONDAY = 1,
    TUESDAY,
    WEDNESDAY,
    THURSDAY,
    FRIDAY,
    SATURDAY,
    SUNDAY
};
int main() {

    // 定义枚举变量
    enum Weekday day;

    // 使用循环遍历出所有的枚举常量
    for (day = MONDAY; day <= SUNDAY; day++) {
        printf("%d \n", day);
    }

    return 0;
}

1.7 应用示例

  • 枚举变量通常用于控制语句中switch 语句。

  • 示例:

#include <stdio.h>

/**
 * 定义枚举
 */
enum Color {
    RED = 1,
    GREEN, // 2
    BLUE   // 3
};

int main() {

    // 禁用 stdout 缓冲区
    setbuf(stdout, nullptr);

    enum Color color;
    
    printf("请输入颜色(1-3)");
    scanf("%d", &color);
    switch (color) {
        case RED:
            printf("红色\n");
            break;
        case GREEN:
            printf("绿色\n");
            break;
        case BLUE:
            printf("蓝色\n");
            break;
        default:
            printf("输入错误\n");
            break;
    }

    return 0;
}

第二章:结构体(

2.1 概述

  • C 语言内置的数据类型,除了几种原始的基本数据类型,只有数组属于复合类型,可以同时包含多个值,但是只能包含相同类型的数据,实际使用场景受限。

2.2 为什么需要结构体?

2.2.1 需求分析 1

  • 现有一个需求,编写学生档案管理系统,这里需要描述一个学生的 信息。该学生的信息包括学号、姓名、性别、年龄、家庭住址等, 这些数据共同说明一个学生的总体情况。

  • 显然,这些数据类型各不相同,无法使用数组进行统一管理。

2.2.2 需求分析 2

  • 隔壁老王养了两只猫咪。一只名字叫小黄,今年 2 岁,橘色;另一只叫小黑,今年 3 岁,黑色。请编写一个程序,当用户输入小猫的名字 时,就显示该猫的名字,年龄,颜色。如果用户输入的小猫名错误,则显示老王没有这只猫。

2.2.3 传统的解决方案

  • 尝试 ① :单独定义多个变量存储,实现需求,但是,多个变量,不便于数据的管理。
  • 尝试 ② :使用数组,它是一组具有相同类型的数据的集合。但在编程中,往往还需要一组类型不同的数据,例如:猫的名字使用字符串、年龄是 int颜色是字符串因为数据类型不同不能用一个数组来存放。
  • 尝试 ③ C 语言提供了结构体。使用结构体,内部可以定义多个不同类型的变量作为其成员。

2.3 什么是结构体

  • C 语言提供了 struct 关键字允许自定义复合数据类型将不同类型的值组合在一起这种类型称为结构体structure类型。

Note

C 语言没有其他语言的对象object和类class的概念struct 结构很大程度上提供了对象和类的功能。

2.4 声明结构体

  • 语法:
struct 结构体名{
    数据类型1 成员名1;   // 分号结尾
    数据类型2 成员名2;
    ……
    数据类型n 成员名n;
}; 

Note

结构体中可以包含以下数据类型:

  • ① 基本数据类型:整型、浮点型、字符型、布尔型。
  • ② 指针类型。
  • ③ 枚举类型。
  • ④ 结构体类型:
    • 可以包含其他结构体作为成员(称为嵌套结构体)。
    • 结构体指针。
  • ⑤ 联合体类型。
  • ⑥ 位域。
  • 示例:
#include <stdio.h>

/**
 * 声明学生的结构体
 */
struct Student {
    int  id;          // 学号
    char name[20];    // 姓名
    char gender;      // 性别
    int  age;         // 年龄
    char address[50]; // 地址
};

int main() {

    // 禁用 stdout 缓冲区
    setbuf(stdout, nullptr);

    return 0;
}
  • 示例:
#include <stdio.h>

/**
 * 声明猫的结构体
 */
struct Cat {
    char name[20];  // 姓名
    int  age;       // 年龄
    char color[50]; // 颜色
};

int main() {

    // 禁用 stdout 缓冲区
    setbuf(stdout, nullptr);

    return 0;
}
  • 示例:
#include <stdio.h>

/**
 * 声明人类的结构体
 */
struct Person {
    char   name[20]; // 姓名
    char   gender;   // 性别
    int    age;      // 年龄
    double weight;   // 体重
};

int main() {

    // 禁用 stdout 缓冲区
    setbuf(stdout, nullptr);

    return 0;
}
  • 示例:
#include <stdio.h>

/**
 * 声明通讯录的结构体
 */
struct Contact {
    char name[50];        // 姓名
    int  year;            // 年
    int  month;           // 月
    int  day;             // 日
    char email[100];      // 电子邮箱
    char phoneNumber[15]; // 手机号
};

int main() {

    // 禁用 stdout 缓冲区
    setbuf(stdout, nullptr);

    return 0;
}
  • 示例:
#include <stdio.h>

/**
 * 声明员工的结构体
 */
struct Employee {
    int  id;          // 员工编号
    char name[20];    // 员工姓名
    char gender;      // 员工性别
    int  age;         // 员工年龄
    char address[30]; // 员工住址
};

int main() {

    // 禁用 stdout 缓冲区
    setbuf(stdout, nullptr);

    return 0;
}

2.5 定义结构体变量

2.5.1 概述

  • 定义了新的数据类型(结构体类型)以后,就可以定义该类型的变量,这与定义其他类型变量的写法是一样的。

2.5.2 方式一

  • 语法:
struct 结构体类型名称 结构体变量名;

Note

  • ① 需要先定义结构体,然后再定义结构体变量。
  • struct 关键字不能省略;否则, C 语言编译器将会报错。

Caution

在 C 语言中结构体struct和结构体变量是两个不同的概念如下所示

  • ① 结构体是一种自定义的数据类型,像一种模板,定义了数据的格式。
  • ② 结构体变量是根据结构体类型创建的变量,代表了一个具体的对象,用于存储数据。
  • 示例:
#include <stdio.h>

/**
 * 声明学生的结构体
 */
struct Student {
    int  id;          // 学号
    char name[20];    // 姓名
    char gender;      // 性别
    int  age;         // 年龄
    char address[50]; // 地址
};

int main() {

    // 禁用 stdout 缓冲区
    setbuf(stdout, nullptr);

    // 定义学生结构体的变量
    struct Student student; // [!code highlight]

    return 0;
}
  • 示例:
#include <stdio.h>

/**
 * 声明猫的结构体
 */
struct Cat {
    char name[20];  // 姓名
    int  age;       // 年龄
    char color[50]; // 颜色
};

int main() {

    // 禁用 stdout 缓冲区
    setbuf(stdout, nullptr);

    // 定义猫结构体的变量
    struct Cat cat; // [!code highlight]
    
    return 0;
}

2.5.3 方式二

  • 语法:
struct 结构体名{
    数据类型1 成员名1;   // 分号结尾
    数据类型2 成员名2;
    ……
    数据类型n 成员名n;
} 结构体变量1,结构体变量2,...; 

Note

在声明结构体的同时定义结构体变量。

  • 示例:
#include <stdio.h>

/**
 * 声明学生的结构体的同时定义结构体变量
 */
struct Student {
    int  id;          // 学号
    char name[20];    // 姓名
    char gender;      // 性别
    int  age;         // 年龄
    char address[50]; // 地址
} stu1,stu2; // stu1 和 stu2 是结构体变量

int main() {

    // 禁用 stdout 缓冲区
    setbuf(stdout, nullptr);

    return 0;
}
  • 示例:
#include <stdio.h>

/**
 * 声明猫的结构体的同时定义结构体变量
 */
struct Cat {
    char name[20];  // 姓名
    int  age;       // 年龄
    char color[50]; // 颜色
} cat; // cat 是结构体变量

int main() {

    // 禁用 stdout 缓冲区
    setbuf(stdout, nullptr);

    return 0;
}

2.5.4 方式三

  • 语法:
struct {
    数据类型1 成员名1;   // 分号结尾
    数据类型2 成员名2;
    ……
    数据类型n 成员名n;
} 结构体变量1,结构体变量2,...; 

Note

  • ① 在声明结构体的同时定义结构体变量,但是不给结构体名,这种方式的结构体也称为匿名结构体
  • ② 和方式二相比,后面的代码将无法通过该结构体来定义变量,因为没有结构体名称,除非使用 typedef 关键字。
  • 示例:
#include <stdio.h>

/**
 * 声明学生的结构体的同时定义结构体变量
 */
struct {
    int  id;          // 学号
    char name[20];    // 姓名
    char gender;      // 性别
    int  age;         // 年龄
    char address[50]; // 地址
} stu1,stu2; // stu1 和 stu2 是结构体变量

int main() {

    // 禁用 stdout 缓冲区
    setbuf(stdout, nullptr);

    return 0;
}
  • 示例:
#include <stdio.h>

/**
 * 声明猫的结构体的同时定义结构体变量
 */
struct {
    char name[20];  // 姓名
    int  age;       // 年龄
    char color[50]; // 颜色
} cat; // cat 是结构体变量

int main() {

    // 禁用 stdout 缓冲区
    setbuf(stdout, nullptr);

    return 0;
}

2.6 结构体变量中成员的获取和赋值

2.6.1 概述

  • 成员是结构体的一个组成部分,一般是基本数据类型、也可以是数组、指针、结构体等。结构体的成员也可以称为属性。
  • 结构体和数组类似,也是一组数据的集合,结构体使用点号 . 获取单个成员,可以进行赋值和取值。

2.6.2 结构体成员逐个赋值

  • 语法:
结构体变量名.成员名 = ; // 值可以是常量或变量
  • 示例:
#include <stdio.h>
#include <string.h>

/**
 * 声明人类的结构体
 */
struct Person {
    char   name[20]; // 姓名
    char   gender;   // 性别
    int    age;      // 年龄
    double weight;   // 体重
};

int main() {

    // 禁用 stdout 缓冲区
    setbuf(stdout, nullptr);

    // 定义猫结构体的变量
    struct Person person;

    // 结构体变量中成员赋值
    strcpy(person.name, "张三");
    person.gender = 'M';
    person.age    = 20;
    person.weight = 60.5;

    // 结构体变量中成员的访问
    printf("姓名:%s\n", person.name);     // 姓名:张三
    printf("性别:%c\n", person.gender);   // 性别M
    printf("年龄:%d\n", person.age);      // 年龄20
    printf("体重:%.2f\n", person.weight); // 体重60.50

    return 0;
}
  • 示例:
#include <stdio.h>
#include <string.h>

/**
 * 声明学生的结构体
 */
struct Student {
    int  id;          // 学号
    char name[20];    // 姓名
    char gender;      // 性别
    int  age;         // 年龄
    char address[50]; // 地址
};

int main() {

    // 禁用 stdout 缓冲区
    setbuf(stdout, nullptr);

    // 定义学生结构体的变量
    struct Student student;

    // 结构体变量中成员赋值
    student.id = 10001;
    strcpy(student.name, "张三");
    student.gender = 'M';
    student.age    = 20;
    strcpy(student.address, "北京市海淀区");

    // 输出结构体变量中成员的值
    printf("学号: %d\n", student.id);      // 学号: 10001
    printf("姓名: %s\n", student.name);    // 姓名: 张三
    printf("性别: %c\n", student.gender);  // 性别: M
    printf("年龄: %d\n", student.age);     // 年龄: 20
    printf("地址: %s\n", student.address); // 地址: 北京市海淀区

    return 0;
}

2.6.3 使用大括号一次性对结构体所有成员赋值

  • 语法:
struct 结构体类型 结构体变量 = {...}; 
struct 结构体类型 结构体变量 = {.成员 = xxx,...}; 

Note

  • struct 结构体类型 结构体变量 = {...};,需要和声明结构体中成员的顺序保持一致。
  • struct 结构体类型 结构体变量 = {.成员 = xxx,...}; ,不需要和声明结构体中成员的顺序保持一致。
  • ③ 如果初始化的属性少于声明时的属性,剩下的那些属性都会初始化为 0 。

Important

CLion 中其实是有这类语法提示的,如下所示:

  • 示例:
#include <stdio.h>
#include <string.h>

/**
 * 声明学生的结构体
 */
struct Student {
    int  id;          // 学号
    char name[20];    // 姓名
    char gender;      // 性别
    int  age;         // 年龄
    char address[50]; // 地址
};

int main() {

    // 禁用 stdout 缓冲区
    setbuf(stdout, nullptr);

    // 定义学生结构体的变量并进行初始化
    struct Student student = {10001, "张三", 'M', 20, "北京市海淀区"}; // [!code highlight]

    // 输出结构体变量中成员的值
    printf("学号: %d\n", student.id);      // 学号: 10001
    printf("姓名: %s\n", student.name);    // 姓名: 张三
    printf("性别: %c\n", student.gender);  // 性别: M
    printf("年龄: %d\n", student.age);     // 年龄: 20
    printf("地址: %s\n", student.address); // 地址: 北京市海淀区

    return 0;
}
  • 示例:
#include <stdio.h>

/**
 * 声明人类的结构体
 */
struct Person {
    char   name[20]; // 姓名
    char   gender;   // 性别
    int    age;      // 年龄
    double weight;   // 体重
};

int main() {

    // 禁用 stdout 缓冲区
    setbuf(stdout, nullptr);

    // 定义人类结构体的变量并进行初始化
    struct Person person = {.gender = 'M', 
                            .name = "张三",
                            .age = 15,
                            .weight = 60.5}; 

    // 结构体变量中成员的访问
    printf("姓名:%s\n", person.name);     // 姓名:张三
    printf("性别:%c\n", person.gender);   // 性别M
    printf("年龄:%d\n", person.age);      // 年龄20
    printf("体重:%.2f\n", person.weight); // 体重60.50

    return 0;
}

第三章:共用体

在C语言、C++等编程语言中,结构体(struct)是一种用户自定义的数据类型,可以包含不同类型的数据字段。结构体的目的是将多个不同类型的数据组合在一起形成一个整体。结构体中可以包含以下数据类型:

  1. 基本数据类型

    • 整型 (int, short, long, unsigned int, unsigned long 等)
    • 浮点型 (float, double)
    • 字符型 (char)
    • 布尔类型 (bool通常在C++中使用)
  2. 指针类型

    • 指向特定数据类型的指针(如 int*, char* 等)
    • 函数指针(如 int (*funcPtr)(int, int)
  3. 数组类型

    • 定长数组(如 int arr[10], char str[50]
    • 字符串数组C语言中的字符数组用于存储字符串
  4. 枚举类型

    • 枚举类型(如 enum day {SUN, MON, TUE}
  5. 结构体类型

    • 可以包含其他结构体作为成员(称为嵌套结构体)
    • 结构体指针
  6. 联合体类型union

    • 可以包含联合体(union),用于多个成员共享同一块内存。
  7. 位域Bit fields

    • 在C语言中结构体中可以使用位域来精确控制字段占用的比特位。

示例代码:

#include <stdio.h>

// 定义结构体
struct Person {
    char name[50];     // 字符数组
    int age;           // 整型
    float height;      // 浮点型
    struct Address {   // 嵌套结构体
        char city[50];
        int zipCode;
    } address;
};

int main() {
    // 创建结构体变量
    struct Person person1 = {"Alice", 30, 5.6, {"New York", 10001}};
    
    // 访问结构体成员
    printf("Name: %s\n", person1.name);
    printf("Age: %d\n", person1.age);
    printf("Height: %.1f\n", person1.height);
    printf("City: %s\n", person1.address.city);
    
    return 0;
}

结构体是灵活且强大的工具,允许我们将各种类型的数据组合在一起,便于代码管理与逻辑抽象。

结构体不能直接包含自己作为成员,这是因为这样会导致无限递归定义,结构体的大小无法确定,编译器无法正确分配内存。

原因详解:

  1. 内存分配问题 假设你定义一个结构体struct A,其中包含一个类型为struct A的成员:

    struct A {
        int data;
        struct A self; // 错误,结构体不能包含自己
    };
    

    编译器会试图计算结构体A的大小,但是因为A中包含另一个A,这个A中又包含另一个A,这种嵌套会无限递归下去。编译器无法确定最终的大小,因为这个定义永远不会结束。

  2. 逻辑上的循环 如果结构体包含自己,这意味着每个结构体实例会包含另一个结构体实例,后者又包含另一个结构体实例,导致逻辑上的循环引用。这是不可能实现的,因为系统的内存和逻辑不能支持这种无穷递归。

可行的解决方案:

虽然不能直接包含自己,但是可以通过指针来引用自身。指针有固定的大小通常是4字节或8字节取决于系统架构因此不会造成上述的无限递归问题。

示例:通过指针包含自身

#include <stdio.h>

// 定义结构体
struct Node {
    int data;
    struct Node* next;  // 使用指针引用自己
};

int main() {
    // 创建结构体节点
    struct Node node1;
    struct Node node2;
    
    // 初始化数据
    node1.data = 1;
    node2.data = 2;
    
    // 链接节点
    node1.next = &node2;
    node2.next = NULL;  // 最后一个节点的 next 指针为 NULL

    // 访问节点
    printf("Node 1 data: %d\n", node1.data);
    printf("Node 2 data: %d\n", node1.next->data);

    return 0;
}

在这个例子中,struct Node中包含了一个指向同类型结构体的指针next,这样可以形成链表等数据结构。通过使用指针,避免了无限递归定义,并且能达到引用自身的目的。

总结:

  • 结构体不能直接包含自身作为成员,因为会导致编译器无法确定结构体的大小,产生无限递归。
  • 可以通过使用指针来实现结构体引用自身的需求。