返回信息流又是cpp vs java 的坑...
【 在 mentalist (超感神探) 的大作中提到: 】
: 是不是那里社会论坛人杂,平均水平不如我们这里啊?
这是一条镜像帖。来源:北邮人论坛 / cpp / #72835同步于 2013/7/26
该镜像源已超过 30 天没有更新,可能在源站已被删除。
CPP机器人发帖
Re: c++到底行不行啊,看水木cpp版都是黑cpp粉java
shenlei
2013/7/26镜像同步102 回复
订阅后,新回复会通过你的通知中心匿名送达。
9 条回复
仅就编程语言来说:如果你仅仅是java给力,其它都是苦手的话,那你和北大青鸟毕业的有什么区别?
【 在 mentalist 的大作中提到: 】
: 那边一边倒啊,是不是因为人杂低端码农多,一致说java好?
:
我也来黑一把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. 看代码:
string place, item;
cout<<"There is a "<<item<<" in "<<place<<endl;
cout<<"在"<<place<<"里面有一个"<<item<<endl;
以上代码无法国际化,因为几个字句的顺序依赖于语言的语法。C++不让你写一个串,对所有语言都使用。还是Java的MessageFormat好
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. 如下代码
template <class T>
void swap(T &a, T &b) {
T tmp=a; a=b; b=tmp;
}
这段代码必须放在头文件里,只要一改,所有用到它的代码都要重新编译。而且编译阶段会把每个用到的数据类型编译一份上述代码,如果每个.cpp文件里都用到某个共同类型,那么编译器也会给每个.cpp都编译一份这个函数,链接时还没法优化。
=== 面向对象的编程 ===
9. 如下代码:
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的参数类型错了。
#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程序员有责任管理内存啊!!!!有木有!!!有木有啊!!!!内存木有了你找谁去喊冤啊!!!!!!!)
为什么放弃治疗。
提高下版面质量吧,别都是这种贴。
【 在 nuanyangyang 的大作中提到: 】
: 我也来黑一把C++。
: 我认为C++不行。
: 理由:
: ...................
0x有gc了。
class ScopeGuard
{
public:
explicit ScopeGuard(std::function<void()> onExitScope)
: onExitScope_(onExitScope), dismissed_(false)
{ }
~ScopeGuard()
{
if(!dismissed_)
{
onExitScope_();
}
}
void Dismiss()
{
dismissed_ = true;
}
private:
std::function<void()> onExitScope_;
bool dismissed_;
private: // noncopyable
ScopeGuard(ScopeGuard const&);
ScopeGuard& operator=(ScopeGuard const&);
};
后面有几条有点道理,但是前面大多数只能看到你对C++的一知半解。
【 在 nuanyangyang 的大作中提到: 】
: 我也来黑一把C++。
: 我认为C++不行。
: 理由:
: ...................
先赞一下楼上列出的问题。对于楼上提的第4点,前几天看到讨论 if( a == 0)或者 if ( 0 == a)这种风格。 if( a=0 )这种问题,确实很头疼。不过,如果编译的时候习惯加入-Wall,编译器会针对 在条件表达式里的赋值发出警告。所以,C++对编程习惯等方面的要求水平还是蛮高的,得自己给自己约束。
c语言不提供面向对象和模板的约束,但大师们常通过函数指针,void指针做一些多态或者泛型的事情,例如redis,linux内核的路由子系统。。
所以,语言本身就是工具,它好不好,决定性的因素是对具体需求满足程度。水木那帮人真够有意思的。。。。。。
【 在 sayaka 的大作中提到: 】
: 为什么放弃治疗。
: 提高下版面质量吧,别都是这种贴。
每一条都涉及一个语言设计或实现的具体问题。我们应该正视这些问题,而不是盲目地宗教式地崇拜某个语言——不得不说C++太流行了,而且以靠近底层的“系统”语言著称(Linus Torvalds不会赞同这个观点的),以至于“我会C++,我很厉害;我只会Java,我不如会C++的人”这样的思想横行。
: 0x有gc了。
C++11需要程序员人工干预,也允许设定GC模式“严格、保守、禁止……”。这样,反而增加了程序实现的复杂度:GC的本意是将“内存管理”这个关注点抽象出来,让程序员可以更加关注逻辑的实现。将内存管理杂糅到程序逻辑中,反而让程序更容易出错。
http://www.stroustrup.com/C++11FAQ.html#gc-abi
: ScopeGuard
这是实现Finalizer的方法。但是,问题是,如果一个异常没有catch,程序会立即终止,即使这个ScopeGuard也不会起作用。这种行为C++语言要求的。
【 在 quan 的大作中提到: 】
: 先赞一下楼上列出的问题。对于楼上提的第4点,前几天看到讨论 if( a == 0)或者 if ( 0 == a)这种风格。 if( a=0 )这种问题,确实很头疼。不过,如果编译的时候习惯加入-Wall,编译器会针对 在条件表达式里的赋值发出警告。所以,C++对编程习惯等方面的要求水平还是蛮高的,得自己给自己约束。
: c语言不提供面向对象和模板的约束,但大师们常通过函数指针,void指针做一些多态或者泛型的事情,例如redis,linux内核的路由子系统。。
: 所以,语言本身就是工具,它好不好,决定性的因素是对具体需求满足程度。水木那帮人真够有意思的。。。。。。
嗯。试了一下
#include<iostream>
using namespace std;
int main() {
int a;
a=9;
if (a=42);
{
cout<<"a="<<a<<endl;
}
return 0;
}
用clang++编译,默认情况下会警告if语句中的赋值表达式。还会警告用空语句作为if的分支。
$ clang++ -o warnings warnings.cpp
warnings.cpp:8:10: warning: using the result of an assignment as a condition without parentheses [-Wparentheses]
if (a=42);
~^~~
warnings.cpp:8:10: note: place parentheses around the assignment to silence this warning
if (a=42);
^
( )
warnings.cpp:8:10: note: use '==' to turn this assignment into an equality comparison
if (a=42);
^
==
warnings.cpp:8:14: warning: if statement has empty body [-Wempty-body]
if (a=42);
^
warnings.cpp:8:14: note: put the semicolon on a separate line to silence this warning
2 warnings generated.
g++默认不会警告,在-Wall的时候会警告赋值,但不会警告那个分号。
g++ -Wall -o warnings warnings.cpp
warnings.cpp: In function ‘int main()’:
warnings.cpp:8:13: warning: suggest parentheses around assignment used as truth value [-Wparentheses]
if (a=42);
^
看上去现代的编译器还蛮智能的。
p.s. 水木那边,有链接吗?没找到。