意想不到的c++:程序结果的多输出

不修改 main 函数, me 们能多大程度去影响程序的输出结果呢?!或是说,如何才能在不修改 main 的情况下去修改程序的输出结果?面试中可能经常问到的一个问题:下面是输出 hello,world 的程序代码

  1. #include <iostream>
  2. using namespace std;
  3.  
  4. int main()
  5. {
  6.     cout << "hello,world\n" << endl;
  7.     return 0;
  8. }

要求在不改变 main 函数写法的前提下程序结果的输出为:

initialized ...
hello,world
cleaned up ...

眨眼一看,貌似不大可能吧,函数执行从 main 开始,在 main 中结束,肿么可能多输出东西呢? O__O"… 昨天就有人在群里问到了这个问题,后来姿势大涨,就写了这一篇 article。

本问题的解决思路有好几种,至少现在已经有了好几种了。

1. 全局对象的构造在 main 函数之前

一切从 main 开始,这在 c 中是对的,但在 c++ 中却不是。 c++ 中的全局对象的构造会在 main 执行之前, 析构自然在 main 执行之后。知道了这一点很快就可以写出代码来了:

  1. #include <iostream>
  2. using namespace std;
  3.  
  4. class Hello{    // struct Hello
  5. public:
  6.     Hello(){ cout << "initialized ...\n"; }
  7.     ~Hello(){ cout << "cleaned up ...\n"; }
  8. };
  9.  
  10. Hello hello;
  11.  
  12. int main()
  13. {
  14.     cout << "hello,world\n" << endl;
  15.     return 0;
  16. }

如程序中注释所说, struct 也是可以的 ! 因为 c++ 中的 struct 就是 class !struct 是成员默认是 public 的 class, 而 class 是成员默认是 private 的 struct 。不管是使用 class 或是 struct 对于该问题的思路是一样的:利用全局对象在 main 之前构造!

2. 宏重定义

以前 me 只知道上面的方案,后来有人说了宏定义,尼玛 ! 赶脚碉堡了! 在写普通程序的时候宏并不推荐使用,因为宏甚至可以改变 u 看到的程序的面貌, main 中的 int 真的就是 int ? main 难道就真的是 main ?! 宏在程序编译之前进行替换或是展开 (预处理),于是编译时候的程序代码可能和预处理之前的截然不同!比如下面的代码就可以解决上面的问题:

  1. #include <iostream>
  2. using namespace std;
  3.  
  4. #define cout cout << "initialized ...\n"
  5. #define endl "cleaned up ...\n" << endl
  6.  
  7. int main()
  8. {
  9.     cout << "hello,world\n" << endl;    // 宏展开: cout << "initialized ...\n" << "hello,world\n" << "cleaned up ...\n" << endl;
  10.     return 0;
  11. }

可以替换的地方真的 tm 太多了,比如 main :

  1. #include <iostream>
  2. using namespace std;
  3.  
  4. #define main main(){ cout << "initialized ...\nhello,world\ncleaned up...\n" << endl; } int f
  5.  
  6. int main()    // 宏展开: int main(){ cout << "initialized ...\nhello,world\ncleaned up...\n" << endl; } int f()
  7. {
  8.     cout << "hello,world\n" << endl;
  9.     return 0;
  10. }

尼玛,这到底是要干嘛!O__O"…

3. 重定义运算符 <<

上面的宏定义尼玛太 bug 了有没有! 完全跳出了语言的范围!O__O"… c++ 中支持运算符重载,所以 me 琢磨是不是可以重新定义 cout 对 "hello,world\n" 的输出? 后来发现是可以的!代码如下:

  1. #include <iostream>
  2. using namespace std;
  3.  
  4. ostream& operator<<(ostream& out, const char* s)
  5. {
  6.     out << string("initialized ...\n") << string(s) << string("cleaned up...\n");
  7.     return out;
  8. }
  9.  
  10. int main()
  11. {
  12.     cout << "hello,world\n" << endl;
  13.     return 0;
  14. }

"hello,world" 的类型是 const char* ,想必这个其实不用太多说。 运算符可以重载来扩展运算符的使用,但是不能重复定义,也就是说 c++ 中实际上没有针对 const char* 进行 << 的定义。 通常使用的 cout << "hello,world\n" 调用的是 string 的 << 方法(否则的话,上面的程序就产生歧义,发生编译错误)。

至于为什么 operator<< 的返回值类型是引用, 那是为了使得可以链式使用 out 比如 cout << "hello" << ",world" 。

4. 重定义 cout

of course, 在同一个域内一个名字不能定义两次!but, cout 只是 iostream 中定义的一个输出流对象,其组织在 std 命名空间之下,也就是说,只要移除 using namespace std; 那么 cout 就可以不再是 std::cout !看看下面的代码,体会一下:

  1. #include <iostream>
  2. using std::ostream;
  3. using std::endl;
  4.  
  5. class HelloOstream{
  6.     ostream& out_;
  7. public:
  8.     HelloOstream(ostream& out):out_(out){}
  9.    
  10.     HelloOstream& operator<<(const char* s){    // 输出 "hello,world\n" 相关的
  11.         out_ << "initialized ...\n" << s << "cleaned up...\n";
  12.         return *this;
  13.     }
  14.    
  15.     HelloOstream& operator<<(ostream& (*v)(ostream&)){    // 输出 endl 相关的
  16.         v(out_);
  17.         return *this;
  18.     }
  19. };
  20. HelloOstream cout(std::cout);    // std::cout 在 <iostream> 中
  21.  
  22. int main()
  23. {
  24.     cout << "hello,world\n" << endl;
  25.     return 0;
  26. }

直到昨天 me 才知道 endl 其实是一个函数,O__O"… 输出流 ostream 不能拷贝,可以引用,所以 HelloOstream 中的 out_ 只能是 ostream& 而不能是 ostream 。

目前 me 知道的有这四种方法, 很可能还有更多的, 如果有欢迎联系 me ,^_^

Tags: 

Article type: