理解c++指针前,先理解c++引用,因为引用的语法更现代化,更好理解。指针是c语言里的,c++兼容c,所以继承了指针。
1. 指针 vs 引用
引用
cpp
int original = 100;
int& ref = original; // ref是original的别名
ref = 200; // 直接修改,original也变成200
// 引用必须在声明时初始化,且不能再指向其他变量
指针
cpp
int original = 100;
int* ptr = &original; // ptr指向original的地址,&在这里是取址操作符取original的地址
*ptr = 200; // 通过解引用修改original
ptr = &other; // 指针可以重新指向其他变量
ptr = nullptr; // 指针可以为空
JavaScript类比
js
// 引用类似于const声明的对象引用
const obj = { value: 100 };
obj.value = 200; // 可以修改内容,但obj永远指向同一个对象
// 指针类似于let声明的变量
let objPtr = { value: 100 };
objPtr.value = 200; // 修改内容
objPtr = { value: 300 }; // 可以指向新对象
objPtr = null; // 可以为空
2. 指针的语法
2.1 指针的基本概念
指针是存储内存地址的变量,它"指向"另一个变量的内存位置。
cpp
// 基本语法:类型* 指针名;
int* ptr; // 声明一个指向int的指针
float* fptr; // 声明一个指向float的指针
char* cptr; // 声明一个指向char的指针
2.2 指针的声明和初始化
cpp
// 方式1:声明后赋值
int x = 42;
int* ptr; // 声明指针
ptr = &x; // 使用取地址符&获取x的地址
// 方式2:声明时初始化
int y = 100;
int* ptr2 = &y; // 声明并初始化
// 方式3:初始化为空指针
int* ptr3 = nullptr; // C++11推荐方式
int* ptr4 = NULL; // 传统方式(不推荐)
2.3 指针的解引用
cpp
int value = 42;
int* ptr = &value;
// 解引用:使用*操作符获取指针指向的值
int result = *ptr; // result = 42
// 通过指针修改值
*ptr = 100; // 现在value = 100
2.4 指针的成员访问
cpp
// 假设有一个类
class Point {
public:
int x, y;
void print() { /* ... */ }
};
Point obj = {10, 20};
Point* ptr = &obj;
// 方式1:箭头操作符 -> (推荐)
ptr->x = 30; // 相当于 (*ptr).x = 30
ptr->print(); // 相当于 (*ptr).print()
// 方式2:解引用后用点操作符
(*ptr).x = 30; // 等价于上面的写法
(*ptr).print();
2. 引用的底层实现就是指针
编译器视角
cpp
int value = 42;
int& ref = value;
// 底层实现类似于:
int* __hidden_ptr = &value; // 编译器生成的隐藏指针
// 当你写 ref 时,编译器自动替换为 (*__hidden_ptr)
汇编代码证明
cpp
int x = 10;
int& ref = x;
int* ptr = &x;
ref = 20; // 编译成:mov [address], 20
*ptr = 20; // 编译成:mov [address], 20
// 生成的汇编代码几乎相同!
引用 = 受限制的指针
cpp
// 引用就是一个有以下限制的指针:
// 1. 不能为nullptr
// 2. 不能重新指向
// 3. 自动解引用
// 4. 不能做指针运算
class SafePointer { // 引用的概念模型
private:
int* ptr; // 底层还是指针
public:
SafePointer(int& target) : ptr(&target) {} // 必须绑定到对象
int& operator=(int value) {
*ptr = value; // 自动解引用
return *this;
}
operator int&() {
return *ptr; // 自动转换为引用
}
// 禁止的操作:
// SafePointer() = delete; // 不能默认构造
// void operator=(SafePointer&) = delete; // 不能重新绑定
// void operator++() = delete; // 不能指针运算
};
引用与指针的相互转换
cpp
// 这两种写法在底层是相似的:
// 方式1:使用引用
void updateCamera(Camera& camera) {
camera.position.z = 5; // 编译器:(*hidden_ptr).position.z = 5
}
// 方式2:使用指针
void updateCamera(Camera* camera) {
camera->position.z = 5; // 显式:(*camera).position.z = 5
}
// 调用方式
Camera myCamera;
updateCamera(myCamera); // 编译器:updateCamera(&myCamera)
updateCamera(&myCamera); // 显式传递地址
cpp
int value = 100;
// 引用写法:
int& ref = value;
ref = 200; // 看起来像赋值
cout << ref; // 看起来像变量
// 等价的指针写法:
int* ptr = &value;
*ptr = 200; // 显式解引用
cout << *ptr; // 显式解引用
// 底层都是对同一块内存的操作!
3. 内存地址的理解
想象计算机内存像一个巨大的公寓楼:
cpp
int age = 25; // 在1001号房间放了数字25
int* p = &age; // p这个变量存储的是"1001"这个房号
cout << age; // 直接看1001号房间:25
cout << p; // 看p存储的房号:1001
cout << *p; // 通过房号1001去看房间内容:25
*p = 30; // 通过房号1001,把房间内容改为30
cout << age; // 现在1001号房间的内容:30
js
// JavaScript隐式管理这些"房号"
let obj = { age: 25 }; // JS引擎自动分配"房号",你看不到
let ref = obj; // ref也指向同一个"房号"
指针和引用在大多数情况下是可以理解成一样的,为什么c++在设计时不把指针和引用结合起来,只用一种概念?导致现在代码中一下用指针一下用引用,阅读理解起来麻烦
4. c++中共存指针、引用的原因
历史原因:c++的两个父亲
cpp
// C++继承了C的指针
int* ptr = &value; // 来自C语言的遗产
// 第一个父亲是C,要兼容C程序必须要有C语法
// C++后来添加了引用
int& ref = value; // C++的改进
// 第二个父亲是C++语言设计者,引入,改进自己的语法
C在那个时代可是很牛逼的存在,程序都是基于C写的,可能由于C的不好用,才出现了C++,但是C++又不能立马改革,替换掉C,要想C++得到发展就得兼容C,会写C的人也能立马会写C++,之前写的C程序也能在C++里跑,这样可以慢慢的指引C开发人员往C++靠,程序也渐渐的从C变成C++,然后经过40年的发展,C/C++程序多的数不胜数了,这些程序要继续工作C++必须向后兼容,如果是新语言可以从零开始设计,比如后面出现的Java/C#,JavaScript,Rust中没有指针(以上都是我个人的臆想,没有依据,乐呵一下就好)