C语言自定义类型结构体详解

C语言中,有内置类型,也就是C语言本身支持的现成的类型,例如int , char, double, float 等,当然,仅有内置类型是不够的,例如,我们想表示一个变量人,但是现有的类型不支持我们表示,因此,我们引入了自定义类型,自定义类型分为:

cpp 复制代码
//结构体 struct
//枚举 - enum	
//联合体 - union

现在,我们介绍结构体的内容。

1.结构体类型的声明,创建和初始化

结构体声明的方式如下:

cpp 复制代码
strcut tag    //tag是结构体的类型
{
	member - list //成员,一个或多个
}vaiable - list //变量名

例如,我们描述一个学生:

cpp 复制代码
struct Stu
{
    char name[20];
    int age;
    char sex[5];
};

或者,我们可以描述一本书

cpp 复制代码
struct Book
{
	char name[20];
	char author[20];
	float price;
	char id[13];
	//...
};

当我们想尝试创建变量时候,可以有哪些方式来创建呢?

(1)创建全局变量的方式

cpp 复制代码
struct Book
{
	char name[20];
	char author[20];
	float price;
	char id[13];
	//...
}b3,b4; //全局变量

struct Book b2; //全部变量创建

这样进行创建时,得到的都是全局变量

(2)局部变量创建的方式

cpp 复制代码
int main()
{
	struct Book b1 = {"hello world","zhangsan",18.8,"PG1001"}; //局部变量创建
	struct Book b6 = { .id = "PG1002",.author = "lisi",.name = "我叫李四",.price = 88.8 };
	struct Book b[5];//数组
	
	r

这里创建的b1和b6都是局部变量,甚至我们可以创建一个结构体的数组,当然,我们在进行变量初始化的时候,也可以有上面两种初始化的方式,第一个是按照顺序初始化,第二个是.+对应内容进行初始化

2.结构体的特殊声明

在结构体的声明时,可以出现不完全声明 - 匿名结构体类型,它的形式如下:

cpp 复制代码
struct 
{
	char c;
	int i;
	double d;
}s;

这里,我们就创建了一个匿名结构体,后续我们使用的时候,直接用s即可

cpp 复制代码
int main()
{
	printf("%c %d %f", s.c, s.i, s.d);

	return 0;
}

那么,我们如果进行这样的声明:

cpp 复制代码
struct 
{
	char c;
	int i;
	double d;
}s;

struct
{
	char c;
	int i;
	double d;
}*ps;
cpp 复制代码
int main()
{
	ps = &s; //编译器会把上面两个定义为不同的,因此非法

	return 0;
}

这样是不可以的,因为编译器会把以上的两个声明当成两个完全不同的类型,因此是非法的,一般来说,匿名结构体类型,如果没有对结构体重命名的话,基本只能使用一次。

3.结构体自引用的方式

数据结构是数据在内存中存储和组织的结构,有线性数据结构:顺序表,链表,栈,队列

有树形数据结构:二叉树,还有图等

我们可以定义链表的结点:

cpp 复制代码
typedef struct 
{
	int data;
	struct Node* next; //下一个节点的地址,结构体指针
}Node;

这种书写方式,后续我们可以直接用Node来定义链表的结构体了

当然,也可以写成这样:

cpp 复制代码
struct Node
{
	int data;
	struct Node* next; //下一个节点的地址,结构体指针
};
typedef struct Node Node;

请注意,匿名的结构体类型是不能实现结构体的自引用效果的

4.结构体内存对齐

现在我们来讨论结构体的大小,它是符合结构体的对齐规则的:

对齐规则:

1.结构体第一个成员对齐的偏移量为0

2.其他成员变量要对其到某个数字的整数倍的地址处

对齐数 = 编译器默认的一个对其数与成员变量大小的较小值

VS默认为8,Linux中gcc没有默认对齐数,对齐数就是成员自身大小

3.结构体总大小必须为最大的对齐数的整数倍

4.如果嵌套了结构体,嵌套的结构体对齐到自己成员中最大对齐数的整数倍,结构体整体大小就是所有最大对齐数整数倍

现在,我们来举出一个例子,来判断结构体的大小吧

cpp 复制代码
struct S
{
	char c1;//1
	int i;//4
	char c2;//1
};

这个结构体是占据12个字节的

这个结构体

cpp 复制代码
struct S
{
	char c1;//1
	char c2;//1
	int i;//4
};

它占据几个字节呢?

我们再看这个结构体

cpp 复制代码
struct S
{
	double d;//8
	char c;//1
    int i;//4

};

现在,我们看这个结构体的大小

这是一个结构体中有另一个结构体

那么,为什么存在内存对齐呢?

事实上,这是拿空间换取时间的做法,

  1. 平台原因(移植原因):不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定 类型的数据,否则抛出硬件异常。

  2. 性能原因: 数据结构(尤其是栈)应该尽可能地在⾃然边界上对⻬。原因在于,为了访问未对⻬的内存,处理器需要 作两次内存访问;⽽对⻬的内存访问仅需要⼀次访问。假设⼀个处理器总是从内存中取8个字节,则地 址必须是8的倍数。如果我们能保证将所有的double类型的数据的地址都对⻬成8的倍数,那么就可以 ⽤⼀个内存操作来读或者写值了。否则,我们可能需要执⾏两次内存访问,因为对象可能被分放在两 个8字节内存块中。

在设计结构体的时候,我们尽量让占用空间小的成员集中在一起,可以节省空间

修改默认对齐数

我们可以修改默认对齐数,方法如下:

cpp 复制代码
#pragma pack(1) //设置默认对齐数
//修改默认对齐数
int main0()
{

	return 0;
}

#pragma pack() //取消

5.结构体的传参

在结构体中,我们传参时尽量传入结构体的地址,这样可以节省时间和空间

例如,我们定义了一个结构体

cpp 复制代码
struct S1
{
	int arr[100];
	int n;
	double d;

};

现在我们想写一个函数,输出这个结构体的某些值

我们一般会这样写:

cpp 复制代码
void print1(struct S tmp)	//空间和时间的双重浪费
{
	int i = 0;
	for (i = 0; i < 5; i++)
	{
		printf("%d ", tmp.arr[i]);
	}
	printf("%d ", tmp.n);
	peintf("%lf ", tmp.d);
}

但是,tmp会进行拷贝,这样不仅占用空间,也造成了时间的浪费,但如果我们传入的是结构体的地址呢?

cpp 复制代码
void print2(const struct S* ps)
{
	int i = 0;
	for (i = 0; i < 5; i++)
	{
		printf("%d ", ps->arr[i]);

	}
	printf("%d ", ps->n);
	printf("%lf", ps->d);
}

这样,就可以了

6.结构体实现位段

什么是位段?

位段的声明和结构是类似的,有两个不同: 1. 位段的成员必须是 int、unsigned int 或signed int ,在C99中位段成员的类型也可以 选择其他类型。 2. 位段的成员名后边有⼀个冒号和⼀个数字。

例如,结构体的形式如下:

cpp 复制代码
struct S
{
	int _a;	
	int _b;	
	int _c;
	int _d;
};

而位段的形式如下:

cpp 复制代码
struct S
{
	int _a:2;	//只占两个比特位
	int _b:5;	//只占五个比特位
	int _c:10;
	int _d:30;
};

冒号后面的代表这个参数占据的空间大小

那现在,我们就来判断一下这个位段的大小吧!

但是,位段存在跨平台问题,原因如下:

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

网络协议中,位段可以有很好的效果

在位段的使用时,我们应该注意,某些位段并不在字节初始的位置,而一个字节对应一个地址,因此我们不能直接使用scanf的方式给位段赋值,我们应该先创建并赋值一个变量,然后利用 = 来赋值

相关推荐
yufei-coder1 分钟前
C#基础语法
开发语言·c#·.net
长天一色2 分钟前
【ECMAScript 从入门到进阶教程】第三部分:高级主题(高级函数与范式,元编程,正则表达式,性能优化)
服务器·开发语言·前端·javascript·性能优化·ecmascript
_.Switch13 分钟前
Python机器学习模型的部署与维护:版本管理、监控与更新策略
开发语言·人工智能·python·算法·机器学习
醉颜凉16 分钟前
银河麒麟桌面操作系统修改默认Shell为Bash
运维·服务器·开发语言·bash·kylin·国产化·银河麒麟操作系统
NiNg_1_23422 分钟前
Vue3 Pinia持久化存储
开发语言·javascript·ecmascript
带带老表学爬虫30 分钟前
java数据类型转换和注释
java·开发语言
qianbo_insist33 分钟前
simple c++ 无锁队列
开发语言·c++
zengy534 分钟前
Effective C++中文版学习记录(三)
数据结构·c++·学习·stl
BigYe程普43 分钟前
我开发了一个出海全栈SaaS工具,还写了一套全栈开发教程
开发语言·前端·chrome·chatgpt·reactjs·个人开发
彭于晏6891 小时前
Android广播
android·java·开发语言