论坛上看到的有人提出的关于C++的一些问题,真是细致(下面还有回复说他对C++一知半解的),一直以为自己的C++还是可以的,可是看了之后真是内牛满面,为什么自己从来没有想的这么深入,差距真的有这么大吗?泪奔~,以后再也不敢说自己会C++了。
=== 数据类型 ===
1. 你知道bool类型的变量占一个字节,但是却不知道bool类型在内存里是如何存储的。true是0吗?false是1吗?
当然,你可以说,程序员不应该关心它在内存里如何存储。可是,C++却偏偏允许你使用union类型以及对指针进行类型转换,让你偷看到它的表示。
可是,即使如此,你保证不同的编译器都用同样的方式表示true和false吗?
2. const char*(指向字符常量的指针变量)和char*(指向字符变量的指针变量)是两个不同的类型。如果一个函数接受char*类型参数,那么如果给它传入const char*型参数,编译器会警告。但是这真的有必要吗?如果一个函数只是在某些情况下要修改参数中指针指向的数组呢?或者它还要将参数再传给别的函数呢?
3. 你知道char, short, int, long, long long分别占几个字节吗?你知道42,42L,42LL分别是什么类型吗?它们在32位x86和64位x86_64机器上分别占几个字节吗?你怎么写一个整数常量,保证它是64位的?(这是我的同学遇到的真实问题)
4. int a; a=9; if (a=42); { cout<<"a="<<a<<endl; }为什么总是输出"a=42"?C++明明有bool类型,但是为什么这样的代码居然编译通得过?
=== 运算符 ===
5. 看代码:
1
2
3
|
string place, item; cout<< "There is a " <<item<< " in " <<place<<endl; cout<< "在" <<place<< "里面有一个" <<item<<endl; |
以上代码无法国际化,因为几个字句的顺序依赖于语言的语法。C++不让你写一个串,对所有语言都使用。还是Java的MessageFormat好
1
2
3
4
|
String englishPattern = "There is a {0} in {1}." ; String chinesePattern = "在{1}里面有一个{0}。" ; MessageFormat.format(englishPattern, "Windows" , "IE" ); // There is a IE in Windows MessageFormat.format(chinesePattern, "Windows" , "IE" ); // 在Windows里面有一个IE |
6. 你知道算数运算符+ - * / %以及位运算符<< >> & | ^ ~以及关系运算符== != < > <= >=谁的优先级高吗?
表达式(3+1<<2)的值是几?我猜你会猜错。写个代码试试。
如果我想判断a和b异或的结果是否等于0,这样写(a^b==0)对吗?
7. 笔试的时候经常有人问你“i = i++ + i++ + i++”之后i的值等于几,但是你却没有拒绝这个公司的勇气。
=== 函数 ===
8. 如下代码
1
2
3
4
|
template < class T> void swap(T &a, T &b) { T tmp=a; a=b; b=tmp; } |
这段代码必须放在头文件里,只要一改,所有用到它的代码都要重新编译。而且编译阶段会把每个用到的数据类型编译一份上述代码,如果每个.cpp文件里都用到某个共同类型,那么编译器也会给每个.cpp都编译一份这个函数,链接时还没法优化。
=== 面向对象的编程 ===
9. 如下代码:
1
2
|
string a = "foo" ; string b = a + "bar" + a + "baz" ; |
你说不清楚以上代码究竟创建了多少个string对象。
10. 给你一个指针SomeClass* ptr;,你怎么知道ptr指向的对象是SomeClass呢,还是它的子类呢?或者这么问:给你一个指针,你怎么知道一个指针void *ptr;指向的目标是不是SomeClass呢?只需编程求解“是”还是“不是”。
答案是:SomeClass *ptr2 = dynamic_cast<SomeClass*>(ptr);。如果ptr真的指向SomeClass,那么它返回这个指针本身;如果不是,则返回NULL。
什么?没人告诉过你?可是Java里有“instanceof”这个运算符,专门用来检测对象的类型的。
11. private继承:你的儿子动不了你的东西,但是你的朋友(friend)可以。这是岂有此理?
12. 菱形继承:
class A { int x; };
class B1 : public A;
class B2 : public A;
class C : public B1, public B2;
那么C里面有几份x?
如果我创建一个对象C c;那么c.x是哪个x?
13. 下面的代码为什么能编译通过?明明f的参数类型错了。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
#include <iostream> using namespace std; class Blah { public : Blah( int x) : _x(x) {} void speak() { cout<< "x=" <<_x<<endl; } private : int _x; }; void f(Blah b) { b.speak(); } int main() { f(42); return 0; } |
14. 为什么类的私有成员变量要写在头文件里?那还叫“私有”吗?类的结构变了,即使只改变私有成员变量,所有依赖于这个类的代码都要重新编译。旧的代码如果不重新编译,就不兼容新的。
如果都是我的代码还好,万一我提供的是一个很流行的很常用的库,岂不是我的类的结构都不敢改了?一改,所有的用户都要重新编译?
解法:请使用私有实现模式(private implementation,简称pimpl模式)
15. 深拷贝。你可以把对象赋给另一个变量,可以按值传入一个函数,也可以按值从一个函数返回。但是其中要经历大量的拷贝。
=== 异常处理 ===
16. 怎样写一段代码,使得即使出现异常的时候也会被执行?比如,如果打开了一个文件,如何在异常出现的情况下,先把文件关掉,再把异常抛到块或者函数外面?
答案是把这样的代码写在一个对象的析构函数中,并在栈上分配这个对象。C++中只有try-catch,却没有finally语句。唯一能够在异常抛出的时候执行的代码就是析构函数了。
但是,就算是析构函数,也不一定会执行。C++中,异常一旦抛出,运行环境就会从栈顶到栈底,一个帧一个帧地寻找一个能抓住这个异常的catch语句。可惜,如果这样的catch语句不存在,那么结果是程序立即终止。任何析构函数都不会执行。
17. 编译器处理C和C++混合的代码非常痛苦,因为C的函数根本不知道“异常”这个概念,但是C++编译器必须要处理“一个C++函数调用了一个C函数,这个C函数又调用了一个C++函数……”这样的情况,同时C编译又会允许“一个C函数调用了一个C++函数(用extern "C"提供了接口),这个C++函数又调用了一个C函数……”这样的情况,而C++也必须考虑到这种情况。
=== 编译 ===
18. 编译一个C++程序非常慢,通常花上半个小时编译一个不大的程序都是常有的事。我听说过有一个软件需要没日没夜地编译5天才能编译好。
如果想体验一下的话,就开始学习wxWidget吧。即使是只有一个对话框HelloWorld程序,也要用半分钟编译。
其原因一般是头文件会互相引用,但是因为使用了大量的模板等,每次都要重新处理一堆头文件。而且构建的时候,make程序有时候需要用半分钟的时间才仅仅能知道“哪些文件变了,哪些文件需要重新编译”,更不用说真的编译的时间了。
19. 如果想提高速度,倒是可以考虑“预编译头文件”。VC6.0就支持这个功能。GCC也有。就是把一个头文件作为“预编译头”,先用编译器预处理。编译器会缓存预处理的结果。它对你的代码的要求,就是所有的.cpp文件都必须首先引用这个头文件(否则头文件之间会互相影响)。
但是预编译头文件会生成一个几十MB的缓存。当然,磁盘空间多了,也不用担心它。但是一不小心把它连代码拷贝走了就不好了。就怕你不知道VisualC++生成的xxxxxx.pch是干什么用的。(PCH=Pre-Compiled Header)
这也是为什么不推荐一开始就使用IDE的原因。如果你一开始就用VC,你大概不知道这个stdafx.h为什么这么特殊,没了它编译就不通过;把别的include预编译语句放到#include "stdafx.h"之前,也编译通不过,也不知道它到底是干什么用的,也不知道如何把它从工程里去掉。另一个没有它的工程编译得慢,你却不知道怎么把它加到工程里。估计一般教材不会提它。其实它就是那个“预编译头文件”。
=== 二进制接口 ===
20. C++允许你进行函数名重载。你可以写几十个函数,都叫同一个名字,比如都叫foo。
可是,当你把这几十个foo编译起来,放到一个动态链接库bar.so(或者bar.dll)里,你用另一个程序打开bar.so,想从里面取出一个foo函数来调用。但是……糟糕!这么多函数都叫foo!!!怎么知道我要哪个foo呢??
实际上C++会把名字进行“混淆”(英文叫mangling),也就是把函数改个名字,把参数的类型以及返回值的类型编入函数中。比如GCC会把上述那个void f(Blah)函数命名为“_Z1f4Blah”。
糟糕的是,不同的编译器,混淆的方法不太一样。所以,一个程序用编译器A编译,另一个程序用编译器B编译,那么它们就不能互相调用对方的函数。
想想为什么Qt库的Windows版要分mingw和MSVC(Microsoft Visual C++)两个版本?
21. 不同的编译器对于“异常处理”不太一致。
22. 刚才说过了类的“私有成员”问题,类一改,就都不兼容了,都要重新编译
=== 一些琐事 ===
23. 凡是涉及“模板”的代码,程序编译出错以后,输出的信息都非常难以看懂。
=== 缺失的特性 ===
有些东西本来应该写入编程语言中,因为其实现高度依赖编译器,但这些却不是C++的一部分,比如:
- coroutine:类似很轻很轻的线程。它们不能同时执行。一个coroutine必须暂停自己,把控制权交给另一个coroutine,另一个适当的时候跳回来。很有用的结构,适合于“生产者-消费者”模型,也适合于大规模的并行处理,也可以简化很多算法(比如二叉树遍历)。可惜能实现这个的只是一篇论文介绍的技术,今年(2013)才发表:http://ulir.ul.ie/handle/10344/2927
- 垃圾回收:正式的名字是“自动内存管理”。用于防止“无用单元”(分配出去了内存,但所有指向它的指针都不见了,再也无法访问它)和“悬垂引用”(一个指针,原本指向一个有效的对象,但这个对象的内存被回收了,这个指针变成了一个无效的指针)。Boost库中有“智能指针”(smart pointer),但那是一个极其朴素的引用计数实现,一旦产生循环引用就完蛋了。而且,Boost里有好几种不同的“智能指针”,很难判断到底应该用哪一种指针。此外还有基于tracing(非引用技术)的Boehm GC,但那是一个保守的垃圾回收,它不能识别所有的指针(因为内存里都是数字,不知道哪个变量是值,哪个是指针),所以也不能回收所有的垃圾。另外,程序员都假设对象一旦分配了是不会移动的,所以也不能用“移动式的垃圾回收”,难以避免内存碎片的产生。(lighttpd啊,你死得好惨啊!!!!你说不是你的错,是malloc的错,说malloc不整理内存碎片,明明有很多空闲内存,就是分配不出来啊!有木有!!可你怎么不说你是用C写的啊!!!C程序员有责任管理内存啊!!!!有木有!!!有木有啊!!!!内存木有了你找谁去喊冤啊!!!!!!!)
--
后面的回复:
1.语言是对机器的抽象,类型就是抽象方法的一种,抽象的目的就是为了隐藏下层细节,后面好几个point都是这个问题。你真的需要知道bool的内存表示么。拿python来说,你不需要一个数字背后的内存表示是32位整型还是一个高精度数,这对你是透明的。你能在java里得到一个变量的内存地址么?不过,如果你想写编译器,那么ISO C++ 98标准4.7节明确的写了"If the source type is bool, the value false is converted to zero and the value true is converted to one."。
2.const是一个接口约定,你说自己是const,那么就代表任何情况下都不会修改引用目标。c++ const的问题几乎和java的checked exception一样,一定程度上保证了程序的安全性和正确性,但是带来了接口的复杂。一种手段解决了某些问题,同时肯定会带来新的问题。
3.这个问题和问题1一样,需要补充的是,在一些加密或者网络传输的长期下,需要用到指定长度的整型类型以优化性能,c99加入了Standard Integer Types,提供了int8_t,uint8_t,int16_t,uint16_t,int32_t,uint32_t,int64_t,uint64_t。
4.c++的bool类型是一种整型,任何整型可以隐式转换成bool,这当然不一定是好的feature,这么做主要是为了保持对c的源代码兼容性。
5.messageformat这条完全没喷到点子上,java的设计和sprintf几乎一样,就c++算没有等价面向对象的实现,在类库层面实现一个也不困难。java真正的优势在于标准字符串是unicode编码,而c/c++是ansi。
6~9完全是扯犊子了
7. 从我毕业那年开始我从没见过这样的笔试题。如果真有类似的问题,他想要的绝对不是一个编译器实现决定的准确数值,而是题目里的两个基础知识点:符号贪婪匹配和表达式求值顺序。我把题目改成int n=0; n=n++;你能回答上来么。
后面关于头文件、二进制兼容性的几点是说到点子上了。c++很多设计上缺陷的根源都来自一点——保持对c的代码兼容性。这个特性语言发展的初期,吸引了大量的开发者,但是这个沉重的历史包袱随着语言的发展越来越凸显。
语言是对机器的抽象,使得某些复杂的机器特性对使用者透明;另一方面也是对问题的抽象,将现实世界的问题抽象成类型系统、控制流。 另外一点是,透明不是绝对的,这取决于你关注的方面。
比如gc,无论是用引用计数还是根搜索实现,本质上是对内存模型的抽象,从物理硬件的线性地址模型抽象到完全对使用者透明。在大部分业务开发的代码里,gc以一定的机器性能换取了人的开发效率。所以说白了gc是一种程序时间和人月的trade off,在不同的场景下,根据资源约束条件的不同来取舍。百度、Google的搜索前台server,QQ的通信server,绝对只可能会用c/c++,因为海量请求、低时延的要求下,性能是非常critical的,同理mysql、memcached、redis、mongodb等等关注单机性能的存储组件无一不是用c/c++,他们不会用任何gc。而对于处理业务逻辑的应用服务器,现在已经基本看不到c和c++的影子了。
--
- coroutine:类似很轻很轻的线程。它们不能同时执行。一个coroutine必须暂停自己,把控制权交给另一个coroutine,另一个适当的时候跳回来。很有用的结构,适合于“生产者-消费者”模型,也适合于大规模的并行处理,也可以简化很多算法(比如二叉树遍历)。可惜能实现这个的只是一篇论文介绍的技术,今年(2013)才发表:http://ulir.ul.ie/handle/10344/2927
--协程boost有,而且分为stackless(参考boost::asio)和stackfull(目前比较流行的golang也是这种),看你想怎么用了
- 垃圾回收:正式的名字是“自动内存管理”。用于防止“无用单元”(分配出去了内存,但所有指向它的指针都不见了,再也无法访问它)和“悬垂引用”(一个指针,原本指向一个有效的对象,但这个对象的内存被回收了,这个指针变成了一个无效的指针)。Boost库中有“智能指针”(smart pointer),但那是一个极其朴素的引用计数实现,一旦产生循环引用就完蛋了。而且,Boost里有好几种不同的“智能指针”,很难判断到底应该用哪一种指针。此外还有基于tracing(非引用技术)的Boehm GC,但那是一个保守的垃圾回收,它不能识别所有的指针(因为内存里都是数字,不知道哪个变量是值,哪个是指针),所以也不能回收所有的垃圾。另外,程序员都假设对象一旦分配了是不会移动的,所以也不能用“移动式的垃圾回收”,难以避免内存碎片的产生。(lighttpd啊,你死得好惨啊!!!!你说不是你的错,是malloc的错,说malloc不整理内存碎片,明明有很多空闲内存,就是分配不出来啊!有木有!!可你怎么不说你是用C写的啊!!!C程序员有责任管理内存啊!!!!有木有!!!有木有啊!!!!内存木有了你找谁去喊冤啊!!!!!!!)
--我的理解GC仅仅是内存回收的一种捷径,c++里面包括很多资源类型,比如文件描述符,锁之类的,而且java貌似只是从一定程度上解决了内存问题,但是引用计数这种辅助的东西,对于资源管理还是不能不用。
http://microcai.org/2013/07/27/gc-is-wrong-way-of-doing-memory-managment.html,我觉得这个比较中肯
——
http://microcai.org/2013/07/27/gc-is-wrong-way-of-doing-memory-managment.html,我觉得这个比较中肯
RAII确实是C++的风格。是处理资源的合理方法。
不过,除了内存以外的其它资源的管理,如文件、网络连接、锁等,确实不应该靠垃圾回收来管理。垃圾回收也不是为管理这些资源设计的。(注意“引用计数”只是一种垃圾回收的方法,更多的灵活的方法是“跟踪”:从“根对象”扫描整个堆,并扔掉没有触及的对象)
对于Java来说,等效于RAII的是try-finally,而不是垃圾回收。资源在try中获取,而finally保证try执行之后一定会执行(不管是正确还是错误的情况)。finally和C++中栈上对象的析构函数是类似的,但即使没有任何catch能抓住异常,它也会执行。
Python 2.5中增加了with语句。在with块结束的时候,它绑定的资源一定会被处理。如:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
# 旧写法(危险,但很流行)。依赖于引用计数。 # 如果Python的实现不用引用计数(Jython、PyPy等)就会造成资源泄漏。 txt = open ( "somefile.txt" ).read() # 旧写法(安全),保证f会关掉 f = open ( "somefile.txt" ) try : txt = f.read() finally : f.close() # 新写法:保证f.__exit__()一定会执行(它会调用f.close())。 with open ( "somefile.txt" ) as f: txt = f.read() |
Java 1.7中增加了try-with-resource结构,更加强化了这种“将资源绑定在静态的作用域上”的概念(C++也是用栈上对象的作用域与资源绑定,这一点是相通的)。
1
2
3
4
5
6
7
8
9
10
11
12
|
// 旧写法 BufferedReader br = new BufferedReader( new FileReader(path)); try { return br.readLine(); } finally { if (br != null ) br.close(); } // 新写法 try (BufferedReader br = new BufferedReader( new FileReader(path))) { return br.readLine(); } |
Ruby本身对块支持得很好。一般这种资源可以用函数将其限制在传入的回调块中。
1
2
3
|
File : :open ( "somefile.txt" ) do |f| puts f.read end |
甚至对锁也可以这样做。Ruby文档里的例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
require 'thread' semaphore = Mutex. new a = Thread . new { semaphore.synchronize { # access shared resource } } b = Thread . new { semaphore.synchronize { # access shared resource } } |
Scala有scala-arm库做自动资源管理。实际上只是用Ruby的风格封装了一下Java的文件等资源。https://github.com/jsuereth/scala-arm
但不得不说,老Python风格的用垃圾回收来自动关闭文件,是不正确的。(我猜想microcai反对的也正是这种用法,而不是垃圾回收本身)起码这种做法将语言限定在“非延迟的引用计数”这种垃圾回收策略上,而这种策略的性能很差。PHP也是因为很多程序依赖这种策略,以至于使用了其它垃圾回收方法就会导致程序不正确。Facebook的HipHop虚拟机(高性能的PHP虚拟机)实现起来很吃力,也是因为这个。
当然,可以将内存也作为“资源”的一种,用同样的方法,通过将动态的资源绑定到静态的资源上,以保证安全地分配和回收。但是,我个人认为内存具有自己的特殊性,所以适合使用垃圾回收,而不是手动地管理。
- 内存的“回收”并没有时效性。如果文件不及时关闭,磁盘上的文件可能是不完整的,其他进程可能看不到已经写入的数据。锁不及时释放,会阻塞其它线程,甚至造成死锁。网络连接如果不及时关闭,对端会认为你没有传输完。但是内存如果不及时回收,最严重的后果不过是一个进程暂时占用了多于实际需要的内存,垃圾回收过后,内存就会恢复合理的占用量。实际上,“回收”这个动作本身是要花时间的。如果一旦内存不用就必须立即回收,还要挤占程序执行的时间。
- 在一个对象被复杂地共享的环境中,很难确定对象生存期由哪个对象维护。如果一个对象产生以后,传递给了其它模块,而且是多个模块,那么任何单个模块都无法决定这个对象是否还要保留。
- 将内存管理的负担交给程序员,轻则加重程序员的负担,重则增加受打击面,容易引发更多的问题。在构造复杂的数据结构的情况下,如果内存管理必须由程序员显式地进行,那么数据结构也会变得复杂。
例如:如在用引用计数和智能指针实现环形链表的时候,必须让正向引用使用强引用,而反向引用使用弱引用,以避免环形引用使得内存无法释放。这样,程序员不但要考虑链表的结构正确性,还要考虑引用类型的正确性。而且必须考虑如果去掉其中一个环节就会导致下一个环节的强引用计数变为0,然后导致之后一系列的对象要自动析构。尽管增加了复杂度,可是弱引用却并不是为这种情形设计的:弱引用的代价比强引用更大。
总结一下,垃圾回收(正如它正式的名字叫“自动内存管理”一样)是适合内存管理的机制,但并不适合管理所有的资源。很多支持垃圾回收的语言都有专门的机制(try-finally或者with结构)来实现更好的资源管理。
--
这是传说中的丧心病狂嘛~
首先C++的原始设计方案就对于底层开发来说就是糟糕的~这也就导致了Bjarne Stroustrup等人在后续的更新设计上变得越来越保守和没有自信。
从根本性上来说,C++ 0x的gc设计还是语法糖。需要回收内存时,以扫描大面积内存的时间开销代价来保证内存管理的尽可能有效!
这种设计至少对于底层开发者来说是不能妥协的!而如果用于应用层开发,那为什么不选择一种垃圾回收更为优美、更容易书写的语言呢?(比如Java、C#...虽然它们的问题也是一大坨)
这就是一个Check&Balance的过程了,也是C++标准制定者们头疼到死的课题。
语言好坏的讨论,总是停留在语言特性的层面上,剩下的看客抛出一句「各有各的用途」,实在是太没营养了。
初学者刚一门语言的时候,往往注意力都集中在语法特性上,因为目标是写出能运行的代码。实际上细节的语法特性只是语言设计哲学的体现。现实世界里,选择编程语言时语法特性是一个非常次要的因素。
大家喜闻乐见的cpp和java,它们最主要的区别是什么,是GC?多重继承?bool类型?标准库?编译?异常处理?根本都不在点子上,他们最核心的区别是,Java跑在JVM这个虚拟机上,而cpp只依赖一个运行时,JVM这一层抽象是二者各种差别的关键。另一个例子是perl和python,python的哲学是"There should be one -- and preferably only one --- obvious way to do it", 而perl的则是"There's more than one way to do it."。换个角度,尝试从语言的设计思路和哲学上来比较分析不同的语言,不要总纠结在语言特性上。
社区、历史、演进和外围工具也是其他几个可以关注的方面。
--
: 有几条确实是比较无厘头,我认为你是认真,所以我解释下。
: 1.语言是对机器的抽象,类型就是抽象方法的一种,抽象的目的就是为了隐藏下层细节,后面好几个point都是这个问题。你真的需要知道bool的内存表示么。拿python来说,你不需要一个数字背后的内存表示是32位整型还是一个高精度数,这对你是透明的。你能在java里得到一个变量的内存地址么?不过,如果你想写编译器,那么ISO C++ 98标准4.7节明确的写了"If the source type is bool, the value false is converted to zero and the value true is converted to one."。
嗯
: 2.const是一个接口约定,你说自己是const,那么就代表任何情况下都不会修改引用目标。c++ const的问题几乎和java的checked exception一样,一定程度上保证了程序的安全性和正确性,但是带来了接口的复杂。一种手段解决了某些问题,同时肯定会带来新的问题。
说的在理。确实觉得和Checked Exception有同样的问题。
: 3.这个问题和问题1一样,需要补充的是,在一些加密或者网络传输的长期下,需要用到指定长度的整型类型以优化性能,c99加入了Standard Integer Types,提供了int8_t,uint8_t,int16_t,uint16_t,int32_t,uint32_t,int64_t,uint64_t。
关键的是“常量”,也就是42, 42L, 42LL之间的区别。这些类型虽然规定了定长的数据类型,但是没有定长数据类型的常量。有可能这位同学的这个用途比较特殊,常量的类型也会决定程序的正确性。而且这个bug他调试了一天多才发现是使用了L而不是LL,使得在32位机上是32位,64位机上是64位。
: 5.messageformat这条完全没喷到点子上,java的设计和sprintf几乎一样,就c++算没有等价面向对象的实现,在类库层面实现一个也不困难。java真正的优势在于标准字符串是unicode编码,而c/c++是ansi。
这个吐槽的是iostream库,并不是C++本身。
Qt中的QString是支持按位置替换的。QString("There is a %1 in %0").arg("Windows").arg("IE");
: 6~9完全是扯犊子了
6~7是纯粹黑,8和9是认真的。
8是关于模版。模版确实会造成代码爆炸。
9如果是字符串,还比较轻松;如果是高精度整数,就要小心了。GNU-MP的C++绑定专门为C++优化了,使得类似a=b+c这样的表达式可以用一个加法运算完成。
一旦涉及对象按值传入函数和按值返回,就和第15条一样了。
: int n=0; n=n++;你能回答上来么。
也是未定义行为(一个表达式里两个部分有副作用)。用gcc4.8编译是1,用clang3.3编译是0。
: 后面关于头文件、二进制兼容性的几点是说到点子上了。
嗯。这几个是认真的,尤其是二进制兼容性。
: 百度、Google的搜索前台server,QQ的通信server,绝对只可能会用c/c++,因为海量请求、低时延的要求下,性能是非常critical的…………他们不会用任何gc。
根本的决定因素还是性能。我认为之所以没有选择垃圾回收的语言,根本原因是目前非垃圾回收的语言可以比垃圾回收的语言跑得更快。但是如果在虚拟机上跑,Type Inference和JIT-Compiling可以提供Ahead-of-time Compiling语言(如Fortran/Ada/C/C++/Rust/Go等)无法触及的优化机遇。当然这两个是未来的技术(虽然上个世纪90年代早就有人研究过),也许暂时应用还比较少。
: 同理mysql、memcached、redis、mongodb等等关注单机性能的存储组件无一不是用c/c++,他们不会用任何gc。
举一个反例吧:CouchDB,也是以高性能著称的数据库,和mongodb类似。使用Erlang语言实现的,而Erlang是在BEAM虚拟机上跑的函数式语言,而函数式语言必须使用GC。
--