C/C++指针介绍

初步了解

所谓指针,就是指向某个地址的东西。最简单的例子就是指南针,它总是指向地理南方,地磁北方。我们也可以将网络上的url【就是你浏览器地址栏的那一串东东】当作一个指针,因为它是某个资源的地址。

C语言中的指针也是同样的意思,而它指向的地址就是某个变量的内存地址,因此也被叫做“指向某个变量的指针”。

在研究指针之前,我们先来康康内存地址在C语言中长啥样

如何查看变量的地址?

int a = 10;
int b = 11;
// 通过 & 运算符
cout << &a << " " << &b << endl;
// 输出结果 0x61ff0c 0x61ff08【请务必记住它们大概的样子】

如何查看该地址所占空间大小?

int a = 10;
cout << sizeof &a << endl;
// 输出结果: 4

知道了地址的样子,我们就稍微作个简单对比:

范畴地址内容名称
网图www.baidu.com/某比基尼图…【略】【请自行想象或者动手百度】萝莉比基尼
C/C++0x61ff0c10a

变量地址的作用

假设你知道了一个宝藏的地址,那么你总会想要知道里面有什么内容,当然啦,该宝藏也应该要有个名字才行,比如“秦始皇陵”
那么问题来了,你要怎样根据这个地址,找到里面的内容呢?
emmm…反正在C语言中,可以通过 指针运算符* 来实现

int a = 10;
// &a: 表示a的地址
// *地址: 表示读取该地址中存储的内容
cout << *&a << endl;
// 输出结果: 10

显然,*&a与 a 实质上是一样的。你肯定想问,既然一样,为啥还要写得这么麻烦?
请先把这个问题吞下,后面再让你消化。

指针变量

这个概念也很好理解,他就是用来存储地址的一个变量。从上面的例子中我们可以看到,地址是一串比较长的东西,因此我们就会想着用另外一个东西来表示。这另外一个东西就是指针变量。而它作为一个变量,那么必然也有自己的内存地址,可以从下图中直观感受一下:
在这里插入图片描述
上图中的指针变量存储的内容是变量a的地址,这种情况下,我们就称其为指向变量a的指针变量

问题1:如何定义指针变量?

方法: int *pointer;
一些概念与操作直接看下图:
在这里插入图片描述
经过这一顿操作(pointer = &a)之后,我们就可以说指针变量pointer指向了变量a。
更多时候,我们会在定义指针变量的时候直接初始化,也就是写成 int *pointer = &a 这种形式。

问题2:如何通过pointer去访问它所指向的变量?

方法: *pointer;
文字解释:*pointer表示 pointer所指向的存储单元的内容,这个内容就是变量。

int a = 10;
int *pointer = &a;
// 在这里 *pointer 这个整体就表示变量a, 因此a能做的事情, *pointer 也能做

图示:
这里特别说明一下,变量存储的值是在一片连续的存储单元中(每个存储单元占1Byte),如果它超过1个字节,那就需要多个存储单元了,但我们要知道它在哪儿的话,只需要记住它的第一个存储单元的位置就可以了,因为一个简单变量的存储空间是连续的。
在这里插入图片描述
小贴士:
请尽量在初始化指针变量的时候赋值,如果不知道赋什么值,请赐它一个NULL。
被赋值为NULL的指针变量,就是传说中的空指针。相信不少java老兄已经蚌埠住了,一定要hole住啊。
空指针是内存中地址为0的区域,是不允许被访问的。非要去访问的话,就给你报错。

关于pointer++

这里说明一下目前所知的运算符的优先级:从高到低

  1. 后置+±-
  2. 前置+±-,!,*,&
  3. 算数运算符
  4. 关系运算符
  5. && 和 ||
  6. 赋值运算符

问题:(*pointer)++ 与 *pointer++ 是否相同?

根据上述的优先级判断,显然是不同的。但是有另外一个问题:pointer存储的是一个内存地址,对它进行++运算,得到的结果是什么呢?【请先不要看下图,冥想一会儿再看】
在这里插入图片描述
譬如上图中,已知pointer当前存储的值是0x0012FF75
你很可能会凭着自己超乎寻常的数学知识算出来 pointer++ 的值是 0x0012FF76.
事实真是如此吗?
不是的,指针在做++运算时,会根据自己的基类型去判断要加多少。这里pointer的基类型是int,因此它的++实质上会+4。而效果就是读到该变量存储空间下面的第一个存储单元,也就是会变成0x0012FF79【现在知道基类型的作用了吧!】
简单来记忆的话,对指针进行 + n 操作,就是跨过n个基类型存储空间的长度。比方说基类型是int,那么对 pointer + n 来说就是在图中跨过n个黄色的区域【每个黄色区域占4byte】。

数组与指针

一些例子

例1

int main() {
    int a[5] = {1, 2, 3, 4, 5}
    cout << a << endl; 		// 0x61fefc
    cout << *a << endl; 	// 1
    cout << &a[0] << endl;  // 0x61fefc
    cout << a[0] << endl; 	// 1
    return 0;
}

我们已经知道a表示的是一个地址,根据第二,第三个输出结果,我们就可以推断出,a所表示的地址就是其第一个元素所在的地址。
也就是说,数组名相当于指向数组第一个元素的指针
对于上面的例子来说就是 a <=> &a[0]
注意: a是一个地址常量,不是变量,无法被赋值。【这与java以及其他语言有区别】

例2

int main() {
    int a[5] = {1, 2, 3, 4, 5};
    int b[5] = {7, 8, 9};
    cout << a << " " << b << endl;
    a = b; // 这里是不能做赋值运算的,如果你的IDE够好,它就会提示你这一行有错误
    // 提示内容:Array type 'int [5]' is not assignable
    cout << a;
    return 0;
}

例3

int main() {
    int a[5] = {1, 2, 3, 4, 5};
    int *p = NULL;
    cout << a << endl;		// 输出:0x61fef8
    p = a; // 由例1可知a就是a[0]的地址,因此可以直接赋值给指针变量P
    cout << p << endl;		// 输出:0x61fef8
    cout << *p << endl;		// 输出:1
    cout << *p++ << endl;	// 输出:1
    cout << *p++ << endl;	// 输出:2
    return 0;
}

如果第四个输出(即第一次*p++的输出)的结果与你想象的不一致,那么这不是你不理解指针,而是你没理解后置++运算。这里稍稍提示,就不展开啦。
顺便给出指针在数组中++的运动图示
在这里插入图片描述
小结:
定义int a[10]; int *p
则:p = a <=> p = &a[0]
数组访问方面:
p + i <=> a + i <=> &a[i]
*(p + i) <=> *(a + i) <=> a[i]
表示形式上:
p[i] <=> *(p+i)

小贴士:

  1. a++是没有意义的,因为a是常量,不能被赋值,但是 p++ 会引起p变化
  2. p可以指向数组范围以外的地址,但a这么做的话就会越界【比如长度为10的数组,你硬要获取a[10];由于使用p来获取数组中的值,不会产生越界异常,因此在使用的时候也要格外小心。

字符串指针

例4:copy

int main() {
    char a[] = "hello kitty", b[15];
    char *p1, *p2;
    for (p1 = a, p2 = b; *p1 != '\0'; p1++, p2++) {
        *p2 = *p1;
    }
    *p2 = '\0';
    cout << "a=>" << a << endl;
    cout << "b=>" << b << endl;
    return 0;
}

例5:关于字符数组的打印

int main() {
    char a[6] = {'h', 'e', 'l', 'l', 'o'};
    char *p = a;
    cout << "value=>" << a << endl; // value=>hello
    cout << "value=>" << p << endl; // value=>hello
    cout << "address=>" << static_cast<void *> (a) << endl; // address=>0x61ff06
    cout << "address=>" << static_cast<void *> (p) << endl; // address=>0x61ff06
    return 0;
}

这个例子说明了cout对字符数组做了特殊处理,如果要打印地址的话可以仿照上面的第三或第四个输出语句。

二维数组的指针

在开始之前,我们先说明一个规范:ISO/IEC9899:2011
在该规范中说了一段话:如果一个数组的名称没有出现在&符号之后,那么这个数组名称的类型将被转化成指针类型。
也就是说int a[10];只要a不出现在&符号之后,那么a就可以表示成指针类型【注意,它不是指针变量】。
那么问题来了,倘若a出现在&之后,也就是 &a,这又表示什么呢?
答:&a 会返回一个指向a数组整体的指针【该指针依然指向a的第一个元素地址,但指针类型不同】。比如a中的元素是int类型,那么a就可以当作是int类型的指针。而 &a 的基类型则是其他类型,可以写作:int (*p)[10],表示指向了一个包含10个int类型元素的数组变量的指针变量,那么这个类型占多大空间呢?整个数组占多少空间,它就占多少空间。
直观的,可以看下图:
虽然 a 和 &a 都指向 a[0] 的地址,但它们基类型所占空间大小不同。
显然,a + 1 => 0x61fef8; 而 &a + 1 => 0x62fff0
在这里插入图片描述
思考: 如果要输出 *(&a),那么结果会是什么呢?
回忆一下上文中刚学习指针变量的时候遇到的一个问题:如何通过pointer去访问它所指向的变量?【这里我们将再次把那张图来画一下,加深记忆】
顺便会议一下:变量地址的作用【*地址: 表示读取该地址中存储的内容】
显然 &a 指向的地址就是数组a,因此就可以画出下面这张十分熟悉的图
在这里插入图片描述
这里使用红色字体表明与先前例子中的区别,但有一点是不变的,那就是可以通过*pointer来间接读取它所指向的变量/常量的值。

另外一种解释:根据规范来,对于*E E为一个指针,那么*E 返回的结果就是E所指向的内容。
这样就很明确了,*(&a) 所指向的内容就是 &a, 而 &a 就是数组 a,因此 *(&a) <=> a
这个结论,我们在上文早已得知,这里不厌其烦地再露一次脸,体现一下重要性。

有了上面的论述,我们就很容易将一维数组扩展到二维数组了。这里不再文字描述,直接上图:
在这里插入图片描述
总结一下:

  1. 数组名相当于指向数组第一个元素的指针
  2. &E 相当于提升基类型
  3. *E 相当于降低基类型

可以观看这个视频来测试一下自己理解了多少:北大计算机概论——二维数组指针练习

必会操作:通过指针遍历二维数组

int main() {
    int a[3][4] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12};
    int *p;
    // 这里注意一下,虽然我们通常会把二维数组写成矩阵的形式,但它在内存中的排列是线性的
    for (p = &a[0][0]; p < &a[0][0] + 12; ++p) {
        cout << p << " " << *p << endl;
    }
    return 0;
}

指针与函数

指针变量做函数参数

#include <iostream>

using namespace std;

void Rank(int *p1, int *p2) {
    int temp;
    if (*p1 < *p2) {
        temp = *p1;
        *p1 = *p2;
        *p2 = temp;
    }
}

int main() {
    int a, b, *p1, *p2;
    cin >> a >> b;
    p1 = &a;
    p2 = &b;
    Rank(p1, p2);
    cout << a << " " << b << endl;
    return 0;
}

这在java中其实就是引用传递的概念。

指针常量与常量指针

参考:黑马——const修饰指针

指针作为函数的返回类型

函数的定义举例:int *function(int param1, int param2)
这里的内容比较简单,但是有一点必须要说明,还是举个例子:

#include <iostream>

using namespace std;

int *getInt() {
    int value = 10;
    return &value;
}

int main() {
    int *p;
    p = getInt();
    cout << *p << endl;
    return 0;
}

你以为会输出20吗?天真了,注意一下 getInt() 方法中 value 的作用范围,这个局部变量在函数调用完后就不存在了,因此return &value; 这个操作是无厘头的。如果你有一款成熟的IDE,那么它就会提示你:Address of stack memory associated with local variable ‘value’ returned。

静态局部变量

静态局部变量的值在函数调用结束后不消失,而保留着,其占用的存储单元不释放,在下一次该函数调用时,仍然可以继续使用该变量。
定义举例: static int a = 1; 要注意,这一步操作只会执行一次,无论它在循环中出现多少次,或者在方法的调用中重复出现多少次。

本文主要参考了李戈老师的北大计算机概论课程,有兴趣的童鞋可以去小破站观摩~

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值