用cJson的例子,来理解二级指针

上篇文章,介绍了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的数据地址,这样只需要使用一级指针。例如:

c 复制代码
cJSON *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()内部,主要的逻辑包括:
    • fopenfread等操作读取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的例子,介绍了二级指针的一种使用场景,给出示例代码,并演示运行结果。

相关推荐
自我意识的多元宇宙2 小时前
【数据结构】二叉排序树
数据结构·算法
量子炒饭大师2 小时前
【优化算法:双指针算法刷题宝典】—— 盛最多水的容器
c++·算法
IT猿手2 小时前
多无人机动态避障路径规划研究:基于壁虎优化算法GJA的多无人机动态避障路径规划研究(可以自定义无人机数量及起始点),MATLAB代码
算法·matlab·无人机
listhi5202 小时前
MATLAB电力系统加权最小二乘法(WLS)状态估计
算法·matlab·最小二乘法
Epiphany.5562 小时前
树上dp问题
数据结构·算法
无籽西瓜a2 小时前
MD5算法原理、适用场景
java·后端·算法·哈希算法·md5
承渊政道2 小时前
【动态规划算法】(简单多状态dp问题入门与经典题型解析)
数据结构·c++·学习·算法·leetcode·macos·动态规划
fie88892 小时前
免疫优化算法在物流配送中心选址中的应用
算法·数学建模
王老师青少年编程2 小时前
csp信奥赛C++高频考点专项训练之贪心算法 --【部分背包问题】:部分背包问题
c++·算法·贪心·csp·信奥赛·部分背包问题