C++指针与引用浅谈

一直对指针有一种恐惧心理,写程序都不怎么敢用指针,但感觉这样不太行,这东西就算不深入,但还是得知道怎么用才对。由于C++还有个引用,因此也把这家伙一并“谈谈”。水平不够,自然只能浅谈,如有错误,还请见谅。

空悬指针、野指针

空悬指针指向已经销毁的对象或已经回收的地址。

比如用freedelete把申请的内存释放了,或者指向的对象超过了作用域被安排了。

注意后一种情况还可以正常用这个指针。比如下面这个例子,p指向的对象已经被安排了,但是依然可以输出指针指向的地址保存的值,即数据未被清除。因此在编程的时候要小心这种情况,因为既然对象被安排了,那么对应的内存空间在之后就有可能被重新利用,从而被覆盖。

int* p = nullptr;
if (1) {
    int a = 1;
    p = &a;
}
cout << *p << endl;

野指针指的是未经初始化的指针。

int *p;
cout << *p << ' ' << p << endl;

报错:
在这里插入图片描述

空指针

空指针不指向任何对象。
生成空指针的方法有以下几种

//方法1:用nullptr来初始化指针,这是C++11新标准引入的一种方法
//nullptr是一种特殊类型的字面值
int *p1 = nullptr;
//方法2:直接将指针初始化为字面值0
int *p2 = 0;
//方法3:这是一种老方法,NULL是一个预处理变量,定义在cstdlib中,其值就是0
int *p3 = NULL;

注意虽然可以直接用字面值0初始化指针,但不能搞个值为0的int变量赋值给指针。
在这里插入图片描述

指针与数组

对指针和数组混在一起,再搞些什么加减法,考虑优先级,容易让人头晕,这也是我不爱玩指针的原因。干写原理很没劲,干脆想些情况分析。
先看点简单的。

int a[] = { 1,3,5 };
int* p = a;
cout << *p << endl;//输出1
cout << *p + 1 << endl;//输出2
cout << *(p + 1) << endl;//输出3

指针p指向数组a的首地址,直接取值(*p)得到1。*p + 1是先取值再加1,输出2。*(p + 1)是指针加1再取值,指针加1,那么p就指向了a的第二个元素,取值就是3了。

再看下面两种情况。

int a[] = { 1,3,5 };
int* p = a;
cout << ++*p << endl;//输出2
int a[] = { 1,3,5 };
int* p = a;
cout << *p++ << endl;//输出1
cout << *p << endl;//输出3

这里要考虑一个优先级以及结合顺序的问题。
翻了翻《C++ Primer》发现后置递增和递减运算符的优先级要比前置的高,前置的递增和递减运算符的优先级和解引用运算符的优先级相同。二者的结合顺序都是右结合。由此,上面一种情况是对解引用的结果递增,结果当然是2。后面一种情况*p++是先递增,但是后置的递增和递减运算符返回的值等于对象递增前的值,因此解引用的还是指向数值首元素的指针,所以输出1。后面因为p已经递增,因此其指向数组第二个元素,对其解引用,输出结果是3。

这里提一个要注意的点就是数组名是个常量,因此不能拿来自增自减。
在这里插入图片描述

再看一个

int a[][3] = { {1,3,5},{7,9,11},{13,15,17} };
cout << a << endl;
cout << a + 1 << endl;
cout << *a + 1 << endl;
cout << *(a + 1) << endl;
cout << *(a + 1) + 1 << endl;
cout << **a + 1 << endl;
cout << *(*a + 1) << endl;
cout << *(*a + 1) + 1 << endl;
cout << *(*(a + 1) + 1) << endl;
cout << *(*(a + 1)) + 1 << endl;

输出如下:
在这里插入图片描述
a是数组首元素的地址。
a+1,二维数组其实就是几个一维数组成员组成的一维数组,这里+1,就跳到了下一个一维数组成员,a+1是下一行首元素的地址。
*a + 1,就是先对a解引用,然后+1,对a解引用的结果还是首元素的地址,不过+1就不再是跳到下一行了,因为对其解引用其实就是取了第一个一维数组成员,所以+1的结果是同一行的下一个元素的地址。
*(a + 1),就是a+1取值,当然依旧是第二行首元素的地址。
*(a + 1) + 1 ,第二行第二个元素的地址。
后面我不想写了,差不多套路,一般分析这种问题可以画个图。

注意,下面这样是不对的

int a[][3] = { {1,3,5},{7,9,11},{13,15,17} };
int** p = a;

vs提示:
在这里插入图片描述
下面这样是可以的。

int a[][3] = { {1,3,5},{7,9,11},{13,15,17} };
int(*p)[3] = a;

注:写这块的时候很多东西我不明白,输出的结果是没错的,但分析的过程所以可能有些错误,今后勿将上面的分析奉为真理

带上const

首先看一个问题,考虑如下代码将输出什么。

int i = 0;
int& v1 = ++i;
int& v2 = ++i;
cout << v1 << ' ' << v2 << endl;

这个答案很简单,由于v1v2都是i的别名,经过两次自加后i的值为2,因此输出2 2.
在这里插入图片描述

之后来看下面一个。

int i = 0;
const int& v1 = i++;
const int& v2 = ++i;
cout << v1 << ' ' << v2 << endl;

判断这段代码的输出要了解两个知识点,一个是前后++的区别,另一个就是const的影响。如果不考虑这两点,就认为v1v2都是i的别名,那么将产生错误的判断。
前置的++和–运算符返回的是左值,后置的++和–运算符返回的是右值。后置++和–的原理是创建一个临时对象,把值放在这个临时对象后,再增加源变量的值,之后返回的也是这个临时对象,注意返回的是个常量。因此这里引用的对象是值为0的整型常量,而非变量i。当然,非常量引用的初始值必须是左值,不能是常量,vs中如果把const去掉就会提示错误。另外,允许常量引用绑定非常量对象。
在这里插入图片描述
最后再来想这段代码是输出就很清楚了,v1绑定的是一个整型常量,所以输出0,v2绑定的是变量i,所以输出2.
在这里插入图片描述

现在再想,如果后面再让i自加呢。

int i = 0;
const int& v1 = i++;
const int& v2 = ++i;
++i;
cout << v1 << ' ' << v2 << endl;

可以看到由于i的自加,其变为了3,由于v2绑定了i,因此v2的值也是3。
在这里插入图片描述

但是由于常量引用,就不能直接对引用进行运算了。
在这里插入图片描述

尾声:不做这个笔记不知道原来我对指针及引用的认知是如此得浅薄,以至于让我觉得有必要再看看基础书籍,把里面的相关概念及相关示例吃透。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值