目录

  • 一、结构体
    • 1、特殊的声明
    • 2、结构体自引用
    • 3、结构体变量的定义和初始化
    • 4、打印结构体
  • 二、==结构体内存对齐==
    • 1、内存对齐
      • 结构体嵌套如何求
      • 为什么存在内存对齐?
    • 2、修改默认对齐数
    • 3、宏offsetof(传参时可以传类型)
    • 4、结构体传参
  • 三、位段
    • 1、结构体实现 位段 的能力
    • 2、位段的内存分配
    • 3、位段的跨平台问题
  • 四、枚举
  • 五、联合体(共用体)
    • 1、联合类型的定义
    • 2、联合的特点
    • 3、联合大小的计算

数据类型:

C语言内置类型:
— char short int long float double
自定义类型/构造类型:
— 结构体,枚举,联合

数据的存储 - 类型详解


一、结构体

1、特殊的声明

在声明结构体的时候,可以不完全地声明

匿名结构体

struct
{int a;char c;double d;
}s1, s2; // correctint main()
{struct s3; // errreturn 0;
}

匿名结构体类型

struct
{int a;char c;double d;
}s1, s2;struct
{int a;char c;double d;
}*ps;int main()
{ps = &s1; // “=”: 从“*”到“*”的类型不兼容// 编译器会把上面两个声明当成完全不同的两个类型, 是非法的return 0;
}

2、结构体自引用

数据结构:描述了数据在内存中存储的结构
线性数据结构:顺序表,链表

同类型节点找到同类型节点

错误写法:

struct Node
{int date;struct Node n; // 无线递归 err
};

正确写法:存地址

struct Node
{int date; // 数据域struct Node* next; // 指针域
};

以下代码错在哪里,如何改正

typedef struct
{int date;Node* next;}Node;

对匿名结构体重命名,而在使用时还没有产生
解决方案:

typedef struct Node
{int date;struct Node* next;}Node;

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

创建并初始化

struct Point
{int x;int y;
}p1 = { 5,6 }, p2; // 全局struct Point p2 = { 1,2 }; // 全局int main()
{struct Point p1 = { 3,4 }; // 局部// 程序运行到此创建// 全局变量在程序运行前就创建了return 0;
}

结构体嵌套初始化

struct Point
{int x;int y;
}p1 = { 5,6 }, p2;struct Point p2 = { 1,2 };struct S
{double d;struct Point p;char name[20];
};int main()
{struct Point p1 = { 3,4 };struct S s = { 3.14, {1, 5}, {"zhangsan"}};return 0;
}

4、打印结构体

#include <stdio.h>struct Point
{int x;int y;
}p1 = { 5,6 }, p2;struct Point p2 = { 1,2 };struct S
{double d;struct Point p;char name[20];int date[20];
};int main()
{struct Point p1 = { 3,4 };struct S s = { 3.14, {1, 5}, {"zhangsan"}, {1,2,3} };printf("%lf\n", s.d); // 3.140000printf("%d %d\n", s.p.x, s.p.y); // 1 5printf("%s\n", s.name); // zhangsanint i = 0;for (i = 0; i < 20; i++){printf("%d ", s.date[i]); // 1 2 3 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0}return 0;
}


二、结构体内存对齐

为什么S1和S2类型的成员一模一样,但是S1和S2所占空间的大小有了一些区别?

#include <stdio.h>struct S1
{char c1;int a;char c2;
};struct S2
{char c1;char c2;int a;
};int main()
{struct S1 s = { 'x', 100, 'y' };printf("%d\n", sizeof(struct S1)); // 12printf("%d\n", sizeof(struct S2)); // 8printf("%d\n", sizeof(s)); // 12return 0;
}

1、内存对齐

结构体内存对齐规则:

  1. 结构体的第一个成员永远放在结构体起始位置偏移为0的位置
  2. 结构体成员从第二个成员开始,总是放在偏移为一个对起数的整数倍处
    对齐数 = 编译器默认的对齐数和变量自身大小的较小值
    Linux - 没有默认对齐数(自身大小就是对齐数)
    VS - 默认对齐数是8
  3. 结构体的总大小必须是各个成员的对齐数中最大那个对齐数的整数倍

练习:

struct S3
{double d;char c;int i;
};
// 16

结构体嵌套如何求

#include <stdio.h>struct S3
{double d;char c;int i;
};struct S4
{char c1;struct S3 s3;double d;
};int main()
{printf("%d\n", sizeof(struct S4));return 0;
}

规则4:
如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,
结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。


为什么存在内存对齐?

  1. 平台原因(移植原因): 不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址
    处取某些特定类型的数据,否则抛出硬件异常。
  2. 性能原因: 数据结构(尤其是栈)应该尽可能地在自然边界上对齐。 原因在于,为了访问未对齐的内存,处理器
    需要作两次内存访问;而对齐的内存访问仅需要一次访问

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

在设计结构体的时候,我们既要满足对齐,又要节省空间,如何做到:
让占用空间小的成员尽量集中在一起。


2、修改默认对齐数

用预处理指令#pragma设置默认对齐数 并取消设置,还原默认

例:修改结构体S1对齐数为1

#include <stdio.h>// 紧挨着放 没有空间浪费 效率低
#pragma pack(1)
struct S1
{char c1;int i;char c2;
};
#pragma pack()int main()
{struct S1 s[] = { 0 };printf("%d\n", sizeof(s)); // 6return 0;
}

3、宏offsetof(传参时可以传类型)

计算一个结构体成员相对于结构体在内存中存储的起始位置的偏移量

size_t offsetof( structName, memberName );

#include <stdio.h>
#include <stddef.h>struct S1
{char c1;int i;char c2;
};int main()
{printf("%d\n", offsetof(struct S1, c1)); // 0printf("%d\n", offsetof(struct S1, i)); // 4printf("%d\n", offsetof(struct S1, c2)); // 8return 0;
}

4、结构体传参

传值 / 传址

#include <stdio.h>struct S
{int data[1000];int num;
};// 结构体传参
void print1(struct S tmp)
{int i = 0;for (i = 0; i < 10; i++){printf("%d ", tmp.data[i]);}printf("\nnum = %d\n", tmp.num);
}// 结构体地址传参
void print2(struct S* ps)
{int i = 0;for (i = 0; i < 10; i++){printf("%d ", ps->data[i]);}printf("\nnum = %d\n", ps->num);
}int main()
{struct S s = { {1,2,3,4,5,6,7,8,9,10}, 100 };print1(s); // 传结构体print2(&s); // 传地址return 0;
}

函数传参的时候,参数是需要压栈,会有时间和空间上的系统开销。
如果传递一个结构体对象的时候,结构体过大,参数压栈的的系统开销比较大,所以会导致性能的下降。



三、位段

1、结构体实现 位段 的能力

  1. 位段是可以节省空间的
  2. 位段 - 二进制位
#include <stdio.h>struct A
{int _a : 2; // _a - 2个bit为位int _b : 5; // _b - 5个bit为位int _c : 10; int _d : 30;
};int main()
{printf("%d\n", sizeof(struct A)); // 8return 0;
}

2、位段的内存分配

  1. 位段的成员可以是 int unsigned int signed int 或者是 char (属于整形家族)类型
  2. 位段的空间上是按照需要以4个字节( int )或者1个字节( char )的方式来开辟的。
  3. 位段涉及很多不确定因素,位段是不跨平台的,注重可移植的程序应该避免使用位段。

一个例子:

struct S
{char a : 3;char b : 4;char c : 5;char d : 4;
};int main()
{struct S s = { 0 };s.a = 10; // 1010 - 010s.b = 12; // 00001100 - 1100s.c = 3; // 00000011 - 00011s.d = 4; // 00000100 - 0100
}

空间是如何开辟的?

 推测:char-> 先开辟1字节 00000000a-3bit    010b-4bit    1100剩下的空间不够用 再开辟1字节c-5bit    00011剩下的空间不够用 再开辟1字节d-4bit    0100左<-右   高0(1100)(010)  000(00011)  0000(0100)62 03 04

3、位段的跨平台问题

  1. int 位段被当成有符号数还是无符号数是不确定的。
  2. 位段中最大位的数目不能确定。(16位机器最大16,32位机器最大32,写成27,在16位机器会出问题。
  3. 位段中的成员在内存中从左向右分配,还是从右向左分配标准尚未定义。
  4. 当一个结构包含两个位段,第二个位段成员比较大,无法容纳于第一个位段剩余的位时,是舍弃剩余的位还是利用,这是不确定的。

总结:
跟结构相比,位段可以达到同样的效果,但是可以很好的节省空间,但是有跨平台的问题存在。



四、枚举

enum Color枚举类型。{}中的内容是枚举类型的可能取值,也叫 枚举常量

#include <stdio.h>enum Color
{RED = 2, // 赋初值GREEN,BLUE,
};int main()
{enum  Color c = GREEN; // 枚举类型赋可能取值if (c == GREEN){printf("绿色\n");}return 0;
}

枚举的优点:

我们可以使用 #define 定义常量,为什么非要使用枚举

  1. 增加代码的可读性和可维护性
  2. 和#define定义的标识符比较枚举有类型检查,更加严谨。
  3. 防止了命名污染(封装)
  4. 便于调试
  5. 使用方便,一次可以定义多个常量

例:实现计算器清晰明了

enum Option
{EXIT, // 0ADD, // 1SUB,MUL,DIV,
};int main()
{int input = 0;do{menu();printf("请选择:>\n");switch (input){case ADD:break;case SUB:break;case MUL:break;case DIV:break;case EXIT:break;}} while (input);return 0;
}


五、联合体(共用体)

1、联合类型的定义

联合也是一种特殊的自定义类型 这种类型定义的变量也包含一系列的成员,特征是这些成员公用同一块空间(所以联合也叫共用体)

#include <stdio.h>//联合类型的声明
union Un
{char c;int i;
};int main()
{//联合变量的定义union Un u = { 0 };// 地址一样printf("%p\n", &u);printf("%p\n", &(u.c));printf("%p\n", &(u.i));return 0;
}

2、联合的特点

联合的成员是共用同一块内存空间的,这样一个联合变量的大小,至少是最大成员的大小(因为联合至少得有能力保存最大的那个成员)

判断当前计算机的大小端存储:

#include <stdio.h>// 1
int main()
{int a = 1;//0x 00 00 00 01//低---------------------> 高//01 00 00 00 - 小端存储//00 00 00 01 - 大端存储char* pc = (char*)&a;if (*pc == 1){printf("小端\n");}else{printf("大端\n");}
}

使用联合体:

// 2
int check_sys()
{union U{char c;int i;}u;u.i = 1;return u.c;
}int main()
{if (check_sys() == 1){printf("小端\n");}else{printf("大端\n");}return 0;
}

3、联合大小的计算

  1. 联合的大小至少是最大成员的大小。
  2. 当最大成员大小不是最大对齐数的整数倍的时候,就要对齐到最大对齐数的整数倍。

例:计算以下代码结果

union Un1
{char c[5]; // 5    1int i; // 4  8     4 // 联合体的总大小必须是4的倍数 在c后浪费3个字节 结果是8
};
union Un2
{short c[7]; // 14    2int i; // 4  8       4// 14不是4的倍数 结果是16
};int main()
{printf("%d\n", sizeof(union Un1)); // 8printf("%d\n", sizeof(union Un2)); // 16
}

通讯录程序

自定义类型详解:结构体(内存对齐、位段) + 枚举 + 联合相关推荐

  1. C语言自定义类型详解

    C语言自定义类型详解 一.结构体 1.结构的声明 2.特殊的声明 3.结构的自引用 4.结构体变量的定义和初始化 5.结构体内存对齐 6.修改默认对齐数 7.结构体传参 二.位段 1.什么是位段 2. ...

  2. 黑马程序员C语言基础(第八天)复合类型(自定义类型)(结构体)、共用体(联合体)、枚举enum、 typedef

    黑马程序员C语言基础(第一天) 黑马程序员C语言基础(第二天) 黑马程序员C语言基础(第三天) 黑马程序员C语言基础(第四天)数据类型 黑马程序员C语言基础(第五天)运算符与表达式.程序流程结构.数组 ...

  3. 你是真的“C”——详解结构体知识点

    你是真的"C"--详解结构体知识点

  4. C语言——自定义类型详解(结构体,联合体,枚举,位段)

    专栏:C语言 个人主页:HaiFan. 专栏简介:本专栏主要更新一些C语言的基础知识,也会实现一些小游戏和通讯录,学时管理系统之类的,有兴趣的朋友可以关注一下. 结构体 前言 一.结构体 1.结构体类 ...

  5. 详解结构体、类等内存字节对齐

    先说个题外话:早些年我学C程序设计时,写过一段解释硬盘MBR分区表的代码,对着磁盘编辑器怎么看,怎么对,可一执行,结果就错了.当时调试也不太会,又根本没听过结构体对齐这一说,所以,问题解决不了,好几天 ...

  6. C语言自定义类型详解!

    目录 内容概述 结构体类型 结构类型的声明 结构体的特殊声明 结构体的自引用 结构体变量的定义和初始化 结构体内存对齐 结构体传参 位段 什么是位段? 位段的内存分配 位段跨平台问题 枚举类型 枚举类 ...

  7. C语言struct关键字详解—结构体

    struct 是个神奇的关键字,它将一些相关联的数据打包成一个整体,方便使用. 在网络协议.通信控制.嵌入式系统.驱动开发等地方,我们经常要传送的不是简单的字节流(char 型数组),而是多种数据组合 ...

  8. C语言 结构体,内存对齐,对齐参数,偏移量

    文章目录 sizeof() 内存对齐 内存对齐的背景 全部规则汇总(看这里) 典型例子: Pragma Pack(n)与内存分配 #pragma pack对对齐模数的影响 偏移量 sizeof() 对 ...

  9. 从硬件到语言,详解C++的内存对齐

    很多写C/C++的人都知道"内存对齐"的概念以及规则,但不一定对他有很深入的了解.小编试着从硬件到C++语言.更彻底地讲一下C++的内存对齐. 什么是内存对齐(memory ali ...

最新文章

  1. 关于Java中serialUID的序列化的问题
  2. __dopostback
  3. python调用cv2.findContours时报错:ValueError: not enough values to unpack (expected 3, got 2)
  4. ACM模式数组构建二叉树
  5. TypeScript 枚举(Enum)
  6. linux gdb检查函数栈,Linux - gdb调试
  7. Rabbitmq取消预取机制配置,配置手动确认后仍然java.lang.IllegalStateException: Channel closed; cannot ack/nack的问题
  8. 软考网络工程师好考吗?怎么备考?
  9. MATLAB坐标轴位置调整
  10. SecureCRT自动登录跳板机/堡垒机直连linux
  11. mysql复制数据到同一张表
  12. 云帆文档易用性功能设计之文档查阅
  13. docker部署seaweedf
  14. 知识产权律师介绍企业涉外法律服务
  15. 项目八总结 数据库安全与维护
  16. 薅羊毛 | Python 自动化带你轻松赚钱(完结版)
  17. centos下载安装软件总结
  18. 【学习记录】Python零基础入门(五)
  19. html5支持h265吗,PC端播放H265
  20. web连接蓝牙电子秤navigator.bluetooth

热门文章

  1. mybatis批量更新 mysql 报错,Mybatis批量更新报错问题
  2. java操作Linux 调用shell命令,shell脚本
  3. docker构建dpdk运行环境镜像
  4. 以太坊geth区块链私链建立
  5. 分布式光伏贷款欲破冰 多家银行推出相关业务
  6. RhinoMock异常ExpectationViolationException以及解决
  7. Thrift架构~从图中理解thrift,它事实上是一种远程过程调用
  8. 弱鸡儿长乐爆零旅Day5
  9. bzoj千题计划203:bzoj3994: [SDOI2015]约数个数和
  10. (47)LINUX应用编程和网络编程之二Linux文件属性