darknet中的list *options = read_data_cfg(datacfg);

29 篇文章 1 订阅
3 篇文章 0 订阅
list *options = read_data_cfg(datacfg);

这是darknet中训练yolo模型时读取datacfg文件的一条语句,这里详细解析一下其内部结构;首先先看list的定义,其实一个双向链表结构

typedef struct list{
    int size;
    node *front;
    node *back;
} list;

然而我们发现它与其他链表不同,因为一般的链表都是使用自身来定义前行与后向指针,从而使得前向指针和后向指针指向自身链表的结点;但是这里很显然并没有使用自身来定义他们;而是使用了其他的双向链表来定义,使得其前后指针指向了node链表结点;其中node的链表结点结构如下:

typedef struct node{
    void *val;//void *无类型指针,可以指向任何类型的数据;为了之后方便指向其他类型的数据
    struct node *next;
    struct node *prev;
} node;

首先跳转到函数主体

list *read_data_cfg(char *filename)
{
    FILE *file = fopen(filename, "r");//打开文件
    if(file == 0) file_error(filename);//若文件不存在,则显示错误,并跳出函数;
    char *line;
    int nu = 0;
    list *options = make_list();//创建list链表结点,用来存储之后的文件数据,并返回指向list的指针;其函数主体如下所示
    /*
    list *make_list()
	{
		list *l = malloc(sizeof(list));
		l->size = 0;
		l->front = 0;
		l->back = 0;
		return l;
	}
	*/
    while((line=fgetl(file)) != 0)//判断fgetl(file)读入的line是否为空,即line是否读入了数据
    {
        ++ nu;
        strip(line);//去除line中的空格换行和跳格符号,并在最后加上'\0';具体函数如下所示
        /*
        void strip(char *s)
		{
    		size_t i;
   		 	size_t len = strlen(s);
   			size_t offset = 0;
    		for(i = 0; i < len; ++i)
    		{
       		 	char c = s[i];
        		if(c==' '||c=='\t'||c=='\n') ++offset;
        		else s[i-offset] = c;
    		}
   			 s[len-offset] = '\0';
		}
        */
        switch(line[0])//使用switch语句判读line第一个字符是否是'\0'、'#'和';',若是则释放line并跳出,即排查第一个字符,'\0'、'#'和';'起到了注释作用;这里还使用了分支重叠,没有在每一个case下使用break,而是前面三个case共同由一个break跳转;
        {
            case '\0':
            case '#':
            case ';':
                free(line);
                break;
            default:
                if(!read_option(line, options))//函数主体见下面
                {
                    fprintf(stderr, "Config file error line %d, could parse: %s\n", nu, line);
                    free(line);
                }
                break;
        }
    }
    fclose(file);
    return options;
}

跳转到fgetl函数主体,其作用就是以行来获取文件数据

char *fgetl(FILE *fp)
{
	//feof函数功能:判其功能是检测流上的文件结束符,文件结束:返回非0值;文件未结束:返回0值
    if(feof(fp)) return 0;//这里就是说如果文件结束,判断为真,直接退出函数;
    size_t size = 512;//定义512个64位无符号整型数据空间
    char *line = malloc(size*sizeof(char));//创建存储每行数据的空间
    //fgets函数的功能是从 fp文件流中读取 size 个字符存储到字符指针变量line所指向的内存空间。它的返回值是一个指针,指向字符串中第一个字符的地址。
    if(!fgets(line, size, fp))//判断文件是否读完,如果文件已经读完,则返回;
    {
        free(line);//释放内存单元
        return 0;
    }

    size_t curr = strlen(line);//获取读取的文件行长度
	//如果文件行的最后一个字符不等于'\n'且文件未结束则进入循环,即判断是否已经读完一行,为了防止文件行的数据大小大于size,而导致的fp指向的那一行数据未能全部读取,且line难以存储一行数据。循环含义就是为了扩容line,将一行之前没有读完的数据读入line中。
    while((line[curr-1] != '\n') && !feof(fp))
    {
    	
        if(curr == size-1)
        {
            size *= 2;
            line = realloc(line, size*sizeof(char));
            if(!line) 
            {
                printf("%ld\n", size);
                malloc_error();
            }
        }
        size_t readsize = size-curr;
        if(readsize > INT_MAX) readsize = INT_MAX-1;
        fgets(&line[curr], readsize, fp);
        curr = strlen(line);
    }
    //判断读入行数据中最后一个字符是不是换行,若是则将其改为'\0'
    if(line[curr-1] == '\n') line[curr-1] = '\0';

    return line;//返回读入的行line
}

read_option函数主体;此函数为读入链表的核心函数

int read_option(char *s, list *options)
{
    size_t i;
    size_t len = strlen(s);//得出现在文件行的长度
    char *val = 0;//用来存储之后的文件数据
    for(i = 0; i < len; ++i)
    {
    	//判断若是等号则替换为'\0'将其断开,将后面的数据赋给val,然后跳出
        if(s[i] == '=')
        {
            s[i] = '\0';
            val = s+i+1;
            break;
        }
    }
    //判断等号后是否有数据,若没有则跳出
    if(i == len-1) return 0;
    //将等号前的键值赋给key
    char *key = s;
    option_insert(options, key, val);//将数据读入链表,函数主体见下
    return 1;
}


option_insert函数

void option_insert(list *l, char *key, char *val)
{
    kvp *p = malloc(sizeof(kvp));//声明一个指向kvp的指针p,并动态存储分配了kvp结构体大小的内存,用来存储key和val数据,其结构体结构如下:
    /*kvp结构体
    typedef struct{
   					char *key;
    				char *val;
  	  				int used;
				} kvp;
	*/
	//将key、val分别赋给结构体中的变量
    p->key = key;
    p->val = val;
    p->used = 0;
    list_insert(l, p);
}

list_insert函数


void list_insert(list *l, void *val)
{
	node *new = malloc(sizeof(node));//声明一个指向node结构体的指针,并动态存储分配内存
	new->val = val;//将指针val赋给new->val,使其指向kvp结构体
	new->next = 0;//将链表后向指针指向设置为0

	if(!l->back)//判断list链表的后向指针指向是否为0,若是则执行if,不为0,则执行else
	{
		l->front = new;//使list链表的前向指针指向新建立的node链表
		new->prev = 0;//将新建的node链表的前向指针指向0
	}
	else
	{
		l->back->next = new;//将list链表的后向指针域中的node的后向指针指向新建的node链表new
		new->prev = l->back;//将新建的node链表new的前向指针指向list链表的后向指针,由于l->back未更新是指向了上一个结点,所以新建的node链表new的前向指针指向上一个结点;
	}
	l->back = new;//将list链表的后向指针指向新建的node链表new
	++l->size;//记录传入的数据次数
}

**(1)**首先先了解传入 list_insert函数 的参数的结构,其中传入 list *l 的 options 的结构通过 make_list() 函数创建,如下所示:
在这里插入图片描述

另一个参数 void *val传入的则是由用来保存文件数据的kvp结构体指针p,也就是结构体的名字,即地址;其指向结构体中的数据;
在这里插入图片描述

其中p->key保存了文件行中等号前的数据,p->val用来保存等号后的数据;
(2)函数运行
第一步node *new = malloc(sizeof(node));//创建一个新的node双向链表结点,然后将指针val赋给new->val,使其指向kvp结构体,并将其后向指针指向设置为0;其结果如下图所示:
在这里插入图片描述

进入if判断语句,很显然当第一次传输数据时,l->back=0,则 !l->back != 0,为真,执行if语句;故第一次存入链表的过程图如下所示;
在这里插入图片描述

第二次传入数据时,l->back=new,不再等于0了,故会执行else语句,下图便是其执行结果图
在这里插入图片描述

这里为了方便,省略了kvp结构体;从上图中可以看出,l->front的功能就是一个头指针,永远都指向第一个结点,而l->back就相当于一个移动的temp指针,永远指向新的结点。
如下图所示:
在这里插入图片描述

list链表结点l中的l->front 是一个头指针,可以通过它来查询保存在node链表中的数据,l->back就是双向链表中的一个移动指针,其永远指向链表中的最后一个结点。这样和普通双向链表一样,只需要通过返回的头指针就能找到链表中的数据。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值