• 总结了一些指针易出错的常见问题(一)


      简介:计算机是如何从内存单元中存取数据的呢?从程序设计的角度看,有两种办法:一是通过变量名;二是通过地址。程序中声明的变量是要占据一定的内存空间的,例如,C语言中整型变量占2字节,实型变量占4字节。程序中定义的变量在程序运行时被分配内存空间。在变量分配内存空间的同时,变量名也就成为了相应内存空间的名称,在程序中可以用这个名字访问该内存空间,表现在程序语句中就是通过变量名存取变量内容(这就是程序中定义变量的用途,即程序中通过定义变量来实现数据在内存中的存取)。但是,有时使用变量名不够方便或者根本没有变量名可用,这时就可以直接用地址来访问内存单元。例如,学生公寓中每个学生住一间房,每个学生就相当于一个变量的内容,变量名指定为学生姓名,房间是存储单元,房号就是存储单元地址。如果知道了学生姓名,可以通过这个名字来访问该学生,这相当于使用变量名访问数据。如果知道了房号,同样也可以访问该学生,这相当于通过地址访问数据。   

      由于通过地址能访问指定的内存存储单元,因此可以说,地址“指向”该内存存储单元(如同说,房间号“指向”某一房间一样)。故将地址形象化地称为“指针”,意思是通过它能找到以它为地址的内存单元。一个变量的地址称为该变量的“指针”。如果有一个变量专门用来存放另一个变量的地址(即指针),则它称为“指针变量”。在许多高级程序设计语言中有专门用来存放内存单元地址的变量类型,这就是指针类型。指针变量就是具有指针类型的变量,它是用于存放内存单元地址的。通过变量名访问一个变量是直接的,而通过指针访问一个变量是间接的。就好像要在学生公寓中找一位学生,不知道他的姓名,也不知道他住哪一间房,但是知道101房间里有他的地址,走进101房间后看到一张字条:“ 找我请到302”,这时按照字条上的地址到302去,便顺利地找到了他。这个101房间,就相当于一个指针变量,字条上的字便是指针变量中存放的内容(另一个内存单元的地址),而住在302房间的学生便是指针所指向的内容。

    1.认识指针

      1.1 指针和内存

        C程序在编译后,会以三种形式使用内存:

        1)静态/全局内存

          静态/全局声明的变量在这里。这些变量从程序开始运行时分配,直到程序终止才消失。

          所有函数都能访问全局变量,静态变量的作用域则局限在定义它们的函数内部。

        2)自动内存

          这些变量在函数内部声明,并且在函数调用时才创建,他们的作用域局限于函数内部,

          而且声明周期限制在函数的执行时间内。

        3)动态内存

          内存分配在堆上,可以根据需要释放,而且直到释放才消失。指针引用分配的内存,作用域

          局限于引用内存的指针。

        数组和指针实现员工链表时情况:

    1.创建数组时需要知道数组的长度,这样就会限制链表所容纳的元素数量。

    2.指针没有上面的限制,新节点可以根据需要动态分配。malloc和free函数分配和释放动态内存。

    Tips:

    1.如何阅读指针声明,那就是倒过来读

    const int *pci     // pci 是一个指向整数常量的指针变量

    2.在不同平台上用一致的方式显示指针的值比较困难。一种方法就是把指针(强制)转化为void指针,然后用%p/%o/%x格式说明符来显示。

     printf("Value of pi: %p ", (void*)pi);

    void指针是通用指针,用来存放任何数据类型的引用。任何指针都可以被赋给void指针,它可以被转换为原来的指针类型。

    void只用作数据指针,不能用作函数指针。

    3.指针被声明为全局/静态。就会在程序启动时被初始化为NULL。

       1.2 指针操作符

         

        直接间接引用

          指针可以用不同的间接引用层级。双重指针**。

    char *titles[]={"ajj","bbs","klc"};
            char **bestbooks[2];
            char **english[1];
              bestbooks[0]=&titles[0];
              bestbooks[1]=&titles[1];
              english[2]=&titles[2];

        常量和指针

    2. C的动内存管理 malloc/realloc/calloc/free

      malloc分配的是字节数/  sizeof(字节数)  

          内存泄漏:丢失内存地址,应该调用free函数没有调用

     

        迷途指针

    3.指针和函数     指针函数/函数指针/程序栈/堆

      程序栈(程序栈通常占据区域下部,堆则上部,栈向上生长,堆向下生长)

    用指针传递数据的一个主要原因就是函数可以修改数据。

       

     4. 函数指针和指针函数

     

     

    为函数指针声明一个类型定义会比较方便。类型定义的如下:

    typedef int (*funcptr)(int);

    funcptr fptr2;

    fptr2=square;

    ......

    #include<iostream> 
    using namespace std;
    int main()
    {
        int square(int num);
        int (*fptr1)(int); //类似于定义变量 
        typedef int (*funcptr)(int);
        funcptr fptr2;
        fptr2=square;
        int n=5;
        fptr1=&square;//此时也可以将其替换为   fptr1=square, 函数名表示函数入口地址
        cout<<"5 square is "<<fptr1(n)<<endl;
        cout<<"5 square is "<<fptr2(n)<<endl;
        return 0;    
    }
     int square(int num)
     {
         return num*num;
     } 

    传递函数指针,只要把函数指针声明作为函数参数(形参)即可。

    #include<iostream>
    using namespace std;
     
     int add (int num1,int num2)
        {
        return num1+num2;
        }
    
    int sub(int num1,int num2)
        {
            return num1-num2;
        }
    
    typedef int (*fptr)(int,int);
        int compute(fptr operation, int num1,int num2)
        {
            return operation(num1,num2);
        }
        
        int main()
    {
        cout<<compute(add,5,8)<<endl;
        cout<<compute(sub,5,8)<<endl;
        return 0;
     } 

    返回函数指针需要把函数的返回类型声明为函数指针。(注意注释部分////)

    #include<iostream>
    using namespace std;
     
     int add (int num1,int num2)
        {
        return num1+num2;
        }
        int sub(int num1,int num2)
        {
            return num1-num2;
        }
        typedef int (*fptr)(int,int);
        int compute(fptr operation, int num1,int num2)
        {
            return operation(num1,num2);
        }
    //////////////////////////////////////////////
        fptr select(char opcode)
        {
            switch(opcode)
            {
                case '+':return add;
                case '-':return sub;
            }
        }
        
        int evalute(char opcode,int num1,int num2)
        {
            fptr operation=select(opcode);
            return operation(num1,num2);
        }    
    ///////////////////////////////////////////////
        int main()
    {
        cout<<evalute('+',5,8)<<endl;
        cout<<evalute('-',5,8)<<endl;
        return 0;
     } 

    使用函数指针数组可以基于某些条件选择要执行的函数,只要把这些函数指针声明为数组类型即可。定义如下:

    #include<iostream>
    using namespace std;
     
     int add (int num1,int num2)
        {
        return num1+num2;
        }
        int sub(int num1,int num2)
        {
            return num1-num2;
        }
        typedef int (*fptr)(int,int);
        int compute(fptr operation, int num1,int num2)
        {
            return operation(num1,num2);
        }
    //////////////////////返回函数指针////////////////////////
        fptr select(char opcode)
        {
            switch(opcode)
            {
                case '+':return add;
                case '-':return sub;
            }
        }
        
        int evalute(char opcode,int num1,int num2)
        {
            fptr operation=select(opcode);
            return operation(num1,num2);
        }    
    /////////////////////使用函数指针数组//////////////////////////
    typedef int (*operation)(int,int);
    operation operations[128]={NULL};
    void initializeopArray()
    {
        operations['+']=add;
        operations['-']=sub;
    }
    int evaArray(char opcode,int num1,int num2)
    {
        fptr operation;
        operation=operations[opcode];
        return operation(num1,num2);
    }
    /////////////////////////////////////////////////////
        int main()
    {
        initializeopArray();
        cout<<evalute('+',5,8)<<endl;
        cout<<evalute('-',5,8)<<endl;
        cout<<evaArray('+',5,8)<<endl;
        cout<<evaArray('-',5,8)<<endl;
        return 0;
     } 

    小结:理解程序栈和堆有助于更深入彻底理解程序的工作方式以及指针的行为。

    备注:本文代码均为Dev C++编译器编译

  • 相关阅读:
    2018软件工程第七次作业(团队二)
    2018软件工程第六次作业(团队一)
    2018软件工程第五次作业(结对二)
    2018软件工程第四次作业(结对一)
    2018软件工程第三次作业(个人三)
    2018软件工程第二次作业(个人二)
    2018软件工程第一次作业(个人一)
    Serializable与transient的联合使用:动态管理成员属性——《Thinking in Java》随笔033
    transient关键字的应用——《Thinking in Java》随笔032
    Serializable:用于保存及还原对象——《Thinking in Java》随笔031
  • 原文地址:https://www.cnblogs.com/iloverain/p/5583612.html
Copyright © 2020-2023  润新知