ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

C++入门学习地图:数据类型、运算符与易错点复习清单

C++入门学习地图:数据类型、运算符与易错点复习清单

简介:面向C++初学者与需要系统梳理语法体系的读者,这份讲义笔记围绕入门、核心与提高三个阶段展开,从创建项目、编写第一个程序讲起,覆盖注释、变量与常量、关键字、标识符命名规则、整型与浮点型等基础内容,并延伸到指针、数组、结构体、类、运算符重载、模板和异常处理等进阶主题。资源包为1个PDF文件,大小约5.07MB,便于在电脑或平板上阅读、打印和做批注,适合课堂配套、自学跟练与考前复习。目前已有25997人学习,说明其内容组织具有一定参考价值。读者可借助清晰的知识点分节,建立从语法入门到面向对象与泛型编程的完整框架,并通过示例代码理解变量初始化、类型占用、常量定义和异常捕获等细节,为后续项目实践打基础。

1. 一张三层地图:C++ 从入门到提高到底在学什么

很多人翻开 C++ 讲义笔记,第一反应是按目录往下背:变量、类型、运算符、循环、函数,背完一章打个勾。真到要写程序时却卡在第一行,因为脑子里装的是散点,不是结构。这门语言其实是分层的:最底下是让程序活起来的入门层,工程与源文件、main 函数、注释、变量与常量、关键字与命名规则;中间是数据层,整型、浮点、字符、字符串、bool 各占几个字节、能表示多大范围,配上算术、赋值、比较、逻辑四类运算符;最上面才是核心与提高层,指针、数组、结构体、类、运算符重载、模板、异常处理。这份笔记的价值不在于把语法罗列一遍,而在于把三层连成一条能自查的线。它适合两类人:正在系统学第一遍、需要一张地图的人,以及学过一遍但做题总在细节上翻车、需要回头补漏洞的人。

2. 程序骨架:从四步流程到命名规则

2.1 四步流程与 main 函数:程序是怎么跑起来的

写一个 C++ 程序,流程固定为四步:建项目、建源文件、写代码、运行。项目不是可有可无的壳子,它保存的是编译选项、包含路径、输出目录这些配置;源文件才是真正被编译器读进去的文本。在集成开发环境里,一般先新建一个空项目,再在源文件目录上右键添加新建项,文件名以.cpp结尾,然后就可以写代码了。这四步里最容易出问题的是第三步和第四步之间的落差:代码写完了但编译不过,或者编译过了运行窗口一闪就没了。

入门第一个程序长这样:

#include <iostream> // 引入输入输出流库,cout 和 endl 都定义在这里 using namespace std; // 展开标准库命名空间,省去每处写 std:: int main() { // 程序唯一入口,返回类型必须是 int cout << "Hello world" << endl; // << 是流插入运算符,endl 换行并刷新缓冲 return 0; // 返回 0 表示正常结束,交给操作系统 }

逐行拆开看,#include是预处理指令,编译器在正式编译前把库的声明文本搬进来;using namespace std;解决的是名字前缀问题,不写它就得写成std::cout、std::endl;main是入口,操作系统调用它,返回值 0 代表正常退出;cout是标准输出流对象,<<在这里不是位移而是流插入,可以连续串多个输出项;endl除了换行还会刷新缓冲区,和只写"\n"有细微差别。老一些的讲义里常见system("pause");,作用是调系统命令暂停窗口,本地调试时省事,但它依赖具体平台,正式代码里不该留。

提示:运行窗口闪退时,先确认是不是程序正常跑完就退出了,而不是程序崩溃。这两种现象看起来一样,排查方向完全不同。

2.2 注释、变量与常量:三种「不可改」的边界

注释有两种写法。单行注释用//,通常放在一行代码上方或语句末尾,解释这一行在做什么;多行注释用/* ... */,通常放在一段代码上方,做整体说明。编译器在预处理阶段就把注释替换成空格,所以注释写多少都不影响运行效率,但注释写错会误导后来读代码的人,包括三个月后的自己。

变量是给一段内存起名字,语法是「数据类型 变量名 = 初始值」。讲义里反复强调创建变量时必须给初始值,否则会报错。严格说,局部变量不初始化,编译器不一定拦你,它只是留下一块内容不确定的内存,你读到的可能是上一次别人用剩下的值,这类问题比编译错误难查得多。所以「必须初始化」与其说是语法要求,不如说是血泪经验总结出来的纪律:定义即初始化,别给自己留后悔药。

常量是程序中不允许被修改的数据,两种定义方式差别不小:

对比项#define宏常量const修饰的变量
写法#define DAY 7,末尾不加分号const int MONTH = 12;
处理阶段预处理阶段做文本替换编译阶段参与类型检查
有无类型无类型,只是字符串替换有明确数据类型
出错表现替换后可能产生意料之外的优先级问题类型不匹配直接编译报错
常见位置文件上方集中定义变量定义处,靠近使用位置
#define DAY 7 // 宏常量:预处理阶段直接替换,末尾不写分号 int main() { const int MONTH = 12; // const 常量:有类型,编译器会检查类型 // DAY = 8; // 编译报错,宏替换后等于给字面量赋值 // MONTH = 24; // 编译报错,const 变量不可修改 return 0; }

两种方式都能实现「不可改」,但宏常量参与的是文本替换,不做类型检查,一旦写复杂表达式就容易翻车;const有类型、有作用域,调试时能看到符号。复习时把这条差异记牢,后面学到函数参数、类成员时,const还会反复出现。

2.3 关键字与标识符命名:编译期就拦下来的错误

关键字是语言预先保留的单词,有固定含义,不能拿来当变量名、常量名或函数名。数量看着多,其实按用途分组就好记:类型相关的一组(int、char、float、double、bool、void、short、long、signed、unsigned、struct、class、enum、union),流程控制的一组(if、else、switch、case、default、for、while、do、break、continue、return、goto),修饰与存储相关的一组(const、static、extern、register、mutable、volatile、inline、virtual、explicit、friend),以及和类型转换、异常、模板相关的一组(static_cast、dynamic_cast、const_cast、reinterpret_cast、try、catch、throw、template、typename、namespace、using、new、delete、this、operator、sizeof、typedef、auto、true、false)。

标识符命名有四条硬规则,加一条软建议:

  1. 不能是关键字;
  2. 只能由字母、数字、下划线组成;
  3. 第一个字符必须是字母或下划线,不能是数字;
  4. 字母区分大小写,age和Age是两个不同的标识符;
  5. 建议见名知意,studentScore比a1可读性高得多。
标识符是否合法原因
_count合法下划线开头,符合规则
max2合法字母开头,含数字
2max非法数字开头
my-name非法含减号,不属于允许字符
int非法是关键字
Total与total都合法区分大小写,是两个不同变量

命名看着是小事,实际是编译错误的高发区。写代码时忽略大小写、把关键字当变量名、名字里混进中文符号或全角字符,都属于编译期就拦下来的错误,报错信息一般会直接指出位置,改起来不难,难的是养成一次写对的习惯。

3. 数据类型:字节数、取值范围与表示精度

3.1 整型家族与 sizeof:字节数决定取值范围

创建变量或常量时必须指定数据类型,因为类型决定了分配多少内存、按什么规则解释这段内存。整型有四种常见写法,本质区别是占用空间不同,占用空间又决定能表示的范围:

类型常见占用空间取值范围
short2 字节-2^15 ~ 2^15-1
int4 字节-2^31 ~ 2^31-1
long32 位平台 4 字节,64 位平台 8 字节随平台变化
long long8 字节-2^63 ~ 2^63-1

sizeof用来统计数据类型的占用空间,语法是sizeof(数据类型)或sizeof(变量),返回的是字节数:

#include <iostream> using namespace std; int main() { cout << "short = " << sizeof(short) << endl; // 输出 2 cout << "int = " << sizeof(int) << endl; // 输出 4 cout << "long = " << sizeof(long) << endl; // 32 位平台 4,64 位平台 8 cout << "long long = " << sizeof(long long) << endl; // 输出 8 return 0; }

参数说明:传给sizeof的可以是类型名也可以是变量名,它返回的是字节数,单位不是位。sizeof在编译期就能确定结果,不参与运行时的实际计算。

这里有个容易忽略的点:long的字节数不是固定的,同一份代码换平台编译,结果可能不同。所以记结论时要记成short < int <= long <= long long,而不是死记某个具体数字。写跨平台代码时,如果对宽度有明确要求,就不该假设long是 4 字节。

3.2 浮点型:有效数字比取值范围更值得记

浮点型用来表示小数,分单精度float和双精度double。两者的差别主要在有效数字范围,而不是简单的「一个大一个小」:

类型占用空间有效数字范围
float4 字节7 位有效数字
double8 字节15 ~ 16 位有效数字
int main() { float f1 = 3.14f; // 字面量后的 f 表示这是 float 类型 double d1 = 3.14; // 不加后缀的浮点字面量默认是 double cout << sizeof(f1) << endl; // 4 cout << sizeof(d1) << endl; // 8 float f2 = 3e2; // 科学计数法,表示 3 * 10^2 float f3 = 3e-2; // 表示 3 * 10^-2 return 0; }

参数说明:f后缀决定了字面量的类型。写float f = 3.14;时,右边的3.14先是double,再赋给float,中间有一次窄化转换,一般编译器给个警告就放过去了,但精度确实丢了。这就是为什么规范里要求float变量初始化时带上f。科学计数法里的e后面跟的是 10 的指数,正负号都可以。

浮点数还有一件要提前知道的事:它不能精确表示所有小数,0.1 + 0.2的结果不会严格等于0.3。入门阶段不用深究二进制表示,但要知道「浮点数比较大小不能直接用等号」这条结论,不然做题时会以为编译器出了玄学问题。

3.3 字符型与 ASCII:一个字节里装的是编码

字符型变量用来显示单个字符,语法是char ch = 'a';。有两条必须记牢的注意事项:字符用单引号括起来,不能用双引号;单引号里只能有一个字符,不能是一串。char只占 1 个字节,它存的不是字符本身,而是字符对应的 ASCII 编码值。

int main() { char ch = 'a'; cout << ch << endl; // 输出 a cout << sizeof(char) << endl; // 1 cout << (int)ch << endl; // 输出 97,字符 a 对应的 ASCII 码 ch = 97; // 直接用 ASCII 码赋值,输出仍是 a cout << ch << endl; return 0; }

ASCII 码表 128 个值,按区间记比逐行背高效得多:

区间含义记忆点
0 ~ 31非打印控制字符给打印机等外设用的控制码
32 ~ 126可打印字符键盘上能找到的字符
48 ~ 57数字0~9字符'0'的值是 48
65 ~ 90大写字母A~ZA是 65
97 ~ 122小写字母a~za是 97
127DEL删除控制符

大小写字母之差固定是 32,这个数字在大小写转换题里经常用到。转义字符是另一组要单独记的,它们用来表示那些没法直接敲出来的字符:

转义字符含义ASCII 值
\n换行,光标移到下一行开头10
\t水平制表,跳到下一个制表位9
\\表示一个反斜杠字符92
\r回车,光标移到本行开头13
\0空字符0
\ddd八进制转义,d 范围 0~7按三位八进制计算
\xhh十六进制转义,h 范围 0~9、a~f、A~F按十六进制计算

入门阶段用得最多的是\n、\t、\\三个。注意\\输出的是一个反斜杠,因为单个\在字符串里是转义引导符,必须成对写。

3.4 字符串、bool 与输入:类型对上了才不翻车

字符串有两种风格。C 风格用字符数组:char str[] = "hello world";,必须用双引号。C++ 风格用string类型:string str = "hello world";,需要引入#include <string>头文件。两者的关键差别在于 C 风格字符串末尾自动带一个\0作为结束标志,所以char str[] = "a"实际占 2 个字节,而string自己管理长度,不用你操心结束符。

布尔类型bool只有两个值:true本质是 1,false本质是 0,占用 1 个字节。这里要建立一个后面一直用的直觉:C++ 里非零即真。判断条件写if (a),只要a不是 0 就成立,不要求它必须是true。

输入用cin >> 变量:

int main() { int a = 0; cin >> a; // 从键盘读一个整数 double d = 0; cin >> d; // 读一个浮点数 string s; cin >> s; // 读字符串,遇到空白字符就停 return 0; }

参数说明:>>是流提取运算符,方向和<<相反。cin读取时会自动跳过前导空白,但读到空白(空格、制表符、换行)就认为这次输入结束,所以cin >> s读不进带空格的整行内容。这是后面处理字符串时的第一道坎,入门阶段先记住这个行为边界。

4. 运算符体系:算术、赋值、比较、逻辑四条线

4.1 算术运算符:整除、取模与除零

算术运算符处理四则运算,共五个:+、-、*、/、%,另外还有正号+和负号-两个单目形式。真正需要重点记的是除法与取模的两个前提条件。

int main() { int a = 10; int b = 3; cout << a / b << endl; // 输出 3,两个整数相除,结果仍是整数,小数部分直接截断 double d1 = 0.5; double d2 = 0.25; cout << d1 / d2 << endl; // 输出 2,浮点数相除保留小数 cout << 10 % 3 << endl; // 输出 1,取模得到余数 return 0; }

参数说明:/的结果类型由两个操作数决定,两边都是整数就做整数除法,向零截断,不做四舍五入;只要有一边是浮点数,就做浮点除法。%只能用于整型,浮点数不能取模。

还有两条硬约束:除数不能为 0,无论除法还是取模都一样。整数除零在运行时会直接崩溃,这种错误编译期不一定报,属于典型的运行期翻车点。

4.2 递增递减:前置与后置差在哪一行

++和--是使用频率很高的两个运算符,单独使用时前置后置没区别,a++和++a都让a加一。区别只出现在「参与表达式」的时候:

写法等价于表达式求值结果变量最终值
b = ++a;(a初值 2)先加,再赋值33
b = a++;(a初值 2)先赋值,再加23
int main() { int a2 = 10; int b2 = ++a2 * 10; // 先 ++a2 得 11,再乘 10,b2 = 110 cout << b2 << endl; int a3 = 10; int b3 = a3++ * 10; // 先用 10 乘 10 得 100,再让 a3 变成 11 cout << b3 << endl; // 输出 100,而不是 110 return 0; }

参数说明:前置是先改值再取值,后置是先取值再改值。上面这段代码里,两个b的结果差了 10,原因是乘法拿到的操作数不同。

注意:把++和--混在复杂表达式里,是阅读理解代码时最容易出错的地方之一。实际写业务代码时,把它们单独拎成一行,几乎不会损失什么,却能省下大量排查时间。

4.3 赋值、比较与逻辑:真假的本质是 1 和 0

赋值运算符除了基本的=,还有+=、-=、*=、/=、%=五个复合形式,a += 2等价于a = a + 2。复合赋值不只是写法简短,它保证左边的表达式只求值一次,这在后面左操作数带副作用时会有实际差别。

比较运算符有六个:==、!=、<、>、<=、>=,结果是布尔值。C 和 C++ 里真用 1 表示,假用 0 表示,所以cout << (a == b)会直接打印出 0 或 1。这里必须分清=和==:前者是赋值,后者是比较,写成if (a = 1)在语法上完全合法,但语义完全跑偏,这类 bug 编译器一般不吭声。

逻辑运算符三个:!非、&&与、||或,规则可以压缩成三句话:

运算符规则记忆口诀
!真变假,假变真取反
&&两边都真才为真同真为真,其余为假
||有一边真就为真同假为假,其余为真
int main() { int a = 10; cout << !a << endl; // 0,非零取反为假 cout << !!a << endl; // 1,两次取反回到真 int b = 0; cout << (a && b) << endl; // 0,有一边为假 cout << (a || b) << endl; // 1,有一边为真 return 0; }

梳理下来会发现,&&和||有一个共同特性:左侧一旦能定下结果,右侧就不再执行。这个特性叫短路求值,平时是优化,但如果右侧写了带副作用的表达式(比如自增),它到底执行没执行,就会成为排查时的一个黑匣子。入门阶段先建立一个意识:逻辑表达式里不要塞副作用。

5. 易漏点:五处看起来会、写起来错的地方

5.1 声明与初始化:编译器不拦,运行时才炸

第一处是变量声明了没给初值。为什么容易漏?因为int a;这行代码本身完全合法,编译通过,语法上挑不出毛病,很多人写完就默认它等于 0。怎么补?把「定义即初始化」当成硬纪律执行,int a = 0;、double d = 0.0;、string s = "";花不了几秒,却能避免读到一个无法预测的值。更进一步,变量尽量在第一次使用的位置附近定义,缩短它从创建到被赋值的空窗期,中间插入代码的可能性就小。

第二处是常量的两种写法混着用。为什么容易漏?因为宏常量写起来更短,不加分号也不报错,用顺手了就到处写。怎么补?记住宏是预处理阶段的文本替换,不参与类型检查,调试时看不到符号;只要不是必须用宏的场景,优先const。判断标准很简单:如果这个常量有明确的类型含义,就用const;如果只是拼接配置字符串或条件编译,才考虑宏。

5.2 字面量与类型:差一个后缀,精度就没了

第三处是float忘了写f后缀。为什么容易漏?因为float f = 3.14;能编译,输出看着也正常,问题和程序一起「正常」运行了下去。怎么补?养成习惯,凡是float类型的字面量一律写成3.14f,并且用sizeof打印一次确认自己写的是 4 字节,亲眼见过一次比背十遍定义都牢。顺带记住:不带后缀的浮点字面量默认是double。

第四处是字符与字符串的引号混用。为什么容易漏?'a'和"a"在屏幕上只差一个字符,读代码时眼睛会自动忽略。怎么补?把三条结论固定下来:字符用单引号,只能有一个字符;C 风格字符串用双引号,末尾自动带一个\0;C++ 风格string也要双引号,还要额外引入<string>头文件。验证方法很直接,sizeof('a')是 1,sizeof("a")是 2,因为多了一个结束符。另外char ch = "abcde";和char ch = 'abcde';两种写法都是错的,前者类型不匹配,后者引号里字符超量。

5.3 运算符与表达式:数学直觉最容易骗人

第五处是整数除法与取模。为什么容易漏?因为数学里的除法没有「截断」这个概念,10 / 3在纸上写 3.33 天经地义,在 C++ 里得到的是 3。怎么补?做除法之前先看两个操作数的类型:都是整数,就预期结果会丢掉小数部分;需要小数结果,至少把一边转成浮点。取模再加两条:只能用于整型,两边都不能是浮点数;除数不能为 0,整数除零在运行时直接崩,浮点除零得到的是特殊值而不是报错。另外负数取模的结果在不同语言里规则不完全一样,涉及负数时先小范围试一次再下结论。

除了这五处,还有一处值得单独提醒:long的字节数依赖平台。为什么容易漏?因为本地跑出来的sizeof(long)是 8,就默认它在所有环境都是 8。怎么补?记相对顺序short < int <= long <= long long,需要固定宽度时不要依赖long,并且在跨平台代码里把sizeof结果当成需要验证的假设,而不是已知常量。

6. 复习清单:用编译报错反推知识漏洞

复习到这个阶段,最有效的方式不是从第一页再读一遍,而是用一张自查表把知识点的「可验证性」拉满。下面这张表左边是检查项,右边是怎么用一行代码验证,凡是不能立刻写出验证方法的,就是要回头补的地方。

检查项一行验证
各整型占用字节sizeof(short)、sizeof(int)、sizeof(long long)
浮点精度差异sizeof(3.14f)与sizeof(3.14)
字符存的是编码(int)'a'
C 风格字符串带结束符sizeof("a")
整除会截断10 / 3
取模只能整型试着写3.14 % 1.1看报错
前置后置的差别b = ++a * 10;与b = a++ * 10;
短路求值0 && (a = 1)之后看a有没有变
常量不可改对const变量赋新值看报错

真正值得花时间的动作是「故意写错」。把char ch = "a";、int a = 3.14;、10 % 3.0、const int x = 1; x = 2;这几行挨个敲进去编译一次,把报错信息读一遍。编译器给出的错误提示,本质上是这份笔记里每条规则的另一种表述方式,看多了就能从报错位置直接定位到是哪一层的概念没吃透。下面这个小练习可以当作收尾自测:

#include <iostream> #include <string> using namespace std; int main() { const int TOTAL = 3; // 常量:题目总数,不可改 int right = 0; // 变量:定义即初始化 double score = 0.0; // 浮点:用 0.0 而不是 0 for (int i = 1; i <= TOTAL; ++i) { // 前置递增,单独成句,不混进表达式 right += 1; } score = right * 100.0 / TOTAL; // 一边转成浮点,避免整除截断 cout << "score = " << score << endl; // 输出 100 return 0; }

这段代码把常量与变量、整型与浮点、整除与类型转换、递增写法几件事串在一起,每一行都能对应回前面某一章。如果哪一行读起来还需要想一下,那就是需要回头补的章节。

我自己的习惯是每学完一章,就在纸上默写三条「这章最容易错的地方」,默不出来说明还没真正过一遍。有段时间我总在整数除法和float后缀上翻车,后来干脆在每次写除法前强制问自己一句「两边是不是都是整数」,问了两周就变成条件反射了。从那以后我每次开新的一章,都强制先写出这章的验证代码再往下读,慢是慢一点,但不用返工。希望帮到你。

本文还有配套的精品资源,点击获取

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进