C语言读取CSV文件

ZhangRui111.github.io

  最近笔者参加了华为软件公开赛,涉及到需要读取CSV文件,考虑到CSV文件在程序猿的世界经常用到,便花时间研究下,本来以为很简单的文件读取,确是让我大费周章,究其原因,我觉得有两个:

  • 1、笔者最近主要精力放在了学习Android上,对于C/C++的语法有所生疏,看来,要成为一名合格的程序员还有很长的路要走。
  • 2、另一个就太坑了,使用中文搜索边搜索边写代码过了三个多小时,最后又回到了原点,然而,使用纯英文搜索和Google,借鉴了一个英文网页,分分钟就搞定了,果然:不想看英文、不会用英文搜索的程序猿不是好的攻城狮[Doge][Doge][Doge],哈哈,个人观点,其实中文搜索和英文搜索还是互有优劣的。
    下面进入正题:

    什么是CSV(摘自维基百科

    CSV是一种通用的、相对简单的文件格式,被用户、商业和科学广泛应用。最广泛的应用是在程序之间转移表格数据,而这些程序本身是在不兼容的格式上进行操作的(往往是私有的和/或无规范的格式)。因为大量程序都支持某种CSV变体,至少是作为一种可选择的输入/输出格式。“CSV”并不是一种单一的、定义明确的格式(尽管RFC 4180有一个被通常使用的定义)。因此在实践中,术语“CSV”泛指具有以下特征的任何文件:
    1 纯文本,使用某个字符集,比如ASCII、Unicode、EBCDIC或GB2312(简体中文环境)等; 2 由记录组成(典型的是每行一条记录);
    3 每条记录被分隔符分隔为字段(典型分隔符有逗号、分号或制表符;有时分隔符可以包括可选的空格); 4 每条记录都有同样的字段序列。

  简单来说呢?就是每个数据之间以相同的分隔符(逗号、分号等)区分,数据的类型可以不同、长度任意。对应每一列的数据要有相同的含义,不然这个文件就是无意义的。


建立结构体

首先,查看CSV文件每一行的格式,例如:我要读取的关于有向图描述的Topo.csv,每一行有四个int型数字,以逗号隔开,分别表示有向边编号、起点、终点和权重
{0,0,1,1}
那么,我要建立的结构体就要包含四个int型变量:

typedef struct DataTopo{
    int lineNum;
    int startPoint;
    int endPoint;
    int weight;
}DataTopo;

读取文件

读取文件和普通的C语言语法没有太大区别,都是
FILE *fpTopo = fopen(argv[1],”rt”);
然后,使用
fgets(buf,sizeof(buf),fpTopo)
一行一行地依次读取,这里要注意建立一个足够大的character buffer,注意,不是int buffer!要做到这一点,最直接的办法就是使得空间取足够大的一个数,比如#define BUFFER_SIZE 1024,这样做有浪费空间的嫌疑,不提倡,但是在这里不是我们关注的重点,暂且不说。

strtok()函数

strtok()函数是C语言的库函数,
头文件:#include < string.h >
定义函数:char *strtok(char *s, const char delim);
strtok()用来将字符串分割成一个个片段。参数s 指向欲分割的字符串,参数delim 则为分割字符串,当strtok()在参数s 的字符串中发现到参数delim 的分割字符时则会将该字符改为\0 字符。在第一次调用时,strtok()必需给予参数s 字符串,*往后的调用则将参数s 设置成NULL
。每次调用成功则返回下一个分割后的字符串指针。(摘自:C语言中文网
这一部分代码如下:


fgets(buf,sizeof(buf),fpTopo);
        char *tok = strtok(buf, ",");
        mDataTopo[count].lineNum = atoi(tok);
        tok = strtok(NULL, ",");
        mDataTopo[count].startPoint = atoi(tok);
        tok = strtok(NULL, ",");
        mDataTopo[count].endPoint = atoi(tok);
        tok = strtok(NULL, ",");
        mDataTopo[count].weight = atoi(tok);
        tok = strtok(NULL, ",");

这里要注意:直接使用 char \tok = strtok(buf, “,”); 得到的数据并不是int型,需要调用atoi()函数进行转化,这里的atoi()函数同样是一个库函数;每得到一个数据,都需要重新调用tok = strtok(NULL, “,”);重新定位指针位置,直到已经得到了这一行的所有数据。
重复行读取fgets(buf,sizeof(buf),fpTopo)直到读完整个CSV文件,那么就大功告成了。
最后,奉上笔者亲自编译测试通过的代码:


#include 
#include 
#include 

#define BUFFER_SIZE 128
#define SHORTLENGTH 128
#define LONGLENGTH 1024

typedef struct DataTopo{
    int lineNum;
    int startPoint;
    int endPoint;
    int weight;
}DataTopo;

typedef struct DataDemand{
    int startPDemand;
    int endPDemand;
    char *lineP;
}DataDemand;

int main(int argc,char* argv[])
{
    FILE *fpTopo,*fpDemand;
    DataTopo mDataTopo[LONGLENGTH];
    DataDemand mDataDemand;
    int count = 0,i = 0;

    if(argc < 2){
        printf("Please input the Topo File&Demand File!\n");
        return 1;
    }
    if(argc == 2){
        printf("Please input the Demand File!\n");
        return 1;
    }

    char buf[BUFFER_SIZE];
    fpTopo = fopen(argv[1],"rt");
    if(fpTopo == NULL){
        printf("Cant't find the Topo file!\n");
        exit(1);
    }
    while(fpTopo != EOF && count < LONGLENGTH){
        fgets(buf,sizeof(buf),fpTopo);
        char *tok = strtok(buf, ",");
        mDataTopo[count].lineNum = atoi(tok);
        tok = strtok(NULL, ",");
        mDataTopo[count].startPoint = atoi(tok);
        tok = strtok(NULL, ",");
        mDataTopo[count].endPoint = atoi(tok);
        tok = strtok(NULL, ",");
        mDataTopo[count].weight = atoi(tok);
        tok = strtok(NULL, ",");
        if(mDataTopo[count].startPoint == 0&&mDataTopo[count].endPoint == 0&&mDataTopo[count].weight == 0){
            break;
        }
        count++;
    }
    //printf("\nCount = %d\n",count);
    /*int i = 0;
    for(;i < count;i++){
        printf("%d,%d,%d,%d\n",mDataTopo[i].lineNum,mDataTopo[i].startPoint,mDataTopo[i].endPoint,mDataTopo[i].weight);
    }*/
    char bufDemand[SHORTLENGTH];
    fpDemand = fopen(argv[2],"rt");
    if(fpDemand == NULL){
        printf("Cant't find the Demand file!\n");
        exit(1);
    }
    fgets(bufDemand,sizeof(bufDemand),fpDemand);
    char *tok = strtok(bufDemand, ",");
    mDataDemand.startPDemand = atoi(tok);
    tok = strtok(NULL, ",");
    mDataDemand.endPDemand = atoi(tok);
    tok = strtok(NULL, ",");
    mDataDemand.lineP = tok;
    tok = strtok(NULL, ",");
    //printf("%d,%d,%s",mDataDemand.startPDemand,mDataDemand.endPDemand,mDataDemand.lineP);

    int lineNum[count],startPoint[count],endPoint[count],weight[count];
    for(;i < count;i++){
        lineNum[i] = mDataTopo[i].lineNum;
        startPoint[i] = mDataTopo[i].startPoint;
        endPoint[i] = mDataTopo[i].endPoint;
        weight[i] = mDataTopo[i].weight;
    }
    /*for(i = 0;i < count;i++){
        printf("%d ",lineNum[i]);
        printf("%d ",startPoint[i]);
        printf("%d ",endPoint[i]);
        printf("%d\n",weight[i]);
    }
    printf("%d,%d,%s",mDataDemand.startPDemand,mDataDemand.endPDemand,mDataDemand.lineP);*/
    return 0;
}


最后,关于while()循环中

if(mDataTopo[count].startPoint == 0&&mDataTopo[count].endPoint == 0&&mDataTopo[count].weight == 0){
            break;
        }

关于CSV文档是否有EOF标记,笔者的确不太清楚,在网上查了一下也没有找到特别权威的答复,这里恳请读者指正。笔者在测试时发现,当有效数据读取完成时,读取不会立刻停止,反而在后面三列会读出许多零(NULL),所以根据语法顺序,启用了这么一个中断While()循环的操作


写数据到.csv文件

话不多说,直接上代码,挺简单的,应该都能看懂—-参考网站


#include
#include

void create_marks_csv(char *filename,int a[][3],int n,int m){
    printf("\n Creating %s.csv file",filename);
    FILE *fp;
    int i,j;
    filename=strcat(filename,".csv");
    fp=fopen(filename,"w+");
    fprintf(fp,"Student Id, Physics, Chemistry, Maths");
    for(i=0;i < m;i++){
        fprintf(fp,"\n%d",i+1);
        for(j=0;j < n;j++)
            fprintf(fp,",%d ",a[i][j]);
    }
    fclose(fp);
    printf("\n %sfile created",filename);
}

int main(){
    int a[3][3]={{50,50,50},{60,60,60},{70,70,70}};
    char str[100];
    printf("\n Enter the filename :");
    gets(str);
    create_marks_csv(str,a,3,3);
    return 0;
}

博主原创博文,转载请注明出处http://zhangrui111.github.io/