目录
前言
我们之前看到的变量都是单独存在的,互相之间不存在联系,在内存中的地址也是互不相干的,但是在日常工作和学习中,我们常常需要把把一些变量放在一块,成组出现。例如,学生的姓名,学号,班级,联系方式,成绩等信息都是属于一个同学的,我们想让他们成组的保存和出现,让他们成为一个数据组合就用到了结构体。
一、结构体的定义和声明
结构体:由不同数据类型数据构成的组合型的数据结构,是用户自定义的数据类型
结构体类型的声明格式:
struct 结构体名
{成员列表} ;
举个例子,写一个用来放学生信息的结构体
struct STU
{
char name[20]; //用来放学生姓名的数组
char num[10]; //用来存放学生学号的数组
char cla[10]; //用来存放学生班级的数组
char add[20]; //用来存放学生地址的数组
float score; //用实型来存放学生的成绩
}; //最后要加上';'
二、结构体变量的定义、初始化及使用
1、结构体变量的定义
struct STU
{
char name[10]; //姓名
char num[10]; //学号
float score; //成绩
} student; //这里的student就是我们定义的结构体变量
注意:student 和 STU 不要搞混,STU 是结构体类型名,student是结构体变量
//定义的结构体变量的另外两种放法
//一、先定义结构体,在定义结构体变量
struct STU
{
char name[10];
char num[10];
float score;
}; //先定义结构体
struct abc student; //再定义结构体变量student
//二、不指定类型名直接定义结构体变量
struct
{
char name[10];
char num[10];
float score;
} student; //直接定义结构体变量
对于相同的结构体类型的变量可以相互赋值,但是结构体类型名不同结构体变量即使结构体成员相同也不可以相互赋值。
2、结构体变量的初始化
//可以直接在定义结构体变量的时候初始化
struct STU
{
char name[10];
char num[10];
float score;
} student = {"name","num",75}; //直接在这里初始化
//或者可以先定义结构体变量,再给结构体变量赋值
struct STU
{
char name[10];
char num[10];
float score;
} student;
student = {"name","num",75}; //在这里初始化
3、结构体成员的使用
//可以通过 结构体变量名.结构体成员名 来单独使用某个单独的成员
//这样一个结构体
#include <stdio.h>
struct score
{
char num[10]; //用来放学号
int math; //数学成绩
int chinese; //语文成绩
} stu; //定义结构体变量
int main()
{
scanf("%s",&stu.num); //输入学号
scanf("%d",&stu.math); //输入数学成绩
printf("%s\n %d\n",stu.num,stu.math); //输出数学成绩和学号
}
三、结构体数组
我们什么时候要用到结构体数组呢? 我们可以一个学生的一组数据,但是当我们要存放10个学生的数据那就要用到结构体数组了。举个例子,用结构体数组存放5名学生的信息,如下
//用结构体数组存放5名同学信息
#include <stdio.h>
struct STU
{
char name[15];
char num[10];
float score;
}student[5];
int main()
{
struct STU student[5] = { {"zhao","1",77.2},{"qian","2",84.2}, //先初始化
{"sun","3",82.1},{"li","4",65.4},{"zhou","5",99.4}};
int i;
for(i = 0;i < 5;i++)
{
printf("name:%s\nnum:%s\nscore%.2f\n",student[i].name,student[i].num,student[i].score);
}
return 0;
}
四、结构体指针
结构体指针对象可以指向结构体变量,也可以指向结构体数组中的元素。
下面举个例子,用结构体指针来输出学生的信息。
//用结构体数组存放5名同学信息
#include <stdio.h>
struct STU
{
char name[15];
char num[10];
float score;
}student[3];
int main()
{
struct STU *p; //定义同结构体类型的指针
struct STU student[3] = { {"zhao","1",77.2},{"qian","2",84.2},{"sun","3",82.1}}; //先初始化
int i;
for(p=student;p<student+3;p++)
{
printf("name:%s\nnum:%s\nscore%.2f\n",p->name,p->num,p->score);
}
return 0;
}
如果 结构体指针指 p 向一个结构体变量 stu,那么可以用以下三种方法表示他的成员名
1、stu.成员名,如(stu.num);
2. (*p).成员名 ,如(*p).num;
3. p->成员名, 如 p->num;
五、结构体做函数参数
可以把结构体变量或者指向结构体变量的指针做参数,与之前其他变量或者指针做参数相同。
下面举个例子,用结构体做参数的函数输出三个学生的信息。
//用结构体变量做参数输出三名同学的信息
#include <stdio.h>
struct STU
{
char name[15];
char num[10];
float score;
}student[3];
int main()
{
void output(struct STU);
struct STU *p; //定义同结构体类型的指针
struct STU student[3] = { {"zhao","1",77.2},{"qian","2",84.2},{"sun","3",82.1}}; //先初始化
int i;
for(i=0;i<3;i++)
{
output(student[i]);
}
return 0;
}
void output(struct STU stud) //结构体变量做参数,输出三名学生信息
{
printf("name :%s\t num :%s \t score :%.2f\n",stud.name,stud.num,stud.score);
}
六、结构体的大小
结构体成员按照定义时的顺序依次存储在连续的内存空间,但是结构体的大小并不是简单的把所有成员大小相加,而是遵循一定的规则,需要考虑到系统在存储结构体变量时的地址对齐问题。
1、没有成员的结构体占用的空间是多少个字节?(来自邱明成)
答案:1个字节。
这就是实例化的原因(空类同样可以被实例化),每个实例在内存中都有一个独一无二的地址,为了达到这个目的,编译器往往会给一个空类或空结构体(C++中结构体也可看为类)隐含的加一个字节,这样空类或空结构体在实例化后在内存得到了独一无二的地址,所以空类所占的内存大小是1个字节。
2、偏移量
偏移量指的是结构体变量中成员的地址和结构体变量地址的差。
结构体大小等于最后一个成员的偏移量加上最后一个成员的大小。
显然,结构体变量中第一个成员的地址就是结构体变量的首地址
struct stru
{
int a; //start address is 0
char b; //start address is 4
int c; //start address is 8
}; //第二个成员偏移量是4,第三个是8,结构体的大小是8+4=12
3、存储变量规则
(1)结构体变量中成员的偏移量必须是成员大小的整数倍(0被认为是任何数的整数倍)
(2)结构体大小必须是所有成员大小的整数倍,也即所有成员大小的公倍数。
下面举例子,来说明一下这两个规则
struct stru1
{
int a; //start address is 0
char b; //start address is 4
int c; //start address is 8
};
用sizeof求该结构体的大小,发现值为12。int占4个字节,char占1个字节,结果应该是9个字节才对啊,为什么呢?这个例子中前两个成员的偏移量都满足要求,但第三个成员的偏移量为5,并不是自身(int)大小的整数倍。编译器在处理时会在第二个成员后面补上3个空字节,使得第三个成员的偏移量变成8。结构体大小等于最后一个成员的偏移量加上其大小,上面的例子中计算出来的大小为12,满足要求
struct stru3
{
char i; //start address is 0
int m; //start address is 4
char n; //start address is 8
};
struct stru4
{
char i; //start address is 0
char n; //start address is 1
int m; //start address is 4
};
虽然结构体stru3和stru4中成员都一样,但sizeof(struct stru3)的值为12而sizeof(struct stru4)的值为8。
由此可见,结构体类型需要考虑到字节对齐的情况,不同的顺序会影响结构体的大小。
4、嵌套的结构体,储存规则。
需要将其展开。对结构体求sizeof时,上述两种原则变为:
(1)展开后的结构体的第一个成员的偏移量应当是被展开的结构体中最大的成员的整数倍。
(2)结构体大小必须是所有成员大小的整数倍,这里所有成员计算的是展开后的成员,而不是将嵌套的结构体当做一个整体
struct stru5
{
short i;
struct
{
char c;
int j;
} tt;
int k;
};
结构体stru5的成员tt.c的偏移量应该是4,而不是2。整个结构体大小应该是16。
5、结构体中包含数组
其sizeof应当和处理嵌套结构体一样,将其展开,如下例子:
struct array
{
float f;
char p;
int arr[3];
};
其值为20。float占4个字节,到char p时偏移量为4,p占一个字节,到int arr[3]时偏移量为5,扩展为int的整数倍,而非int arr[3]的整数倍,这样偏移量变为8,而不是12。结果是8+12=20,是最大成员float或int的大小的整数倍。
七、位域
1、什么是位域
当我们在一个程序中要用到很多,开关量,这些变量只需要存储0和1,这时候我们就不需要那么多的内存空间,我们只需要每个变量的1位就可以存储
struct Keyy
{
int key1 : 1; //定义它们的所占的宽度为1位
int key2 : 1;
int key3 : 1;
}key;
在结构体的基础上,我们可以规定的它所占二进制的位数,可以节省一定的内存空间
2、位域的定义
跟结构体定义的方式类似,在结构体成员的 后面加上 “:位数”就是他们所占的大小
struct Age
{
int a : 3; //定义a所占的二进制位数为3位可以存储0-7
int b : 4; //b占4位,可以存储 0-15
}age;
要注意下面两点
元素 | 描述 |
---|---|
结构体成员类型 | 只能为 int(整型),unsigned int(无符号整型),signed int(有符号整型) 三种类型,决定了如何解释位域的值。 |
位域的宽度 | 位域中位的数量。宽度必须小于或等于指定类型的位宽度。 |
3、位域的使用
使用位域的 方法和使用结构体成员一样
#include <stdio.h>
struct WE
{
unsigned int a : 1; //位域宽度为1
unsigned int b : 3; //位域宽度为3
}cat;
int main()
{
cat.a = 1; //给a和b赋值
cat.b = 7; //三位只能存储0-7,超过7就会出错
printf("a = %d,b = %d\n",cat.a,cat.b); //输出a和b的值
return 0;
}
4、位域大小的计算
位域遵循结构体对齐原则,整个结构体的总大小为最宽基本类型成员大小的整数倍
输出结果:4, 一共占了4个字节。但a,b加起来才3位,还不到一个字节,就是因为最大的基本类型成员unsigned int 占4个字节,整个结构体总大小就要是他的整数倍
#include <stdio.h>
struct WE
{
unsigned int a : 1; //位域宽度为1
unsigned int b : 2; //位域宽度为3
}cat;
int main()
{
printf("%d",sizeof(cat));
return 0;
}
输出结果: 4,两个成员共占15位不到两个字节,所以总大小应补到4个字节
#include <stdio.h>
struct WE
{
unsigned int a : 10; //位域宽度为1
unsigned int b : 5; //位域宽度为2
}cat;
int main()
{
printf("%d",sizeof(cat));
return 0;
}