C++ 入门基础:八个语法点,一篇捋直
0 全景:这篇只回答 8 个问题
| 序号 | 问题 | 一句话回答 |
|---|---|---|
| 1 | 后缀改成 .cpp,为什么就算换语言了 |
编译器按后缀挑前端,.cpp 走 C++ 那套解析规则 |
| 2 | namespace 到底解决什么 |
C 只有一个全局域,重名即重定义;命名空间造出互不相干的多个域 |
| 3 | cout 凭什么不用写 %d |
它靠函数重载按实参类型挑版本,printf 只有一个版本 |
| 4 | 缺省参数为什么只能从右往左给 | 实参按位置匹配,右端空着才补得上,中间空着编译器不认 |
| 5 | 同名函数靠什么区分 | 只看形参的个数、类型、顺序;返回值不参与 |
| 6 | 引用和指针差在哪 | 引用是别名、必须初始化、不能改绑;指针是变量、存地址、能改指向 |
| 7 | inline 为什么不保证展开 |
标准只把它当建议,展开与否由编译器自己权衡 |
| 8 | NULL 有什么坑 |
在 C++ 里它就是字面量 0,会把指针重载骗成整型重载 |
1 第一个程序:改的是后缀,换的是编译器
问:把一个 C 的 hello 程序存成 .cpp,它照样跑得出结果,那它算 C++ 程序吗?
答:不算。跑得起来是因为 C++ 兼容了 C 的绝大多数语法。真正的 C++ 版本长这样:
cpp
#include <iostream>
using namespace std;
int main()
{
cout << "hello" << endl;
return 0;
}
比 C 版多了三处变化:<iostream> 是流所在的头文件;using namespace std; 把标准库的域展开,否则每个名字都得写 std::;cout << "hello" << endl 是流插入的链式写法,cout 是对象、<< 是运算符。
问:为什么后缀一改就算换了语言?
答:因为编译器按后缀挑前端 。.c 交给 C 前端,.cpp 交给 C++ 前端,扩展名不同,语法规则就不同。VS 见到 .cpp 自动调 C++ 编译器,Linux 下对应 g++。
问:从源码到程序,中间发生了什么?
答:四步。
#mermaid-svg-FEu9MLmWb0kQDQ0Q{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-FEu9MLmWb0kQDQ0Q .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-FEu9MLmWb0kQDQ0Q .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-FEu9MLmWb0kQDQ0Q .error-icon{fill:#552222;}#mermaid-svg-FEu9MLmWb0kQDQ0Q .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-FEu9MLmWb0kQDQ0Q .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-FEu9MLmWb0kQDQ0Q .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-FEu9MLmWb0kQDQ0Q .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-FEu9MLmWb0kQDQ0Q .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-FEu9MLmWb0kQDQ0Q .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-FEu9MLmWb0kQDQ0Q .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-FEu9MLmWb0kQDQ0Q .marker{fill:#333333;stroke:#333333;}#mermaid-svg-FEu9MLmWb0kQDQ0Q .marker.cross{stroke:#333333;}#mermaid-svg-FEu9MLmWb0kQDQ0Q svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-FEu9MLmWb0kQDQ0Q p{margin:0;}#mermaid-svg-FEu9MLmWb0kQDQ0Q .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-FEu9MLmWb0kQDQ0Q .cluster-label text{fill:#333;}#mermaid-svg-FEu9MLmWb0kQDQ0Q .cluster-label span{color:#333;}#mermaid-svg-FEu9MLmWb0kQDQ0Q .cluster-label span p{background-color:transparent;}#mermaid-svg-FEu9MLmWb0kQDQ0Q .label text,#mermaid-svg-FEu9MLmWb0kQDQ0Q span{fill:#333;color:#333;}#mermaid-svg-FEu9MLmWb0kQDQ0Q .node rect,#mermaid-svg-FEu9MLmWb0kQDQ0Q .node circle,#mermaid-svg-FEu9MLmWb0kQDQ0Q .node ellipse,#mermaid-svg-FEu9MLmWb0kQDQ0Q .node polygon,#mermaid-svg-FEu9MLmWb0kQDQ0Q .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-FEu9MLmWb0kQDQ0Q .rough-node .label text,#mermaid-svg-FEu9MLmWb0kQDQ0Q .node .label text,#mermaid-svg-FEu9MLmWb0kQDQ0Q .image-shape .label,#mermaid-svg-FEu9MLmWb0kQDQ0Q .icon-shape .label{text-anchor:middle;}#mermaid-svg-FEu9MLmWb0kQDQ0Q .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-FEu9MLmWb0kQDQ0Q .rough-node .label,#mermaid-svg-FEu9MLmWb0kQDQ0Q .node .label,#mermaid-svg-FEu9MLmWb0kQDQ0Q .image-shape .label,#mermaid-svg-FEu9MLmWb0kQDQ0Q .icon-shape .label{text-align:center;}#mermaid-svg-FEu9MLmWb0kQDQ0Q .node.clickable{cursor:pointer;}#mermaid-svg-FEu9MLmWb0kQDQ0Q .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-FEu9MLmWb0kQDQ0Q .arrowheadPath{fill:#333333;}#mermaid-svg-FEu9MLmWb0kQDQ0Q .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-FEu9MLmWb0kQDQ0Q .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-FEu9MLmWb0kQDQ0Q .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-FEu9MLmWb0kQDQ0Q .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-FEu9MLmWb0kQDQ0Q .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-FEu9MLmWb0kQDQ0Q .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-FEu9MLmWb0kQDQ0Q .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-FEu9MLmWb0kQDQ0Q .cluster text{fill:#333;}#mermaid-svg-FEu9MLmWb0kQDQ0Q .cluster span{color:#333;}#mermaid-svg-FEu9MLmWb0kQDQ0Q div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-FEu9MLmWb0kQDQ0Q .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-FEu9MLmWb0kQDQ0Q rect.text{fill:none;stroke-width:0;}#mermaid-svg-FEu9MLmWb0kQDQ0Q .icon-shape,#mermaid-svg-FEu9MLmWb0kQDQ0Q .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-FEu9MLmWb0kQDQ0Q .icon-shape p,#mermaid-svg-FEu9MLmWb0kQDQ0Q .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-FEu9MLmWb0kQDQ0Q .icon-shape .label rect,#mermaid-svg-FEu9MLmWb0kQDQ0Q .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-FEu9MLmWb0kQDQ0Q .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-FEu9MLmWb0kQDQ0Q .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-FEu9MLmWb0kQDQ0Q :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 源文件
预处理
展开宏与头文件
编译
语法分析生成汇编
汇编
转机器码
链接
合并目标文件与库
可执行文件
后面要回头找这条链:宏在预处理 阶段就被替换,inline 展开发生在编译 阶段,"内联函数声明定义分离导致找不到符号"是链接阶段的事。三个坑挂在三个不同环节上,混着想想必乱。
2 命名空间:一个 rand 撞出来的语法
问:C 里写一句 int rand = 10; 会怎样?
答:编译不过,报 error C2365: "rand": 重定义;以前的定义是"函数"。rand 是标准库已有的函数名,而 C 的全局域只有一个,所有名字挤在一起,重名就是重定义。
问:那 C++ 怎么解决撞名?
答:造出新的域。C++ 一共有四种域:函数局部域、全局域、命名空间域、类域。只要不在同一个域里,同名也不冲突。
cpp
namespace zs
{
int rand = 10;
int Add(int left, int right) { return left + right; }
namespace ls // 命名空间可以嵌套
{
int rand = 1;
int Add(int left, int right) { return left + right; }
}
namespace ww
{
int rand = 2;
int Add(int left, int right) { return (left + right) * 10; }
}
}
三个 rand 各活各的,Add 也有三份互不干扰的实现。这就是"命名空间"的本意------给名字划出一块独立的域,不是文档分册。
问:有哪些硬规则?
答:三条。只能定义在全局 ,不能写在函数里;可以嵌套 ,访问时用 :: 一层层串下去;多文件里同名的命名空间被视为同一个 ,会自动合并,所以头文件和源文件都写 namespace zs 完全正常。
问:域影响的是运行还是编译?
答:只影响编译期 "找一个名字的声明出自哪里"这套查找逻辑。生命周期是另一码事:只有局部域和全局域影响生命周期,命名空间域和类域不影响。说白了------域管的是"认得出这是谁",不是"它活多久"。
问:为什么不用 using 就找不到里面的东西?
答:因为查找默认只在局部域和全局域走,不会自己钻进命名空间。
#mermaid-svg-saDeHnGc6rRkXYf1{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-saDeHnGc6rRkXYf1 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-saDeHnGc6rRkXYf1 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-saDeHnGc6rRkXYf1 .error-icon{fill:#552222;}#mermaid-svg-saDeHnGc6rRkXYf1 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-saDeHnGc6rRkXYf1 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-saDeHnGc6rRkXYf1 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-saDeHnGc6rRkXYf1 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-saDeHnGc6rRkXYf1 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-saDeHnGc6rRkXYf1 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-saDeHnGc6rRkXYf1 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-saDeHnGc6rRkXYf1 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-saDeHnGc6rRkXYf1 .marker.cross{stroke:#333333;}#mermaid-svg-saDeHnGc6rRkXYf1 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-saDeHnGc6rRkXYf1 p{margin:0;}#mermaid-svg-saDeHnGc6rRkXYf1 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-saDeHnGc6rRkXYf1 .cluster-label text{fill:#333;}#mermaid-svg-saDeHnGc6rRkXYf1 .cluster-label span{color:#333;}#mermaid-svg-saDeHnGc6rRkXYf1 .cluster-label span p{background-color:transparent;}#mermaid-svg-saDeHnGc6rRkXYf1 .label text,#mermaid-svg-saDeHnGc6rRkXYf1 span{fill:#333;color:#333;}#mermaid-svg-saDeHnGc6rRkXYf1 .node rect,#mermaid-svg-saDeHnGc6rRkXYf1 .node circle,#mermaid-svg-saDeHnGc6rRkXYf1 .node ellipse,#mermaid-svg-saDeHnGc6rRkXYf1 .node polygon,#mermaid-svg-saDeHnGc6rRkXYf1 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-saDeHnGc6rRkXYf1 .rough-node .label text,#mermaid-svg-saDeHnGc6rRkXYf1 .node .label text,#mermaid-svg-saDeHnGc6rRkXYf1 .image-shape .label,#mermaid-svg-saDeHnGc6rRkXYf1 .icon-shape .label{text-anchor:middle;}#mermaid-svg-saDeHnGc6rRkXYf1 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-saDeHnGc6rRkXYf1 .rough-node .label,#mermaid-svg-saDeHnGc6rRkXYf1 .node .label,#mermaid-svg-saDeHnGc6rRkXYf1 .image-shape .label,#mermaid-svg-saDeHnGc6rRkXYf1 .icon-shape .label{text-align:center;}#mermaid-svg-saDeHnGc6rRkXYf1 .node.clickable{cursor:pointer;}#mermaid-svg-saDeHnGc6rRkXYf1 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-saDeHnGc6rRkXYf1 .arrowheadPath{fill:#333333;}#mermaid-svg-saDeHnGc6rRkXYf1 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-saDeHnGc6rRkXYf1 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-saDeHnGc6rRkXYf1 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-saDeHnGc6rRkXYf1 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-saDeHnGc6rRkXYf1 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-saDeHnGc6rRkXYf1 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-saDeHnGc6rRkXYf1 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-saDeHnGc6rRkXYf1 .cluster text{fill:#333;}#mermaid-svg-saDeHnGc6rRkXYf1 .cluster span{color:#333;}#mermaid-svg-saDeHnGc6rRkXYf1 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-saDeHnGc6rRkXYf1 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-saDeHnGc6rRkXYf1 rect.text{fill:none;stroke-width:0;}#mermaid-svg-saDeHnGc6rRkXYf1 .icon-shape,#mermaid-svg-saDeHnGc6rRkXYf1 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-saDeHnGc6rRkXYf1 .icon-shape p,#mermaid-svg-saDeHnGc6rRkXYf1 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-saDeHnGc6rRkXYf1 .icon-shape .label rect,#mermaid-svg-saDeHnGc6rRkXYf1 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-saDeHnGc6rRkXYf1 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-saDeHnGc6rRkXYf1 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-saDeHnGc6rRkXYf1 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 有
没有
有
没有
用到一个名字
局部作用域有?
用这个
全局作用域有?
用全局这个
报错
未声明的标识符
所以要么显式 zs::rand,要么把名字展开出来。
问:三种使用方式什么时候用哪种?
答:按冲突风险与书写量取舍。
| 写法 | 例子 | 什么时候用 |
|---|---|---|
| 指定域访问 | zs::rand |
项目里最稳,永不冲突,代价是多打几个字 |
| 展开单个成员 | using zs::STPush; |
某个名字用得极多、又确定不撞,用它省事 |
| 展开整个命名空间 | using namespace zs; |
只适合刷题和写小练习,项目里不要这么干 |
第三条的风险在于:整个域一旦展开,里面所有名字都涌进当前作用域,任何一个和你自己的名字撞上就要排查重定义。
3 输入输出:不用格式串,靠的是重载
问:printf("%d", x) 和 cout << x 的本质区别?
答:printf 只有一个函数,类型信息全靠你手写格式串------写错了要等到运行期才暴露,而且它压根不认识自定义类型。cout 是对象、<< 是运算符,它"认类型"的本质是函数重载:每种类型各有一个版本,编译器按实参类型挑。
cpp
int a = 1;
double b = 2.5;
char c = 'z';
cout << a << '\n' << b << '\n' << c << endl; // 一次打三种类型
不用管谁是什么类型,这在 C 里做不到:格式串少一个 %、用 %d 打 double,结果就错。
问:endl 和 '\n' 有区别吗?
答:有。'\n' 只是换行字符;endl 是操纵符 ,底层是一个函数,干两件事:插换行、刷新缓冲区。所以性能敏感的场合用 '\n' 更省。另外 cin、cout、endl 都带 std::,是因为标准库全部内容都在 std(standard 缩写)里。
问:还有两个容易忽略的细节?
答:一是 #include <iostream> 在 VS 下会间接 包含 <stdio.h>,所以没手动包 stdio 也能用 printf------换个编译器可能就报错,别依赖这个巧合。二是刷题卡时间时可以先 sync_with_stdio(false) 取消 C 与 C++ 缓冲区的同步,再 cin.tie(nullptr) 解除输入输出绑定。
4 缺省参数:省一个实参,代价是顺序死板
问:缺省参数怎么用?
答:在声明或定义时给参数一个缺省值(默认值),调用时没给实参就用它顶上。
cpp
void Func1(int a = 10, int b = 20, int c = 30); // 全缺省
void Func2(int a, int b = 10, int c = 20); // 半缺省
全缺省可以传 0 到 3 个实参;半缺省第一个参数没缺省值,至少得传 1 个。
问:为什么半缺省必须从右往左连续?
答:因为实参按位置匹配形参,没有"空位"这个概念。
cpp
void Bad(int a, int b = 10, int c); // 错:缺省值跳过了左边的 b,要从右往左,不能跳
Func1(1, , 3); // 错:想跳过中间那个,编译器不认
Func1(1, 2); // 对:从左往右依次给,剩下的用缺省值
既然只能从左往右连续给,"缺的那一截"必然在最右端。这条规定不是背下来的,是被传参机制逼出来的结论。
问:声明和定义分离时,缺省值写哪儿?
答:只能写在声明处,且不能两处同时写。
cpp
// 头文件(声明)
void STInit(Stack* ps, int n = 4);
// 源文件(定义):不带缺省值
void STInit(Stack* ps, int n) { /* ... */ }
两处都写报重定义;只在定义处写,调用方看不到缺省值,同样用不了。
问:除了少打几个字,还有什么用?
答:真正有用的地方是一次开够空间,避免扩容。
cpp
STInit(&st2); // 用缺省值 4,插到第 5 个就得扩容
STInit(&st1, 100); // 一口气开到 100,一次扩容都不会发生
这把"默认行为 + 特殊情况覆盖"做成了同一个函数的两种用法。
5 函数重载:区分权在编译器手里
问:两个同名函数,编译器靠什么不搞混?
答:同作用域、同名、形参不同------个数不同、类型不同、顺序不同,任意一条成立就是合法重载。
cpp
void Swap(int* a, int* b);
void Swap(double* a, double* b); // 类型不同
void f();
void f(int x); // 个数不同
void f(int a, char b);
void f(char a, int b); // 顺序不同
调用时编译器先按名字收集候选,再逐个比对实参,挑出唯一最优的那个:
#mermaid-svg-rlGTRIX0zk1DhZsh{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-rlGTRIX0zk1DhZsh .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-rlGTRIX0zk1DhZsh .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-rlGTRIX0zk1DhZsh .error-icon{fill:#552222;}#mermaid-svg-rlGTRIX0zk1DhZsh .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-rlGTRIX0zk1DhZsh .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-rlGTRIX0zk1DhZsh .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-rlGTRIX0zk1DhZsh .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-rlGTRIX0zk1DhZsh .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-rlGTRIX0zk1DhZsh .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-rlGTRIX0zk1DhZsh .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-rlGTRIX0zk1DhZsh .marker{fill:#333333;stroke:#333333;}#mermaid-svg-rlGTRIX0zk1DhZsh .marker.cross{stroke:#333333;}#mermaid-svg-rlGTRIX0zk1DhZsh svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-rlGTRIX0zk1DhZsh p{margin:0;}#mermaid-svg-rlGTRIX0zk1DhZsh .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-rlGTRIX0zk1DhZsh .cluster-label text{fill:#333;}#mermaid-svg-rlGTRIX0zk1DhZsh .cluster-label span{color:#333;}#mermaid-svg-rlGTRIX0zk1DhZsh .cluster-label span p{background-color:transparent;}#mermaid-svg-rlGTRIX0zk1DhZsh .label text,#mermaid-svg-rlGTRIX0zk1DhZsh span{fill:#333;color:#333;}#mermaid-svg-rlGTRIX0zk1DhZsh .node rect,#mermaid-svg-rlGTRIX0zk1DhZsh .node circle,#mermaid-svg-rlGTRIX0zk1DhZsh .node ellipse,#mermaid-svg-rlGTRIX0zk1DhZsh .node polygon,#mermaid-svg-rlGTRIX0zk1DhZsh .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-rlGTRIX0zk1DhZsh .rough-node .label text,#mermaid-svg-rlGTRIX0zk1DhZsh .node .label text,#mermaid-svg-rlGTRIX0zk1DhZsh .image-shape .label,#mermaid-svg-rlGTRIX0zk1DhZsh .icon-shape .label{text-anchor:middle;}#mermaid-svg-rlGTRIX0zk1DhZsh .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-rlGTRIX0zk1DhZsh .rough-node .label,#mermaid-svg-rlGTRIX0zk1DhZsh .node .label,#mermaid-svg-rlGTRIX0zk1DhZsh .image-shape .label,#mermaid-svg-rlGTRIX0zk1DhZsh .icon-shape .label{text-align:center;}#mermaid-svg-rlGTRIX0zk1DhZsh .node.clickable{cursor:pointer;}#mermaid-svg-rlGTRIX0zk1DhZsh .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-rlGTRIX0zk1DhZsh .arrowheadPath{fill:#333333;}#mermaid-svg-rlGTRIX0zk1DhZsh .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-rlGTRIX0zk1DhZsh .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-rlGTRIX0zk1DhZsh .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-rlGTRIX0zk1DhZsh .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-rlGTRIX0zk1DhZsh .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-rlGTRIX0zk1DhZsh .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-rlGTRIX0zk1DhZsh .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-rlGTRIX0zk1DhZsh .cluster text{fill:#333;}#mermaid-svg-rlGTRIX0zk1DhZsh .cluster span{color:#333;}#mermaid-svg-rlGTRIX0zk1DhZsh div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-rlGTRIX0zk1DhZsh .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-rlGTRIX0zk1DhZsh rect.text{fill:none;stroke-width:0;}#mermaid-svg-rlGTRIX0zk1DhZsh .icon-shape,#mermaid-svg-rlGTRIX0zk1DhZsh .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-rlGTRIX0zk1DhZsh .icon-shape p,#mermaid-svg-rlGTRIX0zk1DhZsh .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-rlGTRIX0zk1DhZsh .icon-shape .label rect,#mermaid-svg-rlGTRIX0zk1DhZsh .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-rlGTRIX0zk1DhZsh .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-rlGTRIX0zk1DhZsh .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-rlGTRIX0zk1DhZsh :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 是
否
调用 f(实参)
按名字收集同作用域所有 f
个数匹配
类型匹配 / 能否隐式转换
顺序匹配
恰好一个最优?
选定并生成调用
报歧义或无匹配
问:返回值不同算重载吗?
答:不算。
cpp
int fxx() { return 0; }
void fxx() { } // 报重定义
因为调用处写 fxx(); 时,根本就不知道你要调用,你想要用哪个函数,编译器根本不会去看返回值------靠返回值挑不出任何东西,只能判成重定义。
问:最阴的坑是哪个?
答:缺省参数和重载撞在一起时。
cpp
void f1() { }
void f1(int a = 10) { }
f1(); // 报错:存在歧义,编译器不知道调用谁,根本就知道调用哪个
f1(20); // 没问题,只能匹配带参的版本,调用第二个
注意两点:这两个函数是 合法重载(形参不同);报错发生在调用 时,不是定义时。f1() 既匹配无参版本、又匹配"有参数但用了缺省值"的版本,两个都成立 → 二义。所以:缺省参数会让形参列表在调用侧"看起来"有多个形态,从而破坏重载唯一性的前提。
6 引用:一个变量可以有第二个名字
问:引用是一个新变量吗?
答:不是。它是给已存在的变量取别名,编译器不为引用单独开空间,它和被引用对象是同一块内存。
cpp
int a = 10;
int& b = a; // b 是 a 的别名,不是新对象
b++; // a 也跟着变成 11
问:有哪几条特性?
答:三条,条条是坑。定义时必须初始化 ,不能先声明后绑定;一个变量可以有多个引用 ,int& c = a; 合法;一旦引用某个实体,就再也不能改引别的实体。
第三条最容易误解:
cpp
int x = 1, y = 2;
int& r = x;
r = y; // 这是把 y 的值赋给 x,不是让 r 改去引用 y
结果 x 变成 2、y 不变、r 仍绑在 x 上。一句话记牢:引用只有一次绑定机会,之后不管写什么都是赋值。
问:那为什么链表、树这些结构必须用指针?
答:因为链式结构的核心动作是"改指向"------把前驱的 next 改成指向新结点,本质是改一个指针里存的值 。引用天生改不了指向,做不了这件事。所以"引用能完全替代指针"是句错话:语法上引用更简洁,但改指向这件事只有指针能做。
问:引用到底拿来干什么?
答:四个功能,前两个是主力。
cpp
// 功能1:形参改实参
void Swap(int& a, int& b); // 调用写 Swap(x, y)
void Swap(int* a, int* b); // 调用写 Swap(&x, &y)
// 功能2:减少拷贝
struct Big { int data[100]; int flag; }; // 传值要拷 404 字节
void Touch(Big& b); // 一个字节都不拷
// 功能3:修改返回对象 功能4:减少返回时的拷贝(见下一节)
功能 2 的数字很直观:int data[100] 加 int flag 共 404 字节,按值传参每个实参都要复制一份,引用传参只是绑定。
问:指针的引用是什么?
答:int*&,被引用的对象是一个指针;它和二级指针 int** 完全等价。
cpp
void PushBack(Node*& head, int x)
{
if (head == nullptr)
head = newNode(x); // 这一句改的是外部那个头指针本身
}
形参若写成 Node* head(按值传),改的只是副本,外面纹丝不动;写成 Node** head 也行,只是读起来绕。
问:引用和指针到底差在哪?
答:六条。
| 维度 | 引用 | 指针 |
|---|---|---|
| 本质 | 取别名,语法层不新开空间 | 是一个变量,存地址,要开空间 |
| 初始化 | 必须初始化 | 建议初始化,语法上不强制 |
| 改指向 | 不能 | 可以随便改 |
| 访问 | 直接访问 | 需要解引用 |
sizeof |
是引用类型的大小 | 恒为地址宽度:32 位 4 字节、64 位 8 字节 |
| 安全性 | 很少出空引用、野引用 | 容易出现空指针和野指针 |
问:那引用底层真的不占空间吗?
答:语法层不占,实现层就是指针。看反汇编:
asm
; int x = 1; → mov dword ptr [x], 1
; int& r = x; → lea eax, [x] / mov [r], eax
; int* p = &x; → lea eax, [x] / mov [p], eax
引用和指针生成的指令一模一样。所以准确说法是:引用在语法上是别名,在实现上是用指针做出来的------语法描述你看到的行为,汇编描述机器实际干的事。
7 引用的两个深坑:返回局部变量、const 引用与临时对象
问:函数返回引用,有什么风险?
答:返回局部变量(生命周期)的引用就是野引用。
cpp
int& Bad()
{
int ret = 0;
return ret; // ret 在函数结束时就被销毁了
}
ret 是局部对象,函数返回即销毁,返回它的别名等于返回一个已失效的地址------和野指针一个性质。危险在于它不一定立刻报错:
cpp
int& x = Bad(); // 拿到悬空别名
Other(); // 另一个函数用了同一块栈内存
cout << x; // 可能读到被别人覆盖的值
栈帧被复用的那一刻,这块内存已经被新函数的局部变量填上了别的值。所以这不是"可能出错",而是行为未定义。
问:什么情况下返回引用才对?
答:返回对象的生命周期比函数调用长------比如容器的元素、类的成员。
cpp
int& At(Seq& s, int i) { return s.data[i]; } // 传引用返回:返回别名
At(s, 0) += 10; // 直接改到容器里,合法
对比按值返回:
cpp
int At2(Seq& s, int i) { return s.data[i]; } // 传值返回:先拷贝一份
At2(s, 0) += 10; // 报错:+= 左操作数必须为左值
函数 At2 返回类型为 int 时,产生的是一个即将销毁的临时副本(即右值)。右值只能出现在赋值符号右边,因为没有固定的内存地址,无法被修改,所以 += 报错。而左值指的是拥有明确内存地址、可被修改的对象(如变量、数组元素),可以出现在赋值符号左边。简言之,报错是因为你试图修改一个临时的右值,而非可寻址的左值。
为什么按值返回不能当左值?因为它会产出一个临时对象 装返回值,而临时对象具有常性、不能改。这也正好解释功能 4:operator[] 返回 int&,v[0] = 10 才改得到容器本身。
问:const 引用是不是只能引用 const 对象?
答:不是。常引用既能引用常对象,也能引用普通对象。真正的规则只有一条:权限可以缩小,但不能放大。
cpp
int x = 1;
const int& r1 = x; // 缩小:可读不可改,合法
const int y = 2;
int& r2 = y; // 放大:编译报错
const int& r3 = y; // 合法
指针同理:const int* p1 = &y; 之后 int* p2 = p1; 是放大,错;反过来是缩小,对。而 int z = y; 不算放大------那是拷贝,z 是个全新的独立变量。
问:临时对象到底怎么冒出来的?这块我老是绕不过来。
答:抓一个标准------当编译器需要一个地方暂存表达式的求值结果时,就悄悄造一个没有名字的对象。
cpp
int a = 1;
double d = 3.14;
a * 3; // 乘完的结果放哪?临时对象
d; // 传给 int 形参时,类型转换的中间值放哪?临时对象
关键那句是:C++ 规定临时对象(即将销毁)具有常性。它里面有值,但不可改。拿普通引用去接,等于要把只读的东西变成可写------权限放大了:
cpp
int& rb = a * 3; // 错:临时对象有常性
int& rd = d; // 错:类型转换产生临时对象
const int& ok1 = a * 3; // 对
const int& ok2 = d; // 对
#mermaid-svg-eQOKrrujyDRBBOJW{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-eQOKrrujyDRBBOJW .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-eQOKrrujyDRBBOJW .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-eQOKrrujyDRBBOJW .error-icon{fill:#552222;}#mermaid-svg-eQOKrrujyDRBBOJW .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-eQOKrrujyDRBBOJW .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-eQOKrrujyDRBBOJW .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-eQOKrrujyDRBBOJW .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-eQOKrrujyDRBBOJW .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-eQOKrrujyDRBBOJW .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-eQOKrrujyDRBBOJW .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-eQOKrrujyDRBBOJW .marker{fill:#333333;stroke:#333333;}#mermaid-svg-eQOKrrujyDRBBOJW .marker.cross{stroke:#333333;}#mermaid-svg-eQOKrrujyDRBBOJW svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-eQOKrrujyDRBBOJW p{margin:0;}#mermaid-svg-eQOKrrujyDRBBOJW .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-eQOKrrujyDRBBOJW .cluster-label text{fill:#333;}#mermaid-svg-eQOKrrujyDRBBOJW .cluster-label span{color:#333;}#mermaid-svg-eQOKrrujyDRBBOJW .cluster-label span p{background-color:transparent;}#mermaid-svg-eQOKrrujyDRBBOJW .label text,#mermaid-svg-eQOKrrujyDRBBOJW span{fill:#333;color:#333;}#mermaid-svg-eQOKrrujyDRBBOJW .node rect,#mermaid-svg-eQOKrrujyDRBBOJW .node circle,#mermaid-svg-eQOKrrujyDRBBOJW .node ellipse,#mermaid-svg-eQOKrrujyDRBBOJW .node polygon,#mermaid-svg-eQOKrrujyDRBBOJW .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-eQOKrrujyDRBBOJW .rough-node .label text,#mermaid-svg-eQOKrrujyDRBBOJW .node .label text,#mermaid-svg-eQOKrrujyDRBBOJW .image-shape .label,#mermaid-svg-eQOKrrujyDRBBOJW .icon-shape .label{text-anchor:middle;}#mermaid-svg-eQOKrrujyDRBBOJW .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-eQOKrrujyDRBBOJW .rough-node .label,#mermaid-svg-eQOKrrujyDRBBOJW .node .label,#mermaid-svg-eQOKrrujyDRBBOJW .image-shape .label,#mermaid-svg-eQOKrrujyDRBBOJW .icon-shape .label{text-align:center;}#mermaid-svg-eQOKrrujyDRBBOJW .node.clickable{cursor:pointer;}#mermaid-svg-eQOKrrujyDRBBOJW .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-eQOKrrujyDRBBOJW .arrowheadPath{fill:#333333;}#mermaid-svg-eQOKrrujyDRBBOJW .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-eQOKrrujyDRBBOJW .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-eQOKrrujyDRBBOJW .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-eQOKrrujyDRBBOJW .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-eQOKrrujyDRBBOJW .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-eQOKrrujyDRBBOJW .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-eQOKrrujyDRBBOJW .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-eQOKrrujyDRBBOJW .cluster text{fill:#333;}#mermaid-svg-eQOKrrujyDRBBOJW .cluster span{color:#333;}#mermaid-svg-eQOKrrujyDRBBOJW div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-eQOKrrujyDRBBOJW .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-eQOKrrujyDRBBOJW rect.text{fill:none;stroke-width:0;}#mermaid-svg-eQOKrrujyDRBBOJW .icon-shape,#mermaid-svg-eQOKrrujyDRBBOJW .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-eQOKrrujyDRBBOJW .icon-shape p,#mermaid-svg-eQOKrrujyDRBBOJW .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-eQOKrrujyDRBBOJW .icon-shape .label rect,#mermaid-svg-eQOKrrujyDRBBOJW .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-eQOKrrujyDRBBOJW .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-eQOKrrujyDRBBOJW .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-eQOKrrujyDRBBOJW :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 有
没有
表达式求值
有现成变量装结果吗?
直接用那个变量
造一个临时对象
暂存中间结果
C++ 规定:临时对象具有常性
只能用 const 引用或 const 指针接
普通引用接 → 权限放大 → 编译报错
问:绕明白这个有什么用?
答:最大用处是形参写 const 引用,一份签名接住五种实参。
cpp
void Show(const int& v);
int x = 1;
const int y = 2;
Show(x); // 普通变量:权限缩小
Show(y); // 常变量:权限不变
Show(10); // 字面量:临时对象
Show(x * 10); // 表达式结果:临时对象
Show(3.14); // double 转 int:类型转换的临时对象
前两个是"权限缩小",后三个是"临时对象本来就只能被常引用接"。一份签名通吃,唯一代价是函数里不能改 v------这也是标准库大量用 const& 形参的原因。
8 inline:编译器只是收到一个建议
问:inline 到底干什么?
答:把函数调用 换成函数体 。编译器在调用点把被调函数的代码就地摊开,于是省掉两笔开销:一条 call 跳转,以及进函数时要建、出函数时要拆的那套栈帧。函数本体并没有消失,只是每个调用点都自带了一份副本------所以它省的是"调用的成本",不是"代码的体积"。
问:为什么要有 inline?
答:为了替掉 C 的宏函数。宏有两个优点------预处理阶段就地替换 、不建立栈帧,但缺点更致命:容易写错、没有类型安全检查、没法调试。
问:宏到底多容易错?
答:一个加法宏能写出五个版本:
cpp
#define ADD1(int a, int b) return a + b; // 错:宏参数没有类型这一说,不用return
#define ADD2(a, b) a + b // 错:优先级不对,要考虑运算符优先级
#define ADD3(a, b) a + b; // 错:多带了一个分号
#define ADD4(a, b) (a + b) // 半对:外层括号有了,内层没有
#define ADD5(a, b) ((a) + (b)) // 对,写法更加严谨
展开看看错在哪:
cpp
ADD2(1, 2) * 5 // 展开成 1 + 2 * 5 = 11,不是 15
ADD4(x | y, x & y) // 展开成 x | y + x & y,语义全歪
所以宏的每个参数都要包一层括号,整体再包一层。少一层就是一类 bug。
问:那 inline 一定展开吗?
答:不一定。inline 对编译器而言只是一个建议。 标准没规定什么情况下必须展开,换编译器、换优化级别,结果都可能不同。适合频繁调用的短小函数 ;递归函数、代码量大的函数加了也会被忽略;VS 的 Debug 版默认不展开(方便调试),想让它展开得改编译设置。
问:为什么 inline 不能声明定义分离到两个文件?
答:这是最容易踩的坑。inline 函数一旦在调用处被就地展开,就没有独立的函数地址 ,不会产出那个符号;而另一个文件里 #include 到的只是一行声明,链接阶段按名字去找定义,找不到------报 无法解析的外部符号。
cpp
// 头文件里必须就地给出定义,别只留声明
inline void f1(int i) { /* ... */ }
// 非内联的普通函数可以分离
void f2(int i); // 头文件只声明
// 源文件里给定义 → 链接时 call f2 能找到
对照着看就清楚了:f1(1) 在编译阶段被直接替换成函数体;f2(2) 变成一条 call f2,地址留给链接器去找。
问:inline 能省多少,值不值得到处加?
答:算笔账。函数编译成 100 行指令、被 10000 个不同位置调用------展开占 10000 × 100 行;不展开占 10000 + 100 行(10000 条 call 加本体 100 行)。所以 inline 不是越多越好:小函数省掉一次栈帧建立与销毁,收益明显;大函数展开会把代码体积撑爆,反过来伤指令缓存。这也正是它只是"建议"的原因------这件事本来就该编译器按函数大小自己拿主意。
9 nullptr:NULL 其实是 0
问:NULL 不就是空指针吗?
答:在 C++ 里它只是字面量 0。标准头文件里大致这么写:
cpp
#ifdef __cplusplus
#define NULL 0
#else
#define NULL ((void*)0)
#endif
C 里 NULL 是 ((void*)0),因为 C 允许 void* 隐式转成任意指针类型;C++ 不允许这种转换,所以干脆定义成 0------一个整数字面量。
问:这会惹什么祸?
答:重载会挑错。
cpp
void f(int x) { /* 整型版本 */ }
void f(int* p) { /* 指针版本 */ }
f(0); // 走 f(int)
f(NULL); // 还是走 f(int)------因为 NULL 就是 0
f(nullptr); // 走 f(int*),这才是本意
本意想调指针版本,结果调了整型版本。强行 f((void*)NULL) 则报 error C2665: "f": 2 个重载中没有可以转换所有参数类型,因为 void* 转不到 int。
问:nullptr 特殊在哪?
答:它是 C++11 的关键字,有自己独立的类型,能隐式转换成任意指针类型,但不能转换成整数类型 。因为不能转整数,它永远不会和整型重载混在一起------这才是它比 NULL 强的地方,不是"写起来更现代"这种理由。
10 面试速记卡
| 问题 | 一句话回答 |
|---|---|
| 编译四阶段 | 预处理 → 编译 → 汇编 → 链接 |
.cpp 与 .c 的差别 |
决定编译器走哪个前端,与代码内容无关 |
| 命名空间的本质 | 造一个独立的域,域内名字与全局域互不干扰 |
| 四种域 | 函数局部域、全局域、命名空间域、类域 |
| 哪个域影响生命周期 | 只有局部域和全局域,命名空间域和类域不影响 |
namespace 能写在函数里吗 |
不能,只能定义在全局 |
多文件同名 namespace |
视为同一个,自动合并 |
| 三种使用方式 | 指定访问 / using 单个成员 / using namespace 全展开 |
cout 为什么能自动识别类型 |
靠函数重载,每类型一个版本 |
endl 与 '\n' |
endl 多刷新一次缓冲区 |
| 半缺省的顺序 | 从右往左连续缺省,不能跳跃 |
| 缺省值写在哪 | 声明与定义分离时只能写在声明处 |
| 重载的判定依据 | 形参的个数、类型、顺序;与返回值无关 |
f1() 与 f1(int=10) |
是合法重载,但 f1() 调用报二义性 |
| 引用的本质 | 别名,语法层不新开空间,实现层是 lea + mov |
| 引用能改绑定吗 | 不能,r = y 是赋值不是改绑 |
| 为什么链表要用指针 | 链式结构必须能改指向,引用改不了 |
int*& 是什么 |
指针的引用,与 int** 等价 |
| 传值返回为什么不能当左值 | 返回值是临时对象,临时对象具有常性 |
| 返回局部变量的引用 | 野引用,行为未定义,可能读到被覆盖的值 |
const 引用的规则 |
权限可以缩小,不能放大 |
| 临时对象何时产生 | 编译器需要暂存表达式求值结果时 |
const& 形参的好处 |
不拷贝,且能接住变量、常量、字面量、表达式、转换结果 |
inline 是强制的吗 |
不是,只是建议,标准不规定是否展开 |
inline 为什么不能声明定义分离 |
展开后没有函数地址,链接阶段找不到符号 |
NULL 在 C++ 里是什么 |
字面量 0 |
nullptr 为什么更安全 |
不能隐式转成整数类型,不会误配整型重载 |
11 追问预演
Q1:既然引用底层就是指针,那用引用还有什么意义?
意义在约束,不在实现。指针能改指向、能为空、能越界,这三条自由都是代价;引用把这三条全砍掉。当你需要"只借用不拥有、绝不改指向"时,它能让编译器替你守住边界。
Q2:为什么"引用是更安全的指针"这句话不严谨?
因为它暗示两者可以互相替代。实际上引用做不到指针能做的事------改指向、存 nullptr、做链式结构。准确说法是:引用是"只借不改"时的首选,指针是"必须操作地址本身"时的唯一选择。
Q3:sizeof(引用) 为什么不是指针的 8 字节?
sizeof 求的是"这个类型的对象占多大",而引用的类型就是被引用对象的类型。int& 的 sizeof 是 4,和 int 一样;指针的 sizeof 才是地址宽度。
Q4:为什么 const int& r = 3.14; 能过,int& r = 3.14; 不能?
3.14 是 double,绑到 int& 上要先转成 int,转换的中间值只能放在临时对象里,而临时对象具有常性,普通引用接它就是放大权限。加 const 之后权限是缩小的,所以合法。顺带一个坑:const int& r = 3.14; 拿到的值是 3,不是 3.14。
Q5:缺省参数为什么不让两处都写?
两处都写而值不一样时,编译器不知道该信哪个。与其定一套"以谁为准"的复杂规则,标准直接禁止两处同现、规定由声明处给,调用方只要看声明就够用。
Q6:重载和缺省参数一起用,为什么会二义?
重载要求"能挑出唯一匹配",缺省参数却让同一份形参列表能匹配多种实参个数。两者叠加就会出现"两个候选都成立",唯一性被破坏。所以实际项目里,要么带缺省参数、要么拆成多个重载,别混着来。
Q7:inline 加了却报"无法解析的外部符号",为什么?
因为函数体放在源文件、头文件只留声明。调用方在编译阶段看不到函数体,无法展开,只能生成一条 call;而内联函数被展开后不产出独立符号,链接时就找不到。修法只有一个:把定义也放进头文件。
Q8:NULL 和 0 在 C 里为什么没问题?
C 没有重载,f(0) 和 f(NULL) 都只有唯一候选,不存在挑错的可能。C++ 有了重载,"NULL 是 0"这个事实才变成隐患。同一个宏,两种语言后果不同,根子在重载。
12 易错点对照表
| 你可能以为 | 其实是这样 | 错在哪 |
|---|---|---|
存成 .cpp 就是在写 C++ |
只是换了编译器前端,语法还得用 C++ 那套 | 把"能跑"当成"是这门语言" |
| 命名空间是为了分文件 | 是为了给名字划出独立的域 | 混淆了物理组织和名字查找 |
| 命名空间域也影响生命周期 | 只有局部域和全局域影响生命周期 | 把"编译期查找"和"运行期存活"混成一件事 |
using namespace std; 可以随手用 |
全展开会撞名,项目里禁止 | 只看到书写方便,没算冲突成本 |
endl 和 '\n' 一样 |
endl 多一次刷新缓冲区 |
漏掉刷新这个动作 |
| 半缺省可以随便挑几个参数给默认值 | 必须从右往左连续缺省 | 忘了实参按位置匹配 |
| 缺省参数声明和定义都写更保险 | 两处同现直接报错,只能写声明处 | 把"重复确认"当成稳妥 |
| 返回值不同也算重载 | 不算,编译器在调用处看不到返回值 | 误以为返回值参与挑选 |
f1() 和 f1(int=10) 不能共存 |
能共存,是合法重载,报错发生在调用 f1() 时 |
把"调用歧义"错记成"定义非法" |
| 引用和指针只是写法不同 | 引用不能改绑、必须初始化,链式结构做不了 | 忽略语义差异,只看语法像 |
r = y 是让 r 改去引用 y |
是把 y 的值赋给 r 绑定的那个变量 |
把赋值当成重新绑定 |
| 引用真的不占任何内存 | 语法层不占,实现层就是 lea + mov |
把语言抽象当成物理事实 |
传值返回也能 += 1 |
返回值是临时对象,具有常性,不是左值 | 忘了临时对象不可写 |
| 返回局部变量的引用只是"不规范" | 是行为未定义,能读到被覆盖的脏值 | 低估悬空引用的破坏力 |
const int& r = x 会把 x 变成常量 |
x 仍可改,只是 r 这条路只读 |
把"这条路的权限"当成"对象的属性" |
int z = y;(y 是 const)是权限放大 |
是拷贝,z 是独立变量,不存在权限问题 |
混淆"指向同一块内存"和"复制一份" |
inline 加上就一定展开 |
只是建议,递归和大函数会被忽略 | 把修饰符当成命令 |
inline 可以声明放头文件、定义放源文件 |
会链接失败,定义必须和声明一起放头文件 | 沿用了普通函数的分文件习惯 |
NULL 是空指针 |
在 C++ 里是字面量 0 |
被名字骗了,没看宏定义 |
nullptr 比 NULL 好的原因是"更现代" |
真正原因是它不能隐式转换成整数 | 记住结论没记住原因 |
13 下一篇讲什么
这一篇把八个语法点各自捋了一遍,但它们都还在"用别人的类型"------int、double、struct。接下来要面对的是:自己定义一个类型,并且让它像内建类型一样好用。
下一篇从 class 与 struct 的分叉开始:访问限定符到底在保护什么、封装为什么不是"把成员藏起来"这么简单、类域怎么和命名空间域区分、为什么成员函数写在类里默认就是 inline。
预告一句:这一篇讲的 inline、引用传参、const 成员函数,到那一篇会全部以新的身份再出现一次。