精华内容
下载资源
问答
  • C 语言具备了面向过程程序设计的基本要求,在诸多领域无可替代,也适合于构建程序设计基础。所以它是一门程序设计的公共基础课程,相信也是...文档介绍指针高级应用的有关内容,《C语言程序设计 现代方法》配套讲义
  • 指针高级应用

    2014-07-07 17:25:06
    有关于C语言指针高级应用,帮助初学者更好地理解和应用指针
  • 指针高级应用.txt

    2021-03-15 11:21:01
    指针高级应用.txt
  • c语言高级指针理解及应用(上)

    千次阅读 2017-12-01 15:16:22
    6.指针应用的一些代码**本文先说明一些指针的概念及一些简单应用,更详细的将在(下)说明正文开始指针的基础知识什么是指针以下来自百度百科:指针,在计算机科学中,指针(Pointer)是编程语言中的一个对象,...

    大纲
    1.指针的基础知识;
    2.指针和指针类型;
    3.二级指针;
    4.指针表达式解析
    5.指针运算;
    6.指针的应用的一些代码**

    本文先说明一些指针的概念及一些简单应用,更详细的将在(下)说明


    正文开始


    指针的基础知识

    什么是指针

    以下来自百度百科:指针,在计算机科学中,指针(Pointer)是编程语言中的一个对象,利用地址,它的值直接指向(points to)存在电脑存储器中另一个地方的值。由于通过地址能找到所需的变量单元,可以说,地址指向该变量单元。因此,将地址形象化的称为“指针”。意思是通过它能找到以它为地址的内存单元。
    我们可以这样来理解:
    定义一个指针:int *p; p为一个变量,用来存放某块内存的地址,如下图
    这里写图片描述
    例:

    #includ<stdio.h>
    int main()
    {
    int a=10;//在内存中开辟了一块空间,存放a
    int *p=&a;//取出a得地址,将a的地址保存在p中,p即为指针变量
    //注意:p才是变量,不是*p;
    return 0;
    }

    总之:指针也是一个变量,是用来存放地址的。


    指针存在的意义

    抛砖引玉,在计算机内部,内存分成很多个小的单元,每个单元都对应一个独一无二的地址,这
    样就一个地址标示一块空间,这样来说计算机的硬件指令可以依赖地址来完成,程序员也能够 以类似于计算机底层的表达方式来表达自己的意愿,这可以使得使用了指针的程序可以更高效的工作。

    那么,问题来了,
    1.一个小的单元是多大?(一个字节)
    2.如何编址?

    经过仔细的计算和权衡我们发现一个字节给一个对应的地址是比较合适的。
    对于32位的机器,假设有32根地址线,那么假设每根地址线在寻址的是产生一个电信号
    (0或者1),那么32根地址线产生的地址就会是

    00000000 00000000 00000000 00000000
    00000000 00000000 00000000 00000001

    11111111 11111111 11111111 11111111
    这里就有2的32次方个地址。
    每个地址标示一个字节,那我们就可以给4G的空闲进行编址。

    同样,对于64位的机器,可以标示 2的64次方地址。

    经过以上理解,我们可以得出两个结论
    1. 在32位的机器上,地址是32个0或1组成序列,地址要用四个字节的空间来存储,一个指针变量的大小是4个字节;
    例:这里写图片描述
    2. 在64位的机器上,那么一个指针变量的大小要用8个字节来存储

    总结

    1. 指针是存放地址才出现的,地址是为了标示一块地址空间的。
    2. 指针让地址有地方存放,指针让内存的访问更加方便。
    3. 指针的大小在32位平台是4个字节,在64位平台是8个字节

    指针和指针类型

    来看代码:

    int num=10;
    int *p=&num;

    那如果写成这样

    int num=10;
    char *p=&num;

    这里写图片描述

    会报错,指针也是有类型的,指针的类型要与它所指向的内容相匹配,举几个例子:

    char *pc = NULL;
    int *pi = NULL;
    short *ps = NULL;
    long *pl = NULL;
    float *pf = NULL;
    double *pd = NULL;

    指针的类型是:type+*的方式;

    char* 类型的指针是为了存放 char 类型变量的地址。
    short* 类型的指针是为了存放 short 类型变量的地址。
    int* 类型的指针是为了存放 int 类型变量的地址。

    以此类推。

    为什么这样做:

    只要有类型的区分,就定出一定的规则,使得编码会更加严谨。
    确定了指针运算的规律。

    那么:指针类型如何确定指针运算的规律:
    1.指针+-整数;
    2.指针的解引用;

    指针+-整数
    示例程序

    #include <stdio.h>
    int main()
    {     
        int num = 10;     
        char *pc = (char *)&num;
        int *pi = &num;
        printf("%p\n", &num);    
        printf("%p\n", pc);   
        printf("%p\n", pc + 1);    
        printf("%p\n", pi);    
        printf("%p\n", pi + 1);     
        return  0;
    }

    这里写图片描述

    第一行:即为num的地址;
    第二行:因为pc指针指向num,所以pc的输出和第一行相同;
    第三行:在定义中,将&num强转为char*类型,pc加1即加一个char的大小,即为加1;
    第四行:因为pi指针指向num,所以pi的输出和第一行相同;
    第五行:pi+1;因为pi为int*类型,加1相当于加int的长度,即加4。


    可以这样说:指针的类型决定了指针向前或者向后走一步有多大(距离)。


    指针的解引用:

    我们都知道 int*p=&num,那么这个*号是什么呢;怎么理解呢;
    引用《c语言深度剖析》中的文章;

    4.1.2,“*”与防盗门的钥匙
    这里这个“*”号怎么理解呢?举个例子:当你回到家门口时,你想进屋第一件事就是
    拿出钥匙来开锁。那你想想防盗门的锁芯是不是很像这个“*”号?你要进屋必须要用钥匙,
    那你去读写一块内存是不是也要一把钥匙呢?这个“*”号就是不是就是我们最好的钥匙?
    使用指针的时候,没有它,你是不可能读写某块内存的。

    #include <stdio.h>
    int main()
    {
        int num = 0x11223344;
        char *pc = (char *)&num;
        char *pi = &num;
        *pc = 0x55;//重点在调试的过程中观察内存的变化。
        *pi = 0; //重点在调试的过程中观察内存的变化。
        return 0;
    }

    总结:
    指针的类型决定了,对指针解引用的时候有多大的权限(能操作几个字节)。
    比如:char*的指针解引用就只能访问一个字节,而int*的指针的解引用就能访问四个字
    节。

    二级指针

    指针也是一个变量,那存放指针这个变量的地址在哪呢,怎么找到呢,我们这里引入二级指针的概念

    例

    a的地址存放在p中,p的地址存放在pp中,pp就是二级指针。
    解引用一次*pp得到的是p,解引用两次**pp得到的是a,即访问的是a。

    int b=20;
    *pp=&b;//等价于p=&b;

    **pp先通过*pp找到p,然后对p进行解引用操作: *p,那找到的是a.

    **pp=30;//等价于把30赋给a。

    那接下来看一个代码

    char ch = 'a';
    char *cp = &ch;

    这是正确的,那什么类型的数据可以做左值,什么类型的数据可以做右值,判断的标准是什么呢?

    先下结论,左值是空间,右值是内容,记住这两条规则,就容易分得清了。

    指针运算

    指针的运算可以分为以下三种:

    • 指针+-整数
    • 指针-指针
    • 指针的关系运算

      define N_VALUES 5

      float values[N_VALUES];
      float *vp;
      //指针+-整数;指针的关系运算
      for (vp = &values[0]; vp < &values[N_VALUES];)
      {
      *vp++ = 0;
      }

      指针-指针

      int my_strlen(char *s)
      {
      char *p = s;
      while(*p != ‘\0’ )
      p++;
      return p-s;
      }

    关系运算

    for(vp = &values[N_VALUES]; vp > &values[0];)
    {
    *–vp = 0;
    }

    指针的一些关系运算

    • 模拟实现strlen
      方式一

    int my_strlen(const char * str)
    {
    int count = 0;
    while(*str)
    {
    count++;
    str++;
    }
    return count;
    }

    方式二

    int my_strlen(const char * str)
    {
    if(*str == ‘\0’)
    return 0;
    else
    return 1+my_strlen(str+1);
    }

    方式三

    int my_strlen(char *s)
    {
    char *p = s;
    while(*p != ‘\0’ )
    p++;
    return p-s;
    }

    -模拟实现strcpy

    char *my_strcpy(char *dest, const char*src)
    {
    char *ret = dest;
    assert(dest != NULL);
    assert(src != NULL);
    while((*dest++ = *src++))
    {
    ;
    }
    return ret;
    }
    • 模拟实现strcat
    char *my_strcat(char *dest, const char*src)
    {
    char *ret = dest;
    assert(dest != NULL);
    assert(src != NULL);
    while(*dest)
    {
    dest++;
    }
    while((*dest++ = *src++))
    {
    ;
    }
    return ret;
    }
    • 模拟实现strstr
    char *my_strstr(const char* str1, const char* str2 )
    {
    assert(str1);
    assert(str2);
    char *cp = (char*)str1;
    char *substr = (char *)str2;
    char *s1 = NULL;
    if(*str2 == '\0')
    return NULL;
    while(*cp)
    {
    s1 = cp;
    substr = str2;
    while(*s1 && *substr && (*s1 == *substr))
    {
    s1++;
    substr++;
    }
    if(*substr == '\0')
    return cp;
    cp++;
    }
    }
    • 模拟实现strcmp
    int my_strcmp (const char * src, const char * dst)
    {
    int ret = 0 ;
    while( ! (ret = *(unsigned char *)src - *(unsigned char
    *)dst) && *dst)
    ++src, ++dst;
    if ( ret < 0 )
    ret = -1 ;
    else if ( ret > 0 )
    ret = 1 ;
    return( ret );
    }
    • 模拟实现memcpy
    void * memcpy ( void * dst, const void * src, size_t count)
    {
    void * ret = dst;
    /*
     * copy from lower addresses to higher addresses
    */
    while (count--) {
    *(char *)dst = *(char *)src;
    dst = (char *)dst + 1;
    src = (char *)src + 1;
    }
    return(ret);
    }
    • 模拟实现memmove
    void * memmove ( void * dst, const void * src, size_t count)
    {
    void * ret = dst;
    if (dst <= src || (char *)dst >= ((char *)src + count)) {
    /*
    * Non-Overlapping Buffers
    * copy from lower addresses to higher addresses
    */
    while (count--) {
    *(char *)dst = *(char *)src;
    dst = (char *)dst + 1;
    src = (char *)src + 1;
    }
    }
    else {
    /*
    * Overlapping Buffers
    * copy from higher addresses to lower addresses
    */
    dst = (char *)dst + count - 1;
    src = (char *)src + count - 1;
    while (count--) {
    *(char *)dst = *(char *)src;
    dst = (char *)dst - 1;
    src = (char *)src - 1;
    }
    }
    return(ret);
    }

    参考资料 《c语言深度剖析》

    展开全文
  • C语言-指针应用

    千次阅读 2019-11-12 15:06:57
    基本概念 一般把数据都放在存储器中,存储器中一个字节称为一个内存单元 不同数据类型所占用的内存单元数不等,如整型量占2个单元,字符量占1个单元,...在C语言中,允许用一个变量来存放指针,这种变量称为指针变量. 因...

    基本概念

    一般把数据都放在存储器中,存储器中一个字节称为一个内存单元 
    不同数据类型所占用的内存单元数不等,如整型量占2个单元,字符量占1个单元,每个内存单元都有编号.这个编号也叫地址.就是我们所说的指针.内存单元的指针和内存单元的内容是两个不同的概念.对于一个内存单元来说,单元的地址即为指针,其中存放的数据才是该单元的内容.在C语言中,允许用一个变量来存放指针,这种变量称为指针变量. 
    因此,一个指针变量的值就是某个内存单元的地址或称为某内存单元的指针. 
    “指针”是指地址,是常量,”指针变量”指取值为地址的变量.定义指针的目的是通过指针去访问内存单元.

    变量的指针和指向变量的指针变量 
    首先,变量的指针就是变量的地址. 
    存放变量地址的变量是指针变量. 
    在C语言中允许用一个变量来存放指针. 
    这种变量称为指针变量.因此 
    一个指针变量的值,就是某个变量的地址.或者称为某个变量的指针. 

    指针变量和它指向的变量的关系:

    C语言中用“*”表示指向,例如用i_pointer表示一个指针变量,则*i_pointer表示i_pointer所指向的变量。

     

    指针数组和数组指针

    数组类型

    C语言中数组有自己特定的类型数组的类型由元素类型和数组大小共同决定,比如int array[5]的类型为int[5]。定义数组类型

    C语言中通过typedef为数组重命名typedef type(name)[size]数组类型typedef int(AINT5)[5]typedef float(AFLOAT10)[10]数组定义AINT5 iArrayAFLOAT10 fArray数组指针

    数组指针用于指向一个数组数组名是数组首元素的起始地址,但不是数组的起始地址,通过取地址符&作用域数组名可以得到数组的起始地址。可以通过数组类型定义数组指针如:ArrayType *pointer,也可以直接定义type (*pointer)[n]

    指针数组

    指针数组是一个普通的数组,数组中的每个元素都是指针指针数组的定义为:type *pArray[n]指向指针的指针

    指针变量在内存中占用一定的空间可以定义指针来保存指针变量的地址值为什么需要指向指针的指针?指针本质上也是变量,对于指针同样也存在传值调用和传址调用。二维数组和二级指针

    二维数组在内存中以一维的方式排布二维数组中的第一维是一维数组,第二维才是具体的值二维数组的数组名可以看做常量指针

            

    数组名

    一维数组的数组名代表数组首元素的地址int a[5]->a的类型是int *二维数组的数组名同样代表数组首元素的地址int m[2][5]->m的类型是int(*)[5]所以得出以下结论:

    二维数组的数组名可以看做指向数组的常量指针二维数组可以看做一维数组,其中每个元素都是同类的一维数组数组参数和指针参数分析

    C语言中只会以值拷贝的方式传递参数当向函数传递数组时,会将数组名看做常量指针传递数组首元素地址。注意:

    C语言中无法向一个函数传递任意的多维数组为了提供正确的指针运算,必须提供除第一维之外的所有维度的长度信息限制:一维数组参数--必须提供一个标识数组结束位置的长度信息。二维数组参数--不能直接传递给函数三维或者更多维度的数组--无法使用函数类型

    C语言中的函数有自己特定的类型。函数的类型由返回值,参数和参数个数共同决定。

    在C语言中通过typedef为函数重命名如:

    typedef type name(paramter list);


     

     

     


    函数指针

    函数指针用于指向一个函数函数名是执行函数体的入口地址可以通过函数类型定义函数指针:functype *pointer也可以直接定义:type (*pointer)(parameter list)回调函数。

    回调函数是利用函数指针实现的一种调用机制,起调用机制原理如下:

    调用者不知道具体事件发生时候需要调用的具体函数,被调用函数不知道何时被调用,只知道被调用会需要完成的任务。当具体事件发生时,调用者通过函数指针调用具体函数,回调机制将调用者和被调函数分开,两者互补依赖。

    如果在程序中定义了一个函数,那么在编译时系统就会为这个函数代码分配一段存储空间,这段存储空间的首地址称为这个函数的地址。而且函数名表示的就是这个地址。既然是地址我们就可以定义一个指针变量来存放,这个指针变量就叫作函数指针变量,简称函数指针。

    那么这个指针变量怎么定义呢?虽然同样是指向一个地址,但指向函数的指针变量同我们之前讲的指向变量的指针变量的定义方式是不同的。例如:

    int(*p)(int, int);

    这个语句就定义了一个指向函数的指针变量 p。首先它是一个指针变量,所以要有一个“*”,即(*p);其次前面的 int 表示这个指针变量可以指向返回值类型为 int 型的函数;后面括号中的两个 int 表示这个指针变量可以指向有两个参数且都是 int 型的函数。所以合起来这个语句的意思就是:定义了一个指针变量 p,该指针变量可以指向返回值类型为 int 型,且有两个整型参数的函数。p 的类型为 int(*)(int,int)。

    所以函数指针的定义方式为:

    函数返回值类型 (* 指针变量名) (函数参数列表);

    “函数返回值类型”表示该指针变量可以指向具有什么返回值类型的函数;“函数参数列表”表示该指针变量可以指向具有什么参数列表的函数。这个参数列表中只需要写函数的参数类型即可。

    我们看到,函数指针的定义就是将“函数声明”中的“函数名”改成“(*指针变量名)”。但是这里需要注意的是:“(*指针变量名)”两端的括号不能省略,括号改变了运算符的优先级。如果省略了括号,就不是定义函数指针而是一个函数声明了,即声明了一个返回值类型为指针型的函数。

    那么怎么判断一个指针变量是指向变量的指针变量还是指向函数的指针变量呢?首先看变量名前面有没有“*”,如果有“*”说明是指针变量;其次看变量名的后面有没有带有形参类型的圆括号,如果有就是指向函数的指针变量,即函数指针,如果没有就是指向变量的指针变量。

    最后需要注意的是,指向函数的指针变量没有 ++ 和 -- 运算。

    使用函数指针调用函数

    int Func(int x);   /*声明一个函数*/
    int (*p) (int x);  /*定义一个函数指针*/
    p = Func;          /*将Func函数的首地址赋给指针变量p*/

    注意:赋值时函数 Func 不带括号,也不带参数。由于函数名 Func 代表函数的首地址,因此经过赋值以后,指针变量 p 就指向函数 Func() 代码的首地址了。

    举例:

    # include <stdio.h>
    int Max(int, int);  //函数声明
    int main(void)
    {
        int(*p)(int, int);  //定义一个函数指针
        int a, b, c;
        p = Max;  //把函数Max赋给指针变量p, 使p指向Max函数
        printf("please enter a and b:");
        scanf("%d%d", &a, &b);
        c = (*p)(a, b);  //通过函数指针调用Max函数
        printf("a = %d\nb = %d\nmax = %d\n", a, b, c);
        return 0;
    }
    int Max(int x, int y)  //Max函数实现
    {
        int z;
        if (x > y)
        {
            z = x;
        }
        else
        {
            z = y;
        }
        return z;
    }

    运行结果:

    please enter a and b:10 8
    a = 10
    b = 8
    max = 10

    指针定义举例和详解


    int p; //这是一个普通的整型变量  。

    int *p; //首先从p 处开始,先与*结合,所以说明p 是一个指针,然后再与int 结合,说明指针所指向的内容的类型为int 型。所以p是一个返回整型数据的指针。

     

    int p[3]; //首先从p 处开始,先与[]结合,说明p 是一个数组,然后与int 结合,说明数组里的元素是整型的,所以p 是一个由整型数据组成的数组 。

    int *p[3]; //首先从p 处开始,先与[]结合,因为其优先级比*高,所以p是一个数组,然后再与*结合,说明数组里的元素是指针类型,然后再与int 结合,说明指针所指向的内容的类型是整型的,所以p 是一个由返回整型数据的指针所组成的数组  。

    int (*p)[3]; //首先从p 处开始,先与*结合,说明p是一个指针然后再与[]结合(与"()"这步可以忽略,只是为了改变优先级),说明指针所指向的内容是一个数组,然后再与int 结合,说明数组里的元素是整型的,所以p是一个指向由整型数据组成的数组的指针 。 

    int **p; //首先从p开始,先与*结合,说是p 是一个指针,然后再与*结合,说明指针所指向的元素是指针,然后再与int 结合,说明该指针所指向的元素是整型数据,由于二级指针以及更高级的指针极少用在复杂的类型中。  

    int p(int); //从p 处起,先与()结合,说明p 是一个函数,然后进入()里分析,说明该函数有一个整型变量的参数,然后再与外面的int 结合,说明函数的返回值是一个整型数据 。

    Int (*p)(int); //从p处开始,先与指针结合,说明p 是一个指针,然后与()结合,说明指针指向的是一个函数,然后再与()里的int 结合,说明函数有一个int 型的参数,再与最外层的int 结合,说明函数的返回类型是整型,所以p 是一个指向有一个整型参数且返回类型为整型的函数的指针 。 

    int *(*p(int))[3]; //从p开始,先与()结合,说明p 是一个函数,然后进入()里面,与int 结合,说明函数有一个整型变量参数,然后再与外面的*结合,说明函数返回的是一个指针,然后到最外面一层,先与[]结合,说明返回的指针指向的是一个数组,然后再与*结合,说明数组里的元素是指针,然后再与int 结合,说明指针指向的内容是整型数据。所以p是一个参数为一个整数类型且返回一个指向由整型指针变量组成的数组的指针变量的函数。

    运算符&和*

    这里&是取地址运算符,*是间接运算符。&a 的运算结果是一个指针,指针的类型是a 的类型加个*,指针所指向的类型是a 的类型,指针所指向的地址嘛,那就是a 的地址。*p 的运算结果就五花八门了。总之*p 的结果是p 所指向的东西,这个东西有这些特点:它的类型是p 指向的类型,它所占用的地址是p所指向的地址。


    int a=12; int b; int *p; int **ptr;  

    p=&a; //&a 的结果是一个指针,类型是int*,指向的类型是  

    //int,指向的地址是a 的地址。  

    *p=24; //*p 的结果,在这里它的类型是int,它所占用的地址是  

    //p 所指向的地址,显然,*p 就是变量a。  

    ptr=&p; //&p 的结果是个指针,该指针的类型是p 的类型加个*,  

    //在这里是int **。该指针所指向的类型是p 的类型,这  

    //里是int*。该指针所指向的地址就是指针p 自己的地址。  

    *ptr=&b; //*ptr 是个指针,&b 的结果也是个指针,且这两个指针  

    //的类型和所指向的类型是一样的,所以用&b 来给*ptr 赋  

    //值就是毫无问题的了。  

    **ptr=34; //*ptr 的结果是ptr 所指向的东西,在这里是一个指针,  

    //对这个指针再做一次*运算,结果是一个int 类型的变量。 
     

     


     

    展开全文
  • C语言强化指针高级应用学习心得

    千次阅读 2016-07-27 11:37:49
    一.关于指针传递的思考 例1-1: #include char* test() {  char str[] = "this is a test";  return str; }   int main(void) {  char* pStr = NULL;  pStr = test();  puts(pStr);

    一.关于指针传递的思考

    1-1

    #include

    char* test()

    {

          char str[] = "this is a test";

          return str;

    }

     

    int main(void)

    {

          char* pStr = NULL;

          pStr = test();

          puts(pStr);

          return 0;

    }

    运行之后,会出现什么情况呢?

    答:很可能是一些乱码。

    因为test返回的的是指向栈,原先开辟出来的内存生存周存在函数执行完之后,就被系统回收。故输出的值不确定。

    1-2

    #include

    #include

    #include

     

    void test(char* str)

    {

          strcpy(str,"this is a test");

    }

     

    int main(void)

    {

          char str[100] = {0};

          test(str);

          printf("str=%s\n",str);

          return 0;

    }

    运行结果是什么呢?

    答:str=this is a test

    传给test函数的参数是指针值,本质上可以说指针传递也是值传递,即传给test函数的是指向str[100]数组的首地址。在test内对指定地址内容进行操作。

    1-3test若改成:

    void test2(char* str)

    {

          str = malloc(sizeof("this is a test"));

          strcpy(str,"this is a test");

    }

    main函数改成:

    int main(void)

    {

          char* str2=NULL;

          test2(str2);

           printf("str2=%s\n",str2);

          free(str2);

     

          return 0;

    }

    输出结果又是什么呢?

    答:输出NULL

    因为str = malloc(sizeof(“this is a test”));语句使str里的地址发生改变,因此后面的strcpy()操作不影响数组str2

    那如何解决这个问题呢?可以采用指针的指针,即将str2的地址传给test3,如下:

    void test3(char** str)

    {

          *str = malloc(sizeof("this is a test"));

          strcpy(*str,"this is a test");

    }

    来一个比较有意思的例子

    1-4char * test2(void)

    {

          char *str = "this is my test";

          return str;

    }

    int main(void)

    {

          char* str2=NULL;

          str2 = test2();

          printf("str=%s\n",str2);

          return 0;

    }

    运行会出现什么情况?

    答:在debug下,会出现“degub assertion failed”,同时也会输出:str=this is my test

    因为”this is my test”是字符串常量,保存在内存区域的文字常量区域,它的生存周期是程序运行的整个过程。利用这一点,将”this is my test”字符串的首地址返回保存起来,之后用这个地址来输出字符串是可行的。但是,函数结束完之后,”this is my test”这个字符串的生存期也就结束了,以后再用它就会有问题。即出现野指针问题。

    不改程序,照样也想运行程序,可以嘛?在vc6.0下,将debug改成release模式(build->configuration,删除debug),就不会再出现degub assertion failed的提示。

    二.函数指针

    如何定义一个指向函数的指针变量?

    一般定义形式为:数据类型  (* 指针变量名)  (函数参数列表)

    这里“数据类型”指函数返回值的类型。

    实例:定义一个函数参数是int型,返回类型为空的函数指针变量

    void  (*f)(int);

    可以说变量f的类型为void (* )(int),既然如此,那不是可以用typedef给这种类型定义一个别名。

    typedef  void  (*F)(int);

    此时就可以用F定义变量了。

    2-1

    #include

    #include

    #include

     

    void test(int n)

    {

          printf("n=%d\n",n);

    }

     

    typedef void(*F)(int);

     

    int main(void)

    {

          F f = test;             //定义函数指针f,并初始化指向test函数

          F *p = NULL;

     

          f(100);

     

          printf("%p %p\n", test,&test);

          return 0;

    }

     

    三.关于const关键字

    const修饰的变量,可以被修改嘛?

    答:可以

    如:

    int* p2;

    const int n2 = 200;

    p2 = &n2;

    *p2 = 300;

    printf("n2=%d\n",n2);

    编译的时候有个警告:warning C4090: '=' : different 'const' qualifiers

    但执行完后,发现n2值变为300了。

    警告提示是类类型不匹配,改成const int * p2之后,就不会出现警告,因为此时类类型都为const int,但会出现错误:error C2166: l-value specifies const object.

    说时const类型不能作为左值。

    关于const比较难理解的是修饰指针变量。如:

    const int a;

    int const a;

    const int *a;

    int * const a;

    const int * const a;

    前两个的作用是一样,a是一个常整型数。第三个意味着a是一个指向常整型数的指针(也就是,整型数是不可修改的,但指针可以)。第四个意思a是一个指向整型数的常指针(也就是说,指针指向的整型数是可以修改的,但指针是不可修改的)。最后一个意味着a是一个指向常整型数的常指针(也就是说,指针指向的整型数是不可修改的,同时指针也是不可修改的)。如果应试者能正确回答这些问题,那么他就给我留下了一个好印象。顺带提一句,也许你可能会问,即使不用关键字 const,也还是能很容易写出功能正确的程序,那么我为什么还要如此看重关键字const呢?我也如下的几下理由: 
    1) 关键字const的作用是为给读你代码的人传达非常有用的信息,实际上,声明一个参数为常量是为了告诉了用户这个参数的应用目的。如果你曾花很多时间清理其它人留下的垃圾,你就会很快学会感谢这点多余的信息。(当然,懂得用const的程序员很少会留下的垃圾让别人来清理的。) 
    2) 通过给优化器一些附加的信息,使用关键字const也许能产生更紧凑的代码。 
    3) 合理地使用关键字const可以使编译器很自然地保护那些不希望被改变的参数,防止其被无意的代码修改。简而言之,这样可以减少bug的出现。

    四. 回调函数

    什么是回调

    软件模块之间总是存在着一定的接口,从调用方式上,可以把他们分为三类:同步调用、回调和异步调用。同步调用是一种阻塞式调用,调用方要等待对方执行完毕才返回,它是一种单向调用;回调是一种双向调用模式,也就是说,被调用方在接口被调用时也会调用对方的接口;异步调用是一种类似消息或事件的机制,不过它的调用方向刚好相反,接口的服务在收到某种讯息或发生某种事件时,会主动通知客户方(即调用客户方的接口)。回调和异步调用的关系非常紧密,通常我们使用回调来实现异步消息的注册,通过异步调用来实现消息的通知。同步调用是三者当中最简单的,而回调又常常是异步调用的基础,因此,下面我们着重讨论回调机制在不同软件架构中的实现。


    对于不同类型的语言(如结构化语言和对象语言)、平台(Win32JDK)或构架(CORBADCOMWebService),客户和服务的交互除了同步方式以外,都需要具备一定的异步通知机制,让服务方(或接口提供方)在某些情况下能够主动通知客户,而回调是实现异步的一个最简捷的途径。

    对于一般的结构化语言,可以通过回调函数来实现回调。回调函数也是一个函数或过程,不过它是一个由调用方自己实现,供被调用方使用的特殊函数。

    在面向对象的语言中,回调则是通过接口或抽象类来实现的,我们把实现这种接口的类成为回调类,回调类的对象成为回调对象。对于象C++Object Pascal这些兼容了过程特性的对象语言,不仅提供了回调对象、回调方法等特性,也能兼容过程语言的回调函数机制。

    Windows平台的消息机制也可以看作是回调的一种应用,我们通过系统提供的接口注册消息处理函数(即回调函数),从而实现接收、处理消息的目的。由于Windows平台的API是用C语言来构建的,我们可以认为它也是回调函数的一个特例。

    对于分布式组件代理体系CORBA,异步处理有多种方式,如回调、事件服务、通知服务等。事件服务和通知服务是CORBA用来处理异步消息的标准服务,他们主要负责消息的处理、派发、维护等工作。对一些简单的异步处理过程,我们可以通过回调机制来实现。

    下面我们集中比较具有代表性的语言(CObject Pascal)和架构(CORBA)来分析回调的实现方式、具体作用等。

    过程语言中的回调(C

    2.1 函数指针

    回调在C语言中是通过函数指针来实现的,通过将回调函数的地址传给被调函数从而实现回调。因此,要实现回调,必须首先定义函数指针,请看下面的例子:

     

    void Func(char *s)// 函数原型

    void (*pFunc) (char *);//函数指针

     

    可以看出,函数的定义和函数指针的定义非常类似。

    一般的化,为了简化函数指针类型的变量定义,提高程序的可读性,我们需要把函数指针类型自定义一下。

     

    typedef void(*pcb)(char *);

     

    回调函数可以象普通函数一样被程序调用,但是只有它被当作参数传递给被调函数时才能称作回调函数。

    被调函数的例子:

     

    void GetCallBack(pcb callback)

    {

    /*do something*/

    }

    用户在调用上面的函数时,需要自己实现一个pcb类型的回调函数:

    void fCallback(char *s)

    {

    /* do something */

    }

    然后,就可以直接把fCallback当作一个变量传递给GetCallBack,

    GetCallBackfCallback;

     

    如果赋了不同的值给该参数,那么调用者将调用不同地址的函数。赋值可以发生在运行时,这样使你能实现动态绑定。

    2.2 参数传递规则

    到目前为止,我们只讨论了函数指针及回调而没有去注意ANSI C/C++的编译器规范。许多编译器有几种调用规范。如在Visual C++中,可以在函数类型前加_cdecl_stdcall或者_pascal来表示其调用规范(默认为_cdecl)。C++ Builder也支持_fastcall调用规范。调用规范影响编译器产生的给定函数名,参数传递的顺序(从右到左或从左到右),堆栈清理责任(调用者或者被调用者)以及参数传递机制(堆栈,CPU寄存器等)。

    将调用规范看成是函数类型的一部分是很重要的;不能用不兼容的调用规范将地址赋值给函数指针。例如:

     

    // 被调用函数是以int为参数,以int为返回值

    __stdcall int callee(int);

     

    // 调用函数以函数指针为参数

    void caller( __cdecl int(*ptr)(int));

     

    // p中企图存储被调用函数地址的非法操作

    __cdecl int(*p)(int) = callee; // 出错

     

    指针pcallee()的类型不兼容,因为它们有不同的调用规范。因此不能将被调用者的地址赋值给指针p,尽管两者有相同的返回值和参数列

    2.3 应用举例

    C语言的标准库函数中很多地方就采用了回调函数来让用户定制处理过程。如常用的快速排序函数、二分搜索函数等。

    快速排序函数原型:

     

    void qsort(void *base, size_t nelem, size_t width, int (_USERENTRY *fcmp)(const void *, const void *));

    二分搜索函数原型:

    void *bsearch(const void *key, const void *base, size_t nelem,

                             size_t width, int (_USERENTRY *fcmp)(const void *, const void *));

     

    其中fcmp就是一个回调函数的变量。

    下面给出一个具体的例子:

     

    #include

    #include

     

    int sort_function( const void *a, const void *b);

    int list[5] = { 54, 21, 11, 67, 22 };

     

    int main(void)

    {

       int  x;

     

       qsort((void *)list, 5, sizeof(list[0]), sort_function);

       for (x = 0; x < 5; x++)

          printf("%i\n", list[x]);

       return 0;

    }

     

    int sort_function( const void *a, const void *b)

    {

       return *(int*)a-*(int*)b;

    }

     

    2.4 面向对象语言中的回调(Delphi

    DephiC++一样,为了保持与过程语言Pascal的兼容性,它在引入面向对象机制的同时,保留了以前的结构化特性。因此,对回调的实现,也有两种截然不同的模式,一种是结构化的函数回调模式,一种是面向对象的接口模式。

    2.4.1 回调函数

    回调函数类型定义:

     

    type

       TCalcFunc=function (a:integer;b:integer):integer;

     

    按照回调函数的格式自定义函数的实现,如

     

    function Add(a:integer;b:integer):integer

    begin

      result:=a+b;

    end;

    function Sub(a:integer;b:integer):integer

    begin

      result:=a-b;

    end;

     

    回调的使用

     

    function Calc(calc:TcalcFunc;a:integer;b:integer):integer

     

    下面,我们就可以在我们的程序里按照需要调用这两个函数了

     

    c:=calc(add,a,b);//c=a+b

    c:=calc(sub,a,b);//c=a-b

     

    2.4.2 回调对象

    什么叫回调对象呢,它具体用在哪些场合?首先,让我们把它与回调函数对比一下,回调函数是一个定义了函数的原型,函数体则交由第三方来实现的一种动态应用模式。要实现一个回调函数,我们必须明确知道几点:该函数需要那些参数,返回什么类型的值。同样,一个回调对象也是一个定义了对象接口,但是没有具体实现的抽象类(即接口)。要实现一个回调对象,我们必须知道:它需要实现哪些方法,每个方法中有哪些参数,该方法需要放回什么值。

    因此,在回调对象这种应用模式中,我们会用到接口。接口可以理解成一个定义好了但是没有实现的类,它只能通过继承的方式被别的类实现。Delphi中的接口和COM接口类似,所有的接口都继承与IInterface(等同于IUnknow),并且要实现三个基本的方法QueryInterface, _AddRef, _Release

    ·         定义一个接口

     

    type IShape=interface(IInterface)

          procedure Draw;

    end

     

    ·         实现回调类

     

    type TRect=class(TObject,IShape)

          protected

          function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall;

          function _AddRef: Integer; stdcall;

    function _Release: Integer; stdcall;

        public

            procedure Draw;

    end;

     

    type TRound=class(TObject,IShape)

          protected

          function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall;

          function _AddRef: Integer; stdcall;

    function _Release: Integer; stdcall;

        public

            procedure Draw;

    end;

     

    ·         使用回调对象

     

    procedure MyDraw(shape:IShape);

    var

    shape:IShape;

    begin

    shape.Draw;

    end;

    如果传入的对象为TRect,那么画矩形;如果为TRound,那么就为圆形。用户也可以按照自己的意图来实现IShape接口,画出自己的图形:

     

    MyDraw(Trect.Create);

    MyDraw(Tround.Create);

    2.4.3 回调方法

    回调方法(Callback Method)可以看作是回调对象的一部分,Delphiwindows消息的封装就采用了回调方法这个概念。在有些场合,我们不需要按照给定的要求实现整个对象,而只要实现其中的一个方法就可以了,这是我们就会用到回调方法。

    回调方法的定义如下:

     

    TNotifyEvent = procedure(Sender: TObject) of object;

    TMyEvent=procedure(Sender:Tobject;EventId:Integer) of object;

    TNotifyEvent Delphi中最常用的回调方法,窗体、控件的很多事件,如单击事件、关闭事件等都是采用了TnotifyEvent。回调方法的变量一般通过事件属性的方式来定义,如TCustomForm的创建事件的定义:

     

    property OnCreate: TNotifyEvent read FOnCreate write FOnCreate stored IsForm;

    我们通过给事件属性变量赋值就可以定制事件处理器。

    用户定义对象(包含回调方法的对象):

     

    type TCallback=Class

        procedure ClickFunc(sender:TObject);

    end;

    procedure Tcallback.ClickFunc(sender:TObject);

    begin

      showmessage('the caller is clicked!');

    end;

    窗体对象:

     

    type TCustomFrm=class(TForm)

      public

          procedure RegisterClickFunc(cb:procedure(sender:Tobject) of object);

    end;

     

    procedure TcustomFrm..RegisterClickFunc(cb:TNotifyEvent);

    begin

      self.OnClick=cb;

    end;

    使用方法:

     

    var

      frm:TcustomFrm;

    begin

      frm:=TcustomFrm.Create(Application);

      frm.RegisterClickFunc(Tcallback.Create().ClickFunc);

    end;

    回调在分布式计算中的应用(CORBA

    3.1 回调接口模型

    CORBA的消息传递机制有很多种,比如回调接口、事件服务和通知服务等。回调接口的原理很简单,CORBA客户和服务器都具有双重角色,即充当服务器也是客户客户。

    回调接口的反向调用与正向调用往往是同时进行的,如果服务端多次调用该回调接口,那么这个回调接口就变成异步接口了。因此,回调接口在CORBA中常常充当事件注册的用途,客户端调用该注册函数时,客户函数就是回调函数,在此后的调用中,由于不需要客户端的主动参与,该函数就是实现了一种异步机制。

    CORBA规范我们知道,一个CORBA接口在服务端和客户端有不同的表现形式,在客户端一般使用桩(Stub)文件,服务端则用到框架(Skeleton)文件,接口的规格采用IDL来定义。而回调函数的引入,使得服务端和客户端都需要实现一定的桩和框架。下面是回调接口的实现模型:

    3.1.1 范例 

    下面给出了一个使用回调的接口文件,服务端需要实现Server接口的框架,客户端需要实现CallBack的框架:

     

    module cb

    {

          interface CallBack;

          interface Server;

     

    interface CallBack

    {

           void OnEvent(in long Source,in long msg);

    };

           interface Server

    {

           long RegisterCB(in CallBack cb);

                void UnRegisterCB(in long hCb);

    };

    };

     

    客户端首先通过同步方式调用服务端的接口RegistCB,用来注册回调接口CallBack。服务端收到该请求以后,就会保留该接口引用,如果发生某种事件需要向客户端通知的时候就通过该引用调用客户方的OnEvent函数,以便对方及时处理。

    展开全文
  • 《C语言高级专题第4部分-4.4.C语言复杂表达式与指针高级应用》 第一部分、章节目录 4.4.1.指针数组与数组指针 4.4.2.函数指针与typedef 4.4.3.函数指针实战1 4.4.4.函数指针实战2 4.4.5.再论typedef 4.4.6.二重指针...

    《C语言高级专题第4部分-4.4.C语言复杂表达式与指针高级应用》

    第一部分、章节目录
    4.4.1.指针数组与数组指针
    4.4.2.函数指针与typedef
    4.4.3.函数指针实战1
    4.4.4.函数指针实战2
    4.4.5.再论typedef
    4.4.6.二重指针
    4.4.7.二维数组
    4.4.8.二维数组的运算和指针

    第二部分、章节介绍
    4.4.1.指针数组与数组指针
        本节讲述2个很容易搞混淆的C语言复杂符号:指针数组与数组指针。并且希望通过这两个“入门级”的复杂符号引入C语言复杂表达式的解析方法。
    4.4.2.函数指针与typedef
        本节讲述函数指针这个更为复杂些的C语言复杂表达式,使用上节介绍过的方法来解析函数指针,让大家再次复习这种非常有效的分析方法。并且引入typedef关键。
    4.4.3.函数指针实战1
        本节进行函数指针的实战编程练习1,通过编写一个计算器程序,让大家体会函数指针的常规用法,并且指出其中隐含的面向对象的思维方式。
    4.4.4.函数指针实战2
        本节继续进行函数指针的实战编程练习2,是一个比较复杂的分层结构下用函数指针实现架构的案例,linux内核驱动中广泛使用了这种技巧。
    4.4.5.再论typedef
        本节详细系统的讲述typedef的惯用法和应用目的,除了整理之前提到的内容外,重点讲了typedef与结构体、typedef与const这两个新知识点。
    4.4.6.二重指针
        本节讲述二重指针,通过代码实例让大家明白二重指针的本质,从而更加明白指针变量的本质。通过示例讲了二重指针的几种常见应用方法,二重指针和指针数组的关系等。

    4.4.7.二维数组
        本节讲解二维数组,首先从内存角度分析二维数组,然后从下标访问和指针访问的角度分别访问数组元素,试图让大家理解二维数组的本质。
    4.4.8.二维数组的运算和指针
        本节将二维数组和指针结合起来,通过一些题目运算让大家了解二维数组和指针之间的一些运算规律,这也是在现实编程中困扰大家最多的地方。

    第三部分、随堂记录
    4.4.1.指针数组与数组指针
    4.4.1.1、字面意思来理解指针数组与数组指针
    (1)指针数组的实质是一个数组,这个数组中存储的内容全部是指针变量。
    (2)数组指针的实质是一个指针,这个指针指向的是一个数组。
    4.4.1.2、分析指针数组与数组指针的表达式
    (1)int *p[5]; int (*p)[5];    int *(p[5]);
    (2)一般规律:int *p;(p是一个指针); int p[5];(p是一个数组)
    总结:我们在定义一个符号时,关键在于:首先要搞清楚你定义的符号是谁(第一步:找核心);其次再来看谁跟核心最近、谁跟核心结合(第二步:找结合);以后继续向外扩展(第三步:继续向外结合直到整个符号完)。
    (3)如何核心和*结合,表示核心是指针;如果核心和[]结合,表示核心是数组;如果核心和()结合,表示核心是函数。
    (4)用一般规律来分析3个符号:
    第一个:int *p[5]; 核心是p,p是一个数组,数组有5个元素大,数组中的元素都是指针,指针指向的元素类型是int类型的;整个符号是一个指针数组。
    第二个,int (*p)[5];
    核心是p,p是一个指针,指针指向一个数组,数组有5个元素,数组中存的元素是int类型; 总结一下整个符号的意义就是数组指针。
    第三个,int *(p[5]); 
    解析方法和结论和第一个相同,()在这里是可有可无的。

    注意:符号的优先级到底有什么用?其实是决定当2个符号一起作用的时候决定哪个符号先运算,哪个符号后运算。
    遇到优先级问题怎么办?第一,查优先级表;第二,自己记住(全部记住都成神了,人只要记住[] . ->这几个优先级比较好即可)。

    4.4.1.3、总结1:优先级和结合性是分析符号意义的关键
    (1)在分析C语言问题时不要胡乱去猜测规律,不要总觉得c语言无从捉摸,从已知的规律出发按照既定的规则去做即可。
    4.4.1.4、总结2:学会逐层剥离的分析方法
    (1)找到核心后从内到外逐层的进行结合,结合之后可以把已经结合的部分当成一个整体,再去和整体外面的继续进行结合。
    4.4.1.5、总结3:基础理论和原则是关键,没有无缘无故的规则

    4.4.2.函数指针与typedef
    4.4.2.1、函数指针的实质(还是指针变量)
    (1)函数指针的实质还是指针,还是指针变量。本身占4字节(在32位系统中,所有的指针都是4字节)
    (2)函数指针、数组指针、普通指针之间并没有本质区别,区别在于指针指向的东西是个什么玩意。
    (3)函数的实质是一段代码,这一段代码在内存中是连续分布的(一个函数的大括号括起来的所有语句将来编译出来生成的可执行程序是连续的),所以对于函数来说很关键的就是函数中的第一句代码的地址,这个地址就是所谓的函数地址,在C语言中用函数名这个符号来表示。
    (4)结合函数的实质,函数指针其实就是一个普通变量,这个普通变量的类型是函数指针变量类型,它的值就是某个函数的地址(也就是它的函数名这个符号在编译器中对应的值)

    4.4.2.2、函数指针的书写和分析方法
    (1)C语言本身是强类型语言(每一个变量都有自己的变量类型),编译器可以帮我们做严格的类型检查。
    (2)所有的指针变量类型其实本质都是一样的,但是为什么在C语言中要去区分它们,写法不一样呢(譬如int类型指针就写作int *p; 数组指针就写作int (*p)[5],函数指针就得写得更复杂)
    (3)假设我们有个函数是:void func(void); 对应的函数指针:void (*p)(void); 类型是:void (*)(void);
    (4)函数名和数组名最大的区别就是:函数名做右值时加不加&效果和意义都是一样的;但是数组名做右值时加不加&意义就不一样。
    (5)写一个复杂的函数指针的实例:譬如函数是strcpy函数(char *strcpy(char *dest, const char *src);),对应的函数指针是:char *(*pFunc)(char *dest, const char *src);

    4.4.2.3、typedef关键字的用法
    (1)typedef是C语言中一个关键字,作用是用来定义(或者叫重命名类型)
    (2)C语言中的类型一共有2种:一种是编译器定义的原生类型(基础数据类型,如int、double之类的);第二种是用户自定义类型,不是语言自带的是程序员自己定义的(譬如数组类型、结构体类型、函数类型·····)。
    (3)我们今天讲的数组指针、指针数组、函数指针等都属于用户自定义类型。
    (4)有时候自定义类型太长了,用起来不方便,所以用typedef给它重命名一个短点的名字。
    (5)注意:typedef是给类型重命名,也就是说typedef加工出来的都是类型,而不是变量。

    4.4.2.4、总结:函数指针的分析方法也是源于优先级与逐层剥离的基本理论

    4.4.3.函数指针实战1
    4.4.3.1、用函数指针调用执行函数
    (1)最简单的函数指针来调用函数的示例,在上节课中已经演示过了。
    (2)本节演示的是用函数指针指向不同的函数来实现同一个调用执行不同的结果。
    (3)如果学过C++或者Java或者C#等面向对象的语言,就会知道面向对象三大特征中有一个多态。多态就是同一个执行实际结果不一样,跟我们这里看到的现象其实是一样的。
    (4)刚才的调试过程,可以得到很多信息:
    第一:当程序出现段错误时,第一步先定位段错误。定位的方法就是在可疑处加打印信息,从而锁定导致段错误的语句,然后集中分析这句为什么会段错误。
    第二:linux中命令行默认是行缓冲的,意思就是说当我们程序printf输出的时候,linux不会一个字一个字的输出我们的内容,而是将其缓冲起来放在缓冲区等一行准备完了再一次性把一行全部输出出来(为了效率)。linux判断一行有没有完的依据就是换行符'\n'(windows中换行符是\r\n, linux中是\n,iOS中是\r)。也就是说你printf再多,只要没有遇到\n(或者程序终止,或者缓冲区满)都不会输出而会不断缓冲,这时候你是看不到内容输出的。因此,在每个printf打印语句(尤其是用来做调试的printf语句)后面一定要加\n,否则可能导致误判。
    第三:关于在linux命令行下用scanf写交互性代码的问题,想说以下几点:
     1. 命令行下的交互程序纯粹是用来学习编程用的,几乎没有实践意义,大家别浪费时间了。
     2. scanf是和系统的标准输入打交道,printf和标准输出打交道。要完全搞清楚这些东西得把标准输入标准输出搞清楚。
     3. 我们用户在输入内容时结尾都会以\n结尾,但是程序中scanf的时候都不会去接收最后的\n,导致这个回车符还存留在标准输入中。下次再scanf时就会先被拿出来,这就导致你真正想拿的那个数反而没机会拿,导致错误。

    4.4.4.函数指针实战2
    主题:结构体内嵌函数指针实现分层
    (1)程序为什么要分层?因为复杂程序东西太多一个人搞不定,需要更多人协同工作,于是乎就要分工。要分工先分层,分层之后各个层次由不同的人完成,然后再彼此调用组合共同工作。
    (2)本程序要完成一个计算器,我们设计了2个层次:上层是framework.c,实现应用程序框架;下层是cal.c,实现计算器。实际工作时cal.c是直接完成工作的,但是cal.c中的关键部分是调用的framework.c中的函数来完成的。
    (3)先写framework.c,由一个人来完成。这个人在framework.c中需要完成计算器的业务逻辑,并且把相应的接口写在对应的头文件中发出来,将来别的层次的人用这个头文件来协同工作。
    (4)另一个人来完成cal.c,实现具体的计算器;这个人需要framework层的工作人员提供头文件来工作(但是不需要framework.c)
    (5)总结:
    第一:本节和上节实际完成的是同一个习题,但是采用了不同的程序架构。
    第二:对于简单问题来说,上节的不分层反而容易理解,反而简单;本节的分层代码不好理解,看起来有点把简单问题复杂化的意思。原因在于我们这个问题本身确实是简单问题,而简单问题就应该用简单方法处理。我们为什么明知错误还要这样做?目的是向大家演示这种分层的写代码的思路和方法。
    第三:分层写代码的思路是:有多个层次结合来完成任务,每个层次专注各自不同的领域和任务;不同层次之间用头文件来交互。
    第四:分层之后上层为下层提供服务,上层写的代码是为了在下层中被调用。
    第五:上层注重业务逻辑,与我们最终的目标相直接关联,而没有具体干活的函数。
    第六:下层注重实际干活的函数,注重为上层填充变量,并且将变量传递给上层中的函数(其实就是调用上层提供的接口函数)来完成任务。
    第七:下层代码中其实核心是一个结构体变量(譬如本例中的struct cal_t),写下层代码的逻辑其实很简单:第一步先定义结构体变量;第二步填充结构体变量;第三步调用上层写好的接口函数,把结构体变量传给它既可。

    4.4.5.再论typedef
    4.4.5.1、C语言的2种类型:内建类型与用户自定义类型
    (1)内建类型ADT、自定义类型UDT
    4.4.5.2、typedef定义(或者叫重命名)类型而不是变量
    (1)类型是一个数据模板,变量是一个实在的数据。类型是不占内存的,而变量是占内存的。
    (2)面向对象的语言中:类型就是类class,变量就是对象。
    4.4.5.3、typedef与#define宏的区别
    typedef char *pChar;
    #define pChar char *

    4.4.5.4、typedef与结构体
    (1)结构体在使用时都是先定义结构体类型,再用结构体类型去定义变量。
    (2)C语言语法规定,结构体类型使用时必须是struct 结构体类型名 结构体变量名;这样的方式来定义变量。
    (3)使用typedef一次定义2个类型,分别是结构体变量类型,和结构体变量指针类型。

    4.4.5.5、typedef与const
    (1)typedef int *PINT;    const PINT p2; 相当于是int *const p2;
    (2)typedef int *PINT;    PINT const p2; 相当于是int *const p2;
    (3)如果确实想得到const int *p;这种效果,只能typedef const int *CPINT; CPINT p1;

    4.4.5.6、使用typedef的重要意义(2个:简化类型、创造平台无关类型)
    (1)简化类型的描述。
    char *(*)(char *, char *);        typedef char *(*pFunc)(char *, char *);    
    (2)很多编程体系下,人们倾向于不使用int、double等C语言内建类型,因为这些类型本身和平台是相关的(譬如int在16位机器上是16位的,在32位机器上就是32位的)。为了解决这个问题,很多程序使用自定义的中间类型来做缓冲。譬如linux内核中大量使用了这种技术.
    内核中先定义:typedef int size_t; 然后在特定的编码需要下用size_t来替代int(譬如可能还有typedef int len_t)
    (3)STM32的库中全部使用了自定义类型,譬如typedef volatile unsigned int vu32;


    4.4.6.二重指针
    4.4.6.1、二重指针与普通一重指针的区别
    (1)本质上来说,二重指针和一重指针的本质都是指针变量,指针变量的本质就是变量。
    (2)一重指针变量和二重指针变量本身都占4字节内存空间,
    4.4.6.2、二重指针的本质
    (1)二重指针本质上也是指针变量,和普通指针的差别就是它指向的变量类型必须是个一重指针。二重指针其实也是一种数据类型,编译器在编译时会根据二重指针的数据类型来做静态类型检查,一旦发现运算时数据类型不匹配编译器就会报错。
    (2)C语言中如果没有二重指针行不行?其实是可以的。一重指针完全可以做二重指针做的事情,之所以要发明二重指针(函数指针、数组指针),就是为了让编译器了解这个指针被定义时定义它的程序员希望这个指针被用来指向什么东西(定义指针时用数据类型来标记,譬如int *p,就表示p要指向int型数据),编译器知道指针类型之后可以帮我们做静态类型检查。编译器的这种静态类型检查可以辅助程序员发现一些隐含性的编程错误,这是C语言给程序员提供的一种编译时的查错机制。
    (3)为什么C语言需要发明二重指针?原因和发明函数指针、数组指针、结构体指针等一样的。
    4.4.6.3、二重指针的用法
    (1)二重指针指向一重指针的地址
    (2)二重指针指向指针数组的
    (3)实践编程中二重指针用的比较少,大部分时候就是和指针数组纠结起来用的。
    (4)实践编程中有时在函数传参时为了通过函数内部改变外部的一个指针变量,会传这个指针变量的地址(也就是二重指针)进去

    4.4.6.4、二重指针与数组指针
    (1)二重指针、数组指针、结构体指针、一重指针、普通变量的本质都是相同的,都是变量。
    (2)所有的指针变量本质都是相同的,都是4个字节,都是用来指向别的东西的,不同类型的指针变量只是可以指向的(编译器允许你指向的)变量类型不同。
    (3)二重指针就是:指针数组指针


    4.4.7.二维数组
    4.4.7.1、二维数组的内存映像
    (1)一维数组在内存中是连续分布的多个内存单元组成的,而二维数组在内存中也是连续分布的多个内存单元组成的。
    (1)从内存角度来看,一维数组和二维数组没有本质差别。
    (2)二维数组int a[2][5]和一维数组int b[10]其实没有任何本质差别。我们可以把两者的同一单元的对应关系写下来。
    a[0][0]     a[0][1]   a[0][4]     a[1][0]    a[1][1]      a[1][4]    
    b[0]     b[1]       b[4]         b[5]        b[6]      b[9]
    (3)既然二维数组都可以用一维数组来表示,那二维数组存在的意义和价值在哪里?明确告诉大家:二维数组a和一维数组b在内存使用效率、访问效率上是完全一样的(或者说差异是忽略不计的)。在某种情况下用二维数组而不用一维数组,原因在于二维数组好理解、代码好写、利于组织。
    (4)总结:我们使用二维数组(C语言提供二维数组),并不是必须,而是一种简化编程的方式。想一下,一维数组的出现其实也不是必然的,也是为了简化编程。

    4.4.7.2、哪个是第一维哪个是第二维?
    (1)二维数组int a[2][5]中,2是第一维,5是第二维。
    (2)结合内存映像来理解二维数组的第一维和第二维的意义。首先第一维是最外面一层的数组,所以int a[2][5]这个数组有2个元素;其中每一个元素又是一个含有5个元素的数组(这个数组就是第二维)。
    (3)总结:二维数组的第一维是最外部的那一层,第一维本身是个数组,这个数组中存储的元素也是个数组;二维数组的第二维是里面的那一层,第二维本身是个数组,数组中存的元素是普通元素,第二维这个数组本身作为元素存储在第一维的数组中。

    4.4.7.3、二维数组的下标式访问和指针式访问
    (1)回顾:一维数组的两种访问方式。以int b[10]为例, int *p = b;。
    b[0] 等同于 *(p+0);   b[9] 等同于 *(p+9);  b[i] 等同于 *(p+i)
    (2)二维数组的两种访问方式:以int a[2][5]为例,(合适类型的)p = a;
    a[0][0]等同于*(*(p+0)+0);     a[i][j]等同于 *(*(p+i)+j)

    4.4.7.4、二维数组的应用和更多维数组
    (1)最简单情况,有10个学生成绩要统计;如果这10个学生没有差别的一组,就用b[10];如果这10个学生天然就分为2组,每组5个,就适合用int a[2][5]来管理。
    (2)最常用情况:一维数组用来表示直线,二维数组用来描述平面。数学上,用平面直角坐标系来比拟二维数组就很好理解了。
    (3)三维数组和三维坐标系来比拟理解。三维数组其实就是立体空间。
    (4)四维数组也是可以存在的,但是数学上有意义,现在空间中没有对应(因为人类生存的宇宙是三维的)。
    总结:一般常用最多就到二维数组,三维数组除了做一些特殊与数学运算有关的之外基本用不到。(四轴飞行器中运算飞行器角度、姿态时就要用到三维数组)


    4.4.8.二维数组的运算和指针
    4.4.8.1、指针指向二维数组的数组名
    (1)二维数组的数组名表示二维数组的第一维数组中首元素(也就是第二维的数组)的首地址
    (2)二维数组的数组名a等同于&a[0],这个和一维数组的符号含义是相符的。
    (3)用数组指针来指向二维数组的数组名是类型匹配的。

    4.4.8.2、指针指向二维数组的第一维
    (1)用int *p来指向二维数组的第一维a[i]

    4.4.8.3、指针指向二维数组的第二维
    (1)二维数组的第二维元素其实就是普通变量了(a[1][1]其实就是int类型的7),已经不能用指针类型和它相互赋值了。
    (2)除非int *p = &a[i][j];,类似于指针指向二维数组的第一维。

    总结:二维数组和指针的纠葛,关键就是2点:
    1、数组中各个符号的含义。
    2、数组的指针式访问,尤其是二维数组的指针式访问。


     

    展开全文
  • 数据结构允许你组织并应用这些算法的信息。这样说来函数已经是算法结构的一部分,而不是数据结构的一部分。然而,能够使用函数作为数据值,通常会使设计有效的接口更容易,因为这样做允许客户端指定操作和数据。...
  • 高级指针概念和应用

    千次阅读 2016-05-01 21:28:54
    一:数组指针指针数组的理解: 1.首先我们从一个例子开始讲起 include int main() { int a[5][5]={0};//创建一个二维整型数组,并初始每个元素化为0; int(*p)[4]=NULL;//定义一个数组指针,这个指针指向...
  • C语言中对于高级指针话题的总结 在C语言中对于指针最开始接触得到的话题就是在计算机科学中,指针(Pointer)是编程语言中的一个对象,利用地址,它的值直接指向(points to)存在电脑存储器中另一个地方的值。由于...
  • 指针高级应用_虚函数

    2008-06-22 11:51:51
    V6.0验证源码 ,分析虚函数的实现过程 ,强制转换的实质 ,多种指针用法,如果完全看懂了相信你的C++,指针会有一个更深刻的认识。希望对大家有用
  • C语言基础

    万人学习 2020-01-08 14:13:00
    一、C语言自我YY 1)、C语言是许多高级计算机语言的基础,学好C语言能更好的学习其他高级语言,为以后的学习打基础;往深学C语言的话那就是学到C在Linux里的应用,Linux十分强大。 2)、C语言是一种计算机程序设计语言...
  • 1.指针数组与数组指针 1.1、字面意思来理解指针数组与数组指针 (1)指针数组的实质是一个数组,这个数组中存储的内容全部是指针变量。 (2)数组指针的实质是一个指针,这个指针指向的是一个数组。   1.2、分析...
  • 结构体对函数指针高级封装应用
  • C语言中指针高级用法

    千次阅读 2016-07-14 17:56:25
    指针的灵活性让它可以实现很多复杂的操作,如指向指针指针,指向函数的指针。  指向指针指针:函数传递参数是传值调用的,如果想在调用函数时改变传入的参数的值,而且该传入的参数的值本来是个
  • C_C++指针指针应用详解

    千次阅读 2016-06-17 20:57:57
    要了解指针,多多少少会出现一些比较复杂的类型,所以我先介绍一下如何完全理解一个复杂类型,要理解复杂类型其实很简单,一个类型里会出现很多运算符,他们也像普通的表达式一样,有优先级,其优先级和运算优先级一样,所以...
  • 在指针中,还有指针数组、数组指针、函数指针、函数指针数组、函数指针的数组的指针这样一些高级指针,所以我们需要了解并掌握它们的含义和用法:我们得先知道, 指针是和数组无关的:1.指针是一种类型;数组也是一...
  • 易语言指针到字节集应用源码
  • Java知识体系最强总结(2021版)

    万次阅读 多人点赞 2019-12-18 10:09:56
    整理的Java知识体系主要包括基础知识,工具,并发编程,数据结构与算法,数据库,JVM,架构设计,应用框架,中间件,微服务架构,分布式架构,程序员的一些思考,团队与项目管理,运维,权限,推荐书籍,云计算,...
  • C语言中用指针指向函数的方法及其高级处理技巧的研究与应用,很好的东西!
  • 基本的指针应用 4.1 指针到底有什么好处? 4.2 我想声明一个指针并为它分配一些空间,但却不行。这些代码有什么问题呢?char*p;*p=malloc(10); 4.3 *p++自增p还是p所指向的变量? 指针操作 4.4 我用指针操作int...
  • 介绍c语言指针的一篇好文章,由浅入深介绍。从基本概念到高级应用,应有尽有,很不错的教材。
  • Java语言特点

    万次阅读 多人点赞 2019-06-30 19:37:45
    对C++来说进行了简化和一定的提高,如:使用接口代替了复杂的多重继承以及取消了指针,还通过实现垃圾自动回收机制,大大简化了程序员的资源释放管理工作。 提供了丰富的类库和API文档,以及第三方开发包工具包,...
  • C语言结构体对函数指针封装示例 示例: #include <stdio.h> int i, j; struct DATE{ int year; char month; char data; char hour; char min; char sec; }; struct DATE date; struct str_func {....
  • void指针void指针的基本概念void的意思就是“无类型”,void指针则为“无类型指针”,void指针可以指向任何类型的数据。 所以void指针一般被称为通用指针或者泛指针,或者叫做万能指针 定义形式 : void *p;**在...
  • 超星高级语言程序设计实验作业 实验05 指针及其在程序设计中的应用 注:以下内容仅供交流,代码都是本人自己写的,还请同学们先自己编写再进行交流。
  • Java面试题大全(2020版)

    万次阅读 多人点赞 2019-11-26 11:59:06
    String 和 StringBuffer、StringBuilder 的区别在于 String 声明的是不可变的对象,每次操作都会生成新的 String 对象,然后将指针指向新的 String 对象,而 StringBuffer、StringBuilder 可以在原有对象的基础上...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 103,013
精华内容 41,205
关键字:

高级指针应用