结构体进阶用法


1、结构体

  在C语言中,结构体(struct)是一种用户自定义的数据类型

  结构体它允许将多个不同类型的变量组合在一起,以便更方便地管理相关数据。初始化一个数组struct queue_prod 类型的变量,每个元素都是该结构体类型的实例。

2、定义结构体的几种方式

  在C语言中,可以使用多种方式来定义结构体。以下是常见的几种方式:

  1. 基本结构体定义: 最常见的方式是通过struct关键字直接定义一个结构体类型,然后在大括号内列出成员变量的名称和类型。例如:
struct Person {
    char name[50];
    int age;
};
  1. 类型重命名: 可以使用typedef关键字将一个已经定义的结构体类型重命名为一个更简洁的类型名。这样可以减少代码中使用的冗长类型名。例如:

   1)第一种方式:

  typedef struct person {
      char name[50];
      int age;
  } Person_t;

   2) 第二种方式:

typedef struct {
    char name[50];
    int age;
} Person;

  这两种方式都在定义一个结构体并使用 typedef 关键字进行重命名。然而,第二种方式是定义了一个匿名的结构体,而不给它一个名字。这种情况下,typedef 直接应用于匿名结构体,而不是在结构体名之后。

  实际上,两种方式在大多数情况下是等效的,可以根据个人偏好选择其中之一。第二种方式更直接,没有引入额外的结构体名,适用于只在局部范围内使用的简单结构体。第一种方式给结构体类型命名,适用于在代码库中更广泛地使用结构体。选择哪种方式取决于的代码风格和组织需求。

  1. 嵌套结构体: 可以在一个结构体中嵌套另一个结构体,形成复杂的数据结构。例如:
struct Date {
    int day;
    int month;
    int year;
};

struct Employee {
    char name[50];
    int id;
    struct Date hireDate; // 嵌套另一个结构体
};
  1. 匿名结构体: 有时可能希望在特定的上下文中定义一个结构体,而不给它起名字。这被称为匿名结构体。例如:
struct {
    char model[20];
    int year;
} car; // 定义了一个匿名结构体,并创建一个结构体变量 car

  这些是一些常见的定义结构体的方式,每种方式都有不同的应用场景。可以根据具体的需求和代码组织风格来选择最适合的方式。

3、结构体变量初始化

  C语言允许使用 .(点运算符) **来访问结构体内部的成员变量,这是一种直观的方式。
示例代码:
  定义结构体类型

struct queue_prod {
  int head;
  int tail;
};

  创建结构体变量并初始化

struct queue_prod sock_opt[5] = {
    [1] = {
        .head = 1,
        .tail = 2
    },
    [2] = {
        // ...
    },
    // ...
};

  在初始化结构体变量时,可以通过使用** . 点运算符来为结构体的各个成员变量指定初始值。这样的语法是为了方便地为结构体的成员变量赋值,使代码更加清晰和易于理解。

  初始化过程中可以使用下标,更明确易懂,[1] 是用于指定数组中的特定元素的索引,也称为数组下标。在C语言中,数组的索引从0开始,因此[0]表示数组的第一个元素,[1]表示数组的第二个元素,依此类推。

  在结构体数组初始化的上下文中,使用[1][2]等索引,可以直接为数组中的特定元素进行初始化。这在你想要只为数组的某些元素提供初始值时非常有用,而不是对整个数组都提供初始值。

  在上述代码段中,[1] 指定了数组 sock_opt 的第二个元素(因为索引从0开始)。使用了这个语法来初始化数组中的第二个元素的成员变量 headtail,分别赋值为 1 和 2。而 [2][3] 等类似的用法可以用于初始化数组的后续元素。

  这种用法在初始化数组时,仅对需要特定值的元素进行初始化,其他元素将会被默认初始化(对于基本类型为0,对于指针为NULL等)。

  总之,[1] 这种用法可以帮助更精确地初始化结构体数组中的特定元素,而不是初始化整个数组。

3.1、实例:

#include <stdio.h>

// 定义一个结构体类型
struct Point {
    int x;
    int y;
};

int main() {
    // 创建一个 Point 结构体变量,并使用 . 初始化成员变量
    struct Point p1 = {
        .x = 10,
        .y = 20
    };

    // 打印初始化后的成员变量值
    printf("p1.x: %d\n", p1.x);
    printf("p1.y: %d\n", p1.y);

    return 0;
}

  在上述示例中,我们首先定义了一个名为 Point 的结构体,它有两个整型成员变量 xy。然后在 main 函数中,我们使用.运算符来初始化结构体变量 p1 的成员变量。

  这里我们使用的语法是 .成员名 = 初始值。在初始化结构体时,通过指定成员变量的名称和对应的初始值,可以使代码更加清晰和易于理解。

  在实际应用中,可以使用此语法来初始化更复杂的结构体,包含各种不同类型的成员变量。

4、对指针变量初始化

#include <stdio.h>

// 定义一个结构体类型
struct Person {
    char *name;
    int age;
};

int main() {
    // 创建一个结构体变量并直接赋值指针成员的地址
    struct Person person = {
        .name = "Alice",
        .age = 25  // 创建一个匿名的 int 变量,然后取其地址
    };

    // 打印初始化后的结构体变量的内容
    printf("Name: %s\n", person.name);
    printf("Age: %d\n", person.age);

    return 0;
}

  首先定义了一个指向结构体的指针成员 name,以及一个整数成员 age。然后,在结构体初始化时,我们使用了.来直接赋值字符串给 name 指针,以及一个匿名整数的地址给 age 指针。

  在实际应用中,如果要分配动态内存给指针成员,最好在初始化后使用malloc等函数来分配内存,并在不再需要时进行内存释放。

5、指针成员指向函数地址

#include <stdio.h>

// 定义一个结构体类型,其中包含两个函数指针成员
struct MathOperations {
    int (*add)(int, int);
    int (*subtract)(int, int);
};

// 实现两个函数
int add(int a, int b) {
    return a + b;
}

int subtract(int a, int b) {
    return a - b;
}

int main() {
    // 创建结构体变量并初始化函数指针成员为相应的函数
    struct MathOperations mathOps = {
        .add = add,
        .subtract = subtract
    };

    // 使用函数指针成员调用函数
    int result_add = mathOps.add(10, 5);
    printf("Add Result: %d\n", result_add);

    int result_subtract = mathOps.subtract(10, 5);
    printf("Subtract Result: %d\n", result_subtract);

    return 0;
}

  上述代码定义了一个包含两个函数指针成员 addsubtract 的结构体类型 MathOperations。然后,实现了两个函数 addsubtract,分别执行加法和减法运算。接着,在 main 函数中,我们创建了结构体变量 mathOps 并初始化函数指针成员为相应的函数。最后,我们使用这些函数指针成员来调用对应的函数。

  通过这种方式,可以在结构体内包含多个不同的函数指针成员,每个函数指针成员对应一个不同的函数,从而实现更灵活的操作。

6、函数指针的弊端

尽管函数指针在某些情况下非常有用,但也存在一些弊端和注意事项:

  1. 复杂性: 使用函数指针可能会增加代码的复杂性。处理函数指针涉及到了更多的语法和概念,可能会增加代码的可读性和维护难度。

  2. 错误处理: 函数指针可以指向无效的地址,如果在调用函数指针之前没有进行有效性检查,可能会导致程序崩溃或产生未定义的行为。

  3. 类型安全性: 函数指针可能会引入类型不匹配的错误。如果函数指针的类型与实际被调用的函数的类型不匹配,可能会导致错误的参数传递和返回值处理。

  4. 维护困难: 当函数原型(参数列表和返回值类型)发生变化时,需要确保所有相关的函数指针声明和调用都进行了相应的更新。

  5. 可读性: 由于函数指针本身是一个引用,函数的调用在代码中可能不够直观,可能需要更多的上下文才能理解它在做什么。

  6. 运行时开销: 函数指针调用的开销可能稍高于直接函数调用,因为它涉及间接寻址。

  7. 代码风格: 过度使用函数指针可能会使代码难以理解。在一些简单场景下,直接函数调用可能更易于阅读和维护。

尽管存在这些弊端,函数指针仍然是一个重要的编程工具,特别是在涉及回调、动态调度、策略模式等需要动态选择函数行为的情况下。使用函数指针时,确保合理使用,并且在可能的情况下使用合适的抽象和设计模式来减少潜在的问题。

7、结构体变量的引用

  在C语言中,使用**.->**两种方式都可以引用结构体成员。它们的区别在于,使用.是在直接引用结构体变量的成员,而使用->是在引用指向结构体的指针的成员。

#include <stdio.h>

// 定义一个结构体类型
struct Person {
    char name[50];
    int age;
};

int main() {
    // 创建结构体变量
    struct Person person1;
    strcpy(person1.name, "Alice");
    person1.age = 25;

    // 创建指向结构体的指针
    struct Person person2;
    struct Person *personPtr = &person2;
    strcpy(personPtr->name, "Bob");
    personPtr->age = 30;

    // 使用.引用结构体变量的成员
    printf("Person 1:\n");
    printf("Name: %s\n", person1.name);
    printf("Age: %d\n", person1.age);

    // 使用->引用指针指向的结构体的成员
    printf("\nPerson 2:\n");
    printf("Name: %s\n", personPtr->name);
    printf("Age: %d\n", personPtr->age);

    return 0;
}

  在这个示例中,我们首先创建了一个结构体变量 person1,以及一个指向结构体的指针 personPtr。然后,我们使用.->分别引用了结构体变量的成员和指针指向的结构体的成员,并进行了输出。

  需要注意的是,当使用->引用指向结构体的指针的成员时,实际上是在通过指针对结构体的成员进行间接引用。使用.直接引用结构体变量的成员。

  区别在于使用.时直接引用结构体变量的成员,而使用->时引用指向结构体的指针的成员。

  • 使用.:用于直接引用结构体变量的成员。例如,person.name 是一个结构体变量 person 的成员 name
  • 使用->:用于引用指向结构体的指针的成员。例如,personPtr->name 是指向结构体的指针 personPtr 所指向结构体的成员 name

这两种操作符的目的是为了方便地访问结构体及其成员,具体使用哪种方式取决于所操作的变量是结构体还是指向结构体的指针。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值