2023/10/08:
动态内存管理:
int val = 20; 在栈空间上开辟四个字节
char arr[10] = {0}; 在栈空间上开辟10个字节的连续空间
动态内存开辟原型:
空间开辟函数malloc
void* malloc (size_t size);
函数的作用是向堆区申请一块连续的空间,并返回指向这段空间的指针
- 如果开辟成功则返回开辟空间的指针
- 如果开辟失败则返回一个NULL指针,所以malloc以后记得要判断是否为NULL指针,如果是空指针则表示开辟空间失败。
- 返回值是void*,因为malloc函数不知道使用者要开辟什么类型的空间,所以决定权交给使用者来决定。
- 参数size_t size,则表示需要开辟多大的空间,以字节为单位。
空间释放函数free:
void free (void* ptr);
- free函数用来释放动态开辟的内存,如果动态内存开辟完空间,到程序结束都没有释放,那就会造成内存泄漏。
- 所以malloc和free函数是配对使用的。
- 如果参数 ptr 指向的空间不是动态开辟的,那free函数的行为是未定义的。
- 如果参数 ptr 是NULL指针,则函数什么事都不做。
养成一个良好的习惯:
if (p == NULL)
{
perror("calloc fail");
return;
}
#include <stdio.h>
#include <stdlib.h>
int main()
{
int* ptr = (int*)malloc(10 * sizeof(int));
if (NULL != ptr)//判断ptr指针是否为空
{
for (int i = 0; i < 10; i++)
{
ptr[i] = 0;
}
}
free(ptr);//释放ptr所指向的动态内存
ptr = NULL;
return 0;
}
几个注意的地方:
- malloc之后必须用别的类型的指针强转。
- 必须判断指针是不是空的,空的就得perror接上return
- 必须free
- free完之后必须让指针变为空指针。
calloc
void* calloc (size_t num, size_t size);
函数的功能和malloc的区别在于,calloc开辟的空间会把每个字节都初始化为0
- num代表要开辟的个数。
- size代表每个个数的大小是多大。
realloc
- realloc函数的出现让动态内存管理更加灵活。
- 有时会我们发现过去申请的空间太小了,有时候我们又会觉得申请的空间过大了,那为了合理的时候内存,我们一定会对内存的大小做灵活的调整。
- realloc 函数就可以做到对动态开辟内存大小的调整。
void* realloc (void*ptr, size_t size);
- ptr 是要调整的内存地址
- size 调整之后新大小
- 返回值为调整之后的内存起始位置。
- 这个函数调整原内存空间大小的基础上,还会将原来内存中的数据移动到新的空间。
int main() { int* ptr = (int*)malloc(100); if (ptr == NULL) { perror("malloc fail"); return; } //扩展容量 ptr = (int*)realloc(ptr, 1000);//这样可以吗?(如果申请失败会如何?) if(ptr == NULL) { perror("malloc fail"); return; } free(ptr); return 0; } int* p = (int*)realloc(ptr, 1000); if (p == NULL) { perror("realloc fail"); return; } ptr = p; free(ptr); ptr = p;
用下面的方法更好:
- malloc了空间的ptr指针作为接收返回值
- 如果申请失败realloc则会返回空指针
- 把空指针赋值给ptr不仅原本来的数据没了,还会造成内存泄漏
所以,新创建一个指针变量来接收realloc的返回值,如果是NULL指针直接返回,如果不是则把新指针给ptr。
常见的动态内存错误
对NULL指针的解引用操作
对动态开辟空间的越界访问
动态开辟内存忘记释放——内存泄露
void test()
{
int *p = (int*)malloc(100);
if(NULL != p)
{
*p = 20;
}
}
- 在test函数内部动态开辟了一次内存空间,由于疏忽忘记释放,所以会一直消耗内存
- 在test函数调用完毕后,直接被销毁,虽然动态开辟的空间还在,但却永远也找不到位置。
解决方案:
int* p = (int*)malloc(100);
if (NULL != p)
{
*p = 20;
}
free(p);
p = NULL;
不完全释放空间
指针p不该乱动!!!
void test() { int *p = (int *)malloc(100); p++; free(p);//p不再指向动态内存的起始位置 }
解决方案:
方案1:
*(p + i) = i;
printf("%d ",*(p + i));
方案2:
p[i]
这两种方法不对p++所以不会把p指向的位置转移走。
对非动态内存空间进行释放
void test() { int a = 10; int *p = &a; free(p); }
笔试题
void GetMemory(char *p)
{
p = (char *)malloc(100);
}
void Test(void)
{
char *str = NULL;
GetMemory(str);
strcpy(str, "hello world");
printf(str);
}
int main()
{
Test();
return 0;
}
把str传参传的是str的地址吗?不是的,传的是str指向的地址罢了,一级指针变量的地址应该用二级指针变量接受,所以传指针用指针接收的话,接收到的只是地址,即p指向0x00000000 NULL;把p指向的地址改了之后跟str指向的地址没关系。
内存开辟:
- 栈区(stack):在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。 栈区主要存放运行函数分配的局部变量、函数参数、返回数据、返回地址等。
- 堆区(heap):一般由使用者分配释放, 若使用者不释放,程序结束时可能由OS回收
- 数据段(静态区)(static)存放全局变量、静态数据。程序结束后由系统释放。
- 代码段:存放函数体(类成员函数和全局函数)的二进制代码。以及只读常量。
通讯录的实现
自定义类型:结构体,枚举,联合
结构体
结构是一些值的集合,这些值称为成员变量。结构的每个成员可以是不同类型的变量。
声明
struct tag
{
member-list;
}variable-list;
匿名结构体
结构的自引用:
struct Node
{
int data;
struct Node* next;
};
结构体变量的定义和初始化:
内存对齐:
如何计算?
首先得掌握结构体的对齐规则:
1. 第一个成员在与结构体变量偏移量为0的地址处。
2. 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值。
VS中默认的值为8
3. 结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍。
4. 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
为什么存在内存对齐?
1. 平台原因(移植原因):
不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。2.性能原因:
数据结构(尤其是栈)应该尽可能地在自然边界上对齐。
原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。
结构体的内存对齐是拿空间来换取时间的做法。
那在设计结构体的时候,我们既要满足对齐,又要节省空间,如何做到:
让占用空间小的成员尽量集中在一起。
struct S1
{
char c1;
int i;
char c2;
};
struct S2
{
char c1;
char c2;
int i;
};
S1和S2类型的成员一模一样,但是S1和S2所占空间的大小有了一些区别。
修改默认对齐数:
#pragma pack(n)
结构体传参
struct S s = {{1,2,3,4}, 1000};
//结构体传参
void print1(struct S s)
{
printf("%d\n", s.num);
}
//结构体地址传参
void print2(struct S* ps)
{
printf("%d\n", ps->num);
}
int main()
{
print1(s); //传结构体
print2(&s); //传地址
return 0;}
传地址更好!
位段:
位段的声明和结构是类似的,有两个不同:
1.位段的成员必须是 int、unsigned int 或signed int、char (属于整形家族)
2.位段的成员名后边有一个冒号和一个数字
位段的空间上是按照需要以4个字节( int )或者1个字节( char )的方式来开辟的
struct A
{
int _a:2;
int _b:5;
int _c:10;
int _d:30;
};A就是一个位段类型。
位段的内存:
这个图简洁明了:
枚举:
enum Day//枚举类型enum Day
{
Mon,//枚举常量
Tues,
Wed,
Thur,
Fri,
Sat,
Sun
};
枚举的优点
我们可以使用#define 定义常量,为什么非要使用枚举?
枚举的优点:
1. 增加代码的可读性和可维护性
2. 和#define定义的标识符比较枚举有类型检查,更加严谨。
3. 防止了命名污染(封装)
4. 便于调试
5. 使用方便,一次可以定义多个常量
枚举的使用:
enum Color//颜色
{
RED=1,
GREEN=2,
BLUE=4
};
enum Color clr = GREEN; //只能拿枚举常量给枚举变量赋值,才不会出现类型的差异。
联合(共用体)
这种类型定义的变量也包含一系列的成员,特征是这些成员公用同一块空间
//联合类型的声明
union Un
{
char c;
int i;
};
//联合变量的定义
union Un un;
//计算连个变量的大小
printf("%d\n", sizeof(un));
- 这样一个联合变量的大小,至少是最大成员的大小
- 当最大成员大小不是最大对齐数的整数倍的时候,就要对齐到最大对齐数的整数倍
联合的内存调用:
union Un
{
int i;
char c;
};
int main()
{
union Un un;
un.i = 1;
// 下面输出的结果是一样的吗?
printf("%x\n", &(un.i));
printf("%x\n", &(un.c));
//下面输出的结果是什么?
un.i = 0x11223344;
un.c = 0x55;
printf("%x\n", un.i);
return 0;
}
有2点:
- un.i与un.c的地址相同,但是读取的步长不一样,一个是int一个是char
-
- un.i = 0x11223344的时候,小端存储44 33 22 11,
- un.c = 0x55的时候,小端存储55,会把44替换掉。
预处理与define:
程序的翻译环境和执行环境
在ANSI C的任何一种实现中,存在两个不同的环境。
- 第1种是翻译环境,在这个环境中源代码被转换为可执行的机器指令。
- 第2种是执行环境,它用于实际执行代码。
翻译环境
- 组成一个程序的每个源文件通过编译过程分别转换成目标代码(object code)。
- 每个目标文件由链接器(linker)捆绑在一起,形成一个单一而完整的可执行程序。
- 链接器同时也会引入标准C函数库中任何被该程序所用到的函数,而且它可以搜索程序员个人的程序库,将其需要的函数也链接到程序中。
运行环境:
程序执行的过程:
1. 程序必须载入内存中。在有操作系统的环境中:一般这个由操作系统完成。在独立的环境中,程序
的载入必须由手工安排,也可能是通过可执行代码置入只读内存来完成。
2. 程序的执行便开始。接着便调用main函数。3. 开始执行程序代码。这个时候程序将使用一个运行时堆栈(stack),存储函数的局部变量和返回地址。程序同时也可以使用静态(static)内存,存储于静态内存中的变量在程序的整个执行过程一直保留他们的值。
4. 终止程序。正常终止main函数;也有可能是意外终止。
预定义符号
__FILE__ //进行编译的源文件
__LINE__ //文件当前的行号
__DATE__ //文件被编译的日期
__TIME__ //文件被编译的时间
__STDC__ //如果编译器遵循ANSI C,其值为1,否则未定义
预处理:
预处理宏:
LINE 表示正在编译的文件的行号
__FILE__表示正在编译的文件的名字__DATE__表示编译时刻的日期字符串,例如: “25 Dec 2007”
TIME 表示编译时刻的时间字符串,例如: “12:30:55”
STDC 判断该文件是不是定义成标准 C 程序 我的vs2013不是定义的标准c语言
#define
- 宏优点
- 1代码复用性
- 2提高性能
- 宏缺点
- 1 不可调试(预编译阶段进行了替换),
- 2无类型安全检查
- 3可读性差,容易出错
#define 定义标识符
#define MAX 1000 注意不要加上;
#define reg register //为 register这个关键字,创建一个简短的名字
#define do_forever for(;;) //用更形象的符号来替换一种实现
#define CASE break;case //在写case语句的时候自动把 break写上。
#define 定义宏
把参数替换到文本中,这种实现通常称为宏
宏的申明方式:
#define name( parament-list ) stuff其中的parament-list 是一个由逗号隔开的符号表,它们可能出现在stuff中。
注意:
参数列表的左括号必须与name紧邻。
如果两者之间有任何空白存在,参数列表就会被解释为stuff的一部分。
例子:#define SQUARE( x ) x * x
但是,这个宏存在一个问题:
SQUARE(4+1)!=25因为这样的:4+1*4+1=9
所以必须再加一个括号
即#define SQUARE( x ) (x) * (x)
又有这种情况:
DOUBLE(x) (x) + (x)
10 * DOUBLE(5);
10*(5)+(5)
解决办法是在宏定义表达式两边加上一对括号就可以了
DOUBLE( x) ( ( x ) + ( x ) )
#define 替换规则
在程序中扩展#define定义符号和宏时,需要涉及几个步骤。
1. 在调用宏时,首先对参数进行检查,看看是否包含任何由#define定义的符号。如果是,它们首先被替换。
2. 替换文本随后被插入到程序中原来文本的位置。
3. 最后,再次对结果文件进行扫描,看看它是否包含任何由#define定义的符号。如果是,就重复上述处理过程。
注意:
宏参数和#define 定义中可以出现其他#define定义的变量。但是对于宏,不能出现递归。
#undef NAME
//如果现存的一个名字需要被重新定义,那么它的旧名字首先要被移除。
条件编译
#if 与 if
条件编译是C语言中预处理部分的内容,它是编译器编译代码时最先处理的部分,
条件编译里面有判断语句,比如 #if 、#else 、#elif 及 #endif
它的意思是如果宏条件符合,编译器就编译这段代码,否则,编译器就忽略这段代码而不编译,如
#define A 0 //把A定义为0
#if (A > 1)
printf( "A > 1"); //编译器没有编译该语句,该语句不生成汇编代码
#elif (A == 1)
printf( "A == 1"); //编译器没有编译该语句,该语句不生成汇编代码
#else
printf( "A < 1"); //编译器编译了这段代码,且生成了汇编代码,执行该语句
#endif
而 if 语句则不然,if 是 C 语言中的关键字,它根据表达式的计算结果来觉定执行那个语句,它里面的每个分支都编译了的, 如
#define A 0
if (A > 1)
printf( "A > 1"); //编译器编译该语句,但因为A == 0 未执行
else if(A == 1)
printf( "A == 1"); //编译器编译该语句,但因为A == 0 未执行
else
printf( "A < 1"); //编译器编译该语句,因为A == 0 故执行
作为一个编译“开关”(常用来注释代码),比如:
#if(条件满足)
执行代码 1
#else
执行代码 2
#endif
#if 常量表达式
//...
#elif 常量表达式
//...
#else
//...
#endif
假如编译时,确实满足条件(结果非0时),则生成的程序文件(.exe文件)中不会有执行代码2的。如果用普通if语句,生成的程序文件就会有执行代码2,这个区别看看生成文件大小就可以知道。如果你的条件在程序编译前就已经确定了,那就用#if;如果条件需要在程序运行过程中才能判断,则用if。
文件包含
我们已经知道, #include 指令可以使另外一个文件被编译。就像它实际出现于#include 指令的地方一样。
这种替换的方式很简单:
预处理器先删除这条指令,并用包含文件的内容替换。
这样一个源文件被包含10次,那就实际被编译10次。
- 本地文件包含#include "filename"
- 库文件包含#include <filename.h>
- 对于库文件也可以使用 “”的形式包含,但是这样做查找的效率就低些
或者
#ifndef __TEST_H__
#define __TEST_H__
//头文件的内容
#endif
结合动态内存管理,文件操作来完成
通讯录
contact.c
#define _CRT_SECURE_NO_WARNINGS #include"contact.h" void InitContact(Contact* pc) { assert(pc); pc->count = 0; memset(pc->data, 0, sizeof(pc->data)); FILE* pfr = fopen("C:\\Users\\lxy\\Desktop\\c.txt","rb"); if (NULL == pfr) { perror(" "); return; } int i = 0; PeoInfo tmp = { 0 }; while (fread(&tmp, sizeof(PeoInfo), 1, pfr) == 1) { pc->data[pc->count] = tmp; pc->count++; } fclose(pfr); } void AddContact(Contact* pc) { assert(pc); if (pc->count == MAX) { printf("通讯录已满\n"); return; } printf("请输入名字\n"); scanf("%s", pc->data[pc->count].name); printf("请输入年龄\n"); scanf("%d", &(pc->data[pc->count].age)); printf("请输入性别\n"); scanf("%s", pc->data[pc->count].sex); printf("请输入电话\n"); scanf("%s", pc->data[pc->count].tele); printf("请输入地址\n"); scanf("%s", pc->data[pc->count].addr); printf("存储完毕\n"); pc->count++; } void ShowContact(Contact* pc) { int i = 0; for (i = 0; i < pc->count; i++) { printf("%-20s\t%-3s\t%-10s\t%-12s%\t%-30s\n","姓名","年龄","性别","电话","地址"); printf("%-20s\t%-3d\t%-10s\t%-12s%\t%-30s\n", pc->data[i].name, pc->data[i].age, pc->data[i].sex, pc->data[i].tele, pc->data[i].addr); } } void SearchContact(Contact* pc) { assert(pc); char name[20] = { 0 }; printf("请输入查找的名字"); scanf("%s", name); int pos = FindByName(pc,name); if (-1 == pos) { printf("不存在"); return; } else { printf("%-20s\t%-3s\t%-10s\t%-12s%\t%-30s\n", "姓名", "年龄", "性别", "电话", "地址"); printf("%-20s\t%-3d\t%-10s\t%-12s%\t%-30s\n", pc->data[pos].name, pc->data[pos].age, pc->data[pos].sex, pc->data[pos].tele, pc->data[pos].addr); } } void ModifyContact(Contact* pc) { assert(pc); char name[20] = { 0 }; printf("请输入修改的名字"); scanf("%s", name); int pos = FindByName(pc, name); if (-1 == pos) { printf("不存在"); return; } else { printf("请输入名字\n"); scanf("%s", pc->data[pos].name); printf("请输入年龄\n"); scanf("%d",&(pc->data[pos].age)); printf("请输入性别\n"); scanf("%s", pc->data[pos].sex); printf("请输入电话\n"); scanf("%s", pc->data[pos].tele); printf("请输入地址\n"); scanf("%s", pc->data[pos].addr); printf("修改完毕\n"); } } int FindByName(Contact* pc, char* name) { assert(pc); assert(name); int i = 0; for (i = 0; i < pc->count; i++) { if (0 == strcmp(name, (pc->data)[i].name)) return i; } return -1; } void DelContact(Contact* pc) { char name[20] = { 0 }; assert(pc); if (pc->count == 0) { printf("空通讯录,无法删除"); return; } printf("输入删除的名字"); scanf("%s", name); if (-1 == FindByName(pc, name)) { printf("没找到"); return; } if (-1 != FindByName(pc, name)) { int i = 0; for (i = FindByName(pc, name); i < pc->count-1; i++) { (pc->data)[i] = (pc->data)[i + 1]; } pc->count--; printf("删除成功"); } } int cmp(void* p1, void* p2) { assert(p1); assert(p2); return strcmp(((PeoInfo*)p1)->name, ((PeoInfo*)p2)->name); } void SortContact(Contact* pc) { qsort(pc->data, pc->count, sizeof(pc->data[0]), cmp); printf("成功"); } void SaveContact(Contact* pc) { assert(pc); FILE* pfw = fopen("C:\\Users\\lxy\\Desktop\\c.txt","wb"); if (NULL == pfw) { perror(" "); return; } int i = 0; for (i = 0; i < pc->count; i++) { fwrite(pc->data+i, sizeof(PeoInfo), pc->count, pfw); } fclose(pfw); pfw = NULL; }
test.c
#define _CRT_SECURE_NO_WARNINGS #include"contact.h" void menu() { printf("******************************************\n"); printf("****** 1.add 2.del *******\n"); printf("****** 3.search 4.modify *******\n"); printf("****** 5.show 6.sort *******\n"); printf("****** 0.exit *******\n"); printf("******************************************\n"); } int main() { int input = 0; Contact con; InitContact(&con); do { menu(); printf("请选择\n"); scanf("%d", &input); switch (input) { case 1: AddContact(&con); break; case 2: DelContact(&con); break; case 3: SearchContact(&con); break; case 4: ModifyContact(&con); break; case 5: ShowContact(&con); break; case 6: SortContact(&con); break; case 0: printf("exit\n"); SaveContact(&con); break; default: printf("选择错误,请重新选择"); break; } } while (input); }
contact.h
#define _CRT_SECURE_NO_WARNINGS #pragma once #define MAX 100 #define DEFAULT_SIZE 100 #include<stdio.h> #include<string.h> #include<assert.h> #include<stdlib.h> typedef struct PeoInfo { char name[20]; int age; char sex[10]; char tele[12]; char addr[30]; }PeoInfo; typedef struct Contact { PeoInfo data[100]; int count; }Contact; void InitContact(Contact* pc); void AddContact(Contact* pc); void ShowContact(Contact* pc); void SearchContact(Contact* pc); void DelContact(Contact* pc); void ModifyContact(Contact* pc); void SortContact(Contact* pc); void SaveContact(Contact* pc);