结构体的概念
结构体说直白点就是自定义类型,c语言有很多内置的类型比如char,int,double等,而结构体就是我们自己命名的一种类型。
区别在于内置类型大多都是单一描述的类型,比如 char studentname='wangwu',只能描述一种类型。学生的属性有很多,比如char 类型的名字,int 类型的学号,double类型的分数。当然你也可以挨个去定义,比如 char name[]="wangwu",int sno=2425678,double scole=85.6;这样只能表示一个学生,如果再来一个学生zhangsan是不是又得写三行
但是这样就显得很麻烦,而为了解决这个问题,c语言就增加了结构体这种自定义数据类型。
总得来说,对于内置类型,结构体有两个不同,一.是可以自定义类型的。二.可以包含多个类型的数据
一个简单的结构体例子:学生结构体
struct student
{
char name[20];
int sno;
int age;
int sex;
double scole;
};
struct 标签(随便起个名字)
{
member-list;成员列表
}variable-list;(变量列表)这里有没有变量列表无所谓,但是分号不能少
结构体的初始化
第一种主函数内全局变量定义初始化
#include<stdio.h>
struct student
{
char name[20];
int sno;
int age;
const char*sex;
double scole;
};
int main()
{
struct student student1={ "zhangsan",20230917,20,"男",98};
}
这种形式必须一一对应,顺续不能变,数量也不能少
第二种初始化方法,指定顺序初始化,这种类型可以不按顺序来访问,直接点符号访问(请不要在不支持C99或更高版本的C++编译器.cpp源文件上用这个,会报错)
#include<stdio.h>
struct stu
{
char name[20]; //名字
int age; //年龄
float score;
};
int main()
{
struct stu s5 = { .score = 98.5f,.name = "hehe",.age = 100.0f };//想要先初始化什么直接用点.操作符就行了
}
第三种全局变量初始化方式,直接在结构体末尾使用
#include<stdio.h>
struct stu
{
char name[20]; //名字
int age; //年龄
float score;
}s5 = { .score = 98.5f,.name = "hehe",.age = 100.0f }, s6 = {"wangwu",20,96};//不用加struct stu的头缀了,同样可以用点操作符改变顺序
int main()
{
return 0;
}
结构体的类型
char a类型是char,int a的类型是int
上面的学生结构体,它的类型不是stu,而是struct stu。
#include<stdio.h>
struct stu
{
char name[20]; //名字
int age; //年龄
float score;
}student;
上面代码的student是类型名吗,其实不是,这个结构体类型依旧是struct stu,而student是变量名
typedef struct DNode
{
Elemtype data;
struct DNode * prior;
struct DNode * next;
}DLinkNode;
上面代码是数据结构中常用的结构体表现形式,此时这个结构体可以说类型是DLinkNode
typedef关键字可以给类型起一个别名,比如typedef char Elemtype就是把char类型起了一个别名Elemtype,此时Elemtype就等价char
同样 上述代码中DLinkDode也等价于struct DNode
结构体变量的访问
#include<stdio.h>
struct student
{
char name[20];
int sno;
int age;
const char*sex;
double scole;
};
int main()
{
struct student student1={ "zhangsan",20230917,20,"男",98};
}
这个结构体已经初始化了student1,如果要求打印出来该怎么操作呢
1.点(.)操作符直接访问
#include<stdio.h>
struct student
{
char name[20];
int sno;
int age;
const char* sex;
double scole;
};
int main()
{
struct student student1 = { "zhangsan",20230917,20,"男",98 };
printf("名字:%s 学号:%d 年龄:%d 性别:%s 分数:%.2lf", student1.name, student1.sno, student1.age, student1.sex, student1.scole);
}
需要注意的是,此时打印是不需要加上struct的,student1仅仅起一个标识作用
举个例子,char i=5;肯定是printf("%c",i),而不是printf("%c",char i);
2.结构体成员的间接访问(结构体指针访问)
#include <stdio.h>
struct student
{
char name[20];
int sno;
int age;
const char* sex;
double scole;
};
int main()
{
struct student student1 = { "zhangsan",20230917,20,"男",98.5 };
struct student* ptr = &student1;
printf("%s %d %d %s %.1f\n", ptr->name, ptr->sno, ptr->age, ptr->sex, ptr->scole);
printf("%s %d %d %s %.1f\n", student1.name, student1.sno, student1.age, student1.sex, student1.scole);
}
ptr->name 不是地址或指针,而是通过指针 ptr 访问结构体中的成员变量 student1中的name,就等价于student1.name
其实也可以等价于(*ptr).name,ptr保存的是变量student1的地址,ptr 存储的是结构体的地址,通过解引用 ptr,我们可以获取结构体的内容,进而访问结构体变量student1的成员变量 name。
既然是解引用,那能不能改变值,数组里解引用然后赋值是可以改变原来的值的
直接写成解引用的形式然后赋值也是可以改变值的
结构体嵌套
结构体嵌套初始化
直接在最外层结构体那里一起初始化就行,大圈套小圈而已
嵌套结构体的访问
点操作符直接访问
先用点操作符找到外层结构体Node的里层结构体变量p,然后p通过点操作符去访问p自己里面的内容x,y
结构体指针访问
可不要用ptr->p->x去访问,用箭头去访问的前提是它是个指针,可是这里p可不是指针变量
结构体大小(内存对齐)
如果求一个整型数据的大小,sizeof(int),它的结果是4,求一个字符char的字节大小,它的结果是1。可是结构体里面有多个成员,各个成员的类型也不完全就是相同的,sizeof结构体的大小是多少呢
示例
#include<stdio.h>
struct student
{
char name[5];
int sno;
int age;
double scole;
};
int main()
{
int ret=sizeof(struct student);
printf("%d", ret);
}
上面那个结构体按照正常的思维存,应该是如下图所示先存char数组的5个字节,然后两个int共8个字节,再然后double类型8个字节,总共21个字节
然而运行起来实际结果是24
0到23正好24个字节,所以结果是24,int name上面编号6和7虽然没存任何东西,但是计算大小还是要计算进来的。
结构体内存对齐的规则
1.结构体的第⼀个成员对⻬到和结构体变量起始位置偏移量为0的地址处
2.其他成员变量要对⻬到某个数字(对⻬数)的整数倍的地址处。
对⻬数=编译器默认的⼀个对⻬数与该成员变量⼤⼩的较⼩值。
VS 编译器中默认的值为 8
Linux中gcc没有默认对⻬数,对⻬数就是成员⾃⾝的⼤⼩
3.结构体总⼤⼩为最⼤对⻬数(结构体中每个成员变量都有⼀个对⻬数,所有对⻬数中最⼤的)的
整数倍。
拿上面那个示例总结一下,第一个结构体变量char name[5]起始位置直接默认从0开始就行,剩下的都要去找默认对齐数的倍数开始存。
name存完了后,要开始处理int sno,VS编译器中默认的对齐数是8,而此时int类型是4个字节,它比给的默认值还小,所以对齐数变为4,要去找4的倍数作为int name存储的起点。找下一个成员int age的存储的起点方法与int name类似
最后一个成员double,它的字节大小为8,与默认对齐数相等,所以对齐数就为8。找8的倍数作为doubie存储的起点,所以就是16了。
规则3是什么意思呢,比如说我最后22个字节就存储完了,但是最大对齐数的是8,所以我最后的结果也应该是8的倍数24,而不是22
再来一个示例
#include<stdio.h>
struct student
{
int sno;
char name[9];
int age;
double scole;
};
int main()
{
int ret=sizeof(struct student);
printf("%d", ret);
}
这个示例中int sno 虽然对齐数是4,但是第一个结构体成员默认从0作为起点。char name[9]总字节是9,而VS编译器默认对齐数是8,所以最小对齐数是8,应该去找8的倍数当作char name[9]的起点
嵌套结构体的大小(嵌套结构体的内存对齐)
如果嵌套了结构体的情况,嵌套的结构体成员对⻬到⾃⼰的成员中最⼤对⻬数的整数倍处,结构
体的整体⼤⼩就是所有最⼤对⻬数(含嵌套结构体中成员的对⻬数)的整数倍。
#include<stdio.h>
struct s3
{
double d;
char c;
int i;
};
struct s4
{
char c1;
struct s3 student3;
double d;
};
int main()
{
printf("%d\n", sizeof(struct s4));
};
要计算嵌套结构体s4的大小,要先计算出被嵌套结构体s3的大小,s3的大小如上图所示为16
s4第一个成员char c1默认存储的起点从0开始,不用考虑。那么第二个成员struct s3 student3从哪里开始呢.一般来讲判断存储的起点要找对齐数,而嵌套结构体对齐数就等于它里面成员最大对齐数。比如s3 它的最大对齐数是double是8,所以整个嵌套结构体s3存储起点要找8的倍数。s4的内存图示如下
0到31正好32个字节存完,要求整个结构体的大小,要求是最后结果是最大对齐数的倍数,而嵌套结构体要把里面的最大对齐数加入比较。s3里面最大对齐数是8,s4里还有一个double类型,所以最大对齐数应该是8的倍数。而32是8的倍数,所以结果是32