[标题] 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 之间转化大全 CString、string、char*、int、double、float 是编程中常用的数据类型,了解它们之间的转化关系非常重要。在本文中,我们将详细介绍 CString、string、char* ...
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* ...
5. **float**: 单精度浮点数`float`占用4个字节,遵循IEEE 754标准,能够表示大约6-7位有效数字的数值。 6. **double**: 双精度浮点数`double`占用8个字节,提供更高的精度,可以表示大约15位有效数字的数值。 在...
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;//指向常量的常量指针下面...
大家在学习或者使用Windows编程中,经常会碰到字符串之间的转换,char*转LPCWSTR也是其中一个比较常见的转换。下面就列出几种比较常用的转换方法。大家可以根据自己的需求选择相对应的方法,下面来一起学习学习吧。 ...
1. **基本类型**:如`int`、`char`、`float`等,代表数据的基本单位。 2. **指针**:通过星号(*)表示,用于存储内存地址。 3. **数组**:通过方括号([])表示,用于存储相同类型的一系列数据。 4. **函数**:通过...
5. **FLOAT**(`double`):用于存储双精度浮点数,对应于C语言中的`double`。 6. **SERIAL**(`long int`):一种自动递增的数据类型,通常用于主键字段。 7. **DATE**(`long int`):用于存储日期值。 8. **...
* FLOAT (float) 转换为 System.Single * DOUBLE (double) 转换为 System.Double * VARIANT 转换为 System.Object * PBYTE (byte \*) 转换为 System.Byte\[] * BSTR 转换为 StringBuilder * LPCTSTR 转换为 ...
C语言常用数学函数 C语言中的数学函数是math.h、stdlib.h、string.h、float.h四个标准头文件...* `char *itoa(int value, char *string, int radix)`: 将整数value转换成字符串并返回该字符串,radix为转换时所用基数
本文将讨论如何把 char str 或 unsigned char str 转换成 CString,详细介绍 CString 的构造函数和成员函数 Format 的使用。 一、CString 的构造函数 CString 提供了多种构造函数,可以将 char str 或 unsigned ...
4. rstoi(char *str, int *intval):把以空值结束的字符串转换成 C 的 int 类型 5. rstol(char *str, long *lngval):把以空值结束的字符串转换成 C 的 long 类型 这些函数可以用于各种字符串操作,例如字符串转换...
extern "C" int __stdcall ZSY3DSynchronizeData(float* x, float* y, float* z, int* count); ////******** 判断窗口是打开还是关闭 ********// extern "C" int __stdcall ZSY3DGetWindowState(float *states); /...
int strncmp(const char *s1, const char *s2, size_t n); ``` - **头文件**: `<string.h>` - **功能**: 比较两个字符串`s1`和`s2`的前`n`个字符。 - **返回值**: 如果`s1`小于`s2`,则返回负数;如果`s1`等于`...
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 ...
本文旨在探讨编程语言中常见的基本数据类型(`char`, `int`, `long`, `float`, `double`)在32位与64位操作系统环境下所占用的字节数量,并深入分析这些差异产生的原因及其对程序设计的影响。 #### 1. 数据类型的...
- **`int strncmp(const char *str1, const char *str2, size_t n)`**:比较两个字符串前`n`个字符。 - 查找函数 - **`char *strstr(const char *haystack, const char *needle)`**:在`haystack`中查找`needle`...
本文将深入探讨字符数组(char*)与C++中的Cstring之间的转换方法,以及更广泛的几种数据类型(如int、float)与CString之间的转换技巧。这不仅适用于C++程序员,对于任何需要在不同数据类型间进行转换的开发者来说...
* char -> char * unsigned char -> byte * short -> short * unsigned short -> ushort * int -> int * unsigned int -> uint * long -> int * unsigned long -> uint * long long -> long * unsigned long long ->...