结构体和联合体
一、结构体类型
设计结构体类型是为了把各个类型不同的相互有联系的变量放在一起,反应他们的内在联系。
1. 结构体类型的设计和初始化
下面将定义一个学生结构体:
struct Student //struct是结构体类型的关键字,Student为类型名
{
int num; //学号为整型
char name[20]; //姓名为字符串
char sex; //性别为字符型
int age; //年龄为整型
float score; //成绩为实型
}; //结构体在设计时花括号后面必须有分号
int main ()
{
struct Student s1={“1001”,”hyy”,”m”,23,105.5};
//在给结构体初始化时,必须注意要按照顺序赋值,不能将顺序打乱;
}
注意:
- 结构体之间可以嵌套:例如学生由姓名、性别、生日构成,生日由年、月、日构成。其中嵌套的结构体可以用花括号括起来,也可以不用。
- 结构体是一种数据类型,不能存放数据,不占用空间,结构体变量能存放数据需要储存空间。
- 结构体成员在内存中的储存情况也是按顺序的。

2. 结构体大小的计算
由于存储变量地址对齐的问题,计算结构体大小的3条规则:
- 结构体变量的首地址,必须是结构体变量中的“最大基本数据类型成员所占字节数”的整数倍。
- 结构体变量中的每个成员相对于结构体首地址的偏移量,都是该成员基本数据类型所占字节数的整数倍。
- 结构体变量的总大小,为结构体变量中“最大基本数据类型成员所占字节数"的整数倍。
比如计算上述结构体大小:16字节
struct node
{
char cha;
char chb;
int a; //大小为8字节
}
struct node
{
char cha;
double chb;
int a; //大小为24字节
}
嵌套结构体的计算:
struct sdate
{
int year;
int month;
int day;
};
struct Student
{
char s_id[10];
char s_name[8];
struct sdate birthday;//嵌套结构体double grade; //32字节
};
3. 结构体成员的访问和赋值
结构体变量的成员使用.访问。访问和赋值的一般格式为:结构体变量名.成员名。
struct Student
{
int num;
char name[20];
char sex;
int age;
float score;
};
int main ()
{
struct Student s1;
s1.num=1010101;
s1.age=18; //结构体变量名.成员名型式
}
注意:
- 结构体类型相同的变量可以作为整体相互赋值。在其他情况的使用过程中只能对成员逐─赋值。
- 如果结构体成员是数组时,结构体变量之间是不能相互赋值或初始化的,因为数组之间不能相互赋值,如果要赋值必须要调用字符串的拷贝函数。
- 在c语言中不存在对结构体类型的强制转换(和内置类型的区别),即标识符不同不能进行相互赋值。
4. 结构体类型和指针
(1)变量名为指针时:
内置类型能够定义指针变量,结构体类型也可以定义结构体类型的指针
结构体类型指针访问成员的获取和赋值形式:
(p).成员名(.的优先级高于,(*p)两边的括号不能少) 或 p->成员名
struct Student
{
int num;
char name[20];
};
int main()
{
struct Student s2={10101,"gaoqia"};
struct Student *s3=&s2
(*s3).num=123456; //指针指向成员赋值
s3->num=123456; //指针访问成员
}
分析下面两种打印函数,那种比较好?

答案:第二种较好,因为使用指针形式可以节省空内存间
(2)结构体赋值时容易出现的问题:
#include<stdio.h>
struct School
{
char s_name[20];
int s_age;
};
void Init_School(struct School* sp) //这里必须使用传地址的方式,传值方式不改变实参的值。
{
scanf_s("%s", sp->s_name, 20); //使用指针指向赋值对象
scanf_s("%d", &sp->s_age); //赋初值时,普通变量需要取地址符,数组不用
}
void Print_School(struct School *sp) //直接传递结构体效率太低所以用指针的方式传递
{
printf("name:%s\n", sp->s_name);
printf("age: %d\n", sp->s_age); //将sp的地址指向对象
}
int main()
{
struct School s1 = {};
Init_School(&s1); //使用传地址方式。
Print_School(&s1);
return 0;
}
如果将s_name定义成数组型式,各处变化如下:
#include<stdio.h>
struct School
{
char* s_name;
int s_age;
};
void Init_School(struct School* sp)
{
static char name[20]; //定义数组name,让s_name指向name,即开辟空间。
sp->s_name = name; //这里必须使用静态关键字,不然会有失效指针出现。
scanf_s("%s", sp->s_name, 20);
scanf_s("%d", &sp->s_age);
}
void Print_School(struct School *sp)
{
printf("name:%s\n", sp->s_name); //原本sp->s_name指向的是一个空指针不能存放数据,除非s_name指向一块空间
printf("age: %d\n", sp->s_age);
}
int main()
{
struct School s1 = {};
Init_School(&s1);
Print_School(&s1);
return 0;
}
在结构体指针成员赋值时,指针成员应该指向一个空间,在函数中定义一个数组让指针指向之后,函数调用结束,系统分派的栈帧空间被释放,指针失效,打印的是乱码,所以应该用静态关键字。
(3)结构体成员为指针时
当结构体成员的类型为指针类型时,储存的方式和其他类型有所不同,栈帧中为指针开辟空间,指针所指向的字符串存在.date区,也是只读区,由指针指向而已。如下图:

两者对比:
如图1所示的结构体,在主函数中定义结构体也是不能编译通过的,因为结构体虽然可以嵌套,但是这样无穷的嵌套不允许有,执行时会发生溢出.
如果将studx,定义为*studx就可以,因为指针不是实实在在的学生结构体变量,它只占了四字节。

二、联合体类型
联合体:对同一内存空间采取不同类型进行解释,联合体变量的长度时成员最长的长度,联合体变量赋值时,新的值会冲掉旧的值。
1. 联合体的三种定义方式
关键字:union
2. 联合体的内存分配
两种方式占用内存的方式不同如下图:

联合体的大小:是联合体内最长字节成员变量的字节数
3. 结构体嵌套联合体的内存大小的计算

4. 联合体的使用

**点分十进制:**我们的ip地址就是联合体类型的

点分十进制的具体实现:
union IPNode
{
unsigned int addr;
strust
{
unsigned char s4;
unsigned char s3;
unsigned char s2;
unsigned char s1;
};
};
void uint_to_ipstr(unsigned int ip ,char*buff)
{
if(buff==nullptr)return ;
IPNode x={};
x.addr=ip;
printf("%d.%d.%d.%d\n",x.s1,x.s2,x.s3,x.s4);
printf输出函数是将数据格式化之后打印到屏幕上
Sprintf函数是将数据格式化之后缓存到缓冲区buff中,sprintf_s(buff,300,”%d”,a)
本文详细介绍了C语言中的结构体和联合体类型。对于结构体,讲解了设计与初始化、大小计算、成员访问与赋值以及结构体与指针的关系。在结构体与指针部分,讨论了指针作为结构体变量、结构体赋值问题及结构体成员为指针的情况。而对于联合体,阐述了其定义方式、内存分配、嵌套结构体的大小计算以及联合体的使用场景,特别是点分十进制IP地址的表示。

1030

被折叠的 条评论
为什么被折叠?



