C++01_入门基础复习

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 成员函数,到那一篇会全部以新的身份再出现一次。

相关推荐
黑马程序员毕设1 小时前
基于 Java 的餐饮点餐系统设计与应用报告
java·开发语言
1-1=02 小时前
C# 委托与事件
开发语言·c#
Fuyo_11193 小时前
C++ 运行时多态与虚函数机制全面剖析【面试重点】
开发语言·c++·面试
w_zero_one3 小时前
递归算法(一)
java·开发语言·算法
Julien20044 小时前
管理机密(一)
运维·服务器·开发语言·学习方法
Chen_harmony4 小时前
七、STL与string类
开发语言·c++
沐晓时光4 小时前
C语言入门,字符串和字符串函数知识讲解
c语言·开发语言
xxxiugou1235 小时前
C++命名空间与引用全解析
开发语言·c++
用户7624117567056 小时前
第十五章 人工智能基础
c++