1.结构体
结构体:结构是⼀些值的集合,这些值称为成员变量。结构的每个成员可以是不同类型的变量。
结构体是一种数据类型,是创建变量的模板,不占用内存空间;结构体变量才包含了实实在在的数据、需要存储空间;
1.1结构体的定义形式和初始化
struct tag
{
member-list;
}variable-list;
结构体定义的基本原则:
1. 定义结构体之后一定要以分号结束 2. 结构体中不能定义函数
形式一:
//最基本形式
struct Student{
int number; //学号
int age;
bool gender; //性别,1女,0男
};
//单独定义结构体变量,按照顺序初始化
struct Student stu1={2,3,1};
//按照指定顺序初始化
struct Student stu2={.age = 3, .name = 2, .gender = 1};
形式二:
//在定义结构体变量的同时就进行变量声明
#include <stdio.h>
#include<stdbool.h>
struct Student{
int age;
int number;
bool gender;
}stu1,stu2={1,2,0};
//初始化
int main()
{
struct Student stu1 = { 18,1,0 };
struct Student stu3 = { 19,1,0 };
printf("%d,%d,%d\n", stu1.age, stu1.number, stu1.gender);
printf("%d,%d,%d\n", stu2.age, stu2.number, stu2.gender);
printf("%d,%d,%d\n", stu3.age, stu3.number, stu3.gender);
return 0;
}
形式三:匿名结构体
该种方法并没有定义结构体名称,不能再在后续代码中声明该结构体变量,因此只能存在stu1,stu2两个结构体变量
通常我们在嵌套结构或联合中使用它们。
//不定义结构体名称,在定义结构体时直接声明结构体变量
struct {
int number;
int age;
bool gender;
}stu1,stu2;
匿名结构体的使用:结构体嵌套结构体使用
#include <stdio.h>
#include<stdbool.h>
struct person
{
char name[20];
struct
{
int number;
int age;
bool gender;
};
};
//初始化
int main(void)
{
struct person x = { "zhangsan",1,18,1};
printf("stu1 = %s,%d,%d,%d", x.name,x.number, x.age, x.gender);
return 0;
}
提前对外部结构体创建结构体变量
提前对匿名结构体创建结构体变量
#include <stdio.h>
struct person
{
char name[20];
struct
{
int number;
int age;
}stu1, stu2;
}a;
//初始化
int main(void)
{
struct person a = { "z",{1,18},{2,19} };//两个结构体,分开初始化
printf("%s\n", a.name);
printf("stu1 = %d,%d\n", a.stu1.number, a.stu1.age);
printf("stu2 = %d,%d\n", a.stu2.number, a.stu2.age);
return 0;
}
形式四:
typedef struct{
int number;
int age;
}Stu;
//初始化
int main(){
Stu stu1={1,2};
printf("学号是%d\n",stu1.number);
printf("年龄是%d\n",stu1.age);
}
形式五:
//使用typedef加上struct定义结构体
typedef struct Student{
int number;
int age;
bool gender;
}Stu;
//声明结构体变量
Stu stu1,stu2;
结构体指针变量
结构体嵌套结构体
1.3结构体成员访问和传参
1.3.1结构体成员访问和传参
结构体变量使用 . 访问;
结构体变量.对象
struct Stu s = { "张三", 20, "男", "20230818001" };
printf("name: %s\n", s.name);
printf("age : %d\n", s.age);
printf("sex : %s\n", s.sex);
printf("id : %s\n", s.id);
struct Stu s2 = { .age = 18, .name = "lisi", .id = "20230818002", .sex = "⼥
printf("name: %s\n", s2.name);
printf("age : %d\n", s2.age);
printf("sex : %s\n", s2.sex);
printf("id : %s\n", s2.id);
注意:对结构体变量 整体赋值 有三种情况
(1)定义结构体变量(用{}初始化)
(2)用已定义的结构体变量初始化
(3)结构体类型相同的变量可以作为整体相互赋值;
在C语言中不存在结构体类型的强制转换。
ansi/iso C规定:“相同类型的结构体是可以直接赋值的”; 合法赋值: 如date1和date2都是date结构体类型的变量,可以这样赋值:date1= date2; 非法赋值: 假设申明了两个成员列表完全一样的两个结构体类型(dataA,dateB),即使他们的成员列表是一样的,编译器会当作两个完全不同的类型,令两个结构体类型的变量间赋值,则是非法的。 但是这种问题不能靠试验获得准确的答案,因为你用的编译器支持并不表明其他编译器也支持。ansi/iso C规定的合法行为,编译器是必须支持的。
#include "stdio.h"
struct date
{
int i;
float x;
} d1={20,9.1};
void main()
{
struct date d2;
d2=d1;
printf("%d, %f\n",d1.i,d1.x);
printf("%d, %f\n",d2.i,d2.x);
}
1.3.2结构体变量和指针
结构体类型指针访问成员的获取和赋值(传参)形式:
(1)(*p). 成员名(.的优先级高于*,(*p)两边括号不能少)
(2) p->成员名(->指向符)
#include<stdio.h>
#include<string.h>
//#define _CRT_SECURE_NO_WARNINGS
struct Inventory//商品
{
char description[20];//货物名
int quantity;//库存数据
};
int main()
{
struct Inventory sta = { "iphone",20 };
struct Inventory* stp = &sta;
char name[20] = { 0 };
int num = 0;
(*stp).quantity = 30;
stp->quantity = 30;
strcpy_s(name,sizeof(stp->description),stp->description);
printf("%s %d\n", stp->description, stp->quantity);
printf("%s %d\n", (*stp).description, (*stp).quantity);
return 0;
}
1.3.3结构体和函数
#include<stdio.h>
#include<string.h>
#define _CRT_SECURE_NO_WARNINGS
struct School
{
char s_name[20];//学校
int s_age;
};
void Print_a(struct School sx)
{
printf("%s %d\n", sx.s_name, sx.s_age);
}
void Print_c(struct School* sp)
{
printf("%s %d\n", sp->s_name, sp->s_age);
}
int main()
{
struct School sx = { "xi'an",100 };
Print_a(sx);
Print_c(&sx);
return 0;
}
结构体传参是对原数据进行了临时拷贝在进行传参,有空间浪费
结构体指针地址传参的好处:
函数传参的时候,参数是需要压栈,会有时间和空间上的系统开销。
如果传递⼀个结构体对象的时候,结构体过⼤,参数压栈的的系统开销⽐较⼤,所以会导致性能的下 降。
1.4结构体与数组
结构体数组,是指数组中的每一个元素都是一个结构体类型
#include<stdio.h>
#include<string.h>
#define _CRT_SECURE_NO_WARNINGS
struct Student
{
char s_name[20];//姓名
int age;//年龄
float score;//成绩
};
int main()
{
struct Student cla[] =
{
{"liuwen",18,149.9},
{"qnge",18,145},
{"anan",19,188},
};
return 0;
}
1.5结构体内存对齐
1.5.1 对齐规则
结构体的第⼀个成员对⻬到和结构体变量起始位置偏移量为0的地址处
其他成员变量要对⻬到某个数字(对⻬数)的整数倍的地址处。
对⻬数 = 编译器默认的⼀个对⻬数 与 该成员变量⼤⼩的较⼩值。
VS 中默认的值为 8
Linux中 gcc 没有默认对⻬数,对⻬数就是成员⾃⾝的⼤⼩
结构体总⼤⼩为最⼤对⻬数(结构体中每个成员变量都有⼀个对⻬数,所有对⻬数中最⼤的)的 整数倍。
如果嵌套了结构体的情况,嵌套的结构体成员对⻬到⾃⼰的成员中最⼤对⻬数的整数倍处,结构 体的整体⼤⼩就是所有最⼤对⻬数(含嵌套结构体中成员的对⻬数)的整数倍
结构体内的数据对齐是看下标索引是否是整数倍,结构体整体看总字节数是否是整数倍
结构体内存存在对齐的原因:结构体的内存对⻬是拿空间来换取时间的做法。
1.5.2修改默认对齐数
如何满足既对齐又节省空间?
#pragma
这个预处理指令,可以改变编译器的默认对⻬数
此时我们把默认对齐数改为1,计算方法跟结构体相同
#include <stdio.h>
#pragma pack(1)//设置默认对⻬数为1
struct S
{
char c1;
int i;
char c2;
};
#pragma pack()//取消设置的对⻬数,还原为默认
int main()
{
//输出的结果是什么?
printf("%d\n", sizeof(struct S));
return 0;
}
1.6结构体实现位段
1.6.1什么是位段
位段的声明和结构是类似的,有两个不同:
位段的成员必须是 int、unsigned int 或signed int ,在C99中位段成员的类型也可以 选择其他类型。
位段的成员名后边有⼀个冒号和⼀个数字。
struct A
{
int _a:2;
int _b:5;
int _c:10;
int _d:30;
};
1.6.2位段的内存分配
1.位段的成员可以是 int, unsigned int, signed int 或者是 char 等类型
位段的空间上是按照需要以4个字节( int )或者1个字节( char )的⽅式来开辟的。
位段涉及很多不确定因素,位段是不跨平台的,注重可移植的程序应该避免使⽤位段。
//⼀个例⼦
struct S
{
char a:3;
char b:4;
char c:5;
char d:4;
};
struct S s = {0};
s.a = 10;
s.b = 12;
s.c = 3;
s.d = 4;
//空间是如何开辟的?
位段分配的内存中的比特位是从右向左使用的,分配剩余的比特位不够使用时,浪费掉剩余内存。
如下,由于位段的限制,二进制存储时要舍弃高位,留下低位
1.6.3位段注意点
注意一:位段占的二进制位数不能超过该基本类型所能表示的最大位数,即位段不能跨字节存储,比如在char是占1个字节,那么最多只能是8位;
struct S
{
char a:9;//err
};
注意二:在位段中要给位段的成员起名字
无名的位段写在结构体中会计入空间大小,但不能对其进行使用
struct S
{
char a:3;
char b:4;
char c:5;
char : 8;//err,无名的位段不能使用
};
int main()
{
struct A s = { 'a','b','c','d' };//此时字符'd不能存储到位段中'
return 0;
}
注意三:不能对位段进行取地址操作
struct A {
char _a : 2;
char _b : 5;
char _c : 8;
char : 8;
}s;
int main()
{
printf("%d\n", sizeof(struct A));
char *p = &(s._a);这里对位段中的元素取地址操作
return 0;
}
注意四:如果位段中存储的数据大于位段自身大小就会发生截断问题,在输出时会将截断之后存储在该字节(char类型为例)的最高位当作符号位
struct A {
char a : 2;
char b : 5;
char c : 8;
char d: 8;
}s;
int main()
{
s.a = 7;//111
s.b = 51;//1 10011
printf("%d\n", s.a);
printf("%d\n",s.b);
return 0;
}
1001111二进制转换为十六进制就是4F,在内存中存储的就是4F
在输出位段的具体值时,可以知道系统将最高位看作了符号位进行输出
那么我们将位段类型转换为无符号是否能打印正确的值呢
struct A {
unsigned char a : 2;
unsigned char b : 5;
char c : 8;
char d: 8;
}s;
int main()
{
s.a = 7;//111
s.b = 51;//1 10011
printf("%d\n", s.a);
printf("%d\n",s.b);
return 0;
}
1.7位段的跨平台问题
int位段被当成有符号数还是⽆符号数是不确定的。
位段中最⼤位的数⽬不能确定。(16位机器最⼤16,32位机器最⼤32,写成27,在16位机器会 出问题。
位段中的成员在内存中从左向右分配,还是从右向左分配标准尚未定义。
当⼀个结构包含两个位段,第⼆个位段成员⽐较⼤,⽆法容纳于第⼀个位段剩余的位时,是舍弃 剩余的位还是利⽤,这是不确定的。
总结: 跟结构相⽐,位段可以达到同样的效果,并且可以很好的节省空间,但是有跨平台的问题存在。
1.8位段的应用
下图是⽹络协议中,IP数据报的格式,我们可以看到其中很多的属性只需要⼏个bit位就能描述,这⾥ 使⽤位段,能够实现想要的效果,也节省了空间,这样⽹络传输的数据报⼤⼩也会较⼩⼀些,对⽹络 的畅通是有帮助的