如果您已经了解 C,如何学习 C++
从 C 迁移到 C++ 容易吗?
是的!C++ 几乎完全是标准 C95(C90 和 1995 年修正案 1)的超集。除了少数例外,每个有效的 C95 程序也是一个有效的 C++ 程序,且含义相同。
一个很好的第一步是简单地将 C++ 用作“一个更好的 C”,这意味着你可以在 C++ 的 C 子集中进行编程,并发现体验比 C 更好,因为 C++ 提供了额外的类型检查,有时甚至对普通的 C 代码也提供额外的性能。
当然,C++ 还提供了更多!一旦你开始将现有的 C 代码编译为 C++,你就可以根据自己的舒适程度,选择性地、策略性地在各个地方使用 C++ 功能——并立即在每一行代码中看到好处。
C++ 和 C 有什么区别?
C++ 是 C95(C90 加一个修正案)的直接后代,它保留了几乎所有 C95 作为子集。C++ 提供了比 C 更强的类型检查,并直接支持比 C 更广泛的编程风格。C++ 是“一个更好的 C”,因为它支持使用 C 进行编程的风格,具有更好的类型检查和更多的符号支持(不损失效率)。同样地,ANSI C90/C95 是比 K&R C 更好的 C。此外,C++ 支持数据抽象、面向对象编程和泛型编程(参见《C++ 程序设计语言》;讨论兼容性问题的附录 B 可供下载)。
我们从未见过可以用 C95 比 C++ 更好地表达的程序(我们不认为这样的程序会存在——C95 中的每个构造在 C++ 中都有一个明显的等价物)。然而,仍然存在一些环境,其中对 C++ 的支持非常薄弱,以至于使用 C 更有优势。不过,这样的环境已经不多了;请参见Stroustrup 的(不完整)编译器列表。
有关 C++ 设计的讨论,包括其与 C 的关系,请参见《C++ 的设计和演变》。
请注意,上面段落中的“C”指的是经典 C 和 C95(带有修正案的 C90)。C++ 不是 C99 的后代;相反,两者都源自 C95。C++11 采用了所有 C99 的预处理器扩展和库扩展,但没有采用 C99 的新语言特性,因此像 C99 中添加的 restrict
关键字这样的语言特性通常不是 ISO C++ 的一部分。这里是C++98 和 C99 之间差异的描述。
C 是 C++ 的子集吗?
在严格的数学意义上,C 不是 C++ 的子集。有些程序在 C 中是有效的,但在 C++ 中不是;甚至有一些编写代码的方式在 C 和 C++ 中具有不同的含义。然而,C++ 支持 C95(C90 加一个修正案)及更早版本支持的所有编程技术。每个这样的 C 程序都可以在 C++ 中以基本相同的方式编写,具有相同的运行时和空间效率。将数万行 ANSI C 转换为 C 风格的 C++ 通常只需几个小时。因此,C++ 是 C95 的超集,就像 C95 是 K&R C 的超集,就像 ISO C++ 是 1985 年存在的 C++ 的超集一样。
编写良好的 C 代码通常在 C++ 中也是合法的。例如,Kernighan & Ritchie:《C 程序设计语言(第二版)》中的每个示例也都是一个 C++ 程序。
C/C++ 兼容性问题示例
int main()
{
double sq2 = sqrt(2); /* Not C++: call undeclared function */
int s = sizeof('a'); /* silent difference: 1 in C++ sizeof(int) in C */
}
调用未声明的函数在 C 中是不良风格,在 C++ 中是非法的。向函数传递参数时使用未列出参数类型的声明也是如此。
void f(); /* argument types not mentioned */
void g()
{
f(2); /* poor style C. Not C++ */
}
在 C 中,void*
可以隐式转换为任何指针类型,并且自由存储分配通常使用 malloc()
完成,后者无法检查是否请求了“足够”的内存。
void* malloc(size_t);
void f(int n)
{
int* p = malloc(n*sizeof(char)); /* not C++. In C++, allocate using `new' */
char c;
void* pv = &c;
int* pi = pv; /* implicit conversion of void* to int*. Not in C++ */
}
请注意 void*
隐式转换为 int*
导致的潜在对齐错误。参见void*
和 malloc()
的 C++ 替代方案。
从 C 转换为 C++ 时,请注意 C++ 比 C 拥有更多的关键字。
int class = 2; /* ok in C. Syntax error in C++ */
int virtual = 3; /* ok in C. Syntax error in C++ */
除了一些上面所示的(并在 C++ 标准和《C++ 程序设计语言(第三版)》附录 B 中详细列出的)示例外,C++ 是 C 的超集。(附录 B 可供下载)。
请注意,上面段落中的“C”指的是经典 C 和 C95(带有修正案的 C90)。C++ 不是 C99 的后代;相反,两者都源自 C95。C++11 采用了所有 C99 的预处理器扩展和库扩展,但没有采用 C99 的新语言特性,因此像 C99 中添加的 restrict
关键字这样的语言特性通常不是 ISO C++ 的一部分。这里是C++98 和 C99 之间差异的描述。
既然有老式可靠的 qsort()
,为什么还要使用 sort()
?
对初学者来说,
qsort(array,asize,sizeof(elem),elem_compare);
看起来很奇怪,比
sort(vec.begin(),vec.end());
更难理解。对于专家来说,sort()
对于相同的元素和相同的比较标准往往比 qsort()
更快,这一事实通常很重要。此外,sort()
是泛型的,因此可以用于容器类型、元素类型和比较标准的任何合理组合。例如
struct Record {
string name;
// ...
};
struct name_compare { // compare Records using "name" as the key
bool operator()(const Record& a, const Record& b) const
{ return a.name<b.name; }
};
void f(vector<Record>& vs)
{
sort(vs.begin(), vs.end(), name_compare());
// ...
}
如果你的编译器支持 C++14,这会变得更简单
struct Record {
string name;
// ...
};
void f(vector<Record>& vs)
{
sort(vs.begin(), vs.end(), [](auto &a, auto &b) { return a.name < b.name; });
// ...
}
此外,大多数人赞赏 sort()
是类型安全的,使用它不需要强制转换,并且他们不必为标准类型编写 compare()
函数。
有关更详细的解释,请参阅 Stroustrup 的论文“将 C++ 作为一种新语言学习”,可从他的出版物列表下载。
sort()
倾向于优于 qsort()
的主要原因是比较更好地内联。
为什么我必须使用强制转换才能从 void*
进行转换?
在 C 中,你可以隐式地将 void*
转换为 T*
。这是不安全的。考虑
#include<stdio.h>
int main()
{
char i = 0;
char j = 0;
char* p = &i;
void* q = p;
int* pp = q; /* unsafe, legal C, not C++ */
printf("%d %d\n",i,j);
*pp = -1; /* overwrite memory starting at &i */
printf("%d %d\n",i,j);
}
使用没有指向 T
的 T*
可能会产生灾难性的后果。因此,在 C++ 中,要从 void*
获取 T*
,你需要进行显式强制转换。例如,要获得上面程序的 undesirable 效果,你必须编写
int* pp = (int*)q;
或者,使用新风格的强制转换使未检查的类型转换操作更明显
int* pp = static_cast<int*>(q);
最好避免强制转换。
这种不安全转换在 C 中最常见的用途之一是将 malloc()
的结果分配给合适的指针。例如
int* p = malloc(sizeof(int));
在 C++ 中,使用类型安全的 new
运算符
int* p = new int;
顺便说一句,new
运算符比 malloc()
具有额外的优势
new
不会意外地分配错误的内存量,new
隐式检查内存耗尽,并且new
提供初始化功能
例如
typedef std::complex<double> cmplx;
/* C style: */
cmplx* p = (cmplx*)malloc(sizeof(int)); /* error: wrong size */
/* forgot to test for p==0 */
if (*p == 7) { /* ... */ } /* oops: forgot to initialize *p */
// C++ style:
cmplx* q = new cmplx(1,2); // will throw bad_alloc if memory is exhausted
if (*q == 7) { /* ... */ }