结构体

C语言中的结构体

在C语言中,结构体(struct)是一种用户自定义的数据类型,它允许你将不同类型的数据组合成一个单独的数据类型。结构体提供了一种方式来封装一组逻辑上相关的变量(即成员或字段),并可以将它们视为一个整体。这样,你就可以像操作一个单独变量一样操作这些相关的变量。

结构体定义了一个数据类型,你可以创建该类型的变量(称为结构体变量或结构体实例),并为每个变量分配存储空间。每个结构体变量都可以存储不同的值,但所有的结构体变量都遵循相同的格式,即它们都有相同类型的成员,并以相同的顺序排列。

以下是C语言中结构体的基本定义和使用方式:

#include <stdio.h>

// 定义结构体类型
struct Student {
    char name[50];
    int age;
    float score;
};

int main() {
    // 创建结构体变量
    struct Student student1;

    // 为结构体变量的成员赋值
    strcpy(student1.name, "Alice");
    student1.age = 20;
    student1.score = 90.5;

    // 访问结构体变量的成员并打印
    printf("Name: %s\n", student1.name);
    printf("Age: %d\n", student1.age);
    printf("Score: %.2f\n", student1.score);

    // 也可以使用结构体类型来定义函数参数
    void printStudent(struct Student s) {
        printf("Name: %s\n", s.name);
        printf("Age: %d\n", s.age);
        printf("Score: %.2f\n", s.score);
    }

    // 调用函数并传递结构体变量
    printStudent(student1);

    return 0;
}

在上面的例子中,我们首先定义了一个名为Student的结构体类型,它包含三个成员:name(一个字符数组用于存储名字)、age(一个整数用于存储年龄)和score(一个浮点数用于存储分数)。然后,我们创建了一个Student类型的变量student1,并为它的成员赋值。最后,我们打印出这些成员的值,并通过一个函数printStudent来展示如何将结构体作为参数传递给函数。

结构体是C语言中一种非常重要的构造,它们使得代码更加模块化、可读性强,并有助于组织和管理复杂的数据结构。

  • 结构体的成员名可以与程序中其他定义为基本类型的变量名同名,同一个程序中不同结构体的成员也名也可以相同,他们代表的是不同的对象,不会出现冲突。
  • 如果结构体类型的定义在函数内部,则这个类型名的作用域仅为该函数;如果定义域在所有函数的外部,则在整个程序中使用。

结构体变量的定义

先定义结构体,后定义变量:

struct 结构体标识符
{
  数据类型1  成员名1;
  数据类型2  成员名2;
  数据类型3  成员名3;
    ……
  数据类型n  成员名n;
}

void function_one(void){
  
  struct 结构体标识符 结构体变量名;
}

结构体标识符我们可以看成类似于基本数据类型中的int等,·struct 结构体标识符 结构体变量名是声明一个结构体变量

定义结构体的同时定义变量:

struct 结构体标识符
{
    数据类型1  成员名1;
  数据类型2  成员名2;
  数据类型3  成员名3;
    ……
  数据类型n  成员名n;
}变量1,变量2,……,变量n;

在实际运用中,此方法适用于临时定义局部变量或结构体成员变量。,为什么呢?因为这种方法用于定义全局变量相当于在程序运行的时候开辟了新的内存空间,当程序用不到这些变量时依旧存在,直到程序结束后才会被释放。而局部变量在局部程序结束后内存就会被释放。

结构体变量的初始化

在C语言中,结构体变量的初始化可以通过多种方式来完成。以下是一些常见的初始化方式:

1. 在定义时直接初始化

你可以在定义结构体变量时直接初始化其成员。这通常是通过在大括号 {} 内列出成员的值来实现的。

#include <stdio.h>

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

int main() {
    // 在定义时直接初始化
    struct Person person1 = {"Alice", 25};
    
    printf("Name: %s\n", person1.name);
    printf("Age: %d\n", person1.age);
    
    return 0;
}

2. 使用指定初始化器(C99及以后)

从C99标准开始,你可以使用指定初始化器(designated initializer)来初始化结构体的特定成员。这对于大型结构体或只需初始化部分成员的情况非常有用。

#include <stdio.h>

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

int main() {
    // 使用指定初始化器
    struct Person person1 = {
        .name = "Alice",
        .age = 25,
        // height 未被初始化,将保持未定义值
    };
    
    printf("Name: %s\n", person1.name);
    printf("Age: %d\n", person1.age);
    // 注意:未初始化的成员(如height)将包含未定义的值,使用前需确保它们被正确初始化
    
    return 0;
}

3. 通过函数返回值初始化

有时,你可能希望通过函数调用返回的结构体来初始化变量。这要求函数返回一个结构体类型的值。

#include <stdio.h>

struct Point {
    int x;
    int y;
};

struct Point create_point(int x, int y) {
    struct Point p = {x, y};
    return p;
}

int main() {
    // 通过函数返回值初始化
    struct Point p = create_point(10, 20);
    
    printf("Point (%d, %d)\n", p.x, p.y);
    
    return 0;
}

4. 使用复合字面量初始化(C99及以后)

在C99及之后的版本中,你可以使用复合字面量(compound literal)来初始化结构体变量。这种方式允许你临时创建一个匿名结构体实例,并将其用于初始化。

#include <stdio.h>

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

int main() {
    // 使用复合字面量初始化
    struct Person person1 = (struct Person){"Alice", 25};
    
    printf("Name: %s\n", person1.name);
    printf("Age: %d\n", person1.age);
    
    return 0;
}

注意事项

  • 在初始化结构体时,必须确保提供的值的类型和数量与结构体的成员相匹配。
  • 如果某个成员没有被初始化,它将包含未定义的值。对于基本数据类型(如intfloat等),这通常意味着它们将包含垃圾值。对于包含指针的成员,它们可能包含随机的内存地址,这在使用前需要特别注意。
  • 初始化大型结构体或包含大量成员的结构体时,使用指定初始化器可以使代码更清晰、更易于维护。

结构体变量与结构体数组

在C语言中,结构体类型变量用于存储单个结构体的实例,而结构体类型数组则用于存储多个结构体的实例。这两种方式都是处理结构体数据的有效手段,下面将分别介绍它们的使用方法和特点。

结构体类型变量

结构体类型变量是结构体类型的单个实例。创建结构体类型变量时,系统会为其分配足够的内存来存储其所有成员。你可以通过变量名和点运算符(.)来访问和修改其成员的值。

#include <stdio.h>

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

int main() {
    // 创建结构体类型变量
    struct Person person1;

    // 为结构体变量的成员赋值
    strcpy(person1.name, "Alice");
    person1.age = 30;

    // 访问结构体变量的成员并打印
    printf("Name: %s\n", person1.name);
    printf("Age: %d\n", person1.age);

    return 0;
}

在这个例子中,person1 是一个 Person 类型的变量,我们为其 nameage 成员分别赋了值,并打印了它们的值。

结构体类型数组

结构体类型数组用于存储多个相同类型的结构体实例。数组的每个元素都是一个结构体变量,你可以通过数组索引和点运算符(.)来访问和修改其成员的值。

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

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

int main() {
    // 创建结构体类型数组
    struct Person people[3];

    // 为数组中的每个结构体实例的成员赋值
    strcpy(people[0].name, "Alice");
    people[0].age = 25;
    strcpy(people[1].name, "Bob");
    people[1].age = 30;
    strcpy(people[2].name, "Charlie");
    people[2].age = 35;

    // 访问数组中的结构体实例的成员并打印
    for (int i = 0; i < 3; i++) {
        printf("Name: %s\n", people[i].name);
        printf("Age: %d\n", people[i].age);
    }

    return 0;
}

在这个例子中,people 是一个包含三个 Person 类型元素的数组。我们为数组中的每个元素(即每个结构体实例)的 nameage 成员分别赋了值,并使用一个循环来访问和打印它们的值。

注意事项

  • 当创建结构体类型数组时,需要为数组中的每个元素分配内存。因此,数组的大小应该根据实际需要来确定,以避免浪费内存或内存不足的问题。
  • 访问数组中的结构体实例的成员时,要注意数组索引的正确性,以避免越界访问导致的错误。
  • 结构体类型数组和结构体类型变量在内存中的存储方式是相似的,只是数组会连续存储多个结构体实例。因此,它们在使用上的主要区别在于可以处理的数据量的大小和灵活性。

结构体指针

在C语言中,结构体指针是一种特殊类型的指针,用于指向结构体变量或结构体数组的内存地址。通过使用结构体指针,你可以间接地访问和修改结构体变量的成员。

定义结构体指针

定义结构体指针的方式与定义其他类型的指针类似,只需将指针类型指定为相应的结构体类型即可。

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

int main() {
    // 定义结构体变量
    struct Person person = {"Alice", 25};

    // 定义结构体指针
    struct Person *personPtr = &person;

    // 通过指针访问结构体成员
    printf("Name: %s\n", personPtr->name);
    printf("Age: %d\n", personPtr->age);

    return 0;
}

在上面的代码中,personPtr 是一个指向 Person 类型结构体的指针。我们通过取 person 变量的地址 &person 来初始化这个指针。

在C语言中,使用结构体指针给结构体变量赋值可以通过几种不同的方式来实现。这里有几个示例:

示例1:直接通过指针访问成员并赋值

#include <stdio.h>

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

int main() {
    // 定义结构体变量
    struct Person person;
    
    // 定义结构体指针,并指向结构体变量
    struct Person *personPtr = &person;
    
    // 通过指针访问结构体成员并赋值
    strcpy(personPtr->name, "Alice"); // 使用strcpy复制字符串
    personPtr->age = 25;               // 直接给整型成员赋值
    
    // 输出结构体变量的值
    printf("Name: %s\n", person.name);
    printf("Age: %d\n", person.age);
    
    return 0;
}

在这个例子中,我们首先定义了一个 Person 类型的结构体变量 person,然后定义了一个指向 Person 的指针 personPtr,并将其初始化为指向 person。接着,我们使用 -> 运算符通过 personPtr 访问 person 的成员,并使用 strcpy 函数给 name 成员赋值,直接给 age 成员赋值。

示例2:通过另一个结构体变量赋值

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

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

int main() {
    // 定义并初始化一个结构体变量
    struct Person source = {"Bob", 30};
    
    // 定义另一个结构体变量
    struct Person destination;
    
    // 定义指向目标结构体变量的指针
    struct Person *destPtr = &destination;
    
    // 使用指针复制源结构体的值到目标结构体
    strcpy(destPtr->name, source.name);
    destPtr->age = source.age;
    
    // 输出目标结构体变量的值
    printf("Name: %s\n", destPtr->name);
    printf("Age: %d\n", destPtr->age);
    
    return 0;
}

在这个例子中,我们首先定义并初始化了一个 source 结构体变量。然后定义了一个 destination 结构体变量,以及一个指向 destination 的指针 destPtr。接着,我们使用 destPtr 来访问 destination 的成员,并将 source 的成员值复制到 destination 中。

示例3:使用指针直接赋值(如果结构体变量相邻)

如果两个结构体变量在内存中相邻存储,并且你想要复制整个结构体的内容(注意:这样做可能不安全,除非你确定两个变量确实是相邻的),你可以这样做:

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

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

int main() {
    // 定义并初始化一个结构体数组(确保它们是相邻的)
    struct Person people[2] = {
        {"Alice", 25},
        {"Bob", 30}
    };
    
    // 定义指向第一个和第二个结构体变量的指针
    struct Person *firstPersonPtr = &people[0];
    struct Person *secondPersonPtr = &people[1];
    
    // 使用指针直接复制结构体(不安全,仅作为示例)
    memcpy(secondPersonPtr, firstPersonPtr, sizeof(struct Person));
    
    // 输出第二个结构体变量的值,它现在应该是第一个结构体的副本
    printf("Name: %s\n", secondPersonPtr->name);
    printf("Age: %d\n", secondPersonPtr->age);
    
    return 0;
}

在这个例子中,我们定义了一个包含两个 Person 结构体的数组 people。我们使用 memcpy 函数通过指针直接复制整个 firstPerson 结构体的内容到 secondPerson 中。注意,使用 memcpy 进行这种操作是不安全的,除非你完全确定目标内存区域足够大,并且没有与其他重要的数据重叠。在实际编程中,应该尽量避免这样做,除非你完全了解潜在的风险。

通常,更推荐的做法是使用 strcpy 来复制字符串成员,并单独复制其他类型的成员,或者使用标准库函数 memcpy 来复制整个结构体(但仅限于当你知道目标区域足够大且没有重叠时)。如果结构体包含指针或其他复杂类型的成员,直接复制可能会导致问题,因为这些成员指向的数据并没有被复制。在这种情况下,你需要实现一个自定义的复制函数来正确复制结构体

使用结构体指针访问成员

通过结构体指针访问结构体成员时,需要使用 -> 运算符。这个运算符用于解引用指针并访问其指向的结构体的成员。

// 假设 personPtr 是一个指向 Person 类型结构体的指针
printf("Name: %s\n", personPtr->name);
printf("Age: %d\n", personPtr->age);

修改结构体成员的值

你也可以通过结构体指针来修改结构体成员的值。

// 修改通过指针访问的成员的值
personPtr->age = 26;
printf("Updated Age: %d\n", personPtr->age);

在这个例子中,我们通过 personPtr 指针修改了 age 成员的值。

结构体指针和函数

结构体指针在函数传递参数和返回结果时特别有用,因为传递指针通常比传递整个结构体更快,且占用的内存更少(只传递地址而不是整个数据)。

#include <stdio.h>

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

void printPerson(struct Person *personPtr) {
    printf("Name: %s\n", personPtr->name);
    printf("Age: %d\n", personPtr->age);
}

int main() {
    struct Person person = {"Alice", 25};
    printPerson(&person); // 传递结构体变量的地址给函数
    return 0;
}

在这个例子中,printPerson 函数接受一个指向 Person 类型结构体的指针作为参数,并通过这个指针来访问和打印结构体的成员。

注意事项

  • 在使用结构体指针之前,确保它已经被正确地初始化为指向一个有效的结构体变量或数组。未初始化的指针包含垃圾值,解引用这样的指针会导致未定义行为。
  • 当通过函数传递结构体指针时,确保在函数内部不会修改指针本身的值(即不要改变它指向的地址),除非你确实有这样的意图。函数内部通常应该只通过指针来访问和修改它所指向的结构体的内容。

练习为结构体变量赋值

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


void pritect(void){
    
    struct judg_one {
        char exit_condition[5];
        double con_dondition;
    }judg_condition;
    
    strcpy(judg_condition.exit_condition,"exit");
    judg_condition.con_dondition = 0;
    
    printf("%s\n",judg_condition.exit_condition);
    printf("%.2lf\n",judg_condition.con_dondition);
    
}

在为结构体变量中的一个字符串类型赋值时,应该使用strcpy将要赋值给字符串类型的字符串赋值到结构体变量对应的成员中。strcpy的使用应当声明头文件<string.h>

judg_condition.exit_condition表示结构体变量下的exit_condition成员。

关于strcpy

在C语言中,strcpy 是一个标准库函数,用于复制字符串。这个函数定义在 <string.h> 头文件中。strcpy 的功能是将源字符串(包括终止的空字符 '\0')复制到目标字符串中。

函数原型如下:

char *strcpy(char *dest, const char *src);
  • dest 是目标字符串的指针,即你要将源字符串复制到的位置。
  • src 是源字符串的指针,即你要从中复制字符串的位置。

strcpy 函数返回目标字符串 dest 的指针。

这里有一个简单的使用示例:

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

int main() {
    char source[] = "Hello, World!";
    char destination[50];

    // 使用 strcpy 复制字符串
    strcpy(destination, source);

    // 输出复制后的字符串
    printf("Copied string: %s\n", destination);

    return 0;
}

在上面的例子中,source 字符串被复制到 destination 字符串中,然后输出复制后的字符串。

需要注意的是,strcpy 函数不会检查目标字符串 dest 是否有足够的空间来容纳源字符串 src,包括其终止的空字符。因此,如果 dest 的大小小于 src 的大小(不包括终止的空字符),就会发生缓冲区溢出,这可能导致程序崩溃或更严重的安全问题。

为了避免这种情况,你可以使用 strncpy 函数,它允许你指定一个最大字符数来复制,从而防止缓冲区溢出:

char *strncpy(char *dest, const char *src, size_t n);

在这个函数中,n 是最多要复制的字符数(不包括终止的空字符)。如果源字符串的长度小于 n,则目标字符串将用额外的空字符填充,直到总共复制了 n 个字符为止。如果源字符串的长度大于或等于 n,则目标字符串将不会被空字符终止,因此在使用 strncpy 后,通常需要手动添加终止的空字符。

posted @ 2024-04-24 21:59  馨元君  阅读(5)  评论(0编辑  收藏  举报