【C++ 基础入门】命名空间、输入输出、缺省参数与函数重载

👋 欢迎阅读

📑 目录


一、前置讲解

在进入正文前,先花 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 小函数也可能被自动内联 现代编译器会自行判断

七、复盘(附答案)

💡 思考题

  1. 在C++ 编码规范中,为什么头文件 里禁止使用 using namespace std;,而 .cpp 文件中可以?如果多个命名空间有同名函数,如何在不使用 using 的情况下简化调用?

  2. cin/cout 比 scanf/printf 更方便,但有人说 cin/cout 效率更低,面试中常问如何关闭同步流 来提升性能?endl 和 '\n' 有什么区别,什么场景下该用哪个?

  3. 缺省参数在函数声明 和函数定义 中同时出现时会发生什么?为什么缺省参数必须从右往左连续提供?如果缺省参数和重载函数同时存在,会不会产生调用冲突?

  4. C++ 支持函数重载而 C 语言不支持,底层原理是什么?如果两个函数只是返回值类型不同,为什么不能构成重载?调用重载函数时,编译器如何确定调用哪个版本?

📝 答案

  1. 头文件会被多个 .cpp 包含 ,在里面展开命名空间会污染所有包含者的全局作用域 ,极易导致编译冲突;.cpp 文件作用域可控,展开影响有限。若不想每次都写 std:: 前缀又担心冲突,可以只引入常用符号 (如只引入 std::cout 和 std::cin),或使用命名空间别名 (如 namespace IO = std;)。

  2. 通过关闭同步流 (ios::sync_with_stdio(false); cin.tie(nullptr);)可大幅提升 cin/cout 效率,接近 scanf/printf 的水平。endl 会刷新缓冲区 ,频繁使用影响性能;'\n' 仅换行不刷新,适用于大多数高性能场景,是刷题和工程中的更优选择。

  3. 声明和定义同时出现会导致重定义默认参数 的编译错误,缺省值只应出现在声明中 。缺省参数必须从右往左连续提供 ,因为调用时按从左到右 的顺序传参,中间不能跳过。与重载共存可能引发二义性调用 :如 void func(int); 和 void func(int, int = 10); 同时存在时,调用 func(5) 无法确定匹配哪个。

  4. C++ 编译时会对函数名进行名字修饰(Name Mangling) ,把参数类型信息编入符号名,因此同名不同参的函数可共存;C 语言无此机制,故不支持重载。返回值不能作为区分依据,因为调用时无法从上下文推断返回值类型,编译器不知道该选哪个。

相关推荐
俩个逗号。。1 小时前
Kotlin flow 详解
android·开发语言·kotlin
m4Rk_1 小时前
【论文阅读】Agent 记忆机制(119):EvoLife——让具身 Agent 把主动问到的答案沉淀为可复用的空间经验
论文阅读·人工智能·学习·开源·github
程序猿乐锅2 小时前
万字详解Single-flight(单飞模式)
java·开发语言·分布式·后端·学习·single-flight
扶尔魔ocy2 小时前
【QT入门到晋级】用开源的自定义控件开发
c++·qt·自定义控件
akinggw3 小时前
libsherpacore - 基于 sherpa-onnx 的 C++ 语音交互静态库
c++·交互·语音识别
xiaoye-duck3 小时前
《C++ 项目:手写大模型接入 SDK》ChatGPT接口设计(中):全量响应实现和测试
网络·c++·语言模型·chatgpt
揽星河..3 小时前
【C语言】指针
c语言·开发语言
小小数媒成员3 小时前
UGUI原理(一)
开发语言·游戏·unity·游戏引擎
赵谨言3 小时前
多语种在线语言学习平台LinguaVerse的设计与实现毕业论文
学习