C++入门基础
1 命名空间
命名空间(Namespace)是 C++ 解决标识符命名冲突的核心机制,通过将全局作用域划分为独立区域,实现代码隔离。
1.1 命名空间的定义
- 核心目的:创建独立作用域,避免名称污染。
1. 基本定义语法
namespace NamespaceName { // 命名空间名称(通常用项目名/模块名)// 成员列表(可包含任意合法标识符)int variable; // 变量void function(); // 函数class MyClass { ... }; // 类struct Node { ... }; // 结构体
}
2. 嵌套命名空间
- 支持多层命名空间,解决复杂模块的命名冲突。
namespace Project {namespace Core { // 内层命名空间int version = 1;}namespace GUI { // 同级命名空间void init() { ... }}
}
3. 跨文件合并特性
- 同一工程中同名命名空间自动合并。
namespace N1
{int rand = 10;
}
namespace N1//同一工程同命名空间自动合并
{int a = 20;
}int main()
{
cout << N1::rand << endl;
cout << N1::a <<endl;
}
- 在同一工程中,N1中嵌套的命名空间N2,若在N1外再定义一个N2,次是N2不能与N1中的N2合并。
namespace N1
{namespace N2{int Sub(int x, int y){return x - y;}}}namespace N2//在N1外定义N2
{int Mult(int x, int y){return x * y;}
}int main()
{//cout << N1::N2::Mult(4, 3) << endl;//报错,这里不能合并
}
1.2 命名空间的使用
1. 作用域限定符 ::
(精确访问)
- 语法: Namespace::member
- 场景:精准调用特定成员,避免歧义
namespace N1
{int a = 20;
}int main()
{cout << N1::a << endl;//精确访问,'name::member'
}
2. using声明(引入单个成员)
- 语法:using Namespace::member;
- 场景:频繁使用某空间的特定成员
namespace N1
{int rand = 10;//定义变量int Add(int x, int y)//定义函数{return x + y;}
}using N1::Add;//'using namespace::member'
int main()
{cout << Add(1, 2) << endl;
}
3. using namespace(引入整个空间)
- 语法:using namespace Namespace;
- 场景:快速原型开发(生产代码慎用)
namespace N1
{int rand = 10;//定义变量int Add(int x, int y)//定义函数{return x + y;}
}using namespace N1;
int main()
{cout << Add(1,2) << endl;
}
- 若在N1中定义N2,在N1外再定义一个N2,当引入N1时,会发生冲突。
namespace N1
{int rand = 10;//定义变量int Add(int x, int y)//定义函数{return x + y;}struct Node//定义类{struct Node* next;int val;};namespace N2//可以嵌套命名空间{int Sub(int x, int y){return x - y;}}}namespace N2
{int Mult(int x, int y){return x * y;}
}
using namespace N1;int main()
{cout << N2::Mult(4, 3) << endl;//报错,N2不明确
}
1.3 典型错误规避
- 错误1:头文件使用using namespace
// mylib.h(污染包含此头文件的所有源文件)
using namespace std; // 绝对禁止!
- 错误2:忽略嵌套空间的作用域
namespace A {int x;namespace B {int x; // 合法但易混淆}
}
A::x = 1; // 访问外层 x
A::B::x = 2; // 必须完整指定
1.4 与 C 语言的对比优势
场景 | C 语言方案 | C++ 命名空间方案 |
---|---|---|
库函数冲突 | 需重命名函数 |
|
大型项目模块化 | 前缀约定 |
|
第三方库集成 | 可能需修改源码 | 通过命名空间无缝共存 |
1.5 命名空间的核心价值
- 隔离性:划分逻辑模块,避免全局名称冲突
- 封装性:隐藏实现细节(结合匿名命名空间)
- 扩展性:支持嵌套和合并,适应工程增长
黄金准则:
- 头文件禁止 using namespace
- 生产代码优先使用
作用域限定符
或using声明
- 嵌套命名空间不超过两层(Project::Module::func)
2 C++输入输出流(I/O Stream)
C++通过流(Stream)机制实现数据输入输出,核心组件包含 <iostream> 头文件中的 cout、cin 及运算符 <<、>>。
2.1 核心组件与基础用法
组件 | 类型 | 作用 | 基础用法示例 |
---|---|---|---|
|
| 标准输出(控制台) |
|
|
| 标准输入(键盘) |
|
| 插入运算符 | 将数据插入输出流 |
|
| 提取运算符 | 从输入流提取数据到变量 |
|
| 操纵符 | 插入换行并刷新缓冲区 |
|
| 转义字符 | 插入换行(不刷新缓冲区) |
|
#include <iostream>
using std::cout; // 推荐方式:只引入必要组件
using std::cin;
using std::endl;int main() {int age;std::string name;// 输入示例cout << "Enter name and age: ";cin >> name >> age; // 从键盘提取数据// 输出示例cout << name << " is " << age << " years old." << endl;
}
2.2 关键特性解析
1. 自动类型识别
- 无需格式说明符(如C的%d),自动推导数据类型。
int x = 10;
double y = 3.14;
char c = 'A';cout << x << " " << y << " " << c; // 输出: 10 3.14 A
2. 链式调用
- 通过运算符重载支持连续操作。
// 连续输出
cout << "Sum: " << (a + b) << ", Product: " << (a * b) << endl;// 连续输入
cin >> width >> height >> depth;
2.3 常用格式控制(需 <iomanip>
头文件)
操纵符 | 作用 | 示例 |
---|---|---|
| 设置字段宽度为 |
|
| 设置浮点数精度为 |
|
| 固定小数格式 | 搭配 |
| 左对齐输出 |
|
| 右对齐(默认) |
|
| 十六进制输出 |
|
示例:格式化表格输出
#include <iomanip>cout << left << setw(15) << "Name" << setw(10) << "Age" << endl;
cout << setw(15) << "Alice" << setw(10) << 25 << endl;
cout << setw(15) << "Bob" << setw(10) << 30 << endl;输出:
Name Age
Alice 25
Bob 30
2.4 对比C语言的优化
特性 | C (printf/scanf ) | C++ (cout/cin ) |
---|---|---|
类型安全 | ✘ 需手动匹配格式符 | ✔ 自动类型推导 |
可扩展性 | ✘ 不支持自定义类型 | ✔ 可重载 << />> 运算符 |
代码简洁性 | ✘ 格式字符串与变量分离 | ✔ 链式调用,直接拼接数据 |
错误处理 | ✘ 无内置校验机制 | ✔ 流状态标志(failbit 等) |
国际化支持 | ✘ 弱 | ✔ 宽字符支持(wcout/wcin ) |
3 缺省参数
缺省参数是 C++ 中提升函数灵活性的重要特性,允许在函数声明时指定参数默认值。
3.1 核心概念
1. 定义:在函数声明中为参数赋予默认值,调用时若省略该实参,则自动使用默认值。
2. 本质作用
- 向后兼容:新增参数时不影响旧代码
- 简化调用:减少冗余参数传递
- 增强可读性:明确常用配置值
3.2 参数分类与使用
1. 全缺省参数
- 定义:所有参数均有默认值
- 调用自由:可传 0~N 个参数
void connect(string ip="127.0.0.1", int port=8080, string user="admin");// 合法调用
connect(); // ip="127.0.0.1", port=8080, user="admin"
connect("192.168.1.100"); // ip="192.168.1.100", port=8080, user="admin"
connect("192.168.1.100", 9090); // ip="192.168.1.100", port=9090, user="admin"
2. 半缺省参数
- 定义:部分参数有默认值
- 关键约束:默认值必须从右向左连续给出,调用时实参按从左向右顺序匹配。
// 正确:默认值从右向左连续
void log(string msg, int level=1, bool timestamp=true); // 错误:默认值不连续
void errFunc(int a=10, int b, int c=30); // 编译报错!// 合法调用
log("Start"); // msg="Start", level=1, timestamp=true
log("Error", 3); // msg="Error", level=3, timestamp=true
log("Debug", 2, false); // msg="Debug", level=2, timestamp=false
3. 底层原理剖析
- 编译期替换机制:编译器在调用点自动补全缺失参数。
// 源代码
log("Message");// 编译后实际执行
log("Message", 1, true); // 自动补全默认值
3.3 关键约束与陷阱
声明与定义分离规则
- 默认值仅能出现在声明中(通常位于头文件)
- 定义中禁止重复指定(源文件仅写参数名)
// mylib.h(声明)
void draw(int x, int y, int color=0); // mylib.cpp(定义)
void draw(int x, int y, int color) { // 此处不可写 color=0/* 实现 */
}
3.4 工程实践指南
适用场景
场景 | 示例 | 优势 |
---|---|---|
扩展函数功能 |
| 旧代码兼容新功能 |
简化高频调用 |
| 减少重复参数 |
配置复杂对象 |
| 明确常用配置 |
4 函数重载
函数重载是 C++ 的核心特性,允许在同一作用域内定义多个同名函数,通过参数列表区分不同实现。
4.1 核心概念与分类
1. 定义
在同一作用域中声明多个同名函数,通过参数列表的差异(类型/数量/顺序)实现不同功能。
2. 重载依据
分类 | 合法重载示例 | 非法示例 |
---|---|---|
参数类型不同 | int add(int, int) vs double add(double, double) | |
参数数量不同 | void log() vs void log(string msg) | |
参数顺序不同 | void set(int, char) vs void set(char, int) | |
返回值不同 | - | int f() vs void f() ✘ |
缺省参数差异 | - | void g(int) vs void g(int=0) ✘ |
示例
void print(int val)
{ cout << "Integer: " << val;
}
void print(double val)
{ cout << "Double: " << val;
}
void print(string val)
{cout << "String: " << val;
}print(10); // 调用 print(int)
print(3.14); // 调用 print(double)
print("text"); // 调用 print(string)
4.2 重载与缺省参数的交互
1. 危险组合:重载 + 缺省参数
void process(int a)
{cout << "process(int a)" << endl;
} // 重载1
void process(int a, int b = 0)
{cout << "process(int a, int b = 0)" << endl;} int main()
{process(10); // 歧义!可能调用:// 重载1: process(10)// 重载2: process(10, 0) → 冲突!
}
2. 解决方案
- 方案1:避免同时使用重载和缺省参数
- 方案2:明确区分参数数量
5 引用
引用是 C++ 的核心特性,本质是变量的别名,与指针功能相似但更安全简洁。
5.1 引用的概念
1. 本质定义
引用是为已存在变量创建的别名,不分配独立内存,与原始变量共享地址空间。
2. 语法形式
类型& 引用名 = 原变量; // 必须初始化
int num = 10;
int& ref = num; // ref是num的别名
3. 底层实现
编译器将引用处理为自动解引用的指针:
; int& ref = num;
lea eax, [num] ; 取num地址 -> 相当于指针
mov dword ptr [ref], eax; ref = 20;
mov eax, dword ptr [ref] ; 获取地址
mov dword ptr [eax], 14h ; 解引用赋值 -> *ptr=20
5.2 引用的三大特性
特性 | 说明 | 示例 | 违反后果 |
---|---|---|---|
定义时必须初始化 | 不存在空引用 | int& r = x; | 编译错误 |
一个变量多个引用 | 支持多别名 | int& r1=x; int& r2=x; | - |
不可重绑定 | 初始化后不能更改指向 | r = y; // 错误! | 实际是赋值操作 |
void TestRef()
{int a = 10;//int& ra;//引用在定义时必须初始化,✘ 错误:未初始化int& ra = a;// √ 正确初始化int& rra = a;//一引用多个实体,√ a的第二个别名int b = 20;ra = b;//✘ 非重绑定!实际将b的值赋给a(a=20)//int& ra = b;//✘引用一旦引用一个实体,不能引用其他实体了int& rrra = ra;//引用也可有别名}
5.3 常引用(const Reference)
1. 作用:避免意外修改数据,增强安全性。
2. 使用规则
场景 | 语法 | 示例 |
---|---|---|
绑定普通变量 | const T& ref = var; | const int& cr = num; |
绑定字面量 | const T& ref = value; | const int& cr = 42; |
类型转换绑定 | const T& ref = expr; | const int& cr = 3.14; // int(3) |
void TestConstRef()
{const int a = 10;//int& ra = a;//a为不可更改的常量,int& ra中,ra是可变的量,a的权限放大,编译时出错const int& ra = a;//int& b = 10;//10为常量,引用后10变为可更改的量,权限放大,编译出错const int& b = 10;double d = 3.14;//int& rd = d;//double转换为int会发生强制类型转换,产生临时变量,再将临时变量赋值给rd,由于临时变量具有常性,也属于权限放大const int& rd = d;
}
5.4 引用使用场景
1. 作函数参数(避免拷贝)
- 场景:传递大对象(结构体/类)
- 优势:零拷贝开销,直接操作原对象
void Swap(int& left, int& right)
{int tmp = left;left = right;right = tmp;cout << left << " " << right << endl;
}int main()
{int a = 1;int b = 2;Swap(a, b);
}
2. 作函数返回值(需谨慎)
- 安全场景:返回静态变量/全局变量
int& Addd(int a, int b)
{static int c = a + b;return c;// 安全:静态变量生命周期持续
}
- 危险场景:返回局部变量(悬空引用)
int& Addd(int a, int b)
{int c = a + b;return c;// 错误!局部变量销毁后引用无效
}
int main()
{int& ret = Add(1,2);cout << ret << endl;//c为Addd的局部函数,出函数后销毁,ret接受的是随机值
}
5.5 传值 vs 传引用效率对比
1. 测试代码
struct BigData
{ int arr[10000];
};// 传值(深拷贝)
void byValue(BigData data)
{}// 传引用(零拷贝)
void byRef(BigData& data)
{}// 测试函数
void test()
{BigData data;// 测试传值clock_t t1 = clock();for (int i = 0; i < 100000; ++i) byValue(data);cout << "ByValue: " << clock() - t1 << "ms\n";// 测试传引用t1 = clock();for (int i = 0; i < 100000; ++i) byRef(data);cout << "ByRef: " << clock() - t1 << "ms\n";
}
int main()
{test();
}
- 测试结果:大对象传引用性能碾压传值,小对象(
int
等)差异可忽略
5.6 引用 vs 指针全面对比
特性 | 引用(Reference) | 指针(Pointer) |
---|---|---|
本质 | 别名(语法层) | 内存地址(硬件层) |
内存占用 | 无独立内存(符号表记录) | 占用4/8字节存储地址 |
初始化 | 必须初始化 | 可滞后初始化(危险!) |
重绑定 | 禁止(终身绑定) | 允许任意次数重指向 |
空值 | 无空引用 | 支持 nullptr |
访问方式 | 隐式解引用(直接操作) | 显式解引用(*ptr ) |
多级间接访问 | 不支持(int&& 是右值引用) | 支持多级(int** pp ) |
类型安全 | 强类型检查 | 弱类型(void* 可任意转换) |
sizeof 行为 | 返回原类型大小(sizeof(ref)==sizeof(int) ) | 返回指针大小(4/8字节) |
自增操作 | ref++ 即原变量+1 | ptr++ 指向下一元素 |
代码可读性 | 更简洁(类似直接变量) | 需频繁使用 * 和 & |
典型代码对比:
// 引用版本(简洁)
void swap(int& a, int& b) {int tmp = a;a = b;b = tmp;
}// 指针版本(繁琐)
void swap(int* a, int* b) {int tmp = *a;*a = *b;*b = tmp;
}// 调用对比
int x=1, y=2;
swap(x, y); // 引用:自然如普通变量
swap(&x, &y); // 指针:需取地址操作
5.7 引用核心价值
- 安全性:无空引用、必须初始化、不可重绑定
- 高效性:传参/返回避免拷贝(尤其大对象)
- 简洁性:无需解引用,代码更直观
- 语义明确:别名语义 vs 指针的地址语义
6 内联函数
内联函数是 C++ 中提升性能的关键特性,通过消除函数调用开销优化小型高频函数。
6.1 内联函数的概念
1. 核心定义
- 用 inline关键字修饰的函数,编译器会尝试在调用点直接展开函数体,避免函数调用的栈帧开销。
2. 基础语法
// 声明 + 定义(通常直接在头文件)
inline int add(int a, int b)
{return a + b;
}
6.2 内联函数的五大特性
特性 | 说明 | 示例/注意事项 |
---|---|---|
编译期展开 | 函数体被插入调用处(非函数调用) | 类似宏替换,但有类型检查 |
空间换时间 | 可能增大二进制文件体积 | 适用于小函数(建议 ≤ 10行) |
编译器建议性 | inline 只是提示,编译器可拒绝展开 | 递归/复杂函数会被忽略(g++ -Winline 可警告) |
头文件定义规则 | 必须在每个使用它的文件中可见(通常直接定义在头文件) | 违反导致链接错误(undefined reference ) |
调试支持 | Debug 模式默认关闭展开(需手动开启),Release 模式自动优化 | VS 设置:项目属性 → C/C++ → 优化 → 内联函数扩展 → 只适用于 __inline (/Ob1) |
6.3 内联函数 vs 宏函数
对比项 | 内联函数 | 宏函数 (#define ) |
---|---|---|
处理阶段 | 编译期 | 预编译期 |
类型安全 | ✔ 编译器检查类型 | ✘ 纯文本替换 |
调试 | ✔ 可调试展开后代码 | ✘ 调试时显示未替换的宏名 |
副作用 | 无(参数只计算一次) | 可能多次计算参数(#define SQUARE(x) x*x → SQUARE(a++) 错误) |
作用域 | 遵守命名空间/类作用域 | 全局替换 |
复杂功能 | 支持循环/递归等完整语法 | 仅支持简单表达式 |
- 宏函数陷阱示例:
#define MAX(a,b) ((a) > (b) ? (a) : (b))int x = 1, y = 2;
int z = MAX(x++, y++); // 展开后:((x++) > (y++) ? (x++) : (y++))
// 结果:x=2, y=4, z=3 (非预期行为!)
- 内联函数安全实现:
inline int max(int a, int b)
{return a > b ? a : b;
}
// 调用:max(x++, y++) → 参数先计算一次,安全
6.4 内联函数最佳实践
1. 适用场景
场景 | 示例 | 性能收益 |
---|---|---|
高频小函数 | Getter/Setter | 消除调用开销 |
数学运算 | 向量点积、矩阵乘法 | 避免栈操作成本 |
模板辅助函数 | 模板中的工具函数 | 头文件必须内联 |
2. 禁用场景
- 大函数(> 20行):显著增大代码体积
- 递归函数:编译器通常拒绝内联(除尾递归优化)
6.5 内联函数底层机制
1. 汇编代码对比
// 普通函数调用
int c = add(a, b);
// 汇编:
push b ; 参数入栈
push a
call _add ; 调用函数(开销大)
add esp, 8 ; 清理栈
mov [c], eax// 内联函数展开
int c = a + b; // 直接展开
// 汇编:
mov eax, [a]
add eax, [b]
mov [c], eax
6.6内联函数核心价值
- 性能优化:消除函数调用开销(栈帧分配/参数传递/返回跳转)
- 安全替代宏:类型安全 + 可调试 + 无副作用
- 零开销抽象:封装小操作不牺牲性能
黄金准则:
- 内联函数 ≤ 10 行代码
- 在头文件中定义实现
- 通过性能测试验证效果
- 优先用
constexpr
替代编译期计算需求
7 auto
关键字深度解析
auto 是 C++11 引入的类型推导关键字,用于自动推断变量类型,提升代码简洁性与可维护性。
7.1 类型别名思考
1. 长类型名问题
复杂类型(如迭代器、闭包)使代码冗长且易错:
std::map<std::string, std::vector<int>>::iterator it = data.begin(); // 冗长
2. typedef
的局限性
- 无法处理模板场景
- 可能引入意外行为:
typedef char* pstring;
const pstring p1 = 0; // 实际类型:char* const(常量指针)
const char* p2 = 0; // 指向常量的指针 → 两者不同!
3. auto
的解决方案
auto it = data.begin(); // 自动推导迭代器类型
7.2 auto
简介
1. 核心定义:auto
指示编译器根据初始化表达式自动推导变量类型。
2. 基础用法
auto a = 10; // int
auto b = 3.14; // double
auto c = "hello"; // const char*
auto d = getValue(); // 推导为getValue()返回类型
3. 与 decltype
对比
特性 | auto | decltype |
---|---|---|
推导依据 | 初始化表达式的值类型 | 表达式的声明类型(不计算) |
典型场景 | 变量声明 | 复杂表达式类型获取 |
示例 | auto x = 42; → int | decltype(f()) y = x; → f() 返回类型 |
7.3 auto
使用细则
1. 与指针和引用结合
语法 | 推导规则 | 示例 |
---|---|---|
auto | 忽略引用,推导值类型 | int x=1; int& rx=x; auto a=rx; → a 为int |
auto* | 推导为指针类型 | auto* p = &x; → int* |
auto& | 推导为引用类型 | auto& r = x; → int& |
const auto | 保留顶层const | const int y=2; auto z=y; → int (const丢失) |
const auto& | 保留底层const和引用 | const auto& cr = y; → const int& |
关键测试:
int x = 10;
const int cx = x;
auto a = cx; // a 是 int(const被丢弃)
const auto b = x; // b 是 const int
const auto& c = cx; // c 是 const int&
2. 多变量声明规则:同一语句中所有变量必须推导为相同类型。
auto a = 10, *b = &a; // √ a是int, b是int*
auto c = 20, d = 3.14; // ✘ 错误:c为int, d为double
3. 结构化绑定:auto用于解构复合类型。
std::tuple<int, string> getData() { return {42, "Alice"}; }auto [id, name] = getData(); // id=int, name=string
7.4 auto
不能推导的场景
1. 函数参数
void foo(auto param) { ... } // ✘ 错误,不能作为函数参数
2. 类非静态成员变量
class MyClass {auto value = 10; // ✘ 错误
};
3. 数组类型
auto arr[] = {1, 2, 3}; // ✘ 错误,不能直接用来声明数组
int arr2[] = {1, 2, 3}; // √ 正确
4. 未初始化变量
auto x; // ✘ 错误:缺少初始化表达式
7.5 auto
核心价值
- 代码简洁性:减少冗余类型声明(尤其模板和迭代器)
- 泛型编程支持:配合模板/Lambda 实现通用代码
- 可维护性:类型变更时自动适应(如函数返回类型修改)
黄金准则:
- 优先在局部变量使用
auto
- 避免在接口头文件中使用
- 复杂表达式配合
decltype
确保正确性
8 范围for循环的语法
作用:简化遍历有范围集合(如数组、容器)的过程,避免手动控制循环边界。
语法格式:
for (declaration : range_expression) {// 循环体
}
declaration:声明一个变量,用于迭代访问范围内的每个元素。
- 常用
auto
自动推导元素类型(如auto e
)。 - 需修改元素时用引用(如
auto& e
);只读访问可用值拷贝(如int e
)。
range_expression:
被迭代的范围(如数组、标准库容器)。
示例:
int array[] = {1, 2, 3, 4, 5};
// 修改元素(使用引用)
for (auto& e : array) {e *= 2; // 每个元素变为2倍
}
// 只读访问(使用值拷贝)
for (auto e : array) {cout << e << " "; // 输出:2 4 6 8 10
}
8.1 范围for的使用条件
范围必须明确
- 编译器需能在编译时确定迭代的起止边界。
- 数组:范围是
[首元素, 末尾元素]
(通过数组大小隐式确定)。 - 类类型:需提供
begin()
和end()
成员函数,返回指向范围起止的迭代器。
常见错误:函数参数中的数组实际是指针,范围不确定:
void TestFor(int array[]) { // array 退化为指针for (auto& e : array) // 错误!范围未知cout << e << endl;
}
注意事项
- 循环控制:可用
continue
跳过当前迭代,或用break
终止整个循环(与传统for
行为一致)。 - 类型推导:推荐用
auto
避免类型书写错误,尤其复杂容器(如std::map<std::string, int>
)。
8.2 总结
关键点 | 说明 |
---|---|
语法 |
|
范围确定性 | 数组需完整传入;类需提供 |
迭代器要求 | 对象必须支持 |
适用场景 | 遍历数组、标准库容器( |
重要:函数参数中的数组无法使用范围for(因退化为指针,范围未知)。
9 指针空值 nullptr
9.1 指针空值的问题
- 传统方式:使用
NULL
或0
初始化空指针。
int* p1 = NULL;
int* p2 = 0;
NULL
的本质:在标准库头文件(如<stddef.h>
)中定义为。
#ifdef __cplusplus
#define NULL 0 // C++中定义为整型0
#else
#define NULL ((void*)0) // C中定义为无类型指针
#endif
- 核心问题:
NULL
在 C++ 中被视为整型常量0
,导致重载函数歧义。 - 原因:编译器将
NULL
解析为整型0
,而非指针类型。
void f(int) { cout << "f(int)" << endl; }
void f(int*) { cout << "f(int*)" << endl; }int main() {f(0); // 调用 f(int)(符合预期)f(NULL); // 调用 f(int)!但预期是 f(int*)f((int*)NULL); // 必须强制转换才能调用 f(int*)
}
9.2 C++11 的解决方案:nullptr
- 引入目的:明确区分空指针与整型
0
。 - 类型安全:
nullptr
是指针空值常量,类型为std::nullptr_t
(隐式转换为任意指针类型)。 - 消除歧义:
f(nullptr); // 明确调用 f(int*)
- 无需头文件:
nullptr
是 C++11 关键字,直接使用。 - 大小与指针相同:
sizeof(nullptr) == sizeof(void*); // 32位系统为4字节,64位为8字节
9.3 使用规范
- 初始化指针:
int* p = nullptr; // 正确:明确空指针
char* q = nullptr; // 任意指针类型均可
- 禁止隐式转换:
int a = nullptr; // 错误!不能将 nullptr 赋值给整型
- 模板与类型推导:
auto ptr = nullptr; // ptr 类型是 std::nullptr_t
9.4 代码建议
场景 | 推荐方式 | 不推荐方式 |
---|---|---|
初始化指针 | int* p = nullptr; | int* p = NULL; |
函数重载中表示空指针 | f(nullptr); | f(0); 或 f(NULL); |
条件判断 | if (p != nullptr) | if (p != NULL) |
9.5 总结
关键点 | 说明 |
---|---|
解决历史问题 | 消除 NULL 导致的类型歧义(整型 vs 指针)。 |
类型安全 | 明确表示指针空值,不与整型 0 混淆。 |
兼容性 | C++11 及以上支持,现代编译器默认启用。 |
工程实践 | 新项目强制使用 nullptr ;旧项目迁移时逐步替换 NULL /0 。 |
- 最佳实践:始终使用
nullptr
表示空指针,避免NULL
和0
,以提高代码可读性和类型安全性。