C语言(Head First C)-4_2:创建小工具:管道和自定义数据流

 

 前文《 C语言(Head First C)-4_1:创建小工具:做一件事并把它做好

 

 4_2 创建小工具

 

 承接前文,我们要求场景:

 对于之前的示例,我们要求显示符合条件的数据,比如latitude大于15 小于20;该怎么做?

 

 一个任务对应一个工具:

 我们应该在创建一个工具来完成这件事;一个filterlocation工具,过滤了范围之外的数据;

 我们之前已经有了一个工具,他可以用来将过滤之后的数据转化成地图所需要的json格式;

 

 如何连接两个工具?

 

 用管道连接输入与输出:

 我们把之前工具重命名为geo2json;我们新创建的工具叫做filterlocation;

 要把filterlocation工具的标准输出连接到geo2json工具的标准输入,可以这样做:

 “符号|表示管道(pipe),他能连接一个进程的标准输出与另一个进程的标准输入”;

 filterlocation工具将满足条件的数据发送到自己的标准输出,管道会把数据从filterlocation工具的标准输出发送到geo2json工具的标准输出;

 操作系统会处理管道的细节,你唯一要做的就是输入一条这样的命令:filterlocation | geo2json,这样操作系统会同时运行两个程序,通过管道,前者的输出会变成后者的输入;

 

 filterlocation工具:

 -该工具不会把所有数据都发送到标准输出中,而只发送满足条件的数据;(条件:15 < latitude < 20,注意之前的geo2json工具要求-90~90)

 -该工具的输出、输入数据的格式相同,都是用来保存GPS数据的CSV格式;

 

 (Code4_1)

/*
 * 过滤GPS数据
 */

#include <stdio.h>

int main() {
    
    float latitude;
    float longitude;
    
    char info[80];
    
    while (scanf("%f,%f,%79[^\n]",&latitude,&longitude ,info ) == 3) {//[^\n]相当于在说:把这一行余下的字符都给我
        if (latitude < 20.0 && latitude > 15) {
            printf("%f,%f,%s\n",latitude,longitude,info);
        }
    }
    
    return 0;
}


 然后运行以下命令:

 bogon:0808-1 huaqiang$ gcc 4_1-filterlocation.c -o filterlocation

 bogon:0808-1 huaqiang$ (./filterlocation | ./geo2json) < gpsdata.csv > output.json

 首先是编译生成过滤工具;

 然后,通过管道连接两个进程,()中的可以看成一个程序,注意这对括号是必须的;

 重定向输入内容:gpsdata.csv

 11.0,11.0,flower1

 12.0,11.0,flower2

 13.0,11.0,flower3

 14.0,11.0,flower4

 15.0,11.0,flower5

 16.0,11.0,flower6

 17.0,11.0,flower7

 18.0,11.0,flower8

 19.0,11.0,flower9

 20.0,11.0,flower10

 21.0,11.0,flower11

 22.0,11.0,flower12

 23.0,11.0,flower13

 

 重定向输出内容:output.json

 data=[

 latitude:16.000000,longitude:11.000000,info:'flower6',

 latitude:17.000000,longitude:11.000000,info:'flower7',

 latitude:18.000000,longitude:11.000000,info:'flower8',

 latitude:19.000000,longitude:11.000000,info:'flower9'

 ]

 

 两个独立的程序用管道连接之后就可以看成一个程序,可以重定向它的标准输入和标准输出;

 

 说明:

 之所以小工具使用标准输入输出,是因为可以使用管道将小工具们串联起来用以解决复杂的问题,毕竟小工具只做一件事;

 管道简单来说就是从一端接收数据,在另一端发送数据的通道;如果两个程序用管道相连,第二个程序不需要等待第一个程序执行完才开始执行,两者可以同时执行,一个发送数据,另一个接收数据就可以马上处理;

 可以使用管道连接多个程序,只要在每个程序前加上一个|就行了,一连串的进程叫流水线(pipeline);

 使用管道连接多个进程时,< >两个重定向运算符,<会把文件内容发送到流水线中第一个进程的标准输入,>会捕捉流水线中最后一个进程的标准输出;

 

 要点:

 -如果想完成一个不同的任务,应该写一个小工具;

 -小工具应该使用标准输入和标准输出;

 -小工具通常读写文本数据;

 -可以使用管道连接一个进程的标准输入和另一个进程的标准输出;

 

 输出多个文件:

 如何向多个文件中发送数据呢?我们需要创建另一个工具:从文件中读取一批数据,然后将文件分类,写到多个文件;

 但问题是,使用重定向最多也只能写两个文件,一个标准输出,一个标准错误;

 

 该怎么办?

 

 创建自己的数据流:

 程序运行时,O/S会为它创建三条数据流:标准输入、标准输出 和 标准错误;

 有时也需要建立自己的数据流:你可以在程序运行时创建自己的数据流;

 

 fopen()函数:

 每条数据流用一个指向文件的指针来表示,可以使用fopen()函数创建新数据流;

 像这样:

 FILE * in_file = fopen("4_2-input.txt","r");   //r表示读模式

 FILE * out_file = fopen("4_2-output.txt","w"); //w表示写模式

 

 fopen()函数接受两个参数:文件名和模式;

 三种模式:

 1)w(写文件-write);

 2)r(读文件-read);

 3)a(在文件末尾追加数据-append);

 

 创建数据流之后,可以使用fprintf()往数据流汇总打印数据;fscanf()函数从中读数据;

 如:

 fprintf(out_file,"flower%i",1);

 fscanf(in_file,"%79[^\n]\n",sentence);//sentence表示某个字符数组

 

 注意,使用完当前流,需要关闭它;虽然所有的数据流在程序结束后都会自动关闭,但还是应该自己关闭它们;

 如:

 fclose(in_file);

 fclose(out_file);

 

 示例:将4_2-input.txt中的数据读取,写到三个文件中,分别是4_2-output1.txt、4_2-output2.txt、4_2-output3.txt;

 4_2-input.txt中数据如下:

 11.0,11.0,flower1

 12.0,11.0,flower2

 13.0,11.0,flower3

 14.0,11.0,flower4

 15.0,11.0,flower5

 16.0,11.0,dog1

 17.0,11.0,dog2

 18.0,11.0,dog3

 19.0,11.0,dog4

 20.0,11.0,elephont1

 21.0,11.0,elephont2

 22.0,11.0,elephont3

 23.0,11.0,elephont4

 

 (Code4_2)

/*
 * fopen()函数创建新数据流
 */

#include <stdio.h>
#include <string.h>
int main() {
    
    FILE * in_file = fopen("4_2-input.txt","r");
    
    FILE * out_file1 = fopen("4_2-output1.txt","w");
    FILE * out_file2 = fopen("4_2-output2.txt","w");
    FILE * out_file3 = fopen("4_2-output3.txt","w");
    
    float latitude;
    float longitude;
    
    char line[80];
    
    while (fscanf(in_file,"%f,%f,%79[^\n]",&latitude,&longitude,line) == 3) {
        if (strstr(line,"flower")) {
            fprintf(out_file1,"latitude:%f,longitude:%f,lineinfo:%s\n",latitude,longitude,line);
        }else if (strstr(line,"dog")) {
            fprintf(out_file2,"latitude:%f,longitude:%f,lineinfo:%s\n",latitude,longitude,line);
        } else {
            fprintf(out_file3,"latitude:%f,longitude:%f,lineinfo:%s\n",latitude,longitude,line);
        }
    }
    
    fclose(in_file);
    fclose(out_file1);
    fclose(out_file2);
    fclose(out_file3);
    
    return 0 ;
    
}

 编译运行:gcc 4_2-fopen.c -o 4_2 && ./4_2

 

 4_2-output1.txt、4_2-output2.txt、4_2-output3.txt内容分别为:

 latitude:11.000000,longitude:11.000000,lineinfo:flower1

 latitude:12.000000,longitude:11.000000,lineinfo:flower2

 latitude:13.000000,longitude:11.000000,lineinfo:flower3

 latitude:14.000000,longitude:11.000000,lineinfo:flower4

 latitude:15.000000,longitude:11.000000,lineinfo:flower5

 

 latitude:16.000000,longitude:11.000000,lineinfo:dog1

 latitude:17.000000,longitude:11.000000,lineinfo:dog2

 latitude:18.000000,longitude:11.000000,lineinfo:dog3

 latitude:19.000000,longitude:11.000000,lineinfo:dog4

 

 latitude:20.000000,longitude:11.000000,lineinfo:elephont1

 latitude:21.000000,longitude:11.000000,lineinfo:elephont2

 latitude:22.000000,longitude:11.000000,lineinfo:elephont3

 latitude:23.000000,longitude:11.000000,lineinfo:elephont4

 

 我们的程序正确运行了;

 

 这个程序,可以根据设置好的关键字,将输入数据进行分类输出;

 但如果,用户想要搜索其他的关键字呢?或者把数据写到其他文件中?是否有办法让用户设置关键字和文件,又不必重新编译程序?

 

 main()可以做的更多:

 用户有权修改程序的工作方式;比如运行程序时,命令行中传入了如下参数:

 ./4_2 flower 4_2-output1.txt dog 4_2-output2.txt elephont 4_2-output3.txt

 

 如何读取命令行参数呢?我们使用main函数的第二种形式:

 int main(int argc,char * argv[]) {

     ……

 }

 main函数能以字符串数组的形式读取命令行参数;(实际是一个字符指针数组)

 -argv[0] 第一个参数是要运行程序的名字 ./4_2;

 数组其他元素分别对应其余的参数值;也就是说,第一个命令行参数其实是argv[1];

 

 C中,需要知道数组长度,argc的值就是用来记录数组中元素个数的;

 命令行参数可以让程序更灵活;

 

 (Code4_3)

/*
 * fopen()函数创建新数据流
 */

#include <stdio.h>
#include <string.h>
int main(int argc , char * argv[]) {
    
    if (argc != 7) {
        fprintf(stderr,"你需要输入6组数据\n");
        return 1;
    }
    char * outkey1 = argv[1];
    char * outkey2 = argv[3];
    char * outkey3 = argv[5];
    
    char * outfile1 = argv[2];
    char * outfile2 = argv[4];
    char * outfile3 = argv[6];
    
    FILE * in_file = fopen("4_2-input.txt","r");
    
    FILE * out_file1 = fopen(outfile1,"w");
    FILE * out_file2 = fopen(outfile2,"w");
    FILE * out_file3 = fopen(outfile3,"w");
    
    float latitude;
    float longitude;
    
    char line[80];
    
    while (fscanf(in_file,"%f,%f,%79[^\n]",&latitude,&longitude,line) == 3) {
        if (strstr(line,outkey1)) {
            fprintf(out_file1,"latitude:%f,longitude:%f,lineinfo:%s\n",latitude,longitude,line);
        }else if (strstr(line,outkey2)) {
            fprintf(out_file2,"latitude:%f,longitude:%f,lineinfo:%s\n",latitude,longitude,line);
        } else {
            fprintf(out_file3,"latitude:%f,longitude:%f,lineinfo:%s\n",latitude,longitude,line);
        }
    }
    
    fclose(in_file);
    fclose(out_file1);
    fclose(out_file2);
    fclose(out_file3);
    
    return 0 ;
    
}

 运行:bogon:0808-1 huaqiang$ ./4_3 flower 4_2-output1.txt dog 4_2-output2.txt elephont 4_2-output3.txt

 相应文件中的内容成功写入,内容同4_2的运行结果;

 

 现在,我们可以把3组不同的关键词输出到不同的文件中了;

 

 安全检查:(操作数据流)

 在程序打开文件准备读写时,最好检查一下有没有错误;如果数据流打开失败,fopen()函数会返回0;

 可以像这样:

 FILE * in;

 if(!(in = fopen("xxx.txt","r"))){

     ……//打开数据流失败了

     return 1;

 }

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值