👋 欢迎阅读

📑 目录
一、前置讲解
在进入正文前,先花 10 秒了解本篇会反复用到的核心概念,带着印象去读正文,学习效率更高。
🧠 前置知识一:作用域
标识符能被访问的代码区域 ,它同时决定可见性 与生命周期。
📦 前置知识二:命名空间域(namespace)
人为划出来的一个作用域 ,让同名标识符各活各的 ,靠 :: 或 using 访问。
📥 前置知识三:流式 IO(cin / cout)
不用写格式占位符,自动识别类型 ,<< 流插入、>> 流提取。
🎛️ 前置知识四:缺省参数
声明时给形参一个默认值 ,调用不传就走默认值,必须从右往左连续给。
🔀 前置知识五:函数重载与名字修饰
同一作用域下同名不同参 的几个函数共存,底层靠 C++ 的名字修饰规则区分。
二、命名空间:先解决"名字被污染"
在了解命名空间之前,先讲一个绕不开的问题:在 C/C++ 中,变量、函数,以及后面要学到的类 都是大量存在的,这些名称都存在全局作用域 中,很容易互相冲突。使用命名空间的目的是对标识符的名称进行本地化 ,以避免命名冲突或名字污染 ------namespace 关键字就是为解决这个问题而出现的。
2.1 一个必然报错的例子
cpp
#include <stdio.h>
#include <stdlib.h>
int rand = 10;
// C语言没办法解决类似这样的命名冲突问题,所以C++提出了namespace来解决
int main()
{
printf("%d\n", rand);
return 0;
}
运行结果:
编译报错:error C2365: "rand": 重定义;以前的定义是"函数"
在 C 语言的 <stdlib.h> 头文件中,声明了一个生成随机数的函数 rand(),因此会出现这样的问题!而 C 语言的库是很多的,你也不希望某一天包了一个库,结果发现撞名字了吧。
所以接下来就要看 C++ 是如何解决这种问题的。
2.2 什么是作用域
作用域 是标识符(变量、函数、类、命名空间等)能够被访问和使用的代码区域,它决定了标识符的可见性 (能否被访问)和生命周期(存在的时间)。
C++ 的做法正是引入了一种新的作用域------命名空间域 ,把名字"关"进去,冲突自然就没了。先看域的种类一共四种:局部域、全局域、命名空间域、类域。
| 域 | 定义 | 特点 |
|---|---|---|
| 全局域 | 不被任何函数、类、命名空间包裹的标识符 | 标识符在整个程序生命周期内有效,且在所有内层作用域中可见 (除非被同名局部标识符隐藏);全局变量存储在静态存储区 |
| 局部域 | 在函数内、if/for/while 代码块内、自定义 {} 块内声明的标识符 |
标识符的生命周期随代码块执行结束而销毁 ;局部标识符会隐藏同名的全局标识符(局部优先) |
| 命名空间域 | 通过 namespace 命名空间名 包裹的标识符 |
不同命名空间中可存在同名标识符,互不冲突 |
| 类域 | 类中声明的成员(成员变量、成员函数、静态成员、嵌套类等) | 类的私有成员仅在类内可见 ;公有/保护成员需通过对象、指针、引用或作用域解析符 :: 访问;静态成员可直接通过 类名::成员 访问 |
2.3 命名空间怎么定义
格式:namespace 关键字
cpp
// 1. 正常的命名空间定义
namespace gfsn
{
// 命名空间中可以定义变量/函数/类型
int rand = 10;
int Add(int left, int right)
{
return left + right;
}
}
// 2. 命名空间可以嵌套
// test.cpp
namespace N1
{
int a;
int b;
namespace N2
{
int c;
int d;
}
}
// 3. 同一个工程中允许存在多个相同名称的命名空间,编译器最后会合成同一个命名空间
// ps:一个工程中的 test.h 和上面 test.cpp 中两个 N1 会被合并成一个
// test.h
namespace N1
{
int Mul(int left, int right)
{
return left * right;
}
}
注意:一个命名空间就定义了一个新的作用域 ,命名空间中的所有内容都局限于该命名空间中。
💡 第 3 点是很多人第一次见会愣住的地方:同名命名空间不是重定义,而是"续写" 。工程里多个文件都可以写
namespace N1 { ... },链接时它们会被合并成同一个N1------这也是为什么标准库能把std拆到几十个头文件里,你用起来却还是一个std。
2.4 命名空间怎么用
命名空间的使用有三种方式 (下面的示例假设命名空间名为 N)。
方式一:加命名空间名称及作用域限定符
cpp
int main()
{
printf("%d\n", N::a);
return 0;
}
方式二:使用 using 将命名空间中某个成员引入
cpp
using N::b;
int main()
{
printf("%d\n", N::a);
printf("%d\n", b);
return 0;
}
方式三:使用 using namespace 命名空间名称 引入
cpp
using namespace N;
int main()
{
printf("%d\n", a);
printf("%d\n", b);
Add(10, 20);
return 0;
}
三种方式的取舍:
| 方式 | 写法 | 优点 | 风险 |
|---|---|---|---|
| 限定符 | N::a |
最安全,一眼看出名字来自哪 | 写起来啰嗦 |
| 引入单个成员 | using N::b; |
常用名字省事,污染范围可控 | 仍有同名风险 |
| 整包展开 | using namespace N; |
最省事 | 相当于把命名空间内容展开到全局域,冲突风险最大 |
注意第三种:这种使用方式相当于把命名空间内容展开到全局域,图省事的代价就是把"隔离"这层保护自己拆了------所以在头文件里尤其要避免。
三、C++ 的输入与输出
要点:需导入 <iostream> 头文件,且属于 std 域;C++ 标准库的头文件无需加 .h,自己写的库还是需要的。
| 方向 | 格式 | 说明 |
|---|---|---|
| 输出 | cout << 内容 << endl |
<< 是流插入 |
| 输入 | cin >> 内容 |
>> 是流提取 |
使用 C++ 输入输出更方便的地方在于:不需要像 printf/scanf 那样手动控制格式 ,C++ 的输入输出可以自动识别变量类型。
代码示例:
cpp
#include <iostream>
using namespace std;
int main()
{
int a;
double b;
char c;
// 可以自动识别变量的类型
cin >> a;
cin >> b >> c;
cout << a << endl;
cout << b << " " << c << endl;
return 0;
}
运行结果:
输入:
10 3.14 A
输出:
10
3.14 A
💡 一个
cin里可以连着提取多个不同类型的变量(cin >> b >> c),cout也可以连着插入不同类型的内容------类型转换和格式全部由流自己处理 ,这正是它比scanf("%d")省心的地方。另外endl会刷新缓冲区 ,刷题追求速度时用'\n'会更快。
四、缺省参数
4.1 概念
缺省参数是声明或定义函数时 为函数的参数指定一个缺省值 。调用该函数时,如果没有指定实参则采用该形参的缺省值,否则使用指定的实参。
cpp
void Func(int a = 0)
{
cout << a << endl;
}
int main()
{
Func(); // 没有传参时,使用参数的默认值
Func(10); // 传参时,使用指定的实参
return 0;
}
运行结果:
0
10
4.2 分类:全缺省与半缺省
全缺省------所有参数都给默认值:
cpp
void Func(int a = 10, int b = 20, int c = 30)
{
cout << "a = " << a << endl;
cout << "b = " << b << endl;
cout << "c = " << c << endl;
}
运行结果(调用 Func() 时):
a = 10
b = 20
c = 30
半缺省------只给右边的一部分参数默认值:
cpp
void Func(int a, int b = 10, int c = 20)
{
cout << "a = " << a << endl;
cout << "b = " << b << endl;
cout << "c = " << c << endl;
}
运行结果(调用 Func(1) 时):
a = 1
b = 10
c = 20
4.3 四条硬规则
| 规则 | 说明 |
|---|---|
| 1. 半缺省必须从右往左连续给 | 不能间隔着给。因为实参是从左往右依次对应形参的,中间一旦空出来,编译器就无法确定你传的值该落到哪个参数上 |
| 2. 声明和定义中不能同时出现缺省值 | 两个地方都写会触发重定义默认参数 的编译错误;声明与定义分离时,缺省值只写在声明里 |
| 3. 缺省值必须是常量或全局变量 | 不能是局部变量、函数调用等运行期才知道的东西 |
| 4. C 语言不支持缺省参数 | 这是 C++ 对 C 的语法增强之一,C 编译器直接不认 |
💡 最容易踩的是第 1 条和第 2 条。第 1 条记成"右边可以缺,左边不能缺 "就不会错;第 2 条记成"默认值只交代一次,放在头文件里"。
五、函数重载
函数重载是函数的一种特殊情况:同一作用域 中声明几个功能类似的同名函数 ,这些同名函数的形参列表 (参数个数 ,或类型 ,或类型顺序)不同,即构成重载;使用该函数时会自动挑选最合适的那个。
5.1 构成重载的三种情况
cpp
// 1、参数类型不同
int Add(int left, int right);
double Add(double left, double right);
// 2、参数个数不同
void f();
void f(int a);
// 3、参数类型顺序不同
void f(int a, char b);
void f(char b, int a);
要点:函数重载和返回值没有关系。
💡 重载是编译期决议 :编译时就已经确定该调哪个函数,运行时没有任何额外开销------所以别把它和"运行时多态"混为一谈。
5.2 为什么 C 语言不支持重载
原因在于二者编译的效果不一样。

这是C++的

| 语言 | 编译后的符号名 | 结果 |
|---|---|---|
| C | 只保留函数名,如 f |
同名函数符号冲突,无法共存 |
| C++ | 名字修饰(Name Mangling),如带参数类型信息 | 参数不同 → 修饰出来的名字不同 → 可以共存 |
通过这里就理解了:C 语言没办法支持重载,因为同名函数没办法区分。而 C++ 是通过函数修饰规则来区分,只要参数不同,修饰出来的名字就不一样,就支持了重载。
六、内联函数
内联概念: 内联函数会在编译时展开 ,作用类似宏函数,但大多数情况下比宏更优秀(有类型检查、能调试)。
格式:inline 函数
6.1 特性一:定义和声明不能分开
定义和声明不能分开 :分离会导致链接错误 ------因为 inline 函数被展开了,没有生成独立的函数地址,链接时找不到它的定义。
代码示例:
cpp
// F.h
#include <iostream>
using namespace std;
inline void f(int i);
// F.cpp
#include "F.h"
void f(int i) // 定义里没有 inline,且被拆到了另一个 .cpp
{
cout << i << endl;
}
运行结果:
链接错误:无法解析的外部符号 f(函数被调用,但找不到它的定义)
💡 正确做法只有一条:
inline函数的定义直接写在头文件里 (声明即定义),让每个包含它的翻译单元都能就地展开。这也是为什么标准库里的inline函数全都在头文件里。
6.2 特性二:是否内联由编译器决定
内联是由编译器决定的 :我们加的 inline 对于编译器而言只是一个建议 ,不同编译器关于 inline 的实现机制可能不同------你写了 inline,编译器仍然可以拒绝展开(比如函数体太大、含递归、取了函数地址)。
| 你的动作 | 编译器的自由度 | 结论 |
|---|---|---|
加 inline |
可以展开,也可以不展开 | 只是建议,不是命令 |
不加 inline |
小函数也可能被自动内联 | 现代编译器会自行判断 |
七、复盘(附答案)
💡 思考题
-
在C++ 编码规范中,为什么头文件 里禁止使用
using namespace std;,而.cpp文件中可以?如果多个命名空间有同名函数,如何在不使用using的情况下简化调用? -
cin/cout比scanf/printf更方便,但有人说cin/cout效率更低,面试中常问如何关闭同步流 来提升性能?endl和'\n'有什么区别,什么场景下该用哪个? -
缺省参数在函数声明 和函数定义 中同时出现时会发生什么?为什么缺省参数必须从右往左连续提供?如果缺省参数和重载函数同时存在,会不会产生调用冲突?
-
C++ 支持函数重载而 C 语言不支持,底层原理是什么?如果两个函数只是返回值类型不同,为什么不能构成重载?调用重载函数时,编译器如何确定调用哪个版本?
📝 答案
-
头文件会被多个
.cpp包含 ,在里面展开命名空间会污染所有包含者的全局作用域 ,极易导致编译冲突;.cpp文件作用域可控,展开影响有限。若不想每次都写std::前缀又担心冲突,可以只引入常用符号 (如只引入std::cout和std::cin),或使用命名空间别名 (如namespace IO = std;)。 -
通过关闭同步流 (
ios::sync_with_stdio(false); cin.tie(nullptr);)可大幅提升cin/cout效率,接近scanf/printf的水平。endl会刷新缓冲区 ,频繁使用影响性能;'\n'仅换行不刷新,适用于大多数高性能场景,是刷题和工程中的更优选择。 -
声明和定义同时出现会导致重定义默认参数 的编译错误,缺省值只应出现在声明中 。缺省参数必须从右往左连续提供 ,因为调用时按从左到右 的顺序传参,中间不能跳过。与重载共存可能引发二义性调用 :如
void func(int);和void func(int, int = 10);同时存在时,调用func(5)无法确定匹配哪个。 -
C++ 编译时会对函数名进行名字修饰(Name Mangling) ,把参数类型信息编入符号名,因此同名不同参的函数可共存;C 语言无此机制,故不支持重载。返回值不能作为区分依据,因为调用时无法从上下文推断返回值类型,编译器不知道该选哪个。