此系列文章,于振南老師向大家講述嵌入式C語(yǔ)言的一些高階知識(shí),俗稱“騷操作”,助你水平再上一個(gè)臺(tái)階!
C語(yǔ)言,是一門非常靈活而強(qiáng)大的編程語(yǔ)言。同樣一個(gè)算法、一個(gè)功能,我們可以把它寫得中規(guī)中矩,也可以把它寫得晦澀難懂。而且很多自詡為編程高手的人,偏偏就喜歡把程序?qū)懗商鞎?,認(rèn)為讓別人看不懂,卻能實(shí)現(xiàn)正確的功能,此乃技術(shù)高超的表現(xiàn)。
我不評(píng)價(jià)這樣的作法是否可取,因?yàn)槊總€(gè)人都有各自的風(fēng)格和個(gè)性。讓他違背意愿去編程,那么編程可能就會(huì)變得索然無(wú)味,毫無(wú)樂(lè)趣。很多時(shí)候不是我們想把程序?qū)懙秒y懂,而是我們要去看懂別人的程序。 在本文中,振南列舉一些我曾經(jīng)見過(guò)和使用過(guò)的編程技巧,并進(jìn)行深入的解析。
一、字符串的實(shí)質(zhì)就是指針
字符串是C語(yǔ)言中最基礎(chǔ)的概念,也是最常被用到的。在嵌入式開發(fā)中,我們經(jīng)常要將一些字符串通過(guò)串口顯示到串口助手或調(diào)試終端上,作為信息提示,以便讓我們了解程序的運(yùn)行情況;或者是將一些常量的值轉(zhuǎn)為字符串,來(lái)顯示到液晶等顯示設(shè)備上。 那么,C語(yǔ)言中的字符串到底是什么?其實(shí)字符串本身就是一個(gè)指針,它的值(即指針?biāo)赶虻牡刂罚┚褪亲址鬃址牡刂贰?為了解釋這個(gè)問(wèn)題,我經(jīng)常會(huì)舉這樣一個(gè)例子:如何將一個(gè)數(shù)值轉(zhuǎn)化為相應(yīng)的16進(jìn)制字符串。比如,把100轉(zhuǎn)為”0X64”。 我們可以寫這樣一個(gè)函數(shù):
void Value2String(unsigned char value,char *str) { char *Hex_Char_Table="0123456789ABCDEF"; str[0] = '0'; str[1] = 'X'; str[4] = 0; str[2]=Hex_Char_Table[value>>4]; str[3]=Hex_Char_Table[value&0X0F]; }
字符串常量實(shí)質(zhì)是內(nèi)存中的字節(jié)序列,如下圖所示:
上面,振南說(shuō)“字符串本身就是指針”,那么見證這句話真正意義的時(shí)刻來(lái)了,我們將上面程序進(jìn)行簡(jiǎn)化:
void Value2String(unsigned char value,char *str) { str[0]='0';str[1]='X';str[4]=0; str[2]="0123456789ABCDEF"[value>>4]; str[3]="0123456789ABCDEF"[value&0X0F]; }
Hex_Char_Table 這個(gè)指針變量其實(shí)是多余的,“字符串本身就是指針”,所以它后面可以直接用 [] 配合下標(biāo)來(lái)取出其中的字符。凡是實(shí)質(zhì)上為指針類型(即表達(dá)的是地址意義)的變量或常量,都可以直接用[]或*來(lái)訪問(wèn)它所指向的數(shù)據(jù)序列中的數(shù)據(jù)元素。
二、轉(zhuǎn)義符
C語(yǔ)言中要表達(dá)一個(gè)字節(jié)數(shù)據(jù)序列(內(nèi)存中連續(xù)存儲(chǔ)的若干個(gè)字節(jié)),我們可以使用字節(jié)數(shù)組,比如unsigned char array[10]={0,1,2,3,4,5,6,7,8,9}。 其實(shí)字符串,本質(zhì)上也是一個(gè)字節(jié)序列,但是通常情況下它所存儲(chǔ)的字節(jié)的值均為 ASCII 中可打印字符的碼值,如’A’、’ ‘、’|’等。那在字符串中是否也可以出現(xiàn)其它的值呢?這樣,我們就可以用字符串的形式來(lái)表達(dá)一個(gè)字節(jié)序列了。很多時(shí)候,它可能比字節(jié)數(shù)組要方便一些。字符串中的轉(zhuǎn)義符就是用來(lái)干這個(gè)的。請(qǐng)看如下程序:
const unsigned char array[10]={0,1,2,3,4,5,6,7,8,9}; char *array="x00x01x02x03x04x05x06x07x08x09";這兩種寫法,array所指向的內(nèi)存字節(jié)序列是基本一樣的(后者最后還有一個(gè)0)。當(dāng)然,如果我們把a(bǔ)rray傳到strlen去計(jì)算長(zhǎng)度,返回的值為0。因?yàn)樗谝粋€(gè)字節(jié)的值為0。但是我們?nèi)匀豢梢允褂胊rray[n]的方式去訪問(wèn)序列中的數(shù)據(jù)。
char *str="ABCDEFG"; char *str="x41x42x43x44x45x46x47";
上面程序中的兩種寫法,是完成等價(jià)的。
字符串中的轉(zhuǎn)義符的目的是為了在本應(yīng)該只能看到ASCII可打印字符的序列中,可以表達(dá)其它數(shù)值或特殊字符。如經(jīng)常使用的回車換行” ”,其實(shí)質(zhì)就是”x0dx0a”;通常我們所說(shuō)的字符串結(jié)束符?,其實(shí)就是0的八進(jìn)制轉(zhuǎn)義表達(dá)形式。
三、字符串常量的連接
在研讀一些開源軟件的源代碼時(shí),我見到了字符串常量的一個(gè)比較另類的用法,在這里介紹給大家。 有些時(shí)候,為了讓字符串常量?jī)?nèi)容層次更加清晰,就可以把一個(gè)長(zhǎng)字符串打散成若干個(gè)短字符串,它們順序首尾相接,在意義上與長(zhǎng)字符串是等價(jià)的。比如“0123456789ABCDEF”可以分解為“0123456789”“ABCDEF”,即多個(gè)字符串常量可以直接連接,夠成長(zhǎng)字符串。這種寫法,在 printf 打印調(diào)試信息的時(shí)候可能會(huì)更多用到。
printf("A:%d B:%d C:%d D:%d E:%d F:%d ",1,2,3,4,5,6); printf("A:%d " "B:%d " "C:%d " "D:%d " "E:%d " "F:%d ",1,2,3,4,5,6);在 printf 的格式化串很長(zhǎng)的時(shí)候,我們把它合理的打散,分為多行,程序就會(huì)顯得更多工整。
四、長(zhǎng)字符串的拆分技巧
很多時(shí)候我們需要進(jìn)行長(zhǎng)字符串的拆分。問(wèn)題是如何實(shí)現(xiàn)它? 很多人可能都會(huì)想到使用那個(gè)分隔字符,比如空格、逗號(hào)。然后去一個(gè)個(gè)數(shù)要提取的參數(shù)前面有幾個(gè)分隔字符,然后后將相應(yīng)位置上的字符組成一個(gè)新的短字符串。如下圖所示: 這種方法固然可行,但是略顯笨拙。其實(shí),對(duì)于這種有明顯分隔符的長(zhǎng)字符串,我們可以采用“打散”或“爆炸”的思想,具體過(guò)程是這樣的:將長(zhǎng)字符串中的所有分隔符全部替換為’?’,即字符串結(jié)束符。此時(shí),長(zhǎng)字符串就被分解成了在內(nèi)存中順序存放的若干個(gè)短字符串。 如果要取出第n個(gè)短字符串,可以用這個(gè)函數(shù):
char * substr(char *str,n) { unsigned char len=strlen(str); for(;len>0;len--) {if(str[len-1]==' ') str[len-1]=0;} for(;n>0;n--) { str+=(strlen(str)+1); } return str; }很多時(shí)候我們需要一次性訪問(wèn)長(zhǎng)字符串中的多個(gè)短字符串,此時(shí)振南經(jīng)常會(huì)這樣來(lái)作:通過(guò)一個(gè)循環(huán),將長(zhǎng)字符串中的所有分隔符替換為’?’,在此過(guò)程中將每一個(gè)短字符串首字符的位置記錄到一個(gè)數(shù)組中,代碼如下:
unsigned char substr(unsigned char *pos,char *str) { unsigned char len=strlen(str); unsigned char n=0,i=0; for(;i舉個(gè)例子:我們要提取”abc 1000 50 off 2500”中的”abc”、”50”和”off”,可以使用上面的函數(shù)來(lái)實(shí)現(xiàn)。 unsigned char pos[10]; char str[30]; strcpy(str,"abc 1000 50 off 2500"); substr(pos,str); str+pos[0]; //"abc" str+pos[2]; //"50" str+pos[3]; //"off"
五、取出數(shù)值的各位數(shù)碼
在實(shí)際項(xiàng)目中,我們經(jīng)常需要提取一個(gè)數(shù)值的某些位的數(shù)碼,比如用數(shù)碼管來(lái)顯示數(shù)值或?qū)⒁粋€(gè)數(shù)值轉(zhuǎn)成字符串,都會(huì)涉及到這一操作。 那如何實(shí)現(xiàn)這一操作呢?雖然這個(gè)問(wèn)題看似很簡(jiǎn)單,但提出這一問(wèn)題的人還不在少數(shù)。 請(qǐng)看下面的函數(shù):
void getdigi(unsigned char *digi,unsigned int num) { digi[0]=(num/10000)%10; digi[1]=(num/1000)%10; digi[2]=(num/100)%10; digi[3]=(num/10)%10; digi[4]=num%10; }它的主要操作就是除法和取余。這個(gè)函數(shù)只是取出一個(gè)整型數(shù)各位的數(shù)碼,那浮點(diǎn)呢?
其實(shí)一樣的道理,請(qǐng)看下面函數(shù)(我們默認(rèn)整數(shù)與小數(shù)部分均取4位)。
void getdigi(unsigned char *digi1,unsigned char *digi2,unsigned float num) { unsigned int temp1=num; unsigned int temp2=((num-temp1)*10000); digi1[0]=(temp1/1000)%10; digi1[1]=(temp1/100)%10; digi1[2]=(temp1/10)%10; digi1[3]=(temp1)%10; digi2[0]=(temp2/1000)%10; digi2[1]=(temp2/100)%10; digi2[2]=(temp2/10)%10; digi2[3]=(temp2)%10; }有人說(shuō),我更喜歡用sprintf函數(shù),直接將數(shù)值格式化打印到字符串里,各位數(shù)碼自然就得到了。char digi[10]; sprintf(digi,"%d",num); //**整型 char digi[10]; sprintf(digi,"%f",num); //**浮點(diǎn)
沒(méi)問(wèn)題。但在嵌入式平臺(tái)上使用sprintf函數(shù),通常代價(jià)是較大的。
作為嵌入式工程師,一定要惜字如金,尤其是在硬件資源相對(duì)較為緊張的情況下。sprintf非常強(qiáng)大,我們只是一個(gè)簡(jiǎn)單的提取數(shù)值數(shù)碼或?qū)?shù)值轉(zhuǎn)為相應(yīng)的字符串的操作,使用它有些暴殄天物。這種時(shí)候,我通常選擇寫一個(gè)小函數(shù)或者宏來(lái)自己實(shí)現(xiàn)。
六、printf的實(shí)質(zhì)與使用技巧
printf是我們非常熟悉的一個(gè)入門級(jí)的標(biāo)準(zhǔn)庫(kù)函數(shù),每當(dāng)我們說(shuō)出計(jì)算機(jī)金句”Hello World!”時(shí),其實(shí)無(wú)意中就提到了它:printf(“hello world!”); 它可以某種特定的格式、進(jìn)制或形式輸出任何變量、常量和字符串,為我們提供了極大的方便,甚至成為了很多人調(diào)試程序時(shí)重要的Debug手段。但是在嵌入式中,我們就需要剖析一下它的實(shí)質(zhì)了。 printf 函數(shù)的底層是基于一個(gè) fputc 的函數(shù),它用于實(shí)現(xiàn)單個(gè)字符的具體輸出方式,比如是將字符顯示到顯示器上,或是存儲(chǔ)到某個(gè)數(shù)組中(類似sprintf),或者是通過(guò)串口發(fā)送出去,甚至不是串口,而是以太網(wǎng)、CAN、I2C等接口。 以下是一個(gè)STM32項(xiàng)目中fputc函數(shù)的實(shí)現(xiàn):
int fputc(int ch, FILE *f) { while((USART1->SR&0X40)==0); { USART1->DR = (u8) ch; } return ch; }
fputc中將ch通過(guò)USART1發(fā)出。這樣,我們?cè)谡{(diào)用printf的時(shí)候,相應(yīng)的信息就會(huì)從USART1打印出來(lái)。
“上面你說(shuō)的這些,我都知道,有什么新鮮的!”確實(shí),通過(guò)串口打印信息是我們司空見慣的。 那么,下面的fputc你見過(guò)嗎?
int fputc(int ch, FILE *f) { LCD_DispChar(x,y,ch); x++; if(x>=X_MAX) { x=0;y++; if(y>=Y_MAX)* { y=0; } } return ch; }
這個(gè)fputc將字符顯示在了液晶上(同時(shí)維護(hù)了字符的顯示位置信息),這樣當(dāng)我們調(diào)用printf的時(shí)候,信息會(huì)直接顯示在液晶上。
說(shuō)白了,fputc 就是對(duì)數(shù)據(jù)進(jìn)行了定向輸出。這樣我們可以把 printf 變得更靈活,來(lái)應(yīng)對(duì)更多樣的應(yīng)用需求。 在振南經(jīng)歷的項(xiàng)目中,曾經(jīng)有過(guò)這樣的情況:單片機(jī)有多個(gè)串口,串口1用于打印調(diào)試信息,串口2與ESP8266 WIFI模塊通信,串口3與SIM800 GPRS模塊通信。3個(gè)串口都需要格式化輸出,但printf只有一個(gè),這該怎么辦? 我們解決方法是,修改fputc使得printf可以由3個(gè)串口分時(shí)復(fù)用,具體實(shí)現(xiàn)如下:
unsigned char us=0; int fputc(int ch,FILE *f) { switch(us) { case 0: while((USART1->SR&0X40)==0); USART1->DR=(u8)ch; break; case 1: while((USART2->SR&0X40)==0); USART2->DR=(u8)ch; break; case 2: while((USART3->SR&0X40)==0); USART3->DR=(u8)ch; break; } return ch; }在調(diào)用的時(shí)候,根據(jù)需要將us賦以不同的值,printf就歸誰(shuí)所用了。#define U_TO_DEBUG us=0; #define U_TO_ESP8266 us=1; #define U_TO_SIM800 us=2; U_TO_DEBUG printf("hello world!"); U_TO_ESP8266 printf("AT "); U_TO_SIM800 printf("AT ");
七、關(guān)于浮點(diǎn)數(shù)的傳輸
很多人不能很好的使用和處理浮點(diǎn),其主要根源在于對(duì)它的表達(dá)與存儲(chǔ)方式不是很理解。最典型的例子就是經(jīng)常有人問(wèn)我:“如何使用串口來(lái)發(fā)送一個(gè)浮點(diǎn)數(shù)?” 我們知道C語(yǔ)言中有很多數(shù)據(jù)類型,其中unsigned char、unsigned short、unsigned int、unsigned long我們稱其為整型,顧名思義它們可以表達(dá)整型數(shù)。而能夠表達(dá)的數(shù)值范圍與數(shù)據(jù)類型所占用的字節(jié)數(shù)有關(guān)。數(shù)值的表達(dá)方法比如簡(jiǎn)單,如下圖所示: 一個(gè)字節(jié)可以表達(dá)0~255,兩個(gè)字節(jié)(unsigned short)自然就可以表達(dá)0~65535,依次類推。 當(dāng)需要把一個(gè)整型數(shù)值發(fā)送出去的時(shí)候,我們可以這樣作:
unsigned short a=0X1234; UART_Send_Byte(((unsigned char *)&a)[0]); UART_Send_Byte(((unsigned char *)&a)[1]);也就是將構(gòu)成整型的若干字節(jié)順序發(fā)送即可。當(dāng)然,接收方一定要知道如何還原數(shù)據(jù),也就是說(shuō),它要知道自己接收到的若干字節(jié)拼在一起是什么類型,這是由具體通信協(xié)議來(lái)保障的。unsigned char buf[2]; usnigned short a; UART_Receive_Byte(buf+0); UART_Receive_Byte(buf+1); a=(*(usnigned short *)buf);OK,關(guān)于整型比較容易理解。但換成float,很多人就有些迷糊了。因?yàn)閒loat的數(shù)值表達(dá)方式有些復(fù)雜。有些人使用下面的方法來(lái)進(jìn)行浮點(diǎn)的發(fā)送:float a=3.14; char str[10]={0}; ftoa(str,a); //**浮點(diǎn)轉(zhuǎn)為字符串* *即3.14**轉(zhuǎn)為"3.14" UART_Send_Str(str); //**通過(guò)串口將字符串發(fā)出很顯然,這種方法非常的“業(yè)余”。
還有人問(wèn)我:“浮點(diǎn)小數(shù)字前后的數(shù)字可以發(fā)送,但是小數(shù)點(diǎn)怎么發(fā)?”這赤裸裸的體現(xiàn)了他對(duì)浮點(diǎn)類型的誤解。
不要被float數(shù)值的表象迷惑,它實(shí)質(zhì)上只不過(guò)是4個(gè)字節(jié)而已,如下圖所示:
所以,正確的發(fā)送浮點(diǎn)數(shù)的方法是這樣的:
float a=3.14; UART_Send_Byte(((unsigned char *)&a)[0]); UART_Send_Byte(((unsigned char *)&a)[1]); UART_Send_Byte(((unsigned char *)&a)[2]); UART_Send_Byte(((unsigned char *)&a)[3]);接收者將數(shù)據(jù)還原為浮點(diǎn):unsigned char buf[4]; float a; UART_Receive_Byte(buf+0); UART_Receive_Byte(buf+1); UART_Receive_Byte(buf+2); UART_Receive_Byte(buf+3); a=*((float *)buf);
其實(shí)我們應(yīng)該發(fā)現(xiàn)數(shù)據(jù)類型的實(shí)質(zhì):不論是什么數(shù)據(jù)類型,它的基本組成無(wú)非就是內(nèi)存中存儲(chǔ)的若干個(gè)字節(jié)。只是我們?nèi)藶榈馁x予了這些字節(jié)特定的編碼方式或數(shù)值表達(dá)??创┝诉@些,我們就認(rèn)識(shí)到了數(shù)據(jù)的本質(zhì)了,我們甚至可以直接操作數(shù)據(jù)。
八、關(guān)于數(shù)據(jù)的直接操作
直接操作數(shù)據(jù)?我們來(lái)舉個(gè)例子:取一個(gè)整型數(shù)的相反數(shù)。一般的實(shí)現(xiàn)方法是這樣的:
int a=10; int b=-a; //-1*a;這樣的操作可能會(huì)涉及到一次乘法運(yùn)算,花費(fèi)更多的時(shí)間。當(dāng)我們了解了整型數(shù)的實(shí)質(zhì),就可以這樣來(lái)作:int a=10; int b=(~a)+1;這也許還不足以說(shuō)明問(wèn)題,那我們?cè)賮?lái)看一個(gè)例子:取一個(gè)浮點(diǎn)數(shù)的相反數(shù)。似乎只能這樣來(lái)作:float a=3.14; float b=a*-1.0;其實(shí),我們可以這樣來(lái)作:float a=3.14; float b; ((unsigned char *)&a)[3]^=0X80; b=a;
沒(méi)錯(cuò),我們可以直接修改浮點(diǎn)在內(nèi)存中的高字節(jié)的符號(hào)位。這比乘以-1.0的方法要高效的多。
當(dāng)然,這些操作都需要你對(duì) C 語(yǔ)言中的指針有爐火純青的掌握。
九、浮點(diǎn)的四舍五入與比較
我們先說(shuō)第一個(gè)問(wèn)題:如何實(shí)現(xiàn)浮點(diǎn)的四舍五入?很多人遇到過(guò)這個(gè)問(wèn)題,其實(shí)很簡(jiǎn)單,只需要把浮點(diǎn)+0.5然后取整即可。 OK,第二個(gè)問(wèn)題:浮點(diǎn)的比較。這個(gè)問(wèn)題還有必要好好說(shuō)一下。 首先我們要知道,C語(yǔ)言中的判等,即==,是一中強(qiáng)匹配的行為。也就是,比較雙方必須每一個(gè)位都完全一樣,才認(rèn)定它們相等。這對(duì)于整型來(lái)說(shuō),是可以的。但是float類型則不適用,因?yàn)閮蓚€(gè)看似相等的浮點(diǎn)數(shù),其實(shí)它們的內(nèi)存表達(dá)不能保證每一個(gè)位都完全一樣。 這個(gè)時(shí)候,我們作一個(gè)約定:兩個(gè)浮點(diǎn)只要它們之差m足夠小,則認(rèn)為它們相等,m一般取10e-6。也就是說(shuō),只要兩個(gè)浮點(diǎn)小數(shù)點(diǎn)后6位相同,則認(rèn)為它們相等。也正是因?yàn)檫@個(gè)約定,很多C編譯器把float的精度設(shè)定為小數(shù)點(diǎn)后7位,比如ARMCC(MDK的編譯器)。
float a,b; if(a==b) ... //**錯(cuò)誤 if(fabs(a-b) <= 0.000001) ...//**正確
十、出神入化的for循環(huán)
for循環(huán)我們?cè)偈煜げ贿^(guò)了,通常我們使用它都是中規(guī)中矩的,如下例:
int i; for(i=0;i<100;i++) {...}
但是,如果我們對(duì)for循環(huán)的本質(zhì)有更深刻的理解的話,就可以把它用得出神入化。
for后面的括號(hào)中的東西我稱之為“循環(huán)控制體”,分為三個(gè)部分,如下圖所示: A、B、C三個(gè)部分,其實(shí)隨意性很大,可以是任意一個(gè)表達(dá)式。所以,我們可以這樣寫一個(gè)死循環(huán):
for(1;1;1) //1**本身就是一個(gè)表達(dá)式:常量表達(dá)式 { ... }當(dāng)然,我們經(jīng)常會(huì)把它簡(jiǎn)化成:for(;;) { ... }既然循環(huán)控制體中的A只是在循環(huán)開始前作一個(gè)初始化的操作,那我這樣寫應(yīng)該也沒(méi)毛?。?int i=0; for(printf("Number: ");i<10;i++) { printf(" %d ",i); }B是循環(huán)執(zhí)行的條件,而C是循環(huán)執(zhí)行后的操作,那我們就可以把一個(gè)標(biāo)準(zhǔn)的if語(yǔ)句寫成for的形式,而實(shí)現(xiàn)同樣的功能:if(strstr("hello world!","abc")) { printf("Find Sub-string"); }char *p; for(p=strstr("hello world!","abc");p;p=NULL) { printf("Find Sub-string"); }
以上的例子可能有些雞肋,“一個(gè)if能搞定的事情,我為什么要用for?”,沒(méi)錯(cuò)。我們這里主要是為了解釋for循環(huán)的靈活用法。深入理解了它的本質(zhì),有助于我們?cè)趯?shí)際開發(fā)中讓工作事半功倍,以及看懂別人的代碼。
以下我再列舉幾個(gè)for循環(huán)靈活應(yīng)用的例子,供大家回味。 例1:
char *p; for(p="abcdefghijklmnopqrstuvwxyz"; printf(p); p++) printf(" ");
提示:printf我們太熟悉了,但有幾個(gè)人知道printf是有返回值的?輸出應(yīng)該是怎樣的?
例2:
char *p; unsigned char n; for(p="ablmnl45ln",n=0;((*p=='l')?(n++):0),*p;p++);提示:還記得C語(yǔ)言中的三目運(yùn)算和逗號(hào)表達(dá)式嗎?n應(yīng)該等于幾?
例3:
unsigned char *index="C[XMZA[C[NK[RDEX@"; char *alphabet="EHUIRZWXABYPOMQCTGSJDFKLNV "; int i=0; for(;(('@'!=index[i])?1:(printf("!!Onz "),0));i++) { printf("%c",alphabet[index[i]-'A']); }
-
嵌入式
+關(guān)注
關(guān)注
5082文章
19104瀏覽量
304797 -
C語(yǔ)言
+關(guān)注
關(guān)注
180文章
7604瀏覽量
136684 -
編程
+關(guān)注
關(guān)注
88文章
3614瀏覽量
93686
原文標(biāo)題:C語(yǔ)言的一些“騷操作”及其深層理解
文章出處:【微信號(hào):玩點(diǎn)嵌入式,微信公眾號(hào):玩點(diǎn)嵌入式】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論