理解声明(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的指针。