C語言是一種通用的程序設計語言,它與UNIX系統之間具有非常密切的聯繫,C語言是在UNIX系統上開發的,並且,無論是UNIX系統本身還是運行其上的大部分程序,都是用C語言編寫編寫的。C語言很適合用來編寫編譯器和操作系統以及各種系統底層軟件,因此被稱爲“系統編程語言”,但它同樣適合於編寫不同領域中的大多數程序。
C語言中,指針的使用非常廣泛。與其他方法相比較,使用指針通常可以生成更高效、更緊湊的代碼。C語言常常因爲聲明的語法問題而受到人們的批評,特別是涉及到函數指針的語法。C語言的語法力圖使聲明和使用相一致。對於簡單的情況,C語言的做法是很有效的,但是,如果情況比較複雜,則容易讓人混淆,原因在於,C語言的聲明不能從左至右閱讀,而且使用了太多的圓括號。例如下面的指針聲明:
int (*daytab)[13]
int *dattab[13]
void *comp()
void (*comp)()
char (*(x())[])()
char (*(*x[3])())[5]
C語言參考手冊對聲明語法作了詳細的描述,簡化的語法形式如下(dcl爲declaration的縮寫):
direct-dcl: name
(dcl)
direct-dcl()
direct-dcl[可選的長度]
右左法則是常用的C語言複雜聲明解析方法。右左法則其實並不是C標準裏面的內容,它是從C標準的聲明規定中歸納出來的方法。C標準的聲明規則,是用來解決如何創建聲明的,而右左法則是用來解決如何辯識一個聲明的,兩者可以說是相反的。右左法則的原文如下:
The right-left rule: Start reading the declaration from the innermost parentheses, go right, and then go left. When you encounter parentheses, the direction should be reversed. Once everything in the parentheses has been parsed, jump out of it. Continue till the whole declaration has been parsed.
其大致意思如下:
右左法則:首先從最裏面的圓括號看起,然後往右看,再往左看。每當遇到圓括號時,就應該掉轉閱讀方向。一旦解析完圓括號裏面所有的東西,就跳出圓括號。重複這個過程直到整個聲明解析完畢。
《C程序設計語言》一書專門講到複雜聲明的解析,並且基於聲明符的語法編寫了解析程序。經修改和完善後的完整程序如下:
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#define MAXTOKEN 100
enum { NAME, PARENS, BRACKETS };
void dcl(void);
void dirdcl(void);
int gettoken(void);
int getline(char s[], int lim);
int tokentype;
char token[MAXTOKEN];
char name[MAXTOKEN];
char datatype[MAXTOKEN];
char out[1000];
#define BUFSIZE 1024
char buf[BUFSIZE];
char line[BUFSIZE];
int bufp = 0;
int linep = 0;
int getch(void){
if (bufp > 0 || (line[linep] != ''))
return (bufp > 0) ? buf[--bufp] : line[linep++];
else
return EOF;
}
void ungetch(int c)
{
if (bufp >= BUFSIZE)
printf("ungetch: too many characters ");
else
buf[bufp++] = c;
}
int main(int argc, char* argv[])
{
getline(line, BUFSIZE);
while (gettoken() != EOF) {
strcpy(datatype, token);
out[0] = '';
dcl();
if (tokentype != ' ')
printf("syntax error ");
printf("%s: %s %s ", name, out, datatype);
}
return 0;
}
int gettoken(void)
{
int c;
char *p = token;
while ((c = getch()) == ' ' || c == ' ') ;
if (c == '(') {
if ((c = getch()) == ')') {
strcpy(token, "()");
return tokentype = PARENS;
} else {
ungetch(c);
return tokentype = '(';
}
} else if (c == '[') {
for (*p++ = c; (*p++ = getch()) != ']';)
;
*p = '';
return tokentype = BRACKETS;
} else if (isalpha(c)) {
for (*p++ = c; isalnum(c = getch());)
*p++ = c;
*p = '';
ungetch(c);
return tokentype = NAME;
} else
return tokentype = c;
}
int getline(char s[], int lim)
{
int c, i;
i = 0;
while (--lim > 0 && (c = getchar()) != EOF && c != ' ')
s[i++] = c;
if (c == ' ')
s[i++] = c;
s[i] = '';
return i;
}
void dcl(void)
{
int ns;
for (ns = 0; gettoken() == '*';)
ns++;
dirdcl();
while (ns-- > 0)
strcat(out, " pointer to");
}
void dirdcl(void)
{
int type;
if (tokentype == '(') {
dcl();
if (tokentype != ')')
printf("error: missing ) ");
} else if (tokentype == NAME)
strcpy(name, token);
else
printf("error: expected name or (dcl) ");
while ((type = gettoken()) == PARENS || type == BRACKETS)
if (type == PARENS)
strcat(out, " function returning");
else {
strcat(out, " array");
strcat(out, token);
strcat(out, " of");
}
}
getline():讀入聲明程序行,由用戶從STDIN輸入
getch(): 模擬讀入一個字符
ungetch():模擬回入一個字符
gettoken():跳過空格與製表符,以查找輸入中的下一個token。
使用dcl程序對上面複雜聲明的例子進行解析可以得到如下結果:
argv: pointer to pointer to char
int (*daytab)[13]
daytab: pointer to array[13] of int
int *daytab[13]
daytab: array[13] of pointer to int
void *comp()
comp: function returning pointer to void
void (*comp)()
comp: pointer to function returning void
char (*(*x())[])()
x: function returning pointer to array[] of pointer to function returning char
x: array[3] of pointer to function returning pointer to array[5] of char