【C语言】自定义类型 —— 结构体

简介: 【C语言】自定义类型 —— 结构体

0. 前言


大家好,我是anduin。今天为大家带来的是结构体的详细讲解。在C语言中,结构体可谓是很重要的一块内容,特别是在学习数据结构时,结构体更发挥了极大的作用。而本篇博客,我们将对结构体的基础知识和结构体内存对齐等知识作出详细讲解。话不多说,我们这就开始。




1. 结构的基础知识


结构是一些值的集合,这些值称为成员变量。结构的每个成员可以是不同类型的变量。




2. 结构的声明


struct tag//结构体标签(自定义)
{
  member-list;//成员列表
}variable-list;//变量列表(全局变量)


例如描述一个学生:

struct Stu
{
    char name[20];//名字
    int age;//年龄
    char sex[5];//性别
    char id[20];//学号
};


这里和上方结构的声明不太一样,少了变量列表,这个和我们本身创建变量有关,在变量列表中创建变量,就为全局变量,在主函数中创建变量就是局部变量,变量列表是可以省略的。



3 结构体创建变量


struct Book
{
  char book_name[20];
  char author[20];
  int price;
  char id[15];
}s3,s4;
//sb3 sb4也是struct Book类型的结构体变量
//是全局变量
int main()
{
  struct Book s1;  //局部变量
  struct Book s2;  //局部变量
  return 0;
}

在主函数和结构体末尾创建的结构体变量作用域不同。



4. 特殊的声明


4.1 匿名结构体

在声明结构时,可以不完全的声明,例如:

struct
{
  char name[20];
  float price;
  char id[12];
}sw;


我们可以看到这个结构体并没有名字,所以称它为匿名结构体。但是它如何使用?


匿名结构体只能在变量列表处,也就是结构体结尾的分号前创建变量,且这个变量为全局变量,相当于一次性使用。不能在别处创建变量。


4.2 小细节


当两个成员列表相同的匿名结构体,它们的类型是否相同?

struct
{
  int a;
  char b;
  float c;
}*p;//结构体指针,指向结构体
struct
{
  int a;
  char b;
  float c;
}ss;
//匿名结构体的成员如果一样,在编译器看来也是不同类型的结构体
int main()
{
  p = &ss;
  return 0;
}



两个匿名结构体成员列表相同,那么将匿名结构体创建的ss变量的地址赋给一个结构体指针,是否可行?当我们编译过后发现它们的类型并不一样,以下是编译结果:

8e76c1c418781b1f0c838a92897fb529.png

分析:


如果以我们平常的想法,它们的结果应该相同。但是编译过后,发现两个匿名结构体变量类型并不相同,依此我们可以得出结论:匿名结构体的成员如果一样,在编译器看来也是不同类型的结构体

总的来说,当一个结构体只想使用一次时,就可以使用匿名结构体。




5. 结构体的自引用


5.1 错误的自引用方式

例如在结构体中包含一个类型为该结构体本身的成员是否可以?例如一个链表的节点的结构体,如果想要让这个节点能找到下一个节点,于是在里面放置下一个节点的结构体,可行吗?

//代码1
struct Node
{
    int data;
    struct Node next;
};


分析:

这样是不行的,如果在结构体中放置下一个节点,那么sizeof(struct Node)的大小为多少?Node里面整形的大小为4个字节,但是剩下一个元素也就是另一个Node是不可知的,这样无限套娃就无法计算大小,所以这样是不可行的。



5.2 正确的自引用方式


要找到下一个节点,那么可以通过地址,也就是通过结构体指针来访问,可以在本次节点中存放下一个节点的地址,这样便可以知道在一个节点中另一个成员的大小:

struct Node
{
    int data;
    struct Node* next;//下一个节点的地址
};
int main()
{
  struct Node n;//创建结构体变量
  return 0;
}



但是如果按照这样写,在创建一个节点的结构体变量时,写结构体的类型时十分繁琐,能不能把struct省略呢?我们知道直接省略肯定是错误的,那应该使用什么"工具"?





6. typedef



6.1 如何重命名

typedef为类型重命名,通过它便可以对复杂的类型进行重命名,比如重命名当前结构体:

typedef struct Node//typdef对struct Node 进行类型重命名
{
  int data;
  struct Node* next;
}Node;//重命名后名字为Node
int main()
{
  Node n;//创建结构体变量
  return 0;
}


只要在typedef后写上对应的类型,然后再结构体分号前写上命名名称,便可完成重命名!

经过重命名后,这样是不是创建变量更加简单了?但是思考一个问题,我们能否这么写:

typedef struct Node//typdef对struct Node 进行类型重命名
{
  int data;
  Node* next;
}Node;//重命名后名字为Node


这样写是万万不可的,结构体中使用的是重命名之后的类型,这时Node还未重命名呢。所以不能在重命名之前使用重命名后的名字!!!

编译结果:

47f4818c8efc227e85d9ff7ffdfc09c1.png


6.2 小细节


如果我在类型重命名时这么写呢?

typedef struct Node//typdef对struct Node 进行类型重命名
{
  int data;
  struct Node* next;
}Node,hello, world;
int main()
{
  Node n;
  hello h;
  world j;
  return 0;
}

我一下写了三个重命名的类型名,这样的话我所创建的3个变量的类型是什么?

a466625f441eb56b363a3a3763e5fbc9.png


发现三个类型重命名后都是Node类型,它们的类型是由什么决定的?


分析:


这是一种"变态"的做法,因为在实际使用中,这样做后面的变量类型就混乱了。变量的类型就是typedef重定的类型来决定的,它会根据重定义来解析到对应的结构类型。也就是说这三个变量的内存空间分布本质上还是这个结构体的内存分布,但是就上层使用上来说,编译器有可能会认为这三个变量不是同一种类型。


但是很巧的是我当前使用的vs编译器是都解析到Node类型的,但是如果给用户来看,那么肯定是难受的。我们通常类型重命名时就定义一个名字!!!


6.3 匿名结构体的重命名


人说:”匿名结构体很可怜,我们是否也能给它一个名字,让它在主函数中创建局部结构体变量?“


神说:“满足你!”

typedef struct
{
  int data;
  struct Node* next;
}name;
int main()
{ 
  name q1;//匿名结构体重命名后创建变量
  return 0;
}



匿名结构体很开心,它也有了名字。


一个小插曲,活跃一下气氛但是匿名结构体用`typedef`重命名是没问题的哈





7. 结构体变量的定义和初始化


7.1 变量的定义

有了结构体类型,就可以定义结构体变量:

struct Book
{
  char book_name[20];
  char author[20];
  int price;
  char id[15];
}sb3,sb4;//全局变量
struct Book sb5;  //全局变量
int main()
{
  struct Book sb1;  //局部变量
  return 0;
}


注:不要以为全局的结构体变量只能在结构体末尾定义,只要不在{}中定义的变量均为全局变量,所以像sb5也是全局变量。



7.2 变量的初始化


7.2.1 结构体有序初始化


一般来说,我们通常按照顺序初始化变量:

struct Book
{
  char book_name[20];
  char author[20];
  int price;
  char id[15];
}sb3 = {"bc","scw",26,"cw10001"}, sb4;
struct Book sb5 = {"nsjl","111",34,"ns10001"};
int main()
{
  struct Book sb1 = {"clanguage","thq",89,"hq10001"};
  return 0;
}


但是这种初始化结构体成员必须全部初始化,否则初始化时数据会混乱。比如:

struct S
{
  char c;
  int a;
  float f;
};
int main()
{
  struct S s = { 10,3.14f };//c成员未初始化
  printf("%c %d %f\n", s.c, s.a, s.f);
  return 0;
}


运行结果:

10a1c9c5f13f22b585ca45354565bde7.png


7.2.2 结构体无序初始化

但有时我们只想初始化部分成员或乱序初始化时,可以用这种写法:

#include <stdio.h>
struct S
{
  char c;
  int a;
  float f;
};
int main()
{
  struct S s = { 'w',10,3.14f };
  printf("%c %d %f\n", s.c, s.a, s.f);
  struct S s2 = { .f = 3.14f, .c = 'w'};
  printf("%c %d %f\n", s2.c, s2.a, s2.f);
  return 0;
}


分析:

当我们使用这种方法初始化时,可以进行乱序初始化,也可以初始化部分值。例如当前我就初始化了f, c,未初始化的a会被默认初始化为0。

运行结果:

eb6c4917fc3614a79ca8b9d05370d4d2.png


7.2.3 结构体嵌套初始化

结构体类型如果嵌套定义的话,在初始化时就需要加上{ },并且采用对应的初始化方式,对嵌套的结构体内容进行初始化。

有序:

struct Point
{
  int x;
  int y;
};
struct S
{
  char c;
  int a;
  struct Point p;
};
int main()
{
  struct S s = { 'w',10,{4,6} };
  printf("%c %d %c %d\n", s.c, s.a, s.p.x, s.p.y);
  return 0;
}

运行结果:

7926fbbd20b5728536e0d7abdabb2781.png


无序:

struct Point
{
  int x;
  int y;
};
struct S
{
  char c;
  int a;
  struct Point p;
};
int main()
{
  struct S s = { 'w',10,{4,6} };
  printf("%c %d %c %d\n", s.c, s.a, s.p.x, s.p.y);
  return 0;
}

这里无序初始化只初始化部分内容时,其他的元素也是初始化为0。

运行结果:

238d2deb797fac7bb9c07d041d7cb15f.png



相关文章
|
11月前
|
存储 网络协议 编译器
【C语言】深入解析C语言结构体:定义、声明与高级应用实践
通过根据需求合理选择结构体定义和声明的放置位置,并灵活结合动态内存分配、内存优化和数据结构设计,可以显著提高代码的可维护性和运行效率。在实际开发中,建议遵循以下原则: - **模块化设计**:尽可能封装实现细节,减少模块间的耦合。 - **内存管理**:明确动态分配与释放的责任,防止资源泄漏。 - **优化顺序**:合理排列结构体成员以减少内存占用。
872 14
|
11月前
|
存储 编译器 C语言
【C语言】结构体详解 -《探索C语言的 “小宇宙” 》
结构体通过`struct`关键字定义。定义结构体时,需要指定结构体的名称以及结构体内部的成员变量。
594 10
|
12月前
|
存储 C语言
C语言如何使用结构体和指针来操作动态分配的内存
在C语言中,通过定义结构体并使用指向该结构体的指针,可以对动态分配的内存进行操作。首先利用 `malloc` 或 `calloc` 分配内存,然后通过指针访问和修改结构体成员,最后用 `free` 释放内存,实现资源的有效管理。
1051 13
|
12月前
|
存储 编译器 数据处理
C 语言结构体与位域:高效数据组织与内存优化
C语言中的结构体与位域是实现高效数据组织和内存优化的重要工具。结构体允许将不同类型的数据组合成一个整体,而位域则进一步允许对结构体成员的位进行精细控制,以节省内存空间。两者结合使用,可在嵌入式系统等资源受限环境中发挥巨大作用。
379 12
|
12月前
|
存储 数据建模 程序员
C 语言结构体 —— 数据封装的利器
C语言结构体是一种用户自定义的数据类型,用于将不同类型的数据组合在一起,形成一个整体。它支持数据封装,便于管理和传递复杂数据,是程序设计中的重要工具。
|
12月前
|
存储 人工智能 算法
数据结构实验之C 语言的函数数组指针结构体知识
本实验旨在复习C语言中的函数、数组、指针、结构体与共用体等核心概念,并通过具体编程任务加深理解。任务包括输出100以内所有素数、逆序排列一维数组、查找二维数组中的鞍点、利用指针输出二维数组元素,以及使用结构体和共用体处理教师与学生信息。每个任务不仅强化了基本语法的应用,还涉及到了算法逻辑的设计与优化。实验结果显示,学生能够有效掌握并运用这些知识完成指定任务。
232 4
|
存储 C语言
如何在 C 语言中实现结构体的深拷贝
在C语言中实现结构体的深拷贝,需要手动分配内存并逐个复制成员变量,确保新结构体与原结构体完全独立,避免浅拷贝导致的数据共享问题。具体方法包括使用 `malloc` 分配内存和 `memcpy` 或手动赋值。
414 10
|
安全 编译器 Linux
【c语言】轻松拿捏自定义类型
本文介绍了C语言中的三种自定义类型:结构体、联合体和枚举类型。结构体可以包含多个不同类型的成员,支持自引用和内存对齐。联合体的所有成员共享同一块内存,适用于判断机器的大小端。枚举类型用于列举固定值,增加代码的可读性和安全性。文中详细讲解了每种类型的声明、特点和使用方法,并提供了示例代码。
131 3
|
存储 大数据 编译器
C语言:结构体对齐规则
C语言中,结构体对齐规则是指编译器为了提高数据访问效率,会根据成员变量的类型对结构体中的成员进行内存对齐。通常遵循编译器默认的对齐方式或使用特定的对齐指令来优化结构体布局,以减少内存浪费并提升性能。
|
编译器 C语言
共用体和结构体在 C 语言中的优先级是怎样的
在C语言中,共用体(union)和结构体(struct)的优先级相同,它们都是用户自定义的数据类型,用于组合不同类型的数据。但是,共用体中的所有成员共享同一段内存,而结构体中的成员各自占用独立的内存空间。