C语言学习笔记(九)自定义数据类型—结构体、枚举、联合体

发布时间 2023-08-10 15:46:41作者: Arongsec

九、自定义数据类型—结构体

自定义类型:结构体、枚举、联合体

结构体的声明

//声明一个结构体类型
//声明一个学生类型,想通过学生类型来创建一个学生变量(对象)
//描述学生的属性:名字、电话、性别、年龄
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;
}

特殊的声明

  1. 匿名结构体类型

​ 没有具体的名字(如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;
}

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

定义变量

  1. 在创建类型是在最后定义变量
  2. 用创建好的结构体类型创建变量

初始化

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)