[标题] char *(*(**(*(*(*x[5])(int,float))[][12])(double))(short,long))[][173] ?!
今天又捧起久违的K&R C拜读了一遍。其实有点东西在6年前就想写,借着今天这个机会,终于把它写出来了。
初看一眼标题中的变量定义感觉是不是很抓狂?:)一直以来,C语言中关于指针、数据和函数的复合定义都是一个难点,其实,理解它也是有规律可循的。然而,即便是国内在讲解指针方面久负盛名的“谭本”也没有将这一规律说清楚,K&R C虽然提到了一点,却始终没有捅破这层窗户纸,也许是K&R觉得以“直观方式”解释太阳春白雪了点吧:)在Blog上面说说这种不值一提的dd倒正合适。
其实,理解C语言中复合定义的关键在于对变量声明语句中各修饰符结合律的把握,我们可以将它们的结合规律简单归纳如下:
(1) 函数修饰符 ( ) 从左至右
(2) 数组修饰符 [ ] 从左至右
(3) 指针修饰符 * 从右至左
其中,(1)与(2)的修饰优先级是相同的,而(3)比前两者的优先级都低,而且是写在左边的。下面我们给出3个直观的例子来说明如何借助结合律来理解复合变量声明,为了简单点,函数修饰符一律使用无形参的签名形式。
示例1. char (*(*x[3])())[5]
这是什么意思?别急,跟着走一遭咱就知道是什么了。根据结合律,我们可以依次写出与x结合的修饰符:
x -1-> [3] -2-> * -3-> () -4-> * -5-> [5] -6-> char
然后我们再来从左至右地对上述过程进行解释:
1说明:x是一个一维数组,数组中元素个数为3
2说明:上述数组中每一个元素都是一个指针
3说明:这些指针都是函数的指针,该函数的签名为( )
4说明:上面的函数所返回的值是一个指针
5说明:上面的指针所指向的是一个一维数组,其元素个数为5
6说明:上面的数组中的每一个元素均是一个字符
不知大家在上面的规范化步骤描述中看出端倪来了没有?:)这个声明的含义是:x是一个由3个指向函数A的指针所组成的一维数组,函数A返回指向一个元素个数为5的字符数组的指针。其实,以结合律来解析复合声明的方式是一种“由近及远”的方式:首先尝试着去说清楚离变量“近”的修饰符的含义,然后再对“远处” 的修饰符进行依次说明,从抽象到具体,从顶到底,层层细化。
实际上,我比较反感这种一步到位的复合方式,它不仅把变量定义和类型声明混为一谈,而且也不能直观地体现出类型的含义,更糟糕的是,这不符合典型的“积木化”的程序思维,我更倾向于采用typedef,以一种“由远及近”的方式来逐步定义变量的形态,即先定义若干基本类型,然后再在其基础上将其扩充成复杂类型,最后利用复杂类型定义变量。例如,上述的例子,如果要我来定义,我觉得如此定义比较恰当:
typedef char ArrayOfChar[5];
typedef ArrayOfChar* PointerOfArrayOfChar;
typedef PointerOfArrayOfChar (*PointerOfFunc)()
typedef PointerOfFunc ArrayOfPointerOfFunc[3]
ArrayOfPointerOfFunc pfa;
这种“堆积木”的方式实际上和那个复合声明是等价的,其看似繁冗,但对于程序员而言却很直观,所以平心而论,我比较推荐这种积木化声明方式,而不推荐以复合声明直接一步到位。
示例2. char (**x[3])()[5]
根据结合律,将上述声明改写如下:
x -1-> [3] -2-> * -3-> * -4-> () -5-> [5] -6-> char
1说明:x是一个数组,这个数组包括3个元素
2说明:每个元素均为一个指针
3说明:上面的指针又指向另一个指针
4说明:上面的第二个指针是一个函数的指针
5说明:上面的函数返回的是一个数组,这个数组包括5个元素?? (错误!)
从上述推导过程可以发现,当我们到达第5步时,其语义提到了“一个函数返回了一个数组”,这在C语言中实际上是错误的定义,即,( )与[ ]相邻是非法的,因此,编译器将拒绝接受这一关于x变量的声明。同样的,在推导过程中[ ]与( )相邻也是不合法的,什么叫做“一个数组,这个数组里面的每一个元素都是一个函数(而不是一个指针)”?在这种情况下,编译器也会100%报错。
示例3. char p[5][7]、char (*q)[7]、char *r[5] 和 char **s
不知p、q、r、s这四个变量类型是否兼容?根据结合律,有:
p -> [5] -> ([7] -> char) const
q -> * -> ([7] -> char)
r -> [5] -> { * -> char} const
s -> * -> { * -> char}
不难发现,无需经过类型强制转换即可将p赋值给q、将r赋给s,而其他的赋值方式均是错误的。为什么?首先,p和r是两个数组,不是指针,因此不能修改其值;其次,不妨让我们来对p与q(或者r与s)在其括号内的类型部分分别进行sizeof运算,可以发现,二者的结果是一样的,即:p、q(或者r、s)指针变量具备一致的增量寻址行为,所以二者才兼容。
看完了上述解释,想必最唬人的指针复合定义恐怕也难不倒你了。试试下面的挑战如何?
1. 解释一下x变量的含义:char *(*(**(*(*(*x[5])(int,float))[][12])(double))(short,long))[][173];
2. 在32位环境下,假设void* p=(void *)(x+1),x=0x1234;则p的16进制值为多少?sizeof(x)等于多少?
;-)
分享到:
相关推荐
CString string char * int double float 之间转化 将每种转化 都有不同的实现方法 以及相关知识 进行总结
void ibm2ieee(float* input, int swap); void toibm (long *addr, int ll); unsigned char ebasc(unsigned char ascii); void ebasd(unsigned char* ascii, unsigned char* ebcd); void asebd(char* ebcd, char* ...
void initGaussKernel( unsigned char * kernel, float *kenelf32,float sigma ,int size); IMAGE_t *getGaussDownPyramids(IMAGE_t * src,uchar *gaussKernel,int gaussSize); IMAGE_t *getGaussDownPyramidsf32...
对于指针和常量,有以下三种形式都是正确的: 代码如下:const char * myPtr = &char_A;//指向常量的指针char * const myPtr = &char_A;//常量的指针const char * const myPtr = &char_A;//指向常量的常量指针下面...
各种基本数据类型转换源程序。 本程序涉及到的数据类型有:float, int , char, string.
Keil MDK-ARM各种数据类型占用的字节数 char short int float double占用字节数,uCOS-II
extern "C" int __stdcall ZSY3DSynchronizeData(float* x, float* y, float* z, int* count); ////******** 判断窗口是打开还是关闭 ********// extern "C" int __stdcall ZSY3DGetWindowState(float *states); /...
5、Char[] to int 将字符串类型转换成整数型,可以使用atoi函数,例如: char c[10]; int n; n = atoi(c); 6、Char[] to float 和第5个技巧一样,使用atof()函数可以转换成float型,例如: char c[10]; float f; f ...
C语言程 停车场管理问题 #include<stdio.h> #include<stdlib.h> struct { char status; int num; int time; }a; /*命令的结构*/ typedef struct{ int num; int time; }Element; struct { Element *base; Element *...
} int Push(Stack_char *S,char x)//进栈 { if(S->top==Stack_Size-1) return (FALSE); S->top++; S->elem[S->top]=x; return (TRUE); } int Pop(Stack_char*S,char*x)//出栈 { if(S->top==-1) return(FALSE); else...
guanlixit6ong#include <stdio.h>#include <string.h>#include <ctype.h>#include <stdlib.h>#defineMONTH_NUM 5 /* 最多的月份 */struct worker{int number; /* 每个工人的工号 */char name[15]; /* 每个工人的姓名...
c语言的itoa:char *m_itoa(int n) 整数转换为字符串。char *m_itoh(unsigned int num, int length, int prefix)整数转换为0x十六进制字符串。num: 要转换的数字,无视符号。length:指定字节长度,一字节为2个十六进制...
所在函数库为math.h、stdlib.h、string.h、float.h int abs(int i) 返回整型参数i的绝对值 double cabs(struct complex znum) 返回复数znum的绝对值 double fabs(double x) 返回双精度参数x的绝对值 ...
学生信息包括: typedef struct { char no[20];...int find(STUDENT t[], int n, char *s) ; /*查找函数*/ int menu_select(void); /*主菜单函数*/ void modify(STUDENT t[],int n);/*修改学生信息*/
int number; /*学号*/ char name[20]; /*姓名*/ float electrics; /*电子技术成绩*/ float c_program; /*C++语言成绩*/ float multimedia; /*多媒体技术成绩*/ float english; /*大学英语成绩*/ float math; /...
用c++写的车牌识别程序(前面一小段)#include #include #include //#include #include #include #include //#include "cv.h" ...void outtext(struct Bmp1 img1,int x,int y); int calstr(byte *srcBmp);
void printf(float x,float y,const char *format,...); void printfc(float x,float y,const char *format,...); protected: GLuint tex_id; GLuint list_id; int step; int space[256][2]; int ...
int float double string char * C->Java Java->C 实例
char ,int,long,float,double,等在64位和32位下分别占多少字节
int x = (rect.Width() - cxIcon + 1) / 2; int y = (rect.Height() - cyIcon + 1) / 2; // Draw the icon dc.DrawIcon(x, y, m_hIcon); } else { CDialog::OnPaint(); } } // The system calls this ...