C语言的内存管理比较让人头疼!虽然可以调用malloc
和free
函数手动的分配和释放内存,但是频繁的调用它们,容易产生内存碎片。具体可参考:malloc 内存分配原理及内存碎片产生的原因。并且,由于调用malloc
函数向操作系统申请堆内存,应用程序要经历从应用层到内核层再到应用层的转变,有时间上的消耗。为程序创建内存池可以提高效率,减少内存碎片。
所谓的内存池(memory pull)就是让程序额外维护的一个缓存区域。当用户申请一块内存块的时候,先在内存池中查找是否有合适的垃圾内存块。如果有,则直接从内存池中获取。如果没有,再调用malloc
函数申请。当用户释放一个内存块时,先检查内存池有没有空间,如果有,就将要释放的内存块扔到内存池中。
内存池的实现:
#include
#include
#define MAX 1024
int count;
struct Lin
{
char name[30];
char number[20];
struct Lin *next;
} ;
struct Lin *pool = NULL;
void ching(struct Lin *hand);
int recu(int i, struct Lin rea, char a[]);
void addperson(struct Lin **hand);
void findperson(struct Lin *hand);
void chingperson(struct Lin *hand);
void delperson(struct Lin **hand);
void displaycontacs(struct Lin *hand);
void freeper(struct Lin **hand);
int main(void)
{
struct Lin *han = NULL;
int num = 0;
while (num != 6)
{
putchar('\n');
printf("请输入命令:\n.....||1(添加新的联系人) ||.....\n.....||2:(查找联系人) ||.....\n.....||3:(更改联系人) ||.....\n.....||4:(删除联系人) ||.....\n.....||5:(查看所有联系人)||.....\n.....||6:(退出) ||.....");
scanf_s("%d", &num);
switch (num)
{
case 1: addperson(&han); break;
case 2: findperson(han); break;
case 3: chingperson(han); break;
case 4: delperson(&han); break;
case 5: displaycontacs(han); break;
}
}
freeper(&han);
}
void ching( struct Lin *hand)
{
int num;
while (1)
{
printf("输入1,改写名字\n输入2,改写号码\n输入-1,退出改写程序");
scanf_s("%d", &num);
switch (num)
{
case 1: printf("请输入名字:"); scanf_s("%s", hand->name, 30);
case 2: printf("请输入号码:"); scanf_s("%s", hand->number, 20);
default:printf("输入错误,请重新输入:");
case -1: break;
}
}
}
int recu(int i, struct Lin rea, char a[])
{
int z;
do
{
z = a[i] - rea.name[i];
i++;
if (z)
{
break;
}
} while(rea.name[i] != '\0');
if (rea.name[i] != '\0')
{
return -1;
}
return 0;
}
void addperson(struct Lin ** hand)
{
struct Lin *adper;
//如果内存池非空,则直接从里面获取空间
if (pool != NULL)
{
adper = pool;
pool = pool->next;
count --;
}
//如果内存池为空,则申请新的内存空间
else
{
adper = (struct Lin *)malloc(sizeof(struct Lin));
if (adper == NULL)
{
printf("分配内存失败!");
exit(1);
}
}
printf("请输入联系人的名字\n:");
scanf_s("%s", adper->name,30);
printf("请输入电话号码\n");
scanf_s("%s", adper->number,20);
if (*hand == NULL)
{
*hand = adper;
adper->next = NULL;
}
else
{
adper->next = *hand;
*hand = adper;
}
}
void findperson(struct Lin * hand)
{
int num;
char a[30];
printf("请输入名字:\n");
scanf_s("%s", a, 30);
while(1)
{
num = recu(0, *hand, a);
if (num == 0)
{
break;
}
hand = hand->next;
if (hand == NULL)
{
printf("抱歉,本通讯录没有此人\n");
break;
}
}
if (hand != NULL)
{
printf("你要找的%s的电话号码是%s\n", hand->name, hand->number);
}
}
void chingperson(struct Lin * hand)
{
int num;
char a[30];
printf("请输入名字:");
scanf_s("%s", a, 30);
while (1)
{
num = recu(0, *hand, a);
if (num == 0)
{
break;
}
hand = hand->next;
if (hand == NULL)
{
printf("抱歉,本通讯录没有此人\n");
break;
}
}
if (hand != NULL)
{
ching(hand);
}
printf("请确认:");
printf("姓名:%s",hand->name);
printf("号码:%s\n",hand->number);
}
void delperson(struct Lin ** hand)
{
int num;
struct Lin *chin;
chin = *hand;
struct Lin *nx;
nx = NULL;
char a[30];
printf("请输入名字:\n");
scanf_s("%s", a, 30);
while (1)
{
num = recu(0, *chin, a);
if (num == 0)
{
break;
}
nx = chin;
chin = chin->next;
if (hand == NULL)
{
printf("抱歉,本通讯录没有此人\n");
break;
}
}
if (count <= MAX)
{
if (chin == *hand)
{
*hand = chin->next;
if (pool = NULL)
{
pool = chin;
count ++;
}
else
{
chin->next = pool;
pool = chin;
count ++;
}
}
else
{
nx->next = chin->next;
if (pool = NULL)
{
pool = chin;
count ++;
}
else
{
chin->next = pool;
pool = chin;
count ++;
}
}
}
else
{
if (chin == *hand)
{
*hand = chin->next;
free(chin);
}
else
{
nx->next = chin->next;
free(chin);
}
}
}
void displaycontacs(struct Lin * hand)
{
printf("开始打印全部成分:\n");
while (hand != NULL)
{
printf("联系人:%s\n", hand->name);
printf("电话:%s\n", hand->number);
printf("====================================");
putchar('\n');
hand = hand->next;
}
printf("打印结束\n");
}
void freeper(struct Lin ** hand)
{
struct Lin *h;
struct Lin *n;
h = *hand;
*hand = NULL;
while (h != NULL)
{
n = h->next;
free(h);
h = n;
}
while (pool != NULL)
{
n = pool->next;
free(pool);
pool = n;
}
exit(1);
}
typedef
是C
语言最重要的关键字之一。
typedef
有一个最基本的功能就是给数据类型起别名。相比起宏定义的直接替换,typedef是对类型的封装。还可以起多个别名。
举个栗子:
#include
typedef int integer;
int main(void)
{
integer a;
int b;
a = 520;
b = a;
printf("a = %d\n",a);
printf("b = %d\n",b);
printf("size of a = %d\n",sizeof(a));
}
[liujie@localhost sle44]$ gcc test.c && ./a.out
a = 520
b = 520
size of a = 4
typedef
经常和结构体一起出现,直接给整个结构体起别名,并且可以同时定义一个指针。这是国际上的通用做法。
举个栗子:
#include
#include
typedef struct Date {
int year;
int month;
int day;
} DATE *PDATE;
int main(void)
{
DATE *date;
date = (DATE *)malloc(sizeof(DATE));
if (date == NULL)
{
printf("内存分配失败!\n");
exit(1);
}
date->year = 2022;
date->month = 11;
date->day = 13;
printf("%d-%d-%d\n",date->year,date->month,date->day);
return 0;
}
[liujie@localhost sle44]$ gcc test1.c && ./a.out
2022-11-13
同时定义一个指针
#include
#include
typedef struct Date {
int year;
int month;
int day;
} DATE ,*PDATE;
int main(void)
{
DATE *date;
date = (PDATE)malloc(sizeof(DATE));
if (date == NULL)
{
printf("内存分配失败!\n");
exit(1);
}
date->year = 2022;
date->month = 11;
date->day = 13;
printf("%d-%d-%d\n",date->year,date->month,date->day);
return 0;
}
[liujie@localhost sle44]$ gcc test1.c && ./a.out
2022-11-13
在编程中使用typedef
目的一般有两个:
举个栗子:
//数组指针
int (*ptr)[3];
typedef int (*PTR_TO_ARRAY)[3];
//函数指针
int (*fun)(void);
typedef int (*PTR_TO_FUN)(void);
//(*array[3])是一个指针数组
//int *A(int);是一个指针函数
int *(*array[3])(int);
//改写为
typedef int *(*PTR_TO_FUN)(int);
PTR_TO_FUN array[3];
//void(*funB)(int)是一个函数指针
//void (*funA(参数))(int)是一个函数指针,指向一个函数。该函数的参数是一个int类型数据,返回值为void类型数据。
void (*funA(int ,void(*funB)(int)))(int);
//改写为
typedef void (*PTR_TO_FUN)(void);
PTR_TO_FUN funA(int,PTR_TO_FUN);
这个写法与signal 函数一致。
共用体也称为联合类型或者联合体。其定义格式与结构体非常相似,只是关键字不一样。语法格式如下:
union 共用体名称
{
共用体成员1;
共用体成员2;
共用体成员3;
......
};
与结构体不同的是:共用体的所有成员共享同一个内存地址(类似人格分裂),但是不能正确访问所有成员的值。
举个栗子:
#include
#include
//声明共同体
union Test
{
int i;
double pi;
char str[6];
};
int main(void)
{
union Test test;
test.i = 520;
test.pi = 3.14;
strcpy(test.str,"FishC");
printf("addr of test.i:%p\n",&test.i);
printf("addr of test.pi:%p\n",&test.pi);
printf("addr of test.str:%p\n",&test.str);
printf("test.i:%d\n",test.i);
printf("test.pi:%.2f\n",test.pi);
printf("test.str:%s\n",test.str);
return 0;
}
[liujie@localhost sle44]$ gcc test2.c && ./a.out
addr of test.i:0x7fff7160b440
addr of test.pi:0x7fff7160b440
addr of test.str:0x7fff7160b440
test.i:1752394054
test.pi:3.13
test.str:FishC
定义共用体跟定义结构体的语法相似,可以先声明一个共用体类型,再定义共用体变量:
//声明一个共用体类型
union Data
{
int i;
char ch;
float f;
};
//定义共用体变量
union Data a,b,c;
也可以在声明的同时定义共用体变量:
union data
{
int i;
char ch;
float f;
} a,b,c;
初始化共用体:由于共用体在同一时间只能存放一个成员值,因此,不要试图初始化多个成员值。
与结构体一样的是:访问共用体成员使用(.
);访问共用体指针中成员使用(->
)。
枚举类型目的是为了提高程序的可读性。如果一个变量只有几种可能的值,那么就可以将其
定义为枚举(enumeration)类型。
声明枚举类型语法格式:
enum 枚举类型名称 {枚举值名称(枚举常量),枚举值名称(枚举常量)...};
枚举常量与宏定义类似,不同于宏定义的是枚举常量的值是整型的,是int
类型。默认情况下,从0开始定义。如果不希望总是以0作为起点,则可以在声明的时候进行赋值。
定义枚举变量:语法格式:
enum 枚举类型名称 枚举变量1,枚举变量2;
举个栗子:
#include
int main(void)
{
enum Color {red = 10,green,blue};
enum Color rgb;
for (rgb = red; rgb <= blue; rgb++)
{
printf("rgb is %d\n",rgb);
}
}
[liujie@localhost sle44]$ gcc test3.c && ./a.out
rgb is 10
rgb is 11
rgb is 12
举个栗子:
#include
#include
int main(void)
{
enum Week {sun,mon,tue,wed,thu,fri,sat};
enum Week today;
struct tm *p;
time_t t;
time(&t);
p = localtime(&t);
today = p->tm_wday;
switch(today)
{
case mon:
case tue:
case wed:
case thu:
case fri:
printf("干活!T_T\n");
break;
case sat:
case sun:
printf("放假!^_^\n");
break;
default:
printf("Error!\n");
}
return 0;
}
位域又可以称为“位段”,“位字段”。位域这种结构允许把一个字节拆开来使用,把一个字节里面的二进制位划分成不同的区域,并指定每个区域的位数。每个区域可以命名,并在程序中单独对其使用。
使用背景:单片机(Microcontrollers)是一种集成电路芯片,是采用超大规模集成电路技术把具有数据处理能力的中央处理器CPU、随机存储器RAM、只读存储器ROM、多种I/0口和中断系统、定时器/计数器等功能(可能还包括显示驱动电路、脉宽调制电路、模拟多路转换器、A/D转换器等电路)集成到一块硅片上构成的一个小而完善的微型计算机系统,在工业控制领域广泛应用。
使用位域的做法是在结构体定义时,在结构体成员后面使用冒号(:
)和数字来表示该成员所占的位数。
举个栗子:
#include
int main(void)
{
struct Test
{
unsigned int a:1;
unsigned int b:1;
unsigned int c:2;
};
struct Test test;
test.a = 0;
test.b = 1;
test.c = 2;
printf("a = %d, b = %d, c = %d\n",test.a,test.b,test.c);
printf("size of test = %d\n",sizeof(test));
return 0;
}
[liujie@localhost sle44]$ gcc test4.c && ./a.out
a = 0, b = 1, c = 2
size of test = 4 //4个字节
C语言规定:位域的宽度不能超过它所依附数据类型的长度。比如:出现错误
//33 > 4 * 8
struct Test
{
unsigned int a:1;
unsigned int b:1;
unsigned int c:33;
};
无名位域:位域成员可以没有名称,只要给出数据类型和位宽即可。
举个栗子:
struct Test
{
unsigned int x:1;
unsigned int y:1;
unsigned int z:1;
unsigned int :1;
};
C语言的标准说明:unsigned int
与signed int
支持位域。C99之后增加_bool
类型支持位域。其他数据类型理论上不支持。事实上,大多数编译器在具体的编译实现时,都进行了扩展,额外支持了signed char
、unsigned char
以及枚举类型。如果考虑到程序的可移植性,就需要谨慎使用!
由于内存的基本单位是字节,而位域是字节的一部分。因此,我们并不能对位域进行取址运算。
C语言的标准并没有规定一个字节是8位,具体是多大与运行环境有关!
C语言是这样规定:”可寻址的数据存储单位,其尺寸必须可以容纳运行环境的基本字符集的任何成员”
对于位的运算,C语言提供了位运算符包括逻辑位运算符与移位运算符。位运算符只作用于整型数据,并且对操作数中每一个二进制位进行运算。
C语言提供的逻辑位运算符有如下4种:
经常与赋值号结合使用。
举个栗子:
#include
int main(void)
{
int mask = 0xFF;
int v1 = 0xABCDEF;
int v2 = 0xABCDEF;
int v3 = 0xABCDEF;
v1 &= mask;
v2 |= mask;
v3 ^= mask;
printf("v1 = 0x%X\n",v1);
printf("v2 = 0x%X\n",v2);
printf("v3 = 0x%X\n",v3);
return 0;
}
[liujie@localhost sle44]$ gcc test5.c && ./a.out
v1 = 0xEF
v2 = 0xABCDFF
v3 = 0xABCD10
C语言除了提供四种逻辑位运算符之外,还提供了可以将某个变量中所有的二进制位进行左移或右移的运算符—移位运算符。左移、右移运算符也可以和赋值号结合。
左移位运算符:<<
,表示乘以2的n次幂。比如:11001010 << 2
。
右移位运算符:>>
,表示除以2的n次幂。比如:11001010 >>2
。
举个栗子:
#include
int main(void)
{
int value = 1;
while (value < 1024)
{
value <<= 1;
printf("value = %d\n",value);
}
printf("----------------------------\n");
value = 1024;
while (value > 0)
{
value >>= 2;
printf("value = %d\n",value);
}
return 0;
}
[liujie@localhost sle45]$ gcc test.c && ./a.out
value = 2
value = 4
value = 8
value = 16
value = 32
value = 64
value = 128
value = 256
value = 512
value = 1024
----------------------------
value = 256
value = 64
value = 16
value = 4
value = 1
value = 0
一些未定义行为:左移、右移运算符右边的操作数如果是为负数,或者右边的操作数大于左边操作数支持的最大宽度,那么表达式的结果均是属于“未定义行为”。
左边的操作数是有符号还是无符号数其实也对移位运算符有着不同的影响。无符号数肯定没问题,因为这时候变量里边所有的位都用于表示该数值的大小。但如果是有符号数,那就要区别对待了,因为有符号数的左边第一位是符号位,所以如果恰好这个操作数是个负数,那么移动之后是否覆盖符号位的决定权还是落到了编译器上。
掩码:在计算机学科中指的是一串二进制的数字,通过与目标数字按位操作来达到屏蔽指定位的需求而实现的。