自定义类型:结构体,声明,变量初始化,结构体内存对齐。

简介: ✅<1>主页:C语言的前男友📃<2>知识讲解:结构体,声明,变量初始化,结构体内存对齐🔥<3>创作者:C语言的前男友☂️<4>开发环境:Visual Studio 2022💬<5>前言:关于结构体我们已经不陌生了,今天我们就来系统的聊一聊结构体的声明,初始化,结构体占的内存大小。

一.结构体

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


67ead548cd7b46cc8ea1ad6703af9199.png


二.特殊的结构体声明

 在声明结构的时候,可以不完全的声明——匿名结构体类型。

//匿名结构体类型
struct
{
    int a;
    char b;
    float c;
}x;
struct
{
    int a;
    char b;
    float c;
}a[20], *p;

上面的两个结构在声明的时候省略掉了结构体标签。

那么问题来了?在上面代码的基础上,下面的代码合法吗?

p = &x;

注意:编译器会把上面的两个声明当成完全不同的两个类型。所以是非法的。


三.结构体自引用

 在结构中包含一个类型为该结构本身的成员是否可以呢?

struct Node
{
    int data;
    struct Node next;
};
可行否?如果可以,那sizeof(struct Node)是多少?



这里显然是不行的。但是是不是就意味着就不行了呢?还是有办法实现自引用的。

正确的结构体自引用方式
struct Node
{
    int data;
    struct Node* next;
};


四.注意:

如果加上 typedef 是不是就可以这样实现结构体自引用呢? 
typedef struct
{
  int data;
  Node* next;
}Node;
这样写代码是不行的
解决方案:
typedef struct Node
{
  int data;
  struct Node* next;
}Node;


五.结构体的定义和初始化

struct Point
{
    int x;
    int y;
}p1; //声明类型的同时定义变量p1
struct Point p2; //定义结构体变量p2
//初始化:定义变量的同时赋初值。
struct Point p3 = {x, y};
struct Stu     //类型声明
{
    char name[15];//名字
    int age;    //年龄
};
struct Stu s = {"zhangsan", 20};//初始化
struct Node
{
    int data;
    struct Point p;
    struct Node* next;
}n1 = {10, {4,5}, NULL}; //结构体嵌套初始化
struct Node n2 = {20, {5, 6}, NULL};//结构体嵌套初始化
struct Stu
{
  char name[20];
  int age;
  char ID[20];
};
int main()
{
  按照结构体顺序初始化变量
  struct Stu stu2 = { "ikun",20,"JNTM123" };
  自定义顺序初始化变量
  struct Stu stu1 = { .ID = "CZU123",.age = 19,.name = "张三" };
  return 0;
}


六.结构体内存对齐

我们已经掌握了结构体的基本使用了,那么一个结构体到底时多大呢?到底怎么计算呢?

struct S1 
{
  char c1;
  int i;
  char c2;
};
struct S2
{
  char c1;
  char c2;
  int i;
};
int main()
{
  printf("%d\n", sizeof(struct S1));
  printf("%d\n", sizeof(struct S2));
  return 0;
}

大家可以猜一下结果


22225b594d30476c918b03631b4ace70.png


结果是不是出乎大家的意料了,虽然结构的成员都是一样的,但是结构的大小却不一样。


到底是为什么造成这样的结果呢?其实结构体的大小可不是简单的结构体成员大小加在一起的。


而是有一种内存对齐的规则。


结构体的对齐规则:

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

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

对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值。(VS中默认的值为8)

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

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

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


下面我们就对着这个规则来做几个例子:

(1)例一:

struct S1
{
  char c1;
  int a;
  char c2;
};
求结构体s1的大小



运行效果:



(2)例二:

1.题目:

struct S2
{
    char c1;
    char c2;
    int i;
};
求结构体 S2 的大小


2.图解:


3.运行效果:



(3)例三:

1.题目:

struct S1
{
  char c1;
  int a;
  char c2;
};
struct S2
{
    char c1;
    struct S1 S;
    char c2;
    int i;
};
//求结构体S2的大小

2.图解:



运行结果:



七、为什么存在内存对齐

(1) 平台原因(移植原因):


不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特

定类型的数据,否则抛出硬件异常。


(2) 性能原因:


数据结构(尤其是栈)应该尽可能地在自然边界上对齐。

原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访

问。


总体来说:


结构体的内存对齐是拿空间来换取时间的做法。

那在设计结构体的时候,我们既要满足对齐,又要节省空间,如何做到:

让占用空间小的成员尽量集中在一起。

例如:

struct S1
{
    char c1;
    int i;
    char c2;
};
struct S2
{
    char c1;
    char c2;
    int i;
};

s2就比s1更节省空间。


四.修改默认对齐数

#pragma pack();

例一:

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

运行结果:



例二:

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


运行结果:



最后:

不操千曲而后晓声,观千剑而后识器。


2.jpg

 

相关文章
|
12月前
TO B产品经理:每个阶段的学习与提升之路
对于TO B产品经理来说,每个阶段都是一次学习和提升的机会。从建立基础到深化专业知识,再到提升战略眼光和持续创新与学习,每一步都需要付出努力和汗水。但正是这样的不断学习和提升,让TO B产品经理能够在竞争激烈的市场中脱颖而出,成为企业和商业世界之间的优秀桥梁。
|
9月前
|
Ubuntu NoSQL 开发工具
《docker基础篇:4.Docker镜像》包括是什么、分层的镜像、UnionFS(联合文件系统)、docker镜像的加载原理、为什么docker镜像要采用这种分层结构呢、docker镜像commit
《docker基础篇:4.Docker镜像》包括是什么、分层的镜像、UnionFS(联合文件系统)、docker镜像的加载原理、为什么docker镜像要采用这种分层结构呢、docker镜像commit
426 70
|
7月前
|
机器学习/深度学习 人工智能 运维
机器学习+自动化运维:让服务器自己修Bug,运维变轻松!
机器学习+自动化运维:让服务器自己修Bug,运维变轻松!
284 14
|
7月前
|
监控 算法 安全
公司电脑网络监控场景下 Python 广度优先搜索算法的深度剖析
在数字化办公时代,公司电脑网络监控至关重要。广度优先搜索(BFS)算法在构建网络拓扑、检测安全威胁和优化资源分配方面发挥重要作用。通过Python代码示例展示其应用流程,助力企业提升网络安全与效率。未来,更多创新算法将融入该领域,保障企业数字化发展。
150 10
|
XML JSON Java
Android App开发即时通信中通过SocketIO在客户端与服务端间传输文本和图片的讲解及实战(超详细 附源码)
Android App开发即时通信中通过SocketIO在客户端与服务端间传输文本和图片的讲解及实战(超详细 附源码)
760 0
|
机器学习/深度学习 TensorFlow 算法框架/工具
使用卷积神经网络(CNN)进行图像分类与识别
使用卷积神经网络(CNN)进行图像分类与识别
2056 0
|
运维 Serverless 开发工具
函数计算产品使用问题之通过GitLab仓库来部署代码,该如何配置GitLab仓库
函数计算产品作为一种事件驱动的全托管计算服务,让用户能够专注于业务逻辑的编写,而无需关心底层服务器的管理与运维。你可以有效地利用函数计算产品来支撑各类应用场景,从简单的数据处理到复杂的业务逻辑,实现快速、高效、低成本的云上部署与运维。以下是一些关于使用函数计算产品的合集和要点,帮助你更好地理解和应用这一服务。
235 0
|
存储 对象存储 数据安全/隐私保护
Docker部署MinIO对象存储服务
Docker部署MinIO对象存储服务
609 0
|
机器学习/深度学习 人工智能 自然语言处理
极智AI | 详解ViT算法实现
大家好,我是极智视界,本文详细介绍一下 ViT 算法的设计与实现,包括代码。
605 0
|
机器学习/深度学习 数据挖掘 PyTorch
图像分类经典神经网络大总结(AlexNet、VGG 、GoogLeNet 、ResNet、 DenseNet、SENet、ResNeXt )
图像分类经典神经网络大总结(AlexNet、VGG 、GoogLeNet 、ResNet、 DenseNet、SENet、ResNeXt )
11956 1
图像分类经典神经网络大总结(AlexNet、VGG 、GoogLeNet 、ResNet、 DenseNet、SENet、ResNeXt )