用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的例子,介绍了二级指针的一种使用场景,给出示例代码,并演示运行结果。

相关推荐
淘矿人18 分钟前
Claude辅助DevOps实践
java·大数据·运维·人工智能·算法·bug·devops
Cosolar26 分钟前
万字详解:RAG 向量索引算法与向量数据库架构及实战
数据库·人工智能·算法·数据库架构·milvus
落羽的落羽2 小时前
【算法札记】练习 | Week4
linux·服务器·数据结构·c++·人工智能·算法·动态规划
萑澈3 小时前
算法竞赛入门:C++ STL核心用法与时空复杂度速查手册
数据结构·c++·算法·stl
Godspeed Zhao3 小时前
从零开始学AI16——SVM
算法·机器学习·支持向量机
江屿风3 小时前
C++OJ题经验总结(竞赛)1
开发语言·c++·笔记·算法
nebula-AI3 小时前
人工智能导论:模型与算法(核心技术)
人工智能·深度学习·神经网络·算法·机器学习·集成学习·sklearn
运筹vivo@4 小时前
LeetCode 2405. 子字符串的最优划分
c++·算法·leetcode·职场和发展·哈希表
数智工坊4 小时前
视觉-语言-动作模型解剖学:从模块、里程碑到核心挑战
论文阅读·人工智能·深度学习·算法·transformer