C++ 笔记(指针和引用)
概念 📝
指针是一个变量,里面存储的是指向内存存储单元的一个地址。
int a = 5;
int *ptr = &a;
cout << "变量 a 的内存地址:" << ptr << " 值:" << *ptr;打印结果:
变量 a 的内存地址: 0x7ffdaea1eb0c 值: 5a 就是个变量,ptr 是指针变量,ptr 里面存储的是 a 的内存地址。
引用是一种特殊的指针,可以理解为是对一个变量起了个别名。
int b = 5;
int &ref = b;
cout << "变量 b 的地址:" << &b << endl;
cout << "引用 ref 的地址:" << &ref << endl;
cout << "引用 ref 的值:" << ref;打印结果:
变量 b 的地址:0x7ffdd17efcd4
引用 ref 的地址:0x7ffdd17efcd4
引用 ref 的值:5不同点 📜
- 指针是一个对象,而引用不是。所以引用不可以为空,创建的时候,必须初始化,指针可以在任何时候被初始化。
- 指针可以有多级,但是引用只能一级(
int **p正确,而int &&p不正确)。 - 指针的值初始化之后可以改变,可以指向其他存储单元,引用初始化之后就不能再改变。
sizeof引用得到的是所指向的变量 (对象) 的大小,而sizeof指针得到的是指针本身的大小。- 如果返回动态内存分配的对象或者内存,必须使用指针,引用可能引起内存泄漏。
- 指针和引用的自增(
++)运算意义不一样。(一个地址 + 1,一个值 + 1)
指向指针的引用 🤔
int i = 42;
int *p;
int *&ref = p; // ref 是一个引用,引用了一个指针
ref = &i; // ref 相当于是 p 的别名,等价于 p = &i
cout << ref << endl;
cout << p << endl;
cout << *ref << endl;
*ref = 100;
cout << i;打印结果:
0x7ffe7289e394
0x7ffe7289e394
42
100复杂表达式从右向左一个一个阅读,离变量名最近的符号是对变量类型有直接影响,这里 *(&ref), 因此 ref 是一个引用,然后 (&ref) 是一个指针。
与 const 结合 🔒
1. const 和引用
常量的引用,必须也是一个常量。
const int ci = 1024; // 这是一个常量
const int &r1 = ci; // 正确,引用及其对应的对象都是常量
int &r2 = ci; // 错误,试图让一个非常量引用指向常量引用。注意这两行代码的区别变量的引用
int i = 42; // 一个变量,未初始化
const int &r1 = i; // r1 是指向 i 的引用常量
const int &r2 = 42; // int &r2 = 42 就会报错了,因为引用必须初始化指向的是对象
const int &r3 = r1 * 2;
cout << r1 << endl;
cout << r3 << endl;
i = 12;
cout << r3 << endl; // r3 依旧引用当初的那个变量 i
cout << i << endl; // i 却已经变了,这是个忧伤的故事
int &r4 = r1 * 2; // 报错,r4 是非常量引用,不能用常量引用来初始化。常量引用指向变量的时候,即便变量修改了值,常量引用依旧指向的是当初的那个变量的值,不离不弃。
2. const 和指针
将指针定义为不可更改的常量。
- 顶层
const:表示地址本身是常量 - 底层
const:表示数据是常量
int num_b = 2;
int *const p_b = &num_b; // 顶层 const
int num_a = 1;
int const *p_a = &num_a; // 底层 const
double pi = 3.14;
const double *p = π // 底层 const
*p = 30; // 错误,即便 pi 是变量,但是指针是常量,没办法修改数据
double x = 32.2;
cout << *p << endl; // 输出 3.14
p = &x; // 正确, 可以修改地址
cout << *p << endl; // 输出 33.2
double pi = 3.14;
double *const p = π // 顶层 const
*p = 30; // 正确,没办法修改地址,但是数据可以修改
double x = 32.2;
p = &x; // 错误,不能修改地址从上面的实验可以了解到,底层 const 虽然值是不可以改变的,但是可以通过指针地址的变动,从而修改里面的值。
顶层 const 指向的地址无法改变,那如果我指向一个 const 类型的变量,直接修改它的值,是不是就能变相修改 const 变量的值了呢,答案是不行的。
const double z = 3.14;
double *const y = &z; // 编译报错作为函数参数 🛠️
让指针或者引用作为函数参数来传递,是为了减少对实参的副本拷贝。
我的理解是指针和引用作为函数参数,都可以达到想要的效果,引用可以看作是对实参起了个别名,操作的是同一个对象。
函数参数是指针的函数
void testPoint(int *p)
{
int a = 1;
p = &a; // 获取它的值必须要写成 *p
cout << p << " " << *p << endl << endl;
}函数参数是引用的函数
void testRef(int &ref)
{
int a = 1;
ref = a; // 直接 ref 拿来使用
cout << &ref << " " << ref << endl << endl;
}指针函数和函数指针 🎭
函数指针
一种特殊的指针,指向函数入口
/*
* 定义一个函数指针 p,只能指向返回值为 int,形参为两个 int 的函数
*/
int (*p)(int, int);
int max(int a, int b) {
return a > b ? a : b;
}
int min(int a, int b) {
return a < b ? a : b;
}
int main() {
p = max; // 函数指针 p 指向求最大值的函数 max
int c = (*p)(1, 2);
p = min; // 函数指针 p 指向求最小值的函数 min
c = (*p)(1, 2);
}指针函数
一个函数,返回值是指针
class Hello {
public:
string world;
};
Hello *p(string world) {
Hello *hello = new Hello();
hello->world = world;
return hello;
}
int main() {
Hello *hp = p("name");
cout << "对象中的值" << hp->world << endl;
}上面是用 new 的方式生成了一个 hello 实例,然后返回了这个实例的指针,乍看上去,应该出了作用域就应该被回收了,但是没有,并打印出了 name。
如果是简单类型,就回收了。
int *function(int a) {
int temp = 5;
return &temp;
}
int main() {
int *hp = function(3);
cout << "指针的值" << *hp << endl; // 打印了 0
}原因应该是用了 new,则需要显式 delete,否则内存不释放,就 GG 了。
那不用 new 这种显式声明呢?
class Hello {
public:
string world;
};
Hello *r(string world) {
Hello hello;
hello.world = world;
return &hello;
}
int main() {
Hello *hr = r("name");
cout << "指针的值" << hr->world << endl; // 直接空指针了
}不声明 new,出了作用域,对象马上灭亡,所以空指针。
指向数组 📚
char x[] = {'a', 'b', 'c', '\\0'};
cout << *x; // 指向第 0 个元素,输入 a一个 Array 类型的变量,其实本质上是一个指向数组第一个元素的指针。