智者不为情所困
用结构体描述一个复杂对象的基本信息:学生
struct Stu
{//Stu是结构体标签,struct Stu才是结构体类型,相当于int
char name[20];名字
int age;年龄
char sex[2];性别
char id[20];学号
};分号不能丢
数组元素可以通过下标访问,是因为数组的元素长度相同,但是结构体的成员变量的类型不同,因此不能使用下标访问结构体的成员变量
正确的结构体自引用:结构体的
struct Stu1
{
int a;
char b;
struct Stu1* c;结构体指针变量
};
错误的结构体自引用:
struct Stu2
{
int a;
char b;
struct Stu2 c;结构体
};
结构体包含一个类型为该结构体本身的成员是非法的,有点像永不终止的递归程序; 但是结构体内部包含一个指向该结构体本身的指针是合法的,在单链表中很常见
单链表中结构体中包含结构体指针的使用:
typedef struct Node
{
int date;
struct Node* ps;但不能写成ListNode* ps
//因为这个typedef声明的类型名直到声明的末尾才被定义
}ListNode;
此处的typedef是给struct Node进行了类型重命名,也类似于int,以后就可以直接用Node定义结构体变量
typedef类型重命名前定义结构体变量:
struct Node newnode;
typedef类型重命名后定义结构体变量:(是不是少些了个struct,适当偷懒)
ListNode newnode;
结构体的初始化方式和数组的初始化方式很像,结构体成员是否包含数组分别对于一维和二维数组的初始化很像
struct Stu
{
char name[20];
int age;
char sex[2];
}student1={{宋小宝},18,男};
typedef struct date
{
int year;
int month;
int day;
}Date;
struct Stu
{
Date a;
int number;
};
什么是偏移量和对齐数?
结构体内存对齐规则:
概念麻烦,实际简单的一批
给你举个例子画个图吧
struct AKIGN
{
char a;第一个数不用考虑内存对齐,直接放
int b;第二个数放的位置(偏移量)要是int字节(4)的整数倍,也就是4
char c;第三个数放的位置(偏移量)要是char字节(1)的整数倍,也就是9
};
由于结构体的整体大小就是所有最大对齐数(这里是4)的整数倍,所以结果总共是占12个字节
总之就是要综合考虑数据类型和偏移量,匹配整数倍就可以啦,是不是很简单
为什么存在内存对齐? 1.1、平台原因:不是所有的硬件平台都能访问任意地址上的任意数据,某些硬件平台只能在某些地址处取某些特定类型的数据 2、性能原因:,对于访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问,使得处理起来速度快。 本质: 空间换时间\ 怎样设计可以减少空间浪费? 使得空间小的成员尽量集中在一起
学会了?来几道测试题?
// 练习2
struct S2
{
char c1; 1
char c2; 1-2
int i; 4-8
};
printf("%d\n", sizeof(struct S2));//要是double(8)个字节的整数倍,答案:占8个字节
//练习3
struct S3
{
double d; 1-8
char c; 9
int i; 12-16
};
printf("%d\n", sizeof(struct S3));//要是double(8)个字节的整数倍,答案:占16个字节
//练习4-结构体嵌套问题
//如果嵌套了结构体,嵌套的结构体对齐到自己的最大对齐数的整数倍中
struct S4
{
char c1; 1
struct S3 s3; 8-16
double d; 16-24
};
printf("%d\n", sizeof(struct S4));//要是double(8)个字节的整数倍,答案:占24个字节
修改默认对齐数
#include <stdio.h>
#pragma pack(4)//设置默认对齐数为4
struct S1
{
char c1;
double i;默认对齐数(4)和成员变量的字节数(8)比较取最小的为默认对齐数(4):4个字节
char c2;
};
#pragma pack()//取消设置的默认对齐数,还原为默认
#pragma pack(1)//设置默认对齐数为8
struct S2
{
char c1;
int i;
char c2;
};
#pragma pack()//取消设置的默认对齐数,还原为默认
int main()
{
//输出的结果是什么?
printf("%d\n", sizeof(struct S1));答案是16个字节
printf("%d\n", sizeof(struct S2));答案是6个字节
return 0;
}
结构体传参的两种方式:(不考虑效率而言)
1.传结构体本身:
print_struct(struct Stu);
2.传结构体地址:
print_struct(&struct Stu);
关于效率高的传参方式:传结构体指针
struct S
{
int data[1000];
int num;
};
struct S s = {{1,2,3,4}, 1000};
//结构体传参
void print1(struct S s)
{
printf("%d\n", s.num);
}
//结构体地址传参
void print2(struct S* ps)
{
printf("%d\n", ps->num);
}
int main()
{
print1(s); //传结构体
print2(&s); //传地址
return 0;
}
有的时候我们存放的一个数据并不需要一个字节那么大的空间,而只需要几个比特位就够了,比如我们用二进制表示灯泡的开关,只需要0和1就足够表示这两种状态,那么怎么实现这样减少空间的浪费呐?这就需要用到我们强大的位段。
位段和结构体的声明的区别:
struct S
{
char a : 3;
char b : 4;
char c : 5;
char d : 4;
};
int main()
{
struct S s = { 0 };
s.a = 10;
s.b = 12;
s.c = 3;
s.d = 4;
printf("%d\n", sizeof(struct S));
}
注意:注重可移植性的程序应该避免使用位段:(理由如下)
联合体:联合体就是共用同一块内存
#include<stdio.h>
union UN//如果省去UN就是匿名结构体类型,只能使用一次
{
int a;
char ch;
};
int main()
{
//判断猜测:共用同一块内存
union UN u;
printf("%p\n", &(u.ch));//地址1
printf("%p\n", &(u.a));//地址1
printf("%d\n", sizeof(u));//4
u.a = 1;
printf("%d\n", u.a);//1
//通过共用一块内存通过改变u.c就能改变u.a
u.ch = 0;
printf("%d\n", u.ch);//0
//判断是大端还是小端
printf("%d\n", u.ch);//0
return 0;
}
判断机器是大端机还是小端机:
union un
{
int a;
char c;
};
int test1()
{
int n = 1;;
return *(char*)&n;
}
int test2()
{
union un u;
u.a = 1;
return u.c==1;
}
//这里都是使得int类型的变量赋值为1,也就是十六进制的都是00 00 00 01,然后通过让char或者联合本身的特点访问其中的低地址的内容,如果最低的字节内容是1,则是整数的最低字节的内容放在了低地址处,则是小端,反之。。
int main()
{
int ret=test1();
if (ret == 0) printf("大端\n");
else if(ret==1) printf("小端\n");
}
计算联合体的大小: 1.