结构体、共用体/联合体、动态内存分配
文章目录
一、结构体
1. 定义及使用
自定义的数据类型,一批数据组合成的结构型数据,每一个数据就是结构体的”成员“。
struct
{
char name[100];
int age;
char gender;
double height;
}
书写在函数内部((局部位置)只可在本函数使用;书写在函数外部(全局位置)可以全局使用。
结构体使用:
//此处为每个属性单个赋值的写法,下个代码示例有更简便的写法
struct student stu;
strcpy(stu.name, "张三");
stu.age = 18;
stu.gender = 'M';
stu.height = 1.83;
printf("%s年龄%d,性别%c,身高%.2f\n", stu.name, stu.age, stu.gender, stu.height);
//输出结果为:张三年龄18,性别M,身高1.83
2. 结构体数组
例:定义结构体表示学生,将三个学生信息放入数组并遍历
struct student
{
char name[100];
int age;
char gender;
double height;
};
int main()
{
struct student stu1 = {"张三", 18, 'M', 1.83};
struct student stu2 = { "李四", 20, 'M', 1.75 };
struct student stu3 = { "王五", 22, 'F', 1.70 };
struct student stuArr[3] = {stu1, stu2, stu3};
for (int i = 0; i < 3; i++)
{
struct student temp = stuArr[i];
printf("%s年龄%d,性别%c,身高%.2f\n", temp.name, temp.age, temp.gender, temp.height);
}
return 0;
}
3. 别名
使用别名可以方便我们定义结构体,体现在两个方面:
- 简化”数据类型“名字的书写;
- 定义时不需要再加上struct;
代码示例:
#include <stdio.h>
typedef struct heroCharacter //此处有完整的名称,并且前面加上typedef
{
char name[100];
int powerNum;
int defenseNum;
int blood;
}man; //别名
int main()
{
man man1 = { "莉莉娅", 150, 200, 600 };
man man2 = { "凯隐", 160, 200, 600 };
man man3 = { "弗耶戈", 160, 200, 600 };
man manArr[3] = {man1, man2, man3};
for (int i = 0; i < 3; i++)
{
man temp = manArr[i];
printf("%s的攻击力为%d,防御力为%d,血量为%d\n", temp.name, temp.powerNum, temp.defenseNum, temp.blood);
}
return 0;
}
4. 结构体作为函数的参数
两种情况:传递结构体中的数据值;传递结构体的地址值。
函数的声明建议写在结构体定义的下面,因为程序自上而下运行,如果函数的参数中存在结构体,而函数声明写在结构体上面,就会报错。
(我已经报错过了,报错后才发现问题,哭)
例:书写一个修改结构体变量的数据的函数
typedef struct student
{
char name[100];
int age;
char gender;
double height;
}S;
void updateStu(S* p);
int main()
{
struct student stu1 = {"张三", 18, 'm', 1.83};
updateStu(&stu1);
printf("%s年龄%d,性别%c,身高%.2f\n", stu1.name, stu1.age, stu1.gender, stu1.height);
return 0;
}
void updateStu(S* p)
{
printf("请输入新的姓名:");
scanf("%s", (*p).name);
printf("请输入新的年龄:");
scanf("%d", &((*p).age));
}
5. 结构体嵌套
例.为上述的结构体添加联系方式,联系方式包括手机号、电子邮箱
typedef struct contactWay
{
char phoneNum[12];
char e_mail[100];
}Contact;
typedef struct Student
{
char name[100];
int age;
char gender;
double heihght;
Contact contact;
}Stu;
// 注意给字符串赋值要使用strcpy()函数
// 也可以整体赋值,如下:
Stu stu1 = { "李四", 22, 'M', 1.73, {"13412345678", } };
6. 内存对齐
规则:不管是结构体还是普通的变量都存在内存对齐。
-
确定变量位置:只能放在自己类型整数倍的内存地址上;
如int类型,方便理解使用十进制数进行解释,int类型占4个字节,内存地址为0、4、8等可以存储,内存地址为2、3、5等就不可以。
即“内存地址 / 占用字节,可以整除”。
对齐的时候会补空白字节,但不会改变原本占用字节的大小,如char补位之后仍是占用一个字节。
**结构体在此基础上多一条控制总大小的补位:
-
最后一个补位:结构体的总大小,是最大类型的整数倍(用于确定最后一个数据补位的情况)。
比如一个结构体中有:int、double、char等数据类型,并且已经占了17个字节的位置,但因为double占用8个字节,为保持整数倍,这个结构体会共占用24个字节。
因此在定义结构体的时候一般会将小的数据类型放在上面,大的数据类型放在下面,以此节约空间。
二、共用体
1. 核心
核心:一个数据可能有多种数据类型。
比如金额有整数、小数、汉字形式书写,分别需要使用整型、浮点型、字符串等数据类型。
代码示例:
union moneyType //定义共同体
{
double moneyD;
int moneyI;
char moneyC[100];
};
int main()
{
union moneyType money; //定义共同体变量
money.moneyD = 100.2; //赋值
printf("%s\n", money.moneyD);
return 0;
}
2. 起别名
同样的,共同体也可以起别名。
typedef union moneyType //定义共同体
{
double moneyD;
int moneyI;
char moneyC[100];
}MT;
int main()
{
MT money; //定义共同体变量
money.moneyD = 100.2; //赋值
printf("%s\n", money.moneyD);
return 0;
}
3. 特点
-
共用体,也叫联合体、共同体;
-
所有的变量使用同一个内存空间;
如图所示:地址相同。
-
所占用的内存大小 = 最大成员的长度(受内存对齐影响) && 最大数据类型的整数倍;
-
比如一个共用体中为double和int两种成员,那么占用的就是8个字节。
-
但同时也要注意 总大小一定是最大数据类型长度的整数倍。
思考:占用内存的长度本来就是最大成员的长度了,那一定是整数倍啊,为什么会要注意这个呢?
下面这个结构体占用多少空间呢?
union MoneyType { int moneyI; double moneyD; char moneyC[100] };
占用104个,字符串底层是字符数组,一个字符占用一个字节,最大成员moneyC的长度为100,但最大的数据类型为double,8个字节的长度,所以占用空间应该是8的整数倍,所以会填补4个空白字节,共占用104个。
-
-
每次只能给一个变量赋值,因为第二次赋值会覆盖原有的数据;
如图蓝色部分所示:
三、结构体和共用体的区别
-
在底层:
结构体:一种事物中包含多个属性(一个人的信息,姓名、年龄、身高、体重等);
共用体:一个属性有多种类型(金额为整数、小数、字符串等);
-
存储方式:
结构体:每个属性各存各的,占用不同的内存地址;
共用体:存在一起,多次存会覆盖;
-
内存占用:
结构体:各个变量的总和(内存对齐的影响);
共用体:最大类型(内存对齐的影响);
大概可以想结构体串联,共用体并联。
四、动态内存分配
1. 常用函数
这些函数定义在了stdlib.h头文件中,所以要先导入该头文件。
1.1 malloc – 申请空间(掌握)
该函数使用次数最多,要掌握,效率会更高。
申请连续的空间,函数参数为总长度,单位为字节,
示例代码如下:
int* p = malloc(100 * sizeof(int));
for (int i = 0; i < 10; i++)
{
*(p + i) = (i + 1) * 10; //赋值方式一
p[i] = (i + 1) * 10; //赋值方式二
}
for (int i = 0; i < 10; i++)
{
printf("%d ", *(p + i)); //取值方式一
printf("%d ", p[i]); //取值方式二
}
1.2 calloc – 申请空间(了解)
该函数使用次数较少,作为了解,因为初始化的数据一般用不到。
申请空间并进行初始化,函数参数为元素个数、单个元素的大小。
int* p = Calloc(10, sizeof(int));
for (int i = 0; i < 10; i++)
{
printf("%d ", *(p + i)); //取值方式一
printf("%d ", p[i]); //取值方式二
}
1.3 realloc – 扩容(了解)
修改空间的大小,函数参数为被修改空间的首地址及要修改的大小,单位为字节。
作为了解,因为内存空间一旦申请完毕,一般不会再去变动。
-
修改后地址值可能发生变化,也可能不发生变化(取决于原空间后面有没有空间),所以最好是用一个变量进行接收。
-
原本空间不需要进行释放,底层会进行处理。
int* p = malloc(10 * sizeof(int));
printf("%p\n", p);
for (int i = 0; i < 10; i++)
{
*(p + i) = (i + 1) * 10;
}
int *pp = realloc(p, 20 * sizeof(int)); //也可以改小
printf("%p\n", pp);
for (int i = 0; i < 20; i++)
{
printf("%d ", *(p + i));
}
1.4 free – 释放空间(掌握)
如果申请的空间不再进行使用,一定要记得进行释放,如果不做释放,占用的空间会越来越多。函数参数为指针。
free(p);
2. malloc函数的细节
细节很多,但是我们不需要背下来,只要在代码运行结果与自己预期不一样时候知道怎么去改就可以了。
注释:
- malloc返回的是void类型具有通用性,可以转换为其他类型;
- int*记录的是步长,p记录的是首地址,所以sizeof§得不到内存空间的大小,需要定义一个变量进行记录,且最好记录的是数据的个数而不是占用内存空间的大小;
- 内存泄漏:不断地申请空间总有一天会满,这种情况就叫做内存泄露,所以要养成释放内存的好习惯hh;
- 虚拟内存:每一个内存空间不会在刚申请时就立马使用,当开始存储数据时才会真正的分配空间(有效提高了内存的使用效率);
3. C语言的内存结构
我们已经学习过了数组,为什么还要使用malloc等函数来申请空间、分配内存呢?
- 数组是整体存储在栈中,使用malloc等函数申请的空间是在堆当中,而堆的空间要比栈的空间大很多很多;
- 当函数运行结束,里面的数组也会随之消失,如果用malloc等函数,它占用的空间是在堆里面,只要不free(),就会一直存在直至程序结束。