c语言连续区间判断标准(两个原则理解复杂声明)
c语言连续区间判断标准(两个原则理解复杂声明)可以总结为右左原则:1.2.3 前缀操作符:星号*表示”指向...的指针”。1.2.2 后缀操作符:括号()表示这是一个函数,而方括号口表示这是一个数组。
C语言的声明模型之所以如此晦涩,这里有几个原因。六十年代晚期,人们在设计C语言的这部分内容时,“类型模型(type model)”这个概念对于当时的编程语言理论而言尚属陌生。BCPL语言(C语言的祖先)几乎没有类型,它把二进制字作为惟一的数据类型,所以C语言先天有缺。然后出现了一种C语言设计哲学,要求对象的声明形式与它的使用形式尽可能相似。一个int类型的指针数组被声明为int *p[3]; 并以*p[i]这样的表达式引用或使用指针所指向的int数据,所以它的声明形式和使用形式非常相似。这种做法的好处是各种不同操作符的优先级在“声明”和“使用”时是一样的。它的缺点在于操作符的优先级(有15级或更多,取决于你怎么算)是C语言中另外一个设计不当、过于复杂之处。程序员需要记住特殊的规则才能推断出int *p[3] 到底是一个int类型的指针数组,还是一个指向int数组的指针。
1 声明的优先级规则与右左原则1.1 声明从它的名字开始读取,然后按照优先级顺序依次读取。
1.2 优先级从高到低依次是:
1.2.1 声明中被括号括起来的那部分
1.2.2 后缀操作符:
括号()表示这是一个函数,而
方括号口表示这是一个数组。
1.2.3 前缀操作符:星号*表示”指向...的指针”。
可以总结为右左原则:
一路向右,遇到改变优先级的右括号或声明表达式的末尾才向左,通常是先右后左(有改变优先级的括号)。人们的习惯阅读顺序是左右原则,这是C声明的晦涩之处。
2 函数与数组声明的分裂写法看函数与数组的简单声明:
int foo(int a); // 函数的标识“()”,内包含参数类型与返回类型的分裂
// 右左原则去理解:foo的右边是(),表明是一个函数,参数是int,然后左边,返回类型时int
int arr[3][4]; //数组的标识“[]”,内包含元素个数,与元素类型的分裂
// 右左原则去理解:arr的右边是[],表明是一个数组,内有3个元素,再往右,元素的类型还是数组
函数的返回值不能是一个函数,所以像化foo()()这样是非法的。
函数的返回值不能是一个数组,所以像foo()[]这样是非法的。
数组里面不能有函数,所以像foo[]()这样是非法的。
但像下面这样则是合法的:
函数的返回值允许是一个函数指针,如:int(* fun())();
函数的返回值允许是一个指向数组的指针,如:int(* foo())[];
数组里面允许有函数指针 如int (* foo[])();
数组里面允许有其他数组,所以你经常能看到int foo[][];
数组,函数声明分裂成两部分,分别在首部和尾部,声明的重心原则上要落在后边(右边),但分裂的尾部显然不是重心所在,要改变重心所在,自然需要使用改变优先级的小括号。
分裂原则:数组和函数声明时的分裂写法:
函数返回数组或函数指针时,会分裂到声明的首部与尾部。
a 数组指针在尾部[]内标识元素数量,然后到前部去找元素类型,声明的重心落在改变优先级的小括号内;
b 函数指针在尾部()内标识参数类型,然后到前部去找返回类型,声明的重心落在改变优先级的小括号内;
一路向右原则的合理性理解:函数和数组声明的后缀操作符()、[],按分裂原则,写在标识符的右边,也可以理解为括号(小括号、中括号)优先。另外,按语法的句法规则,句子重心一般落在后面(右边)。
通过数组指针与指针数组来理解:
int *parr[3]; // 右左和分裂原则,往右,[]表明是数组,往左,标识数组元素类型是int*;
int (*arrp)[]; // 右左和分裂原则,往右,碰到右括号),阻止了往右,往左,标识是指针
// 往右,[]表明是数组,往左,表明数组的元素类型。
因为数组声明的分裂写法,在声明数组指针时,()既表明了优先级,又由向右变更成了向左,因为如果向左的话看到的是[],则是数组了。
以下数组包含函数指针元素同样如此:
int (* foo[])(); // foo[]表明是一个数组,剩下的就是元素类型(指针,函数指针)
以下函数返回函数函数指针或数组指针同样如此:
int(* fun())();//fun()表明是一个函数,剩下的就是返回类型(指针,函数指针)
int(* foo())[]; // foo()表明是一个函数,剩下的就是返回类型(指针,数组指针)
3 应用上述两个原则来理解几个较复杂的实例
char * const *(*next)();
(*next) ,一个指针;
往右是(),函数标识,无参,next是一个函数指针;
往左(函数分析完参数,然后是函数返回类型),char* const*就是函数返回类型,也是返回一个指针,指针本身const,指向目标类型是char*。
char *(* c[10])(int **p);
c[10],一个数组,数组分析完元素个数,通常要往左分析,怎样才能往左,使用改变优先级的小括号
(* c[10]) ,数组的元素是指针类型;
再往右,标识()表明是函数,函数参数是int **p,返回类型是char*
void (*signal(int sig void(*func)(int)))(int);
signal(,表明是一个函数,继承找到匹配的右括号)
signal(int sig void(*func)(int)),函数两个参数,一个是int类型,一个是函数指针
往右,碰到右括号,往左,指针符号*,表明函数的返回类型是指针,什么指针呢?
往右,碰到(int)表明是函数,无法继承往右,往左,函数的返回类型是void
上述函数指针可以使用typedef来简化声明:
typedef void(*ptr_to_func) (int);
signal()函数声明可以改写为:
ptr_to_func signal (int ptr_to_func);
这样就清晰多了。
4 总结一下因为数组和函数声明的分裂写法,以及声明的优先级规则,重心会落在右边,以下三类、五种声明要通过小括号来改变“一直向右”的规则(后缀操作符优先规则):
4.1 小括号括定标识符和指针声明符
① 声明函数指针时;
int (*funcPtr)(int a int b);
② 声明数组指针时;
int(*arrPtr)[5];
4.2 小括号括定数组声明及指针声明符
③ 声明函数指针数组时;
int (*funcPtrArr[5])(int a int b);
4.2 小括号括定函数声明及指针声明符
④ 函数返回函数指针时;
int (*funcRetFuncPtr(int a int b))(int a int b);
⑤ 函数返回数组指针时;
int(*funcRetArrPtr(int a int b)[5];
以上五种声明写法,也可以直接从小括号内部开始,然后再写小括号外部部分。
ref:
Peter Van Der Linden 《C专家编程》
-End-