日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程资源 > 编程问答 >内容正文

编程问答

《C专家编程》第三章——分析C语言的声明

發(fā)布時(shí)間:2023/12/18 编程问答 22 豆豆
生活随笔 收集整理的這篇文章主要介紹了 《C专家编程》第三章——分析C语言的声明 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

  前面一章我們已經(jīng)說(shuō)過(guò)C語(yǔ)言存在的一些問(wèn)題和它晦澀的地方,讓我們對(duì)這門神奇的語(yǔ)言有了更深的了解?,F(xiàn)在這一章則集中精力來(lái)討論C語(yǔ)言的聲明,分為三塊,首先是說(shuō)明C語(yǔ)言聲明晦澀難懂的原因和聲明是如何形成的,其次就是學(xué)習(xí)怎樣對(duì)C語(yǔ)言的聲明進(jìn)行分析,另外本文將詳細(xì)來(lái)探討一個(gè)分析C語(yǔ)言聲明的工具——cdecl,分析和編寫它的源代碼。

  C語(yǔ)言的聲明晦澀難懂這一點(diǎn)應(yīng)該是名不虛傳的,比如說(shuō)下面這個(gè)聲明:

  void (*signal(int sig, void(*func) (int)))(int);

這可不是嚇人的,熟悉C語(yǔ)言的人會(huì)發(fā)現(xiàn),這原來(lái)就是ANSI C標(biāo)準(zhǔn)中的信號(hào)的信號(hào)處理函數(shù)的函數(shù)原型,如果你沒(méi)有聽(tīng)說(shuō)過(guò),那么你確實(shí)應(yīng)該好好補(bǔ)補(bǔ)你的C語(yǔ)言了。那么這個(gè)函數(shù)原型是什么意思呢?后面會(huì)說(shuō)明,在這里提出就是證明在C語(yǔ)言中,的確存在這種晦澀難懂的聲明。

  為什么在C語(yǔ)言中會(huì)存在這種晦澀難懂的聲明呢?這里有幾個(gè)原因。首先,在設(shè)計(jì)C語(yǔ)言的時(shí)候,由于人們對(duì)于“類型模型”尚屬陌生,而且C語(yǔ)言進(jìn)化而來(lái)的BCPL語(yǔ)言也是無(wú)類型語(yǔ)言,所以C語(yǔ)言先天有缺。然后出現(xiàn)了一種C語(yǔ)言設(shè)計(jì)哲學(xué)——要求對(duì)象的聲明形式與它的使用形式盡可能相似,這種做法的好處是各種不同操作符的優(yōu)先級(jí)在“聲明”和“使用”時(shí)是一樣的。比如說(shuō):

  聲明一個(gè)int型變量時(shí):int n;

  使用這個(gè)int型變量時(shí):n

可以看出聲明形式和使用形式非常相似。不過(guò)它也有缺點(diǎn),它的缺點(diǎn)在于操作符的優(yōu)先級(jí)是C語(yǔ)言中另外一個(gè)設(shè)計(jì)不當(dāng)?shù)牡胤?。也就是說(shuō),C語(yǔ)言之前存在的操作符優(yōu)先級(jí)的問(wèn)題在這里又繼續(xù)影響它的聲明和定義,這就導(dǎo)致程序員需要記住特殊規(guī)則才能推測(cè)出一些稍微復(fù)雜的聲明,當(dāng)然之前也說(shuō)過(guò),C語(yǔ)言并不是為程序員設(shè)計(jì)的,它只是為編譯器設(shè)計(jì)的。在C++中,這一點(diǎn)倒是有所改善,比如說(shuō)int &p;就是聲明p是一個(gè)只想整形地址的數(shù)也就是指針。C語(yǔ)言的聲明存在的最大問(wèn)題是你無(wú)法以一種人們所習(xí)慣的自然方式從左向右閱讀一個(gè)聲明,在ANSI C引入volatile和const關(guān)鍵字之后,情況就更糟糕了。由于這些關(guān)鍵字只能出現(xiàn)在聲明中,這就使得聲明形式與使用形式完全對(duì)得上號(hào)的越來(lái)越少了。我相信有很多學(xué)習(xí)C語(yǔ)言的人都搞不太清楚const與指針之間的聲明關(guān)系,請(qǐng)看下面的例子:

  const int * grape;

  int const * grape;

  int * const grape;

  const int * const grape;

  int const * const grape;

怎么樣?如果你能正確的分析它們的含義,那么說(shuō)明你的C語(yǔ)言學(xué)得不錯(cuò),如果你已經(jīng)暈了,那也不怪你,畢竟這種情況只會(huì)在C語(yǔ)言里出現(xiàn)。不過(guò),還是讓我們來(lái)解決這幾個(gè)例題,首先我們要明白const關(guān)鍵字,它的名字經(jīng)常誤導(dǎo)人們,導(dǎo)致讓人覺(jué)得它就是個(gè)常量,在這里有個(gè)更合適的詞適合它,我們把它叫做”只讀“,它是個(gè)變量,不過(guò)你只有讀取它的權(quán)限,不能對(duì)它進(jìn)行任何修改。我是這么分析這種const聲明的:只要const出現(xiàn)在"*"這個(gè)符號(hào)之前,可能是int const *,也可能是const int *,總之,它出現(xiàn)在”*"之前,那么就說(shuō)明它指向的對(duì)象是只讀的。如果它在”*"這個(gè)符號(hào)之后,也就是說(shuō)它靠近變量名,那么就說(shuō)明這個(gè)指針是只讀的。換句話也可以這么說(shuō),如果它出現(xiàn)在"*"之前,說(shuō)明它修飾的是標(biāo)識(shí)符int或者其他類型名,那么說(shuō)明這個(gè)int的值是只讀的,說(shuō)明它指向的對(duì)象是常量;如果它出現(xiàn)在“*"之后,說(shuō)明它修飾的是變量名grape,那么說(shuō)明這個(gè)指針本身是只讀的,說(shuō)明這個(gè)指針為常量。這樣再來(lái)看上面兩個(gè)例題就很簡(jiǎn)單了,第一個(gè)和第二個(gè)的const均出現(xiàn)在"*"符號(hào)之前,而"*"之后沒(méi)有const變量,那么說(shuō)明這兩個(gè)都是常量指針,也就是說(shuō)指向的int值是只讀的;第三個(gè)const則出現(xiàn)在"*"之后,而”*"之前沒(méi)有,說(shuō)明第三個(gè)是一個(gè)指針常量,這個(gè)指針是只讀的;第四個(gè)和第五個(gè)const出現(xiàn)在“*"之前和之后,就說(shuō)明它既是指針常量也是常量指針,指針本身和指針?biāo)赶虻膇nt值都是只讀的。

  看到這里,相信大家已經(jīng)對(duì)C語(yǔ)言這種晦澀的聲明語(yǔ)法有所體會(huì)了,這樣看來(lái),正常人都不是很喜歡這種晦澀的語(yǔ)法,可能只有編譯器才會(huì)喜歡了吧!

  下面我們來(lái)看看聲明是如何形成的:

  首先要了解的東西叫做聲明器——是所有聲明的核心。聲明器是標(biāo)識(shí)符以及與它組合在一起的任何指針、函數(shù)括號(hào)、數(shù)組下標(biāo)等。下面我列出一個(gè)聲明器的組成部分,首先它可以有零個(gè)或多個(gè)指針,這些指針是否有const或是volatile關(guān)鍵字都沒(méi)有關(guān)系,其次,一個(gè)聲明器里有且只有一個(gè)直接聲明器,這個(gè)直接聲明器可以是只有一個(gè)標(biāo)識(shí)符,或者是標(biāo)識(shí)符[下標(biāo)],或者是標(biāo)識(shí)符(參數(shù)),或者是(聲明器)。書中給出的表格可能有些困難,所以把它總結(jié)下來(lái)就是這么一個(gè)公式:

  聲明器 = 直接聲明器( 標(biāo)識(shí)符 or 標(biāo)識(shí)符[下標(biāo)] or 標(biāo)識(shí)符(參數(shù)) or (聲明器) ) + (零個(gè)或多個(gè)指針)

這個(gè)式子已經(jīng)相當(dāng)簡(jiǎn)潔了,不過(guò)早些時(shí)候提到過(guò),()操作符在C語(yǔ)言中代表的意思太多了,在這里就體現(xiàn)了出來(lái),它既表示函數(shù),又表示聲明器,還表示括號(hào)優(yōu)先級(jí)。為了讓大家更好的理解,我來(lái)舉出一些例子給予說(shuō)明:

  有一個(gè)直接聲明器,并且這個(gè)聲明器為標(biāo)識(shí)符:n

  有一個(gè)直接聲明器為標(biāo)識(shí)符,還有一個(gè)指針:  * n

  有一個(gè)直接聲明器為標(biāo)識(shí)符[下標(biāo)],還有一個(gè)指針: * n[10]

  有一個(gè)直接聲明器為標(biāo)識(shí)符(參數(shù)):  n(int x)

這些聲明器看上去跟我們平時(shí)的聲明很相似,但是好像又不完整,別著急,因?yàn)槁暶髌髦皇锹暶鞯囊粋€(gè)部分,下面我們來(lái)看一條聲明的組成部分:C語(yǔ)言中的聲明至少由一個(gè)類型說(shuō)明符和一個(gè)聲明器以及零個(gè)或多個(gè)其他聲明器和一個(gè)分號(hào)組成。下面我們一一來(lái)介紹這每個(gè)部分:

  首先類型說(shuō)明符有這些:void、char、short、int、long、signed、unsigned、float、double以及結(jié)構(gòu)說(shuō)明符、枚舉說(shuō)明符、聯(lián)合說(shuō)明符。然后我們知道C語(yǔ)言的變量存儲(chǔ)類型有auto、static、register,鏈接類型有extern、static,還有類型限定符const、volatile,這些都是C語(yǔ)言常見(jiàn)的關(guān)鍵字和各種類型,那么一個(gè)聲明中至少要有一個(gè)類型說(shuō)明符,這個(gè)很好理解,因?yàn)檫@個(gè)類型說(shuō)明符告訴計(jì)算機(jī)我們要存儲(chǔ)的數(shù)據(jù)類型。

  聲明器的部分見(jiàn)上面,我已經(jīng)把它說(shuō)得比較清楚了。

  關(guān)于其他聲明器,我舉出一個(gè)例子大家就明白了:

  char i, j;

  看到它同時(shí)聲明了兩個(gè)變量,其中j就是其他聲明器,這表示同一條聲明語(yǔ)句中可以同時(shí)聲明多個(gè)變量。

  最后一個(gè)分號(hào),是C語(yǔ)言中一條語(yǔ)句結(jié)束的標(biāo)志。

  至此,C語(yǔ)言的聲明就已經(jīng)很清楚了,不過(guò)要注意在聲明的時(shí)候還是有一些其他規(guī)則,比如說(shuō)函數(shù)的返回值不能是一個(gè)函數(shù)或數(shù)組,數(shù)組里面也不能含有函數(shù)。

  了解了C語(yǔ)言聲明的詳細(xì)內(nèi)容之后,我們?cè)賮?lái)看看如何分析C語(yǔ)言的聲明

  接下來(lái)我們要做的事情就是,用通俗的語(yǔ)言把聲明分解開(kāi)來(lái),分別解釋各個(gè)組成部分。

  在這里提一句,關(guān)于分析C語(yǔ)言的聲明部分,在《C與指針》的第13章——高級(jí)指針話題中也有詳細(xì)的描述,會(huì)一步一步從簡(jiǎn)單的聲明到復(fù)雜的聲明,再介紹一些高級(jí)指針的用法。而在本書中,我們將著重建立一個(gè)模型來(lái)分析所有的聲明。

  先來(lái)理解C語(yǔ)言聲明的優(yōu)先級(jí)規(guī)則:

  A  聲明從它的名字開(kāi)始讀取,然后按照優(yōu)先級(jí)順序依次讀取

  B  優(yōu)先級(jí)從高到低依次是:

      1. 聲明中被括號(hào)括起來(lái)的部分

      2. 后綴操作符:括號(hào)()表示這是一個(gè)函數(shù);[]則表示這是一個(gè)數(shù)組

      3. 前綴操作符:星號(hào)* 表示"指向...的指針“

  C  如果const或volatile關(guān)鍵字存在,那么按我在前面所說(shuō)的辦法判斷它們修飾標(biāo)識(shí)符還是修飾類型

  下面,還是給出一個(gè)例子來(lái)幫助理解:

  char * const *(*next) ();

  A  首先,變量名是next

  B  next被一個(gè)括號(hào)括住,而括號(hào)的優(yōu)先級(jí)最高,所以”next是一個(gè)指向...的指針

      然后考慮括號(hào)外面的后綴操作符為(),所以”next是一個(gè)函數(shù)指針,指向一個(gè)返回值為...的函數(shù)“

      然后考慮前綴操作符,從而得出”next是一個(gè)函數(shù)指針,指向一個(gè)返回值為...的指針的函數(shù)“

  C  最后,char * const是一個(gè)指向字符的常量指針

  所以,我們可以得出結(jié)論:next是一個(gè)函數(shù)指針,該函數(shù)返回一個(gè)指針,這個(gè)指針指向一個(gè)類型為char的常量指針。

  當(dāng)然,如果不想自己分析這些復(fù)雜的聲明,你還有一個(gè)好的選擇,就是用一個(gè)工具來(lái)幫助你分析;或者你想知道自己的分析對(duì)不對(duì),也可以用到這個(gè)工具——cdecl,它是一個(gè)C語(yǔ)言聲明的分析器,可以解釋一個(gè)現(xiàn)存的C語(yǔ)言聲明。下面我簡(jiǎn)述它的安裝和使用過(guò)程,同樣是在Linux上:

  首先,安裝命令

sudo apt install cdecl

  然后直接輸入應(yīng)用程序名進(jìn)入程序

cdecl

  然后直接輸入,來(lái)檢測(cè)一下我們剛剛分析的例子

cdecl> explain char * const *(*next) (); declare next as pointer to function returning pointer to const pointer to char

  噢,看起來(lái)很不錯(cuò)嘛,我們分析得對(duì),這個(gè)程序也解釋得很棒,怎么樣,是不是對(duì)這個(gè)程序感到好奇,下面我們來(lái)嘗試自己實(shí)現(xiàn)這個(gè)程序

  首先我們想辦法用一個(gè)圖來(lái)表示分析聲明的整個(gè)過(guò)程,上面給出的步驟很有用,但是還是不夠直觀,在書中作者給出了一個(gè)解碼環(huán)的圖來(lái)描述這個(gè)步驟,下面我把這個(gè)圖大致的描述出來(lái),有的地方可能加上我自己的理解和修改:

?  

  要注意的事項(xiàng)我已經(jīng)把它們都標(biāo)志出來(lái)了,現(xiàn)在讓我們用這個(gè)流程圖來(lái)分析一個(gè)實(shí)例:

  char * const *(*next) ();

  分析過(guò)程中另外有一點(diǎn)需要特別注意,那就是需要逐漸把已經(jīng)處理過(guò)的片段“去掉”,這樣便能知道還需要分析多少內(nèi)容。

  

  上面的表格就是這個(gè)表達(dá)式根據(jù)前面給出的流程圖分析聲明的全部過(guò)程,從表格中第一列可以看出這個(gè)表達(dá)式被處理過(guò)的部分在一步一步的去掉,這個(gè)程序的處理過(guò)程現(xiàn)在已經(jīng)講得非常清楚了接下來(lái),給出實(shí)現(xiàn)這個(gè)過(guò)程的具體代碼,為了簡(jiǎn)單起見(jiàn),暫且忽略錯(cuò)誤處理部分,以及在處理結(jié)構(gòu)、枚舉、聯(lián)合時(shí)只簡(jiǎn)單的用“struct”,“enum“和”union“來(lái)代表,假定函數(shù)的括號(hào)內(nèi)沒(méi)有參數(shù)列表,否則事情就變得復(fù)雜多了!這個(gè)程序可能要用到一些數(shù)據(jù)結(jié)構(gòu),比如說(shuō)堆棧,像這種需要一個(gè)一個(gè)按序列讀取的程序總是免不了要用到堆棧的,在表達(dá)式求值等其他應(yīng)用也經(jīng)常見(jiàn)到。

  我們把用結(jié)構(gòu)來(lái)包括每一種標(biāo)記,首先定義數(shù)據(jù)結(jié)構(gòu)

#include <stdio.h> #include <string.h> #include <ctype.h> #include <stdlib.h>#define MAXTOKENS 100  //一條聲明中的標(biāo)記數(shù)量 #define MAXTOKENLEN 64   //每個(gè)標(biāo)記最長(zhǎng)的長(zhǎng)度 enum type_tag {IDENTIFIER, QUALIFIER, TYPE}; //定義類型標(biāo)簽,包括標(biāo)識(shí)符、限定符、類型符 struct token{            //聲明一個(gè)標(biāo)記結(jié)構(gòu),其中type就是枚舉變量中的類型 char type; char string[MAXTOKENLEN];  //類型的名字,例如int }; int top = -1;            //棧頂 struct token stack[MAXTOKENS];  //棧的內(nèi)存為MAXTOKENS struct token this;          //剛剛讀入的那個(gè)標(biāo)記 #define pop stack[top--]      //用宏定義兩個(gè)函數(shù),一個(gè)是push,一個(gè)是pop #define push(s) stack[++top] = s

?  定義好數(shù)據(jù)結(jié)構(gòu)之后,main函數(shù)要做的第一步是找出標(biāo)識(shí)符,然后按照上面的流程表執(zhí)行就行

main() {read_to_first_identifier();  //找出第一個(gè)標(biāo)識(shí)符deal_with_declarator();  //進(jìn)入處理標(biāo)識(shí)的函數(shù)printf("\n"); return 0; }

  下面來(lái)介紹單個(gè)函數(shù),首先是兩個(gè)基本工具函數(shù),一個(gè)是用來(lái)判斷當(dāng)前讀取的標(biāo)記的類型,另一個(gè)用來(lái)讀取下一個(gè)標(biāo)記到”this“

enum type_tag classify_string(void)  //判斷當(dāng)前讀取的標(biāo)記是什么類型,返回值為枚舉中的一個(gè)值 {char *s = this.string;        //如果是const,則把這個(gè)標(biāo)記的string成員改為"只讀“ if(!strcmp(s, "const")){ strcpy(s, "read-only"); return QUALIFIER; } if(!strcmp(s, "volatile")) return QUALIFIER; if(!strcmp(s, "void")) return TYPE; if(!strcmp(s, "char")) return TYPE; if(!strcmp(s, "signed")) return TYPE; if(!strcmp(s, "unsigned")) return TYPE; if(!strcmp(s, "short")) return TYPE; if(!strcmp(s, "int")) return TYPE; if(!strcmp(s, "long")) return TYPE; if(!strcmp(s, "float")) return TYPE; if(!strcmp(s, "double")) return TYPE; if(!strcmp(s, "struct")) return TYPE; if(!strcmp(s, "union")) return TYPE; if(!strcmp(s, "enum")) return TYPE; return INDENTIFIER;        //如果標(biāo)記既不是限定符也不是類型符,那就是標(biāo)識(shí)符 } void gettoken(void)      //讀取下一個(gè)標(biāo)記到”this“ { char *p = this.string; while((*p = getchar()) == ' ');  //跳過(guò)所有的空白字符 if(isalnum(*p)){          //如果遇到標(biāo)記(標(biāo)記必須以字母或數(shù)字開(kāi)頭) while(isalnum(*++p = getchar()));   ungetc(*p, stdin); *p = '\0'; this.type = classify_string(); return ; } if(*p == '*'){        //如果遇到指針 strcpy(this.string, "pointer to"); this.type = '*'; return ; } this.string[1] = '\0'; this.type = *p; return ; }

  接下來(lái)看讀取第一個(gè)標(biāo)識(shí)符,并且同時(shí)從左至右掃描聲明,遇到標(biāo)記則壓入棧中

read_to_first_identifer()    //[流程中的第一步] {gettoken();while(this.type != IDENTIFIER){push(this); gettoken(); } printf("%s is ",this.string); gettoken(); }

  找到標(biāo)識(shí)符的同時(shí),標(biāo)記的左半部分我們也已經(jīng)壓入了棧中,接下來(lái)處理標(biāo)識(shí)符之后可能存在的數(shù)組、函數(shù)

deal_with_declarator() {switch(this.type){        //查看當(dāng)前標(biāo)記的類型case '[': deal_with_arrays();      //如果是數(shù)組,那么執(zhí)行對(duì)數(shù)組的處理[流程中的第二步] break; case '(':              //如果是函數(shù),那么執(zhí)行對(duì)函數(shù)的處理[流程中的第三步] deal_witch_function_args(); } deal_with_pointers();    //處理指針,流程中的第五步 while(top >= 0){ if(stack[top].type == '('){  //判斷是否是左括號(hào),流程中的第四步 pop; gettoken(); deal_with_declaration(); }else{ printf("%s ",pop.string); } } }

  接下來(lái)就是編寫這些處理函數(shù)

deal_with_arrays() {while(this.type == '['){ printf("array "); gettoken(); if(isdigit(this.string[0])){ printf("0..%d ",atoi(this.string) - 1); gettoken(); } gettoken(); printf("of "); } } deal_with_fucntion_args() { while(this.type != ')'){ gettoken(); } gettoken(); printf("function returning "); } deal_with_pointers() { while(stack[top].type == '*'){ printf("%s ", pop.string); } }

  以上就是整個(gè)程序簡(jiǎn)單版的代碼,為了增加一些趣味,有興趣的朋友可以用昨天的lint檢查程序?qū)ι厦娴某绦蜃龀鰴z查,從而完善這個(gè)簡(jiǎn)單版的程序。

轉(zhuǎn)載于:https://www.cnblogs.com/monster-prince/p/6215769.html

總結(jié)

以上是生活随笔為你收集整理的《C专家编程》第三章——分析C语言的声明的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。