上一篇最后,我们完成了基本的数据存储结构,今天我们来实现基本的数据操作API。
在堆中申请内存
前面我们讲过,如果需要使用大量内存空间的话,需要用malloc函数申请堆中的空间。如:
int* p = (int*)malloc(100000 * sizeof(int));
这里要说的是一个非常常用的知识,通过一个函数调用得到一段堆中的空间。先看一下这段代码:
void fun(int* p)
{
p = (int*)malloc(10 * sizeof(int));
}
int main()
{
int* pBuf = NULL;
fun(pBuf);
pBuf[0] = 1;
return 0;
}
这段程序的目的是通过对函数fun的调用,实现给pBuf指针一段堆空间。大家可以试试运行一下这段程序,结果是在执行pBuf[0] = 1这句的时候报错了。因为pBuf依然是一个空指针。
这就说明fun()函数没有达到预期的效果。让我们看看错在哪里。当我们把指针pBuf当做参数传给fun时,形参p和指针pBuf同时指向了同一个位置。由于pBuf为空,此时p也为空。
当malloc申请一段内存空间之后,p指针指向了这段内存空间。此时的pBuf依然是空指针。当fun函数执行完成后,形参p的生命周期结束被销毁,而那段空间就没有指针去指向它了。
那正确的方法是什么呢?应该是这样:
void fun(int** p)
{
*p = (int*)malloc(10 * sizeof(int));
}
int main()
{
int* pBuf = NULL;
fun(&pBuf);
pBuf[0] = 1;
return 0;
}
看出区别了吗?通过指针的指针让形参p能够找到pBuf从而在fun函数内部让pBuf指向新申请的空间。
当然,我们还有另外一种方法,通过返回值来传出申请内存空间的地址。代码如下:
int* fun()
{
int *p = (int*)malloc(10 * sizeof(int));
return p;
}
int main()
{
int* pBuf = fun();
pBuf[0] = 1;
return 0;
}
这样也是可以的,不过对于一些复杂的操作可能有局限性。
String类型修改
我们上一个项目中的String类型空间是以数组的方式定义的,用的是栈空间。我们现在要把它改成堆空间。
在String.h中,我们做了如下修改:
-
结构体定义
typedef struct _tagString { char* pBuf; int len; }String;
-
初始化函数声明
String* StringCreate();
-
内存回收函数声明
void StringDestroy(String* pStr);
在String.c中,加上新增两个函数的函数体:
String* StringCreate()
{
String *pStr = (String*)malloc(sizeof(String));
pStr->pBuf = (char*)malloc(BUF_SIZE * sizeof(char));
pStr->pBuf[0] = '\0';
pStr->len = 0;
return pStr;
}
void StringDestroy(String* pStr)
{
if (pStr == NULL)
{
return;
}
if (pStr->pBuf != NULL)
{
free(pStr->pBuf);
pStr->pBuf = NULL;
pStr->len = 0;
}
free(pStr);
}
初始化函数是我们今天讲的在函数中申请内存空间,而这段空间需要在不用时自行删除,否则其他程序无法使用。
这里我们用一个之前的例子做修改,看看新的String类型如何使用。
int main(void)
{
char a[] = "ABCDE";
char b[] = "FGHIJ";
String* pStr = StringCreate();
StringSet(pStr, a);
printf("%s\n", pStr->pBuf);
StringAppend(pStr, b);
printf("%s\n", pStr->pBuf);
StringDestroy(pStr);
return 0;
}
还是这个字符串拼接的程序,看懂了吗?
Record类型修改
为了用上我们今天讲的内容,我们把Record.h这样修改:
#ifndef __RECORD_H__
#define __RECORD_H__
#include "String.h"
typedef struct _tagRecord
{
String* _pStrName;
String* _pStrTel;
String* _pStrPS;
}Record;
void RecordInit(Record** pR);
void RecordDestroy(Record* pR);
#endif
再创建新文件Record.c,内容如下:
#include "Record.h"
void RecordInit(Record** pR)
{
*pR = (Record*)malloc(sizeof(Record));
(*pR)->_pStrName = StringCreate();
(*pR)->_pStrTel = StringCreate();
(*pR)->_pStrPS = StringCreate();
}
void RecordDestroy(Record* pR)
{
if (pR != NULL)
{
StringDestroy(pR->_pStrName);
StringDestroy(pR->_pStrTel);
StringDestroy(pR->_pStrPS);
free(pR);
}
}
RecordInit相对较复杂一些,有问题欢迎讨论。今天的内容就是这些,请大家好好练习。