理解声明(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 = ' '; /* 加上字符串结束符 */
this.type = (char)classify_string(); /* 判断类型 */
} else if (*p != ' ') { /* 单字符标记 */
strcpy(next_string, p + 1); /* 修改字符串 */
this.type = *p;
this.string[1] = ' ';
}
}
// 读至第一个标识符
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
", str[i]);
strcpy(next_string, str[i]);
read_to_first_identifier();
deal_with_declarator();
printf("
");
top = -1;
}
return 0;
}
答案
运行上面的程序,给出的结果为:
Identifier "c" is array 0..9 of pointer to function returning pointer to char
即c是一个大小为10的数组,其元素类型是函数指针,指向的函数的返回值是一个指向char的指针。