
🎬 个人主页 :道尔柯南
❄专栏传送门 :《C语言》《C++》《Linux操作系统》
昙花一现,却等待了整个白昼;蝉鸣一夏,却蛰伏了好几个四季。 技术专栏:C++ 编程基础 · 适合初学者 · 关键词:命名空间、输入输出、函数重载、引用、内联函数、nullptr
一、C++ 简介与发展历程
1.1 什么是 C++?
C++ 是一种通用的编程语言,在 C 语言的基础上发展而来,并进一步提供了面向对象编程、泛型编程等能力。它兼顾了较强的运行效率和丰富的抽象能力,广泛应用于系统软件、游戏引擎、音视频处理、桌面应用和高性能服务等领域。
C++ 课程通常会涉及以下内容:
- 基础语法: 变量、函数、控制流程、引用等。
- 面向对象编程: 类、对象、封装、继承和多态。
- STL 标准模板库: 常用容器、算法和迭代器。
- 高阶数据结构与编程技术: 为复杂项目开发打下基础。
1.2 C++ 的发展历史
1979
起源阶段
Bjarne Stroustrup 在贝尔实验室开展相关研究,希望在保留 C 语言效率的同时,提升大型软件开发中的表达能力与可维护性。
1983
正式命名为 C++
语言逐步引入类、封装、继承等面向对象特性,形成 C++ 的早期形态。
1998
第一个 ISO C++ 国际标准
C++98 标准发布,语言及其标准库的发展进入更加规范的阶段。
持续演进
不断扩展语言能力
C++ 后续持续更新,逐步增加了新的语言特性和标准库功能,以适应现代软件开发需求。
其中,STL(Standard Template Library,标准模板库)是 C++ 学习过程中的重要组成部分。它提供了常用的数据结构与算法,能够帮助开发者减少重复实现基础功能的工作。
1.3 C++ 学习参考文档
学习 C++ 时,建议结合不同类型的参考资料:
- cplusplus.com:按照头文件和功能组织文档,适合快速查找常见接口。
- cppreference 中文版:内容较为全面,适合查询语言特性与标准库。
- cppreference 英文版:可作为英文技术文档阅读练习。
需要注意的是,文档的更新范围和组织方式不同,实际使用时应根据所使用的 C++ 标准选择相应资料。
二、为什么要学习 C++?
C++ 不仅是一门基础编程语言,也是一项能够应用于多种技术方向的开发技能。
使用 buildroot 和 QEMU 调试 linux 内核 - kicey's blog
- 系统软件开发
操作系统、编译器、数据库及部分浏览器核心组件,都涉及 C++ 或其他系统级编程技术。
Where to Find the Suitable Crowd Cheering Audio and How to Use It
- 音视频处理
FFmpeg、WebRTC 等技术生态中广泛使用 C/C++,涉及编解码、媒体处理和实时通信。
Qt Design Studio 1.1 Beta released
- 桌面客户端开发
C++ 配合 Qt,可以开发跨平台图形用户界面(GUI)应用,例如工具软件、桌面管理系统等。
Game Development Using the Open-Source Godot Game Engine - Tero Salmela BACHELOR'S THESIS - Theseus
- 游戏与高性能服务
游戏引擎、游戏服务器、实时处理系统等场景,经常需要利用 C++ 的性能和底层控制能力。
Research & Science --- AI Labs | Scientific AI R&D
- 嵌入式与底层开发
智能设备、车载系统、摄像头和机器人等产品中,也可能使用 C++ 完成应用逻辑和设备控制。
因此,学习 C++ 的意义不仅在于掌握语法,更在于理解程序运行、内存管理、数据结构和软件工程的基础知识。
三、编写第一个 C++ 程序:Hello World
学习一门编程语言,通常从输出一条简单的信息开始。
3.1 使用 C 风格的输入输出
C++ 兼容 C 语言的许多语法,因此可以使用 printf() 输出信息。
#include <stdio.h>
int main()
{
printf("Hello World\n");
return 0;
}
代码说明:
#include <stdio.h>:引入标准输入输出相关声明。int main():程序入口函数。printf():向标准输出打印文本。\n:表示换行。return 0:表示程序正常结束。
3.2 使用 C++ 风格的输入输出
C++ 提供了自己的流式输入输出机制。
#include <iostream>
int main()
{
std::cout << "Hello World" << std::endl;
return 0;
}
这里有三个需要掌握的知识点:
| 语法 | 作用 |
|---|---|
iostream |
提供标准输入输出流相关功能 |
std::cout |
标准输出对象 |
std::endl |
插入换行并刷新输出流 |
其中,<< 在这里是流插入运算符,用于把数据送入输出流。
C++ 程序通常使用 .cpp 作为源文件后缀。使用 GCC 工具链时,可以通过 g++ 编译:
g++ test.cpp -o test
在 Linux 环境中运行:
./test
学习建议: 初学阶段可以先学会运行示例程序,再逐步理解每一条语句的作用,不必在第一次接触时就完全理解标准库的内部实现。
四、命名空间 namespace:解决名称冲突
4.1 为什么需要命名空间?
在大型项目中,不同模块可能定义相同名称的变量或函数。
例如,某个模块定义了一个名为 rand 的变量,而程序中又使用了标准库提供的 rand() 函数。如果没有合理的名称组织方式,就容易发生名称冲突。
C++ 引入了 namespace,用于把相关标识符组织在独立的命名空间中。
4.2 命名空间的定义
基本语法如下:
namespace bit
{
int value = 10;
int Add(int left, int right)
{
return left + right;
}
}
上述代码定义了名为 bit 的命名空间,其中包含一个变量和一个函数。
使用作用域解析运算符 ::,可以明确指定访问哪个命名空间的成员:
#include <iostream>
namespace bit
{
int value = 10;
}
int main()
{
std::cout << bit::value << std::endl;
return 0;
}
输出结果:
10
这里的 bit::value 表示访问 bit 命名空间中的 value。
4.3 命名空间可以嵌套
命名空间支持嵌套定义,用于进一步组织代码。
namespace company
{
namespace developer
{
int level = 1;
}
}
int main()
{
int n = company::developer::level;
return 0;
}
通过 company::developer::level,可以明确地访问嵌套命名空间中的成员。
在多个源文件中,只要命名空间名称相同,它们的定义可以共同组成同一个命名空间。这种方式适合将同一模块的声明与实现拆分到不同文件中。
4.4 命名空间的三种使用方式
方式一:推荐用于项目代码
使用完整限定名称
std::cout << "Hello" << std::endl;
优点是名称明确,可以有效减少冲突。
方式二:按需引入
使用 using 声明单个成员
using std::cout;
using std::endl;
cout << "Hello" << endl;
适合经常使用、且不容易与其他名称冲突的成员。
方式三:谨慎使用
引入整个命名空间
using namespace std;
cout << "Hello" << endl;
写小型练习程序时比较方便,但大型项目中可能增加名称冲突风险。
需要特别注意:namespace 本身不会创建一个新的独立变量生命周期。它主要用于组织名称与控制名称查找,而变量的生命周期取决于其存储期等因素。
五、C++ 输入与输出:cin、cout 和 endl
C++ 的流式输入输出可以根据数据类型选择相应的处理方式,使用起来比较方便。
5.1 基本输入输出示例
#include <iostream>
int main()
{
int age = 0;
double height = 0.0;
std::cout << "请输入年龄和身高:" << std::endl;
std::cin >> age >> height;
std::cout << "年龄:" << age << std::endl;
std::cout << "身高:" << height << std::endl;
return 0;
}
假设输入:
20 1.75
输出结果:
请输入年龄和身高:
年龄:20
身高:1.75
5.2 常用流操作符
| 符号 | 含义 |
|---|---|
std::cin |
标准输入流对象 |
std::cout |
标准输出流对象 |
>> |
流提取运算符,常用于输入 |
<< |
流插入运算符,常用于输出 |
std::endl |
换行并刷新输出流 |
与 scanf()、printf() 相比,C++ 流式输入输出可以通过类型相关的重载机制处理不同类型的数据,减少手动指定格式符的需要。
5.3 endl 与换行符的区别
std::cout << "Hello\n";
和:
std::cout << "Hello" << std::endl;
都可以输出换行,但 std::endl 还会刷新输出流。频繁刷新可能影响输出效率,因此不需要立即刷新时,可以优先考虑使用 \n。
对于大量输入输出的程序,介绍以下优化方式:
#include <iostream>
int main()
{
std::ios_base::sync_with_stdio(false);
std::cin.tie(nullptr);
std::cout.tie(nullptr);
// 后续进行输入输出操作
return 0;
}
这些设置可以在合适的场景下提高 C++ 流式输入输出的效率。不过,关闭同步后应避免随意混用 C 与 C++ 的标准输入输出,以免产生输出顺序等问题。
六、缺省参数:让函数调用更加灵活
6.1 什么是缺省参数?
缺省参数,也称默认参数,是指在声明函数时,为部分形参指定默认值。
当调用函数时,如果没有提供相应实参,就会使用默认值。
#include <iostream>
void Func(int value = 10)
{
std::cout << value << std::endl;
}
int main()
{
Func(); // 使用默认值 10
Func(100); // 使用传入的值 100
return 0;
}
输出结果:
10
100
6.2 全缺省与半缺省参数
全缺省参数:
所有参数都设置默认值。
void Print(int a = 10, int b = 20, int c = 30);
调用时可以提供不同数量的参数:
Print();
Print(1);
Print(1, 2);
Print(1, 2, 3);
半缺省参数:
只为部分参数设置默认值。
void Print(int a, int b = 10, int c = 20);
此时可以这样调用:
Print(100);
Print(100, 200);
Print(100, 200, 300);
6.3 缺省参数的注意事项
- 半缺省参数必须从右向左连续设置默认值。
- 调用函数时,实参按照从左到右的顺序传递,不能跳过中间参数而直接指定后面的参数。
- 当函数声明与定义分离时,默认参数通常写在函数声明中,不应在声明和定义中重复指定。
例如,在栈初始化函数中,可以把默认容量设置为 4:
// Stack.h
void STInit(ST* ps, int n = 4);
// Stack.cpp
void STInit(ST* ps, int n)
{
// 初始化栈
}
这样,普通场景下可以直接调用 STInit(&stack);如果明确知道需要较大的初始容量,也可以调用 STInit(&stack, 1000)。
缺省参数能够减少重复代码,让函数接口更加简洁。
七、函数重载:同名函数实现不同功能
7.1 什么是函数重载?
C++ 允许在同一作用域中定义多个同名函数,只要这些函数的参数列表能够形成有效区分,就可以构成函数重载。
重载常见的区分方式包括:
- 参数类型不同。
- 参数个数不同。
- 参数类型的顺序不同。
例如:
#include <iostream>
// 参数类型不同
int Add(int a, int b)
{
return a + b;
}
double Add(double a, double b)
{
return a + b;
}
// 参数个数不同
void Print()
{
std::cout << "无参数版本" << std::endl;
}
void Print(int value)
{
std::cout << "整数:" << value << std::endl;
}
int main()
{
std::cout << Add(10, 20) << std::endl;
std::cout << Add(1.5, 2.5) << std::endl;
Print();
Print(100);
return 0;
}
输出结果:
30
4
无参数版本
整数:100
编译器会根据调用时提供的实参,选择匹配的函数。
7.2 为什么不能只根据返回值重载?
例如,下面两个函数不能仅凭返回类型构成有效的重载:
int Func()
{
return 1;
}
// 错误:不能仅通过返回类型区分
// double Func()
// {
// return 1.0;
// }
当代码调用 Func() 时,编译器无法仅根据返回值来决定应该调用哪个版本。
7.3 缺省参数与函数重载的组合风险
下面的代码看起来定义了两个不同的函数:
void Func()
{
}
void Func(int a = 10)
{
}
但是调用 Func() 时,两个函数都可能匹配,造成调用歧义。
学习重点: 函数重载依据参数列表区分,而默认参数可能扩大某个函数的可调用范围。设计接口时,应尽量避免产生歧义。
八、引用:C++ 中的重要概念
引用是 C++ 相比 C 语言的一个重要语法特性,也是理解后续类、对象和数据结构实现的基础。
8.1 引用的基本概念
引用可以理解为一个已经存在的变量的别名。
基本语法:
类型& 引用名 = 变量名;
例如:
#include <iostream>
int main()
{
int a = 10;
int& b = a;
b = 20;
std::cout << a << std::endl;
std::cout << b << std::endl;
return 0;
}
输出结果:
20
20
这是因为 b 是 a 的引用,对 b 进行赋值,实际修改的就是 a。
8.2 引用的三个重要特性
第一,引用定义时必须初始化。
int a = 10;
int& b = a;
不能直接定义一个没有绑定对象的普通引用。
第二,一个变量可以有多个引用。
int a = 10;
int& b = a;
int& c = a;
此时 a、b、c 都指向同一个变量所代表的对象。
第三,引用一旦绑定,就不能重新绑定到其他对象。
int a = 10;
int b = 20;
int& ref = a;
ref = b;
最后一行并不是让 ref 改为引用 b,而是把 b 的值赋给 a。执行后,a 和 ref 的值都为 20,而 ref 仍然引用 a。
8.3 引用传参:简化函数调用
引用最常见的用途之一,就是让函数直接操作调用者传入的变量。
例如,使用引用实现交换函数:
#include <iostream>
void Swap(int& x, int& y)
{
int temp = x;
x = y;
y = temp;
}
int main()
{
int a = 10;
int b = 20;
Swap(a, b);
std::cout << a << " " << b << std::endl;
return 0;
}
输出结果:
20 10
如果使用普通值传递,函数拿到的是参数的副本,交换副本不会直接改变外部变量。引用传参则可以直接操作原对象。
8.4 const 引用:只读访问与临时对象
当函数只需要读取一个对象、不需要修改它时,可以考虑使用 const 引用。
#include <iostream>
void Print(const int& value)
{
std::cout << value << std::endl;
}
int main()
{
int a = 10;
Print(a);
Print(100);
return 0;
}
这里的 const int& 有两个重要作用:
- 可以引用普通的
int对象,但通过这个引用不能修改对象。 - 可以绑定到合适的临时对象,例如示例中的整数常量
100。
再看一个权限控制的例子:
const int a = 10;
// 正确:只读引用
const int& ref = a;
// 错误:不能通过非 const 引用修改 const 对象
// int& ref2 = a;
理解 const 引用的关键是:允许缩小访问权限,但不能通过引用扩大原对象允许的访问权限。
8.5 引用返回值
引用还可以作为函数返回类型,让调用者通过函数返回值直接访问原对象。
例如:
int& GetElement(int* array, int index)
{
return array[index];
}
如果 array 指向的数组仍然有效,那么返回数组元素的引用后,可以通过它修改对应元素:
int a[] = {10, 20, 30};
GetElement(a, 1) = 99;
执行后,数组第二个元素变为 99。
不过,返回引用时必须确保被引用对象的生命周期足够长。不要返回局部变量的引用,因为局部变量在函数结束后通常就不再有效。
九、指针与引用的区别
指针和引用在使用上有一定相似性,但概念并不相同。
| 对比维度 | 指针 | 引用 |
|---|---|---|
| 基本概念 | 保存对象的地址 | 已有对象的别名 |
| 定义时初始化 | 语法上不强制,但建议初始化 | 必须初始化 |
| 是否可以改变指向 | 可以改变指向 | 不能重新绑定 |
| 访问对象 | 通常通过解引用 * |
可以直接访问 |
| 空值表达 | 可以表示为空指针 | 普通引用必须绑定有效对象 |
| 使用风险 | 需要注意空指针、悬空指针等问题 | 使用更简洁,但同样要注意对象生命周期 |
例如:
int a = 10;
int* ptr = &a;
int& ref = a;
*ptr = 20;
ref = 30;
执行后,a 的值为 30。
需要注意,引用并不意味着绝对安全。如果引用绑定的对象已经失效,继续访问它仍然可能导致未定义行为。
十、inline 内联函数
10.1 什么是内联函数?
在 C++ 中,可以使用 inline 关键字声明内联函数。
inline int Add(int a, int b)
{
return a + b;
}
调用:
int result = Add(10, 20);
传统意义上,内联函数允许编译器考虑将函数体展开到调用位置,以减少部分普通函数调用的开销。
不过,inline 只是向编译器表达一种意图,不保证编译器一定进行内联展开。实际优化取决于编译器、编译选项和函数本身等因素。
10.2 为什么需要内联函数?
与直接使用宏定义相比,内联函数具有更清晰的函数语法和类型检查能力。
例如,下面是一个简单的宏:
#define ADD(a, b) ((a) + (b))
它可以完成基本的加法,但宏属于预处理阶段的文本替换,复杂时容易出现运算优先级和重复求值等问题。
使用普通函数或合适的内联函数,通常更容易理解、调试和维护。
10.3 使用 inline 时需要注意什么?
inline并不保证一定提升性能。- 代码量较大或结构复杂的函数,不一定适合内联。
- 现代编译器会根据优化情况决定是否展开函数。
- 内联函数的定义通常应放在头文件中,便于需要它的源文件看到完整定义。
这里需要特别区分:inline 不只是性能提示,它还会影响函数定义在多个翻译单元中出现时的链接规则。因此,在多文件项目中,应正确组织内联函数的声明和定义。
十一、nullptr:C++11 引入的空指针字面量
11.1 为什么需要 nullptr?
在传统 C/C++ 代码中,NULL 经常被用于表示空指针。但在 C++ 中,NULL 可能被定义为整数常量 0,从而在函数重载时引发歧义。
例如:
#include <iostream>
void Func(int)
{
std::cout << "整数版本" << std::endl;
}
void Func(int*)
{
std::cout << "指针版本" << std::endl;
}
int main()
{
Func(0);
Func(nullptr);
return 0;
}
输出结果:
整数版本
指针版本
Func(0) 匹配整数参数版本,而 Func(nullptr) 匹配指针参数版本。
11.2 nullptr 的优势
nullptr 是 C++11 引入的专用空指针字面量,可以转换为指针类型,但不会像整数 0 那样被当作普通整数实参进行重载匹配。
因此,推荐使用:
int* ptr = nullptr;
而不是依赖整数 0 来表示空指针。
需要注意,nullptr 并不是一个普通的指针变量,而是具有专门类型 std::nullptr_t 的字面量。
十二、C++ 入门知识点总结
学完这部分内容后,可以用下面的知识结构来复习。
C++ 入门基础
建立语言基础与代码组织能力
程序基础
main、编译、输入输出
代码组织
namespace、作用域
函数特性
缺省参数、函数重载
对象访问
引用、指针、const
编译优化
inline 与函数调用
安全表达
nullptr、生命周期
建议重点掌握以下内容:
- 能够独立编写、编译和运行 C++ 程序。
- 理解命名空间的作用,知道如何使用
std::和::。 - 掌握
cin、cout等基本输入输出方式。 - 理解缺省参数和函数重载的匹配规则。
- 熟练区分引用与指针,理解引用传参和
const引用。 - 了解内联函数的作用及其局限。
- 理解
nullptr为什么比使用整数0表示空指针更明确。
十三、学习建议:从看懂代码到独立编程
对于刚开始学习 C++ 的同学,建议按照以下步骤练习。
- 先运行,再修改。 把课件中的每个示例输入开发环境,观察输出结果,再尝试修改变量、参数和函数名称。
- 针对每个知识点编写小程序。 例如,使用命名空间定义两个同名函数,使用引用实现交换,使用函数重载实现整数与浮点数加法。
- 主动制造错误。 尝试删除引用初始化、创建有歧义的重载,或者错误地使用
NULL,通过编译器报错理解语法规则。 - 整理个人笔记。 每个知识点记录定义、核心语法、代码示例、易错点和实际应用场景。
- 逐步结合数据结构。 学习栈、队列、顺序表和链表时,思考引用、指针与命名空间如何帮助组织代码。
结语
C++ 入门阶段的重点,并不是记住多少语法,而是建立正确的编程思维。
从第一个 Hello World 程序开始,到理解命名空间、函数重载、引用、内联函数和 nullptr,这些知识共同构成了进一步学习 C++ 的基础。
当你能够解释代码为什么这样写、预测程序的运行结果,并且独立修改和编写示例时,就真正迈出了从语法学习走向实际开发的重要一步。
后续可以继续学习类与对象、构造函数、拷贝控制、继承、多态,以及 STL 容器与算法,逐渐形成完整的 C++ 知识体系。
结尾
uu们,本文的内容到这里就全部结束了,道尔在这里再次感谢您的阅读!
|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| ### 道尔柯南 C/C++ & Linux 底层探索者 | 一个正在努力学习的技术博主 *** ** * ** *** 👀 【关注】 跟随我一起深耕技术领域,见证每一次成长。 ❤️ 【点赞】 让优质内容被更多人看见,让知识传递更有力量。 ⭐ 【收藏】 把核心知识点存好,在需要时随时查、随时用。 💬 【评论】 分享你的经验或疑问,评论区一起交流避坑! 不要忘记给博主"一键四连"哦! "今日目标达成!" |
结语:希望对学习Linux相关内容的uu有所帮助,不要忘记给博主"一键四连"哦!