01--CPP入门
1. 命名空间域
1.1. 命名空间的概念
命名空间: CPP用于解决/减轻C语言符号名冲突问题的一种语法层面上的域
- 域: 在CPP中,大致可以分为四大域
全局域
(生命周期 和 作用域)局部域
(生命周期 和 作用域)命名空间域
(作用域)类域
(作用域)
- 编译器域的搜索规则:
- 指定域情况下:默认指定域
- 未指定域:局部 --> 全局
1.2. 命名空间的定义
- 语法:
namespace + [name]
1.3. 命名空间域的访问
- 指定访问:
::域作用限定符::函数名/变量名 指定访问域中的某个函数/变量.
- 部分展开某个域:
using szg2::c 代表展开 szg2 中的 c 函数.
- 全部展开某个域:
using namespace std; 展开 std 整个域.
命名空间展开与头文件展开的区别
- 头文件包含是把头文件拷贝到#include处,头文件展开式代码拷贝。
- 命名空间影响的是编译器的搜索规则来影响是可否进行访问,命名空间展开是把搜索权限放开。
1.4. 命名空间的意义
极大的缓解了 C标识符 命名冲突问题(第三方库使用效果明显)
1.5. 命名空间的使用建议
场景 | 命名空间使用 | 原因 |
日常写代码 | using namespace std; | 写代码基本不会命名冲突 |
企业级开发 | std::函数/变量 + using std::cout; | 常用的指定, 不常用的直接指定展开 |
2. CPP输入与输出(+endl)
2.1. 概念
首先, 命名实际上来自两个英文单词:
console(操纵台) + out(出) -> cout 输出
console(操纵台) + in(进) -> cin 输入
2.2. 输入输出的使用建议
场景 | 建议 | 写法示例 |
要求指定个数输入/输出 | printf/scanf |
|
方便 | cout/cin |
|
2.3. 拓展: endl
一个 CPP 中的关键字, 主要有两个作用:
- 换行
- 冲刷缓冲区
3. 缺省参数
缺省参数:函数形参的"默认值"
分类
- 全缺省
- 半缺省
缺省参数的使用细则
- 半缺省参数必须从右到左给,实参传递必须从左向右给,不可跳跃
- 缺省参数必须是常量值/全局变量
- 缺省参数不能在声明和定义中同时出现(一般要求在声明中定义缺省参数)
问: 为什么缺省参数不可以在声明与定义中同时出现??
答: 这是一种语法规定,防止声明与定义缺省参数不一致,我们一般要求在声明中定义缺省参数。
问: 为什么不在定义中定义缺省参数,而在声明中定义缺省参数?
答: 这就需要联系到编译器的编译原理了。
- 避免重复定义, 避免歧义
- 方便进行语法检查(在编译阶段编译器先拿着声明 与 函数调用 进行语法检查)
4. 函数重载
概念: 同一作用域下, 函数同名不同参(形参, 类型, 顺序, 个数等)
重载原理: 按照一定算法规则重新命名标识符
c语言中函数找寻原理:
我们知道,test与stack是相分离的,所以其实在链接之前test调用函数的地方压根都不知道stack函数到底在哪里。直到链接时候是编译器生成一个符号表的东西去拿着调用函数的名称与符号表中的名称去寻找,再根据符号表提供的函数地址去找函数地址就好了。
CPP中函数找寻原理:修饰函数名
为了解决C语言不能函数重载的问题。CPP中把函数名称加上一定修饰来区分重载函数。
比如,在Linux下,void fun(int a, int b) 会写作_Z3funii
其实只在函数名后面把参数首字母加进去就行了,但是前面多加个Z,可能是为了快速查找匹配函数的效率考量的吧。
5. 引用
概念: 给标识符取别名
特性:
- 引用在使用时必须初始化。
- 引用可以定义多个
- 引用的指向不可更改
- 引用不能对const修饰的变量取别名(这里指的是权限不能扩大问题)
引用的使用
- 做参数,输出型参数
- 传参/返回参数,减少拷贝, 提高效率.
但是, 也会存在一些错误使用的情况: - 常引用:为具有常性的变量取别名
问: 传值返回与引用返回的区别?
传值返回:生成临时变量,拷贝到外界进行接收。
传引用返回:直接给变量起别名,返回的是变量本身。
问: 如何才能规避错误引用?
只要出了函数还存在的变量就可以引用返回。比如说全局变量,静态变量,堆变量等等...
对比 指针 与 引用
引用与指针的区别:是两种不同的语法.
6. 内联函数
6.1. 引入
现在有这么一个场景, 某一个函数, 频繁调用 100w 次, 会造成大量的栈帧开辟, 栈帧销毁, 也就降低了效率. 在 C 语言中, 我们是用宏来解决这个问题的. eg: #define Add(a, b) ((a)+(b))
但是宏函数存在很多问题:
1. 不能调试
2. 语法复杂, 容易出错
3. 编译器没有对应的类型安全检查
为了解决这种频繁调用并且造成大量栈帧开辟的问题, CPP 提出了内联函数的概念.
思考: 为什么 a + b 里面还要加小括号?
答: 因为有传表达式的情况.
6.2. 内联函数概念
内联函数,在编译器允许的前提下, 将短小且频繁调用的函数以类似于代码嵌入的方式结合到源代码中, 而不再开辟函数栈帧的一种特殊函数
6.3. 内联函数的特性
- 本质是一种以空间换时间的做法, 即牺牲编译时间换取运行时间的做法 -> 容易引发代码膨胀问题
- 内联函数是对编译器的一种建议
- inline不建议声明和定义相分离
问: 为什么inline 函数不建议声明与定义分离???因为 inline 不会生成对应的链接地址.
总结: 说白了inline函数在语法上允许声明和定义分离, 只不过一般情况下都是不分离的, 因为这样做更加符合inline函数的目的(内联展开, 不用开辟函数栈帧, 提高代码执行效率, 牺牲一部分编译效率), 其次的话声明和定义相分离会存在链接问题以及维护的一些问题, 虽然有办法解决, 但是整体比较麻烦.
总结来说, 不是inline函数不能展开, 而是没有展开的必要.
[资料补充] inline 为啥建议定义和声明定义在同一个地方, 而不建议分离呢???
首先, 如果分离会带来下面问题:
在C++中,inline
函数的声明和定义技术上可以分离,但强烈不建议这样做。以下是详细解释:
1. 为什么可以分离?
从语法角度看,C++允许将inline
函数的声明和定义分开:
// 头文件(声明)
inline void foo(); // 源文件(定义)
inline void foo() { // 实现
}
2. 为什么不建议分离?(核心问题)
(1) 违反内联函数的根本目的
inline
函数的初衷是让编译器在调用处直接展开代码(避免函数调用开销)。- 如果定义不在头文件中,其他源文件无法看到函数体,编译器无法内联展开,只能像普通函数一样调用。
- 此时
inline
关键字实际失效,仅剩下"允许重复定义"的作用。
(2) 链接问题(One Definition Rule, ODR)
- 虽然
inline
函数允许多个编译单元包含相同定义,但所有定义必须完全一致。 - 若定义在源文件中:
- 其他文件通过头文件声明调用函数时,链接器会寻找函数的唯一实现。
- 若忘记在某个源文件中定义,导致链接错误(undefined reference)。
(3) 维护困难
- 分散的声明/定义会增加修改成本,容易引发不一致。
- 与模板类似,内联函数依赖于"定义可见性",分离会破坏这一特性。
3. 正确做法:始终在头文件中定义
// 正确示例:声明和定义在一起(在头文件中)
inline int add(int a, int b) {return a + b;
}
- 优点:
- 所有包含该头文件的源文件都能看到完整定义,编译器可内联优化。
- 遵守ODR规则(所有定义相同且全局可见)。
4. 特殊情况:源文件内私有内联函数
若函数仅在单个源文件内使用,可直接在源文件中定义(无需声明分离):
// 在 .cpp 文件内
inline void helper() { ... } // 仅本文件可见,无链接问题
最佳实践:始终将inline
函数的完整定义放在头文件中,确保编译器在所有调用处都能看到函数体以实现内联优化。分离声明和定义会破坏内联的核心机制,应避免使用。
7. auto关键字
平常当我们与代码时候,往往需要用到比较复杂的类型,比如函数数组指针,写起来又麻烦又容易出错。
为了解决这个问题,c可以用typedef来解决这个问题,简化类型,但是有时候typedef并不能全部替代,比如typedef 11 longlong;但是一旦用到longlong*的时候,如果写作11*就会报错
7.1. 概念
概念: CPP引入的一种根据接受值自动推导类型的一种模板类型
7.2. auto使用场景
7.3. auto使用细则
- auto使用必须初始化
- auto与指针结合时右值必须是地址
- 在同一行定义多个auto变量,右值必须一致
- auto不能做函数参数
- auto不能用来定义数组
- auto使用必须初始化
auto不能当作函参?
答: 因为auto是一种模板自动推到的类型,类似于一种源代码的"占位符",到了编
译阶段就需要根据实参知道应该是什么类型,但是函数参数是链接阶段才会链接到函数体的
7.4. auto的意义
- 优:简化类型
- 缺:影响代码可读性
- 注:
typeid
不一定能解决代码可读性问题。因为auto是一个推导器,可能会出现一大堆推到代码。
8. 范围for
- 使用
- 范围for使用细则
- 循环for迭代范围确定
- 迭代对象要支持
++
和==
操作
- 循环for迭代范围确定
9. nullptr
nullptr
的特性(优势)
- 是关键字,不用包头文件
- 健壮性更好
- 与sizeof((void*)0)大小一致