【C语言学习】——结构体

5. 结构体

5.1 基本概念

1. 什么是结构体?

​ 编程最重要步骤之一就是选择合适的数据表示方式。C语言提供了许多内置基本变量类型(譬如:char、short、int、long、float、double)和数组等。实际应用中仅仅这些类型难以表示复杂的对象,例如一个班集体、一个公司等包含多种数据类型。

​ C语言提供了自定义数据类型——结构变量(structure variable),用于组织和存储不同类型数据。总结:

  1. 结构体是一种构造的数据类型,可以把不同类型的数据组合成一个整体进行处理;类似于class
  2. 多个成员组成: 结构体由多个成员组成,每个成员可以是不同的数据类型。

2. 创建结构体

创建结构体包括:创建结构体模版(声明)、定义结构体变量和初始化结构体变量

① 创建结构体模版(声明)
struct 结构体名
{
   	menber_list;
    //结构体的成员列表(基本的数据类型,指针,数组等);
}veriable_list;


// 结构体声明(结构体模板)
struct Person 
{
    char name[50];
    int age;
    float height;
};

printf("Size of struct Person is %d bytes.\n:",sizeof(struct Person));
//Size of struct Person is 60 bytes.
  1. 上述声明了一个由一个字符数组name[50]、一个float和一个int变量组成的结构;
  2. 描述了结构体如何存储数据(注意还没有真正的存储在内存,即还没真正的分配内存空间)。
  3. 结构体的 sizeof 只是在编译时计算结构体的大小,而不涉及实际的内存分配。在运行时,只有创建结构体实例时,系统才会为其分配内存。
  4. 为什么不是58bytes?因为编译器进行了内存对齐

② 定义结构体变量

此时,定义变量即会分配内存!

//创建结构体模板,再声明变量
struct Person 
{
    char name[50];
    int age;
    float height;
};
struct Person person;
struct Person *Ptperson;//指向结构体Person类型的指针变量


//创建结构体模板,接着声明变量
struct Person 
{
    char name[50];
    int age;
    float height;
}person, *Ptperson;

printf("Size of struct person is: %d bytes.\n", sizeof(person));
printf("Size of struct Ptperson is: %d bytes.\n", sizeof(Ptperson));//指向结构体Person类型的指针变量

//Size of struct person is: 60 bytes.
//Size of struct Ptperson is: 4 bytes.

③ 初始化结构体变量
//创建结构体模板,实例化普通变量
struct Person 
{
    char name[50];
    int age;
    float height;
}person = {"Andrew", 18, 178};

​ 初始化指向结构体的指针变量:

//创建结构体模板,实例化指针变量
struct Person 
{
    char name[50];
    int age;
    float height;
};

/********方案1********/
struct Person *Ptperson;
Ptperson = (struct Person*)malloc(sizeof(struct Person)); // 分配内存
// 检查内存分配是否成功
if (Ptperson == NULL) 
{
    printf("Memory allocation failed.\n");
    return 1;
}

strcpy(Ptperson->name, "Andrew"); 
(*Ptperson).age = 18;
Ptperson->height = 1.78;

printf("Name: %s\n", Ptperson->name);
printf("Age: %d\n", Ptperson->age);
printf("Height: %.2f meters\n", Ptperson->height);

// 释放分配的内存
free(Ptperson);
Ptperson = NULL;

/*
Name: Andrew
Age: 18
Height: 1.78 meters
*/



/********方案2********/
struct Person *Ptperson;
struct Person temp = {"Andrew", 18, 178};
Ptperson = &temp; // 将结构体变量的地址赋值给指针
④ 结构体成员为字符指针

​ 上述代码修改如下:

struct Person 
{
    char *name;
    int age;
    float height;
};

// 创建结构体变量
struct Person person1;


// 分配内存并为指针成员赋值
person1.name = (char*)malloc(50 * sizeof(char));
strcpy(person1.name, "WANG");
person1.age = 20;
person1.height = 1.76;

//打印结果
printf("Name: %s\n", person1.name);
printf("Age: %d\n", person1.age);
printf("Height: %.2f meters\n", person1.height);
printf("\n");

//计算大小
printf("Size of struct person is: %d bytes.\n", sizeof(person1));

//释放内存
free(person1.name);

/*
Name: WANG
Age: 20
Height: 1.76 meters

Size of struct person1 is: 12 bytes.
*/

​ 结构体大小由60 bytes变为12 bytes,字符串使用指针效率更加高效和节约空间;

3. 访问结构体成员

​ 访问结构体成员可以使用成员访问运算符(.)或指针运算符(->),具体的使用取决于是直接操作结构体变量还是结构体指针;

①. 使用.操作符(结构体变量)
struct Person {
……
};
struct Person person1 = {"John", 25, 1.75};
// 访问结构体成员
printf("Name: %s\n", person1.name);
printf("Age: %d\n", person1.age);
printf("Height: %.2f meters\n", person1.height);
②. 使用->操作符(结构体指针)

有指向结构体的指针,应该使用->操作符来访问结构体成员。

struct Person {
……
};
struct Person *Ptperson;

//初始化……

// 访问结构体成员
printf("Name: %s\n", personPtr->name);
printf("Age: %d\n", personPtr->age);
printf("Height: %.2f meters\n", personPtr->height);

4. 匿名结构体

即不完全声明结构体,只能使用一次:

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

5. 使用typedef定义创建结构体

typedef struct Person 
{
    char name[50];
    int age;
    float height;
}Person, *Ptperson;

Person person1 = {"John", 25, 1.75};
Ptperson ptperson1 = &person1;

//or
typedef struct
{
    char name[50];
    int age;
    float height;
}Person, *Ptperson;//匿名结构体

注意:Person并不是创建的变量,而是对于struct Person的重命名,

同样:Ptperson并不是创建的变量,而是对于struct Person *的别名,是类型名。后续可按照需求使用:Person来创建变量也可以用struct Person来创建。

使用typedef方式的好处在于:

  1. 更简洁:不需要在每次声明结构体变量时都使用 struct 关键字。
  2. 更具可读性:通过typedef为结构体定义一个新的名称,可以使代码更清晰易读。

6. 嵌套结构体

​ 在一个结构体内部包含另一个结构体,这样一个结构体中组织和存储更为复杂的数据结构;

#include <stdio.h>

// 定义日期结构体
struct Date 
{
    int day;
    int month;
    int year;
};

// 定义学生结构体,包含日期结构体作为成员
struct Student 
{
    char name[50];
    int age;
    struct Date birthdate; // 嵌套结构体
};

int main() 
{
    // 声明并初始化嵌套结构体
    struct Student student1 = {"Alice", 20, {15, 5, 2002}};

    // 访问嵌套结构体成员
    printf("Name: %s\n", student1.name);
    printf("Age: %d\n", student1.age);
    printf("Birthdate: %d/%d/%d\n", student1.birthdate.day, student1.birthdate.month, student1.birthdate.year);

    return 0;
}

注意:

使用多次.操作符,用以访问嵌套结构体成员。

例如 student1.birthdate.day 表示学生的出生日期中的天数,student1.address.street 表示学生的地址中的街道名。

5.2 结构体和数组

​ 结构数组提供了一种有效的方式来组织和管理相似数据,使得代码更加模块化和易于维护。

1. 声明结构数组

#define MAXPEOPLE
// 定义一个简单的结构体
struct Person {
    char name[50];
    int age;
    float height;
};

// 声明结构体数组
struct Person people[MAXPEOPLE];

注意1:

struct Person people[MAXPEOPLE] 是在栈上静态分配的,编译器在编译时就知道要为它分配多少内存。

这种方式适用于相对较小的数组,且内存的分配和释放是隐式的,由编译器自动处理。

// 动态分配内存来存储结构体数组
struct Person *people = (struct Person *)malloc(3 * sizeof(struct Person));

if (people == NULL)
{
	printf("Memory allocation failed.\n");
	return 1;
}

free(people);

注意2:

用了 malloc 函数动态分配了足够的内存来存储结构体数组。

这种方式适用于需要在运行时确定数组大小,或者需要较大内存空间的情况。

动态分配的内存需要手动释放,以避免内存泄漏。

2. 初始化结构数组

// 静态分配内存,初始化结构体数组
struct Person people[MAXPEOPLE] = 
{
    {"Alice", 25, 1.65},
    {"Bob", 30, 1.75},
    {"LaoW", 20, 1.78},
    {"ZhangS", 50, 1.95},
    // ... 继续初始化其他元素
};

动态分配如下:

struct Person *people = (struct Person *)malloc(MAXPEOPLE * sizeof(struct Person));

if (people == NULL) 
{
	fprintf(stderr, "Memory allocation failed.\n");
	return 1;
}

// 初始化结构体数组
for (int i = 0; i < MAXPEOPLE; ++i)
{
	sprintf(people[i].name, "Person %d", i + 1);
	people[i].age = 20 + i;
	people[i].height = 1.70 + i * 0.01;
}

free(people);

3. 使用结构体数组

动态分配内存:

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

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

int main() {
    int num_people;

    // 获取人数
    printf("Enter the number of people: ");
    scanf("%d", &num_people);

    // 动态分配内存
    struct Person* people = (struct Person*)malloc(num_people * sizeof(struct Person));

    if (people == NULL) 
    {
        fprintf(stderr, "Memory allocation failed.\n");
        return 1;
    }

    // 获取每个人的信息
    for (int i = 0; i < num_people; ++i) 
    {
        printf("Enter details for Person %d:\n", i + 1);
        printf("Name: ");
        scanf("%s", people[i].name);
        printf("Age: ");
        scanf("%d", &people[i].age);
        printf("Height: ");
        scanf("%f", &people[i].height);
    }

    // 打印结果
    for (int i = 0; i < num_people; ++i) 
    {
        printf("Person %d:\n", i + 1);
        printf("Name: %s\n", people[i].name);
        printf("Age: %d\n", people[i].age);
        printf("Height: %.2f meters\n", people[i].height);
        printf("\n");
    }

    // 释放动态分配的内存
    free(people);

    return 0;
}

非动态分配内存:

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

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

int main() 
{
    // 初始化结构体数组并直接赋值
    struct Person people[5] = 
    {
        {"Alice", 25, 1.65},
        {"Bob", 30, 1.75},
        {"LaoW", 20, 1.78},
        {"ZhangS", 50, 1.95},
        {"Eve", 28, 1.78}
    };

    // 打印结果
    for (int i = 0; i < 5; ++i) 
    {
        printf("Person %d:\n", i + 1);
        printf("Name: %s\n", people[i].name);
        printf("Age: %d\n", people[i].age);
        printf("Height: %.2f meters\n", people[i].height);
        printf("\n");
    }

    return 0;
}

5.3 结构体和指针

指向结构体的指针一般比结构体本身,更容易操控,效率更高。

1. 声明和初始化结构体指针

结构体指针可以指向已有的结构体变量;

typedef struct Person 
{
    char name[50];
    int age;
    float height;
}Person, *Ptperson;

Person person1 = {"John", 25, 1.75};
Ptperson ptperson1 = &person1; //指向结构体变量

也可以指向新的结构体变量;

ptperson1 = (struct Person *)malloc(sizeof(struct Person));//Ptperson ptperson1 = (Ptperson)malloc(sizeof(struct Person));

//使用后
free(ptperson1);

2. 使用结构体指针

typedef struct Person
{
    char name[50];
    int age;
    float height;
}Person, *Ptperson;

int main()
{

    Person people[5] =
    {
        {"Alice", 25, 1.65},
        {"Bob", 30, 1.75},
        {"LaoW", 20, 1.78},
        {"ZhangS", 50, 1.95},
        {"Eve", 28, 1.78}
    };
    
    //创建结构体指针
    Ptperson ptperson1;
    
    //指向&people[0]
    ptperson1 = &people[0];
   
    // 打印结果
    for (int i = 0; i < 5; ++i)
    {
        printf("Person %d:\n", i + 1);
        printf("Address of people %d is: %p\n", i + 1, &people[i]);
        
        //指针访问符
        printf("Name: %s\n", ptperson1->name);
        printf("Age: %d\n", (*ptperson1).age);
        printf("Height: %.2f meters\n", ptperson1->height);
        printf("\n");
        
        //指向下一个结构体
        ptperson1++;
    }

    return 0;
}

/*运行结果
Person 1:
Address of people 1 is: 012FF90C
Name: Alice
Age: 25
Height: 1.65 meters

Person 2:
Address of people 2 is: 012FF948
Name: Bob
Age: 30
Height: 1.75 meters

Person 3:
Address of people 3 is: 012FF984
Name: LaoW
Age: 20
Height: 1.78 meters

Person 4:
Address of people 4 is: 012FF9C0
Name: ZhangS
Age: 50
Height: 1.95 meters

Person 5:
Address of people 5 is: 012FF9FC
Name: Eve
Age: 28
Height: 1.78 meters
*/

5.4 结构体和函数

函数的参数把值传给函数,这个值可以是:基本的数据类型(int、float型等)一个地址等;

1. 结构体值传递

​ 值传递会拷贝副本,形参改不了实参;

① 结构体
struct Person 
{
    char name[50];
    int age;
    float height;
};

void printPerson(struct Person person) 
{
    printf("Name: %s\n", person.name);
    printf("Age: %d\n", person.age);
    printf("Height: %.2f\n", person.height);
}

int main() 
{
    struct Person person1 = {"Chris", 25, 1.75};
    printPerson(person1);
    return 0;
}
② 结构体成员
struct Person
{
    char name[50];
    int age;
    float height;
};

int addPersonAge(int a, int b)
{
    return a + b;
}


int main()
{
    struct Person person1 = { "Chris", 25, 1.75 };
    struct Person person2 = { "WANG", 24, 1.78 };
    int addAge = addPersonAge(person1.age, person2.age);
    printf("sum of age: %d", addAge);
    return 0;
}

2. 结构体作函数返回值

​ 函数也可以返回结构体,这样可以将多个相关的数据打包在一起返回。

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

struct Person createPerson(char name[], int age, float height) 
{
    struct Person newPerson;
    strcpy(newPerson.name, name);
    newPerson.age = age;
    newPerson.height = height;
    return newPerson;
}

int main() 
{
    // 调用函数,获取返回的结构体
    struct Person person2 = createPerson("Bob", 30, 1.75);

    // 打印结构体成员的值
    printf("Name: %s\n", person2.name);
    printf("Age: %d\n", person2.age);
    printf("Height: %.2f\n", person2.height);
    
    return 0;
}

3. 结构体地址传递

​ 如果结构体很大,传递结构体的副本可能会导致性能问题。这时候可以传递结构体的指针。

但是注意:使用值传递可以保护原数据不被修改;

使用指针传递参数,若要防止意外被修改,可以加上const关键字;

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

void printPersonPtr(const struct Person *personPtr) 
{
    printf("Name: %s\n", personPtr->name);
    printf("Age: %d\n", personPtr->age);
    printf("Height: %.2f\n", personPtr->height);
}

int main() {
    struct Person person3 = {"Charlie", 28, 1.80};
    const struct Person *ptrPerson3 = &person3;
    printPersonPtr(ptrPerson3);
    
    return 0;
}

5.5 结构体内存对齐

1. 为什么有结构体内存对齐?

  • 结构体内存对齐是指编译器为了提高存取效率,按照一定规则在结构体中插入一些填充字节(padding bytes)的过程。
  • 在计算机中,内存访问通常是按照特定的规则进行的,比如字节对齐(byte alignment)。字节对齐要求数据类型的起始地址必须是其大小的整数倍。例如,一个4字节的整型变量需要以4字节的边界对齐,而一个8字节的双精度浮点数需要以8字节的边界对齐。
  • 结构体是由多个成员变量组成的复合数据类型。如果结构体的成员变量没有按照规定的对齐方式排列,可能会导致内存空间的浪费和访问速度变慢。当成员变量没有对齐时,CPU 需要额外的操作来读取或写入这些数据,这增加内存访问的时间和开销。
  • 通过进行结构体内存对齐,可以将结构体的各个成员变量按照对齐规则排列,避免内存浪费,并使得 CPU 在读取和写入结构体数据时更加高效。

2. 对齐规则

  1. 结构体的第1个成员,存放在结构体变量开始位置的0偏移量(第一个字节处)处;
  2. 【关于偏移量】从第2个成员开始,都要对齐到**对齐数的整数倍地址(偏移量)**处;
  3. 【关于结构体总字节大小】结构体的总大小必须是最大对齐数的整数倍
  4. 【如果存在嵌套结构体】嵌套的结构体(结构体中的那个结构体)对齐到自己的最大对齐数的整数倍处。
  • 结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
  • 对齐数 = 编译器默认的对齐数结构体成员大小较小值
  • 不是所有的编译器都有默认对齐数,VS 中的默认对齐数为 8,Linux 的GCC编译器则没有默认对齐数,当编译器没默认对齐数时,成员变量的大小就是该成员的对齐数
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值