四十一、内存空间分配及动态申请与释放
41.1 虚拟内存空间分配
- 栈区:满足栈的思想,先进后出,先定义变量后分配空间
- 堆区:满足队列的思想,先进先出,先定义变量先分配空间
示例:
代码赏析:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int a; // 全局,静态区,未初始化.bss
int b = 100; // 全局,静态区,初始化,.data
static int c; // 静态变量 未初始化,.bss
static int d = 200; // 静态变量 ,初始化 .data
const int e = 10; // const修饰的全局,在静态区,.ro
char str[6] = "hello"; // str全局,初始化..data
//"hello":字符串常量,空间在ro
// 把字符串常量赋值一份给str,可以修改
char *p = "hello"; // p全局,初始化.data
//"hello":字符串常量,空间在ro
// p直接指向ro段的首地址,不可以修改
int *p = (int *)malloc(4); // 报错,全局不可以调用函数
int main(int argc, const char *argv[])
{
int a; // 局部,栈区
int b = 100; // 局部,栈区
static int c; // 静态局部变量 静态区,未初始化.bss
static int d = 200; // 静态局部变量 静态区,初始化.data
const int e = 10; // const修饰的局部在栈区
char str[] = "hello"; // str:在栈区,hello在静态只读段
char *p = "hello"; // p在栈区,hello在静态区的只读段
int *p = (int *)malloc(4); // p在栈区,malloc4字节在堆区
return 0;
}
41.2 内存空间的动态申请和释放
原因: 堆区空间生命周期长(由程序手动申请,手动释放),程序长期运行时堆区节约空间
- 在释放指针时,需要保存堆区空间的首地址,否则会造成空间泄露问题
41.2.1 malloc动态申请
malloc :
头文件:#include <stdlib.h>
格式 :
void *malloc(size_t size);
参数 :
size_t size 堆区分配空间字节大小
返回值:
void *
通用类型指针,可以指向任意类型的地址,但是需要类型强转
当堆区空间申请成功默认返回堆区首地址,失败返回NULL
使用格式:
分配单个空间:
int a;
int *p=&a
int *p=(int *)malloc(sizeof(int));
分配连续空间:
int arr[10]
int *p=arr;
int *p2=(int *)malloc(sizeof(int)*n);
41.2.1 free释放
free :
头文件:
#include <stdlib.h>
格式 :
void free(void *ptr);
返回值:
无
参数 :
void *ptr 表示释放的指针
使用格式:
int *p=(int *)malloc(sizeof(int));//一个整型所占用的空间
free(p);// 释放表示这片空间自由不被占用,操作系统会重新给其他变量分配
p=NULL; //防止野指针
野指针 :
1. 未初始化的指针,世界使用,成为野指针
int *p; //p计算机随机指向一片空间
*p=100;
2. 指针指向数组,通过指针越界访问
int arr[5];
int *p=arr;
*(p+5)=100;
3. 函数返回一个局部变量的地址
int *fun()
{
int arr[4];//局部变量,调用函数arr申请空间,函数结束释放
return arr;
}
int main()
{
int *p=fun(); //野指针
}
4. 指针指向堆区空间,释放堆区空间
int *p=(int *)malloc(sizeof(int));//一个整型所占用的空间
free(p);// 释放表示这篇空间自由不被占用,操作系统会重新给其他变量分配
*p=100;
四十二、typedef
42.1 typedef 类型重定义
typedef :
使用格式:
typedef 数据类型 类型别名;
1. 类型别名:满足命名规范
2. 类型别名可以是多个,多个之间使用逗号隔开
typedef int size_4; // size_4等价int
size_4 a=100; // 使用size_4 定义变量并初始化
变量的定义
int a;
int arr[10];
int arr[2][3];
int *p;
int **p;
int *p[3];
int (*p)[3];
int *p();
int (*p)();
int (*p[3])();
变量的数据类型
int ;
int [10];
int [2][3];
int *;
int **;
int *[3];
int (*)[3];
int *();
int (*)();
int (*[3])();
数据类型和typedef结合
typedef int size_4;
typedef int arr_t[10]; //arr_t不是数组变量名,是一个数组的类型
//arr_t 等价于 int [10]
typedef int arr_t[2][3];
typedef int *p_t;
typedef int **p_t;
typedef int *p_arr_t[3];
typedef int (*p_t)[3];
typedef int *p();
typedef int (*p)();
typedef int (*p[3])();
42.2 宏 与 typedef 的区别
#define N 100 N=100
#define size_4 int size_4=int
1. 宏属于宏替换,类型重定义起别名
2. 宏不属于c语句,类型重定义是C语句
3. 宏只能做基类型的替换,不可以做复杂类型
4. 类型重定义可以重定义任何复杂数据类型
typedef int arr_t[10]
#define N int [10] 不识别
四十三、结构体
43.1 结构体定义
- 数组存储多个类型相同的数据
- 如果需要存储类型不同的数据需要使用结构体
结构体: 存储类型相同或不同数据类型数据的构造类型
构造类型: 可以分割
定义格式:
struct 结构体名
{
数据类型 成员变量1;
数据类型 成员变量2;
数据类型 成员变量3;
...
数据类型 成员变量n;
};
1. struct:
定义结构体的关键字
2. 结构体名:
满足命名规范,可以省略,无名结构体
3. {} 不可以省略
4. 分号; 不可以省略
5. 数据类型:
基本类型,构造类型、空类型、指针类型
6. 成员变量的个数任意
7. 结构体描述不分配空间,直到定义结构体变量时
eg: 定义一个结构体,存储学生信息:姓名、性别、年龄,身份证,分数
struct studnet
{
char name[10];//姓名
char sex;//性别
int age;//年龄
char id[20];//身份证号
float score;//分数
};
结构体类型:struct studnet 如同 int 但内容不同
struct student a;
int a;
43.2 结构体变量以及初始化
间接初始化(常用)
间接定义变量: 定义结构体描述后,定义结构体变量
struct student
{
char name[10];//姓名
char sex;//性别
int age;//年龄
};//直接
1. 按顺序表初始化
struct student stu={"张三",'M',18};
struct student stu={'M',18,"张三"};//报错
2. 不按顺序表初始化
struct student stu={.sex='M',.age=18,.name="张三"};
3. 单个初始化
struct studnet stu;
strcpy(stu.name,"张三");
stu.age=18;
stu.sex='M'
4. 输入赋值
struct studnet stu;
scanf("%s",stu.name)//name是数组名,数组表示数组的首地址
scanf("%d",&stu.age);
scnaf("%c",&stu.sex);
直接初始化
直接定义变量: 定义结构体描述的同时定义结构体变量
1. 按顺序表初始化
struct student
{
char name[10];//姓名
char sex;//性别
int age;//年龄
}stu={"张三",'M',18},stu1;
2. 不按顺序表初始化
struct student
{
char name[10];//姓名
char sex;//性别
int age;//年龄
}stu={.sex='M',.age=18,.name="张三"};
3. 单个初始化
struct student
{
char name[10];//姓名
char sex;//性别
int age;//年龄
}stu;
strcpy(stu.name,"张三");
stu.age=18;
stu.sex='M';
4. 输入赋值
struct student
{
char name[10];//姓名
char sex; //性别
int age; //年龄
}stu;
scanf("%s",stu.name)//name是数组名,数组表示数组的首地址
scanf("%d",&stu.age);
scnaf("%c",&stu.sex);
5. 只有直接初始化才可以省略结构体名,无名结构体
struct
{
char name[10];//姓名
char sex;//性别
int age;//年龄
}stu={"张三",'M',18},stu1;
43.3 结构体数组
结构体数组: 实际上就是一维数组
定义格式: struct 结构体名 结构体变量名[常量表达式];
间接定义变量并初始化(常用)
struct student
{
char name[10];//姓名
char sex;//性别
int age;//年龄
};//间接
1. 按顺序表初始化
struct student stu[2]={{"张三",'M',18},{"李四",'W',20}};
struct student stu[2]={"张三",'M',18,"李四",'W',20};
2. 不按顺序初始化
struct student stu[2]={[1]={.sex='W',.name="李四",.age=20},
[0]={"张三",.sex='M',.age=18}};
3. 单个赋值初始化
struct student stu[2];
strcpy(stu[0].name,"张三");
stu[0].sex='M';
stu[0].age=18;
strcpy(stu[1].name,"张三");
stu[1].sex='M';
stu[1].age=18;
4. 循环输入赋值
struct student stu[2];
for(int i=0;i<2;i++)
{
scanf("%s",stu[i].name); //stu[i].name等价于name,就是数组名
scanf(" %c",&stu[i].sex);//stu[i].sex等价于sex,单字符输入需要加&
scanf("%d",&stu[i].age);
}
直接定义变量并初始化
1. 按顺序表初始化
struct student
{
char name[10];//姓名
char sex;//性别
int age;//年龄
}stu1[2]={{"张三",'M',18},{"李四",'W',20}},
stu2[2]={"张三",'M',18,"李四",'W',20};
2. 不按顺序初始化
struct student
{
char name[10];//姓名
char sex;//性别
int age;//年龄
}stu[2]={[1]={.sex='W',.name="李四",.age=20},
[0]={.name="张三",.sex='M',.age=18}};
3. 单个赋值初始化
struct student
{
char name[10];//姓名
char sex;//性别
int age;//年龄
}stu[2];
strcpy(stu[0].name,"张三");
stu[0].sex='M';
stu[0].age=18;
strcpy(stu[1].name,"张三");
stu[1].sex='M';
stu[1].age=18;
4. 循环输入赋值
struct student
{
char name[10];//姓名
char sex;//性别
int age;//年龄
}stu[2];
for(int i=0;i<2;i++)
{
//stu[i].name等价于name,就是数组名
scanf("%s",stu[i].name);
//stu[i].sex等价于sex,单字符输入需要加&
scanf(" %c",&stu[i].sex);
scanf("%d",&stu[i].age);
}
5. 当直接初始化时,可以省略结构体名,无名结构体
struct
{
char name[10];//姓名
char sex;//性别
int age;//年龄
}stu1[2]={{"张三",'M',18},{"李四",'W',20}},
stu2[2]={"张三",'M',18,"李四",'W',20};
43.4 结构体指针(重点)
定义:
struct 结构体名 *指针名;
和基本数据类型的指针的 使用、初始化以及赋值的方式是一样的
访问结构体成员
- 结构体不是基本数据类型,是构造类型
- 一个结构体变量包含多个成员
访问方式:
- 结构体变量.成员名;
- 结构体指针->成员名;
示例:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct Stu
{
char name[100];
int score;
};
int main(int argc, const char *argv[])
{
struct Stu s1;
//<==>int s1;
printf("%p\n",&s1);
struct Stu *p = &s1; //定义了一个结构体指针p指向s1这个结构体变量
printf("%p\n",p); //结构体指针,指向结构体的首地址
s1.score = 100;
p->score = 90; //直接通过结构体指针,访问到结构中的成员
//s1 (*p).score
strcpy(p->name,"zhangsan");
printf("姓名:%s\t成绩%d\n",p->name,s1.score);
printf("%ld\n",sizeof(p));
return 0;
}
43.5 结构体和typedef结合
typedef struct Stu
{
char name[100];
int score;
}Stu,*pstu;
//把struct Stu重定义为Stu类型,后面可以直接使用Stu定义结构变量
//把struct *重定义为pstu类型,后面可以直接使用pstu定义结构体指针变量
上面的代码可以拆分表示为:
struct Stu
{
charr name[100];
int score;
}s1;
struct Stu
{
charr name[100];
int score;
}*pstu;
例如:
int a; //a是一个变量名
typedef int a; //a被重定义过的int类型
使用示例:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct Stu
{
char name[100];
int score;
}Stu,*pstu; //把struct Stu类型重定义为Stu,
//把stu这个结构体指针类型,重定义为pstu
int a,*p; //int a; int *p;
int main(int argc, const char *argv[])
{
Stu s1;
pstu p; //定义了一个结构体指针变量p
p = &s1;
printf("%p\n",&s1);
printf("%p\n",p);
return 0;
}
43.6 结构体的大小
字节对齐的规则:
- 除了结构体中的第一个成员外,其他成员必须存储在其本身对齐量的整数倍位置
- 结构体的整体大小,需要是结构体中最大对齐成员对齐量的整数倍
结构体成员的对齐量 = (成员本身数据类型的大小<操作系统对齐数) ? 成员本身的大小 : 操作系统的对齐数 (在没有指定的情况下,32位按4Byte对齐,64位按8Byte对齐)
示例:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct A
{
double d2; //8
char c1; //1
//7
int *p; //8
int num1;
short int s2;
}a; //64位 32 //32位 24
int main(int argc, const char *argv[])
{
printf("%d\n",sizeof(a));
printf("%d\n",sizeof(double));
return 0;
}
例题:
- #pragma pack(2) 意思是按照两字节对齐
- 用空格隔开的是用于对齐的空白空间
struct data{
char t1;
char t2;
unsigned short t3;
unsigned long t4;
};
1+1+2+4=8
1+1+2+ 4 +8=16
struct data{
char t1;
int t2;
short t3;
};
1+ 3 +4+2+ 2 =12
1+ 3 +4+2+ 2 =12
struct s1
{
char c1;
int i;
char c2;
};
1+ 3 +4+1+ 3 =12
//2字节对齐
struct s2
{
char c1;
char c2;
int i;
};
1+1+4=6
typedef struct Test
{
short a;
struct
{
int b;
double c;
char d;
}p;
int e;
}Test;
(4+ 4 +8+1+ 7 =24)
2+ 6 +24+4+ 4 =40
#pragma pack(2)
typedef struct Test
{
short a;
struct
{
int b;
double c[10];
char d;
}p;
int e;
}Test;
(4+80+1+ 1 =86)
2+86+4=92
struct C{
char b;
int a;
short c;
};
1+ 3 +4+2+ 2 =12
struct C {
char a;
char b[3];
char c;
};
1+3+1=5
typedef struct
{
int b;
char a;
long e;
char c;
float d;
double t;
}node;
4+1+ 3 +8+1+ 3 +4+8=24
小作业:
- 间接定义结构体数组,进行4种方式的定义和初始化
- 定义结构体存储10辆车(车的信息:品牌、单价、颜色)
- 定义函数,实现循环输入
- 定义函数,实现排序
- 定义函数,计算红色车的个数
我写的:
1.
2.
代码如下:
1
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct student
{
char name[32];
int age;
};
void out_struct(struct student *stu, int n)
{
for (int i = 0; i < n; i++)
{
printf("姓名:%s\t年龄:%d\n", (*(stu + i)).name, (*(stu + i)).age);
}
}
int main(int argc, const char *argv[])
{
// 按顺序初始化
struct student stu1[2] = {{"张三", 12}, {"李四", 13}};
// 不按顺序初始化
struct student stu2[2] = {[1].age = 14, [0].name = "王五", [1].name = "赵六", [0].age = 16};
// 单个初始化赋值
struct student stu3[2];
strcpy(stu3[0].name, "德玛西亚");
stu3[0].age = 21;
strcpy(stu3[1].name, "艾欧尼亚");
stu3[1].age = 22;
// 循环输入赋值
struct student stu4[2];
for (int i = 0; i < 2; i++)
{
scanf("%s", stu4[i].name);
scanf("%d", &stu4[i].age);
}
out_struct(stu1,2);
out_struct(stu2,2);
out_struct(stu3,2);
out_struct(stu4,2);
return 0;
}
2
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct car
{
char pinpai[32];
float danjia;
char color[16];
} che;
void input_car(che *car);
void sort_car(che *car);
int calc_red_car(che *car);
void output_car(che *car);
void swap_car(che *car1, che *car2);
int main(int argc, const char *argv[])
{
che car[10];
input_car(car);
sort_car(car);
output_car(car);
printf("红色车有 %d 辆\n", calc_red_car(car));
return 0;
}
void input_car(che *car)
{
for (int i = 0; i < 10; i++)
{
printf("第 %d 辆\n", i + 1);
printf("请输入车的品牌:");
scanf("%s", (*(car + i)).pinpai);
printf("请输入车的单价:");
scanf("%f", &(*(car + i)).danjia);
printf("请输入车的颜色:");
scanf("%s", (*(car + i)).color);
}
}
void swap_car(che *car1, che *car2)
{
che temp;
// temp.danjia=(*car1).danjia;
// strcpy(temp.color,(*car1).color);
// strcpy(temp.pinpai,(*car1).pinpai);
// (*car1).danjia=(*car2).danjia;
// strcpy((*car1).color,(*car2).color);
// strcpy((*car1).pinpai,(*car2).pinpai);
// (*car2).danjia=temp.danjia;
// strcpy((*car2).color,temp.color);
// strcpy((*car2).pinpai,temp.pinpai);
temp = *car1;
*car1 = *car2;
*car2 = temp;
}
// 价格升序
void sort_car(che *car)
{
for (int i = 0; i < 10; i++)
{
for (int j = 0; j < 10 - i - 1; j++)
{
if ((*(car + j)).danjia > (*(car + j + 1)).danjia)
{
swap_car(car + j, car + j + 1);
}
}
}
}
int calc_red_car(che *car)
{
int num = 0;
for (int i = 0; i < 10; i++)
{
if (!strcmp((*(car + i)).color, "红色"))
{
num++;
}
}
return num;
}
void output_car(che *car)
{
for (int i = 0; i < 10; i++)
{
printf("品牌:%s\t单价:%f\t颜色:%s\n",
(*(car + i)).pinpai, (*(car + i)).danjia, (*(car + i)).color);
}
}