编程语言的语法糖与语法抽象

语法糖(Syntactic sugar)是由Peter J. Landin(和图灵一样的天才人物,是他最先发现了Lambda演算,由此而创立了函数式编程)创造的一个词语,它意指那些没有给计算机语言添加新功能,而只是对人类来说更“甜蜜”的语法。语法糖往往给程序员提供了更实用的编码方式,有益于更好的编码风格,更易读。不过其并没有给语言添加什么新东西。

假设解决某一问题有两个写法A,B,其中A使用较简单的语法,需要写较多的代码;B使用较抽象的语法,只需要写较少的代码。B使用的语法是对A使用的语法的抽象,称为语法糖,其中的对应关系由编译器来实现。

所以说,在C等许多语言中都有许多习惯用法其实都是语法糖。

语法糖可以提供方便,让程序员少写几行代码,让代码看起来简洁。不过有时语法糖不如多个语句组合灵活。

1 数组下标运算符是指针算术运算的语法糖

#include 
int main()
{
    int a =1;
    int *p =&a;
    printf("%d
",0[p]); // 1
    int b[1] = {3};
    int *q = b;
    printf("%d
",0[q]); // 3
    return 0;
}

这说明什么呢?在《征服C指针》一书中提到,表达式中的[ ]其实就是语法糖,也就是说对于一个指向某种类型的指针p,一个整数n而言:p[n]=[n]p=*(p+n) ,即指针p所指向的内存地址+sizeof(*p*n)之后的地址。

2 lambdas表达式是函数对象(functor)的语法糖

实际上,lambda不是函数(这是它们如何避免C++不支持嵌套函数的限制的一部分)。它们是一种特殊的对象,叫做仿函数(functor)。functor是包含重载操作符()的对象,重载操作符使它们像函数一样可调用。

当编译器遇到lambda定义时,它会为lambda创建一个自定义对象定义(custom object definition)。每个捕获的变量都成为对象的数据成员。

在运行时,当遇到lambda定义时,将实例化lambda对象,并在此时初始化lambda的成员。

3 for循环

for循环是while循环的语法糖,while循环是跳转指令的语法糖。

int i = 0;
while(i

这里for循环的优点是循环控制变量的初始化和更新都集中到了一起,能防止自增语句忘了写,do()部分长一点的话往往被忽略。不过有些情况使用其它循环写法更易读:

while(iter!=list.end)
{
    if(cond(iter))
        iter=list.delete(iter); //删除list中符合条件的元素
    else
        ++iter;
}

基于范围的for语句是普通for的语法糖。

for(declaration: expression)
    statement

expression是一个对象,表示一个序列;

declaration部分定义一个变量,被用于访问序列中的基础元素,每次迭代该变量会被初始化为expression的下一个元素值。

如:

vector v = {0, 1, 2, 3, 4, 5};
for(auto &r: v)
      r *= 2;

4 自增自减++ 、 --

使用自增的优点是大大减少了代码量,有时会代码更精简,有时更易读。但有时也会令人费解,降低代码的可读性。特别是后置自增自减语句,理解起来一定要将其做为两句来理解。

n = *p++;
// 要理解为:
n = *p;
p++;

demo code:

//减少代码量的例子
while((*des++ = *src++) != 0);
//可以用下面的代替
while((*des = *src) != 0)
{
    ++des;
    ++src;
}
//下面这个代码就没办法缩成一行,把自增放到上面会有未定义行为
while(des[i] = src[i]) // 同一个表达式不能在多处对同一变量产生副作用
    ++i;

5 复合赋值运算符

当变量名较长时,如果不使用复合赋值运算符,当这一变量是对自身取值并更新时,变量名要写两次,费事且有时会容易写错。早期编译器如果不使用复合赋值运算符,还有临时对象一说。

6 面向对象都可以理解为面向过程的语法糖

面向对象的代码最终会翻译成面向过程的代码形式,其实这也是一种分层抽象的方式(当然,你也可以理解为高级编程语言是机器语言的抽象,指令集是CPU的抽象)。

#include 
using namespace std;

// 角色c1 圆接口定义者,可放到头文件,使用文件来区分模块
struct CCircle{
    double radius;
};
double area(struct CCircle cc);

// 角色c2 圆接口实现者,可以放到实现文件
double area(struct CCircle cc){
    return cc.radius * cc.radius * 3.14159;
}

// 角色c3 圆接口使用者,可以放到使用文件
void calc()
{
    struct CCircle cc;
    cc.radius = 10;
    double a = area(cc);
    printf("circle's area is %lf
", a);
}

// 角色cpp1 圆接口定义者,可放到头文件,使用文件和类来区分模块
class CppCircle{
public:
    double radius;
    double area();
};

// 角色cpp2 圆接口实现者,可以放到实现文件
double CppCircle::area(){
    return radius * radius * 3.14159;
}

// 角色cpp3 圆接口使用者,可以放到使用文件
void calc2()
{
    CppCircle cc;
    cc.radius = 10;
    double a = cc.area();
    printf("circle's area is %lf
", a);
}

int main()
{
    calc();
    calc2();

    getchar();
    return 0;
}

7 类型别名

7.1 typedef

7.2 using

using SI = Sales_item; //SI是Sales_item的别名声明,
// 把等号左侧的名字规定成等号右侧类型的别名

7.3 using可以起模板别名.

8 auto

auto让编译器通过初始值来推算变量类型。

auto i = 0, *p = &i; // ok
auto sz = 0, pi = 3.14; // error, 类型不统一

auto一般会忽略顶层const,保留底层const(修饰指针或引用的const)。如果希望保留顶层const,要明确指出

const auto p = ci;

设置类型为auto的引用时,初始值中的顶层const属性保留。

 int const * const p; // 底层 * 顶层:

9 decltype

类型指示符,顶层const能被保留。

decltype((i)) d; // error, 对表达式套括号的结果永远是引用,
// 不套括号则仅当i是引用时,才是引用。

-End-

发表评论
留言与评论(共有 0 条评论) “”
   
验证码:

相关文章

推荐文章