结构体
- 1. 概念
- 创建新类型的工具
- 结构体类型的存储区里可以包含多个不同类型的子存储区,每个子存储区可以存放一个数字,甚至也可以是结构体类型的存储区
- 2. 声明方法
- 使用 struct 关键字声明
- 包含多个成员变量声明语句
struct 结构体类型名称 {
成员变量声明语句
};
#include <stdio.h>
//结构体声明里虽然包含了多个变量声明语句,但这些变量声明语句并不会分配内存
//所以整个结构体声明语句都是不会分配内存的,所以可以写在头文件里
//结构体声明写在外面,这个文件里所有的函数都可以使用这个结构体
struct person {
int age;
float height;
char name[10];
};
int main() {
return 0;
}
-
- 可以先声明结构,再使用typeof关键字给结构体类型起别名
typedef struct 结构体类型名称 别名;
-
- 也可以在声明结构体类型的时候同时起别名
typedef struct 结构体类型名称 {
成员声明变量语句;
} 结构体别名;
//先声明了一个结构体,然后起别名
#include <stdio.h>
struct person {
int age;
float height;
char name[10];
};
//struct person 合起来代表原有的结构体类型的名称,类似于 int
//前面加typedef, 后面再加一个别名sperson, 就可以使用sperson来当做这个结构体类型的名称使用,比struct person 使用起来简单的多
typedef struct person sperson;
int main() {
return 0;
}
//在声明结构体类型的时候同时起别名
#include <stdio.h>
//sperson 是可以当做结构体类型名称来使用的,所以是可以省略结构体本身的名称的
//留着也可以 ,可以把 struct person合起来做结构体类型名称使用
typedef struct /*person*/ {
int age;
float height;
char name[10];
}sperson;
int main() {
//struct person prsn;
//结构体变量的初始化,和数组初始化类似
sperson prsn1 = {20, 1.45f, "abc"};
return 0;
}
-
3. 结构体使用方法
- 结构体作为 类型名称 声明结构体变量
- struct 结构体名称 结构体变量名称;
- 结构体别名 结构体变量名称;
- 结构体变量的初始化,和数组初始化类似
- 结构体作为 类型名称 声明结构体变量
-
4. 结构体变量使用方法
- 结构体变量不作为整体使用,一次只使用结构体变量中的某一个成员变量
- 通过结构体变量表示子存储区
- 通过结构体指针表示子存储区
- 但在一种特殊情况下可以作为整体使用: 同类型结构体变量之间可以直接赋值
- 结构体变量不作为整体使用,一次只使用结构体变量中的某一个成员变量
#include <stdio.h>
struct person {
int age;
float height;
char name[10];
};
typedef struct person sperson;
int main() {
sperson prsn1 = {20, 1.45f, "abc"};
//声明一个结构体指针
//用 结构体类型的名称sperson 做 类型名称 声明 指针,表示这个指针指向了一个结构体类型存储区
sperson *p_person = &prsn1;
//通过结构体变量表示子存储区
printf("%d\n", prsn1.age);
printf("%g\n", prsn1.height);
printf("%s\n", prsn1.name);
//通过结构体指针表示子存储区,结构体指针 + 箭头 + 成员变量
//这样写的前提是结构体指针已经指向了一个有效的结构体存储区
printf("%d\n", p_person -> age);
printf("%g\n", p_person -> height);
printf("%s\n", p_person -> name);
//把两个结构体变量当做整体来使用
prsn1 = prsn2;
return 0;
}
-
- 调用函数和被调用函数之间应该传递结构体存储区而不要传递结构体数据 ,因为这样可以减少时间和空间
#include <stdio.h>
#include <string.h>
typedef struct {
int age;
float height;
char name[10];
} sperson;
//把结构体存储区的地址当做返回值传递给调用函数
sperson *read(void) {
//prsn 如果是一个非静态局部变量,它地址是不能传递给调用函数使用的
static sperson prsn = {0};
printf("请输入年龄: ");
scanf("%d", &(prsn.age));
printf("请输入身高: ");
scanf("%g", &(prsn.height));
printf("请输入姓名: ");
//当用户输入完升高后,把换行字符 \n 留在了缓冲区里,要删掉的
scanf("%*[^\n]");
scanf("%*c");
fgets(prsn.name, 10, stdin);
if (strlen(prsn.name) == 9 && prsn.name[8] != '\n') {
scanf("%*[^\n]");
scanf("%*c");
}
return &prsn;
}
//用结构体指针形参 接收 调用函数传递过来的结构体存储区地址,不会修改指针指向的存储区内容就加const
void print(const sperson *p_person) {
printf("年龄是%d\n", p_person -> age);
printf("身高是%g\n", p_person -> height);
printf("姓名是%s\n", p_person -> name);
}
int main() {
//返回值记录在结构体指针里
sperson *p_person = read();
print(p_person);
return 0;
}
/*
请输入年龄: 18
请输入身高: 180
请输入姓名: 你爸
年龄是18
身高是180
姓名是你爸
//最后这个 \n 是用户输入姓名时按的回车
*/
-
5. 数据对齐
- 数据对齐可以这样描述:
- 计算机内存是由很多个字节构成的,上图*代表字节,字节是分了组的,4个字节一组,而且每个字节都是有地址的,假设地址是12345…,一个存储区可能由一个或多个相邻字节构成,当多个相邻字节要被合并成一个存储区的时候,是不可以把不同组的字节合并成一个存储区的。另外,同一组里的字节也不能任意使用,因为数据的内存地址要能被该数据类型对应存储区大小整除,即: 一个存储区的地址,必须是它自身大小的整数倍(说的不对,要结合#pragma pack(n) n = 1,2,4,8 来考虑 )
- 但是这里有个例外,双精度浮点类型的存储区包含了8个字节,任何一组字节都装不下,需要两组,所以它的存储区地址不需要是8的整数倍,是4的整数倍就行了
- 结构体的子存储区也需要数据对齐规则
- 数据对齐会造成相邻子存储区之间有空隙
- 数据对齐可以这样描述:
-
6. 数据补齐
- 结构体存储区的大小必须是最大子存储的大小的整数倍,但是如果结构体成员变量里面有双精度浮点类型,结构体存储区的大小只要是4的整数倍就行
- 数据补齐可能造成结构体存储区的最后有浪费字节
#include <stdio.h>
typedef struct {
//大小2字节
char buf[2];
//大小4字节
int val;
}tmp;
typedef struct {
//大小2字节
char ch;
int val;
char ch1;
} tmp1;
int main() {
printf("sizeof(tmp)是%d\n", sizeof(tmp));
printf("sizeof(tmp1)是%d\n", sizeof(tmp1));
return 0;
}
/*
output:
sizeof(tmp)是8
//可以理解为 buf[]占了地址是4,5的字节,val用不了6,7,(因为6,7不能被4整数)只能从8开始
sizeof(tmp1)是12
//tmp1最大子存储区的大小是4个字节,结构体存储区的大小最后要补成4的整数倍
*/