上篇文章,介绍了C指针(一级指针)的用法。本篇,来介绍二级指针的使用场景。
如果上篇对一级指针有了深刻的了解,那二级指针就和容易推理地理解其用法。
简单来说,在函数传参中,因为实参是传递的拷贝的值给形参,所以如果想通过函数参数来将数值传递出来,就需要改用传这个数据的地址,让函数根据这个地址来修改对于位置的数据的值,也就是一级指针的一种使用场景。
由此推理,如果想通过函数参数来将指针类型的数据传递出来,就需要改用传这个指针的地址,也就是二级指针了。
1 读取cJson文件的例子
1.1 不带函数的解析示例
整个逻辑都写在main函数中,实现对josn文件的读取、解析操作:
test1.c
c
// gcc test1.c cjson/cJSON.c -o test1
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "cjson/cJSON.h"
#define CONFIG_FILE "config.json"
int main(void)
{
FILE *fp = fopen(CONFIG_FILE, "r");
if (!fp)
{
printf("fopen:%s failed\n", CONFIG_FILE);
return -1;
}
// 计算文件的大小
fseek(fp, 0, SEEK_END);
long len = ftell(fp);
fseek(fp, 0, SEEK_SET);
// 分配内存
char *buf = malloc(len + 1);
if (NULL == buf)
{
fclose(fp);
return -1;
}
memset(buf, 0, len + 1); // 清空,防止脏数据
// 读取内存
fread(buf, 1, len, fp);
fclose(fp);
cJSON_Minify(buf); // 删除注释+压缩
// json解析
cJSON *jRoot = NULL;
jRoot = cJSON_Parse(buf);
// 释放内存
free(buf);
buf = NULL;
if (NULL == jRoot)
{
printf("cJSON_Parse err: %s\n", cJSON_GetErrorPtr());
return -1;
}
// 输出无格式 JSON(紧凑字符串)
char *s = NULL;
s = cJSON_PrintUnformatted(jRoot);
// 释放整个JSON树
cJSON_Delete(jRoot);
jRoot = NULL;
if (NULL == s)
{
printf("cJSON_PrintUnformatted: %s\n", cJSON_GetErrorPtr());
return -1;
}
printf("jRoot:%s\n", s);
free(s);
s = NULL;
return 0;
}
准备一个json文件,然后运行,结果如下:

1.2 将josn文件的读取和解析封装为函数
要封装的函数:
c
int read_json_file(const char *filePath, cJSON **jsonRoot);
参数:
- filePath:json文件的地址
- jsonRoot:解析出的josn数据的地址(二级指针)
返回值:
- 成功返回0,失败返回-1
注:这里是通过函数参数演示二级指针,实际使用中,如果不想用二级指针,也可以通过函数返回值josn的数据地址,这样只需要使用一级指针。例如:
ccJSON *read_json_file(const char *filePath);通过判断返回值是否是空指针,来判断解析是否成功,也是一种方法
完整代码:
test2.c
c
// gcc test2.c cjson/cJSON.c -o test2
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "cjson/cJSON.h"
#define CONFIG_FILE "config.json"
int read_json_file(const char *filePath, cJSON **jsonRoot)
{
printf("[%s:%d] jsonRoot:%p(&jsonRoo:%p)[*jsonRoot:%p]\n", __func__, __LINE__, jsonRoot, &jsonRoot, *jsonRoot);
FILE *fp = fopen(filePath, "r");
if (!fp)
{
printf("fopen:%s failed\n", filePath);
return -1;
}
// 计算文件的大小
fseek(fp, 0, SEEK_END);
long len = ftell(fp);
fseek(fp, 0, SEEK_SET);
// 分配内存
char *buf = malloc(len + 1);
if (NULL == buf)
{
fclose(fp);
return -1;
}
memset(buf, 0, len + 1); // 清空,防止脏数据
// 读取内存
fread(buf, 1, len, fp);
fclose(fp);
cJSON_Minify(buf); // 删除注释+压缩
// json解析
*jsonRoot = cJSON_Parse(buf);
// 释放内存
free(buf);
buf = NULL;
printf("[%s:%d] jsonRoot:%p(&jsonRoo:%p)[*jsonRoot:%p]\n", __func__, __LINE__, jsonRoot, &jsonRoot, *jsonRoot);
return 0;
}
int main(void)
{
cJSON *jRoot = NULL;
printf("[%s:%d] jRoot:%p(&jRoot:%p)\n", __func__, __LINE__, jRoot, &jRoot);
int ret = read_json_file(CONFIG_FILE, &jRoot);
if (0 != ret)
{
printf("read_json_file err:%d\n", ret);
return -1;
}
if (NULL == jRoot)
{
printf("cJSON_Parse err: %s\n", cJSON_GetErrorPtr());
return -1;
}
printf("[%s:%d] jRoot:%p(&jRoot:%p)\n", __func__, __LINE__, jRoot, &jRoot);
// 输出无格式 JSON(紧凑字符串)
char *s = NULL;
s = cJSON_PrintUnformatted(jRoot);
// 释放整个JSON树
cJSON_Delete(jRoot);
jRoot = NULL;
if (NULL == s)
{
printf("cJSON_PrintUnformatted: %s\n", cJSON_GetErrorPtr());
return -1;
}
printf("jRoot:%s\n", s);
free(s);
s = NULL;
return 0;
}
准备一个json文件,然后运行,结果如下:

分析一下数据的关系:

对照这个简化的示例:
c
cJSON *jRoot = NULL;
printf("[%s:%d] jRoot:%p(&jRoot:%p)\n", __func__, __LINE__, jRoot, &jRoot);
int ret = read_json_file(CONFIG_FILE, &jRoot);
printf("[%s:%d] jRoot:%p(&jRoot:%p)\n", __func__, __LINE__, jRoot, &jRoot);
//...
cJSON_Delete(jRoot);
- 首先,在函数外部定义一个
cJSON *的指针,暂时无指向的内容,用于在josn解析时,获取最终的json数据的指向 - 在
read_json_file()内部,主要的逻辑包括:fopen、fread等操作读取josn文件的文本内容cJSON_Parse通过文本内存,解析出josn树,注意其内部是动态申请内存,构建整个josn节点树(因此外部使用完json树之后,要自行释放json指针指向的json树所用的内存)- 根据外部传入的json指针,来修改json指针的指向为解析出的json树的地址
- 函数外部,json指针有了json树的指向后,就可以使用了,使用完之后,释放json树
2 更进一步,函数需要再封装一层
对于json文件读取的场景,再进一步考虑,假设有两个不同版本的json文件,并且在读取之前,不确定哪个的版高。
需求是:封装一个函数接口,读取两个json,哪个数据版本高返回哪个json树。
那解决方案的一种思路是,上面介绍的这个函数:
c
int read_json_file(const char *filePath, cJSON **jsonRoot)
再加一层封装,函数内部来调用两次该函数,分别读取两个json文件,判断出版本大小后,再返回出版本大的json树的地址。
所以,如果是通过函数参数传递json,需要怎么写,需要三级指针吗?不需要,因为本质只是给最外部的json指针修改指向,而这个指向最多就是二级指针。
2.1 示例代码1
test3.c 略去前面重复的部分
c
int read_max_version_json_file(const char *filePath1, const char *filePath2, cJSON **jsonRoot)
{
int ret = 0;
cJSON *jRoot1 = NULL;
cJSON *jRoot2 = NULL;
cJSON *jData = NULL;
int json1Version = 0;
int json2Version = 0;
// 分别读取
ret = read_json_file(filePath1, &jRoot1);
if (0 != ret)
{
return ret;
}
ret = read_json_file(filePath2, &jRoot2);
if (0 != ret)
{
return ret;
}
// 根据jRootX数据解析到版本字段,赋值给jsonX_version
if ((jData = cJSON_GetObjectItem(jRoot1, "version")))
{
json1Version = jData->valueint;
}
if ((jData = cJSON_GetObjectItem(jRoot2, "version")))
{
json2Version = jData->valueint;
}
printf("[%s] json1Version:%d, json2Version:%d\n", __func__, json1Version, json2Version);
if (json1Version >= json2Version)
{
*jsonRoot = jRoot1;
cJSON_Delete(jRoot2);
}
else
{
*jsonRoot = jRoot2;
cJSON_Delete(jRoot1);
}
return 0;
}
#define CONFIG_FILE1 "config1.json"
#define CONFIG_FILE2 "config2.json"
int main(void)
{
cJSON *jRoot = NULL;
printf("[%s:%d] jRoot:%p(&jRoot:%p)\n", __func__, __LINE__, jRoot, &jRoot);
int ret = read_max_version_json_file(CONFIG_FILE1, CONFIG_FILE2, &jRoot);
if (0 != ret)
{
printf("read_max_version_json_file err:%d\n", ret);
return -1;
}
if (NULL == jRoot)
{
printf("cJSON_Parse err: %s\n", cJSON_GetErrorPtr());
return -1;
}
printf("[%s:%d] jRoot:%p(&jRoot:%p)\n", __func__, __LINE__, jRoot, &jRoot);
// 输出无格式 JSON(紧凑字符串)
char *s = NULL;
s = cJSON_PrintUnformatted(jRoot);
// 释放整个JSON树
cJSON_Delete(jRoot);
jRoot = NULL;
if (NULL == s)
{
printf("cJSON_PrintUnformatted: %s\n", cJSON_GetErrorPtr());
return -1;
}
printf("jRoot:%s\n", s);
free(s);
s = NULL;
return 0;
}
运行结果:

2.2 示例代码2
再来演示另一种方案,前面提到过,如果不想在参数中使用二级指针,一种替代方案是通过返回值,返回一级指针。
示例代码:
test4.c 略去前面重复的部分
c
cJSON *read_max_version_json_file(const char *filePath1, const char *filePath2)
{
int ret = 0;
cJSON *jRoot1 = NULL;
cJSON *jRoot2 = NULL;
cJSON *jData = NULL;
cJSON *jRootMaxVersion = NULL;
int json1Version = 0;
int json2Version = 0;
// 分别读取
ret = read_json_file(filePath1, &jRoot1);
if (0 != ret)
{
return NULL;
}
ret = read_json_file(filePath2, &jRoot2);
if (0 != ret)
{
return NULL;
}
// 根据jRootX数据解析到版本字段,赋值给jsonX_version
if ((jData = cJSON_GetObjectItem(jRoot1, "version")))
{
json1Version = jData->valueint;
}
if ((jData = cJSON_GetObjectItem(jRoot2, "version")))
{
json2Version = jData->valueint;
}
printf("[%s] json1Version:%d, json2Version:%d\n", __func__, json1Version, json2Version);
if (json1Version >= json2Version)
{
jRootMaxVersion = jRoot1;
cJSON_Delete(jRoot2);
}
else
{
jRootMaxVersion = jRoot2;
cJSON_Delete(jRoot1);
}
return jRootMaxVersion;
}
int main(void)
{
cJSON *jRoot = NULL;
printf("[%s:%d] jRoot:%p(&jRoot:%p)\n", __func__, __LINE__, jRoot, &jRoot);
jRoot = read_max_version_json_file(CONFIG_FILE1, CONFIG_FILE2);
if (NULL == jRoot)
{
printf("cJSON_Parse err: %s\n", cJSON_GetErrorPtr());
return -1;
}
printf("[%s:%d] jRoot:%p(&jRoot:%p)\n", __func__, __LINE__, jRoot, &jRoot);
// 输出无格式 JSON(紧凑字符串)
char *s = NULL;
s = cJSON_PrintUnformatted(jRoot);
// 释放整个JSON树
cJSON_Delete(jRoot);
jRoot = NULL;
if (NULL == s)
{
printf("cJSON_PrintUnformatted: %s\n", cJSON_GetErrorPtr());
return -1;
}
printf("jRoot:%s\n", s);
free(s);
s = NULL;
return 0;
}
运行结果:

3 总结
本篇通过cJSON的例子,介绍了二级指针的一种使用场景,给出示例代码,并演示运行结果。