初识C语言12. 结构体(自定义类型的核心工具)

目录

前言:

一、结构体:从"声明"到"初始化"的基础逻辑

[1.1 定义类型时直接创建变量:](#1.1 定义类型时直接创建变量:)

[1.2 先声明类型,再创建变量:](#1.2 先声明类型,再创建变量:)

[1.3 匿名结构体(仅用于一次性变量):](#1.3 匿名结构体(仅用于一次性变量):)

二、结构体的核心操作:成员访问与传参

[2.1 成员访问: . 与 -> 的区别](#2.1 成员访问: . 与 -> 的区别)

[2. 2结构体传参:值传递 vs 地址传递](#2. 2结构体传参:值传递 vs 地址传递)

三、底层细节:内存对齐与位段

[3.1 对齐规则:](#3.1 对齐规则:)

[3.2 修改对齐数](#3.2 修改对齐数)

[四、 结构体实现位段](#四、 结构体实现位段)

[4.1 什么是位段](#4.1 什么是位段)

[4.2 位段的内存分配](#4.2 位段的内存分配)

[4.3 位段的跨平台问题](#4.3 位段的跨平台问题)

总结:


前言:

在C语言的编程体系中,"结构体"是实现自定义复合数据类型的核心机制,它让开发者能够将不同类型的数据封装为一个逻辑整体,是构建复杂数据结构(如链表、树)与贴近现实场景数据模型的基础。本文将结合"自定义类型"的学习框架,拆解结构体的核心用法与底层逻辑。

一、结构体:从"声明"到"初始化"的基础逻辑

结构体的使用首先从类型声明开始------通过 struct 关键字定义新的类型模板,例如描述学生信息的结构体:

复制代码
// 声明结构体类型
struct Student {
    char name[20];  // 字符数组存储姓名
    int age;        // 整型存储年龄
    float score;    // 浮点型存储成绩
};

声明仅定义"模板",需创建变量并初始化才能实际存储数据(此时并没有开辟新的空间)

1.1 定义类型时直接创建变量:

复制代码
struct Student {
    char name[20];
    int age;
    float score;
} stu1 = {"Zhang", 18, 92.5};  // 同时创建并初始化变量stu1

1.2 先声明类型,再创建变量:

复制代码
struct Student stu2;
// 逐个成员赋值
strcpy(stu2.name, "Li");
stu2.age = 19;
stu2.score = 88.0;

1.3 匿名结构体(仅用于一次性变量):

复制代码
struct {
    int x;
    int y;
} point = {3, 4};  // 无类型名,仅能在此处创建变量

二、结构体的核心操作:成员访问与传参

2.1 成员访问: . 与 -> 的区别

访问结构体成员需使用成员访问操作符:

  • 当操作结构体变量时,用 (.):

    printf("姓名:%s\n", stu1.name); // 输出"Zhang"

  • 当操作结构体指针时,用 (-> ):

    struct Student *p = &stu2;
    printf("年龄:%d\n", p->age); // 输出19

数据在上面创建中已经初始化

2. 2结构体传参:值传递 vs 地址传递

结构体作为函数参数传递时,有两种方式:

  • 值传递:将整个结构体拷贝一份传入函数,修改不会影响原变量(内存开销大):

    void printStudent(struct Student s) {
    printf("成绩:%.1f\n", s.score);
    }
    printStudent(stu1); // 传入stu1的拷贝

  • 地址传递:传入结构体指针,修改会影响原变量(内存开销小):

    void updateScore(struct Student *p, float newScore) {
    p->score = newScore;
    }
    updateScore(&stu2, 90.0); // 修改stu2的成绩为90.0

实际开发中优先选择地址传递,尤其是结构体成员较多时。

三、底层细节:内存对齐与位段

结构体的内存布局并非简单的成员大小相加,而是遵循内存对齐规则------这是编译器为了提升CPU访问效率的优化策略:

3.1 对齐规则:

首先得掌握结构体的对齐规则:

  1. 结构体的第⼀个成员对齐到和结构体变量起始位置偏移量为0的地址处 2

. 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。 对齐数 = 编译器默认的⼀个对齐数 与 该成员变量大小的较齐值。

  • VS 中默认的值为 8 -

Linux中 gcc 没有默认对齐数,对齐数就是成员自身的大小

  1. 结构体总大小为最大对齐数(结构体中每个成员变量都有⼀个对齐数,所有对齐数中最大的)的整数倍。

  2. 如果嵌套了结构体的情况,嵌套的结构体成员对齐到自己的成员中最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体中成员的对齐数)的整数倍。

    struct Demo {
    char a; // 占1字节,偏移0
    int b; // 占4字节,偏移4(补3字节)
    char c; // 占1字节,偏移8(补3字节)
    };
    // 总大小:12字节(而非1+4+1=6字节)

3.2 修改对齐数

可通过 #pragma pack(n) 指定对齐数,以平衡内存效率与空间占用。

若需进一步节省内存,可使用结构体位段------通过指定成员占用的二进制位数实现精细化存储:

复制代码
#include <stdio.h>
#pragma pack(1)//设置默认对⻬数为1
struct S
{
char c1;
int i;
char c2;
};
#pragma pack()//取消设置的对⻬数,还原为默认
int main()
{
//输出的结果是什么?
printf("%d\n", sizeof(struct S));
return 0;
}

四、 结构体实现位段

4.1 什么是位段

位段的声明和结构是类似的,有两个不同:

  1. 位段的成员必须是 int、unsigned int 或signed int ,在C99中位段成员的类型也可以选择其他类型。

  2. 位段的成员名后边有⼀个冒号和⼀个数字

    struct BitField {
    int a : 2; // 占用2位
    int b : 5; // 占用5位
    int c : 10; // 占用10位
    };

4.2 位段的内存分配

  1. 位段的成员可以是 int unsigned int signed int 或者是 char 等类型

  2. 位段的空间上是按照需要以4个字节( int )或者1个字节( char )的方式来开辟的。

  3. 位段涉及很多不确定因素,位段是不跨平台的,注重可移植的程序应该避免使用位段。

4.3 位段的跨平台问题

  1. int 位段被当成有符号数还是无符号数是不确定的。

  2. 位段中最大位的数目不能确定。(16位机器最大16,32位机器最大32,写成27,在16位机器会

出问题。

  1. 位段中的成员在内存中从左向右分配,还是从右向左分配,标准尚未定义。

  2. 当⼀个结构包含两个位段,第⼆个位段成员比较大,无法容纳于第⼀个位段剩余的位时,是舍弃

剩余的位还是利用,这是不确定的。

总结:

跟结构相比,位段可以达到同样的效果,并且可以很好的节省空间,但位段的跨平台兼容性较差(不同编译器的存储规则可能不同),需谨慎使用

总结:

结构体是C语言脱离"单一基础类型"、实现灵活数据组织的关键工具,其语法虽简单,但内存对齐、传参方式等细节直接影响程序的效率与稳定性。掌握结构体,是从"基础语法"迈向"实际开发"的重要一步。

相关推荐
刚入坑的新人编程4 小时前
算法训练.17
开发语言·数据结构·c++·算法
汤姆yu4 小时前
基于python大数据深度学习的酒店评论文本情感分析
开发语言·python·深度学习
米饭不加菜4 小时前
typora的基本用法
笔记
狂团商城小师妹4 小时前
JAVA无人共享台球杆台球柜系统球杆柜租赁系统源码支持微信小程序
java·开发语言·微信小程序·小程序
Fortunate Chen4 小时前
初识C语言13.自定义类型(联合体与枚举)
c语言·开发语言
麦麦鸡腿堡4 小时前
Java的抽象类实践-模板设计模式
java·开发语言·设计模式
im_AMBER4 小时前
React 03
前端·笔记·学习·react.js·前端框架·react
云知谷4 小时前
【经典书籍】《编写可读代码的艺术》精华
开发语言·c++·软件工程·团队开发
空空kkk4 小时前
Java——接口
java·开发语言·python