简介:面向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)。
标识符命名有四条硬规则,加一条软建议:
- 不能是关键字;
- 只能由字母、数字、下划线组成;
- 第一个字符必须是字母或下划线,不能是数字;
- 字母区分大小写,
age和Age是两个不同的标识符; - 建议见名知意,
studentScore比a1可读性高得多。
| 标识符 | 是否合法 | 原因 |
|---|---|---|
_count | 合法 | 下划线开头,符合规则 |
max2 | 合法 | 字母开头,含数字 |
2max | 非法 | 数字开头 |
my-name | 非法 | 含减号,不属于允许字符 |
int | 非法 | 是关键字 |
Total与total | 都合法 | 区分大小写,是两个不同变量 |
命名看着是小事,实际是编译错误的高发区。写代码时忽略大小写、把关键字当变量名、名字里混进中文符号或全角字符,都属于编译期就拦下来的错误,报错信息一般会直接指出位置,改起来不难,难的是养成一次写对的习惯。
3. 数据类型:字节数、取值范围与表示精度
3.1 整型家族与 sizeof:字节数决定取值范围
创建变量或常量时必须指定数据类型,因为类型决定了分配多少内存、按什么规则解释这段内存。整型有四种常见写法,本质区别是占用空间不同,占用空间又决定能表示的范围:
| 类型 | 常见占用空间 | 取值范围 |
|---|---|---|
short | 2 字节 | -2^15 ~ 2^15-1 |
int | 4 字节 | -2^31 ~ 2^31-1 |
long | 32 位平台 4 字节,64 位平台 8 字节 | 随平台变化 |
long long | 8 字节 | -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。两者的差别主要在有效数字范围,而不是简单的「一个大一个小」:
| 类型 | 占用空间 | 有效数字范围 |
|---|---|---|
float | 4 字节 | 7 位有效数字 |
double | 8 字节 | 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~Z | A是 65 |
| 97 ~ 122 | 小写字母a~z | a是 97 |
| 127 | DEL | 删除控制符 |
大小写字母之差固定是 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) | 先加,再赋值 | 3 | 3 |
b = a++;(a初值 2) | 先赋值,再加 | 2 | 3 |
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后缀上翻车,后来干脆在每次写除法前强制问自己一句「两边是不是都是整数」,问了两周就变成条件反射了。从那以后我每次开新的一章,都强制先写出这章的验证代码再往下读,慢是慢一点,但不用返工。希望帮到你。
本文还有配套的精品资源,点击获取