• 正文
  • 相关推荐
申请入驻 产业图谱

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

04/28 10:54
222
加入交流群
扫码加入
获取工程师必备礼包
参与热点资讯讨论

上篇文章,介绍了C指针(一级指针)的用法。本篇,来介绍二级指针的使用场景。

如果上篇对一级指针有了深刻的了解,那二级指针就和容易推理地理解其用法。

简单来说,在函数传参中,因为实参是传递的拷贝的值给形参,所以如果想通过函数参数来将数值传递出来,就需要改用传这个数据的地址,让函数根据这个地址来修改对于位置的数据的值,也就是一级指针的一种使用场景。

由此推理,如果想通过函数参数来将指针类型的数据传递出来,就需要改用传这个指针的地址,也就是二级指针了。

1 读取cJson文件的例子

1.1 不带函数的解析示例

整个逻辑都写在main函数中,实现对josn文件的读取、解析操作:

test1.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 failedn", 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: %sn", cJSON_GetErrorPtr());
        return-1;
    }
    
    // 输出无格式 JSON(紧凑字符串)
    char *s = NULL;
    s = cJSON_PrintUnformatted(jRoot);
    // 释放整个JSON树
    cJSON_Delete(jRoot);
    jRoot = NULL;
    
    if (NULL == s)
    {
        printf("cJSON_PrintUnformatted: %sn", cJSON_GetErrorPtr());
        return-1;
    }
    
    printf("jRoot:%sn", s);
    free(s);
    s = NULL;

    return0;
}

准备一个json文件,然后运行,结果如下:

1.2 将josn文件的读取和解析封装为函数

封装的函数:

int read_json_file(const char *filePath, cJSON **jsonRoot);

参数:

    filePath:json文件的地址jsonRoot:解析出的josn数据的地址(二级指针)

返回值:

    成功返回0,失败返回-1

注:这里是通过函数参数演示二级指针,实际使用中,如果不想用二级指针,也可以通过函数返回值josn的数据地址,这样只需要使用一级指针。例如:

cJSON *read_json_file(const char *filePath);

通过判断返回值是否是空指针,来判断解析是否成功,也是一种方法

完整代码:

test2.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 failedn", 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);
    
    return0;
}

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:%dn", ret);
        return-1;
    }
    
    if (NULL == jRoot) 
    {
        printf("cJSON_Parse err: %sn", 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: %sn", cJSON_GetErrorPtr());
        return-1;
    }
    
    printf("jRoot:%sn", s);
    free(s);
    s = NULL;

    return0;
}

准备一个json文件,然后运行,结果如下:

分析一下数据的关系:

对照这个简化的示例:

    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树。

那解决方案的一种思路是,上面介绍的这个函数:

int read_json_file(const char *filePath, cJSON **jsonRoot)

再加一层封装,函数内部来调用两次该函数,分别读取两个json文件,判断出版本大小后,再返回出版本大的json树的地址。

所以,如果是通过函数参数传递json,需要怎么写,需要三级指针吗?不需要,因为本质只是给最外部的json指针修改指向,而这个指向最多就是二级指针。

2.1 示例代码1

test3.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:%dn", __func__, json1Version, json2Version);
    
    if (json1Version >= json2Version)
    {
        *jsonRoot = jRoot1;
        cJSON_Delete(jRoot2);
    }
    else
    {
        *jsonRoot = jRoot2;
        cJSON_Delete(jRoot1);
    }
    
    return0;
}

#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:%dn", ret);
        return-1;
    }
    
    if (NULL == jRoot) 
    {
        printf("cJSON_Parse err: %sn", 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: %sn", cJSON_GetErrorPtr());
        return-1;
    }
    
    printf("jRoot:%sn", s);
    free(s);
    s = NULL;

    return0;
}

运行结果:

2.2 示例代码2

再来演示另一种方案,前面提到过,如果不想在参数中使用二级指针,一种替代方案是通过返回值,返回一级指针。

示例代码:

test4.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) 
    {
        returnNULL;
    }
    ret = read_json_file(filePath2, &jRoot2);
    if (0 != ret) 
    {
        returnNULL;
    }
    
    // 根据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:%dn", __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: %sn", 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: %sn", cJSON_GetErrorPtr());
        return-1;
    }
    
    printf("jRoot:%sn", s);
    free(s);
    s = NULL;

    return0;
}

运行结果:

3 总结

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

C语言将float拆分为4个hex传输与重组C语言打印数据的二进制格式-原理解析与编程实现C语言结构体对齐是怎么计算AES加密C代码实现C语言实现一个简易数据库

相关推荐

登录即可解锁
  • 海量技术文章
  • 设计资源下载
  • 产业链客户资源
  • 写文章/发需求
立即登录