如果您不熟悉这两种语言,您可能会听到人们说C++是C的超集。如果您在这两种语言中都有经验,您就会知道这一点根本不正确。
当然,C++有许多C不具备的特性,但也有一些只有C才具备的特性。而且,也许最重要的是,有用两种语言编译的代码,但是它们可以执行不同的任务。
关于这两种语言之间的差异,有很多信息可供使用,但很多信息似乎是分散的。我想尝试为经常被忽视的细节创建一个简明的指南,并从语言标准中摘录来支持这些细节。
![](https://upload-images.jianshu.io/upload_images/24563956-063eb0224ab628ed.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
注意事项:
这主要是针对那些至少熟悉C或C++的人。
当我提到 C ++ 时,我指的是 C ++ 11 以上的版本,尽管本文大部分都适用于 C++ 早期的标准。 我也将引用 C ++ 17 标准 (目前 C++ 的最新标准)。
当我提到 C 时,我指的是 C99 标准,同时我也将参考 C11 标准 (目前 C 的最新标准)。
值得注意的是,许多编译器不完全兼容编程语言标准。这正是难以确定什么是标准,什么是不合规以及什么是实现定义的部分原因。如果你想要查看其他编译器的示例,我建议使用 编译器资源管理器亲自动手实践一番,对比很有趣。
同样的代码,用两种语言编译,但结果不同
这是我认为最重要的分歧类别。并不是所有C和C++看起来共享的东西都像它看起来的那样。
常量
什么是常量的表达?
关键词constC++中的语义含义与C中的语义不同,但它比我第一次写这篇博客文章时想象的要微妙得多。
差异归结为每种语言所允许的常数表达式。可以在编译时计算常量表达式。例如,需要对静态数组的大小进行编译时评估,如下面的示例所示,它将在C++中编译,但它是否在C中编译将被定义为实现:
1 const size_t buffer_size = 5;
2 int buffer[buffer_size];
3
4 // int main() {
5 // ...
6 // }
但是常量表达式在 C 中的表现如何呢?
在这里,我们引用 C11 标准的几个部分以阐述为什么如此实现,C11 6.6 第 6 段定义了一个整数常量表达式:
整数常量表达式应具有整数类型,并且只能具有整数常量的操作数、枚举常量、字符常量,结果为整数常量的 sizeof 表达式,以及作为强制转换的直接操作数的浮点常量。 整数常量表达式中的转换运算符只能将算术类型转换为整数类型,除非作为 sizeof 运算符的操作数的一部分。
但什么是“整数常数”? 从 6.4.4 开始,这些是字面值,而不是变量,例如 1。
这归结为只有像 1 或 5 + 7 这样的表达式可以是 C 中的常量表达式。变量不能是常量表达式。 正如我所料,此示例在 gcc 编译编译不通过,但它确实可以在 Clang 编译通过:为什么?
答案见 C11 6.6 第 10 段:
一种实现可以接受其他形式的常量表达式。
所以在 C 中,如果要编写可移植版本代码,上面的代码必须使用宏预处理器:
1 #define BUFFER_SIZE (5)
2 int buffer[BUFFER_SIZE];
关键词const就是为了这个目的由Bjarne Stroustrop创建的:减少对宏的需求。C++对于什么可以是常量表达式更宽容,这使得const变量更强大。
我很惊讶地得知const起源于后来成为C++的东西,然后被C所采纳。const来自C,C++采用了相同的概念,并对其进行了扩展,以减少对宏的需求。我知道C支持宏,但是故意降低const当标准化C。
![](https://upload-images.jianshu.io/upload_images/24563956-e885a41fa8a828d9.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
联动
另一个区别是文件范围const默认情况下,变量在C++中具有内部链接。这是为了让你const在头中声明,而不存在多个定义错误。4
修改Const变量
在C中,以下代码违反了约束:
1 constintfoo =1;
2 int* bar = &foo;
3 *bar =2;
C11 6.5.16.1第1款列出了一些约束,其中之一必须是有效的转让。我们的例子的相关限制是:
左操作数具有原子、限定或非限定指针类型,并且(考虑到左操作数在lvalue转换后将具有的类型)两个操作数都是指向兼容类型的限定或非限定版本的指针,以及左指向的类型具有由右指向的类型的所有限定符。
为了符合要求,如果存在约束违规,编译器必须进行诊断,这可能是警告或错误。 我发现它通常是一个警告,这意味着它通常可以在 C 中编译,但运行后会给出未定义的结果:
上述代码,在C ++ 中不会编译。 我认为这是因为const T 是与T 不同的类型,并且不允许隐式转换。 而在C 中,const 只是一个限定符。
C++17 6.7.3:
类型的cv限定或cv非限定版本是不同的类型。
没有参数的函数声明
1 intfunc();
在C++中,这声明了一个不带参数的函数。但是在C中,这声明了一个函数,它可以接受任意类型的任意数量的参数。
来自C11标准6.7.6.3第10和14段:
虚空类型的未命名参数作为列表中唯一的项的特例指定该函数没有参数。
作为函数定义的一部分的函数声明器中的空列表指定该函数没有参数。函数声明器中不属于该函数定义的一部分的空列表指定不提供有关参数的数量或类型的信息。
![](https://upload-images.jianshu.io/upload_images/24563956-edc99df70983356d.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
因此,以下是合法的C:
![](https://upload-images.jianshu.io/upload_images/24563956-e162df8d253f5d3f.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
不过,同样代码将导致 C ++ 中的编译器报错:
![](https://upload-images.jianshu.io/upload_images/24563956-b0233992a36a92a2.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
名称解析
有一些常见的实现细节,使我们可以进一步阐明这一点。 假如我在 Linux 机器上使用 Clang 编译器,则以下代码可以在 C 下编译和链接:
![](https://upload-images.jianshu.io/upload_images/24563956-bef0320325340719.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
但是上述代码却不能在 C ++ 中编译通过。
因为,C ++ 编译器通常使用名称来进行函数重载。它们“破坏”函数的名称以便对它们的参数进行编码,例如:通过将参数类型附加到函数中。通常,C 编译器只将函数名称存储为符号。我们可以通过反编译 C 和 C ++,来比较 func.o 的符号表看看这些区别。
C 编译的 func.o 解析如下:
![](https://upload-images.jianshu.io/upload_images/24563956-fc243cbf03cbb51b.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
C++ 编译的 func.o 解析如下:
![](https://upload-images.jianshu.io/upload_images/24563956-9ccf6aaea4fb62e4.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
这些实现细节并不是标准的一部分,但是我会惊讶地看到一个实现做了非常不同的事情。
![](https://upload-images.jianshu.io/upload_images/24563956-b4371119b1e68d25.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
汽车
我主要是为了好玩才把它包括在内,因为我认为它并不像可能的那样为人所知。auto用于C++中的类型推断,但也是一个C关键字,只是一个我从未见过的关键字。
auto用于声明具有自动存储类的内容。它很少被看到,因为这是块中声明的所有变量的默认存储类。
以下C语言违反了约束,即不指定类型。这可能会出错,但我从来没有找到一个编译器来给它提供任何关于隐式转换的警告:
1 intmain() {
2 autox ="actually an int";
3 returnx;
4 }
在c99之前,没有类型说明符是合法的,并且该类型被假定为int。当我使用 Clang 和 gcc 编译它时会发生这种情况,因此我们得到一个警告,因为隐式将 char 数组转换为 int。
在C++中,这不会编译,因为x被推断为const char*:
error:cannotinitializereturnobjectoftype'int'withanlvalueoftype'const char *'returnx;
C++没有的特性
尽管C是一种非常小的语言,而且C++非常庞大,但是有一些C++没有的特性。
变长阵列
VLAS允许您定义一个可变长度的自动存储持续时间数组。例如:
1 voidf(intn) {
2 intarr[n];
3 // ......
4 }
实际上VLAS在C11标准中是可选的,这使得它们不太便携。
这些不是C++的一部分,部分原因可能是C++标准库在很大程度上依赖于动态内存分配来创建像std::vector也可以类似地使用。您可能不想使用这种动态分配,但可能不会使用C++。
受限指针
C定义第三种类型限定符(除了const和volatile): restrict7。这只用于指针。使指针受到限制就是告诉编译器“我只能通过这个指针来访问这个指针的作用域的底层对象”。因此,它不能别名。如果你违背了这个承诺,你就会有不明确的行为。
这是为了帮助优化。一个典型的例子是memmove可以告诉编译器,src和dst不要重叠。
摘自C11 6.7.3第8段:
通过限制限定指针访问的对象与该指针具有特殊关联。这种关联在下面6.7.3.1中定义,要求对该对象的所有访问都直接或间接地使用该特定指针的值。135)限制限定符(如寄存器存储类)的目的是促进优化,并且从组成符合程序的所有预处理转换单元中删除限定符的所有实例不会改变其含义(即可观察的行为)。
受限制的指针不是C++标准的一部分,但实际上被许多编译器作为扩展支持。
我怀疑restrict。这似乎是在玩火,而且有趣的是,在使用它时遇到编译器优化bug似乎很常见,因为它的使用很少。但很容易对我从未使用过的东西产生怀疑。
![](https://upload-images.jianshu.io/upload_images/24563956-0919beabc28b07cf.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
指定首字母
C99引入了一种非常有用的方法来初始化结构,我不明白为什么C++没有采用它。
![](https://upload-images.jianshu.io/upload_images/24563956-a52f1ca742207f93.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
在C++中,您必须像这样初始化:Colour c = { 0.1, 0.5, 0.9 };的定义中的变化更难理解,也不具有鲁棒性。Colour。您可以定义一个构造函数,但是为什么我们必须对一个简单的聚合类型这样做呢?我听说现在C++20里有指定的首字母。只花了21年。
尽管C++ 源自C, 而且大部分都建立在C 的基础上, 但是有一些合法的C 代码在C++ 中不合法。相反的, ANSI C 继承了C++ 的几个特性, 包括原型和常量, 所以这两个语言并不是另一个的超集或子集;而且它们在一些通用构造的定义上也不同。尽管有这些不同, 许多C 程序在C++ 环境中编译正确, 许多最新的编译器同时提供C 和C++ 的编译模式。但是, 把C 代码当成C++ 来编译通常是个坏的注意; 两个语言的差异普遍上足够让你得到不好的结果。
看到这里,你是不是对“C/C++又有了一点新的认知呢~如果你喜欢这篇文章的话,动动小指,加个关注哦~
![](https://upload-images.jianshu.io/upload_images/24563956-9bd052ad0b8f7df3.gif?imageMogr2/auto-orient/strip)
最后,如果你也想成为程序员,想要快速掌握编程,这里为你分享一个学习企鹅圈子!
里面有资深专业软件开发工程师,在线解答你的所有疑惑~编程语言入门“so easy”
编程学习书籍:
![](https://upload-images.jianshu.io/upload_images/24563956-178bd2e18b5d99bc.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
编程学习视频:
![](https://upload-images.jianshu.io/upload_images/24563956-884a3bf8b90c7e6b.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)