C语言 - 自定义类型

本文详细介绍了C语言中的结构体、枚举和联合的概念及使用方法。结构体允许将不同类型的数据组合在一起,可以使用typedef重命名结构体类型。枚举提供了一种定义和使用常量的方便方式,而联合则允许在相同内存位置存储不同类型的值。文章还讨论了结构体的内存对齐和位段的使用,以及结构体在函数参数传递中的性能考虑。此外,还涵盖了联合体的内存占用和使用注意事项。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

结构体

结构是一些值的集合,这些值称为成员变量。结构的每个成员变量的类型可以不同

结构体定义,声明,初始化

struct 结构体标签

{

        成员列表

} 变量列表;

#include<stdio.h>

struct Stu
{
	char name[20];
	int age;
	char sex[8];
	int id;
}s1, s2;
//s1,s2是由struct Stu创建的结构体变量,是全局变量


int main()
{
	//s3是结构体变量,局部变量
	//结构体变量s3定义并初始化
	struct Stu s3 = {"qian",20,"male",195521};
	
	//struct不能省略,否则会出错
	//Stu s4;

	return 0;
}

可以用typedef将结构体类型重命名

方式1

#include<stdio.h>

struct Stu
{
	char name[20];
	int age;
	char sex[8];
	int id;
}s1,s2;

//将struct Stu重命名为Stu
typedef struct Stu Stu;


int main()
{
	struct Stu s3;

	//此时使用Stu定义结构体变量就不会出错
	Stu s4;

	return 0;
}

方式2

#include<stdio.h>

typedef struct Stu
{
	char name[20];
	int age;
	char sex[8];
	int id;
}Stu;


int main()
{
	struct Stu s3;

	//此时使用Stu定义结构体变量就不会出错
	Stu s4;

	return 0;
}

结构体成员的访问

1.结构体变量.结构体成员

2.(*结构体指针).结构体成员

3.结构体指针->结构体成员 

#include<stdio.h>

typedef struct Stu
{
	char name[20];
	int age;
	char sex[8];
	int id;
}Stu;


int main()
{
	Stu s = { "qian",20,"male",195521 };

	Stu* ps = &s;

	//结构体变量.结构体成员
	printf("%s %d %s %d\n", s.name, s.age, s.sex, s.id);

	//(*结构体指针).结构体成员
	printf("%s %d %s %d\n", (*ps).name, (*ps).age, (*ps).sex, (*ps).id);

	//结构体指针->结构体成员 
	printf("%s %d %s %d\n", ps->name, ps->age, ps->sex, ps->id);

	return 0;
}

结构体传参

#include<stdio.h>

typedef struct Stu
{
	char name[20];
	int age;
	char sex[8];
	int id;
}Stu;

void print1(Stu s)
{
	printf("%s %d %s %d\n", s.name, s.age, s.sex, s.id);
}

void print2(Stu* ps)
{
	printf("%s %d %s %d\n", ps->name, ps->age, ps->sex, ps->id);
}

int main()
{
	Stu s = { "qian",20,"male",195521 };

	//将结构体传过去
	print1(s);

	//将结构体指针传过去
	print2(&s);

	return 0;
}

print1函数传的参数是结构体变量,而当函数调用时需要再创建一个和结构体大小相等的临时拷贝,如果这个结构体所占内存空间很大,系统开销就会很大,会影响性能

print2函数传的参数是结构体指针,当函数调用时只需要创建一个结构体指针变量用来接收,在32位机只需要花费4个字节,在64位机只需要花费8个字节,花销极大减少

因此在传参选择时,推荐选择传递结构体地址

结构体大小

结构体内存对齐

1. 第一个成员变量在与结构体变量偏移量为0的地址处
2. 其他成员变量要对齐到对齐数的整数倍的地址处

        对齐数 = 编译器默认的对齐数 与 该成员变量大小的较小值(VS中默认对齐数为8;Linux中没有默认对齐数,成员变量自身的大小就是对齐数)        

3. 结构体总大小为(所有成员变量的对齐数中)最大对齐数的整数倍
4. 如果有嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有对齐数(含嵌套结构体的对齐数)中最大对齐数的整数倍

#include<stdio.h>

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

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

struct S3
{
	char c1;
	struct S2 s;
	double d;
};

int main()
{
	printf("%zd\n", sizeof(struct S1));	//12
	printf("%zd\n", sizeof(struct S2));	//8
	printf("%zd\n", sizeof(struct S3));	//24
	return 0;
}

如上图,蓝框里面是结构体用掉的空间,蓝框中白色的部分是浪费掉的空间,在设计结构体的时候,要满足对齐,又要节省空间,应让占用空间小的成员尽量集中在一起,如上述代码中的strcut S1,struct S2,它们的成员类型都相同,但是占用的空间却不同

修改默认对齐数

#pragma pack(8)        //设置默认对齐数为8

#pragma pack( )         //取消自己设置的默认对齐数,还原为系统默认

#pragma pack(1)        //默认对齐数为1,不对齐

位段

struct 位段名

{        

        成员类型 成员名:数字 ;

}

位段的成员必须是int、unsigned int 、signed int 和 char

位段的空间需要以4个字节( int )或者1个字节( char )来开辟

#include<stdio.h>

struct S1
{
	int a : 2;		//a只占2个bit位
	int b : 5;		//b只占5个bit位
	int c : 10;
	int d : 30;
};

struct S2
{
	char a : 3;
	char b : 4;
	char c : 5;
	char d : 4;
};

int main()
{
	printf("%zd\n", sizeof(struct S1));	//8
	printf("%zd\n", sizeof(struct S2));	//3
	return 0;
}

位段可以实现和结构体同样的效果,但可以节省空间,不需要对齐,但位段会有跨平台问题,因此可移植程序应该尽量避免使用位段

在一个平台使用位段时,应先测试出位段在内存中的空间是如何开辟的,这里我用VS2022编写程序测试用例

#include<stdio.h>

struct S
{
	char a : 3;
	char b : 4;
	char c : 5;
	char d : 4;
};

int main()
{
	struct S s = { 0 };

	s.a = 14;
	s.b = 10;
	s.c = 6;
	s.d = 9;

	return 0;
}

 在其它平台用位段之前仍应测试,因为可能会有如下问题

1.int位段被当成有符号数还是无符号数不确定

2.位段中最大位的数目不能确定。16位机器最大是16,32位机器最大是32,在32位机器上用超过16的不会出现问题,而使用在16位机器上就会出现问题

3.位段中的成员在内存中从左向右分配,还是从右向左分配标准尚未定义

4.当一个结构包含两个位段,如果第一个位段成员开辟的字节剩余的位无法容纳下第二个位段成员时(如上面测试中成员a,b放入同一个字节后,剩余的位无法容纳成员c),是舍弃剩余的位还是利用,这是不确定的

枚举

enum 类型名

{

        枚举常量,

        枚举常量

};

枚举默认值从0开始,向后未赋初值的枚举常量依次加1,如不想从0开始,可以直接在定义时赋初值

枚举是常量,在赋初值后无法被修改

#include<stdio.h>

enum Color
{
	RED,
	GREEN,
	BULE
};

enum Sex
{
	MALE = 7,
	FEMALE =5,
	SECRET
};


int main()
{
	//RED = 7;	//错误:无法修改

	printf("%d\n", RED);	//0
	printf("%d\n", GREEN);	//1
	printf("%d\n", BULE);	//2

	printf("%d\n", MALE);	//7
	printf("%d\n", FEMALE);	//5
	printf("%d\n", SECRET);	//6

	return 0;
}

联合(共用体)

union Un

{

        类型 成员名;

};

联合的所有成员引用的是内存中的相同位置,在不同的时间把不同的东西存储在同一个位置

#include<stdio.h>

union S
{
	char a;
	int b;
};

int main()
{
	union S s;

	printf("%p\n", &s);			//000000D3292FFCA4
	printf("%p\n", &(s.a));		//000000D3292FFCA4
	printf("%p\n", &(s.b));		//000000D3292FFCA4

	return 0;
}

联合体的大小

至少是最大成员的大小,当这个大小不是最大对齐数的整数倍时,同样要对齐到最大对齐数的整数倍

#include<stdio.h>

union Un
{
	//当成员是数组时,对齐数是数组元素的对齐数,这里是char,对齐数为1
	char arr[5];

	int i;
};


int main()
{
	printf("%zd\n", sizeof(union Un));	//8
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值