前言
本文重点讲述了在内存管理当中的链表与动态内存的一种用法,在讲述这种用法之前,先讲解了数据流的概念,通过数据流的缓存,实现了连续申请内存,进行链表实现。
数据流概念
基于流的IO操作与基于文件描述符的IO操作十分类似。
对流进行操作的第一步是将其打开,可以调用库函数fopen()函数打开一个流,函数会返回一个FILE结构指针,结构体指针包括所开文件的描述符,为流准备的缓冲区指针、大小等。
当执行程序时,三个流会自动打开,分别是标准输入,标准输出和标准错误输出。对应的指针分别是stdin,stout,stderr。
当流被打开之后,就可以执行IO操作了,通过FILE指针,调用相应的库函数进行IO操作。
常见的流操作函数有:
函数 | 作用 |
---|---|
fopen、freopen、fdopen | 打开一个流、打开一个文件对应到流、打开一个流对应到文件 |
fclose | 关闭流 |
setbuf | 设置是否打开缓冲 |
setbuffer | 定义缓冲区大小 |
setlinebuf | 设定为行缓冲 |
setvbuf | 是以上三个函数的实现函数,可通过参数实现以上3个函数 |
fflush | 将缓冲区数据强制写入文件 |
fpurge | 将缓冲区内数据完全清楚 |
fread、fwrite | 直接读取或者写入,可指定读取、写入大小 |
feof | 检查是否到文件末尾 |
ferror | 检查是否出现了读写错误 |
printf、fprintf | 输出到“标准输出流”,输出到指定“流” |
sprintf、snprintf | 输出到字符串、snprintf可控制输出大小 |
scanf、fscanf、sscanf | 输入到“标准输入流”,输入到指定流,从一个字符串输入数据 |
fgetc、getc、getchar | 输出字符 |
fputc、putc、putchar | 输入字符 |
ungetc | 退回字符到流 |
fgets、gets | 读取指定大小数据,余下数据下次读取,gets不能 |
fputs、puts | 同上 |
静态内存与动态内存
静态内存 | 动态内存 | |
---|---|---|
优点 | 1、由编译器分配内存,无需释放,不会内存泄漏。 2、可直接使用变量名调用变量,无需使用指针。 | 1、随时分配,可自由释放 2、自定义大小,减少资源浪费 |
缺点 | 1、无法动态设置内存大小,容易溢出。 2、设置过大,容易造成资源浪费 | 1、管理复杂,容易内存泄漏 2、容易引起程序崩溃 |
链表与动态内存结合
动态内存在申请时,需要知道申请的大小,然后再进行申请,但经常是无法预知将要进行申请的大小的,可以使用链表的方式将其分块保存。
下面介绍一种经典用法:
main函数的主要部分如图所示:
获取输入信息主要由fgets函数完成,该函数基于流进行操作,存在着缓存信息,这里暂不做详细分析。
下面重点图解“添加至链表”
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define DATASIZE 10//设置一次存储字符串的大小
typedef struct stringdata
{
char *string;//指向字符串地址
int iscontinuing;//结束符标志
struct stringdata *next;//指向下一个结构体
}mydata;
mydata *append(mydata *start,char *input);//添加结构体
void displaydata(mydata *start);//显示字符串
void freedata(mydata *start);//释放字符串
int main(void)
{
char input[DATASIZE];//输入字符串
mydata *start=NULL;//第一个结构体,指向空地址
printf("ENTER SOME DATA,AND PRESS Ctrl+D WHEN DONE. \n");
while(fgets(input,sizeof(input),stdin))//满10个字节,就会返回一次1,基于流的操作
{
printf("your input words:%s\n",input);//打印一次完成
start=append(start,input);//将输入信息通过start结构体,插入链表
}
displaydata(start);//显示字符串
freedata(start);//释放链表
return 0;
}
mydata *append(mydata *start,char *input)
{
mydata *cur=start,*prev=NULL,*new;//设置3个结构体,分别为“之前”、“当前”、“新的”
while(cur)//遍历链表,直到cur变为null
{
prev=cur;//“之前”变为“当前”,
cur=cur->next;//继续遍历
}
cur=prev;//“当前”变为“之前”,用于配置next,因为“之前”的next为null
new=malloc(sizeof(mydata));//为new结构体申请内存
if(!new)//异常处理
{
printf("COULDN'T ALLOCATE MEMORY!\n");
exit(-1);
}
if(cur)//如果存在当前地址,则赋值
cur->next=new;
else
start=new;//start地址指向新的结构体地址
cur=new;//当前地址设为新的结构体地址
if(!(cur->string=malloc(sizeof(input)+1)))//当前的结构体中的string地址指向新的申请的内存
{
printf("ERROR ALLOCATING MEMORY!\n");
exit(-1);
}
strcpy(cur->string,input);//将输入信息放在string后的内存当中
cur->iscontinuing=!(input[strlen(input)-1]=='\n'||input[strlen(input)-1]=='\r');//判断是否收到结束符
cur->next=NULL;
return start;//start地址已被修改为最新的start
}
void displaydata(mydata* start)
{
mydata *cur;
int linecounter=0,structcounter=0;//行数,结构体数
int newline=1;//标志位
cur=start;
while(cur)//遍历链表
{
if(newline)//判断是否链表末尾
printf("LINE%d:",++linecounter);
structcounter++;
printf("%s",cur->string);//打印字符串
newline=!cur->iscontinuing;
cur=cur->next;//指向下一个
}
printf("THIS DATA CONTAINED %d LINES AND WAS STORED IN %d STRUCTS.\n",linecounter,structcounter);
}
void freedata(mydata *start)
{
mydata *cur,*next=NULL;
cur=start;
while(cur)//遍历链表
{
next=cur->next;//指向下一结构体
free(cur->string);//释放内存
free(cur);//释放内存
cur=next;//指向下一结构体
}
}