魔法罐子

C++ 笔记(指针和引用)

2018-09-12

概念

指针是一个变量,里面存储的是指向内存存储单元的一个地址。

1
2
3
int a = 5;
int *ptr = &a;
cout<<"变量 a 的内存地址:"<<ptr<<"值:"<<*ptr;

打印结果:
变量 a 的内存地址: 0x7ffdaea1eb0c 值: 5 变量 b 的地址:0x7ffdaea1eb08

a 就是个变量,ptr 是指针变量,ptr 里面存储的是 a 的内存地址。

引用是一种特殊的指针,可以理解为是对一个变量起了个别名。

1
2
3
4
5
int b=5;
int &ref = b;
cout <<"变量 b 的地址:"<<&b<<endl;
cout <<"引用 ref 的地址:"<<&ref<<endl;
cout <<"引用 ref 的值:"<<ref;

打印结果:

1
2
3
变量 b 的地址:0x7ffdd17efcd4
引用 ref 的地址:0x7ffdd17efcd4
引用 ref 的值:5

不同点

1、指针是一个对象,而引用不是。所以引用不可以为空,创建的时候,必须初始化,指针可以在任何时候被初始化。
2、指针可以有多级,但是引用只能一级(int **p 正确,而 int &&p 不正确)。
3、指针的值初始化之后可以改变,可以指向其他存储单元,引用初始化之后就不能再改变。
4、”sizeof 引用”得到的是所指向的变量 (对象) 的大小,而”sizeof 指针”得到的是指针本身的大小。
5、如果返回动态内存分配的对象或者内存,必须使用指针,引用可能引起内存泄漏。
6、指针和引用的自增(++)运算意义不一样。(一个地址 + 1,一个值 + 1)

指向指针的引用

1
2
3
4
5
6
7
8
9
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;

打印结果:

1
2
3
4
0x7ffe7289e394
0x7ffe7289e394
42
100

复杂表达式从右向左一个一个阅读,离变量名最近的符号是对变量类型有直接影响,这里 *(&ref), 因此 ref 是一个引用,然后 (&ref) 是一个指针。

与 const 结合

1、const 和引用
常量的引用,必须也是一个常量。

1
2
3
const int ci=1024;// 这是一个常量
const int &r1=ci;// 正确,引用及其对应的对象都是常量
int &r2=ci;//c 错误,试图让一个非常量引用指向常量引用。注意这两行代码的区别

变量的引用

1
2
3
4
5
6
7
8
9
10
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:表示数据是常量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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 = &pi;// 底层 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 = &pi;// 顶层 const
*p = 30; // 正确,没办法修改地址,但是数据可以修改
double x=32.2;
p = &x; // 错误,不能修改地址

从上面的实验可以了解到,底层 const 虽然值是不可以改变的,但是可以通过指针地址的变动,从而修改里面的值。
顶层 const 指向的地址无法改变,那如果我指向一个 const 类型的变量,直接修改它的值,是不是就能变相修改 const 变量的值了呢,答案是不行的。

1
2
const double z = 3.14;
double *const y = &z;// 编译报错

作为函数参数

让指针或者引用作为函数参数来传递,是为了减少对实参的副本拷贝。
我的理解是指针和引用作为函数参数,都可以达到想要的效果,引用可以看作是对实参起了个别名,操作的是同一个对象。
函数参数是指针的函数:

1
2
3
4
5
6
void testPoint(int *p)
{
int a=1;
p=&a; // 获取它的值必须要写成 * p
cout<<p<<""<<*p<<endl<<endl;
}

函数参数是引用的函数:

1
2
3
4
5
6
void testRef(int& ref)
{
int a=1;
ref=a; // 直接 ref 拿来获取用了
cout<<&ref<<""<<ref<<endl<<endl;
}

指针函数和函数指针

函数指针:
一种特殊的指针,指向函数入口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/*
* 定义一个函数指针 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(){
f = max; // 函数指针 f 指向求最大值的函数 max
int c = (*f)(1, 2);
f = min; // 函数指针 f 指向求最小值的函数 min
c = (*f)(1, 2);
}

指针函数:
一个函数,返回值是指针

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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,
如果是简单类型,就回收了。

1
2
3
4
5
6
7
8
9
int *function(int a){
int temp = 5;
return &temp;
}
int main()
{
int *hp = function(3);
cout << "指针的值"<<hp<<endl; // 打印了 0
}

原因应该是用了 new,则需要显式 delete,否则内存不释放,就 GG 了。
那不用 new 这种显式声明呢?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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,出了作用域,对象马上灭亡,所以空指针。

指向数组

1
2
char x[] = {'a','b','c','\0'};
cout<< *x; // 指向第 0 个元素,输入 a

一个 Array 类型的变量,其实本质上是一个指向数组第一个元素的指针。

使用支付宝打赏
使用微信打赏

若你觉得我的文章对你有帮助,欢迎点击上方按钮对我打赏

扫描二维码,分享此文章