对于 C++ 这门语言的一点思考


注:本文部分内容可能引起争议,如有错误,敬请指正

第一次正儿八经的学习 C++ 是在 20 年的 3 月,现在已经是 22 年的 12 月了,差不多快三年的时间,中间来回读了几遍的 C++ PrimerEffective 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++ 很多使用不舒服的痛点

  1. 牺牲了自由性,带来了非常强大的编译前运行检查
  2. 使用垃圾回收机制避免了复杂的内存管理
  3. 去除了分号,但同时也在同一行有多个语句时也可以用分号,如:var a=3; a=5
  4. 引入了可选值,当没有结果时返回 nil,而 C++ 通常我们可能会通过返回 -1,nullptr 的方式解决
  5. 去除了繁琐的头文件机制,同一个项目不需要头文件也可直接导入(通过新的访问控制状态 internal 实现),不同项目使用 import 导入,语法上也比 C++ 简单
  6. 方便的范围控制,如 for i in 0...3 代表 [0,3], 0..<3 代表 [0,3)
  7. for (index, num) in nums.enumerated() 能够同时获取索引及相关值
  8. 方便的元组解包,let (a, b) = (2, 3),而 C++ 既存在 pair 又存在 tuple,语法上存在很大冗余,也很丑陋
  9. if 后必须加 {},统一了风格
  10. if,while 后省略了无意义的 (),使得语法更加简洁

C++ 这门语言学的越多,越发现这门语言包袱之重,兼容 C 及 40 年来的各种标准的整改,让这门语言拥有了更多的特性,让其很“自由”,代价就是写起来有各种的实现方法,可能导致各种问题,很多问题难以定位,更别提修改了

但尽管 C++ 有着各种问题,人们却不得不用它,一方面过去的很多项目都是 C++ 开发的,再使用别的语言重新开发代价太大,另一方面 C++ 的性能仍然首屈一指,很多高性能要求领域还是得用

目前看来指望标准组丢弃历史包袱是不现实的,只能等待一门新兴的语言慢慢取代 C++ 在各方面的地位


文章作者: Mou shuai
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Mou shuai !
评论
  目录