C语言提供了结构体,来管理不同类型的数据组合。
如,针对某学生,存在姓名,性别,年龄,身高等不同类型的数据,可以使用结构体将这些信息定义为一个变量。
1.结构体的声明
定义结构体变量之前,需要先声明一个结构体类型。声明一个student
结构体的代码示例如下:
struct student
{
char name[20];
char gender[10];
int age;
float height;
};
(声明结构体类型,末尾是必须加分号的。)
2.结构体的定义
定义结构体对象的时候,要避免结构体变量的变量名与结构体类型名相同,不然可能带来风险。
此外,对于结构体对象的访问,必须一个成员、一个成员地访问,不支持同时访问。
# include <stdio.h>
struct student
{
char name[20];
char gender[10];
int age;
float height;
};
int main(){
struct student s001={"Tom", "male", 18, 170.5};
printf("%s,%s,%d,%f\n",s001.name,s001.gender,s001.age,s001.height);
return 0;
}
程序输出结果如下:
Tom,male,18,170.500000
也可以使用输入函数的方式,示例如下:
# include <stdio.h>
struct student
{
char name[20];
char gender[10];
int age;
float height;
};
int main(){
struct student s001;
scanf("%s %s %d %f",&s001.name,&s001.gender,&s001.age,&s001.height);
printf("%s,%s,%d,%f\n",s001.name,s001.gender,s001.age,s001.height);
return 0;
}
输入:
Tom male 18 170.5
输出:
Tom,male,18,170.500000
3. 结构体数组
结构体数组是数组,不是结构体。
结构体数组,可以理解为,元素是结构体的数组。
# include <stdio.h>
int main(){
struct student{
char name[20];
char gender[10];
int age;
float height;
}stu01[5]={{"a","male",18,165.3},
{"b","female",17,160.6},
{"c","male",18, 178.2},
{"d","male",16,163.5},
{"e","female",20,177.6}
};// 也可以在声明结构体类型的时候,一并定义变量
int i;
for(i=0;i<5;i++){
printf("%s,%s,%d,%f\n",stu01[i].name,stu01[i].gender,stu01[i].age,stu01[i].height);
}
return 0;
}
输出结果:
a,male,18,165.300003
b,female,17,160.600006
c,male,18,178.199997
d,male,16,163.500000
e,female,20,177.600006
4.修改值
要修改结构体中的某个值也是支持的。参考:
stu01[0].age=20;
5.结构体对齐
结构体的大小必须是其最大成员的整数倍。(最大成员不包含数组类型)。
此即结构体的对齐。
对齐的目的是:为了让CPU高效地去取内存上的数据。
下边用一段代码来说明:
# include <stdio.h>
int main(){
struct student
{
char name[20];
char gender[10];
int age;
float height;
};
struct student s001={"Tom","male",20,175.6};
printf("%s,%s,%d,%f\n",s001.name,s001.gender,s001.age,s001.height);
printf("%d %d %d %d\n",sizeof(s001.name),sizeof(s001.gender),sizeof(s001.age),sizeof(s001.height));
printf("%d\n",sizeof(s001));
return 0;
}
程序输出结果如下:
Tom,male,20,175.600006
20 10 4 4
40
四个元素的大小分别是20字节,10字节,4字节,4字节。
最终得到结构体的大小为40字节。计算方法为:
先对每个元素的大小进行求和,即20+10+4+4
的结果为38字节。然后在此基础上,不考虑数组元素的大小,只考虑除数组以外元素的大小。这里需要考虑的是后两个元素,都占了4字节。则在38的基础上,再加上2,即满足了为最大成员大小4
的整数倍。(不考虑数组大小)此即结构体的对齐。
如果将float height
改为double height
,则20+10+4+8
的结果为42,最终结构体的大小为48字节。(大多数时候都是4或8的倍数)
此外,假设已知最大成员的大小,此时还有两个更小的成员,这两个更小的成员的字节大小的和小于最大成员大小。则可以将这两个更小的成员写在一起以节省空间。
如:
# include <stdio.h>
int main(){
struct student
{
char name[20];
char gender[10];
double height; //8字节
short age; //2字节
char level;
};
struct student s001={"Tom","male",175.6,20,"A"};
printf("%s,%s,%d,%f\n",s001.name,s001.gender,s001.height,s001.level);
printf("%d %d %d %d %d\n",sizeof(s001.name),sizeof(s001.gender),sizeof(s001.height),sizeof(s001.age),sizeof(s001.level));
printf("%d\n",sizeof(s001));
return 0;
}
输出结果:
Tom,male,858993459,0.000000
20 10 8 2 1
48
空间分析:
非数组的最大成员
double
大小为8字节
先考虑数组,name和gender的空间大小的和为20+10=30
,修正为8的倍数,为32字节。
再考虑非数组,height
,age
,level
分别为8,2,1个字节。
此时height
独占8个字节,age
与level
的和小于8个字节,则可以结合起来占8个字节。
所以该结构体一共占了32+8+8
=48字节。
6.结构体指针
一个结构体变量的指针,即该结构体变量所占用内存段的起始地址。
在下边的代码中,先定义一个结构体类型student
,然后定义一个结构体对象s001
并打印之。
然后再定义一个结构体指针变量p,指向结构体s001
。并尝试通过指针变量p来访问每个成员。
# include <stdio.h>
int main(){
struct student
{
char name[20];
char gender[10];
int age;
float height;
};
struct student s001={"Tom","male",18,172.5};
printf("%s,%s,%d,%f\n",s001.name,s001.gender,s001.age,s001.height);
struct student *p;
p=&s001;
printf("%s,%s,%d,%f\n",(*p).name,(*p).gender,(*p).age,(*p).height); //不推荐的访问方法
printf("%s,%s,%d,%f\n",p->name,p->gender,p->age,p->height); //推荐的访问方法
return 0;
}
程序输出结果:
Tom,male,18,172.500000
Tom,male,18,172.500000
Tom,male,18,172.500000
以(*p).name
为例,访问结构体的每个成员时,不能写作*p.name
,因为点.
的优先级高于*
,不加括号的话则会被解读为向指针变量取成员,这样的逻辑是错误的。此外,即便加了括号,这样的写法也是不推荐的。因该写法过于繁琐,c语言为此提供了->
符号来实现对结构体指针成员的访问。通过指针变量p访问成员name
则可写作p->name
。日常实践和应用中通常建议使用此种方法。(*p).name
的写法可能会让人觉得困惑,一般要避免使用,了解即可。
7. typedef方法
7.1 给结构体取别名
定义结构体类型的时候,使用typedef
来起别名,可以让代码更加简洁。
# include <stdio.h>
int main(){
typedef struct student
{
char name[20];
char gender[10];
int age;
float height;
}stu;
stu s001={"Tom","male",18,172.5};
printf("%s,%s,%d,%f\n",s001.name,s001.gender,s001.age,s001.height);
return 0;
}
输出:
Tom,male,18,172.500000
在该示例中,通过typedef
方法,给定义的student
类型起了一个别名stu
。然后再定义每一个该类别的结构体变量时,代码都会更为简洁。(此时依然支持struct student s001={"Tom","male",18,172.5};
但不会再采用。)
7.2 给结构体指针取别名
同理,对于结构体指针类型,我们也可以通过typedef
方法来起别名pstu
,然后起到简化代码的作用。
# include <stdio.h>
int main(){
typedef struct student
{
char name[20];
char gender[10];
int age;
float height;
}stu,*pstu;
stu s001={"Tom","male",18,172.5};
printf("%s,%s,%d,%f\n",s001.name,s001.gender,s001.age,s001.height);
pstu p001=&s001;
printf("%s,%s,%d,%f\n",p001->name,p001->gender,p001->age,p001->height);
return 0;
}
输出:
Tom,male,18,172.500000
Tom,male,18,172.500000
(其中stu
即等价于struct student
,pstu
即等价于struct student *
。
pstu p001=&s001;
也可以写作stu *p001=&s001
。)
7.3 其他用途
取别名不仅仅是为了简化代码,也可以起到一定的筛选作用。
在特定的地方,比如某个局部空间内,可以通过给某一个变量起别名,比如给int
类型起别名为ingeter
,来实现对在区域内所以int
类型的筛选。比如若我们存在需求:将该区域内别名为ingeter
的int
变量转变为浮点类型,则只需要修改该别名对应的对象即可。而该区域内,直接使用int
定义的整型变量则不会受其影响。
# include <stdio.h>
int main(){
typedef int ingerter;
int a = 1;
ingerter b=2;
ingerter c=3;
return 0;
}
如若将typedef int ingerter;
中的int
修改为其他类型,则变量a
将不受影响,变量b
和变量c
将会同时改变。