您现在的位置是:首页 >技术杂谈 >c中的指针详解网站首页技术杂谈

c中的指针详解

2140274814 2024-07-15 06:01:02
简介c中的指针详解

介绍:

        本文章介绍指针的详细运用和指针的灵活使用方法,指针对于初学者可能是比较大的难点,而指针对于编程学者是一个重要内容,指针也是后面深入学习的基础内容。

目录:

一,指针的理解:

二,指针的类型:

三,指针与数组:

四,指针与函数:

五,二级指针的运用:

一,指针的基础理解

        指针实名叫做地址,通过地址,我们可以找到这个地址里面所包含的内容,而在C语言中,通常用&(即取地址符号)来表示一个变量或常量的地址,用*(即解引用符号)表示地址里的内容。

例如:&a表示a  的地址,*a表示地址里的数值。对于复杂的,例如:*(&a)表示a的数值(&a表示a的地址,而在加上*就对a进行了解引用),*&*a表示地址里的数据(*a表示地址里的数据,而加上&表示a的地址,再加上*表示里面的数值)。其他的更复杂的与之类似。

        ·但需要提醒的是,一般情况下,对于变量的地址,编译器一般用是十六进制存储的。代码演示如下:

#include<stdio.h>
int main()
{
    int a = 0;

//%p是打印地址的形式
    printf("%p", &a);
}

运行图:

 可看出,a的地址是用十六进制表示的

二,指针的类型:

        刚刚的介绍,让我们大概明白了指针的概念,即指针用来指向地址,但需注意的是,指针拥有自己的类型,平常我们都是用在一个类型的前面加上一个*来表示指针,然后用指针来存储变量或常量的地址,如下:

#include<stdio.h>
int main()
{
    int i = 0;
    char j = 0;
    int* a=&i;//整形指针,将i的地址放到指针a中
    char* b=&j;//字符指针,将j的地址放到b中

    return 0;
}

其他与之类似,在类型前面加上一个*就表示什么类型的指针,(在这里只是介绍初级的指针,跟复杂的指针类型在后面会介绍),需注意的是,当指针类型与指向数据地址的类型不一样的时候,仍然可以通过,但是在有些情况下运用会出错,因为指针类型只是决定了指针的步长,int型步长为4,float型为4,double型步长为8,char型步长为1,short型为2,long为4或8。

以下用Int型来表示,其他类型与之类似

#include<stdio.h>
int main()
{
    int a = 0;
    int* p = &a;
    printf("%p %p", p, p + 1);
    return 0;
}

运行图:

        可看出,int型指针在加1时跳4,若换成其他类型,步长会发生变化,而数据的类型决定了数据所占的字节大小,在计算机中一个地址的空间拥有一个字节的大小,当指针的步长与数据存储的大小不同时,会发生错误,因此,不能随意运用指针的类型,要根据指针指向的数据类型来决定用那个指针类型。

         最后要提醒的是,指针也是变量,也需要存储空间,而指针是地址,所以在32位平台的机器上指针占的是32/8=4字节的空间,而在64位平台上指针占的是64/8=8字节的空间,与指针类型无关,因为指针类型只决定了指针相加的步长。

三,指针与数组:

1,指针与一维数组

        首先,我们要明白,数组中元素的地址都是连续的,而数组名即代表数组首先素的地址,在用指针指向数组的方式很灵活,在这里,我先给学者们介绍几种,用指针来打印数组元素。

//建立一个指针,直接指向数组

#include<stdio.h>
int main()
{
    int a[10] = { 1,2,3,4,5,6,7,8,9,10 };
    int* p = a;//用指针指向首元素的地址,打印数组所有的元素
    for (int i = 0; i < 10; i++)
        printf("%d ", *(p + i));
    return 0;
}

//数组名表示首元素的地址,直接用数组名来打印数组元素

#include<stdio.h>
int main()
{
    int a[10] = { 1,2,3,4,5,6,7,8,9,10 };
    for (int i = 0; i < 10; i++)
        printf("%d ", *(a + i));//陆续访问元素地址,然后解引用输出数据
    return 0;
}

//因为数组名代表首元素地址,而指针直接指向数组名,所有指针和数组名可以互换

#include<stdio.h>
int main()
{
    int a[10] = { 1,2,3,4,5,6,7,8,9,10 };
    int* p = a;
    for (int i = 0; i < 10; i++)
        printf("%d ", p[i]);

//用以下方式效果也是一样
    //for (int i = 0; i < 10; i++)
      //  printf("%d ", *(a + i));
    return 0;
}

        因此,一维数组与指针的关系可间接理解为,数组名即代表指针,而[]操作就是访问其元素(注意:二维数组不可这么运用)

2,指针与二维数组

二位数组也是数组,其元素的地址也是连续的,可根据指针的步长来相加后解引用,也可运用数组指针或指针数组;

数组指针:

        

        数组指针最多用在指向二维数组的情况,但也可指向一维数组,只是比较麻烦

指针数组:


       总: 指针数组也就是包含指针的数组,而数组指针也就是指向数组的指针

        对于二维数组而言,用指针的方法多种多样(也可用二级指针,后面会详细介绍),运用起来也比较灵活,可根据个人习惯而定。

四,指针与函数:

1,指向函数的指针

        首先,要补充的是,函数名即是函数地址,若有一个函数int add(int ,int),则指向此函数的指针int (*p)(int ,int )=add或者int (*p)(int,int)=&add,其中,函数指针里的参数类型以及函数指针的类型必须与函数参数和函数的类型一样,需注意的是,若函数为void型,则函数指针也为void型。

        因为函数名即为地址,所以,函数名也看成指针,因此,对于以上例子,(*p)(2,3)==p(2,3),加不加*符号传参都一样。对于函数指针的操作,几乎与指向一维数组的指针操作一样,即指针和函数名逻辑上是一样的,可以互换。

        有了以上的学习我们可再深一步了解——函数指针数组(即指向多个函数的指针)

#include<stdio.h>
int main()
{
    //下面三个是函数
    int arr(int, int);
    int sub(int, int);
    int mul(int, int);
    //函数指针数组,指向arr,sub,mul三个函数
    int (*p[])(int, int) = { arr,sub,mul };//初始化时[]里面可不加数字
    return 0;
}
 

而再使用时,形式为int ret=p[n](x,y),其中,p[n]对应的函数,例:p[0]指向arr这个函数,而p[0](3,4)==arr(3,4)。

2,返回指针的函数

        以整形为例,形式为 int *p(int ,int),此时,函数返回为整形指针,其他类型同理。

        万能指针:void *p(.......),即无类型的指针,此种指针可存储int,char,double等所有类型的指针。但需注意:这种指针不可解引用操作,也不可进行+,-操作,例如:void *p(...)    p++,这种方式是错误的

3,形参为指针的函数

        在进行函数传参的过程中,有时我们想要改变实参的值,若不用指针型,是不会影响的实参的,因为形参是内存中有开辟的一块空间,要想改变其值,就必须用到指针,即传递地址,而形参用指针存储,方法比较简单,在这里就不做过多演示。

五,二级指针的运用:

        二级指针的形式:int **p,即前面加两个**,用来指向指针,即地址的地址,当一次解引用时,只是访问指向的地址,二次解引用时,访问的是指向地址的指向的空间数据,因此,只要是设计一级指针,都可用二级指针来指向,下面用一道运用二级指针的例题来说明:

//n个人围成一个圈,数到3退出,请输出最后在圈中的人,即:出圈游戏

#include<stdio.h>
int main()
{
    int i,j=1,k=0,a[1000],*p=a,m=0,n,**pa;
    scanf("%d",&n);
    for(i=1;i<=n;i++)
        *(p+i)=i;
    pa=&p;//p是一个地址,二级指针指向p这个一级指针的地址
    while(m!=n-1)
    {
        k++;
        if(k==3)
        {
            k=0;
            m++;
        }
        else

//*pa==p,而**pa==*p,通过指针的步长来渐进访问元素
            *(*pa+n+j-m)=*(*pa+j);
        j++;
    }
    printf("%d ",*(*pa+j));
    return 0;
}

        通过以上例子,可大概明白二级指针的初级运用,下面我们在深入学习二级指针与指针数组和数组指针的运用。

1,二级指针与指针数组的运用

        首先,经过前面的介绍,要明白指针数组是指向指针的数组,即包含许多指针的数组。而数组名有代表首元素的地址,前面已说明,数组名可理解为指针,两者可以互换,因此,当用二级指针指向指针数组时,只用指向其数组名即可,例:int* p[5] = {a,b}, ** pa = p,根据前面的学习,此时的pa==p,而在大多数情况中,对于运用指针指向数组的情况,数组加不加&效果都一样,不影响程序,运用的原理仍是数组名代表数组的首元素地址(注意:若是指针不指向数组首元素地址,此时要加&)。

        下面是代码运行:

#include<stdio.h>
int main()
{
    int a[5] = { 1,2,3,4,5 }, b[5] = {6,7,8,9,10};
    int* p[5] = {a,b}, i, ** pa = p;//也可写成**pa=&p
    for(i=0;i<5;i++)

//下面改成(*(pa))[i]即打印数组a中的所有元素,pa==p,两者可互换
        printf("%d ", (*(pa+1))[i]);//打印数组b中的所有元素
    return 0;
}

2,二级指针于数组指针的运用

        数组指针说白了就是指向数组的指针,因此用二维指针指向数组指针时,必须用&符号的形式,即Int **pa =&p,其中p是数组指针。在解引用时,*pa==p,进而用指针的步长访问其中的元素,具体代码如下:

#include<stdio.h>
int main()
{
    int a[2][5] = { 1,2,3,4,5,6,7,8,9,10 };
    int(*p)[5] = a, i, ** pa = &p;

//用以下操作访问数组a中的所有元素
    for(i=0;i<10;i++)
        printf("%d ",*(*pa)+i);//*(*pa)+i==(*pa)[i],用哪个方式有一样
    return 0;
}

        最后,提醒一下,二级指针稍微逻辑上有点绕圈,通常能不用二级指针就不要用二级指针,而二级指针的运用通常与指针数组运用的情况比较多。

总结:指针的运用非常灵活,对于跟复杂的指针结构,要以以上的讲解为基础,学会分布解析,在以后的学习中可能还会遇到三级指针,四级指针等等,其方法都与二级指针一样。还有,对于指针的学习,笔者还是建议多动手操作,理清中间的逻辑关系。

风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。