zoukankan      html  css  js  c++  java
  • 搞清楚C语言指针

    Part 0:为什么要写这篇文章

    C语言中的指针是C语言的精髓,也是C语言的重难点之一。
    然而,很少有教程能把指针讲的初学者能听懂,还不会引起歧义。
    本文章会尝试做到这一点,如有错误,请指出。

    Part 1:地址和&

    我们先抛开指针不谈,来讲一个小故事:

    一天,小L准备去找小S玩。但是小L不知道小S的家住在哪里,正当他着急的时候,他看到了一个路牌,上面写着:小S的家在神仙小区403

    哦,真的是要素过多。为什么这么说?

    1. 小L和小S:我们可以看做是两个变量/常量。
    2. 小S的家:这里可以看做是变量/常量小S的地址。
      我们要搞清楚,每个变量/常量都和我们一样:我们每个人都有自己的家,正如变量也有自己的地址。通俗的理解,地址是给变量/常量来存放值的地点
    3. 路牌:注意注意注意!这里就指出了变量/常量小S的地址:神仙小区403
      事实上,我们等会会讲,输出一个变量的地址其实是个16进制的数字。

    搞懂了上面,我们再来聊聊&
    &这个符号我们一个不陌生,你最初用到应该是在:scanf("%d",&a)里边。
    &叫做取址符,用来获取一个变量/常量的地址。
    那么我们为什么要在scanf里边用&,不在printf里边用呢?
    一开始我也很疑惑,后来我看到了这个例子:
    你是一个新生,你要进教室。
    但是你并不知道教室在哪里,这个时候你需要教室的地址。
    下课了,你要出教室。
    由于你已经在教室里了,你就不需要获取教室的地址就可以出去了。


    Part 2:一定要记住的东西

    一定要记住:指针就是个变量!
    重要的事情说三次:
    指针就是个变量!他储存的是地址!他自己也有地址!
    指针就是个变量!他储存的是地址!他自己也有地址!
    指针就是个变量!他储存的是地址!他自己也有地址!

    为什么这么说?我们从指针的定义开始:

    指针的定义方法:<类型名+*> [名称]
    也就是说,指针的定义大概是这样的:

    int* ip;            //类型是int*,名称是ip
    float* fp;          //类型是float*,名称是fp
    double* dp;         //类型是double*,名称是dp
    

    有的书上会这么写:

    int *ip;
    float *fp;
    double *dp;
    

    这么写当然没问题,但是对于初学者来说,有两个问题:

    1. 有的初学者会把*p当做是指针名
    2. 有的初学者会把定义时出现的*p取值时出现的*p弄混

    指针他有没有值?有!我们会在下一节给他赋值。
    既然他的定义方式和变量一样,他也有值,他为什么不是变量呢?


    Part 3:与指针相关的几个符号

    与指针相关的符号有两个,一个是&,一个是*
    先来聊聊&
    &我们上面讲过,他是来取地址的。举个例子:

    #include <stdio.h>
    int main(){
        int a = 10;
        float b = 10.3;
        printf("%p,%p",&a,&b);
    }
    

    %p用来输出地址,当然,你也可以写成%d或者%x。先不管这个,我们来看看他会输出什么:

    那么也就是说,变量ab的地址是000000000062FE1C000000000062FE18
    那么我们怎么把这个地址给指针呢?很简单:p = &a;,举个例子:

    #include <stdio.h>
    int main(){
        int a = 10;
        int* p;
        p = &a;
        printf("a的地址:%p
    ",&a);
        printf("指针p自身的地址:%p
    ",&p);
        printf("指针p指向的地址:%p",p);
    }
    

    得到输出:

    a的地址:000000000062FE1C
    指针p自身的地址:000000000062FE10
    指针p指向的地址:000000000062FE1C

    你发现了吗?如果我们有p = &a;,我们发现:直接输出p会输出a的地址,输出&p会输出p的地址(这就是为什么我一再强调p是个变量,他有自己的地址,正如路牌上有地址,路牌自身也有个地址一样)。

    请注意!如果你的指针为int*,那么你只能指向int类型;如果是double*类型,只能指向double类型,以此类推

    当然,void*类型的指针可以转化为任何一种不同的指针类型(如int*,double*等等)

    那么,我们来聊聊第二个符号*
    *有两个用法。第一个在定义指针时用到,第二个则是取值,什么意思?看下面这个例子:

    #include <stdio.h>
    int main(){
        int a = 10;
        int* p;
        p = &a;
        printf("a的地址:%p
    ",&a);
        printf("指针p自身的地址:%p
    ",&p);
        printf("指针p指向的地址:%p
    ",p);
        printf("指针p指向的地址的值:%d",*p);
    }
    

    得到输出:

    a的地址:000000000062FE1C
    指针p自身的地址:000000000062FE10
    指针p指向的地址:000000000062FE1C
    指针p指向的地址的值:10

    哈,我们得到了a的值!
    也就是说,当我们有p = &a,我们可以用*p得到a的值。
    那能不能操作呢?当然可以。
    我们可以把*p当做a的值,那么,我们尝试如下代码:

    #include <stdio.h>
    int main(){
        int a = 10;
        int* p;
        p = &a;
        printf("指针p指向的地址的值:%d
    ",*p);
        *p = 13;
        printf("指针p指向的地址的值:%d
    ",*p);
        *p += 3;
        printf("指针p指向的地址的值:%d
    ",*p);
        *p -= 3;
        printf("指针p指向的地址的值:%d
    ",*p);
        *p *= 9;
        printf("指针p指向的地址的值:%d
    ",*p);
        *p /= 3;
        printf("指针p指向的地址的值:%d
    ",*p);
        *p %= 3;
        printf("指针p指向的地址的值:%d
    ",*p);
    }
    

    得到输出:

    指针p指向的地址的值:10
    指针p指向的地址的值:13
    指针p指向的地址的值:16
    指针p指向的地址的值:13
    指针p指向的地址的值:117
    指针p指向的地址的值:39
    指针p指向的地址的值:0

    棒极了!我们可以用指针来操作变量了。
    那么,我们要这个干什么用呢?请看下一节:实现交换函数


    Part 4:交换函数

    交换函数是指针必学的一个东西。一般的交换我们会这么写:

    t = a;
    a = b;
    b = t;
    

    那么我们把它塞到函数里边:

    void swap(int a,int b){
          int t;
          t = a;
          a = b;
          b = t;
    }
    

    好,我们满怀信心的调用他:

    #include <stdio.h>
    void swap(int a,int b){
          int t;
          t = a;
          a = b;
          b = t;
    }
    int main(){
          int x = 5,y = 10;
          printf("x=%d,y=%d
    ",x,y);
          swap(x,y);
          printf("x=%d,y=%d",x,y);
    }
    

    于是乎,你得到了这个输出:

    x=5,y=10
    x=5,y=10

    啊啊啊啊啊啊啊啊,为什么不行!!!
    问题就在你的swap函数,我们来看看他们做了些啥:

    swap(x,y);             --->把x赋值给a,把y赋值给b
    ///进入函数体
    int t;                 --->定义t
    t = a;                 --->t赋值为a
    a = b;                 --->a赋值为b
    b = t;                 --->b赋值为t
    

    各位同学,函数体内有任何一点谈到了x和y吗?
    所谓的交换,交换的到底是a和b,还是x和y?
    我相信你这时候你恍然大悟了,我们一直在交换a和b,并没有操作x和y

    那么我们怎么操作?指针!
    因为x和y在整个程序中的地址一定是不变的,那么我们通过上一节的指针运算可以得到,我们能够经过指针操作变量的值。
    那么,我们改进一下这个函数

    void swap(int* a,int* b){
          int t;
          t = *a;
          *a = *b;
          *b = t;
    }
    

    我们再来试试,然后你就会得到报错信息。

    我想,你是这么用的:swap(x,y)
    问题就在这里,我们看看swap需要怎样的两个变量?int*int*类型。
    怎么办?我告诉你一个小秘密:
    任何一个变量加上&,此时就相当于在原本的类型加上了*
    什么意思?也就是说:

    int a;
    &a ---> int*;
    double d;
    &d ---> double*;
    int* p;
    &p ---> int**;//这是个二级指针,也就是说指向指针的指针
    

    那么,我们要这么做:swap(&a,&b),把传入的参数int换为int*

    再次尝试,得到输出:

    x=5,y=10
    x=10,y=5

    累死了,总算是搞好了


    Part 5:char*表示字符串

    char*这个神奇的类型可以表示个字符串,举个例子:

    
    #include <stdio.h>
    
    int main()
    {
        char* str;
        str = "YOU AK IOI!";
        printf("%s",str);
    }
    

    请注意:输入和输出字符串的时候,都不能带上*&

    你可以用string.h中的函数来进行操作


    Part 6:野指针

    有些同学他会这么写:

    int* p;
    printf("%p",p);
    

    哦千万不要这么做!
    当你没有让p指向某个地方的时候,你还把他用了!这个时候就会产生野指针。
    野指针的危害是什么?
    第一种是指向不可访问(操作系统不允许访问的敏感地址,譬如内核空间)的地址,结果是触发段错误,这种算是最好的情况了;

    第二种是指向一个可用的、而且没什么特别意义的空间(譬如我们曾经使用过但是已经不用的栈空间或堆空间),这时候程序运行不会出错,也不会对当前程序造成损害,这种情况下会掩盖你的程序错误,让你以为程序没问题,其实是有问题的;

    第三种情况就是指向了一个可用的空间,而且这个空间其实在程序中正在被使用(譬如说是程序的一个变量x),那么野指针的解引用就会刚好修改这个变量x的值,导致这个变量莫名其妙的被改变,程序出现离奇的错误。一般最终都会导致程序崩溃,或者数据被损害。这种危害是最大的。

    不论如何,我们都不希望看到这些发生。
    于是,养成好习惯:变量先赋值。

    指针你可以这么做:int *p =NULL;让指针指向空

    不论如何,他总算有个值了。


    Part 7:总结

    本文干货全部在这里了:

    1. 指针是个变量,他的类型是数据类型+*,他的值是一个地址,他自身也有地址
    2. 指针有两个专属运算符:&*
    3. 指针可以操作变量,不能操作常量
    4. 指针可以表示字符串
    5. 请注意野指针的问题

    本文没有讲到的:

    1. char[],char,const char的区别与联系
    2. const修饰指针会怎么样?
    3. void*指针的运用
    4. 多级指针的运用
    5. NULL到底是什么
    6. malloc函数的运用

    感谢观看!

  • 相关阅读:
    20135337——信息安全系统设计基础第二周学习总结
    20135337——信息安全系统设计基础第一周学习总结
    实验五Java网络编程及安全——20135337朱荟潼
    20135337朱荟潼——实验三之结对实验项目
    【记录】uni-app H5如何给网站设置favicon.icon图标
    【记录】解决maven 打包spring boot 项目生成的jar包少8小时
    【记录】linux 服务器nginx 配置
    【记录】uni-app页面跳转,当前页面刷新
    【记录】java 阿里oss频繁上传文件报错:网络连接错误,详细信息:Connection pool shut down
    【记录】java 阿里云OSS上传文件
  • 原文地址:https://www.cnblogs.com/sdltf/p/13053718.html
Copyright © 2011-2022 走看看