C/C++中的语法糖

34

我一直在研究Ruby,发现它的关键字"until"和"unless"非常有趣。因此,我想知道如何在C/C++中添加类似的关键字。以下是我的想法:

#define until(x)    while(!(x))
#define unless(x)   if(!(x))

我希望能够得到一些建议。有没有人能够提供更好的替代方案?

这是一个我编写的程序示例,用于说明我的意图:

#include <stdio.h>
#include <stdlib.h>

#define until(x)    while(!(x))
#define unless(x)   if(!(x))

unsigned int factorial(unsigned int n) {
    unsigned int fact=1, i;
    until ( n==0 )
        fact *= n--;
    return fact;    
}

int main(int argc, char*argv[]) {
    unless (argc==2)
        puts("Usage: fact <num>");
    else {
        int n = atoi(argv[1]);
        if (n<0)
            puts("please give +ve number");
        else
            printf("factorial(%u) = %u\n",n,factorial(n));
    }
    return 0;
}

如果您能指出一些类似的技巧,可以在C或C++中使用,那将是很棒的。


1
似乎你已经做过了。 - Thiago Cardoso
27
虽然一开始看起来这些很花哨,但即使您只是为自己编程,使用标准的C++更为适宜。 在团队合作中这是不可取的。您的代码将会变得陌生。我记得有一个名为sugar.h的文件包含类似于Pascal的定义... - mbx
4
我不喜欢在else子句中使用unless(然而,在Lisp中,我很喜欢when/unless)。我对使用until循环也没有什么特别的印象,可能是因为我以前从未使用过。 - 6502
1
你可能会对这个项目感兴趣:http://lolcode.com/,它还添加了一些语法糖... :D - Inverse
14
如果你一定要这样做,我唯一的建议是正确拼写“until”。 - Zach Rattner
显示剩余4条评论
10个回答

112

有没有人能提供更好的替代方案?

有。根本不需要这样做,直接使用whileif语句即可。

当你在C或C++中编程时,请使用C或C++。虽然untilunless在某些语言中可能经常被使用,但它们在C或C++中并不是惯用语。


16
同意这个观点。在 Ruby 中这可能是惯用法,但你现在不是在用 Ruby 编程,对吧?不要试图强制一种语言表现得像另一种语言,那样只会写出糟糕的代码。 - Mike Bailey
6
我知道在一个庞大的环境中,像这样编写代码只会招来麻烦。但我的问题是如何最好地去做这件事,而不是是否应该这样做。 - BiGYaN
15
最好的方法是根本不要去做这件事。 - James McNellis
1
那很有用! - n-smits

17
你这么做的方式在我看来是正确的方式,如果你真的要这样做的话。因为宏展开非常类似于预期[1],我认为将宏看起来像语法(),而不是通常建议使用的SCARY_UPPERCASE_MACROS(),用于显示此代码不遵循通常的语法,您只应谨慎使用它,是有效的。
[1]唯一的缺点是无法声明变量,但这很少见,并且在使用不正确时可能会在正确位置产生错误,而不是做一些奇怪的事情。
此外,即使阅读性略微提高也非常重要,因此能够说“until(”而不是“while(!”,确实使阅读许多循环更容易。如果结束条件更容易被视为异常条件(无论是否如此),以那种方式编写循环可以更容易地阅读。因此,即使这只是语法糖,我认为有理由考虑它。
然而,我不认为值得这样做。好处很小,因为大多数程序员习惯于阅读if(!)而成本是真实的:任何人阅读代码都必须检查这是一个宏还是自定义编译器,以及它是否做他们认为的事情。它可能会让您误以为您可以执行类似于i = 5除非xxxx;的操作。如果这样的小改进广泛采用,它们将使语言分裂,因此通常最好按标准方式处理事情,并缓慢采用改进。
然而,它可以做得很好:整个boost和tr1,特别是使用模板完成的扩展库的扩展,涉及以各种方式扩展C ++,其中许多没有被采用,因为它们似乎不值得,但其中许多具有小或非常广泛的接受程度,因为它们带来了实际的改进。

5
这个答案的第一段至少有问题!宏并没有命名空间,它们是完全全局的。因此,给宏起短名称可能会在其他地方使用,这是不好的。 如果您定义一个名为until的宏,然后包含一个声明成员函数until的第三方头文件,那么该头文件将会被破坏。它将尝试声明void until(int a);,但实际上会变成void while(!(int a));祝你好运理解错误信息吧!因此(毫不奇怪地),Boost为其所有的宏技巧都使用带前缀的恐怖大写名称。 - Daniel Earwicker

14

这让我想起了我在某人的代码中看到的东西:

#define R return;

此外,让代码难以理解,您还增加了维护成本。


8

我建议最好不要使用它们。

在Ruby风格中,您不能使用它们,如下所示:

`printf("hello,world") unless(a>0);`

是非法的。

而且对于C程序员来说,理解代码会更加困难。同时,额外的宏可能会成为一个问题。


6
如果a大于0,就会打印出“Hello, world!”这句话。 - Martín Fixman

5

如果您要定义宏,最好将它们看起来变得非常丑陋。特别是,它们应该全部大写,并具有某种前缀。这是因为C++没有名称空间,也没有与类型系统或重载分辨率的协调。

因此,如果您的宏名为,那么它就不算“过分”了。

如果您想扩展C++的新循环结构,请考虑研究C++0x中的lambda表达式,其中您可以允许:

until([&] { return finished; }, [&] 
{
    // do stuff
});

虽然不完美,但比宏更好。


2
你提议使用lambda表达式来做这件事,今晚我会做噩梦的。 - 6502
1
我感到矛盾:使用lambda表达式既可怕又令人惊叹。尽管如此,我还是喜欢它。 - James McNellis
2
对于像重新实现 while(!b) 这样简单的东西,显然是过度设计了。 - Daniel Earwicker
1
这确实是可怕而令人敬畏的。然而,在函数式编程语言中,这是相当正常的代码(而且非常干净)。定义自己的控制结构的能力非常非常有用。 - ysdx

5

如果你的宏仅在自己的代码库中使用,我认为它们并不特别糟糕。你可能会对这篇文章感兴趣。

话虽如此,在C++中使用你的宏存在一些缺点,例如我们不能这样写:

until (T* p = f(x)) ...
unless (T* p = f(x)) ...

另一方面,我们可以这样写:
while (T* p = f(x)) ...
if (T* p = f(x)) ...

对于 unless,如果我们将其定义为:

#define unless(x) if (x) {} else

那么我们可以写成unless (T *p = f(x)) ...。然而,在这种情况下,我们不能在其后添加else子句。


从只是一个C编码人员的角度来看,这引起了我的注意。你不能在C中做到这一点(问题标记为C和C++)。我几乎不敢相信你可以在C++中做到这一点... 你为什么要这样做呢? - James Morris
@JamesMorris:是的,C++中的宏只有在使用时才会出现这些缺点。谢谢您指出。我已经编辑了答案。 - Ise Wisteria

3

看看 boost foreach 是如何实现的。

该头文件定义了 BOOST_FOREACH(一个带前缀的丑陋宏)。你可以

#define foreach BOOST_FOREACH

为了让代码更加清晰,您应该在 .cpp 文件中使用这个方法。但是不要在 .h 文件中使用,而是使用丑陋的 BOOST_FOREACH。

现在,这里有一组“函数式编程风格”的宏,用于“方便”的 IF THEN ELSE 表达式(因为 ?: 很丑):

#define IF(x) (x) ?
#define ELSE :

现在

int x = IF(y==0) 1
        ELSE IF(y<0) 2*y
        ELSE 3*y;

转化为desugar:

int x = (y==0) ? 1 : (y<0) ? 2*y : 3*y;

2

好的语法糖示例(在我看来):

struct Foo {
    void bar() {}
};
typedef std::vector<Foo*> FooVector;
typedef boost::ptr_vector<Foo> FooPtrVector;

FooVector v1;
for (FooVector::iterator it = v1.begin(); it != v1.end(); ++it)
    (*it)->bar(); // ugly

FooPtrVector v2;
for (FooPtrVector::iterator it = v2.begin(); it != v2.end(); ++it)
    it->bar(); // nice

1

正如人们所说,添加这些单词并没有真正提供有用的语法糖,因为读取 while(或 if (!)的成本很小,所有 C 开发人员都习惯了,并且使用这样的宏会使大多数 C 开发人员感到恐慌。此外,让一种语言看起来像另一种语言并不是一个好主意。

但是,语法糖很重要。正如已经说明的那样,在 C++ 中,boost 通过模板添加了很多语法糖,而 stl 也提供了一些语法糖(例如,std::make_pair(a, b)std::pair<decltype(a), decltype(b)>(a, b) 的语法糖)。

随着语言的改进,开发人员的可读性、可写性和效率都会得到提高,包括功能和语法糖。例如,在 C++11 规范中,添加了“for (elements in datastructure)”(请参见下文),还添加了“auto”关键字,允许弱类型推断(我说弱是因为您需要在很多地方输入很多类型,而类型实际上是“明显”的和冗余的)。

此外,在Haskell中,如果不使用do符号(语法糖)来使用单子将会非常麻烦,而没有人会使用它们1
一个没有语法糖的例子:
//C++ < 11
std::vector<int> v;
v.push_back(3);
v.push_back(7);
v.push_back(9);
v.push_back(12);
for (std::vector<int>::iterator it = v.begin();
     it != v.end();
     it++)
{
    std::cout << *it << std::endl;
}

而且有语法糖:

//C++ >= 11
std::vector<int> v {3, 7, 9, 12};

for (auto elm : v)
{
    std::cout << elm << std::endl;
}

有点更可读了,不是吗?

一个关于IO Monad的Haskell示例(来自HaskellWiki):

f :: IO String
f =
  ask "What's your name ?" >>= \name ->
  putStrLn "Write something." >>= \_ ->
  getLine >>= \string ->
  putStrLn ("Hello " ++ name ++ " you wrote " ++ string) >>= \_ ->
  return name

g :: IO String    
g = do
  name <- ask "What's your name ?"
  putStrLn "Write something."
  string <- getLine
  putStrLn ("Hello " ++ name ++ " you wrote " ++ string)
  return name

这是一个指向ideone的链接:http://ideone.com/v9BqiZ


1:实际上,这种语言比C++更加灵活,允许创建运算符(例如&^、+.、:+:等),因此我们可以想象,有人会很快再次引入语法糖 :)


-1

你可以这样做,但要确保它不在源文件中。我建议采用CoffeeScript方法来处理JavaScript,而不进行优化生成。

总的来说,你应该编写你的语言,但导出、给出和转换代码时,就像你用C编写一样,具有极高的兼容性。

尝试研究一下awk,并使其在保存时转换所有以.cugar结尾的文件或类似的操作。 :)

祝你好运。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接