zoukankan      html  css  js  c++  java
  • (C++)函数参数传递中的一级指针和二级指针

    (C++)函数参数传递中的一级指针和二级指针

    主要内容:

    1、一级指针和二级指针

    2、函数指针传递的例子

    3、什么时候需要传递二级指针?

    4、二级指针在链表中的使用

    1、一级指针和二级指针

    一级指针:即我们一般说的指针,就是内存地址;

    二级指针:指向指针的指针,就是地址的地址;

    如:

    int a=1;

    int *p=&a;  // p为a变量的地址,通过*p可以得到a的值

    int **q=&p;   // q为p指针的地址,通过**q可以得到a的值 

    2、函数指针传递的例子

    程序1:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    #include<stdio.h>
     
    void  fun(int  *p){
        int  b=100;
        p=&b;
    }
     
    int main(){
        int  a=10;
        int  *q;
        q=&a;
        printf("%d ",*q);
        fun(q);
        printf("%d ",*q);
        return  0;
    }

    运行结果:

    10

    10

    程序2:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    #include<stdio.h>
     
    void  fun(int  **p){
        int  b=100;
        *p=&b;
    }
     
    int main(){
        int  a=10;
        int  *q;
        q=&a;
        printf("%d ",*q);
        fun(&q);
        printf("%d ",*q);
        return  0;
    }

    运行结果:

    10

    100

    程序3:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    #include<stdio.h>
    #include<stdlib.h>
     
    void  myMalloc(char  *s){
         s=(char*)malloc(100);
    }
     
    int main()
    {
         char  *p=NULL;
         myMalloc(p);
         if(p==NULL)
            printf("P is not changed! ");
         else{
            printf("P has been changed! ");
            free(p);
         }
         return 0;
    }

    运行结果:

    P is not changed!

    程序4:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    #include<stdio.h>
    #include<stdlib.h>
     
    void  myMalloc(char  **s){
         *s=(char*)malloc(100);
    }
     
    int main()
    {
         char  *p=NULL;
         myMalloc(&p);
         if(p==NULL)
            printf("P is not changed! ");
         else{
            printf("P has been changed! ");
            free(p);
         }
         return 0;
    }

    运行结果:

    P has been changed!

    3、什么时候需要传递二级指针?

    通过上述例子,我们可以看到,在某些情况下,函数参数传递一级指针时,在函数体内对指针做变动,也不会对原始指针产生变化,而传递二级指针时,则可以,这是为什么呢?

    在传递一级指针时,只有对指针所指向的内存变量做操作才是有效的;

    在传递二级指针时,只有对指针的指向做改变才是有效的;

    下面做简单的分析:

    在函数传递参数时,编译器总会为每个函数参数制作一个副本,即拷贝;

    例如:

    void fun(int *p),指针参数p的副本为_p,编译器使_p=p,_p和p指向相同的内存空间,如果在函数内修改了_p所指向的内容,就会导致p的内容也做相应的改变;

    但如果在函数内_p申请了新的内存空间或者指向其他内存空间,则_p指向了新的内存空间,而p依旧指向原来的内存空间,因此函数返回后p还是原来的p。

    这样的话,不但没有实现功能,反而每次都申请新的内存空间,而又得不到释放,因为没有将该内存空间的地址传递出来,容易造成内存泄露。

    void fun(int **p),如果函数参数是指针的地址,则可以通过该参数p将新分配或新指向的内存地址传递出来,这样就实现了有效的指针操作。

    如果觉得二级指针比较难理解,也可以通过函数返回值的形式来传递动态内存(切记不能返回栈内存),如:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    #include<stdio.h>
    #include<stdlib.h>
     
    char* myMalloc(){
         char *s=(char*)malloc(100);
         return s;
    }
     
    int main()
    {
         char  *p=NULL;
         p=myMalloc();
         if(p==NULL)
            printf("P is not changed! ");
         else{
            printf("P has been changed ");
            free(p);
         }
         return 0;
    }

    知道了上述这些,就不难理解上面四个小程序的执行结果了。

    4、二级指针在链表中的使用

    在链表或者树的操作中,也需要用到二级指针,

    比如创建链表的头指针:

    在初始化链表函数中,传入头指针,并在函数中为该指针分配空间,此时就应该使用二级指针,如void initLinklist(Node **head);

    而在添加删除结点的过程中,我们并没有改变函数参数指针的指向,而是通过传入的指针如Node *head,找到要删除结点的位置,并未对该指针做改变,因此退出函数后,该指针无影响。

    (1) c++中的悬浮指针:声明了但没有被付值的指针,它指向内存中的任意一个空间。避免悬浮指针的一个方法是开始就付值为NULL

    (2)“野指针”不是NULL指针,是指向“垃圾”内存的指针。人们一般不会错用NULL指针,因为用if语句很容易判断。但是“野指针”是很危险的,if语句对它不起作用。野指针的成因主要有两种:

    一、指针变量没有被初始化。任何指针变量刚被创建时不会自动成为NULL指针,它的缺省值是随机的,它会乱指一气。所以,指针变量在创建的同时应当被初始化,要么将指针设置为NULL,要么让它指向合法的内存。
    二、指针p被free或者delete之后,没有置为NULL,让人误以为p是个合法的指针。别看free和delete的名字恶狠狠的(尤其是delete),它们只是把指针所指的内存给释放掉,但并没有把指针本身干掉。通常会用语句if (p != NULL)进行防错处理。很遗憾,此时if语句起不到防错作用,因为即便p不是NULL指针,它也不指向合法的内存块。例:
      char *p = (char *) malloc(100);
      strcpy(p, “hello”);
      free(p); // p 所指的内存被释放,但是p所指的地址仍然不变
      if(p != NULL) // 没有起到防错作用
      strcpy(p, “world”); // 出错  

    三、另外一个要注意的问题:不要返回指向栈内存的指针或引用,因为栈内存在函数结束时会被释放。strlen是对char*的,string不行,这个很容易让人误解啊

    四、我们易犯的错误:

    对于第二个错误很容易在C++中出现,比如在类的定义时的构造函数和析构函数,如果在构造函数中动态开辟(new),在析构函数中要释放,然而我们一般都delete释放内存后就结束了,殊不知,指向先前内存的指针就成了野指针(迷途指针),稍有不慎,就会出错,当你向未知区域赋值时,运气好的话会是程序运行错误,要是运气不佳,很可能引起系统崩溃!
    解决方法:将将要指向未知区域的指针(刚定义的或是释放内存的指针)等于NULL或指向常量,使用指针之前再做判断null

    无论在什么情况下delete之后是否要设置为NULL?唯一的判断标准就是以后会不会再用它, 如果以后有可能用,就一定设置为NULL,否则就不必, 除非是对软件的性能要求很强,否则尽管每次delete后都设置NULL好了  这样做,永远是不会运行出错的, 潜在的后果就是,这个赋NULL值的操作,浪费了你万分之一秒不到的时间。

    五 、知识补充:

    一般我们常说的内存泄漏是指堆内存的泄漏。堆内存是指程序从堆中分配的,大小任意的(内存块的大小可以在程序运行期决定),使用完后必须显示释放的内存。应用程序一般使用malloc,realloc,new等函数从堆中分配到一块内存,使用完后,程序必须负责相应的调用free或delete释放该内存块,否则,这块内存就不能被再次使用,我们就说这块内存泄漏了

  • 相关阅读:
    git 查看远程分支、本地分支、删除本地分支
    iOS edgesForExtendedLayout、extendedLayoutIncludesOpaqueBars、automaticallyAdjustsScrollViewInsets属性详解
    【iOS开发】UIWebView与JavaScript(JS) 回调交互
    iOS打印Debug日志的方式
    iOS项目上传到AppStore步骤流程
    IOS开发之实现App消息推送(最新)
    DKNightVersion 的实现 --- 如何为 iOS 应用添加夜间模式
    用Session实现验证码
    HTTP中Get与Post、ViewState 原理
    ASP.NET获取服务器文件的物理路径
  • 原文地址:https://www.cnblogs.com/D-DZDD/p/7245497.html
Copyright © 2011-2022 走看看