C语言进阶第七课-----------自定义类型的讲解(结构体枚举联合) 1

简介: C语言进阶第七课-----------自定义类型的讲解(结构体枚举联合)

结构体

结构体的声明

在C语言中,有自己的内置类型,如int 、double、 float、 等等,这些类型只能解决一些问题,但是还有一些问题无法解决,比如定义一个人,而有头、手…等许多的特征,如果光靠这里些类型来描述这些很难完成,而结构体就是为了解决这个问题出现的

结构的基础知识

结构是一些值的集合,这些值称为成员变量,结构体的成员可以是不同的变量,这跟为我们学习过的数组很像,数组是一组相同类型元素的集合。

声明

#include<stdio.h>
struct tag1
{
  int hand;
  int head;
  int leg;
} person;
person = { 1,2,3 };
struct tag2
{
  int hand;
  int head;
  int leg;
} Person2 = { 2,1,3 };
typedef struct tag3
{
  int hand;
  int head;
  int leg;
}st;
int main()
{
  struct tag1 Person1 = { 2,1,2 };
  st Person3 = { 2,1,3 };
  return 0;
}

这里我只是列举了两三种结构类型的声明方式

还有一种匿名结构体声明,只能使用一次,就是在声明的时候进行使用,一旦结束声明就会销毁

#include<stdio.h>
struct
{
  char name[20];
  char auother[30];
  int a;
} ar = {"fdfdg", "fg", 5}, *ps = &ar;
int main()
{
  printf("%p\n", ps);
  printf("%s\n", ps->name);
  printf("%s\n", ps->auother);
  return 0;
}

这种结构体只能使用一次

结构的自引用

小小插曲:

数据结构:描述 的是数据在内存中存储和组织的结构

例如 我们存储12345在内存中是连续存放


安照这种结构(线性数据结构)我们称之为顺序表

如果按照这个结构(线性数据结构),我们称之为链表,图中的每一个存储的方框都是一个节点

#include<stdio.h>
 struct Node
{
  int num;//存放数据--数据域
  struct Node* pa;//存放下一个结构体的地址--指针域
};
int main()
{
  struct Node a5 = { 5, NULL };
  struct Node a4 = { 4, &a5};
  struct Node a3 = { 3, &a4 };
  struct Node a2 = { 2, &a3 };
  struct Node a1 = { 1, &a2 };
  struct Node* p = &a1;
  while (p)
  {
    printf("%d\n", p->num);
    p = (*p).pa;
  }
  return 0;
}

而结构体的自引用也就是自己的成员有自己类型的指针

自引用方式:

 struct Node
{
  int num;
  struct Node* pa;
};

需要注意的是匿名结构体不行,即使使用了typedef类型重定义也不行,

 typedef struct Node
{
  int num;
  Node* pa;
}Node;

这种写法是错误的,因为类型重定义还没有执行完就直接使用就会报错,代码从上往下执行,

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

在前面我写的结构体声明有很多种,初始化也有多种

#include<stdio.h>
struct Point
{
  int a;
  int b;
} a1 = {1,2}, s1; //1
struct Sp
{
  int a;
};
struct Sd
{
  int b;
  struct Sp c;
};
struct Point a2 = { 1,2 };//2
int main()
{
  struct Point s = { 3,4 };//3
  s1.a = 3;//4
  s1.b = 6;
  struct Point s3 = { s3.a = 5, s3.b = 9 };//5
  struct Point s4 = { .a = 5, .b = 9 };
  struct Sd s5 = { 4, {6} };
  printf("%d", s5.c.a);
  return 0;
}

这里我列举了一些定义和初始化的情况,包含结构体嵌套结构体

结构体内存对齐

#include<stdio.h>
struct S1
{
  int a;
  char b;
  short c;
};
struct S2
{
  short c;
  int a;
  char b;
};
int main()
{
  printf("%d\n", sizeof(struct S1));
  printf("%d\n", sizeof(struct S2));
  return 0;
}

可以看到两个结构体,大小不一样,但是成员是一样的,是啥造成这个原因?

其实就是关于结构体内存对齐

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

1.第一个成员在与结构体变量偏移量为0的地址处。

2.其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。

对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值。

VS中默认的值为8

3.结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍。

4.如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整

体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。


130e3106d7cb4014a172e1398e50a0e9.png

也就是说在结构体的大小是由内存对齐决定的,结构体的第一个成员的开辟的空间必须是在偏移量为0的地址处

这里我引入一个宏offsetof 计算结构体成员的偏移量

第一个参数就是结构体类型,第二个参数就是结构体成员

返回值:

类型为 size_t 的值,其偏移值为类型中的成员。

#include<stdio.h>
#include<stddef.h>
struct S1
{
  int a;
  char b;
  short c;
};
int main()
{
  printf("%d\n", offsetof(struct S1, a));
  printf("%d\n", offsetof(struct S1, b));
  printf("%d\n", offsetof(struct S1, c));
  printf("%d", sizeof(struct S1));
  return 0;
}

我们来模拟一下这个宏

#include<stdio.h>
#include<stddef.h>
#define OFFSETOF(type, money) (size_t)(&(((type*)0) ->money))//设计一个结构体初始地址,使用 ->可以找到对应的成员,然后取地址,因为offsetof的返回值的类型为size_t
struct stu
{
  char name[20];
  int money;
};
int main()
{
  printf("%zd\n", OFFSETOF(struct stu, money));
  printf("%zd\n", offsetof(struct stu, money));
  return 0;
}

如果我们要计算出结构体的内存对齐情况,不可能全部根据offsetof来确定,所以我们要学会计算结构体内存对齐

在vs编译器中默认的对齐数是8,而成员的对齐数是成员的大小(空间大小)和vs编译器默认的对齐数进行比较,取最小的为该成员的对齐数

变量a从结构体变量的偏移量为0的地址处开始,存放4个字节,当我们遇到变量b对齐时要判断这个地址处是否是b的对齐数的整数倍,图中的地址4是1的整数倍,所以可以对齐,遇到变量c时,因为c的对齐数是2.而5不是2的倍数,所以要往后找。直到找到是2的倍数的地址,当我们把所有的成员对齐后,计算出目前结构体的大小,结构体的大小是该结构体最大对齐数的整数倍,而图中的当我们对齐完刚刚好,大小为8,而结构体的最大对齐数为4

相关文章
|
8天前
|
存储 C语言
C语言程序设计核心详解 第九章 结构体与链表概要详解
本文档详细介绍了C语言中的结构体与链表。首先,讲解了结构体的定义、初始化及使用方法,并演示了如何通过不同方式定义结构体变量。接着,介绍了指向结构体的指针及其应用,包括结构体变量和结构体数组的指针操作。随后,概述了链表的概念与定义,解释了链表的基本操作如动态分配、插入和删除。最后,简述了共用体类型及其变量定义与引用方法。通过本文档,读者可以全面了解结构体与链表的基础知识及实际应用技巧。
|
13天前
|
C语言
c语言中的结构体
本文档详细介绍了C语言中结构体的使用方法,包括结构体的基本定义、变量声明与赋值、数组与指针的应用,以及结构体嵌套、与`typedef`结合使用等内容。通过示例代码展示了如何操作结构体成员,并解释了内存对齐的概念。
|
19天前
|
C语言
C语言结构体赋值的四种方式
本文总结了C语言结构体的四种赋值方式,并通过示例代码和编译运行结果展示了每种方式的特点和效果。
27 6
|
29天前
|
编译器 程序员 Linux
【C语言篇】结构体和位段详细介绍
跟结构相⽐,位段可以达到同样的效果,并且可以很好的节省空间,但是有跨平台的问题存在。
|
29天前
|
存储 编译器 C语言
【C语言篇】自定义类型:联合体和枚举详细介绍
像结构体⼀样,联合体也是由⼀个或者多个成员构成,这些成员可以不同的类型。
|
5天前
|
存储 Serverless C语言
【C语言基础考研向】11 gets函数与puts函数及str系列字符串操作函数
本文介绍了C语言中的`gets`和`puts`函数,`gets`用于从标准输入读取字符串直至换行符,并自动添加字符串结束标志`\0`。`puts`则用于向标准输出打印字符串并自动换行。此外,文章还详细讲解了`str`系列字符串操作函数,包括统计字符串长度的`strlen`、复制字符串的`strcpy`、比较字符串的`strcmp`以及拼接字符串的`strcat`。通过示例代码展示了这些函数的具体应用及注意事项。
|
8天前
|
存储 C语言
C语言程序设计核心详解 第十章:位运算和c语言文件操作详解_文件操作函数
本文详细介绍了C语言中的位运算和文件操作。位运算包括按位与、或、异或、取反、左移和右移等六种运算符及其复合赋值运算符,每种运算符的功能和应用场景都有具体说明。文件操作部分则涵盖了文件的概念、分类、文件类型指针、文件的打开与关闭、读写操作及当前读写位置的调整等内容,提供了丰富的示例帮助理解。通过对本文的学习,读者可以全面掌握C语言中的位运算和文件处理技术。
|
8天前
|
存储 C语言
C语言程序设计核心详解 第七章 函数和预编译命令
本章介绍C语言中的函数定义与使用,以及预编译命令。主要内容包括函数的定义格式、调用方式和示例分析。C程序结构分为`main()`单框架或多子函数框架。函数不能嵌套定义但可互相调用。变量具有类型、作用范围和存储类别三种属性,其中作用范围分为局部和全局。预编译命令包括文件包含和宏定义,宏定义分为无参和带参两种形式。此外,还介绍了变量的存储类别及其特点。通过实例详细解析了函数调用过程及宏定义的应用。
|
13天前
|
Linux C语言
C语言 多进程编程(三)信号处理方式和自定义处理函数
本文详细介绍了Linux系统中进程间通信的关键机制——信号。首先解释了信号作为一种异步通知机制的特点及其主要来源,接着列举了常见的信号类型及其定义。文章进一步探讨了信号的处理流程和Linux中处理信号的方式,包括忽略信号、捕捉信号以及执行默认操作。此外,通过具体示例演示了如何创建子进程并通过信号进行控制。最后,讲解了如何通过`signal`函数自定义信号处理函数,并提供了完整的示例代码,展示了父子进程之间通过信号进行通信的过程。
|
13天前
|
C语言
C语言 字符串操作函数
本文档详细介绍了多个常用的字符串操作函数,包括 `strlen`、`strcpy`、`strncpy`、`strcat`、`strncat`、`strcmp`、`strncpy`、`sprintf`、`itoa`、`strchr`、`strspn`、`strcspn`、`strstr` 和 `strtok`。每个函数均提供了语法说明、参数解释、返回值描述及示例代码。此外,还给出了部分函数的自实现版本,帮助读者深入理解其工作原理。通过这些函数,可以轻松地进行字符串长度计算、复制、连接、比较等操作。