注:本文部分内容可能引起争议,如有错误,敬请指正
第一次正儿八经的学习 C++ 是在 20 年的 3 月,现在已经是 22 年的 12 月了,差不多快三年的时间,中间来回读了几遍的 C++ Primer 及 Effective C++,Leetcode 上用 C++ 写的题目也有 500 多道,回想过去的时光,不敢说是精通 C++,但也算是有所熟悉,对于 C++ 这门语言也有不少牢骚,干脆就记录一下
我帮各位回顾一下 C++ 的发展历程
C++ 是由 Bjarne Stroustrup 于 1979 年在贝尔实验室开发的一种编程语言。当时,Stroustrup 正在研究一种名为 Simula 67 的面向对象编程语言。他发现 Simula 67 虽然优秀,但由于它的运行速度较慢,并不适合于开发大型系统。因此,他开始着手设计一种新的编程语言,该语言能够兼具 Simula 67 的面向对象特性和 C 语言的运行速度。
C++ 的最初版本被称为 “C with Classes”。它于 1983 年正式推出,并在接下来的几年里经历了许多修改和更新。1985 年,C++ 正式更名为 C++,并于 1989 年推出了最终版本——C++ 2.0。
C++ 在推出后迅速流行起来,并成为许多大型系统的首选开发语言。它的优秀性能和灵活的语法使它成为许多领域的标准编程语言,例如游戏开发、操作系统开发、网络编程等。
随着时间的推移,C++ 也不断演进和发展。例如,1998 年推出了 C++98 标准,2003 年推出了 C++03 标准,2011 年推出了 C++11 标准,并在接下来的几年中又推出了 C++14 和 C++17 等
详细可见 wiki
可以说已经有了 40 多年的历史了,同一个历史舞台的很多语言都没落了,但 C++ 仍然流行,在浏览器,游戏,操作系统等对性能要求较高的领域,都能看见它的身影,这当然要得益于其与时俱进的标准,能够吸收很多优秀的特性,如:模板,异常处理,自动类型推导,还引入了方便的 STL 库,可谓是算法党的福音
C++ 又可以认为是 C 语言的超集,除了某些方面,如:
最常见的差异之一是,C 允许从 void* 隐式转换到其它的指标类型,但 C++ 不允许。
C++ 的效率上差约 5% 多一些
但是 40 多年的历史新特性不断增增改改,再加上其继承于 C 语言又拥有 C 语言的很多特性,在拥有很强兼容性的同时,又让 C++ 变成了一个怪物语言
参差不齐的输出方式
// C
printf("Hello World!\n");
puts("Hello World!");
// C++
cout << "Hello World!\n";
cout << "Hello World!" << endl; // 清空缓冲区
C++ 拥有复杂的输出方式,甚至不同的输出方式混用也会出现问题
熟悉算法竞赛的一定知道
ios_base::sync_with_stdio(false); cin.tie(NULL);
第一个语句将 stdio 与 iostream 的缓冲区同步设置为关,默认是开的
如果为开,C 风格的 IO 和 C++ 风格的 IO 就可以一起用了,但是缺点在与 C++ 风格的 IO 会变得慢
所以我们设置为 false
,这样能够加快 C++ 风格 IO
的速度
缺点在于混用 stdio 和 iostream IO 的先后顺序没有同步缓冲区的保护,可能出现异常
所以如果你只使用 C++ 的 IO,建议使用
ios_base::sync_with_stdio(false);
来加快程序运行的速度
cin.tie(NULL);
则将 cin 与 cout 解耦,通常 cin
前都要刷新缓冲区,即 cout << flush;
,导致效率很低
当然解耦后,如
cout << "input your name: ";
string name;
cin >> name;
在屏幕上可能不会正常工作,可能先输入才有输出。
不过在算法竞赛中,检验结果看的是 stdout 这个文件描述符
说起 IO,就不得不吐槽下 C++ 反人类的 cout,格式化字符串直到 C++20 才得到解决,但这个标准还不知道要多少年才能成为主流
e.g.
int a, b;
scanf("%d %d", %a, %b); // hard
cin >> a >> b; // easy
printf("result: %d+%d=%d\n", a, b, a+b); // easy
cout << "result: " << a << "+" << b << "=" << a+b << "\n"; // hard
cout << format("result: {}+{}={}\n", a, b, a+b);
即便有了 format,相比较其他语言依然不够方便
a, b= 3, 5
print(f'result: {a}+{b}={a+b}')
let a = 3, b = 5
print("result: \(a)+\(b)=\(a+b)")
在其他设计方面,如非要在每句结尾加
;
,本来是设计上就可以避免的事
C++ 还有很多问题是由于太自由引起的,它给予程序员的自由度太高了,不同水准的程序员写出的代码质量天差地别,而其他语言,如 Java 就没有这种问题
最典型的例子,莫过于指针,C++ 把动态内存分配的权利交给了程序员,这让我们能够更好的利用内存,但给程序员带来了很大的挑战,一不小心就会导致程序崩溃,而在指针中我们总会遇到各种各样的问题
以下的各种指针你能认识哪些
int *p1; // 是一个指向整型的指针。它可以指向任何整型变量。
const int *p2; // 是一个指向常整型的指针。它可以指向任何常整型变量,但不能通过这个指针去更改它所指向的变量。
int const *p3; // 是一个指向常整型的指针。这与 const int *p2 是等价的,它可以指向任何常整型变量,但不能通过这个指针去更改它所指向的变量。
int * const p4; // 是一个常指针。它指向一个整型变量,并且不能更改它所指向的变量。
const int * const p5; // 是一个常指针,指向一个常整型变量。它不能更改它所指向的变量。
int *p6[10]; // 是一个整型指针数组。它有 10 个元素,每个元素都是一个指向整型的指针。
int (*p7)[10]; // 是一个指向包含 10 个整型元素的数组的指针。
void (*func)(int *, double *); // 是一个指向函数的指针。这个函数接受两个参数:一个指向整型的指针和一个指向浮点型的指针。它没有返回值,因为它的返回值类型是 void。
当然 C++ 的引用也能够缓解指针存在的问题,但无疑增加了学习成本
而很多语言都存在的垃圾回收机制,C++ 直到 C++11 才引入了智能指针的库(auto_ptr 那种不算),库出的太晚,且并没有作为特性而存在
并且支持 new 和 make_shared 两种方法赋值,只推荐后一种
C++ 为了极致的性能,在 C++11 又提出了移动语义这种复杂的概念
再深入还有元编程这座大山,C++ 论复杂说第二,真没有语言敢称第一
再谈面向对象的方面,C++ 有着复杂的多继承
对比之下,Swift 使用单继承,但可以遵循多个协议
若要细细对比 C++ 和 Swift,可以看出 Swift 作为一个新兴的语言修改了 C++ 很多使用不舒服的痛点
- 牺牲了自由性,带来了非常强大的编译前运行检查
- 使用垃圾回收机制避免了复杂的内存管理
- 去除了分号,但同时也在同一行有多个语句时也可以用分号,如:
var a=3; a=5
- 引入了可选值,当没有结果时返回 nil,而 C++ 通常我们可能会通过返回 -1,nullptr 的方式解决
- 去除了繁琐的头文件机制,同一个项目不需要头文件也可直接导入(通过新的访问控制状态 internal 实现),不同项目使用 import 导入,语法上也比 C++ 简单
- 方便的范围控制,如
for i in 0...3
代表 [0,3],0..<3
代表 [0,3) for (index, num) in nums.enumerated()
能够同时获取索引及相关值- 方便的元组解包,
let (a, b) = (2, 3)
,而 C++ 既存在 pair 又存在 tuple,语法上存在很大冗余,也很丑陋 - if 后必须加 {},统一了风格
- if,while 后省略了无意义的 (),使得语法更加简洁
C++ 这门语言学的越多,越发现这门语言包袱之重,兼容 C 及 40 年来的各种标准的整改,让这门语言拥有了更多的特性,让其很“自由”,代价就是写起来有各种的实现方法,可能导致各种问题,很多问题难以定位,更别提修改了
但尽管 C++ 有着各种问题,人们却不得不用它,一方面过去的很多项目都是 C++ 开发的,再使用别的语言重新开发代价太大,另一方面 C++ 的性能仍然首屈一指,很多高性能要求领域还是得用
目前看来指望标准组丢弃历史包袱是不现实的,只能等待一门新兴的语言慢慢取代 C++ 在各方面的地位