2023-5-10首次编辑
C[内存管理/构造类型]
Unit 8:内存管理
一.概念
作用域:
- 变量起作用的范围
生命周期:
- 开辟空间到释放空间的整个过程
局部变量(auto)
- 作用域 : 在定义变量的{}中有效
- 生命周期 : 程序运行至变量定义处开辟空间,所在的函数结束之后释放空间
- 未初始化的值 : 随机
静态局部变量(static)
- 作用域 : 在定义变量的{}中有效
- 生命周期 : 指向面函数之前就已经开辟空间,程序结束之后才释放空间
- 未初始化的值 : 0
全局变量
- 作用域 : 整个工程,所有文件
- 生命周期 : 执行main函数之前九已经开辟空间,程序结束之后才释放空间
- 未初始化的值 : 0
静态全局变量
- 作用域 : 当前文件
- 生命周期 : 执行main函数之前九已经开辟空间,程序结束之后才释放空间
- 未初始化的值 : 0
注意:
- 在头文件中,全局变量只声明不定义,定义只放在源文件中
- 变量重名时,考虑作用域的前提下,采用就近原则(不同作用域可以重名)
二.静态函数
- 只可以被当前文件函数调用
- 普通函数 == 全局函数,整个工程可以调用
三.mem系列
#include <string.h>
1.memset
void *memset(void *s, int c, size_t n);
功能:将s的内存区域的前n个字节以参数c填入
参数:
s:需要操作内存s的首地址
c:填充的字符,c虽然参数为int,但必须是unsigned char , 范围为0~255
n:指定需要设置的大小
返回值:s的首地址
2.memcpy
void *memcpy(void *dest, const void *src, size_t n);
功能:拷贝src所指的内存内容的前n个字节到dest所值的内存地址上。
参数:
dest:目的内存首地址
src:源内存首地址
//dest和src所指的内存空间不可重叠,可能会导致程序报错
n:需要拷贝的字节数
返回值:dest的首地址
3.memcmp
int memcmp(const void *s1, const void *s2, size_t n);
功能:比较s1和s2所指向内存区域的前n个字节(遇到0,'\0'不会结束操作)
参数:
s1:内存首地址1
s2:内存首地址2
n:需比较的前n个字节
返回值:
相等:=0
大于:>0
小于:<0
#define _CRT_SECIRE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
//1.memset
//填充
void test1()
{
int a = 10;
memset(&a, 0, sizeof(a));
printf("a=%d\n", a); //0
char buf[10] = "";
strcpy(buf, "hello");
printf("%s\n", buf); //hello
memset(buf, 0, sizeof(buf));
printf("%s\n", buf); //空
memset(buf, 'a', sizeof(buf)-1);
printf("%s\n", buf); //aaaaaaaaa
}
//2.memcpy
//拷贝
void test2()
{
char str1[128] = "";
char str2[128] = "abcdef\0ABCDEF";
int a[10] = { 1,2,3,4,5,6,7,8,9,10 };
int b[10] = { 0 };
memcpy(str1, str2, sizeof(char) * 10); //遇到'\0'会结束拷贝
printf("%s\n", str1); //abcdef
memcpy(b, a, sizeof(10) * 5);
for (int i = 0; i < sizeof(b)/sizeof(b[0]); i++)
{
printf("%d", b[i]);
}
printf("\n");
}
//3.memcmp
//比较
void test3()
{
char num1[] = { 1,0,3,4,5,6,7 };
char num2[] = { 1,0,3,7,6,5,4 };
char str1[] = "abc\0ABC";
char str2[] = "abc\0CBA";
printf("%d\n", memcmp(num1, num2, sizeof(char) * 7)); //-1
printf("%d\n", strncmp(num1, num2, sizeof(char) * 7)); //0,遇到0会结束
printf("%d\n", memcmp(str1, str2, sizeof(str1))); //-1
printf("%d\n", strncmp(str1, str2, sizeof(str1))); //0,遇到'\0'会结束
}
int main()
{
//test1();
//test2();
test3();
system("pause");
return 0;
}
四.申请/释放
#include <stdlib.h>
1.malloc申请
void *malloc(size_t size);
功能:在内存的动态存储区(堆区)中分配一块长度为size字节的连续区域,用来存放类型说明符指定的类型。分配的内存空间内容不确定,一般使用memset初始化。
参数:
size:需要分配内存大小(单位:字节)
返回值:
成功:分配空间的起始地址
失败:NULL
2.free释放
void free(void *ptr);
功能:释放ptr所指向的一块内存空间,ptr是一个任意类型的指针变量,指向被释放区域的首地址。
//对同一内存空间多次释放会出错
参数:
ptr:需要释放空间的首地址,被释放区应是由malloc函数所分配的区域。
返回值:无
#define _CRT_SECIRE_NO_WARNINGS
#include <stdio.h>
void test1()
{
//1.申请
char *p = (char *)malloc(1024);
memset(p, 0, 1024);
strcpy(p, "helloworld");
printf("%s\n", p);
//2.释放
//free只能释放一次
//地址必须与malloc的相同,不能改变这个地址
free(p);
//free(p+1); //err
}
void test2()
{
int *p = (int *)malloc(sizeof(int) * 10);
memset(p, 0, sizeof(int) * 10);
*p = 1000;
*(p + 5) = 2000;
for (int i = 0; i < 10; i++)
{
printf("%d ", *(p + i));
}
printf("\n");
free(p);
}
int main()
{
test1();
test2();
system("pause");
return 0;
}
五.内存泄漏/污染
内存泄漏:
- 只申请,不释放
内存污染:
- 向没有申请过的内存空间写入数据
程序退出后,所使用的所有内存都将释放
六.子函数返回地址
返回变量的地址:
- 只有普通局部变量的地址不可以返回
- 普通局部变量在所在的函数结束之后会被释放
返回堆区的地址
- 只要没被free释放
- 都可以被返回
#define _CRT_SECIRE_NO_WARNINGS
#include <stdio.h>
void *func()
{
char *q = (char *)malloc(100);
return q; //返回堆区地址
}
void test()
{
char *p = func();
//p = "hello";
//进行free会报错
//p此时指向的是文字常量区"hello"
strcpy(p, "hello");
//p此时指向的还是堆区空间
free(p);
}
int main()
{
test();
system("pause");
return 0;
}
七.大小端
小端:
- 高位高地址,低位低地址(逆序)
- 应用于小型计算机
大端:
- 高位低地址,低位高地址(顺序)
- 应用于大型服务器,网络上的数据
Unit 8:构造类型
一.结构体(复合类型)
将多个相同或不同类型的数据存放在一块连续的内存空间中
#define _CRT_SECIRE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
//定义一个结构体数据类型
//1.只是一个类型丶一个模板,没有空间,不可以给结构体成员赋值
//2.{}后面需要加分号
struct stu1
{
int id;
int age;
char name[128];
}a; //定义类型时,同时定义了两个结构体变量struct stu a;
void test1()
{
//1.初始化所有成员
//struct stu d = { 1,20,"悟空" };
//2.部分成员初始化,其他成员内容为0
//struct stu d = { .age = 20 };
//3.所有成员不初始化
struct stu1 d;
//1.通过结构体变量操作结构体成员,使用点域.
/*d.id = 2;
d.age = 22;
strcpy(d.name, "贝吉塔");
printf("%d %d %s\n", d.id, d.age, d.name);*/
//2.通过结构体地址操作结构体成员,使用->
(&d)->id = 3;
(&d)->age = 3000;
strcpy((&d)->name, "比克大魔王");
printf("%d %d %s\n", (&d)->id, (&d)->age, (&d)->name);
}
//结构体数组:
//每一个数组元素都是结构体
void test2()
{
struct stu1 num[5] = { {1,20,"悟空"},{2,22,"贝吉塔"},{3,3000,"比克大魔王"} };
//打印
for (int i = 0; i < sizeof(num)/sizeof(num[0]); i++)
{
printf("%d %d %s\n", num[i].id, num[i].age, num[i].name);
}
}
//结构体套结构体
struct stu2
{
struct stu1 s;
char subject[128];
};
void test3()
{
struct stu2 c;
c.s.id = 1;
c.s.age = 20;
strcpy(c.s.name, "悟空");
strcpy(c.subject, "C");
printf("%d %d %s %s\n", c.s.id, c.s.age, c.s.name, c.subject);
}
//结构体赋值
void test4()
{
struct stu1 a = { 1,20,"悟空" };
struct stu1 b;
b = a; //相同类型的变量之间可以相互赋值
printf("%d %d %s\n", b.id, b.age, b.name);
}
int main()
{
//test1();
//test2();
//test3();
test4();
system("pause");
return 0;
}
二.结构体与指针
#define _CRT_SECIRE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
struct stu
{
int id;
int age;
char name[128];
};
//结构体指针
void test1()
{
//p不能是野指针
//1.
//struct stu a;
//struct stu *p = &a;
//2.
struct stu *p = (struct stu *) malloc(sizeof(struct stu));
p->id = 1;
p->age = 20;
strcpy(p->name, "悟空");
printf("%d %d %s\n", p->id, p->age, p->name);
free(p);
}
//结构体套指针
struct t
{
int a;
};
struct tea
{
int id;
char *p; //字符指针
struct t *b; //结构体指针
};
void test2()
{
struct tea *tmp = (struct tea *)malloc(sizeof(struct tea));
tmp->id = 100;
//1.p指向常量区
//tmp->p = "hello";
//strcpy(tmp->p, "world"); //err:不可以修改常量区的内容
//2.p指向堆区空间
tmp->p = (char *)malloc(100);
strcpy(tmp->p, "hello");
strcpy(tmp->p, "world");
printf("%s\n", tmp->p);
//不能直接赋值b所指向的空间,否则会造成野指针
tmp->b = (struct t *)malloc(sizeof(struct t));
tmp->b->a = 1000;
//从内往外释放申请的堆区空间
free(tmp->p);
free(tmp->b);
free(tmp);
}
//结构体数组作为函数的形参
void set_num(struct stu *p, int n)
{
for (int i = 0; i < n; i++)
{
//(*(p + i)).id = i + 10;
//(p + i)->id = i + 10;
p[i].id = i + 10; //10 11...
p[i].age = i + 100; //100 101...
char buf[128] = "";
sprintf(buf, "%d%d%d", i, i, i);
strcpy(p[i].name, buf); //000 111...
}
}
void test3()
{
struct stu num[5];
memset(num, 0, sizeof(num));
set_num(num, sizeof(num) / sizeof(num[0])); //此处num = &num[0]
for (int i = 0; i < sizeof(num)/sizeof(num[0]); i++)
{
printf("%d %d %s\n", num[i].id, num[i].age, num[i].name);
}
printf("\n");
}
//const修饰的结构体指针变量
void test4()
{
struct stu a;
struct stu b;
a.id = 1;
b.id = 2;
//1.结构体常量指针
struct stu const *p = &a;
//p->id = 100; //err:不能修改指针所指向空间的内容
p = &b; //可以修改指针指向
printf("%d\n", p->id);
//2.结构体指针常量
struct stu * const q = &a;
q->id = 100; //可以修改指针所指向空间的内容
//q = &b; //err:不能修改指针指向
printf("%d\n", q->id);
}
int main()
{
//test1();
//test2();
//test3();
test4();
system("pause");
return 0;
}
三.共用体
- 多个变量共用同一块内容空间
- 同一时刻,只能有一个变量起作用
#define _CRT_SECIRE_NO_WARNINGS
#include <stdio.h>
union abc
{
short a;
char buf[2];
};
//共用体验证大小端
void test()
{
union abc tmp;
tmp.a = 0x0102;
if (tmp.buf[0] = 0x01)
{
printf("大端:高位低地址,低位高地址(顺序)\n");
}
else
{
printf("小端:高位高地址,低位低地址(逆序)\n");
}
}
int main()
{
test();
system("pause");
return 0;
}
四.枚举
- 将枚举类型的变量值一一列出来,变量的值只限于列举出来的值的范围内
#define _CRT_SECIRE_NO_WARNINGS
#include <stdio.h>
//值都为常量,默认从0开始(顺序递增)
enum ab { SUN, RAIN = 5, SNOW };
void test()
{
enum ab tmp = SNOW; //枚举变量的值只可以赋值为{}里面的值
printf("%d %d %d\n", SUN, RAIN, SNOW); //0,5,6
int a = 0;
scanf("%d", &a);
if (SUN == a)
{
printf("sleep\n");
}
else if (RAIN == a)
{
printf("music\n");
}
else
{
printf("game\n");
}
}
int main()
{
test();
system("pause");
return 0;
}
五.typedef关键字
取别名
#define _CRT_SECIRE_NO_WARNINGS
#include <stdio.h>
//常用于结构体
typedef struct stu
{
int id;
int age;
}SS; //取的别名
void test()
{
SS tmp; //SS == struct stu
tmp.id = 1;
tmp.age = 100;
printf("%d %d\n", tmp.id, tmp.age);
}
int main()
{
test();
system("pause");
return 0;
}