C++默认按值传递参数,包括指针本身;要修改指针变量的指向,须传二级指针(int*)或指针引用(int&),后者更安全自然。

函数里改不了原始变量?那大概率传的是值
当函数内部修改了参数,但调用方的变量没变,说明你传的是副本——也就是按值传递。C++ 默认所有参数都是传值,包括指针变量本身。比如 void foo(int* p),传进来的 p 是原指针的拷贝,你能通过它改它指向的内存(如 *p = 42),但没法让调用方的指针变量指向别处(比如在函数里写 p = new int(99),外面的指针不受影响)。
常见错误现象:
- 想在函数里分配内存并让调用方拿到新地址,结果调用方指针仍是 nullptr 或旧地址;
- 函数里对指针做了 ++p,返回后原指针没动。
- 传值:函数得到的是指针变量的副本,地址值相同,但变量本身独立
- 想让函数能“重绑定”指针(即改变指针变量本身的值),必须传指针的地址(即
int**)或引用(int*&) - 性能上,传指针比传大对象(如
std::vector)便宜,但传指针本身开销仍是复制一个地址(通常 8 字节)
想让函数修改指针本身?用指针的指针或引用
如果目标是让函数内部分配内存、并让调用方的指针变量真正指向新地址,就得把指针的地址交给函数——也就是二级指针。或者更现代、更安全的做法:用指针引用。
示例对比:
void alloc_bad(int* p) { p = new int(100); } // 外面看不到 new 出来的地址
void alloc_good(int** p) { *p = new int(100); }
void alloc_clean(int*& p) { p = new int(100); }调用方式:
立即学习“C++免费学习笔记(深入)”;
int* ptr = nullptr; alloc_good(&ptr); // 必须取地址 alloc_clean(ptr); // 语法更自然,语义更清晰
-
int**易错:容易漏掉&,也容易误写成**p = ...(那是改内容,不是改指针) -
int*&更符合直觉,且编译器会阻止你传临时量(如alloc_clean(&x)这种错误写法直接报错) - 两者都避免了拷贝整个对象,但引用形式可读性和安全性更高
传值 vs 传指针 vs 传引用:别只看“能不能改内容”
关键区别不在“能不能改数据”,而在于“谁的数据”和“谁的变量”。同一个 int x = 5;,三种传法行为完全不同:
-
func(x):函数收到x的副本,改它不影响x -
func(&x):函数收到&x的副本(即一个int*),能通过它改x的值,但不能让&x变成别的地址(因为地址字面量不可变) -
func(x)配合void func(int& r):函数直接操作x本体,改r就是改x;且无法传字面量(如func(5)报错),除非加const
注意:int* 和 int& 都能实现“修改原始值”,但语义不同:前者强调“我操作的是某块内存”,后者强调“我操作的就是这个变量”。实际选型时,优先用引用(&)代替裸指针(*)做输入/输出参数,除非需要空值(nullptr)语义。
const 修饰位置决定你能干什么
const 放在指针声明的不同位置,约束的对象完全不同,直接影响函数能否修改数据或指针本身:
void f1(const int* p); // *p 不可改(数据只读),p 可改(指针可移动) void f2(int* const p); // p 不可改(指针固定),*p 可改(数据可写) void f3(const int* const p); // 两者都不可改
最常见的是 f1 形式,用于只读访问外部数据(如字符串字面量、只读缓冲区)。如果你发现函数里被禁止写 *p = ...,检查是不是加了前面的 const;如果被禁止写 p++ 或 p = ...,那就是后面的 const 在起作用。
- 传参时加
const不仅是自我约束,更是接口契约:告诉调用方“我不会改你的数据” - 编译器可能据此做优化(如缓存值、省去重读)
- 但要注意:
const int* p允许你把一个非 const 变量的地址传进去,只是函数内部不能改它
真正容易被忽略的是:指针本身的可变性常被当成理所当然,直到你在函数里试图重新赋值指针却失败,才回头翻声明——这时候看 const 在哪,比猜逻辑快得多。










