「这是我参与2022首次更文挑战的第22天,活动详情查看:2022首次更文挑战」
自定义数据类型 - 结构体
C语言自己的数据类型 - 内置类型
声明一个结构体类型
//声明一个结构体类型
//声明一个学生类型,是想通过学生类型来创建学生变量(对象)
//描述学生:属性+名字+电话+性别
struct Stu
{
char name[20];
char tele[20];
char sex[10];
int age;
};
结构体变量 结构体标签
{
成员变量;
}变量列表;
创建结构体变量
struct Stu s1;
分为全局变量、局部变量。
匿名结构体类型
没有结构体标签
struct
{
int a;
char b;
}x;
匿名结构体指针类型
struct
{
int a;
char b;
}* psa;
两个结构体类型虽然成员变量相同,但是是两个不同的结构体。
警告:编译器会把两个声明当成完全不同的两个类型。
结构体的自引用
struct Node
{
int data;
struct Node* next;
};
不使用指针:计算结构体的内存大小将会是无限大(无限套娃)
typedef : 类型重命名
声明 -> 定义
结构体变量的定义与初始化
{ }
结构体内存对齐 - 计算结构体大小
栗子
struct S1
{
char a;
char b;
int c;
}s1;
struct S2
{
char a;
int b;
char c;
}s2;
int main()
{
printf("%d\n", sizeof(s1)); // 8
printf("%d\n", sizeof(s2)); // 12
return 0;
}
-
第一个成员在与结构体变量偏移量为0的地址处(第一个变量放置在顶端,该内存空格为对齐数是0的位置)
-
其他成员变量要对齐某个数字**(对齐数)整数倍的地址处**
对齐数 = 编译器默认的一个对齐数 与 该成员 大小的较小值 (VS默认值为8, gcc没有默认对齐数)
-
结构体总大小是最大对齐数(每一个成员变量都有有个对齐数)的整数倍。
-
如果嵌套了结构体,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
从对齐数开始(对齐数从0开始)
顺序往下放
struct S3
{
double a;
char c;
int i;
}s3; // 16 = 8 + 1 + 3(浪费)+ 4
为什么需要内存对齐?
空间换取时间。读取方便。
32位平台:32根地址线,即32根数据线 4字节
在设计结构体时,让占用空间小的成员尽量集中在一起。
修改默认对齐数
#pragma 预处理指令
设置默认对齐数位4
#pragma pack(4)
取消设置的默认对齐数
#pragma pack()
偏移量
offsetof
可以求偏移量,本质是一个宏,宏的参数可以是数据类型
size_t offsetof( structName, memberName );
头文件
<stddef.h>
结构体传参
值传递 & 地址传递
地址传递 :不管结构体多大,传的地址在32位平台总是4个字节。
如果传递一个结构体对象时,结构体过大,参数压栈的系统开销大。
自定义数据类型 - 位段
结构体实现位段。
位段
1.位段的成员必须是int , unsigned int, signed int或者char
2.位段的成员名后边有一个冒号和一个数字
#include <stdio.h>
struct A
{
int _a : 2; //a只需要2个比特位
int _b : 5; //b只需要5个比特位
int _c : 10; //c只需要10个比特位
int _d : 30; // ......
};
int main()
{
struct A a;
printf("%d\n", sizeof(a)); //8个字节
return 0;
}
位段的内存分配
位:二进制位 (比特位) 1字节 = 8比特
1.位段成员可以是 int , unsigned int , signed int 或 char(整型家族)
2.位段的空间是按照需要以4个字节(int)或者 1 个字节(char)的方式开辟的。
开辟空间的时候是按照整型开辟的。
与结构相比,位段可以达到同样的效果,但是可以很好的节省空间,但是有跨平台问题。
内存 -> 十六进制
自定义数据类型 - 枚举
//枚举类型
enum Sex
{
//枚举的可能类型 - 枚举常量
MALE,
FEMALE,
SECRET
};
int main()
{
enum Sex s = MALE;
//s = FEMALE;
printf("%d %d %d", MALE, FEMALE, SECRET); // 0 1 2
return 0;
}
给常量赋初始值,后面不可再修改
//枚举类型
enum Sex
{
//枚举的可能类型
MALE = 2,
FEMALE = 4,
SECRET = 8
};
int main()
{
enum Sex s = MALE;
//s = FEMALE;
printf("%d %d %d", MALE, FEMALE, SECRET); //2 4 8
return 0;
}
相当于 const 字符串常量
C语言的源代码 --- 预编译 ---> 编译 ---> 链接 ---> 可执行程序
(可调试阶段)
可以用 #define 定义常量,为什么要用枚举?
1.增加代码可读性和可维护性
2.和#define定义的标识符比较枚举有类型检查,更加严谨
3.防止命名污染(封装)
4.便于调试
5.使用方便,一次可定义多个常量
自定义数据类型 - 联合
联合 - 联合体 - 共用体 : 共用同一块空间。
开辟一块内存空间,大家都从头开始放。
#include <stdio.h>
union Un
{
char c;
int i;
};
int main()
{
union Un u;
printf("%d\n", sizeof(u)); //4
return 0;
}
联合的特点
联合的成员是共用同一块内存空间的,这样一个联合变量的大小,至少是最大成员的大小(因为联合至少有能力保存最大的那个成员)
成员不能同时存在使用。
大小端
int a = 0x11223344
低地址-----------------------------高地址
.......[][][11][22][33][44][][][][][][][].....大端存储模式
.......[][][44][33][22][11][][][][][][].....小端存储模式
判断平台大小端存储方式?
int a = 1; 0x 00 00 00 01
低地址 ----- 高地址
....[][01][00][00][00][][][] ...... 小端存储模式 - 1
...[][00][00][00][01][][][] ...... 大端存储模式 - 0
代码
#include <stdio.h>
int main()
{
int a = 1;
if (*(char*)&a == 1)
printf("小端\n");
else
printf("大端\n");
return 0;
}
优化 1
#include <stdio.h>
int check_sys()
{
int a = 1;
// 1 - 小端
// 0 - 大端
return *(char*)&a;
}
int main()
{
int a = 1;
int ret = check_sys();
if (ret == 1)
printf("小端\n");
else
printf("大端\n");
return 0;
}
优化 2 ----- 联合体
联合体大家都从开始放
int check_sys()
{
union Un
{
char c;
int i;
}u;
u.i = 1;
//返回1 --- 表示小端
//返回0 --- 表示大端
return u.c;
}
共同体和结构体内存存储方式不一样。
联合体(共用体)大小的计算
1.联合体的大小至少是最大成员的大小。
2.当最大成员大小不是最大对齐数的整数倍的时候,就要对齐到最大对齐数的整数倍。
数组的对齐数 是 单个元素的对齐数。