一个看起来很奇怪但确实没毛病的东西

98 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第2天,点击查看活动详情

开幕雷击

先来看一段代码!

#include <bits/stdc++.h>
int main() {
  std::string res;
  for (int i = 0; i < 10; i++)
    res = ({
      if (i % 2)
        res = "odd";
      else
        res = "even";
      res;
    }),
    ({
      std::cout << res << " ";
      res;
    });
  return 0;
}

上述代码输出什么呢?

它会输出如下东西

even odd even odd even odd even odd even odd 

逗号表达式

逗号表达式应该都挺熟悉的

我们可以写出这样不是很好看的东西

int main() {
  int a = (1, 2, 3, 4);
}

虽然有病,但是没毛病 QAQ

那么,逗号表达式嵌入逗号表达式也是理所应当的

你还可以用它来做一个 swap 函数

#include <bits/stdc++.h>
#define SWAP(x, y)                                                             \
  {                                                                            \
    typeof(x) t;                                                               \
    (t = x, (x = y, 0), (y = t, 0), 0);                                        \
  }
int main() {
  int x = 1, y = 2;
  SWAP(x, y);
  std::cout << x << " " << y << std::endl;
  std::string a = "11", b = "22";
  SWAP(a, b);
  std::cout << a << " " << b << std::endl;
  return 0;
}

嗯,看起来还不错,我们这个函数居然还可以交换 stl中的string的值,就是拷贝有点严重!

目前看起来一切都还在正常范围内

对于这个表达式 (t = x, (x = y, 0), (y = t, 0), 0); 在末尾放在0,可以放置可以不放置,但是我就想让末尾为0,这样,整个表达式的值一定为0。

逗号表达式嘛

复合语句

什么是复合语句呢?

就是用{}包起来的语句,我们叫他复合语句,因为可以执行多条语句序列嘛!

那么,从语句的角度来看待,这个玩意儿也应该是一个语句啊!

是语句就可以分为两类:

有值(表达式...),无值(if, for, while)

我们可以在局部写一个复合语句,当作一个语句看待,内部有他自己的作用域,这个是清楚的

那么,我们可以把他当作一个有值的语句,那么是不是可以嵌套到逗号表达式中呢?

怪起来了!

但是注意一点,我们要把这个表达式看作一个值,总得看起来不一样一点嘛

int main() {
  ({
    std::cout << "hello"
                 "world"
              << std::endl;
    0;
  });
  return 0;
}

那么,我们是不是可以和我们的逗号表达式配合起来用一用呢?

int main() {
  int x = 1;
  (({ x = 2; }), ({ x = 3; }), ({ x = 4; }), 0);
  return 0;
}

这个确实是可行的

只要有值,就可以成为逗号表达式中的一项嘛!

我嵌套一个复合语句进去,有问题吗?

现在就大抵可以回答开头的内容了

应用

那我们来看看这个玩意有什么用途呢?

宏函数

#include "assert.h"
#include <bits/stdc++.h>
#define address_of(arg)                                                        \
  ({                                                                           \
    assert((&(arg)));                                                          \
    &(arg);                                                                    \
  })
struct E {
  int a, b;
  void f() { std::cout << " haha" << std::endl; }
};
void fun(E *q) { q->f(); }
int main() {
  E a;
  E *b = 0;
  fun(b);
  fun(address_of(a));
  return 0;
}

比如要实现一个函数,判断是不是空,不是空就返回自己的指针

很多时候,一个你可以内联这样干,也可以直接用宏来实现,就没有出栈和入栈的消耗

并且,也可以实现相同的功能

std::cout << address_of(a)->a;
std::cout << address_of(a)->b;

当然,这个并不是我的臆想,而是一个有用的东西

比如Linux内核中的 container_of

#define offsetof(TYPE, MEMBER) ((size_t) & ((TYPE *)0)->MEMBER)
#define container_of(ptr, type, member)                                        \
  ({                                                                           \
    const typeof(((type *)0)->member) *__mptr = (ptr);                         \
    (type *)((char *)__mptr - offsetof(type, member));                         \
  })

就利用了宏函数的特性,使得可以返回任意值,返回什么值看你最后一行的类型

而类型则用typeof推导

我们就可以使用 成员中任意一个成员的指针找到整体结构的指针

(在c++中不一定适用)(看container_of的实现吧,typeof本来好像就是gcc的拓展,其他编译器同意与否不可预知)
当然,既然c++是兼容c的,那么应该没问题,实在不行把typeof换成decltype搞

struct E {
  int a;
  int b;
  int c;
};

int main() {
  E a{1, 2, 3};
  std::cout << container_of(&a.b, E, b)->a;
  return 0;
}