简介:这份C++讲义笔记面向零基础入门及希望系统巩固核心知识的自学者与高校学生,以PDF形式梳理从第一个程序到面向对象与泛型编程的学习脉络,帮助读者建立较完整的C++知识框架。资源包共1个PDF文件,大小约5.07MB,内容按章节组织,便于在电脑或平板上连续阅读与检索。已有25997人学习下载,具备一定参考价值。笔记覆盖开发环境创建项目与源文件、Hello World、注释、变量与常量、关键字及标识符命名规则,并延伸到整型、浮点型、字符型等数据类型、sizeof、指针、数组、结构体、类、运算符重载、模板和异常处理等主题;对每个知识点配以语法说明和示例代码,适合课前预习、课后复盘与查漏补缺。
1. 从一段 Hello world 看整份 C++ 入门讲义的知识地图
很多人复习 C++ 入门时喜欢从背关键字表开始,这份 C++ 入门、核心、提高三阶段的讲义笔记顺序恰好相反:它先把一段能跑起来的 Hello world 摆在你面前,再按「程序骨架 → 数据怎么存 → 数据怎么算」三层往回拆。第一层是 C++ 初识,解决工程怎么建、注释怎么写、变量与常量怎么定义、关键字和标识符怎么避开雷区;第二层是数据类型,把整型、浮点、字符、转义字符、字符串、bool 和 cin 输入逐个过一遍字节数与取值范围;第三层是运算符,四则、赋值、比较、逻辑,外加前后置递增这个经典分水岭。这份讲义笔记真正值钱的地方,是它把入门语法和后面提高阶段的地基压在同一张地图上——数组、指针、类、模板、异常处理,全都长在这三层之上,地基一松,后面全是返工。
2. 程序骨架:注释、变量、常量与命名的一场排雷
2.1 四步建工程与 main 函数究竟在做什么
讲义把写一个 C++ 程序拆成四个动作:创建项目、创建文件、编写代码、运行程序。前两步是纯操作,用一款带智能提示的桌面集成开发环境就能完成——新建项目,在源文件目录上右键添加新建项,给文件起个带.cpp后缀的名字,然后写代码、点运行。真正需要理解的是后两步,因为从第一次编译开始,报错信息就会一直跟着你,能不能读懂那几行红字,决定了入门阶段是顺畅还是反复受阻。
#include <iostream> // 引入输入输出流库,cout、cin 都来自这里 using namespace std; // 展开 std 命名空间,省掉每处都写 std:: int main() { // 程序入口,操作系统从这里开始执行 cout << "Hello world" << endl; // 向控制台输出字符串,endl 换行并刷新缓冲 system("pause"); // 让控制台停住,等待按键再关闭 return 0; // 返回 0 表示程序正常结束 }逐项拆开看:#include <iostream>是预处理指令,把输入输出流的声明整段搬进来,没有它cout就是未定义标识符;using namespace std;的作用是把标准库的名字从std::前缀里解放出来,代价是在大工程里容易和自定义名字撞车,入门阶段图省事可以先用,但要记住这只是便利,不是必须;main的返回值类型必须是int,返回 0 是约定俗成的「正常退出」,写成return EXIT_SUCCESS;效果一样,但需要额外引入<cstdlib>;endl除了换行还会刷新输出缓冲,和直接写"\n"不完全等价。
注意:
system("pause")是调用系统命令,只在本地练习时方便,放到在线评测环境里会被判成运行错误,凡是提交到判题系统的代码都要把它删掉。
编译失败时先看报错的第一条,后面的错误往往是第一条的连锁反应。最常见的三类:头文件没引入导致标识符不认识;语句末尾漏分号,报错位置常常指向下一行;花括号不配对,报错会指向文件末尾。这个「只看第一条、从下往上找括号」的习惯,比背语法表有用得多。
2.2 注释与变量:为什么「必须给初值」要当成铁律
注释是给人和未来的自己看的,编译器在预处理之后会把它们整段丢掉。讲义给了两种格式:单行注释//通常放在一行代码的上方或语句末尾,用于解释这一行在干什么;多行注释/* ... */放在一段逻辑的上方,用于说明整体意图。区别不只是长短——多行注释不能嵌套,一段/* */里再写一个/*会提前闭合,剩下的字符就变成了代码,这类问题在临时注释掉一大段调试代码时特别容易出现。养成用//逐行注释的习惯,能省掉不少麻烦。
#include <iostream> using namespace std; int main() { // 单行注释:声明一个整型变量并赋初值 int a = 10; /* 多行注释: 用来对一整段逻辑做说明, 编译器会整段跳过,不参与编译 */ cout << "a = " << a << endl; return 0; }变量的语法是「数据类型 变量名 = 初始值」,三个部分缺一不可。数据类型决定分配多少字节、按什么规则解释这段内存;变量名是你操作这段内存的把手;初始值决定它一开始是什么。讲义里强调创建变量时必须给初值,严格说不同编译器的表现并不一致——有的直接报错,有的只给警告,有的静默通过——但结论是一致的:局部变量不初始化,读到的是栈上残留的旧数据,同一段代码在不同机器、不同优化等级下可能给出不同结果。这种不确定性在调试时几乎无法复现,属于典型的黑匣子问题,所以把它当成硬性规则比记住「会不会报错」更划算。
2.3 常量的两条路:#define 与 const 怎么选
程序里总有一些不该被改的值,比如一周七天、一年十二个月。讲义给了两种定义方式,写法只差一行,背后的机制完全不同。
#include <iostream> using namespace std; #define DAY 7 // 宏常量:预处理阶段做纯文本替换,没有类型 int main() { const int MONTH = 12; // const 常量:有类型,参与编译期检查 cout << "一周有 " << DAY << " 天" << endl; cout << "一年有 " << MONTH << " 个月" << endl; // DAY = 8; // 展开后变成 7 = 8,编译报错 // MONTH = 24; // const 修饰的变量只读,同样编译报错 return 0; }| 对比项 | #define宏常量 | const修饰的变量 |
|---|---|---|
| 生效阶段 | 预处理,编译之前 | 编译期 |
| 是否有类型 | 无,纯文本替换 | 有,遵循类型规则 |
| 调试器能否看到 | 看不到,符号已被替换掉 | 能看到变量和值 |
| 作用域 | 从定义处到文件末尾,无块作用域 | 遵循正常作用域规则 |
| 典型风险 | 替换引发优先级问题、无类型检查 | 几乎没有额外风险 |
最容易出错的是宏的文本替换特性:#define DOUBLE(x) x * 2在DOUBLE(1 + 2)处会展开成1 + 2 * 2,结果是 5 而不是 6,正确写法要给参数和整体都套上括号。这也是现代 C++ 更推荐const、constexpr的原因——有类型就能被编译器拦住。入门阶段记住结论即可:能被const表达的常量,就不要用宏。
2.4 关键字与标识符:一张表把命名规则固定下来
关键字是语言预留的单词,讲义里列了完整一张表,int、const、class、namespace、static_cast、reinterpret_cast都在其中。它们的共同点是:不能拿来当变量名、常量名、函数名。之所以要背,不是因为考试会考,而是因为一旦撞上,报错信息往往指向奇怪的位置,初学者很难联想到是名字的问题。
标识符的规则只有四条:不能是关键字;只能由字母、数字、下划线组成;首字符必须是字母或下划线;区分大小写。规则之外还有一条建议——见名知意。把这条建议落到实处的办法是对照检查:
| 写法 | 问题 | 改法 |
|---|---|---|
int a; | 见名不知意,三天后自己都看不懂 | int studentAge; |
int 1count; | 数字开头,直接违反规则 | int count1; |
int my age; | 含空格,被当成两个标识符 | int myAge; |
int class; | 撞上关键字 | int classId; |
int Age; int age; | 只差大小写,极易混用 | int studentAge; int teacherAge; |
梳理下来会发现,命名规则的坑几乎都集中在两类场景:一是临时写的循环变量i、j、k用顺手了,到了多层嵌套就分不清谁是谁;二是缩写过度,cnt、tmp、val满屏飞,回头改逻辑时得重新推一遍每个变量的含义。命名这件事没有标准答案,但有一条判断标准很实用:如果你需要写注释才能解释这个变量是什么,那这个名字就该改。
3. 数据类型:字节数、取值范围与 sizeof 的验证法
3.1 整型四兄弟与 long 的平台差异
C++ 规定创建变量必须指定数据类型,因为类型决定了分配多少内存、按什么方式解释这段二进制。整型家族有四个成员,讲义的表格给了字节数和取值范围:
| 类型 | 占用空间 | 取值范围 |
|---|---|---|
short | 2 字节 | -2^15 ~ 2^15-1 |
int | 4 字节 | -2^31 ~ 2^31-1 |
long | 64 位桌面系统多为 4 字节,64 位类 Unix 系统多为 8 字节 | 随字节数变化 |
long long | 8 字节 | -2^63 ~ 2^63-1 |
long这一行是整张表里唯一需要打星号的。它不是「固定 8 字节」,标准只规定了它不小于int,具体多宽交给编译器和平台决定。这也是跨平台代码里最常翻车的地方:在本机测试通过的逻辑,换一个系统就可能因为整型宽度变化而溢出。
#include <iostream> using namespace std; int main() { // sizeof 既可以接类型名,也可以接变量名,结果单位是字节 cout << "short = " << sizeof(short) << endl; cout << "int = " << sizeof(int) << endl; cout << "long = " << sizeof(long) << endl; cout << "long long = " << sizeof(long long) << endl; return 0; }这段代码的价值不在于跑出四个数字,而在于它给了你一个随时可用的验证手段。凡是拿不准某个类型在目标平台上占多少字节,写一行sizeof打印出来比查资料快,也比凭记忆可靠。1 字节等于 8 位,所以 4 字节的int能表示 32 位二进制组合,去掉一个符号位,正数最大到 2^31-1。讲义给出的结论是short < int <= long <= long long,注意中间用的是小于等于——标准只保证这个偏序关系,不保证严格递增。实际写代码时,需要明确宽度就用<cstdint>里的int32_t、int64_t,把平台差异交给头文件处理。
3.2 浮点型:有效数字比字节数更值得记
浮点型用来表示小数,分单精度float和双精度double,字节数分别是 4 和 8,但真正要记的是有效数字:float大约 7 位十进制有效数字,double大约 15 到 16 位。字节数决定占多少内存,有效数字决定这个数能不能算准,后者才是写代码时实际会出问题的地方。
#include <iostream> using namespace std; int main() { float f1 = 3.14f; // 末尾的 f 表明这是 float 字面量 double d1 = 3.14; // 不写后缀的小数字面量默认是 double float f2 = 3e2; // 科学计数法:3 * 10^2,即 300 float f3 = 3e-2; // 3 * 10^-2,即 0.03 cout << f1 << " " << d1 << endl; cout << f2 << " " << f3 << endl; cout << sizeof(f1) << " " << sizeof(d1) << endl; // 4 8 return 0; }后缀f不是可有可无的装饰。3.14默认是double,把它赋给float会发生窄化转换,精度可能被截掉,部分编译器会给出警告;写成3.14f就是直接构造一个float字面量,语义清楚,也没有警告。另一个容易忽略的点是cout的默认行为:它只显示 6 位有效数字,所以float的精度损失在默认输出下常常看不出来,得配合<iomanip>里的setprecision才能看清。这也是很多人误以为「float 和 double 输出一样」的原因。
注意:浮点数不要用
==判断相等。0.1 + 0.2 在二进制浮点表示下并不精确等于 0.3,正确做法是两数相减取绝对值,判断是否小于一个足够小的误差范围。
3.3 字符型:存的是编码,不是字形
char占 1 个字节,用来表示单个字符,语法是char ch = 'a';。这里有三条硬规则:必须用单引号,不能用双引号;单引号内只能有一个字符,不能是字符串;变量里存的不是字符的形状,而是它对应的 ASCII 编码。
#include <iostream> using namespace std; int main() { char ch = 'a'; cout << ch << endl; // 按字符解释,输出 a cout << (int)ch << endl; // 强制转换成整型,输出编码 97 ch = 97; // 直接用编码赋值,输出仍然是 a cout << ch << endl; // ch = "a"; // 错误:双引号是字符串字面量 // ch = 'ab'; // 错误:单引号内只能有一个字符 return 0; }理解「存的是编码」这件事,能一次性解决很多困惑。为什么ch = 97合法?因为 97 就是'a'的编码,赋进去等于赋了同一个字节。为什么cout << (int)ch能打印出 97?因为强制转换没有改变内存,只是换了种解释方式。ASCII 表不需要整张背,记住几个锚点就够用:'0'是 48,'A'是 65,'a'是 97,空格是 32,换行是 10;大写和小写字母相差 32;数字字符'0'到'9'是连续的,所以ch - '0'能把字符数字转成真正的整数,这个技巧在字符串处理题里出现频率极高。
3.4 转义字符与字符串的两种风格
转义字符用来表示那些没法直接敲出来的 ASCII 字符,写法是反斜线加特定字符。讲义列了一整张表,实际高频使用的不多:
| 转义写法 | 含义 | 常见用途 |
|---|---|---|
\n | 换行 | 输出多行文本 |
\t | 水平制表 | 对齐输出 |
\\ | 一个反斜线 | 输出路径、转义本身 |
\r | 回车,回到本行开头 | 进度条类输出 |
\0 | 空字符 | C 风格字符串的结束标志 |
\ddd/\xhh | 八进制 / 十六进制编码 | 按编码直接写字符 |
字符串有两种风格,这是入门阶段最容易混的地方:
#include <iostream> #include <string> // C++ 风格字符串必须引这个头文件 using namespace std; int main() { char str1[] = "hello world"; // C 风格:字符数组,末尾自动补 '\0' string str2 = "hello world"; // C++ 风格:标准库类型,可拼接可赋值 cout << str1 << endl; cout << str2 << endl; str2 += "!"; // string 支持直接拼接 cout << str2 << endl; return 0; }差别的根源在于:C 风格字符串本质是字符数组,长度是字符数加 1(那个隐藏的'\0'是结束标志,cout靠它知道在哪停下),不能直接赋值、不能用==比较内容、不能相加。C++ 风格的string是标准库提供的类,+拼接、==比较、=赋值全都是现成的,代价是要多引一个头文件。做算法题时用string省事,但涉及下标越界和'\0'的题目,就得回到字符数组的视角去看。
3.5 bool 与 cin:输入的边界在哪
bool只有两个值,true本质是 1,false本质是 0,占 1 个字节。它的用处是把判断结果存下来,而不是每次重新算一遍。
输入用cin >>,语法和输出对称,也是流操作符,方向反过来而已。它可以链式使用,把多个读取串在一行:
#include <iostream> #include <string> using namespace std; int main() { int a = 0; double d = 0; char ch = 0; string str; bool flag = false; // 链式读取,每遇到空白字符就切分一次 cin >> a >> d >> ch >> str >> flag; cout << a << " " << d << " " << ch << " " << str << " " << flag << endl; return 0; }输入这部分的重点不在语法,而在边界。cin >>以空白(空格、制表、换行)作为分隔符,所以string读到空格就停,想读整行得用getline。往int里输入字母会让流进入失败状态,之后所有读取都会失效,得先cin.clear()清状态、再清掉缓冲区里的残留字符才能恢复。bool输入时非 0 一律视为true,输入2得到的也是true,打印出来是 1。这几个行为讲义没有逐条展开,但做输入输出题时几乎都会撞上。
4. 运算符:从四则到前后置递增的求值顺序
4.1 算术运算符与两条红线
算术运算符处理四则运算,讲义列了+、-、*、/、%加上前置后置的++、--。符号本身不复杂,复杂的是整数和浮点在除法上的行为差异,以及两条不能碰的红线。
#include <iostream> using namespace std; int main() { int a = 10, b = 3; cout << a / b << endl; // 3,小数部分直接截断,不是四舍五入 cout << a % b << endl; // 1,取模只能用于整型 double d1 = 0.5, d2 = 0.25; cout << d1 / d2 << endl; // 2,浮点除法保留小数部分 // cout << a / 0 << endl; // 运行时错误:除数不能为 0 // cout << d1 % d2 << endl; // 编译错误:浮点不能取模 return 0; }第一条红线是除数不能为零。整数除以零在运行时直接崩溃,浮点除以零会得到inf或nan而不是报错,后者更隐蔽——它不会中断程序,但会把错误值一路传下去。第二条红线是取模只能对整型做,浮点取模编译期就会被拒。整数除法的截断同样值得单独记一笔:10 / 3得 3,10 / 20得 0,两个整数相除永远不会得到小数,想要小数必须先把其中一个转成浮点。负数取模在 C++11 之后有明确规则:余数的符号和被除数一致,所以-7 / 2得 -3,-7 % 2得 -1。这条规则在不同语言里并不统一,跨语言做题时最容易出错。
4.2 赋值与比较:真假在 C++ 里就是 1 和 0
赋值运算符把右边的值写进左边,除了基本的=,还有+=、-=、*=、/=、%=这几个复合形式。a += 2完全等价于a = a + 2,区别只在于可读性和少写一遍变量名——在变量名很长或者表达式很复杂时,复合赋值的优势就体现出来了。
比较运算符有六个:==、!=、<、>、<=、>=,结果是布尔值,而 C++ 用 1 表示真、0 表示假。
#include <iostream> using namespace std; int main() { int a = 10, b = 20; // 括号不能省:<< 的优先级高于 ==,不加括号会先算 cout << a cout << (a == b) << endl; // 0 cout << (a != b) << endl; // 1 cout << (a > b) << endl; // 0 cout << (a < b) << endl; // 1 int c = 10; c += 2; // 等价于 c = c + 2,c 变成 12 c *= 3; // 等价于 c = c * 3,c 变成 36 cout << c << endl; return 0; }括号那一行是血泪经验。cout << a == b不会输出比较结果,因为<<的优先级高于==,编译器先算cout << a,拿这个流对象再去和b比较,报出来的是一大串模板相关的错误,看不出真正原因。凡是把比较结果直接输出的地方,一律加括号,这是成本最低的防御。
4.3 逻辑运算符与短路求值
逻辑运算符有三个:!取反,&&逻辑与,||逻辑或。真值规则讲义用两句话概括得很准:逻辑与同真才为真,其余为假;逻辑或同假才为假,其余为真。取反则是真变假、假变真。
#include <iostream> using namespace std; int main() { int a = 10, b = 0; cout << (a && b) << endl; // 0,一个为假结果就为假 cout << (a || b) << endl; // 1,一个为真结果就为真 cout << !a << endl; // 0,非零取反得 0 cout << !!a << endl; // 1,两次取反把任意非零值归一到 1 return 0; }比真值表更值得记的是短路求值:&&左边为假时,右边根本不会执行;||左边为真时,右边同样跳过。这不是编译器的优化选项,而是语言标准规定的行为,很多惯用写法都依赖它,比如用if (i < n && arr[i] > 0)把越界检查放在前面,后面那次下标访问就永远不会在非法位置发生。反过来,如果右边的表达式带有副作用(自增、赋值、函数调用),短路会让它有时执行有时不执行,这种代码读起来就是灾难,能拆成两条语句就拆开。
4.4 前置与后置递增:一行代码里的顺序差
++和--都有前置后置两种写法,区别只有一个:前置先改变量再参与表达式,后置先参与表达式再改变量。
#include <iostream> using namespace std; int main() { int a = 10; int b = a++; // 先把 10 赋给 b,再让 a 变成 11 cout << a << " " << b << endl; // 11 10 int c = 10; int d = ++c; // 先让 c 变成 11,再把 11 赋给 d cout << c << " " << d << endl; // 11 11 int e = 10; int f = ++e * 10; // e 先自增到 11,再乘 10,f 为 110 int g = 10; int h = g++ * 10; // 先用 10 乘 10,再让 g 变成 11,h 为 100 cout << f << " " << h << endl; return 0; }单独写一行a++;或++a;时两者没有区别,差别只在它们嵌进表达式的时候才显现。真正要避开的是同一个变量在一条表达式里被多次修改,比如i = i++ + ++i——这在 C++11 之前属于未定义行为,不同编译器能给出完全不同的结果,考试题里出现属于故意刁难,工程里出现属于必须重构。
5. 易错点:五处最容易被笔记一笔带过的地方
5.1 类型与字面量上的三个坑
易漏点一:把long的字节数记成固定值。为什么漏?因为讲义表格里short、int、long long都是确定的,唯独long带了个括号说明,复习时眼睛一扫就过去了,脑子里留下的印象是「8 字节」。怎么补?把sizeof当成随身工具,凡是要写和宽度相关的逻辑(位运算、二进制读写、大数边界),先在目标平台上打印一遍;需要跨平台确定宽度时,直接用<cstdint>的int32_t、int64_t,别在long上赌运气。
易漏点二:float字面量漏写f后缀,以及用==比较浮点。为什么漏?因为float f = 3.14;在大多数编译器上只给警告不报错,程序照跑,结果看起来也对,问题被完全掩盖。怎么补?定义float时把f当成语法的一部分写上去;所有浮点比较改成「相减取绝对值,判断是否小于误差范围」,误差范围按业务精度定,一般取 1e-6 对float、1e-9 对double;需要看真实精度时用setprecision输出,不要相信默认的 6 位显示。
易漏点三:字符的单双引号混用与多字符字面量。为什么漏?char ch = "a";的报错信息是「无法从 const char[2] 转换为 char」,初学者读不出这是在说引号用错了;而char ch = 'ab';在多数字面量规则下属于实现定义行为,有的编译器只给警告,值还是「一个」字符,看起来像能跑。怎么补?记一条简单对应:单引号包一个字符是char,双引号包一串是字符串,两者不能互换;编译器给出「数组转字符」类报错时,第一反应就是去检查引号。
5.2 运算与输入上的两个坑
易漏点四:整数除法的截断与负数取模的符号。为什么漏?正数场景下10 / 3 = 3很好理解,一旦换成负数或者混入浮点,脑子里的「除法」直觉就失效了,而讲义示例基本都用正数演示。怎么补?记住两条:两个整数相除,结果永远向零截断,想要小数必须先转浮点;C++11 起余数符号跟随被除数,-7 % 2是 -1 而不是 1。做题时遇到负数取模,先在纸上按规则算一遍再写代码。
易漏点五:cin >>的空白截断与链式读取的残留。为什么漏?讲义示例都是单个变量、单次输入,看起来一切正常;一旦要读带空格的字符串,或者先读数字再读整行,问题才暴露出来。怎么补?读含空格的整行用getline(cin, str);cin >>之后紧接着用getline,要先吃掉缓冲区里剩下的换行符;输入类型不匹配导致流进入失败状态时,先cin.clear()再清空缓冲,否则后面所有读取都会静默失效。
提示:这五条的共同点是「编译能过、程序能跑、结果不对」。凡是这类问题,靠读代码很难发现,靠打印中间值最快——把可疑变量在关键位置输出一次,比盯着屏幕推理半小时有效。
6. 复习清单:三张表加一个自查回路
把这份讲义压成可自查的形式,我整理成三张表。第一张是骨架表:main是唯一入口,#include <iostream>管输入输出,using namespace std是便利不是必须,return 0表示正常退出,system("pause")只能本地用。第二张是类型表:short2 字节、int4 字节、long long8 字节、long看平台、float4 字节 7 位有效数字、double8 字节 15 到 16 位、char和bool都是 1 字节。第三张是运算符表:/在整数间截断、%只对整型、除零是红线、比较返回 0 或 1、&&和||短路、前置后置的区别只在嵌入表达式时才显现。
| 自查项 | 一句话判据 | 不合格的表现 |
|---|---|---|
| 变量是否都初始化 | 每个声明处都有= 初值 | 出现「有时对有时错」的随机结果 |
常量是否用const | 除特殊场景外不出现#define常量 | 宏展开导致优先级错乱 |
| 类型宽度是否验证过 | 关键处有sizeof打印或定宽类型 | 换平台后溢出 |
| 浮点是否用误差比较 | 全篇搜不到浮点的== | 明明相等的两个数判不相等 |
| 输入是否处理了空白 | 读整行用getline,混用前清缓冲 | 第二次读取直接跳过 |
| 输出比较结果是否加括号 | 所有cout << (a == b) | 报一大串模板错误 |
自查的流程建议固定成三步:先通读代码找「未初始化」和「浮点等值比较」这两类硬伤;再在关键分支前后各打一次中间值,确认每个变量的取值范围符合预期;最后把可疑的类型宽度用sizeof打印出来对一遍。这套流程走完,入门阶段九成的问题都能被拦住。
有一个习惯值得单独说:每逢写和宽度、精度、符号相关的代码,我都强制先在纸上写清楚「这个变量最大可能是多少、用什么类型装得下」,再动手敲。因为入门阶段真正难的不是语法记不住,而是语法全对、结果全错——那类问题没有报错信息可查,只能靠事前的判断和事后的打印去兜。把这份讲义的三层地图和五条易错点过一遍,再配一遍自己的代码自查,后面的数组、指针、类才有落脚的地方。希望帮到你。
本文还有配套的精品资源,点击获取