在此之前我们已经知道,C语言中有 int char short long float doudble 等数据类型这些类型在C语言中被称为内置类型,也就是说是C语言自己的数据类型。但如果我们要表示一个复杂对象比如一个学生的信息,内容可能会包含姓名(char name[]),年龄(int age),成绩(float score)等信息,为了方便管理要把这些变量组合成一个整体对学生这一个对象进行描述,而这个整体就称为结构体。结构体可以描述各种各样的变量组合,所以说结构体是一个复杂对象,而这个复杂对象是由我们自己来声明的类型,所以结构体被称为自定义类型,常见的自定义类型还有:枚举、联合体等。
在声明前需要了解下面俩点:
1.结构体是自定义类型,说白了它是一个与 int,char 等一样是一个类型,所以当你声明一个结构体类型时,这个类型并不会分配空间,只有结构体变量才会分配空间和地址。
2.结构体类型始终只是一个类型,所以拿一个结构体类型创建一个变量,数组,指针变量等,跟int类型创建变量不会有太大区别
结构是一些值的集合,这些值称为成员变量
。结构的每个成员可以是不同类型的变量,结构的成员可以是标量、数组、指针,甚至是其他结构体。
声明结构体的初级格式:
struct tig//结构体标签
{
member-list;//成员变量列表
}variable-list;//结构体变量列表
用结构体声明来描述一个学生:
struct stu
{
char name[20];
char gender[10];
int age;
float score;
};
结构体变量的定义:
1)也可以直接在声明结构体的同时定义结构体变量,不过定义是全局变量
struct stu
{
char name[20];
char gender[10];
int age;
float score;
}s2,s3,s4;
int main()
{
int a;
struct stu s1;
return 0;
}
2)声明匿名结构体,定义匿名结构体变量
匿名结构体没有结构体标签(结构体名),定义变量时只能在声明结构体的同时定义全局的结构体变量,否则不能再定义结构体变量
struct
{
char name[20];
char gender[10];
int age;
float score;
}s1,s2,s3;
1) 声明结构体的同时初始化结构体变量
2)结构体变量的初始化放在定义之后
效果一样的,只不过一个是全局变量一个是局部变量,但建议使用第二种,少用全局变量。
struct stu
{
char name[20];
char gender[10];
int age;
float score;
};s={张三”,"男“,22,98.8};//第一种
int main()
{
struct stu s={"王四", "男", 28, 92.8};//第二种
printf("%s %s %d %0.2lf\n",s.name,s.gender,s.age,s.score);
return 0;
}
结构变量的成员是通过点操作符(.)访问的。点操作符接受两个操作数
struct stu
{
char name[20];
char gender[10];
int age;
float score;
};
int main()
{
struct stu s={"王四", "男", 28, 92.8};
printf("%s %s %d %0.2lf\n",s.name,s.gender,s.age,s.score);
return 0;
}
struct Node
{
int data;
//next存放的是下一个节点的地址
struct Node* next;//该结构体类型的指针变量
};
1.结构体数值传参
typedef struct stu
{
char name[20];
char gender[10];
int age;
float score;
}stu;
void print1(stu s)
{
printf("%s\n",s.name);
printf("%s\n",s.gender);
printf("%d\n",s.age);
printf("%0.2lf\n",s.score);
}
int main()
{
struct stu s={"王二麻子", "男", 22, 98.8};
print1(s);
return 0;
}
2.结构体地址传参
typedef struct stu
{
char name[20];
char gender[10];
int age;
float score;
}stu;
void print2(stu* p)
{
printf("%s\n",p->name);
printf("%s\n",p->gender);
printf("%d\n",p->age);
printf("%0.2lf\n",p->score);
}
int main()
{
struct stu s={"王二麻子", "男", 22, 98.8};
print2(&s);
return 0;
}
值传递,传的是整个结构体而结构体有很多成员变量会占很大的空间,由于传的结构体很大,一是传递时间更久,二是内存的浪费,如果结构体很大,那要重新开辟很大的内存空间。
而值传递传的是一个结构体的地址,我们知道凡是是地址再32位平台上都是四个字节,这样就减少的内存的浪费,以及时间会更快,而指针会指向结构体变量通过解引用操作就可以访问该结构体
总结
函数传参的时候,参数是需要压栈的。
如果值传递一个结构体对象的时候,结构体过大,参数压栈(临时变量的存储)的的系统开销比较大,所以会导致性能的下降。
1.结构体内存对齐
先看一个代码
比较结构体变量 s1 ,s2 的大小
struct S1
{
char c1;
int a;
char c2;
};
struct S2
{
char c1;
char c2;
int a;
};
int main()
{
struct S1 s1={0};
struct S2 s2={0};
printf("%d\n",sizeof(s1));
printf("%d\n",sizeof(s2));
return 0;
}
这里为什么会出现这种结果,那就要涉及到结构体的内存对齐了
先看内存对齐规则:
这里的大小都是以字节为单位
1. 第一个成员在相对于结构体变量地址偏移量为0的地址处。
2. 其他成员变量要对齐到某个数字(成员变量的对齐数)的整数倍的地址处。
成员变量的对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值。
VS中默认的对齐数为8
3. 结构体总大小为最大对齐数(每个成员变量都有一个对齐数,)是成员变量的最大对齐数的整数倍。
4. 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍
2.内存对齐的原因
1. 平台原因(移植原因):
不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取(例如4的倍数)某些特定类型的数据,否则抛出硬件异常。
2. 性能原因:
数据结构(尤其是栈)应该尽可能地在自然边界上对齐。
原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访
问
结构体的内存对齐是拿空间来换取时间的做法,原因就是现在科技发达了内存是越来越大的而且越来越便宜,总的来说时间要比空间更具性价比
那我们在声明结构体的时候,我们既要满足对齐,又要节省空间,如何做到:
让占用空间小的成员尽量集中在一起。
struct S1
{
char c1;
int i;
char c2;
};
struct S2
{
char c1;
char c2;
int i;
};
格式:#pragma pack(对齐数)
#pragma pack(6)//设置默认的对齐数
struct S2
{
char c1;
char c2;
int a;
};
#pragam pack()//取消默认设置的对齐数
int main()
{
printf("%d\n",sizeof(struct S2));
return 0;
}
offsetof:是一个带参宏可以求出,结构体成员变量地址相对于结构体变量起始地址的偏移量。
#define OFFSETOF(type,number) (int) (&( *(type*) 0 ).number)
struct S2
{
char c1;
char c2;
int a;
};
int main()
{
printf("%d\n",OFFSETOF(struct S2,c1));
printf("%d\n",OFFSETOF(struct S2,c2));
printf("%d\n",OFFSETOF(struct S2,a));
return 0;
}
结构体很难,慢慢学习吧