zoukankan      html  css  js  c++  java
  • C语言>实验室>指针在函数中的使用

    一 分析

      指针在代码中的生命周期是:1 创建、2 使用、3 销毁。

      指针在函数中存在的角色有:1 参数、2 返回值、3 存储

      指针还具备特性:1 不能自动结束、2 不能自动增长、3 指向的多样性

    二 创建、使用和销毁

    1 创建一个指针

    int *Pint;//指向int数据的指针
    char *Pchar;//指向char数据的指针
    float *Pfloat;//指向浮点数的指针
    //指向数组和指向结构,以及指向指针的指针以后再专门研究

    问题1:指向各种数据的指针是啥意思
    问题2:不同类型的数据在内存中的表现有什么不同
    问题3:*Pint代表这个指针,还是Pint代表指针

    1)指针是什么

        按定义,‘指针是指向内存地址的变量’,啥意思呢?
        从字面上看,指针应是一个变量,同时这个变量内存储了一个地址,这个地址代表着内存的中某个数据。

    2)如何定义一个指针

      很多人习惯的定义方式其实严格的来说是有问题的,如上面列出的定义方法,很多人会误解*Pint代表了这个指针,int表明这个指针是int指针。而实际上应该是int*表明这是一个int指针变量,Pint是这个变量的变量名。所以规范的定义方式应该是这样。

    int* Pint;//指针变量Pint,指针类型是int*
    char* Pchar;//指针变量Pchar,指针类型是char*
    float* Pfloat;//指针变量Pfloat,指针类型是float*

      这样我们就不会将以下几个形式搞混了1)int*Pint、2)Pint、3)*Pint、4)&Pint。

    3)指针不同形式下的含义

      (1)int* Pint;

        这里定义了一个int*指针变量,变量名是Pint

      (2)Pint

        Pint代表了一个指针变量,作为一个变量,其本身存储的是一个内存的地址,这个内存地址指向的才是实际存储的数据。同时作为一个变量,其在内存中本身也被分配了一个内存地址。那么现在就涉及到了两个重要的概念:(a)Pint变量本身的内存地址,(b)Pint变量中存储的内存地址

        (2-a)Pint变量本身的内存地址

          &Pint就是Pint变量本身的内存地址,测试一下:

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    
    int main()
    {
            int* Pint;
    
            printf("Pint's memory address is: %p\n",&Pint);
            return 0;
    }

          第一次执行:

    Pint's memory address is: 0xbf94c8bc

          第二次执行:

    Pint's memory address is: 0xbf8c9dfc

          由输出可以看出,系统每次都是随机给变量分配了一个内存地址。

        (2-b)Pint变量中存储的内存地址

          Pint变量的值就是其存储的内存地址,试一下:

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    
    int main()
    {
        int* Pint;
        
        printf("Pint save a dress: %p\n",Pint);
        return 0;
    }

          第一次执行:

    Pint save a dress: 0x864ff4

          第二次执行:

    Pint save a dress: 0x3e9ff4

          由输出可以看出,指针指向的内存地址也是随机分配的。不过,需要注意的是,我们在代码里并没有对Pint进行赋值,系统就随机给了指针一个地址,稍不注意就会导致很严重的问题,而且你还不知道哪里出了问题。因为Pint现在指向的内存地址也许是没有数据的,但是随着程序的运行,说不上什么时候这个内存地址就被系统使用了,然后当你试图对其进行修改的时候,就会出错啦。所以在定义指针的时候,最好顺便给它申请好内存,或者干脆就定义为空指针。

        (2-c)比较妥当的指针定义

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    
    int main()
    {
        int* Pint = (int*)malloc(5*sizeof(int));
        int* Pint2 = NULL;
        
        printf("Pint point to: %p\n",Pint);
        printf("Pint2 point to: %p\n",Pint2);
    
        return 0;
    }

          输出:

    Pint point to: 0x9b31008
    Pint2 point to: (nil)

          这样就不用怕未分配内存的指针到处乱跑了。

       (3)*Pint

       首先要明确的是Pint是一个变量,*Pint是访问这个变量指向的地址,假如Pint=0x864ff4,那么*Pint的本质含义应该是*(0x864ff4),访问0x864ff4这个地址的内存。如果Pint=0x864ff5,那么*Pint就应该是*(0x864ff5)。有的同学就会说,哇,这样的话,我给Pint随便赋个值,比如说0x000001,那我就能直接通过*Pint访问和修改了?没错,确实是这样的,那这样岂不是就能直接修改别的程序的内存数据,达到不可告人的目的?哦,不行,因为所有程序都是独立的内存空间,你怎么也访问不到别人的内存。。。而且,我们使用的是虚拟内存空间,还不是实际的内存,所以,搞不了。。。

      (4)&Pint

       前面我们提到过&Pint代表的是Pint这个变量本身的内存地址,这里就不再多说。

    4)不同类型的指针有啥不同

       虽然大家一直都在用指针,但是很多同学还没搞清楚,不同类型的指针具体是有什么不同,为何要定义不同类型的指针。我们就以int*和char*为例,实验一下,这两个类型指针在内存里有啥不同的表现。

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    
    int main()
    {
        int* Pint = (int*)malloc(5*sizeof(int));
        char* Pchar = (char*)malloc(5*sizeof(char));
    
        printf("begin:\n");
        printf("  Pint point to: %p\n",Pint);
        printf("  Pchar point to: %p\n",Pchar);
    
        Pint++;
        Pchar++;
    
        printf("end:\n");
        printf("  Pint point to: %p\n",Pint);
        printf("  Pchar point to: %p\n",Pchar);
    
        return 0;
    }

       这其实就是一个简单的地址累加,将Pchar和Pint指向的地址各累加1,我们看看结果

    begin:
      Pint point to: 0x8353008   Pchar point to: 0x8353020 end:
      Pint point to:
    0x835300c   Pchar point to: 0x8353021

       看上面Pint一开始指向08,最后指向的是0C,增加了4个b。Pchar一开始是20,最后指向了21,只增加了1个b。可是他们不都只累加了一吗?这就是不同类型指针的区别啦!因为int数据是4个b的,所以int*型指针每累加1,内存地址就向前移动4个b,相对的char*型指针每累加一,内存地址只向前移动1个b。所以说,对于内存来说,里面存什么东西它自己是不关心的,重点是你写的程序是怎么看待这些存起来的数据。


    2 指针的使用

      在前面,我们知道了如何定义一个指针,同时也知道了指针在不同形式下的含义。前面我们还讲到,定义一个指针变量后,系统会随机为其赋一个值,这给程序带来了很大的隐患,所以我们要么给他分配一个新的值(一块指定的内存空间),要么就分配一个NULL值给他。给指针分配好内存之后呢,我们就要往里边放东西,前面说过,虽然指针变量是分为int、char等类型的,但是内存可不是这么看的。最后呢,我们还要将指针指向的内存取出来。

    1)给指针分配内存

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    
    int main()
    {
        int* Pint = (int*)malloc(5*sizeof(int));
    
        return 0;
    }

    或者:

    int main()
    {
        int* Pint;
        Pint = (int*)malloc(5*sizeof(int));
    
        return 0;
    }

      前面我们讲过,指针在申明的时候就已经随机分配了一个值,这个值指向了内存中一个随机位置。然后我们使用malloc函数为其分配了内存空间,也就是赋了新的值。但是指针变量在同一时间内只能存放一个内存地址啊,而我们上面的代码中为其分配了5个int数据空间,也就是20个b,有20个内存地址(当然,Pint每累加一个数,内存地址向前跳4个b)。我们在使用指针的时候指针能知道自己可指向的内存范围吗?答案是:不能~指针很笨的,所以用的时候得悠着点。

      (1)不知道自己适用范围的指针

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    
    int main()
    {
        int* Pint = (int*)malloc(5*sizeof(int));
        printf("first Pint point to: %p\n",Pint);
    
        Pint = Pint + 10;
        printf("then Pint point to: %p\n",Pint);
        printf("get the value: %i\n",*Pint);
        return 0;
    }

      输出:

    first Pint point to: 0x978d008
    then Pint point to: 0x978d030
    get the value: 0

      上面的代码中,我们声明了一个Pint,然后给他分配了5个int数据空间,接着使其累加10。可以看到内存地址前进了40,也就是10个int数据,但是我们只给这个指针分配了5个int数据的空间。指针超范围的访问,不只可以读数据,甚至还能写数据。

    2)给指针赋值

      给指针赋值有两种含义,1-给指针变量本身赋值、2-给指针变量指向的内存空间赋值。

      (1)给指针变量本身赋值

      前面的malloc,给指针分配内存空间就是一种赋值方法,给指针变量赋一个值,其实质就是改变指针指向的内存位置,存在这么几种赋值的方法:

        a)malloc分配内存空间

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    
    int main()
    {
    	int* Pint = (int*)malloc(5*sizeof(int));
    	return 0;      
    }

        调用malloc函数可以给指针分配一块内存空间,指针即指向该内存空间的第一个地址。需要注意的是系统并不会约束指针的访问范围。

        b)从其他指针获取

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    
    int main()
    {
        int* Pint1 = (int*)malloc(5*sizeof(int));
        int* Pint2 = Pint1;
    
        *Pint1 = 123;
        printf("*Pint2's value is: %i\n",*Pint2);
      return 0; }

        输出:

    *Pint2's value is: 123

        Pint2从Pint1获得了其内存的空间的首地址,所以可通过Pint2来读取和修改Pint1当前指向的内存。但是,这个时候,我们能否说Pint2就是Pint1呢?留到后面指针的销毁中我们再探讨。

        c)从其他变量获取

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    
    int main()
    {
        int a = 234;
        int* Pint;
    
        Pint = &a;
        printf("*Pint's value is: %i\n",*Pint);
        return 0;
    }

        输出:

    *Pint's value is: 234

        在上面的代码中,我们通过&符号取得了变量a的内存地址,然后将这个地址交给Pint变量,这样,Pint的值就指向了变量a的内存地址。那么现在Pint和a是什么关系,如果我们free了Pint会怎样,我们后面再探讨。

        d)直接赋值

        直接赋值有两种含义,一是给指针变量本身赋值,二是给指针变量指向的内存空间赋值。这里指的是给指针变量本身赋值,通过我多次试验,程序不允许这种行为,当然,我们也不建议这种行为。不过,我试验的都是在代码中直接赋值,还有种方法是通过控制台手动输入赋值,这个没试过。

      (2)给指针变量指向的内存空间赋值

      变量Pint代表的是内存空间的地址,我们在它前面加个*号,用*Pint来对该内存地址指向的内存空间进行操作。这里需要注意一个问题了,*Pint不是变量,它只是一个通向内存的桥梁,通过它我们可以操作内存,但是绝不能把它看成是一个变量,例如:

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    
    int main()
    {
        int* Pint;
        Pint = (int*)malloc(10*sizeof(int));
    
        *Pint = 123;
        printf("first *Pint's value is:%i\n",*Pint);
    
        Pint++;
        printf("then *Pint's value is:%i\n",*Pint);
    
        return 0;
    }

      输出:

    first *Pint's value is:123
    then *Pint's value is:0

      在上面的代码中,我们没有对*Pint做任何操作,但是我们改变了Pint变量的值,于是*Pint跟着就变了,所以,*Pint不是变量,Pint才是变量,*Pint只是在变量Pint前面加了个操作符而已(跟&Pint一个道理)。

      回归正题,给内存空间赋值有这么几种情况:

        a)直接赋值

        直接赋值的意思就是通过*Pint直接改变内存的数值,比如这样:

    int main()
    {
        int* Pint;
        Pint = (int*)malloc(10*sizeof(int));
    
        *Pint = 123;
        printf("*Pint's value is:%i\n",*Pint);
    
        return 0;
    }

     输出:

    Pint's value is:123

        b)间接赋值

        间接赋值其实就是将其他变量的值赋给指针指向的内存,比如这样

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    
    int main()
    {
        int* Pint;
        int* a;
        Pint = (int*)malloc(10*sizeof(int));
    
        a = 123;
        *Pint = a;
        printf("*Pint's value is:%i\n",*Pint);
    
        return 0;
    }

        给指针变量指向的内存空间赋值其实没啥好说的,指针的精华还是在于‘指针变量存储内存地址,内存地址指向实际的值’。

    3)从指针取值

      从前面那么多的废话我们总结如下:1.指针其实是个变量 2.这个变量里存的是内存的地址 3.数据其实是存在内存里的,我们可以通过‘*’符+指针变量名来读写该内存地址指向的内存空间 3.改变指针变量的值,也就是改变了其指向的内存。取值的方法其实我们前面已经说过了,这里就不再赘述。

    3 指针的销毁

      在前面的内容中,我们创建了指针,我们还试着用了用,由此我们了解了指针的本质以及用法。现在我们就来讨论怎么对指针进行销毁,不过在销毁之前,我们先得搞清楚,为何要销毁指针,销毁的又是什么?

    1)为何要销毁指针

      在前面的讨论中,我们说过,如果要使用指针,首先得给这个指针分配一个内存空间(malloc函数),分配之后就随你使用啦,使用完毕之后呢?我们将指针放在那里,不管他其实也不是不行的,毕竟内存空间很大的嘛。不过,对于某些程序来说,可能需要周期性的申请内存空间,使用之后就扔掉,内存是有限的嘛,不可能无限制的给你申请下去。所以,要养成良好的使用习惯,用完就销毁指针,释放你申请的内存。

    2)如何销毁(怎么判断是否已销毁)

      在C语言中,我们使用free函数就能销毁指针了,不过这里有个问题,如何判断这个指针是销毁了,还是没销毁?现在我们就从几个方面来试验一下看看

      a)通过指针变量值来判断

      指针被销毁后,指针变量的值是否会产生什么变化呢?

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    
    int main()
    {
            int* Pint;
        int i;
            Pint = NULL;
        Pint = (int*)malloc(10*sizeof(int));
        printf("first Pint's value is:%p\n",Pint);
    
        free(Pint);
        printf("then Pint's value is:%p\n",Pint);
    
            return 0;
    }

      事实证明:

    first Pint's value is:0x8d6e008
    then Pint's value is:0x8d6e008

      我们比较free前后Pint变量输出的值,可以看到,其值并未改变,也就是说,释放内存后,指针变量指向的内存地址并没有变化,它依然指向free前的那个内存地址。这里我们把这种指针称为“野指针”,意思就是说,Pint变量指向了内存空间某个地址,而该地址并不能为其所用。野指针产生的影响与指针未被初始化类似,所以一个比较妥当的,创建、销毁指针的过程如下:

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    
    int main()
    {
        int* Pint;
        int i;
        Pint = NULL;
        Pint = (int*)malloc(10*sizeof(int));
        //your code
        free(Pint);
        Pint = NULL;
    
        return 0;
    }

      b)内存空间的值

      指针被销毁后,分配给它的内存空间的值是否会发生改变呢?

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    
    int main()
    {
            int* Pint;
        int i;
            Pint = NULL;
        Pint = (int*)malloc(10*sizeof(int));
        //set the memory
        for(i=1;i<=5;i++)
        {
            *Pint = i;
            Pint++;
        }    
            Pint = Pint-5;
        //output
        for(i=1;i<=5;i++)
        {
            printf("first *Pint's value is:%i\n",*Pint);
            Pint++;
        }
        //free
        Pint = Pint-5;
        free(Pint);
        //output
        for(i=1;i<=5;i++)
        {
            printf("then *Pint's value is:%i\n",*Pint);
            Pint++;
        }
    
        Pint = NULL;
            return 0;
    }

      输出:

    first *Pint's value is:1
    first *Pint's value is:2
    first *Pint's value is:3
    first *Pint's value is:4
    first *Pint's value is:5
    then *Pint's value is:0
    then *Pint's value is:2
    then *Pint's value is:3
    then *Pint's value is:4
    then *Pint's value is:5

      在上面的代码中,我们首先对内存空间赋值,然后打印到屏幕。接着free指针,再将内存空间的值打印出来,发现除了第一个值之外,其他值都没变。不过在不同的编译器下,其表现可能还不太一样。(在有些编译器中,会将free后的内存空间全部清零)总的来说,通过内存空间值变化来判断知否free成功还是不靠谱的。

      c)重复进行free

      free函数是没有返回值的,所以内存是否释放成功我们也无从得知,不过,可以想象,若进行重复释放,应该是会报错的。

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    
    int main()
    {
            int* Pint;
            int i;
            Pint = NULL;
            Pint = (int*)malloc(10*sizeof(int));
    
            free(Pint);
            free(Pint);
            Pint = NULL;
            return 0;
    }

      输出:

    *** glibc detected *** ./a.out: double free or corruption (fasttop): 0x08c9d008
    ***
    ======= Backtrace: =========
    /lib/i386-linux-gnu/libc.so.6(+0x6ebc2)[0xad7bc2]
    /lib/i386-linux-gnu/libc.so.6(+0x6f862)[0xad8862]
    /lib/i386-linux-gnu/libc.so.6(cfree+0x6d)[0xadb94d]
    ./a.out[0x804844d]
    /lib/i386-linux-gnu/libc.so.6(__libc_start_main+0xf3)[0xa82113]
    ./a.out[0x8048381]
    ======= Memory map: ========
    004b9000-004d7000 r-xp 00000000 08:01 1442712    /lib/i386-linux-gnu/ld-2.13.so

      综上,我们使用与malloc对应的free函数来释放内存,不过,我们并没有办法来立即判断free函数是否执行成功,但是若重复执行free的话,会输出异常。所以在指针的使用中,我们务必养成良好的使用习惯,一个malloc对应一个free。

    三 在函数中的使用

      指针在函数中担任的角色有:1)参数、2)返回值、3)存储,下面就来探讨一下其不同角色下的一些特性

    1 指针做参数

      将指针作为参数使用到函数中有别于普通的变量,下面我们做个试验。

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    
    int testf(int i,int* Pint)
    {
        i = 1;
        *Pint = 9;
        return 0;
    }
    
    
    int main()
    {
            int* Pint = NULL;
        int i;
        Pint = (int*)malloc(10*sizeof(int));
        *Pint = 99;
        i = 100;
    
        testf(i,Pint);
    
        printf("*Pint's value is:%i\n",*Pint);
        printf("i's value is:%i\n",i);
    
        free(Pint);
    
        Pint = NULL;
        return 0;
    }

      输出:

    *Pint's value is:9
    i's value is:100

      由上例可见,指针作为参数时,其值是不受现场保护的,而一般的变量受到现场保护。为何呢?这就要说到参数是用来干嘛的。

      1)指针与普通变量的值传递

      这个概念很重要,那就是参数传递的是变量的值,而不是变量本身。在上例中,函数 int testf(int i,int* Pint)中定义了两个变量,int i 和 int* Pint,主函数在调用testf时,为testf中的两个变量进行了赋值,使 i=100,Pint=Pint(main)。需要注意的是,testf中的两个变量 i 和 Pint 与主函数的 i 和 Pint 是没有任何关系的,他们只是值相同。于是我们可以明白,在testf中对变量 i 进行赋值,并不会影响到 main函数中的i变量,那testf中的Pint是如何影响了main中的Pint呢?原因就是Pint中存储的并不只是一个简单的数值而是一个内存地址,另外testf中改变的也不是变量Pint的数值而是该数值对应内存地址里存储的值。简单点说,i = 1; *Pint = 9;这两个操作具有本质的不同,前一个改变的是变量的值,后一个改变的是内存地址指向的值。而Pint传递了内存地址,所以main函数和testf函数中的*Pint操作都是操作了同一个内存地址,于是在表现上貌似testf函数改变了main函数里的变量。若我们在testf中执行的是Pint=9(而不是*Pint=9),那么testf在表现上也不会影响到main函数的Pint变量。

    2 指针做返回值

      在前面的探讨中,我们知道当指针作为参数时,其传递的是指针变量的值,同理,指针做返回值时,其返回的也是指针变量的值。例如:

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    
    int* testf(int* Pint)
    {*Pint = 9;
        return Pint;
    }
    
    
    int main()
    {
            int* Pint = NULL;
        int* Pint2 = NULL;
        Pint = (int*)malloc(10*sizeof(int));
        
        Pint2 = testf(Pint);
    
        printf("*Pint's value is:%i\n",*Pint);
        printf("*Pint2's value is:%i\n",*Pint);
    
        free(Pint);
    
        Pint = NULL;
        Pint2 = NULL;
            return 0;
    }

      输出:

    *Pint's value is:9
    *Pint2's value is:9

      可以看到,*Pint与*Pint2最后的值是一样的,因为他们指向的是同一个内存空间。细心的同学会发现,在代码的末尾我们仅free了Pint没有free掉Pint2,这是为何?因为在代码的前面压根就没有为Pint2分配任何的内存啊~所以要注意一点,给谁分配内存就free谁。Pint2只是获得了Pint的第一个内存地址,在这里,我个人也不建议大家使用函数返回值的方式使用指针,很容易引起混淆。

    3 指针做存储

      挖坑,暂时没啥好填的

  • 相关阅读:
    瑞士军刀DLib的VS2015编译
    win10编译libpng
    win10编译zlib
    win10编译jpeglib
    Hough Transform直线检测
    html+css简单的实现360搜索引擎首页面
    HTML和css简单日常总结
    MySQL中的分区(六)KEY分区
    CentOS 8 安装vsftpd 服务器
    linux负载过高 排查方法及说明 附:Centos安装iostat
  • 原文地址:https://www.cnblogs.com/cation/p/3079796.html
Copyright © 2011-2022 走看看