前言:在C语言中,我们知道有这么几种基本数据类型,包括整型(char 、 short、 int、long 、long long),浮点型(float 、 double)。这些都是C语言已经规定好了的数据类型,那么能不能自己定义一个类型呢?事实上是可以的,结构体就是其中的一种。
接下来我会以这五点来使你了解结构体究竟是何物:
目录
1.结构体的定义规则
结构体定义由关键字 struct 和结构体名组成,结构体名可以根据需要自行定义。struct 语句定义了一个包含多个成员的新的数据类型,struct 语句的格式如下:
//定义一个结构体
struct Student {
//在内部创建你需要的数据类型
char name[10];
int age;
char sex[5];
};
上面的代码是我想创建一个类型来描述一个学生的信息,我将学生名字、年龄、性别创建在了一个名叫Student的结构体中,这样一来我们就创建好了自己定义的一个结构体。
注:结构体中的内容可依据需求自行创建,一般来说结构体的创建都是在代码最开始的地方创建,并且要注意在创建好结构体之后要加上 ;(分号)!
2.结构体类型的变量创建
当一个数据类型创建好了之后,我们就避免不了创建一个变量使用它,如何创建一个结构体类型的变量呢?
(1)定义结构体后直接创建变量
#include<stdio.h>
struct Student {
char name[10];
int age;
char sex[5];
}stu;
int main()
{
//执行的代码
return 0;
}
如图,我们定义好结构体后直接在其尾部创建了一个变量。
(2)定义结构体后在创建变量
#include<stdio.h>
struct Student {
char name[10];
int age;
char sex[5];
};
struct Student stu;
int main()
{
//执行的代码
return 0;
}
如图,创建好结构体后,我们在创建一个结构体变量。
注:以上的两种创建的结构体变量都是全局变量。
(3)定义结构体后创建局部变量
#include<stdio.h>
struct Student {
char name[10];
int age;
char sex[5];
};
int main()
{
struct Student stu;
//执行的代码
return 0;
}
如图,我们在main大括号中创建了结构体的变量,其为局部变量。
3.结构体的初始化
结构体的变量创建好了,那么也就避免不了对其进行初始化了,结构体的初始化大致分成三种初始化方式。
还是以上面学生的结构体为例
(1)定义时后直接进行初始化
#include<stdio.h>
struct Student {
char name[10];
int age;
char sex[5];
};
int main()
{
struct Student stu = { "zhangsan",20,"男" };
//执行的代码
return 0;
}
即,直接在后面用 { } 将数据进行初始化。
(2)定义后逐个赋值
#include<stdio.h>
#include<string.h>
struct Student
{
char name[10];
int age;
char sex[5];
};
int main()
{
struct Student stu;
strcpy(stu.name, "zhangsan");
stu.age = 20;
strcpy(stu.sex, "男");
return 0;
}
即,分开一个一个进行赋值。
注:定义好结构体后如果不直接进行初始化,那么在后续的给结构体中内容赋值时不能在一次性全部赋值了。(如图)
(3)乱序初始化结构体
#include<stdio.h>
struct Student {
char name[10];
int age;
char sex[5];
};
int main()
{
struct Student stu = {.age = 20,.name = "zhangsan",.sex = "男"};
//执行的代码
return 0;
}
即,初始化不按照定义时的顺序进行初始化。
4.结构体中数据的调用
结构体变量创建好了,初始化也初始化好了,那么我们如何调用结构体中的内容呢?调用结构体中的内容我们使用 ( . )操作符。
例如我想打印一下结构体中的内容:
#include<stdio.h>
struct Student {
char name[10];
int age;
char sex[5];
};
int main()
{
struct Student stu = {.age = 20,.name = "zhangsan",.sex = "男"};
printf("%s %d %s", stu.name, stu.age, stu.sex);
return 0;
}
即,我们想使用结构体中的内容我们只需要用创建变量的名字 . 类型名即可。
当然我们还可以使用->来调用结构体中的内容:
#include<stdio.h>
struct Student
{
char a;
int b;
};
void Print(struct Student* p)
{
p->a = 'Z';
p->b = 1024;
printf("%c %d", p->a, p->b);
}
int main()
{
struct Student stu;
Print(&stu);
return 0;
}
也就是说,普通调用的时候我们使用结构体名 . 类型名称调用,而对于结构体的地址而言,我们使用->来调用结构体中的内容。
5.结构体大小
在C语言中,每一个数据类型都有其对应的大小,通常我们使用sizeof()操作符来计算数据类型的大小,那么自定义结构体的类型大小是多大呢?
首先我们先来看一下这个结构体:
struct stu
{
char a;
int b;
char c;
};
一般来说,char为一个字节大小,int为4个字节大小,那么这个结构体的大小应该为6,但是事实并非如此,我们看一下结果:
#include<stdio.h>
struct stu
{
char a;
int b;
char c;
};
int main()
{
printf("%zd", sizeof(struct stu));
return 0;
}
这和我们想象中的结果有所出入,那么为什么其大小是12,而不是6呢?这就必须介绍结构体的对齐规则。
结构体的对齐规则:
1.结构体的第一个成员对齐到和结构体变量起始位置偏移量为0的地址处;
2.其他成员变量要对齐到某个数字(对齐数)的整数倍的偏移量的地址处(对齐数 = 编译器默认的一个对齐数 与 该成员变量大小的较小值,VS 中默认的值为 8);
3.结构体总大小为最大对齐数(结构体中每个成员变量都有一个对齐数,所有对齐数中最大的)的整数倍;
4.如果嵌套了结构体的情况,嵌套的结构体成员对齐到自己的成员中最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体中成员的对齐数)的整数倍;
我们直接使用一个例子来举例:
(1)不含嵌套结构体:
以上我们就了解了结构体的大小到底如何进行计算了。
(2)含义嵌套结构体:
以上我们就了解了有嵌套结构体的大小是到底如何进行计算了。
注意:嵌套的结构体计算对齐数时,不需要在和默认对齐数进行比较。
当然我们也可以使用pragma来修改默认对齐数:(如图)
#include<stdio.h>
#pragma pack(1)
struct sin
{
char a;
int b;
char c;
};
#pragma pack()
int main()
{
printf("%zd", sizeof(struct sin));
return 0;
}
由上面的计算我们已经知道了此结构体如果默认对齐数是8时大小为12,但是我们使用pragma修改其默认对齐数后其大小为6(读者可以自己进行计算)
那么为什么会出现对齐数呢?主要原因有二:
1.平台原因 (移植原因):不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。
2.性能原因:数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。假设一个处理器总是从内存中取8个字节,则地址必须是8的倍数。如果我们能保证将所有的double类型的数据的地址都对齐成8的倍数,那么就可以用一个内存操作来读或者写值了。否则,我们可能需要执行两次内存访问,因为对象可能被分放在两个8字节内存块中。
以上就是结构体的全部内容了~~~