• 指针的高级应用


    1、指针数组和数组指针

    (1)指针数组

    也就是说是数组,只不过数组保存的是指针,也就是专门用于保存地址的数组

    int *p[5];

        也就是说,p[] 数组里面保存的都是地址,、

    使用:

    *a[i];   // 保存的地址的嘛,使用肯定就是加上 星号

    (2)数组指针

    也就是说是一个指针,但是这个指针是只能指向数组的一个指针,

    int (*p)[5]
     

    例子代码:

    int a[4] = { 1, 2, 3, 4 };
        int *b[4]; //指针数组
        int(*c)[4]; //数组指针
        //将数组c中元素赋给数组a
        for (int i = 0; i < 4; i++)
        {   // 指针数组 保存数组的每个元素的地址
            b[i] = &a[i];
        }
        cout << "指针数组进行打印"<<endl;
        for (int i = 0; i < 4; i++)
        {   // 指针数组 保存数组的每个元素的地址
            cout << *b[i] << "";
        }
        c = &a;  // 数组指针指向数组
        cout << endl;
        cout << "数组指针进行打印" << endl;
        for (int i = 0; i < 4; i++)
        {   // 数组指针 
            cout << (*c)[i] << "";
        }
        while (1);
    }

     

    2、函数指针与指针函数

        函数体的本质其实就是一块代码体,这一块代码体是在内存连续分布的,所以这块代码的第一个地址就重要了,而函数名其实就是一个指针,指向函数首地址的指针。

    (1)指针函数

    比如:

    int *func(int x, int y)

        因此,函数func()运行,结束的时候,返回一个int * 类型的地址。

        当然函数的接收的,也必须是相同的类型(int *)的进行接收。

    格式:

        类型 * 函数名(参数类型),返回的是地址,这个比较好理解

    (2)函数指针

        函数指针,实质就一个指针变量,是指向一个函数的指针变量。格式如下:

    类型说明(*函数名)(参数)

        其实,严格来说,这边的函数名,确切应该叫做指针,括号必须存在,如果没有了括号的话,就变成指针函数了。

    int (*ptr)(int, int) // 函数指针的声明

     

    ptr 是函数指针变量名,它的类型是int(*)(int,int),而它的返回值是int 类型。

        函数指针,实质就是函数的入口地址。既然函数名表示了函数的入口的地址,因此函数名就是一个函数指针(常量)。

        函数指针变量:存放了函数口入地址的变量,与其他的指针变量类型,只不过他是指向的目标是指令代码,而不是数据。

        函数指针的的赋值:

    函数指针变量名 = 函数名

    函数指针变量名 = &函数名

        这两种方法都是可以的。实际使用:

    int add(int x, int y)
    {
        return x + y;
    }
     
    int main()
    {
        int(*ptr)(int, int); 
        ptr = add;
        //ptr = &add; // 都可以的
     
        printf("sum = %d
    ",ptr(1,2));
        while (1);
    }

     

    int(*ptr)(int, int);

    是函数指针的声明,声明的时候,要么必须和需要复制的add函数的类型全部一致,要么就必须是void,使用的时候再加以指定。

    赋值可以是:ptr = add; 或者ptr = &add,这样,函数指针ptr,就指向了函数add()函数的入口地址

    使用void类型做函数只针对的声明:

    int add(int x, int y)
    {
        return x + y;
    }
     
     
    int main()
    {
        void(*ptr); // 函数指针的声明
       //   void *ptr;也可以这样声明
     
        ptr = add;// 函数指针变量的赋值
     
        printf("sum = %d
    ",((int(*)(int,int))ptr)(4,5));
    }

    函数指针声明:void(*ptr);可以直接是void *ptr;但是,在调用的时候,就可以进相知类型zhuanhuan :

    ((int(*)(int,int))ptr)(4,5) 

    函数的理解: 函数其实就是一大块代码体,这一块代码体是在内存连续分布的,而指向这块代码体的首地址就是函数名。

    3、typedef 关键字与函数指针的结合

        typedef 关键字,是用来重命名作用。

    int add(int x, int y)
    {
        return x + y;
    }
     
    typedef int(*FUNC)(int, int);  //移植性比较好
     
    int main()
    {
        //FUNC ptr = add;
        FUNC ptr = &add; // 两个函数指针的赋值都是可以的
     
        printf("sum = %d
    ",((int(*)(int,int))ptr)(4,7));
        while (1);
    }

    typedef补充:

        C语言的两种类型:内建类型(int、char)、自定义类型(typedef)。

    经过typedef 定义之后,其实是创建了一个新的类型typedef 与 define 的区别,别的帖子做总结。

    4、二重指针

        二重指针和一般的指针都是类似的,但是二重的指针是指向指针变量的指针变量,也就是指向指针的指针。

    (1)二重指针指向指针变量

    int a = 1;

     

    int a = 1;
    int *p1;
    int **p2;
    p1 = &a;   
    p2 = &p1;  // 指向指针变量

        p1 指针变量 a,而 p2 指向了 p1, 也就是指向了指针变量的指针变量。


    (2)二重指针指向指针数组

    int *p1[5];  // 指针数组
        int *p2;
        int **p3;   // 二重指针
        p3 = p1;
        int a[5] = { 1, 2, 3, 4, 5 };
        for (int i = 0; i < 5; i++)
        {
            p3[i] = &a[i];
        }
        for (int i = 0; i < 5; i++)
        {
            printf("a[%d] = %d
    ", i, *p3[i]);
        }
        while (1);

        对于指针数组(本质上是数组,但是数组保存的内容是指针,即 int*),而数组的数组名也是指针,所以二重指针是可以指向指针数组的。


    (3)应用

    A、传变量的地址,来修编变量的值

    一般编程中,函数传值的参数,可以通过传递一个变量的地址,然后可以通过这个地址来修改这个地址上保存的值。

    void setvalue(int *p)
    {
        *p = 3.14;
    }
    int main(int argc, char *argv[])
    {
        int a = 1;
        setvalue(&a);
        printf("a = %d
    ", a);
        
        while (1);
    }
    打印的结果是: 2

     

    B、传地址的地址,来修改地址

        当函数传参的是一个地址的时候,又想修改这个地址,这个时候必须传递指针的指针,也就是二重指针。

    错误的例子:传递地址并不能修改地址

    void getmemory(char *p)
    {
        p = (char *)malloc(100);
    }
    int main(int argc, char *argv[])
    {
        char *p = NULL;
        getmemory(p);
        printf("p = %p
    ", p);
        while (1);
    }

    打印的结果:p = 00000000

        想通过子函数来达到修改地址,显然没有成功。

    原因分析: 对于函数的传参,并没有使用C++的引用,使得对变量i引用的操作就可以等同于对变量进行操作。上面函数传递的地址,也就是指针p将值传递给形参p,传递的是值,这个时候,主函数的p并不会随着子函数的操作而受到影响。

     

    解决的办法1:返回值解决

    char *getmemory(void)
    {
        char *p = (char *)malloc(100);
        return p;
    }
    int main(int argc, char *argv[])
    {
        char *p = NULL;
        p = getmemory();
        printf("p = %p
    ", p);
        while (1);
    }

    打印输出: p = 0071A180

       并没有传递任何值,而是通过子函数分配了一个指针之后进行返回。

    解决办法2:传递指针的指针

    void getmemory(char **p)
    {
         *p = (char *)malloc(100);
    }
    int main(int argc, char *argv[])
    {
        char *p = NULL;
        getmemory(&p);
        printf("p = %p
    ", p);
        while (1);
    }

     

    打印输出:p = 0049A180

        通过传递指针变量的地址,也就是传递的是一个二重地址,去修改指针变量。

    总结:

        (1)想要修改值,那么可以传递地址

        (2)想要修改地址,那么就传递地址的地址


    5、二维数组


    二维数组:
    int a[2][5] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };

        定义了两行五列的二维数组,第一维:行,2;第二维:列,5

    a[0][0]  a[0][1]  a[0][2]  a[0][3] a[0][4]
    a[1][0]  a[1][1]  a[1][2]  a[1][3] a[1][4]

    一维数组:

    int b[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };

        相比于一维数组而言,一维二维从内存的存储上面来说都是一样的。一维数组:在一一块连续的内存空间上,一次保存数组的成员;对于二维数组:也是在一块连续的内存空间上面,先连续保存第一行的数据,紧接着保存第二行;所以从内存管理的角度上来说,二维数组与一维数组都是一样的。

    二维数组的访问:

    (1)通过下标访问

    int a[i][j];

        通过下标来访问 第 i 行,第 j 列。

    (2)通过指针进行访问

        访问 a[i][j] 的话可以通过:

    *(*(a+i) + j)

        来进行访问。可以将二维的数组名理解为指向第一行的行指针,指向的是是第一行第一个元素的地址,

    int main(int argc, char *argv[])
    {
        int a[2][5] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
        
        printf("*a = %d
    ", *a);
        printf("*a+1 = %d
    ", *a+1);
        printf("*(a+1) = %d
    ", *(a + 1));
        printf("&a[0][0] = %d
    ", &a[0][0]);
        printf("&a[1][0] = %d
    ", &a[1][0]);
        
        while (1);
    }

     

    打印输出:

    *a = 4127892 // 代表了第一行的地址

    *a+1 = 4127896 // 地址 加 1 嘛,相当于加 4

    *(a+1) = 4127912 // 代表了第二行的地址

     

    &a[0][0] = 4127892 // 第一行第一列的地址

    &a[1][0] = 4127912 // 第二行第二列的地址

     

    可见,二维数组名确实是指向了第一行第一列元素的地址,代表的是第一行的首地址。所以才可以通知,*(*(a+i)+j) 的方式进行访问二维数组,其实就是先指定了行号: i,接着执行了列号: j。而*(a+1)则是代表了二维数组第二行的地址。

  • 相关阅读:
    WPF 使用Code创建Canvas,StackPanel,DockPanel。Grid,Border,UniformGrid,ViewBox,WrapPanel等
    myeclipse6.5安装maven2插件教程 (Maven+MyEclipse6.5)
    myeclipse使用maven教程
    【技术贴】如何汉化 myeclipse 中的svn插件
    【技术贴】小米利用Connectify共享无线网卡连接教程
    Myeclipse 自动生成 javadoc 教程
    MyEclipse6.5配置Tomcat7.x
    maven构建项目自动部署到tomcat中遇到的各种sb问题总结
    吐槽之程序猿
    html5 css3构造的漂亮表格
  • 原文地址:https://www.cnblogs.com/qxj511/p/4930707.html
Copyright © 2020-2023  润新知