指针的高级应用

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)则是代表了二维数组第二行的地址。

原文地址:https://www.cnblogs.com/qxj511/p/4930707.html