NEFU C语言大作业总结JSON解析生成器

Posted 鱼竿钓鱼干

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了NEFU C语言大作业总结JSON解析生成器相关的知识,希望对你有一定的参考价值。

【NEFU C语言大作业总结】JSON解析生成器

今天把生成器部分写完了,目前.h 60多行.c库代码500多行,test单元测试400多行(使用网上单元测试框架)。把打印部分写完这个项目应该就完结了。总共应该快1000行了吧,算是大作业及格代码量?

下面进行一些技术总结

到时候如果顺利通过的话我会比较闲,可以私聊我。我可以提供一些简单的代码优化,当然你需要支持小额报酬呵呵。

多看nb的开源项目真滴可以收获很多东西!

新奇操作

内存泄漏检测

在 Windows 下,可使用 Visual C++ 的 C Runtime Library(CRT) 检测内存泄漏。

/*检测内存泄露*/
#ifdef _WIN32   
#define _CRTDBG_MAP_ALLOC
#include <crtdbg.h>
#endif
int main() {
#ifdef _WIN32
    _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
#endif

通用堆栈

typedef struct {
	const char* json;
	/*c语言实现混合类型堆栈,参考RapidJSON库*/
	char* stack;
	unsigned int size, top;//由于需要动态扩展,top不使用指针类型,并且push和pop操作可以通过简单的+-来实现
}fish_context;

/*压入堆栈,返回值为数据头指针位置*/
static void* fish_context_push(fish_context* c, unsigned int size) {
	void* ret;
	assert(size > 0);
	if (c->top + size >= c->size) {
		if (c->size == 0)
			c->size = FISH_PARSE_STACK_INIT_SIZE;
		while (c->top + size >= c->size)/*参考C++ STL vector  优化 2倍扩展变1.5倍扩展*/
			c->size += c->size >> 1;
		void* new_ptr= (char*)realloc(c->stack, c->size);
		if (new_ptr == NULL) {
			free(new_ptr);
			puts("FISH_CONTEXT_PUSH_CAN_NOT_REALLOC!");
		}
		else {
			c->stack = new_ptr;
		}
	}
	ret = c->stack + c->top;
	c->top += size;
	return ret;
}
static void* fish_context_pop(fish_context* c, unsigned int size) {
	assert(c->top >= size);
	return c->stack + (c->top -= size);
}

代码编写

效率优化

虽然现在编译器基本都可以优化,但是下面这些写法都或许可以显得你比较专业哈哈哈
无限循环

for(;;)优于while(1)

逻辑分支

switchif else

内存优化

当结构体存在多选一的数据时候,我们可以采用联合体来优化内存

JSON的数据结构中,string,array,object是多选一的,所以我们可以使用联合体来优化内存。
可以参考一下这篇文章

struct fish_value{
    char* s; unsigned int len;              //string
    fish_value* e; unsigned int arr_size;   //array
    fish_member* m; unsigned int obj_size;  //object
    double n;
    fish_type type;
};

联合体实现

struct fish_value {
    union {
        struct { fish_member* m; size_t size; }o; //object
        struct { fish_value* e; size_t size; }a; //array
        struct { char* s; size_t len; }s;          //string
        double n;                                  
    }u;
    lept_type type;
};

可读性优化

枚举类型优化可读性
如果一些数字有特定的意义,我们可以采用枚举类型来增强代码的自说明性

enum {
    FISH_PARSE_OK = 0,
    FISH_PARSE_EXPECT_VALUE,
    FISH_PARSE_INVALID_VALUE,
    FISH_PARSE_ROOT_NOT_SINGULAR,
    FISH_PARSE_NUMBER_TOO_BIG,
    FISH_PARSE_MISS_QUOTATION_MARK,
    FISH_PARSE_INVALID_STRING_ESCAPE,
    FISH_PARSE_INVALID_STRING_CHAR,
    FISH_PARSE_MISS_COMMA_OR_SQUARE_BRACKET,
    FISH_PARSE_MISS_KEY,
    FISH_PARSE_MISS_COLON,
    FISH_PARSE_MISS_COMMA_OR_CURLY_BRACKET
};

试着比较一下下面两个语句

/*如果ch=='} 那么返回解析成功*/
if(ch=='})return FISH_PARSE_OK;

if(ch=='})return 0;

宏优化可读性
do{}while
用do{…}while(0);包裹住要操作的#define,无论你外面怎么操作,都不会影响#define的操作

#define PUTC(c, ch)         do { *(char*)fish_context_push(c, sizeof(char)) = (ch); } while(0)
#define PUTS(c,s,len)		do { memcpy(fish_context_push(c, len), s, len);} while(0)

各位可以参考这篇博客

使用比较常见的变量名
可以去这个网站查询

ret 返回值
size 大小
length 长度
buffer 缓冲区
init 初始

整齐的排版多使用TAB

安全性优化

防御性编程

EXPECT宏的使用:
虽然在实际测试和使用过程中,调用某一个函数可能已经进行过参数检查或者条件判断了。但是我们在测试和编写的过程中应该尽可能遵守防御性编程的方法。在函数内部对一些指针,参数条件进行检查。常见的检查方法有:使用断言,抛出异常

检查参数是否符合输入

#define EXPECT(c, ch)       do { assert(*c->json == (ch)); c->json++; } while(0)
/*解析TRUE*/
static int fish_parse_true(fish_context* c, fish_value* v){
	EXPECT(c, 't');//防御性编程,在fish_parse_value中已经判断过了,但是对于功能函数还需要考虑单独应用场景,例如test.c文件中的单元测试。
	if (c->json[0] != 'r' || c->json[1] != 'u' || c->json[2] != 'e')
		return FISH_PARSE_INVALID_VALUE;
	c->json += 3;
	v->type = FISH_TRUE;
	return FISH_PARSE_OK;
}

检查空指针

/*获取类型*/
fish_type fish_get_type(const fish_value* v) {
    assert(v != NULL);
    return v->type;
}

自定义类型free

可以专门写一个free函数来释放自己的类型

void fish_free(fish_value* v){
	assert(v != NULL);
	switch (v->type) {
		case FISH_STRING:	
			free(v->s);
			break;
		case FISH_ARRAY:
			for (unsigned int i = 0; i < v->arr_size; i++)
				fish_free(&v->e[i]);
			free(v->e);
			break;
		case FISH_OBJECT:
			for (unsigned int i = 0; i < v->obj_size; i++) {
				free(v->m[i].key);
				fish_free(&v->m[i].v);
			}
			free(v->m);
			break;
		default: break;
	}
	v->type = FISH_NULL;//避免重复释放 狗牌标记
}

测试

TDD测试驱动开发

测试驱动开发(Test-Driven Development)
说白了就是你先把需求中可能用到的数据造出来,然后写个测试(比如最普通的单元测试)。
然后你根据这些测试来编写你的程序,这样保证了你的需求都能被满足。

但是坏处是,这种开发方式,非常耗费时间。
我在写C语言大作业的时候找到了现成的JSON解析生成器测试框架和大量测试数据,所以比较轻松。如果各位找不到足够数据的话,可以尝试写个程序生成合法的随机数据。但是我们要保证这些数据能够满足测试强度需求。
下面给出一些需要考虑的数据
测试数字
极大的数据,极小的数据,整数,小数,0,正数,负数,科学计数法
测试字符串
空格,连续空格,转移字符,utf-8,中文字符,空串,极长的字符串

单元测试框架

平常写锐格的时候,我们测试一个函数或者程序是否允许成功常常会使用printf等来打印输出,然后人工检查结果。但是这对于一个有几十个函数的大作业来说或许已经有一些繁琐了。

一般我们会采用自动的测试方式,例如单元测试(unit testing)。单元测试也能确保其他人修改代码后**,原来的功能维持正确**(这称为回归测试/regression testing)。同时如果后期要重构优化代码,也很轻松。

各位可以了解一下Google的单元测试框架

自定义性

我们当申请一些空间大小的时候,我们可以采用宏的方式定义初始SIZE大小,这样要调整的时候方便调整

#ifndef FISH_PARSE_STACK_INIT_SIZE
#define FISH_PARSE_STACK_INIT_SIZE 256
#endif

#ifndef FISH_PARSE_STRINGIFY_INIT_SIZE
#define FISH_PARSE_STRINGIFY_INIT_SIZE 256
#endif

推荐各位无聊的时候读的几本书/文档

《代码大全2》(本人有纸质版,如果你看的话可以私聊我)
《重构》(纸质版过几天应该到了,你要的话也可以私聊我)
《人月神话》(不是啥技术书,但是有些用处)
Google代码规范

先写这么多吧。

以上是关于NEFU C语言大作业总结JSON解析生成器的主要内容,如果未能解决你的问题,请参考以下文章

NEFU大一下C语言阶段一考试参考代码(带注释)

[NEFU大一下C语言 期末模拟]参考答案

第七周作业

[NEFU锐格 数据结构]实验一 线性表有关的操作

[NEFU 数据结构] 第 1 章 绪论 知识点整理

[NEFU 数据结构] 第 2 章 线性表 知识点整理