前文《 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;
}