考察C程序員是否合(he)格的一(yi)(yi)個重(zhong)要(yao)標準就是看(kan)他(ta)操作字(zi)符串(chuan)的能力,一(yi)(yi)個合(he)格的C程序員應該可以熟練的對(dui)字(zi)符串(chuan)進行拆分(fen)、組合(he)、格式轉換以及搜索定(ding)位,從一(yi)(yi)堆數據中提取出(chu)有效信息。
比如說(shuo)我們要做一個GPS導航的(de)(de)項目,需(xu)(xu)要讀取(qu)GPS模塊以ASCII碼的(de)(de)形式發送過來的(de)(de)數(shu)據,然(ran)后對這些(xie)數(shu)據進行處理,提取(qu)我們需(xu)(xu)要的(de)(de)信(xin)息。這就涉及(ji)到(dao)很多操(cao)作字符(fu)串的(de)(de)問題(ti)。下面(mian)就以此為例,利用(yong)strstr函(han)數(shu)和sscanf函(han)數(shu)解析GPS數(shu)據。
GPS輸出的數據格式如下:
$GPGGA,121252.000,3937.3032,N,11611.6046,E,1,05,2.0,45.9,M,-5.7,M,,0000*77
$GPRMC,121252.000,A,3958.3032,N,11629.6046,E,15.15,359.95,070306,,,A*54
$GPVTG,359.95,T,,M,15.15,N,28.0,K,A*04
$GPGGA,121253.000,3937.3090,N,11611.6057,E,1,06,1.2,44.6,M,-5.7,M,,0000*72
$GPGSA,A,3,14,15,05,22,18,26,,,,,,,2.1,1.2,1.7*3D
$GPGSV,3,1,10,18,84,067,23,09,67,067,27,22,49,312,28,15,47,231,30*70
$GPGSV,3,2,10,21,32,199,23,14,25,272,24,05,21,140,32,26,14,070,20*7E
$GPGSV,3,3,10,29,07,074,,30,07,163,28*7D
可以看到,GPS模塊發送過來的原始數據有很多,但是通常我們只需要其中的一部分信息就夠用了,比如對于導航的功能,我們只需要以$GPRMC開頭,以換行符結束的一行信息就夠了。即:
$GPRMC,121252.000,A,3958.3032,N,11629.6046,E,15.15,359.95,070306,,,A*54
因此我們需(xu)要做的就是從讀取的數據中截取以$GPRMC開頭(tou)的一行信(xin)息,然后(hou)從中解析出經(jing)緯度、日(ri)期時(shi)間(jian)等有效信(xin)息即可。
假設從串口讀取的數據存放在一個字符串指針char *raw_buf指向的內存單元里,首先我們通過ANSI C提供的strstr()函數找到以$GPRMC開頭以換行符’\n’結束的字符串:
/* find "$GPRMC" from raw_buf */
if ((wellhandled_string = strstr(raw_buf, “$GPRMC”)) != NULL)
{
for (i=0; i<strlen(wellhandled_string); i++)
{
if (wellhandled_string[i] == '\n')
{
wellhandled_string[i] = '\0'; //replace ‘\n’ with null
}
}
}
strstr()函數的原型是這樣聲明的:
char *strstr(const char *haystack, const char *needle);
strstr()函(han)數可以(yi)在字(zi)(zi)(zi)(zi)符(fu)串(chuan)haystack中搜(sou)索(suo)字(zi)(zi)(zi)(zi)符(fu)串(chuan)needle第一(yi)次出(chu)(chu)現(xian)的(de)位置,并且(qie)返回(hui)(hui)指(zhi)(zhi)向字(zi)(zi)(zi)(zi)符(fu)串(chuan)needle首地址的(de)指(zhi)(zhi)針,如果沒(mei)有(you)搜(sou)索(suo)到則返回(hui)(hui)NULL。因此上面的(de)代碼(ma)為我們在讀取的(de)原始(shi)數據raw_buf里搜(sou)索(suo)$GPRMC第一(yi)次出(chu)(chu)現(xian)的(de)位置,并將(jiang)返回(hui)(hui)的(de)指(zhi)(zhi)針賦給wellhandled_string,這樣如果搜(sou)索(suo)成功,則wellhandled_string就會指(zhi)(zhi)向以(yi)$GPRMC開始(shi)的(de)字(zi)(zi)(zi)(zi)符(fu)串(chuan),接下來通(tong)過一(yi)個(ge)for循環找(zhao)到換行符(fu)’\n’,將(jiang)其替換為’\0’,即字(zi)(zi)(zi)(zi)符(fu)串(chuan)結束符(fu)。這樣就得到了(le)一(yi)個(ge)指(zhi)(zhi)向有(you)效數據的(de)字(zi)(zi)(zi)(zi)符(fu)串(chuan)指(zhi)(zhi)針wellhandled_string。
然后要做的工作就是從wellhandled_string中提取出經緯度、日期時間等信息。這個工作就可以交給強大的sscanf函數來實現。sscanf函數的原型如下:
int sscanf(const char *str, const char *format, ...);
我們都比較熟悉scanf這個函數,scanf可以從標準輸入流讀取與指定格式相符的數據。sscanf則是從const char *str中讀取。它的強大之處在于可以方便地從字符串中取出整數、浮點數和字符串等各種類型的數據,而且它還具有類似于正則表達式的匹配功能,sscanf默認是以空格分隔字符串的,如果不是以空格來分割的話,就可以使用%[ ]來指定分割的條件。如%[a-z]表示讀取a到z的所有字符,%[^a-z]表示過濾a-z之間的所有字符,即只要遇到a到z之間的任意字符,轉換立刻停止。比如:
sscanf(“abcdefABCDEF”, “%[^A-Z]”, str);
printf(“%s\n”, str);
result is: abcdef
%[^A-Z]這樣(yang)的匹(pi)配格式為我們取遇到大(da)寫字(zi)母(mu)為止的字(zi)符(fu)串。利(li)用這種匹(pi)配方式,我們就可以(yi)靈活(huo)的操作字(zi)符(fu)串,得到我們想(xiang)要的結果。
現在我們需要從下面的字符串中提取有效信息:
$GPRMC,121252.000,A,3958.3032,N,11629.6046,E,15.15,359.95,070306,,,A*54
GPRMC每個字段的含義如下:
$GPRMC,<1>,<2>,<3>,<4>,<5>,<6>,<7>,<8>,<9>,<10>,<11>,<12>*hh<CR><LF>
<1> UTC時間,hhmmss(時分秒)格式
<2> 定位狀態,A=有效定位,V=無效定位
<3> 緯度ddmm.mmmm(度分)格式(前面的0也將被傳輸)
<4> 緯度半球N(北半球)或S(南半球)
<5> 經度dddmm.mmmm(度分)格式(前面的0也將被傳輸)
<6> 經度半球E(東經)或W(西經)
<7> 地面速率(000.0~999.9節,前面的0也將被傳輸)
<8> 地面航向(000.0~359.9度,以真北為參考基準,前面的0也將被傳輸)
<9> UTC日期,ddmmyy(日月年)格式
<10> 磁偏角(000.0~180.0度,前面的0也將被傳輸)
<11> 磁偏角方向,E(東)或W(西)
<12> 模式(shi)指(zhi)示(shi)(僅NMEA0183 3.00版本輸出(chu),A=自主定(ding)位,D=差分(fen),E=估(gu)算,N=數據無效)
我們提取1~9九條信息。用一個結構體存放這些信息:
typedef struct gps_info
{
char utc_time[BUF_SIZE];
char status;
float latitude_value;
char latitude;
float longtitude_value;
char longtitude;
float speed;
float azimuth_angle;
char utc_data[BUF_SIZE];
}GPS_INFO;
因為每一個字段之間都是以逗號間隔開的,所以我們可以利用%[^,]來分割字符串,這樣用sscanf函數就可以實現對有效信息的提取:
sscanf(wellhandled_string,"$GPRMC,%[^,],%c,%f,%c,%f,%c,%f,%f,%[^,]",
rmc_info->utc_time,\
&(rmc_info->status),&(rmc_info->latitude_value),&(rmc_info->latitude),\
&(rmc_info->longtitude_value),&(rmc_info->longtitude),&(rmc_info->speed),\
&(rmc_info->azimuth_angle),\
rmc_info->utc_data );
這個函數執行后,打印出的保存在struct gps_info結構體里的信息如下所示:
utc_time: 024813.640
status: A
latitude: N latitude value: 3158.460693
longtitude: E longtitude value: 11848.374023
speed: 10.050000
azimuth_angle: 324.269989
utc_data: 150706
可見(jian),利用好sscanf函數,可以讓(rang)我們可以很高效(xiao)的(de)處理字符串。