九、自定义数据类型—结构体
自定义类型:结构体、枚举、联合体
结构体的声明
//声明一个结构体类型
//声明一个学生类型,想通过学生类型来创建一个学生变量(对象)
//描述学生的属性:名字、电话、性别、年龄
struct Stu
{
//结构体的成员变量:
char name[20];//名字
char tel[12];//电话
char sex[10];//性别
int age;
}s4,s5,s6;//注意不能缺少分号,s4,s5,s6是全局变量
struct Stu s3;//s3为全局变量
int main()
{
int num;
char c;
//创建结构体变量s1,s2,为局部变量
struct Stu s1;
struct Stu s2;
return 0;
}
特殊的声明
- 匿名结构体类型
没有具体的名字(如Stu),必须要在声明结构体类型的时候直接在最后创建变量,否则之后无法创建变量;
创建变量时也可以创建结构体指针,但两个声明默认是不同的类型,如果有两个结构体,即使创建的都是指针,也不能互相赋值。
结构体的自引用
找下一个同类型的变量-->用指针
struct Node
{
int data;//自身数据->数据域
struct Node* next;//下一个结点的地址->指针域
};
typedef重命名
typedef struct Node//此处Node不可以省略
{
int data;//自身数据->数据域
struct Node* next;//下一个结点的地址->指针域
}Node;//将名字简化为Node
int main()
{
struct Node n1;
Node n2;
return 0;
}
结构体变量的定义和初始化
定义变量
- 在创建类型是在最后定义变量
- 用创建好的结构体类型创建变量
初始化
struct S
{
char c;
int a;
double d;
char arr[20];
};
int main()
{
struct S s = {'c',100,3.14,"hello!"};//使用大括号进行初始化,给每个成员都赋值
return 0;
}
结构体成员的访问和打印
struct S
{
char c;
int a;
double d;
char arr[20];
};
int main()
{
struct S s = {'c',100,3.14,"hello!"};//使用大括号进行初始化,给每个成员都赋值
printf("%c %d $lf %s\n",s.c,s.a,s.d,s.arr);
return 0;
}
struct T
{
double weight;
short age;
};
struct S
{
char c;
int a;
double d;
char arr[20];
struct T st;
};
int main()
{
struct S s = {'c',100,3.14,"hello!",{55.6,30}};//使用大括号进行初始化,给每个成员都赋值
printf("%c %d $lf %s %lf \n",s.c,s.a,s.d,s.arr,s.st.weight);
return 0;
}
结构体内存对齐(sizeof())
结构体的对齐规则
- 第一个成员在与结构体变量偏移量为0的地址处。
- 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。(相对于起始位置的偏移量)
对齐数 = 编译器默认的一个对齐数 与 该成员大小 的较小值
vs中默认值为8
gcc没有默认对齐数,对齐数就是该成员的大小
- 结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍
- 如果是嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍,结构体的整体大小就是所有最大对齐数(包含嵌套结构体的对齐数)的整数倍
struct S1
{
char c1;
int a;
char c2;
};
struct S2
{
char c1;
char c2;
int a;
};
int main()
{
struct S1 s1 = {0};
printf("%d ",sizeof(s1));
struct S2 s2 = {0};
printf("%d",sizeof(s2));
return 0;
}
//输出结果为12 8
具体对齐:
即要内存对齐,又要节省空间,该怎么做?
让占用空间小的变量尽量集中在一起
修改默认对齐数
pragma
pragma pack( )
注意:后面没有分号
#pragma pack(4)//设置默认对齐数是4
struct S
{
char c1;
double d;
};
#pragma pack()//执行到此处然后取消设置的默认对齐数
int main()
{
struct S s;
printf("%d\n",sizeof(s));
return 0;
}
offsetof()
代表每个变量起始位置的偏移量
#include<stdio.h>
#include<stddef.h>
struct S
{
char c;
int i;
double d;
};
int main()
{
printf("%d ",offsetof(struct S,c));
printf("%d ",offsetof(struct S,c));
printf("%d ",offsetof(struct S,c));
return 0;
}
//结果为0 4 8
结构体传参
- 将结构体作为参数传递给函数,并要对其内容进行使用、修改,要传递地址!
(函数内部想要对函数外部的变量进行操作,都要传递地址)
- 不改变结构体变量内容,单纯打印,可以直接传递 s
struct S
{
int a;
char c;
double d;
};
void Init(struct S* tmp)
{
tmp->a = 100;
tmp->c = 'w';
tmp->d = 3.14;
printf("%d %c %lf",tmp->a,tmp->c,tmp->d);
}
void Print(struct S s)
{
printf("%d %c %lf",s.a,s.c,s.d);
}
int main()
{
struct S s = {0};//记得要初始化
Init(&s);//传址
Print(s);//传值
return 0;
}
枚举
把可能的取值一一列举
枚举类型的定义
enum Day//枚举类型
{ //可能的取值——枚举常量
MOn,
Tues,
Wed,
Thur,
Fri,
Sat,
Sun//末尾无逗号
};//结尾有分号
enum Sex
{
Male,
Female,
Secret
};
int main()
{
enum Sex s = Male;
s = Female;//可修改为三个值中的其他值
}
枚举类型的打印
enum Color
{
RED,
GREEN,
BLUE
};
int main()
{
printf("%d %d %d",RED,GREEN,BLUE);
}
//打印结果为0 1 2,默认从0开始
enum Color
{
RED = 2,
GREEN = 4,
BLUE = 8
};
int main()
{
printf("%d %d %d",RED,GREEN,BLUE);
}
//打印结果为2 4 8
//赋初值,并不是修改常量,枚举常量是不能被修改的,即不能再在int函数里修改MALE的值,且enum函数中如果已经对一个变量赋初值了,之后也不能再在enum函数里对同一变量再赋初值
enum Color
{
RED = 2,
GREEN,
BLUE
};
int main()
{
printf("%d %d %d",RED,GREEN,BLUE);
}
//结果为2 3 4
//后面的取值如果没有被特意赋初值,则默认为前一个取值的值+1
枚举类型的赋值
只能赋予定义中可能存在的取值,不能赋其他类型
enum Color
{
RED,
GREEN,
BLUE
};
int main()
{
enum Color c1 = 10;//错误写法
enum Color c2 = RED;//正确写法
}
//打印结果为0 1 2,默认从0开始
枚举的优点
为什么使用枚举而不是用#define?
- 增加代码的可维护性
- 枚举有类型检查
- 防止命名污染
- 便于调试
- 使用方便,一次可以定义多个常量
枚举的大小
enum Sex
{
Male,
Female,
Secret
};
int main()
{
enum Sex s = Male;
printf("%d\n",sizeof(s));
}
//结果为4
枚举的取值的类型由编译器指定
联合体(共用体)
结构体的定义
联合也是一种特殊的自定义类型,这种类型定义的变量也包含一系列的成员,特征是这些成员共用一块空间。
例:
union Un
{
char c;//注意是分号
int i;
};//结尾处有分号
int main()
{
union Un u;
return 0;
}
大小端存储
判断是哪种存储方式:
int main()
{
int a = 1;
if(*(char*)&a == 1)
{
printf("小端");
}
else
{
printf("大端");
}
return 0;
}
int check_sys()
{
int a = 1;
return *(char*)&a;
}
int main()
{
int a = 1;
int ret = check_sys();
if(ret == 1)
{
printf("小端");
}
else
{
printf("大端");
}
return 0;
}
int check_sys()
{
union Un
{
char c;
int i;
}u;
u,i = 1;
return u.c//1是小端,0是大端
}
int main()
{
int a = 1;
int ret = check_sys();
if(ret == 1)
{
printf("小端");
}
else
{
printf("大端");
}
return 0;
}
结构体所占用的空间大小
-
联合体的成员共用一块空间,这样一个联合变量的大小,至少是最大成员的大小
-
当最大成员大小不是最大对齐数的整数倍时,就要对齐到整数倍。
注:同一时刻,联合体内的成员是不可以被同时使用的
union Un
{
char c;//注意是分号
int i;
};//结尾处有分号
int main()
{
union Un u;
printf("%d\n",sizeof(u));
return 0;
}
//结果为4
union Un
{
int a;
char arr[5];
};//结尾处有分号
int main()
{
union Un u;
printf("%d\n",sizeof(u));
return 0;
}
//结果为8(本来需要的空间是5,最大对齐数是int的4,所以要对齐到8)