)
一、认识C#includeiostream int main() { std::coutHello worldstd::endl; return 0; }#include 是包含的意思作用是把你需要用到的工具提前拿进来。iostream 是一个头文件工具库里面提供了输入输出的功能。std::cout 标准输出cout character output相当于控制台屏幕。输出运算符可以理解为把右边的数据送到左边。数据流向右边 → 屏幕。std::endl表示换行end line。结尾的 ; :C 中,每条语句必须以分号结尾就像中文句子的句号。二、命名空间namespace2.1 定义一个命名空间namespace QingHua { void student() { std::cout我是清华里的学生std::endl; } } namespace BeiDa { void student() { std::cout我是北大里的学生std::endl; } }上面定义了两个命名空间里面各有一个同名函数 student。因为它们住在不同的学校里所以不冲突。2.2 使用命名空间里的东西作用域解析运算符 :::: 读作作用域解析运算符可以理解为的。QingHua::student();//调用QingHua里的studentBeiDa::student();//调用BeiDa里的studentQingHua::student() 读作“QingHua的student。:: 前面的部分叫限定符用来告诉编译器去哪找这个名字。2.3 偷懒方法一using namespace 名字;每次写 std::cout 很麻烦可以一次性把整个命名空间里的名字全部搬出来#includeiostream using namespace std; // 把 std 里所有名字都引入 int main() { cout不用写std::了endl; return 0; }风险提示using namespace std; 是把 std 里的**所有**名字都搬进来如果自己定义的名字恰好和 std 里的重名就会产生二义性冲突。小练习里用用无所谓**大型工程里不建议使用**。2.4 偷懒方法二using 名字::成员;推荐#includeiostream using std::cout;// 只引入 cout using std::endl;// 只引入 endl int main() { cout只搬需要的endl; return 0; }2.5 命名空间的嵌套namespace A { namespace B { int num10; } } int main() { std::coutA::B::numendl;//输出10 return 0; }2.6 本章小结与易错点- 命名空间解决的是**名字冲突**问题。- 访问方式命名空间::名字。- using namespace std; 方便但危险using std::cout; 更安全。- 一个命名空间可以分多段定义同名 namespace 会被合并例如namespace Test{int a1;} namespace Test{int b2;}//和上面同一个命名空间三、输入输出3.1 输出cout 和 #includeiostream int main() { std::cout100; // 输出整数 std::cout3.14; // 输出小数 std::coutA; // 输出字符注意是单引号 std::coutHello; // 输出字符串双引号 return 0; }连续输出 可以串成一串因为 cout x 这个表达式的结果还是 cout可以继续 y#includeiostream int main() { int age18; std::cout我今年age岁std::endl;//输出我今年18岁 return 0; }记忆技巧 的尖指向 cout表示数据**流向屏幕**。3.2 输入cin 和 #includeiostream int main() { int age; std::cout请输入你的年龄; std::cinage;// 从键盘读入一个整数存进 age std::cout你的年龄是age岁std::endl; return 0; }运行效果请输入你的年龄20你的年龄是 20 岁连续输入一次读入多个变量用空格或回车分隔#includeiostream int main() { int a,b; std::cout请依次输入两个数; std::cinab; std::cout“两个数的和是abstd::endl; return 0; }结果记忆技巧 的尖指向变量表示数据从键盘**流进变量**。3.3 换行的两种方式cpp std::cout 第一行 std::endl; // 方式一endl std::cout 第二行\n; // 方式二\n 转义字符 - std::endl换行并且会刷新缓冲区把内容立刻显示出来速度稍慢。- \n只换行不刷新缓冲区速度更快。写程序时两者都常用随便用哪个都行。3.4 常用的转义字符转义字符以 \ 开头表示特殊含义的字符放在双引号或单引号里。| 写法 | 含义 ||------|------|| \n | 换行 || \t | 水平制表符跳到下一个制表位常用来对齐 || \\ | 输出一个反斜杠 \ || \ | 输出一个双引号 || \ | 输出一个单引号 |示例cpp std::cout 他说\你好\\n; // 输出他说你好并换行 std::cout A\tB\tC\n; // 输出A B C std::cout 路径是 C:\\test\\a.txt\n; // 输出路径是 C:\test\a.txt 3.5 易错点| 错误 | 原因/后果 ||------|-----------|| cin a 时输入了字母a 是 int | 读取失败a 的值不确定程序可能表现异常 || cout 与 cin 方向写反 | 编译报错 || 字符串用了单引号 hello | 编译报错单引号只能包一个字符 || 忘了 #include iostream | cout/cin 找不到编译报错 |四、缺省参数默认参数4.1 什么是缺省参数缺省参数又叫**默认参数**给函数参数提前准备一个默认值调用时如果不传这个参数就用默认值。生活类比你去奶茶店点单店员问要加糖吗如果你不说话默认就按半糖做。这个半糖就是缺省参数。4.2 基本用法#includeiostream void introduce(std::string name,int age18) { std::coutname今年age岁std::endl; } int main() { introduce(张三,24);//传了年龄用24 introduce(李四);// 没传年龄用默认值 18 return 0; }输出张三今年24岁李四今年18岁4.3 重要规则一默认值必须从右往左连续给cpp void f(int a, int b 2, int c 3); // ✅ 正确b、c 从右往左连续 void g(int a 1, int b, int c 3); // ❌ 错误a 有默认值但它右边的 b 没有 void h(int a 1, int b 2); // ✅ 正确全部都有默认值全缺省 **为什么** 因为调用时只能省略**末尾**的参数cpp f(10); // 相当于 f(10, 2, 3) f(10, 20); // 相当于 f(10, 20, 3) f(10, 20, 30);// 三个都传 // 不存在只省略中间 b的写法所以中间参数不能有默认值而右边没有4.4 重要规则二声明和定义不能重复给默认值#includeiostream void show(int x100);//声明里给了默认值 int main() { show(); //调用时不传参使用默认值 100 show(50); //传50 return 0; } void sohw(int x) // 定义里不再写默认值 { std::coutxstd::endl; }如果两边都写 100编译器会报重定义默认参数的错误。4.5 易错点- 默认值要写**从右往左连续**中间不能有没默认值的参数。- 同一个参数默认值只给一次。- 默认值要用常量或表达式不能用局部变量#includeiostream int main() { int n 10; void f(int a n); // ❌ 错误n 是局部变量不能当默认值 return 0; }五、函数重载5.1 什么是函数重载函数重载overload允许**多个同名函数**同时存在只要它们的**参数列表不同**参数个数、类型或顺序不同编译器就能根据调用时传入的参数自动选择合适的那个。生活类比同一个动词开可以说开门、开窗、开灯对象不同但都是开。函数重载就像给同一个函数名配多种参数配方。5.2 基本示例#includeiostream int add(int a,int b)// 版本一两个 int { return ab; } double add(double a,double b)// 版本二两个 double { return ab; } int add(int a,int b,int c)// 版本三三个 int { return abc } int main() { std::coutadd(1,2)std::endl; // 调用版本一输出 3 std::coutadd(1.5,2.5)std::endl; // 调用版本二输出 4 std::coutadd(1,2,3)std::endl; // 调用版本三输出 6 return 0; }同一个名字 add编译器会根据实参的类型和个数自动挑最合适的那个这个过程叫**重载决议**。5.3 重载的成立条件**只有参数列表不同才算重载**。参数列表不同指以下三种情况之一1. **参数个数**不同f(int) 和 f(int, int) ✅2. **参数类型**不同f(int) 和 f(double) ✅3. **参数顺序**不同f(int, double) 和 f(double, int) ✅**注意只有返回值不同不算重载**cpp int f(int a); double f(int a); // ❌ 错误参数列表相同只有返回值不同无法区分 **为什么** 调用 f(10) 时编译器只看参数列表来分辨调用哪个函数根本不知道你打算用哪个返回值所以无法区分。5.4 类型不匹配时的隐式转换调用重载函数时如果没有完全匹配的版本编译器会尝试做**隐式类型转换**去匹配这可能带来意外cpp add(1, 2.5); 调用 add(1, 2.5) 时没有 add(int, double) 版本。编译器发现 add(int,int) 需要把 2.5 转成 int精度丢失add(double,double) 需要把 1 转成 double。两个版本都能通过转换匹配此时编译器**不知道该选谁**会报歧义ambiguous错误。所以重载时要**小心隐式转换造成的歧义**。5.5 重载 缺省参数 大坑缺省参数和函数重载组合使用时非常容易产生歧义#includeiostream void f(int a) { std::coutf(int)std::endl; } void f(int a,int b10) { std::coutf(int int)std::endl; } int main() { f(1); // ❌ 编译错误调用有歧义 // 既可能是 f(int) // 也可能是 f(int,int) 的第二个参数用了默认值 return 0; }编译器懵了f(1) 两个函数都能对上。所以**定义重载函数时尽量不要和缺省参数混用**容易踩坑。六、引用6.1 什么是引用**引用reference就是给变量起一个别名外号**。外号和本名指向的是**同一个东西**不是复制品。生活类比你的大名是王小明小名叫小明。不管喊哪个名字指的都是你**同一个人**。#includeiostream int main() { int a10; int ba;// b 是 a 的引用别名注意 写在类型和名字之间 std::couta a,b bstd::endl;// a 10, b 10 b20; // 通过别名修改 std::couta astd::endl; // a 20a 跟着变了 return 0; }关键点b 20 之后 a 也变成了 20因为它们**共享同一块内存**。6.2 引用的三条铁律1. **引用必须初始化**声明引用时就得指出它是谁的别名不能先声明后绑定。cpp int a 10; int b a; // ✅ 正确 int c; // ❌ 错误引用必须初始化 2. **引用一旦绑定终身不换**别名认定了本名不能再改成别人的别名。cpp int a 10, x 99; int b a; // b 是 a 的别名 b x; // ⚠️ 这不是让 b 变成 x 的别名而是把 x 的值 99 赋给 a // 现在 a 和 b 都是 99但 b 仍然是 a 的别名 3. **引用不是独立变量**对引用的所有操作实际上都是对它绑定的那个变量操作。6.3 引用 vs 指针简单对比| 比较项 |引用| | 指针 || 必须初始化 | 必须 | | 可以不初始化但危险 || 能否改指向 | 不能 | | 能 || 用法 | 像普通变量一样直接用 | | 要用 * 解引用、 取地址 || 有空值吗 | 没有空引用 | 有空指针 nullptr |一句话**引用用起来更像变量本身指针用起来更麻烦但更灵活**。入门阶段先掌握引用即可。6.4 引用做函数参数重点普通传参是**值传递**实参被复制一份给形参函数里改的是复制品外面的变量不变。而且如果参数很大比如大对象复制成本很高。引用传参解决了这两个问题#includeiostream void swap(int x,int y) { int tmpx; xy; ytmp; } int main() { int a3,int b5; swap(a,b);// 直接把 a、b 传进去不复制 std::couta bstd::endl; return 0; }- 传引用**不复制**数据效率高。- 函数内对引用的修改**直接作用于实参**所以 swap 才能真的交换 a 和 b。6.5 引用做返回值了解 大坑函数可以返回引用但**绝对不能返回局部变量的引用**#includeiostream int func() { int n30; // n 是局部变量 return n; // ❌ 大错特错 } int main() { int rfunc();// r 绑定了一个已经死亡的变量 return 0; }**为什么错** 局部变量 n 在函数结束时就被销毁了返回它的引用等于给一个已经拆除的房子发门牌号之后访问就是悬空引用程序行为无法预测。合法的做法是返回**在函数结束后仍然存在的变量**的引用例如静态变量、全局变量、或传入的引用参数cpp int func(int x) { x 1; return x; // ✅ x 是别人传入的函数结束后还活着 } 初学阶段记住一句话**千万别返回局部变量的引用**就躲开了最大的坑。6.6 易错点- 引用忘了初始化 → 编译错误。- 把 int b a; 写成 int b a;也可以 贴着类型或名字都行效果一样但要和取地址的 区分开**声明语句里的 是引用表达式里的 是取地址**。- 试图让引用改嫁重新绑定→ 实际是赋值。- 返回局部变量的引用 → 未定义行为。七、 const 引用常引用7.1 什么是 const 引用const 是常量的意思。**const 引用 只读别名**可以通过别名看但不能通过别名改。生活类比图书馆的书可以借阅看但不能在上面写字涂改。const 引用就是一张只读借书证。#includeiostream int main() { int a10; const int ba; //b是a的别名 std::coutbstd::endl;//可以读输出10 //b20;// ❌ 编译错误const 引用不能用来修改 a20;// ✅ 但通过本名 a 修改是允许的 std::coutbstd::endl;//输出20b和a是同一个东西) return 0; }7.2 权限规则只能缩小不能放大把变量的可读可写看成一种权限- 普通引用int 读写权限- const 引用const int 只读权限**权限只能缩小不能放大**cpp int a 10; const int b 20; const int r1 a; // ✅ 可读写的 a → 只读的 r1权限缩小允许 int r2 b; // ❌ 编译错误只读的 b → 可读写的 r2权限放大禁止 第二行错误的原因很好理解如果允许就能通过 r2 修改 b那 b 的 const 就形同虚设了。7.3 const 引用的超能力绑定临时变量普通引用不能绑定字面量常量和表达式结果**const 引用可以**cpp int r1 10; // ❌ 编译错误普通引用不能绑定字面量 const int r2 10; // ✅ 正确 int a 5, b 3; int r3 a b; // ❌ 编译错误表达式结果不是有名变量 const int r4 a b; // ✅ 正确 **背后的原理**10 或 a b 的结果没有名字编译器会悄悄创建一个临时变量存起来再让 const 引用绑定它。因为临时变量转瞬即逝只能看不能改才是安全的所以只有 const 引用有这个待遇。7.4 为什么函数参数爱用 “const 引用”重点把 const 和引用结合起来做参数是 C 最常用的技巧#includeiostream #includestring void print(const std::string s)//const引用参数 { std::coutsstd::endl; } int main() { std::string name张三; print(name); // ✅ 传变量不复制效率高 print(李四); // ✅ 传字面量const 引用可以绑定临时对象 return 0; }三大好处1. **不复制**引用传参没有拷贝开销传大对象比如长字符串效率高。2. **只读保护**const 保证函数内改不了实参防止误改。3. **万能接收**既能接收变量也能接收字面量、表达式结果普通引用做不到。经验法则**只读用 const T要修改用 T小而简单的类型如 int直接按值传**。7.5 易错点- 想通过 const 引用修改值 → 编译错误。- 普通引用绑定 const 变量 → 编译错误权限放大。- 以为 const 引用是复制了一份数据 → 错它还是别名本名变了它也会变。八、inline 内联函数8.1 函数调用是有成本的每次调用函数程序都要做一系列准备工作保存现场、跳转到函数代码、执行、跳回来、恢复现场……函数本身越短小这些准备工作占的比例就越大。inline内联的意思是**建议编译器把这个函数的代码直接展开到调用处**省掉跳来跳去的开销。就像把一句话原文照抄进文章里而不是写成见附录三。8.2 用法在函数定义前加 inline 关键字即可#includeiostream inline int add(int a,int b) { return ab; } int main() { std::coutadd(3,4)std::endl; // 如果编译器采纳了 inline上面这行相当于 // std::cout (3 4) std::endl; return 0; }8.3 重要认知inline 只是建议- inline 是给编译器的**建议**不是命令。编译器会自己判断函数体太长、有递归、有循环等都可能拒绝内联仍然按普通函数调用。- 反过来即使不写 inline现代编译器开启优化后也可能自动内联某些短小的函数。- 所以 inline 关键字对**语义程序行为没有任何影响**纯粹是性能优化提示。8.4 inline 和宏的区别C 语言时代人们常用带参宏来展开代码但它有很多坑inline 就是为了替代它| 对比项 | 宏 #define | inline 函数 || 本质 | 文本替换 | | 真正的函数 || 类型检查 | 无随便传什么都行 | | 有编译器严格检查参数类型 || 调试 | 很难调试 | | 正常调试 || 求值次数 | 参数可能被重复求值 | | 只求值一次 |经典的宏坑#define SQUARE(x) ((x) * (x)) SQUARE(a); // 展开成 ((a) * (a))a 被加了两次行为诡异 inline int square(int x) { return x * x; } square(a); // 正常a 只加一次 结论**能用 inline 函数的地方就别用宏。**九、nullptr9.1 问题背景NULL 的先天缺陷在传统 C/C 中表示空指针用 NULL。但 NULL 的本质是个0cpp #define NULL 0 // 典型定义NULL 就是整数 0 // 或者 #define NULL ((void*)0) 既然它是 0就会产生**类型歧义**——尤其在函数重载的时候#includeiostream void func(int x) { std::coutint 版本std::endl; } void func(void* p) { std::cout指针 版本std::endl; } int main() { func(0); //输出int 版本0 是整数毫无疑问 func(null); //输出int 版本 本意想调用指针版本却匹配到了 int return 0; } 程序员想用 NULL 表示空指针结果被当成整数 0 了。这就是 NULL 的致命缺陷。9.2 nullptr 登场C11 引入了 **nullptr**它是一等公民的空指针常量**类型是 std::nullptr_t**专门表示空指针不再是个整数。#includeiostream void func(int x) { std::coutint 版本std::endl; } void func(void* p) { std::cout指针 版本std::endl; } int main() { func(0); // 输出int 版本 func(nullptr); // 输出指针版本 ✅ 完美匹配 return 0; }nullptr 还能**隐式转换**成任意类型的空指针cpp int* p1 nullptr; // ✅ double* p2 nullptr; // ✅ char* p3 nullptr; // ✅ int n nullptr; // ❌ 编译错误nullptr 不能转成整数这正是我们要的 9.3 判断空指针#includeiostream int main() { int* pnullptr; if(pnullptr)// 判断是否为空指针 { std::coutp为空std::endl; } if(p) // 指针非空则为真 { std::coutp不为空std::endl; } return 0; }9.4 使用建议- **永远使用 nullptr不要再用 NULL**除非要兼容很老的代码。- 需要 C11 或更高标准现代编译器默认都支持。