linx295头像
关注

单元〇 · 别把 C 当成 C++ 的入场券

单元〇 · 别把 C 当成 C++ 的入场券

专栏《Zero to C++》· 第 0 篇
阅读时间约 8 分钟。本篇不含需要你动手的技术细节,只交代立场和它的依据。


问题是怎么被提出来的

这句话我听过很多遍,语气都差不多,半是请教,半是心虚:

「我什么语言都没学过,想学 C++。听说要先学 C,是这样吗?」

提问的人通常已经做过功课。他翻过几篇帖子,看过几门课的目录,里面都躺着同一句:「建议先学习 C 语言基础。」
于是他退回去,打开一门 C 语言课,花两个月学指针、数组、mallocfree,再回来学 C++——然后发现要学的东西没有变少,反而变多了。

这篇交代本专栏的立场,以及支撑它的依据。

结论

没有 C 基础,可以直接学 C++。

需要拆掉的是另一个前提:C 是 C++ 的前置课程。
这个前提流传很广,但它是教学惯性留下来的,不是技术上的必要条件。下面逐个检查它的三个来源。

前提的来源之一:血缘

C++ 确实从 C 演化而来。1979 年 Bjarne Stroustrup 做「C with Classes」,就是拿 C 当基底。
很多人由此推断:学 C 就是学 C++ 的前半段。

推断不成立。A Tour of C++ 里那张家族树写得很清楚:

C 和 C++ 是兄弟,不是父子。它们都源于 Classic C,各自带走了一部分特质,谁也不和 Classic C 完全兼容。

兄弟之间没有承接关系。沿 C 的路走下去,不会自动走到 C++。这一点在交谈录里讲得更完整。

前提的来源之二:教材

这一条影响最大。

2005 年,Bjarne 去教大一,把当时最流行的约二十本 C++ 教材读了一遍。读完他说了一句后来被反复引用的话:

「如果这就是 C++,我也不喜欢它!」

原因是这些教材大多走在「C 先行」的老路上,把 STL 和 RAII 整个省掉,或塞进一个学生根本读不到的「高级特性」章节。很多人觉得 C++ 又难又丑,是因为他们学的不是现代 C++。

而这件事上,有一份来自另一本主流教材的直接证据。《C++ Primer》第 5 版在序言里写:

Most texts present C++ in the order in which it evolved. They teach the C subset of C++ first, and present the more abstract features of C++ as advanced topics at the end of the book. There are two problems with this approach: Readers can get bogged down in the details inherent in low-level programming and give up in frustration. Those who do press on learn bad habits that they must unlearn later.

译过来是:多数教材按 C++ 演化的顺序来讲,先教 C 子集,把抽象特性放到书末当高级话题。这样做有两个问题——读者会陷在低层编程固有的细节里,在沮丧中放弃;而那些硬撑下来的人,会学到一些日后必须改掉的坏习惯。

《C++ Primer》是市面上最主流的 C++ 入门书之一,它自己在序言里反对「C 子集先行」,并且全书就是这么做的:stringvector 在第 3 章就出现,而「怎么写自己的类」被推到第 7 章起步、第 13 章才完整。

前提的来源之三:路径

你问那些主张「要先学 C」的人,他们多半是这么过来的。
在他们入行的年代,没有别的路可走。那是历史造成的路径,不是学习顺序上的必要。

一个人怎么学会的,和他建议别人怎么学,是两件事。

先学 C 会留下什么

如果先学 C 只是「多花两个月」,代价还可以接受。代价在别处:它会在你手上装进一套和 C++ 相冲突的默认动作,事后要花力气拆掉。

说到这里,上面那条《C++ Primer》的引文已经点破了一半——「learn bad habits that they must unlearn later」。具体是哪几种习惯,可以看代码。

一、用指针参数访问函数外的对象。

/* C 里的写法:传递指针,函数内不知道该不该改、也不知道长度 */
void process(int* buf, size_t n);

《C++ Primer》在第 6 章的 Best Practices 框里专门标了这条:

Programmers accustomed to programming in C often use pointer parameters to access objects outside a function. In C++, programmers generally use reference parameters instead.

C++ 把「我打算拿这块内存做什么」写进了类型:

void process(std::span<const int> data);   // 只看,不改、不拥有;长度跟着一起来
const Widget& peek(const Widget& w);       // 不拥有,但保证它活着
void take(std::unique_ptr<Widget> w);      // 接管所有权

二、手动配对资源。 mallocfreefopenfcloselockunlock——获取和释放分散在代码两头,中间任何一条提前返回或异常,配对就断了。

/* 每个使用点都要记得释放,每条错误路径都要手写回滚 */
typedef struct { int* data; size_t size; } Vec;

Vec* vec_new(size_t n) {
    Vec* v = malloc(sizeof(Vec));
    if (!v) return NULL;
    v->data = malloc(n * sizeof(int));
    if (!v->data) { free(v); return NULL; }   /* 少写这一句,就是一次泄漏 */
    v->size = n;
    return v;
}
void vec_free(Vec* v) { if (v) { free(v->data); free(v); } }
// 同一个需求:一个能自动增长的整数序列
std::vector<int> v(n);   // 分配和初始化一次说完;离开作用域自动释放,抛异常也释放

右边这行常被当成语法糖。它做的事是把资源的生命周期绑到对象上——这就是 RAII,C++ 里资源管理的基本手法。从 C 带过来的手动配对习惯,会让你本能地不信任它,甚至绕开它。这才是「先学 C」最贵的那笔账。

三、用 char 数组组织文本。 关于这一点,《C++ Primer》的措辞比多数教材都直接:

Although C++ supports C-style strings, they should not be used by C++ programs. C-style strings are a surprisingly rich source of bugs and are the root cause of many security problems. They’re also harder to use!

C 的底层知识怎么办

讲到这里必须把话说全,否则这段就变成了教条。

内存、指针、编译链接,在 C++ 里同样重要,一点没打折。 一个不知道对象在内存里怎么摆放、不知道指针是什么、不知道程序怎么从源码变成可执行文件的人,写不出好的 C++,也调不出 bug。

要处理的只是什么时候学、以什么方式学。答案是:这些知识不必前置,可以边学 C++ 边补。

这么安排的依据有两层。

第一层,《C++ Primer》给的 C++ 三层结构。 它把现代 C++ 分成三部分:从 C 继承来的低层语言、用来定义自己类型的高级语言特性、标准库。第一层明确存在,位置固定。所以本专栏做的是补上「对称物」,而不是假装那一层不存在。

第二层,这样学效率更高。 C 里 int* p 传递一段内存,函数不知道后面有几个元素,越界也没人拦。C++20 给了它一个对称物:

void process(std::span<const int> data);   // 指针 + 长度,绑成一个类型

span 把「一段连续只读内存」变成了类型,边界信息进了函数签名。在这一处你会同时看清三件事:指针的本质是地址加类型、长度信息在 C 里丢了、类型能把契约提前到编译期。这比先在 C 里硬背一遍指针、再回来重新理解一遍要快。

但有一条边界要说清楚。 《C++ Primer》只承诺读者不必懂 C,它同时写明:

we assume you know enough about programming to write, compile, and run a program in at least one modern block-structured language.

也就是说,它假定你已经会一门结构化语言——用过变量、写过函数、用过编译器。
对绝对零基础的读者,《C++ Primer》不是第一本书。专栏的路线图会在开篇把两类读者的入口分开给。

本专栏做什么

本专栏的核心工作可以概括成一句:

补齐 C++ 里关于 C 的对称认知。

malloc/free 在 C++ 里的对称物是什么,裸指针、数组、char*、宏、函数指针、头文件、链接方式又各自对应什么,什么时候该用哪一个——做成一张完整的对照表,一处一处讲透。同时把那些「零基础最容易染上、以后最难戒掉」的习惯提前标出来。

篇目是这样安排的:

篇目你会得到什么
01先学 C 的隐性代价三组 C/C++ 对照,看清习惯怎么倒灌
02C++ 有自己的核心思想六件套:类、RAII、模板、STL、智能指针、Lambda
03那 C 的底层知识怎么办三层分工 + 两类读者的入口
04对称认知·上:内存与指针malloc → 智能指针,数组 → vector/spanchar*string
05对称认知·下:编译与链接头文件、宏、extern "C"、名字修饰、g++gcc
06零基础路线图-第 0 周·先把环境跑起来编译并跑通第一个程序,看懂一条报错
07零基础路线图-第 1–3 周·变量、类型与字符串string 读写文本,做简单计算
08零基础路线图-第 4–6 周·控制流与函数写判断和循环,把重复逻辑抽成函数
09零基础路线图-第 7–9 周·类与 RAII定义自己的类,写出不会泄漏资源的程序
10零基础路线图-第 10–12 周·STL、智能指针与 Lambda用容器和算法解决实际问题
11FAQ 与自测高频疑问 + 分阶段检查清单

关于 C++ 大小,Bjarne 有句话是全专栏的地基:

「C++ 里面,藏着一个更小、更干净、想冲出来的语言。」

你学的是那个小语言——类、RAII、模板、STL、智能指针、Lambda。它有自己完整的一套核心思想,不需要先用 C 打底,也不需要先接受一套面向过程的思维方式再推倒重建。

直接进门。缺的那部分,边走边补。


附录 · 术语表

本篇出现的词,按主题归类。不要求记住——读的时候遇到不懂的,回来查一眼就行。
后面的单元会各自补自己的术语;全部写完后再合并成一份总表。

语言与它的来历

C — 1970 年代诞生的一门编程语言,特点是贴近硬件、运行快。Linux 内核大量用 C 写。
C++ — 在 C 的能力之上加了类、模板、标准库等工具的一门语言。本篇要论证的正是:它和 C 是兄弟,不是上下级。
Classic C — 还没有官方标准的那个 C(1970–80 年代)。今天的 C(ISO C)和 C++ 都从它分出来。
C with Classes — C++ 最早的名字。1979 年 Bjarne 做它,目的是给 C 加上「类」。
现代 C++(Modern C++) — 指 C++11 及之后的 C++。相比"把 C++ 当带类的 C 来写"的旧风格,它有一整套自己的写法。
Bjarne Stroustrup — C++ 的创造者,在贝尔实验室做出这门语言。专栏里多处引用他的话。

程序是怎么跑起来的

源代码 — 你写出来的那些文本文件。
编译器(compiler) — 把源代码翻译成计算机能执行的程序的工具。g++clang++ 是两种常用的 C++ 编译器。
可执行文件 — 编译器翻译出来的产物。运行它,程序就跑起来。
编译 / 链接(compile / link) — 从源代码到可执行文件的两个主要步骤。编译把每个源文件翻成机器码,链接把多个部分拼到一起、接上标准库。
函数签名(function signature) — 函数的名字加上参数类型那一段,例如 void process(int* buf, size_t n)。它决定别人怎么调用这个函数。

内存与指针

内存(memory) — 程序运行时存放数据的地方,可以想成一长排带编号的格子。
指针(pointer) — 一个格子的编号。int* p 表示 p 里存的是一个地址,而不是整数本身。
裸指针(raw pointer) — 上面那个指针本身。C 里只有这一种;C++ 里还有"智能指针"(见下)。“裸"是强调它不带任何管理能力
引用(reference) — C++ 里的"别名”。Widget& w 表示 w 是某个已存在对象的另一个名字,不是拷贝。
数组(array) — 一排连续存放的、类型相同的元素。
结构体(struct) — 把几个不同类型的数据打包在一起的自定义类型。
内存泄漏(memory leak) — 用 mallocnew 要来的内存,用完了忘了还。程序跑得越久,占的内存越多。
所有权(ownership) — “谁负责最后把这块资源还回去”。C 靠注释和纪律约定,C++ 把它写进类型。
生命周期(lifetime) — 一个对象从被创建到被销毁的这段时间。
作用域(scope) — 一个名字在代码里有效的范围。一对 { } 通常就是一个作用域,"离开作用域"就是执行到 } 之外。

C 的资源管理

malloc / free — C 里申请和释放内存的一对函数。用了 malloc,就必须记得配一次 free
fopen / fclose — C 里打开和关闭文件的一对函数,同样是"配对着用"。
资源(resource) — 程序向系统借来的东西:内存、文件、锁、网络连接。借了就得还。
RAII — C++ 管资源的基本手法:把资源的获取放进构造函数,释放放进析构函数。这样对象一离开作用域,资源自动还回去,不需要手写释放。

C++ 的工具

类(class) — 把数据和操作这些数据的函数打包在一起的自定义类型。
STL(标准模板库) — C++ 标准库的核心部分,提供容器、迭代器和算法。
容器(container) — 现成的、用来装东西的数据结构。
std::vector<int> — 能自动增长的整数序列。它替掉了 C 里"动态数组 + realloc + 手写销毁"那一整套。
std::string — C++ 的文本类型。替掉 C 里以 '\0' 结尾的 char 数组。
std::span — C++20 引入的类型,把"一段连续内存"和"它的长度"绑在一起。std::span<const int> 读作"看得到一段连续的整数,只读"。
模板(template) — 写一份代码,让它对多种类型通用。std::vector<int>std::vector<double> 用的是同一个 vector 模板。
Lambda — 随手写在表达式里的匿名小函数,常用来给算法传一段判据。
智能指针(smart pointer) — 会自己释放内存的指针。std::unique_ptr<Widget> 表示"只有我一个主人,我走了它就销毁"。用它就不必手写 delete
异常(exception) — 运行期出错的机制。一旦抛出(throw),正常执行路径被打断,控制权交给上层的处理代码。
语法糖(syntactic sugar) — 一个写法只是让代码更好看,背后做的事一样。本篇提到 std::vector<int> v(n); 时,说的是它做的事比语法糖多——它把资源的生命周期绑到了对象上。

本篇引用的资料

A Tour of C++ — Bjarne 写的一本 C++ 速览小书。"C 和 C++ 是兄弟,不是父子"这个说法出自它。
C++ Primer — 市面上最主流的 C++ 入门教材之一。本篇多次引用它第 5 版的序言。


转载自 CSDN-专业IT技术社区

原文链接:https://blog.csdn.net/qq_42237638/article/details/164975831

文章来源转载

评论

赞0

评论列表

微信小程序
QQ小程序

关于作者

点赞数:0
关注数:0
粉丝:0
文章:0
关注标签:0
加入于:--