结构体相关知识

结构体的概念

结构体说直白点就是自定义类型,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
struct student
{
	char name[20];
	int sno;
	int age;
	const char*sex;
	double scole;
};
int main()
{
	struct student student1={ "zhangsan",20230917,20,"男",98};
}

 这种形式必须一一对应,顺续不能变,数量也不能少

结构体相关知识_第1张图片

第二种初始化方法,指定顺序初始化,这种类型可以不按顺序来访问,直接点符号访问(请不要在不支持C99或更高版本的C++编译器.cpp源文件上用这个,会报错)

#include
struct stu
{
	char name[20]; //名字
	int age; //年龄
	float score;
};
int main()
{
	struct stu s5 = { .score = 98.5f,.name = "hehe",.age = 100.0f };//想要先初始化什么直接用点.操作符就行了
}

第三种全局变量初始化方式,直接在结构体末尾使用
  

#include
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
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
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
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);
}

结构体相关知识_第2张图片

需要注意的是,此时打印是不需要加上struct的,student1仅仅起一个标识作用

举个例子,char i=5;肯定是printf("%c",i),而不是printf("%c",char i);

2.结构体成员的间接访问(结构体指针访问)

#include 
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);
}

结构体相关知识_第3张图片

 ptr->name 不是地址或指针,而是通过指针 ptr 访问结构体中的成员变量 student1中的name,就等价于student1.name

其实也可以等价于(*ptr).name,ptr保存的是变量student1的地址,ptr 存储的是结构体的地址,通过解引用 ptr,我们可以获取结构体的内容,进而访问结构体变量student1的成员变量 name。

既然是解引用,那能不能改变值,数组里解引用然后赋值是可以改变原来的值的

结构体相关知识_第4张图片

直接写成解引用的形式然后赋值也是可以改变值的 

结构体相关知识_第5张图片

结构体嵌套

 结构体嵌套初始化

结构体相关知识_第6张图片

直接在最外层结构体那里一起初始化就行,大圈套小圈而已

嵌套结构体的访问

 点操作符直接访问

先用点操作符找到外层结构体Node的里层结构体变量p,然后p通过点操作符去访问p自己里面的内容x,y

结构体相关知识_第7张图片

结构体指针访问 

结构体相关知识_第8张图片

可不要用ptr->p->x去访问,用箭头去访问的前提是它是个指针,可是这里p可不是指针变量 

结构体大小(内存对齐)

如果求一个整型数据的大小,sizeof(int),它的结果是4,求一个字符char的字节大小,它的结果是1。可是结构体里面有多个成员,各个成员的类型也不完全就是相同的,sizeof结构体的大小是多少呢

示例

#include
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个字节

结构体相关知识_第9张图片

 然而运行起来实际结果是24

结构体相关知识_第10张图片

结构体相关知识_第11张图片

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
struct student
{
	
	int sno;
	char name[9];
	int age;
	double scole;
};
int main()
{
	int ret=sizeof(struct student);
	printf("%d", ret);
}

结构体相关知识_第12张图片

结构体相关知识_第13张图片

这个示例中int sno 虽然对齐数是4,但是第一个结构体成员默认从0作为起点。char name[9]总字节是9,而VS编译器默认对齐数是8,所以最小对齐数是8,应该去找8的倍数当作char name[9]的起点

嵌套结构体的大小(嵌套结构体的内存对齐)

如果嵌套了结构体的情况,嵌套的结构体成员对⻬到⾃⼰的成员中最⼤对⻬数的整数倍处,结构
体的整体⼤⼩就是所有最⼤对⻬数(含嵌套结构体中成员的对⻬数)的整数倍。

#include
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));
};

 结构体相关知识_第14张图片

要计算嵌套结构体s4的大小,要先计算出被嵌套结构体s3的大小,s3的大小如上图所示为16

s4第一个成员char c1默认存储的起点从0开始,不用考虑。那么第二个成员struct s3 student3从哪里开始呢.一般来讲判断存储的起点要找对齐数,而嵌套结构体对齐数就等于它里面成员最大对齐数。比如s3 它的最大对齐数是double是8,所以整个嵌套结构体s3存储起点要找8的倍数。s4的内存图示如下

结构体相关知识_第15张图片

0到31正好32个字节存完,要求整个结构体的大小,要求是最后结果是最大对齐数的倍数,而嵌套结构体要把里面的最大对齐数加入比较。s3里面最大对齐数是8,s4里还有一个double类型,所以最大对齐数应该是8的倍数。而32是8的倍数,所以结果是32

结构体相关知识_第16张图片 

 

你可能感兴趣的:(算法,数据结构)