zoukankan      html  css  js  c++  java
  • 深入理解C指针 指针和函数

    局部变量也称为自动变量,他总是分配在栈帧上。

    3.1 程序的堆和栈

    3.1.1 程序栈

      程序栈是支持函数执行的内存区域,通常和堆共享。也就是说,程序栈和堆共享同一块内存区域。程序栈通常占据这块区域的下部,而堆用的则是上部。

    程序栈存放栈帧(stack frame),栈帧有时也称为活跃记录或活跃帧。栈帧存放函数参数和局部变量。堆管理动态内存。

    调用函数时,函数的栈帧被推到栈上,栈向上“长出”一个栈帧。当函数终止时,其栈帧从程序栈上弹出。栈帧所使用的内存不会被清理,但最终可能被推到程序栈上的另一个栈帧覆盖。

    动态分配的内存来自堆,对向下“生长”。随着内存的分配和释放,堆中会布满碎片。尽管堆是向下生长的,但这只是个大体方向,实际上内存可能在堆上的任意位置分配。

    3.1.2 栈帧的组织

      栈帧由以下几种元素组成:

    • 返回地址:函数完成后要返回的程序内部地址
    • 局部数据存储:为局部变量分配的内存
    • 参数存储:为函数参数分配的内存
    • 栈指针和基指针:运行时系统来管理栈的指针

    栈指针通常指向栈顶部。基指针(帧指针)通常存在并指向栈帧内部的地址。这两个指针都不是C指针。它们是运行时系统管理程序栈的地址。

    将栈帧推到程序栈上时,系统可能会耗尽内存,这种情况称为栈溢出。要牢记每个线程通常都会有自己的程序栈。

    3.2 通过指针传递和返回数据

      传递参数(包括指针)时,传递的是他们的值。也就是说,传递给函数的是参数的一个副本。当涉及大型数据结构时,传递参数的指针会高效。

    3.2.1 用指针传递数据

      void swap(int *pnum1, int *pnum2)
      {
          int tmp;
          tmp = *pnum1;
          *pnum1 = *pnum2;
          *pnum2 = tmp;
      }

    3.2.2 用值传递数据

      如果不通过指针传递参数,那么交换就不会发生。num1和num2中保存的只是参数的副本。修改形参不会影响实参。

      void swap(int num1, int num2)
      {
          int tmp;
          tmp = num1;
          num1 = num2;
          num2 = tmp;
      }

    3.2.3 传递指向常量的指针

    如果只是传递指针,数据就能被修改,如果不希望数据被修改,就要传递指向常量的指针。

      void pass(const int * num1,int num2)  不能修改通过指向常量的指针传进来的值

    3.2.4 返回指针

    函数返回指针时可能存在几个潜在的问题:

    • 返回为初始化的指针
    • 返回指向无效地址的指针
    • 返回局部变量的指针
    • 返回指针但是没有释放内存

    3.2.5 局部数据指针

      返回局部变量的指针,一旦函数执行完栈帧就被弹出了,尽管数据可能还在,但如果之后继续执行其他函数,该内存就会被覆盖。

    如果把变量声明为static,这样把函数的作用域限制在函数内部,但是分配在栈帧外面,避免其他函数复写变量值。但每次调用该函数都会重复利用这个变量,这样相当于每次都把上一次调用的结果覆盖掉了。此外,静态数组必须声明为固定长度,这样会限制函数处理变长数组的能力。

    3.2.7 传递指针的指针

      void allocateArray(int **arr, int size, int value) {
          *arr = (int *)malloc(size * sizeof(int));//解引整数指针的指针得到的是整数指针
          if (*arr != NULL){
              for (int i = 0; i < size; i++)
                  *(*arr + i) = value;
            }
        }

    这个函数可用下面代码测试:

      int *vector = NULL;
        allocateArray(&vector, 5, 6);

    实现自己的free函数:

    void saferFree(void **pp){
    if(pp != NULL && *pp != NULL){
    free(*pp);
    *pp = NULL;
    }
    }

    使用指针的指针允许修改传入的指针,而使用void类型则可以传入所有类型的指针。如果调用这个函数时没有显示的把指针类型转换为void会产生警告,执行显示转换就不会有警告。下面这个safeFree宏调用saferFree函数,执行类型转换,并使用了取地址操作符,这样就省去函数使用者做类型转换和传递指针的地址:

     #define safeFree(p) safeerFree( (void**) &p )

    3.3 函数指针

      函数指针是持有函数地址的指针。

    3.3.1 声明函数指针

      类型标识符 (* 指针变量名)( )

      void (* foo)( )     void:返回类型  foo:函数指针变量名  参数

    使用函数指针时一定要小心,因为C不会检查参数传递是否正确。以下是声明函数指针的一些例子:

      int (*f1)(double); //传入 double,返回 int
      void (*f2)(char*); //传入 char 指针,没有返回值
      double* (*f3)(int,int); //传递两个整数,返回 double 指针

    不要把返回指针值的函数和函数指针搞混淆。

      int *f4(); //f4是一个函数,返回一个整数指针
      int (*f5)(); //f5是一个返回整数的函数指针
      int* (*f6)(); //f6是一个返回整数指针的函数指针

    3.3.2 使用函数指针

      int (*fptr)(int);
      int square(int num){
          return num*num;
      }
      ...
      int n = 5;
      fptr = square; // fptr = &square;     让指针等于函数的入口地址。
      printf("%d squard is %d ",n,fptr(n));

      在这种上下文环境中,编译器会忽略取地址符操作,所以你可以写出来,但没必要这么做。

    因为fptr是一个函数指针,那么*fptr就是该指针所指向的函数,所以(*fptr)( )就是调用该函数的方式,ANSIC标准允许简写fptr( )。

    为函数指针声明一个类型定义会比较方便,类型定义的名字是声明的最后一个元素。

      typedef int (*funcptr)(int);
      ...
      funcptr fptr1;
      fptr1 = square;
      printf("%d squard is %d ",n,fptr1(n));

    3.3.3 传递函数指针

     传递函数指针很简单,只要把函数指针声明作为函数参数即可。

    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);
        }
    
        printf("%d
    ", compute(add, 5, 6));
        printf("%d
    ", compute(sub, 5, 6));

    3.3.4 返回函数指针

      返回函数指针需要把函数的返回类型声明为函数指针。

    int add(int num1, int num2) {
        return num1 + num2;
    }
    int sub(int num1, int num2) {
        return num1 - num2;
    }
    typedef int(*fptr)(int, int);
    
    fptr select(char opcode) {
        switch (opcode){
        case '+':return add; 
        case '-':return sub; 
        }
        return 0;
    }
    int evaluate(char opcode, int num1, int num2) {
        fptr operation = select(opcode); //函数指针赋值
        return operation(num1, num2);
    }
        printf("%d
    ", evaluate('+', 5, 6));
        printf("%d
    ", evaluate('-', 5, 6));

     

    3.3.5 使用函数指针数组

      类型标识符( *指针变量名[  ] ) ( );

        int (*operations[128])(int, int)={ NULL };  这个数组的所有元素都被初始化为NULL

    也可以用typedef来声明这个数组:

       typedef int (*operation)(int, int); 如果数组的初始化值是一个语句块,系统会将块中的值赋给连续的数组元素。

       operation operations[128]={ NULL };

    #include<stdio.h>
    #include<stdlib.h>
    
    int plus(int a, int b){
        return(a + b);
    }
    int minus(int a, int b){
        return(a - b);
    }
    int multiplied(int a, int b){
        return(a * b);
    }
    int divided(int a, int b){
        return(a / b);
    }
    int percent(int a, int b){
        return(a % b);
    }
    void main1()
    {
        //int(*p)(int a, int b); 函数指针
        int(*pn[5])(int a, int b) = { plus,minus, &multiplied ,&divided,percent }; //函数指针数组初始化
        int ax = 100; int by = 20;
    
        for (int i = 0; i < 5; i++) //下标方式循环数组
            printf("%d
    ", pn[i](ax, by)); //分别调用五种运算输出结果
    
        system("pause");
    }
    void main()        //函数指针数组名是二级函数指针
    {
        int(*pn[5])(int a, int b) = { plus,minus, multiplied ,divided,percent };
        int ax = 100; int by = 20;
        //通过二级函数指针的方式遍历函数指针数组
        for (int(**p)(int a, int b) = pn; p < pn + 5; p++) //指针循环
        {
            printf("%d
    ", (*p)(ax, by));
        }
    
        system("pause");
    }

    3.3.6 比较函数指针

      可以用相等和不等操作符来比较函数指针。

    3.3.7 转换函数指针

      我们可以将指向某个函数的指针转换为其他类型的指针,不过要谨慎使用,因为运行时系统不会验证函数指针所用的参数是否正确。也可以再转回来,得到的结果和原指针相同,但函数指针的长度不一定相等。

    int add(int num1, int num2) {
        return num1 + num2;
    }
    
    typedef int(*fptrToSing)(int);
    typedef int(*fptrToInts)(int, int);
    
    void main()
    {
        fptrToInts fptrFirst = add;
        fptrToSing fptrSecond = (fptrToSing)fptrFirst;
        fptrFirst = (fptrToInts)fptrSecond;
        printf("%d
    ", fptrFirst(5, 6));

    无法保证函数指针和数据指针转换后正常工作。

      void* 指针不一定能用在函数指针上。也就是说不应该像下面这样把函数指针赋给void * 指针:  void * pv = add;

     不过在交换函数指针时,通常会见到如下声明所示的“基本”函数指针类型。这里把fptrBase声明为指向不接受参数也不返回结果的函数的函数指针。

      typedef void (*fptrBase)( ); 

  • 相关阅读:
    两种代理模式(JDK和Cglib)实例
    打印1到最大的n位数
    二叉树的构建以及先中后序遍历
    二维数组的查找
    斐波那契递归和非递归俩种算法实例
    淘宝tairKV分布式
    OSI七层模型详解
    Spring事务配置的五种方式
    linux中cat、more、less、tail、head命令的区别
    跨域的理解与实现
  • 原文地址:https://www.cnblogs.com/Yang-Xin-Yi/p/13532364.html
Copyright © 2011-2022 走看看