【C语言】结构体篇

server/2025/3/13 9:53:20/

目录

  • 结构体的定义
  • 结构体变量的声明和初始化
    • 声明结构体变量
    • 初始化结构体变量
  • 访问结构体成员
  • 结构体数组
  • 结构体指针
  • 结构体嵌套
  • 结构体作为函数参数
    • 值传递
    • 指针传递
  • 结构体的内存对齐
  • 位域

结构体的定义

结构体是一种自定义的数据类型,它把不同类型的数据组合成一个整体,方便管理和操作相关的数据。在定义结构体时,使用 struct 关键字,后面跟着结构体的名称,再用花括号 {} 包含结构体的成员列表,每个成员由数据类型和成员名组成,成员之间用分号 ; 分隔。

struct 结构体名 {数据类型 成员1;数据类型 成员2;// 可以有更多成员
};

例如,定义一个描述图书信息的结构体:

struct Book {char title[100];  // 书名,用字符数组存储char author[50];  // 作者,用字符数组存储int year;         // 出版年份,用整数存储float price;      // 价格,用浮点数存储
};

注意事项和细节

  • 结构体名的命名规则:遵循标识符的命名规则,不能与关键字重名,尽量使用有意义的名称,以提高代码的可读性。
  • 结构体定义结束的分号:结构体定义的末尾必须有分号 ;,这是 C 语言语法的要求。
  • 结构体只是一种类型定义:定义结构体本身并不分配内存,只是告诉编译器这种新的数据类型的组成结构。

结构体变量的声明和初始化

声明结构体变量

先定义结构体,再声明变量:这是最常见的方式,先定义好结构体类型,然后在需要使用的地方声明该类型的变量。

struct Book {char title[100];char author[50];int year;float price;
};
struct Book book1;  // 声明一个 Book 类型的变量 book1

在定义结构体的同时声明变量:这种方式在定义结构体的同时就声明了变量,适用于只在当前代码块使用该结构体类型的情况。

struct Movie {char name[80];int duration;
} movie1, movie2;  // 定义 Movie 结构体的同时声明了两个变量 movie1 和 movie2

使用匿名结构体声明变量:匿名结构体没有结构体名,只能在定义时声明变量,之后无法再声明该类型的其他变量,使用场景较少。

struct {int x;int y;
} point;  // 声明一个匿名结构体类型的变量 point

初始化结构体变量

按成员顺序初始化:按照结构体定义中成员的顺序,依次为每个成员赋值。

struct Book book2 = {"C Programming", "John Doe", 2020, 29.99};

指定成员名初始化(C99 及以后支持):可以不按照成员的顺序,通过指定成员名来初始化,提高代码的可读性和可维护性。

struct Book book3 = {.author = "Jane Smith", .title = "Data Structures", .year = 2021, .price = 39.99};

注意事项和细节

  • 成员顺序初始化时的匹配:按成员顺序初始化时,提供的值的类型和顺序必须与结构体成员的类型和顺序一致。
  • 部分初始化:如果只初始化部分成员,未初始化的成员将被自动初始化为 0(对于数值类型)或空字符(对于字符类型)。
struct Book book4 = {"Python Basics"};  // 只初始化了 title 成员,其他成员自动初始化为 0 或空字符
  • 匿名结构体的局限性:匿名结构体无法在其他地方再次使用,因为没有结构体名,不利于代码的复用。

访问结构体成员

使用点运算符 . 来访问结构体变量的成员。点运算符的左边是结构体变量名,右边是结构体的成员名。

#include <stdio.h>
#include <string.h>struct Book {char title[100];char author[50];int year;float price;
};int main() {struct Book book;strcpy(book.title, "Java Programming");  // 给 title 成员赋值strcpy(book.author, "Mark Johnson");     // 给 author 成员赋值book.year = 2019;                        // 给 year 成员赋值book.price = 49.99;                      // 给 price 成员赋值printf("Title: %s\n", book.title);printf("Author: %s\n", book.author);printf("Year: %d\n", book.year);printf("Price: %.2f\n", book.price);return 0;
}

注意事项和细节

  • 成员访问的合法性:只能访问结构体中已经定义的成员,访问不存在的成员会导致编译错误。
  • 字符数组成员的赋值:对于字符数组类型的成员,不能直接使用赋值运算符 = 进行赋值,需要使用 strcpy 等字符串处理函数。

结构体数组

结构体数组是由多个相同结构体类型的元素组成的数组。可以将多个相关的结构体变量组织在一起,方便进行批量处理。

#include <stdio.h>struct Book {char title[100];char author[50];int year;float price;
};int main() {struct Book books[3] = {{"C++ Primer", "Stanley Lippman", 2012, 79.99},{"Effective Java", "Joshua Bloch", 2018, 59.99},{"The Pragmatic Programmer", "Andrew Hunt", 2019, 49.99}};for (int i = 0; i < 3; i++) {printf("Book %d:\n", i + 1);printf("Title: %s\n", books[i].title);printf("Author: %s\n", books[i].author);printf("Year: %d\n", books[i].year);printf("Price: %.2f\n", books[i].price);printf("\n");}return 0;
}

注意事项和细节

  • 数组大小的确定:在声明结构体数组时,需要明确指定数组的大小,或者在初始化时由编译器根据初始化列表的元素个数自动确定。
  • 数组元素的访问:通过数组下标访问结构体数组的元素,再使用点运算符访问元素的成员。
    内存连续性:结构体数组的元素在内存中是连续存储的,这有利于提高访问效率。

结构体指针

可以定义指向结构体的指针,通过指针来访问结构体的成员。使用 -> 运算符来访问指针所指向结构体的成员,-> 运算符是 (*指针).成员 的简写形式。

#include <stdio.h>
#include <string.h>struct Book {char title[100];char author[50];int year;float price;
};int main() {struct Book book = {"JavaScript: The Definitive Guide", "David Flanagan", 2020, 69.99};struct Book *p = &book;  // 定义一个指向 Book 结构体的指针 p,并指向 book 变量printf("Title: %s\n", p->title);printf("Author: %s\n", p->author);printf("Year: %d\n", p->year);printf("Price: %.2f\n", p->price);return 0;
}

注意事项和细节

  • 指针的初始化:在使用结构体指针之前,必须先将其初始化为一个有效的结构体变量的地址,否则会导致未定义行为。
  • -> 运算符的使用:-> 运算符用于通过指针访问结构体成员,避免了使用 (*指针).成员 这种较为繁琐的写法。
  • 动态内存分配:可以使用 malloc、calloc 等函数动态分配结构体的内存,并使用指针来管理。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>struct Book {char title[100];char author[50];int year;float price;
};int main() {struct Book *p = (struct Book *)malloc(sizeof(struct Book));if (p == NULL) {printf("Memory allocation failed!\n");return 1;}strcpy(p->title, "Python Crash Course");strcpy(p->author, "Eric Matthes");p->year = 2015;p->price = 39.99;printf("Title: %s\n", p->title);printf("Author: %s\n", p->author);printf("Year: %d\n", p->year);printf("Price: %.2f\n", p->price);free(p);  // 释放动态分配的内存return 0;
}

结构体嵌套

结构体可以嵌套,即一个结构体的成员可以是另一个结构体类型。通过结构体嵌套,可以更复杂地组织数据。

#include <stdio.h>struct Date {int year;int month;int day;
};struct Book {char title[100];char author[50];struct Date publishDate;  // 嵌套 Date 结构体float price;
};int main() {struct Book book = {"The C Programming Language","Brian Kernighan",{1978, 2, 22},  // 初始化嵌套的 Date 结构体29.99};printf("Title: %s\n", book.title);printf("Author: %s\n", book.author);printf("Publish Date: %d-%d-%d\n", book.publishDate.year, book.publishDate.month, book.publishDate.day);printf("Price: %.2f\n", book.price);return 0;
}

注意事项和细节

  • 嵌套结构体的初始化:在初始化包含嵌套结构体的结构体变量时,需要按照嵌套的层次依次初始化。
  • 成员访问的层次:访问嵌套结构体的成员时,需要使用多个点运算符,从外层结构体逐步访问到内层结构体的成员。
  • 内存布局:嵌套结构体的内存布局是按照成员的定义顺序依次分配的,嵌套结构体的成员也遵循内存对齐的规则。

结构体作为函数参数

值传递

将结构体变量的值复制一份传递给函数,函数内部对参数的修改不会影响原结构体变量。

#include <stdio.h>struct Point {int x;int y;
};// 函数接受一个 Point 结构体作为参数
void printPoint(struct Point p) {printf("Point: (%d, %d)\n", p.x, p.y);
}int main() {struct Point p = {3, 4};printPoint(p);  // 传递结构体变量 p 的值给函数return 0;
}

注意事项和细节

  • 内存开销:值传递会复制整个结构体变量,当结构体较大时,会占用较多的内存和时间。
  • 数据的独立性:函数内部对参数的修改不会影响原结构体变量,保证了数据的独立性。

指针传递

将结构体变量的地址传递给函数,函数内部可以通过指针修改原结构体变量的值。

#include <stdio.h>struct Point {int x;int y;
};// 函数接受一个指向 Point 结构体的指针作为参数
void movePoint(struct Point *p, int dx, int dy) {p->x += dx;p->y += dy;
}int main() {struct Point p = {3, 4};movePoint(&p, 1, 2);  // 传递结构体变量 p 的地址给函数printf("New Point: (%d, %d)\n", p.x, p.y);return 0;
}

注意事项和细节

  • 内存开销小:指针传递只传递结构体变量的地址,不复制整个结构体,内存开销小。
  • 数据的修改:函数内部可以通过指针修改原结构体变量的值,需要注意避免意外修改数据。
  • 指针的有效性:在函数内部使用指针时,需要确保指针指向的是有效的结构体变量,避免空指针引用。

结构体的内存对齐

结构体的成员在内存中并不是连续存储的,编译器会根据成员的类型和平台的要求进行内存对齐,以提高访问效率。内存对齐的规则如下:

  • 结构体的第一个成员的偏移量为 0。
  • 每个成员的偏移量必须是该成员类型大小的整数倍。
  • 结构体的总大小必须是最大成员类型大小的整数倍。
#include <stdio.h>struct Example {char c;  // 1 字节int i;   // 4 字节char d;  // 1 字节
};int main() {printf("Size of struct Example: %zu\n", sizeof(struct Example));return 0;
}

在这个例子中,由于内存对齐的原因,struct Example 的大小可能不是 6 字节(1 + 4 + 1),而是 12 字节。char c 从偏移量 0 开始存储,占 1 个字节;int i 由于偏移量必须是 4 的整数倍,所以从偏移量 4 开始存储,占 4 个字节;char d 从偏移量 8 开始存储,占 1 个字节;为了满足结构体总大小是最大成员类型大小(4 字节)的整数倍,结构体的总大小为 12 字节。

注意事项和细节

  • 内存浪费:内存对齐会导致一定的内存浪费,特别是当结构体中包含不同大小的成员时。
  • 平台相关性:不同的平台和编译器可能有不同的内存对齐规则,因此结构体的大小可能会有所不同。
  • 调整对齐方式:可以使用 #pragma pack 指令来调整结构体的对齐方式,但这可能会影响访问效率。

位域

位域允许在一个结构体中以位为单位来指定成员所占的存储空间,用于节省内存。

#include <stdio.h>struct Flags {unsigned int flag1 : 1;  // 占 1 位unsigned int flag2 : 1;  // 占 1 位unsigned int flag3 : 1;  // 占 1 位unsigned int flag4 : 1;  // 占 1 位
};int main() {struct Flags f;f.flag1 = 1;f.flag2 = 0;f.flag3 = 1;f.flag4 = 0;printf("Flag1: %d\n", f.flag1);printf("Flag2: %d\n", f.flag2);printf("Flag3: %d\n", f.flag3);printf("Flag4: %d\n", f.flag4);return 0;
}

在这个例子中,struct Flags 的四个成员 flag1、flag2、flag3 和 flag4 各占 1 位,总共只占 1 个字节的存储空间。

注意事项和细节

  • 位域的类型:位域的类型必须是 int、unsigned int 或 signed int,有些编译器也支持 char 类型。
  • 位域的宽度:位域的宽度不能超过其类型的大小,例如 unsigned int 类型的位域宽度不能超过 32 位。
  • 位域的可移植性:位域的实现可能因编译器和平台而异,因此位域的使用可能会影响代码的可移植性。
  • 未命名位域:可以使用未命名的位域来填充字节,以达到特定的对齐要求。
struct Example {unsigned int flag1 : 1;unsigned int : 3;  // 未命名位域,占 3 位unsigned int flag2 : 1;
};

http://www.ppmy.cn/server/174598.html

相关文章

AI对前端开发的冲击

Cursor cursor新版本0.46版本号中有部分是改成了新布局其实 Agent 和 Edit 和 Composer 是一样的&#xff0c;为了方便大家使用&#xff0c;我们把它们合并了&#xff0c;Edit 相当于普通模式下的 Composer&#xff0c;Agent 就是代理模式。 快捷键ctrli、ctrll、ctrlk 4o适合…

SIP 协议详解:原理、用途与应用场景

1. SIP 协议简介 SIP&#xff08;Session Initiation Protocol&#xff0c;会话初始化协议&#xff09;是一个应用层协议&#xff0c;属于计算机网络的七层模型&#xff08;OSI 模型&#xff09;中的第七层。在计算机网络中&#xff0c;OSI 参考模型将网络通信划分为以下 7 层…

升级DDR5后,为何游戏性能不增反降?

随着 AMD Zen 4 CPU 开始彻底淘汰 DDR4&#xff0c;以及 Intel 12-14 代酷睿、酷睿Ultra系列全面支持 DDR5 内存。 我们不得不承认&#xff0c;属于 DDR5 内存的时代它已经来了&#xff01; 相较前几年面世初期的价格高昂、性能拉胯、产品丰富度不足等&#xff0c;现如今DDR5内…

报表控件stimulsoft操作:使用 Angular 应用程序的报告查看器组件

Stimulsoft Ultimate &#xff08;原Stimulsoft Reports.Ultimate&#xff09;是用于创建报表和仪表板的通用工具集。该产品包括用于WinForms、ASP.NET、.NET Core、JavaScript、WPF、PHP、Java和其他环境的完整工具集。无需比较产品功能&#xff0c;Stimulsoft Ultimate包含了…

赛逸展2025 亮点前瞻,众多前沿科技将集中亮相

2025第七届亚洲消费电子技术贸易展&#xff08;赛逸展&#xff09;作为亚洲极具影响力的消费电子展会&#xff0c;一直是展示前沿科技成果的重要平台。2025 年&#xff0c;赛逸展将再次汇聚全球顶尖科技企业&#xff0c;众多前沿科技成果将集中亮相。 “创新材料展区” 将展示一…

【优选算法】二分法(总结套路模板)

目录 1. 题目一 &#xff1a;二分查找 解题思路&#xff1a; 模板总结&#xff08;简单版&#xff0c;不适用所有情况&#xff09; 代码实现&#xff1a; 2. 题目二 解题思路&#xff1a; 模板总结&#xff08;几乎万能&#xff09; 代码实现&#xff1a; 3. 题目…

关于WPS的Excel点击单元格打开别的文档的两种方法的探究

问题需求 目录和文件结构如下&#xff1a; E:\Dir_Level1 │ Level1.txt │ └─Dir_Level2│ Level2.txt│ master.xlsx│└─Dir_Level3Level3.txt现在要在master.xlsx点击单元格进而访问Level1.txt、Level2.txt、Level3.txt这些文件。 方法一&#xff1a;“单元格右键…

【C++指南】一文总结C++类和对象【下】

&#x1f31f; 各位看官好&#xff0c;我是egoist2023&#xff01; &#x1f30d; 种一棵树最好是十年前&#xff0c;其次是现在&#xff01; &#x1f680; 今天来学习C类和对象的语法知识。 &#x1f44d; 如果觉得这篇文章有帮助&#xff0c;欢迎您一键三连&#xff0c;分享…