C++:指针:看完还不会指针,来捶我

前言:

  指针使得C语言能够更高效地实现对计算机底层硬件的操作,而计算机硬件的操作很大程度上依赖地址,指针便提欧共了一种对地址操作的方法,在一定意义上,指针是C语言的精髓,所以一定要耐心看完。

1:指针介绍

在程序中我们声明一个变量(int a = 1);将数据1 存储到变量a中,计算机内部会将这个数据存到 RAM中,那么,数据存到某个地方,就会涉及地址,就像你买的快递,快递到了就要存到某个驿站里面放着,你的快递就是一个数据,驿站就是一个变量,驿站需要有地址。这就是地址的概念。

那么你现在想想地址(ox0000 0001)不也是一个数据吗?那么不也可以用一个变量存储这个地址数据,是的,当然是可以的,这个变量我就叫指针,指针它就是存储另一个变量内存地址的一种数据类型(指针的内容就是另一个变量的内存地址)

1:指针本身也是一个变量,这个指针变量也有自己的地址,只不过这个地址存放的是另一个变量的地址而已。

2:既然指针它不不过是一种数据类型,那么为了方便,我们就规定在这种类型后面加 *号,表示该类型指针:char型指针(char*),double型指针(double *), int型指针(int *)

// main.cpp
#include<iostream>
int main(){
    int a = 1;     // 定义一个变量 a

    int *p = &a ; // 定义一个int型指针 p ,&a表示取变量a的地址,指针p的内容就是a的地址


    // %p是打印地址(指针地址),十六进制的形式
    printf("%p\n",&a); // 打印变量a的地址

    printf("%p\n",p);  // 打印指针p存储的地址

}

打印结果

0061FF18
0061FF18

很显然两个结果是一样的

C/C++规定:*操作符号:从对应指针类型存放的地址中拿出相应的数据。这个操作也被称为:解引用。所以 *p 打印处理就是 1.

2: 指针的相关操作(运算)(偏移)

算数运算 : +  -  ++  -- 

注意:指针的运算特别容易搞错,不要以为和 普通的int 类型数据运算一样

  指针+1/指针-1,加/减的是整个指针类型的长度,与其说指针的加减法,我认为不如说成指针的偏移更合适,接下来看为什么是偏移,举个非常明显的例子:

#include<iostream>

int main(){
    // 定义一个char型数组,这里的a实质上是一个指针,指向这个数组的首元素a[0]的指针

    char a[5] = { 1, 2, 3, 4, 5 };  
    char* p = a;
    printf("%d\n", *p);        // 输出1 --> a[0]
    printf("%d\n", *(p + 1));  // 输出2 --> a[1]
}


打印结果
1
2
  1. 看输出的结果就很容易看出规律,p指针指向a[0],特别注意p+1指针变成指向a[1],所以*(p+1)  = a[1] = 2,而不是*(p+1)  = a[0] + 1 = 2,当然这里两个答案凑巧一样,但是把数组的内容换一下就不会是一样了,
  2. 如果是改成(*p) + 1,那么就是(*p) + 1 = a[0] + 1 = 2,同理可以改成p+2、p+3......
  3. 减法就不用多说了,理解了指针p+1/p-1,那么指针p++/p--其实是一样的,都是偏移。

3:多级指针 

说起多级指针:曾经大一学习C语言时候,学到二级指针的时候,就已经蒙圈了,如果当时写个n级指针,估计就是C语言从入门到放弃了

  1.  我们先来说说二级指针吧!前面有讲到,指针也是一种数据类型,是一种变量,也有自己的地址,所以既然有地址,而指针就是存放另一个变量的地址的呀,那为什么不能再用一个指针存放这个指针的地址呢,对吧!所以就有了二级指针,就是指向指针的指针
  2. 那么n级指针就是:指向指针的指针的指针的指针的指针........,是不是非常简单!
  3. 最后,大家要明白一个概念,其实并没有什么多级指针这种东西,多级指针就是个指针,称为多级指针是为了我们方便表达而取的逻辑名称。

int a = 1;
int *p = &a;

// 二级指针pp存放指针p的地址,即二级指针pp指向指针p
int **pp = &p;     

// 三级指针ppp存放二级指针pp的地址,即三级指针ppp指向二级指针pp
int ***ppp = &pp;  
......

总之,如果一个内存如果存放的是另一个变量的地址,那么就叫指针。一块内存要么存放实际内容/数据,要么存放的是另一个变量的地址,确实是刚刚所说的非常简单。

 1. 指针本身也是一个变量,也有自己的地址,需要内存存储。 

 2. 指针存放的是所指向的变量的地址,这个所指向的变量也可以是一个指针。

特别注意】:面试可能被问到指针的大小

 1. 指针的大小跟指针是什么类型的没有任何关系。

 2. 在32为系统系统中,所有的指针大小都是4个字节,原因是32系统上所有变量的地址都是32位的,而指针用来存地址的。

4:多维数组

二维数组其实和二级指针有着相似的理解方法:

比如a[3][2],把它理解成一个一维数组来看待,这个一维数组里面有三个元素,只是这个一维数组有点特殊,它的每个元素又是一个一维数组而已。

 前面我们已经知道一维数组a[3]中,a实质上是一个指针,指向这个数组首元素a[0]:

int main(){

    int a[3] = {1, 2, 3};
    // a[0]  -->  *a
    printf("%d\n", *a);       // 打印 1  -->  a[0] 的值

  
    // a[1]  -->  *(a + 1)
    printf("%d\n", *(a + 1)); // 打印 2  -->  a[1] 的值
 
    // a[2]  -->  *(a + 2)
    printf("%d\n", *(a + 2)); // 打印 3  -->  a[2] 的值
}

那么,二维数组a[3][2]当成一维数组看是不是可以得出:

int main(){

       int a[3][2] = {{1, 2}, {3, 4}, {5, 6}};
       
       // a[0][0]  -->  (*a)[0]
       printf("%d\n", (*a)[0]);       // 打印 1  -->  a[0][0] 的值

       // a[1][0]  -->  (*(a + 1))[0]
       printf("%d\n", (*(a + 1))[0]); // 打印 3  -->  a[1][0] 的值

       // a[2][0]  -->  (*(a + 2))[0]
       printf("%d\n", (*(a + 2))[0]); // 打印 5  -->  a[2][0] 的值

      // a[2][1]  -->  (*(a + 2))[1]
      printf("%d\n", (*(a + 2))[1]); // 打印 6  -->  a[2][1] 的值
      
      // ..... 二维数组其它元素类似都可以输出
}

结论一:a[m][n]  等价于 (*(a + m)[n]  -->就是一个数组指针(后面会提到)

 基于前面两种指针和数组的变换,可以继续得出:

int main(){

    int a[3][2] = {{1, 2}, {3, 4}, {5, 6}};
    
    // a[0][0]  -->  (*a)[0]  -->  *(*a + 0)  -->  把 *a 当成整体
    printf("%d\n", *(*a));           // 打印 1  -->  a[0][0] 的值

    // a[1][0]  -->  (*(a + 1))[0]  -->  *(*(a + 1) + 0)
    printf("%d\n", *(*(a + 1)));     // 打印 3  -->  a[1][0] 的值

    // a[2][0]  -->  (*(a + 2))[0]  -->  *(*(a + 2) + 0)
    printf("%d\n", *(*(a + 2)));     // 打印 5  -->  a[2][0] 的值

    // a[2][1]  -->  (*(a + 2))[1]  -->  *(*(a + 2) + 1)
    printf("%d\n", *(*(a + 2) + 1)); // 打印 6  -->  a[2][1] 的值
    
    // ..... 二维数组其它元素类似都可以输出

}

结论二:a[m][n]   等价于   *(*(a + m) + n)

5: 数组指针与指针数组 

1. 数组指针:指针在后,说明它就是个指针,所以数组指针指向的是数组,相当于一次声明了一个指针。从前面就已经知道,二维数组a[3][2]中,a实质上就是一个数组指针

公式:

指向的那个数组的元素类型  (*指针名字)[指向的数组的元素个数]

2. 指针数组:数组在后,说明它就是个数组。字符数组是什么?就是存放字符的数组,那么指针数组就是存放指针类型的数组,相当于一次声明了多个指针 

公式:

数组元素的类型  数组名字[数组元素个数]

int main(){

    
    char *a[3] = {"red", "green", "blue"};
    
    char **pp = a;    //定义二级指针pp, a本质上相当于二级指针
    
    printf("%s\n", pp[0]);    // 打印 red
    
    printf("%s\n", pp[1]);    // 打印 green
    
    printf("%s\n", pp[2]);    // 打印 blue

}

直观上区分数组指针和指针数组的方法:

由于数组指针的 [] 比 * 的优先级高,所以数组指针的指针加括号,所以看看指针有没有用圆括号括起来,就可以区分开。

6: 总结

  1. 关于指针想写的内容还有很多,其实这只是开了个头,比如:野指针、函数指针、函数参数传递方式、const 修饰指针、动态内存分配: malloc 和 free、堆, 栈、内存泄露......,以后再慢慢补齐。
  2. 指针在链表使用的比较多,多写一些链表的操作会对指针理解很有帮助,链表节点的增加、删除、修改、查找,单向链表、双向链表、双向循环链表、内核链表等等。

  • 10
    点赞
  • 60
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值