C/C++语法之若干个为什么

一、语法
语法是一个语言的基础,每个语言都会形式化的定义自己的语法规则,因为现在大部分时候还是“属性文法”,也就是基于语法的语义识别,所以严格的语法对于任何一门语言都是必须的,即使所谓的第四代编程语言SQL语言看起来非常高端,但是事实上它的语法也是有严格规定的,也可以通过语法文件(好像应该叫巴克斯-诺尔范式吧)来描述。
C语言是我们接触的比较多的语言,它的语法事实上是相对比较宽松自由的,比方说,可以在没有看到声明的时候调用一个函数,如果函数声明中没有通过void或者其它方法指明参数类型,那么调用的时候参数是任意多个的等。这些规则在习惯了教科书教育的同学看来来比较不可思议的,因为容易出错。但是对于C语言的发明者来说,这些都是为了简洁方便的。因为早期的程序员都是一些水平比较高的开发者,语法并不负有保证或者避免程序员犯低级错误的责任,只有让程序员尽量少的敲击键盘,尽量的freestyle。但是后来随着计算机的普及,计算机编程的门槛越来越低,计算机语言的设计者们就考虑到使用语言本身来避免可能发生的低级错误。
二、早期C语言函数参数后置声明
在早期的C语言函数定义中,函数的参数并不是在函数体内声明的,而是在表示参数的右括号')'和表示函数体实现的左大括号'{'之间声明函数类型的。举个例子来说,对于我们现在常用的
int foo(int bar, int baz)
{
return bar + baz;
}
最为正宗的声明方式是
int foo(bar,baz)  int bar, baz;
{
return bar + baz;
}
也就是函数参数的声明是在参数之外的。这个语法大家现在看起来是比较的诡异(weird),但是换一个角度来看,这个实现在某些情况下是有好处的,比方说,你声明的两个参数都是double类型,或者是一个结构类型,那么这种写法是可以节省输入量的。我们换个例子
int foo(double x,double y)
如果换成是古典的定义方法,可以是
int foo(x,y) double x,y;
大家可以计算一下输入量,是不是第二中方法的输入量更少一些。但是现在作为工程中的码农来说,这种参数声明为x和y的做法是不被允许的,所以可能参数输入可能会大于输入量。另一方面,古典的定义方法的另一个好处就是可以一目连然的看到所有同种类型的函数参数。当然这一点在很多公司的编码规范里也是比较忌讳的,因为一方面现在的软件协作开发对于可读性和可维护性要求更高,另一方面是现在公司假设大部分码农的理解能力要比以前低一些。
有人就说了,你在这里比比吃吃的说这么多,是不是吃饱了撑的慌,恩,吃是吃饱了,但是一直没休息好。
三、gcc的__attribute__不能放在函数定义括弧后面
我也是偶尔用到了gcc的这个扩展语法,是真正用到,不是在这里钻牛角尖或者晒智商下限。当时就发现一个问题,就是修饰函数属性的__attribute__只能写在函数定义的开始,而不能写在函数体之前,例如
int foo () __attribute__((weak)) 
{
return ;2
}
这个是在gcc中编译不过的。但是在声明中是可以的,也就是
int foo () __attribute__((weak));
可以的。而且
int __attribute__((weak)) foo () 
{
return ;2
}
也是可以的。
大家现在可能会明白我为什么会说第二点的原因。大家可以看到,为了兼容早期的C语言定义,在参数和函数体之间是留给函数参数类型声明的,所以不能用来放函数属性。和现实一样,很多你过眼即望的历史可能正在影响着你的现在。
顺便说一下,结构中声明的属性就可以在语句的最后。
四、C++ 类中静态成员初始化
对于C++的静态成员的初始化,可能刚开始大家都觉得有些别扭,所以我在这里再描述一下。比方说在类i里演示一下它的初始化方法
struct FOO
{
static int myvar;
};
int FOO::myvar = 0x1234;
为什么不能把这个初始化直接放在类的声明中呢?
因为在C++中,变量的初始化可能是要执行初始化函数的,而初始化函数中可能会完成各种操作。如果把静态成员的初始化放入函数申请,那么这个初始化函数由谁来执行?因为一个类的声明会被任意多的编译单元(通俗的说就是源文件)编译,你如何在编译阶段判断这个初始化函数是由哪一个编译单元承担。所以只能让用户来手动决定在哪里执行这个静态成员的唯一一次初始化动作。我这里的例子里的静态成员比较简单,所以没有体现展示效果,如果大家连这个都理解不了,那可能也不会来看这篇文章了。
五、C++类中常量成员初始化
常量成员虽然是常量,但是它是以对象为单位的,不同的实例对于这个类来说可能是不相同的。声明为const,只是说在类的执行过程中不应该修改这个成员的值,但是执行函数构造函数的时候是可以初始化的,而事实上这个也是唯一的一次初始化机会。这个初始化只能在构造函数的参数声明和函数体之间定义,也就是所谓的initializer。
例如
struct FOO
{
const int myconst;
FOO():myconst(something) {
myconst = something;//注意,这种方法同样有编译错误,只能在上面的冒号之后,函数体定义之前初始化。事实上,编译并不会理解初始化函数中执行的语法对于函数初始化有什么语义和影响,它只能理解那些初始化子,因为那里的定义更为严格,可控性强。
};
}
六、C++中函数默认参数
这一点对于刚从C语言转到C++的同学来说可能比较神奇,所以我们现在想象一下它的实现方法。
事实上函数默认参数对被调用者(callee)来说是透明的,因为它并不知道调用者到底自备了几个参数,它只能一视同仁,假设所有调用者传递了所有的参数,包括默认参数。而对于调用者(caller)来说,它只是由编译器代劳完成了一些形式化的东西,也就是如果你调用的地方没有给出所有参数,那么编译器将默认参数作为参数,依然是凑够了所有参数,这一点对于程序员和被调用函数来说都是透明的。这里就有一个前提,那就是调用者必须看到包含有默认参数的函数声明,这一点并不是问题,因为C++中的确是必须知道所有调用函数的原型(应为名字粉碎的原因“name mangling”,可以参考我之前一篇博客中关于为什么C++函数必须声明的文章)。

猜你喜欢

转载自www.cnblogs.com/tsecer/p/10487414.html