2、深入理解 C++ 引用、指针、内联函数与效率对比 —— 实战讲解与代码分析
🚀 深入理解 C++ 引用、指针、内联函数与效率对比 —— 实战讲解与代码分析
在 C++ 编程中,引用、指针、内联函数、类型推导等概念常常被初学者混淆。本文通过一段完整的代码,逐步讲解这些核心知识点,并通过时间戳测试展示引用的效率优势。
📌 一、const 指针的使用与区别
const int d = 10;
const int c = 10;
const int* p = &d;
p = &c; // ✅ 可以修改指针本身
// *p = 20; // ❌ 错误,不能修改指针指向的内容
const int* p
表示:指针指向的内容是常量,不能修改,但指针本身可以指向其他地址。- 如果写成
int* const p
,则表示指针本身是常量,不能修改指向,但可以修改指向的内容。
🔁 二、引用的使用场景
✅ 引用作为函数参数(交换两个变量)
void swap(int& x, int& y) {int temp = x;x = y;y = temp;
}
引用传参避免了值拷贝,提高效率,并且可以直接修改原始变量。
✅ 引用作为返回值(链式调用)
class MyClass {
public:MyClass& setValue(int v) {value = v;return *this;}void print() {cout << "value: " << value << endl;}
private:int value;
};MyClass obj;
obj.setValue(100).print(); // 链式调用
返回对象自身的引用可以实现链式调用,常用于类成员函数设计。
🧠 三、引用返回值与静态变量
int& Add(int a, int b) {static int c = a + b;return c;
}int& ret = Add(1,2);
// 并且这一个代码在执行的时候,直接就是return c
int& ret = Add(3,4);
cout << ret << endl;
// 会发现打印出来的还是3,因为ret和ret2是同一个c的别名
- 使用
static
保证变量c
的生命周期贯穿整个程序运行期。重点的是这个static只会执行一次 - 返回局部变量的引用是错误的,因为函数结束后局部变量会被销毁。
static
避免了这个问题,但也意味着每次调用返回的是同一个变量。
⚡ 四、引用效率测试(与值传递对比)
void ModifyByValue(int val) { val += 1; }
void ModifyByRef(int& val) { val += 1; }const int N = 100000000;
int* arr1 = new int[N]();
int* arr2 = new int[N]();size_t start = clock();
for (size_t i = 0; i < N; ++i) {ModifyByValue(arr1[i]);
}
size_t end = clock();
cout << "不使用引用的时间: " << double(end - start) / CLOCKS_PER_SEC << "秒" << endl;size_t start2 = clock();
for (size_t i = 0; i < N; ++i) {ModifyByRef(arr2[i]);
}
size_t end2 = clock();
cout << "使用引用的时间: " << double(end2 - start2) / CLOCKS_PER_SEC << "秒" << endl;delete[] arr1;
delete[] arr2;
🔍 结果分析:
- 引用传参避免了值拷贝,尤其在大量数据处理时效率更高。
clock()
用于测量 CPU 时间,适合粗略性能对比。
new和malloc的区别
🧠 本质区别
特性 | new (C++) | malloc (C) |
---|---|---|
所属语言 | C++ | C |
返回类型 | 正确类型指针 | void* (需强转) |
是否调用构造函数 | ✅ 会调用构造函数 | ❌ 不会调用构造函数 |
是否需要头文件 | 不需要 | 需要 <stdlib.h> |
是否支持重载 | ✅ 支持 | ❌ 不支持 |
是否可以自定义行为 | ✅ 可重载 operator new | ❌ 不可自定义 |
是否需要手动释放 | ✅ 用 delete | ✅ 用 free |
🔧 示例代码对比
使用 new
:
int* p = new int(10); // 分配并初始化为10
delete p; // 释放内存
使用 malloc
:
int* p = (int*)malloc(sizeof(int)); // 分配但未初始化
*p = 10;
free(p); // 释放内存
🧩 构造函数调用差异
class MyClass {
public:MyClass() { cout << "构造函数被调用" << endl; }
};MyClass* obj1 = new MyClass(); // ✅ 构造函数被调用
MyClass* obj2 = (MyClass*)malloc(sizeof(MyClass)); // ❌ 构造函数不会被调用
这就是为什么在 C++ 中推荐使用 new
,它更安全、更面向对象。
🛡 安全性与类型检查
new
返回的是正确类型的指针,编译器能做类型检查malloc
返回void*
,必须强制类型转换,容易出错
🧩 五、指针 vs 引用
int x = 10;
int* p = &x; // 指针
int& r = x; // 引用
特性 | 指针 | 引用 |
---|---|---|
可为空 | ✅ 是 | ❌ 否 |
可重新绑定 | ✅ 是 | ❌ 否 |
语法复杂度 | 稍高 | 更简洁 |
底层实现 | 类似 | 类似指针 |
引用必须绑定一个合法对象,不能为 nullptr
,而指针可以为空。
并且在实际底层实现上,其实是一样的,引用也是调用指针去实现
🧠 六、auto 类型推导与 typeid 类型识别
auto a = 10; // int
auto b = 10.5; // double
auto c = &x; // int*cout << "a的类型: " << typeid(a).name() << endl;
cout << "b的类型: " << typeid(b).name() << endl;
cout << "c的类型: " << typeid(c).name() << endl;
auto
是 C++11 引入的关键字,用于自动推导变量类型。typeid(...).name()
可以查看变量的实际类型(编译器相关,可能显示为i
、d
、Pi
等)。
🧱 七、内联函数与宏的区别
inline int Add_1(int a, int b) {return a + b;
}
- 内联函数是对编译器的建议,可能被忽略。
- 优点:减少函数调用开销,适合频繁调用的小函数。
- 不适合复杂逻辑或递归函数。
- 通常定义在头文件中,便于展开。
🧼 八、NULL 与 nullptr 的区别
int* p1 = NULL; // C 风格,等价于 0
int* p2 = nullptr; // C++11 引入,更类型安全
nullptr
是 C++11 的新关键字,表示空指针,避免与整数混淆。- 推荐使用
nullptr
,尤其在模板和重载场景中更安全。