1. 程式人生 > 實用技巧 >C專家程式設計(2)

C專家程式設計(2)

理解宣告(P64-66)

面對一些複雜的宣告形式,可以通過以下兩種方法來理解,分別是優先順序法和圖示法。下面以書上的
char * const *(*next)(); 為例,分別進行分析。

  • 優先順序法(P64)

適用規則 解釋
A 首先,看變數名next,並注意到它直接被括號所括住
B.1 所以先把括號裡的東西作為一個整體,得出“next是一個指向...的指標”
B 然後考慮括號外面的東西,在星號字首和括號字尾之間作出選擇
B.2 規則告訴我們優先順序較高的是右邊的函式括號,所以得出“next是一個函式指標,指向一個返回...的函式"
B.3 然後,處理字首*
,得出指標所指的內容
C 最後,把char * const解釋為指向字元的常量指標

這個宣告表示“next是一個指標,它指向一個函式,該函式返回另一個指標,該指標指向一個型別為 char的常量指標”

  • 圖表法(P65)

signal函式宣告的解析

首先來看一下signal的宣告形式:

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

簡化後為void(*signal(x,xx))(int),表明signal函式有兩個引數(x和xx),並返回一個函式指標,指向的函式接受int型別的引數並返回void。而xx引數表示的函式與signal本身的形式一樣。

因此可以使用typedef void(*pf)(int);來簡化函式的宣告,簡化後為:pf signal(int, pf);

練習

char *(* c[10])(int **p);  // 答案在文章末尾

程式設計挑戰

#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define MAXTOKENLEN (256)
#define MAXTOKENS (16)
#define TOTAL_NUMS(array) (sizeof(array) / sizeof(array[0]))

char *STR_TYPE[] = {"char",   "short",    "int",  "long",   "float", "double",
                    "signed", "unsigned", "void", "struct", "union", "enum"};
char *STR_QUALIFIER[] = {"const", "volatile"};

typedef enum {
  TYPE,       /* 型別 */
  QUALIFIER,  /* 限定符 */
  INDENTIFIER /* 識別符號 */
} type_t;

struct token {
  char type;
  char string[MAXTOKENLEN];
};

struct token stack[MAXTOKENS]; /* 儲存第一個標識之前的所有標記 */
struct token this;             /* 儲存剛讀入的標記 */
char next_string[MAXTOKENLEN];

int top = -1; /* 棧頂 */
// 壓棧
static void push(struct token t) { stack[++top] = t; }
// 出棧
static struct token pop(void) { return stack[top--]; }

// 字串分類,獲取當前標識的型別
static type_t classify_string(void) {
  int i = 0;
  char *s = this.string;

  for (i = 0; i < (int)TOTAL_NUMS(STR_TYPE); i++) {
    if (strcmp(s, STR_TYPE[i]) == 0) {
      return TYPE;
    }
  }
  for (i = 0; i < (int)TOTAL_NUMS(STR_QUALIFIER); i++) {
    if (strcmp(s, STR_QUALIFIER[i]) == 0) {
      return QUALIFIER;
    }
  }
  return INDENTIFIER;
}

// 獲取標記
static void gettoken(void) {
  char *p = next_string; /* 取新的一段字串 */

  while (*p == ' ') { /* 忽略空格 */
    p++;
  }
  strcpy(this.string, p); /* 字串賦值 */
  p = this.string;

  if (isalnum(*p)) { /* 如果是字母數字組合 */
    while (isalnum(*++p))
      ;                                  /* 直到讀取到其他字元 */
    strcpy(next_string, p);              /* 修改字串 */
    *p = '\0';                           /* 加上字串結束符 */
    this.type = (char)classify_string(); /* 判斷型別 */
  } else if (*p != '\0') {               /* 單字元標記 */
    strcpy(next_string, p + 1);          /* 修改字串 */
    this.type = *p;
    this.string[1] = '\0';
  }
}

// 讀至第一個識別符號
static void read_to_first_identifier(void) {
  gettoken();
  while (this.type != INDENTIFIER) /* 不是識別符號,將標記入棧 */
  {
    push(this);
    gettoken(); /* 取下一個標記 */
  }

  printf("Identifier \"%s\" is ", this.string);

  gettoken();
}

/*************** 解析程式 ***************************************/
// 處理函式引數
static void deal_with_function_args(void) {
  while (this.type != ')') {
    gettoken();
  }
  gettoken();
  printf("function returning ");
}

// 處理函式陣列
static void deal_with_arrays(void) {
  while (this.type == '[') /* 繼續讀取數字或']' */
  {
    printf("array ");
    gettoken();
    if (isdigit(this.string[0])) {             /* 如果是數字 */
      printf("0..%d ", atoi(this.string) - 1); /* 列印陣列大小 */
      gettoken();                              /* 獲取']' */
    }
    gettoken();
    printf("of ");
  }
}

// 處理任何指標
static void deal_with_any_pointers(void) {
  while (stack[top].type == '*') {
    pop();
    printf("pointer to ");
  }
}

// 處理宣告器
static void deal_with_declarator(void) {
  if (this.type == '[') {
    deal_with_arrays();
  } else if (this.type == '(') {
    deal_with_function_args();
  }

  deal_with_any_pointers();

  while (top > -1) {
    if (stack[top].type == '(') {
      pop();
      gettoken();
      deal_with_declarator();
    } else {
      deal_with_any_pointers();
      printf("%s ", pop().string);
    }
  }
}

int main(void) {
  // 測試用例參考 https://blog.csdn.net/yyhustim/article/details/9612185
  char *str[] = {"char * const *(*next)()", "char *(* c[10])(int **p)",
                 "const int * grape",       "int const * grape",
                 "int * const grape",       "int sum(int a, int b)",
                 "char (*(*x())[])()",      "char (*(*x[3])())[5]"};
  for (int i = 0; i < (int)TOTAL_NUMS(str); i++) {
    printf("== %s\n", str[i]);
    strcpy(next_string, str[i]);
    read_to_first_identifier();
    deal_with_declarator();
    printf("\n\n");
    top = -1;
  }

  return 0;
}

答案

執行上面的程式,給出的結果為:

Identifier "c" is array 0..9 of pointer to function returning pointer to char

即c是一個大小為10的陣列,其元素型別是函式指標,指向的函式的返回值是一個指向char的指標。