C语言从指针入门到指针进阶

目录

一、​​​​​​​了解指针

二、二级指针

三、字符指针

四、野指针

1.未初始化

2.指针越界访问

3.动态内存释放

4.规避野指针

五、指针数组

六、数组指针

七、函数指针

八、函数指针数组

九、指向函数指针数组的指针

十、函数的回调


C语言之所以难,就是因为有指针这个东西,那么首先我们需要了解指针,这里基本概念我会简单略过,主要讲解指针的各种运用。

一、​​​​​​​了解指针

概念:指针=地址(本篇指针和地址会换着使用,但请读者了解这两个是一个东西),而我们平常习惯说的指针,实质上是指针变量,这两个是不同的概念,千万别搞混了!

我们用指针来指向地址,例如:

#include
int main()
{
    int a=10;
    int* p=&a;
    return 0;
}

这里我要提一下,*号告诉p是指针,p前面的int是告诉我们p指向的对象的类型是int类型

我们调试便可以知道p和a的地址是相同的C语言从指针入门到指针进阶_第1张图片

细心的小伙伴肯定发现p本身也是有地址的,所以有了二级指针。

二、二级指针

#include
int main()
{
    int a=10;
    int* p=&a;
    int** pp=&p;
    return 0;
}

有了指针以后,我们可以用*号去解引用,可以进行修改等一系列操作,例如:

  那么我们要如何理解这里的操作呢?请看下面的图例

C语言从指针入门到指针进阶_第2张图片

那么我们为什么要加*号呢,我的理解是a的类型是int,p的类型是int *,我们想要类型匹配,就需要在加一个*号,这样我们才能去使用a保存的值,而不是地址。

三、字符指针

概念:指向字符的指针

#include
int main()
{
    //这里保存的并不是整个字符常量,而是字符常量的首地址,即w的地址
    char* c="work hard";
    printf("%s",c);
    return 0;
}

四、野指针

什么叫野指针?即:指针指向的空间是未知的。

导致这个问题有几个原因,我们来一一分析。

1.未初始化

int main()
{
    int *p;//p指向一个随机的位置
    *p=20;
    printf("%d",*p);
    return 0;
}

我们调试的时候便可以看到问题:

C语言从指针入门到指针进阶_第3张图片

2.指针越界访问

int main()
{
    int arr[5]={1,2,3,4,5};
    int* p=arr;
    //数组最大是5,我们却访问了后面的元素,不仅指针不能这么用,数组也不能这么用
    for(int i=0;i<9;i++)
    printf("%d",*(p+i));
    return 0;
}

3.动态内存释放

int main()
{
    int* a=(int*)malloc(sizeof(int));
    free(a);
    //free只是释放这个空间,单这个空间还可以被使用,为了不被使用,我们需要置空
    a=NULL;
    return 0;
}

4.规避野指针

    (1) 指针初始化

    (2)不越界访问

    (3)指针指向的空间要释放

    (4)避免返回局部变量的地址(因为局部变量出了作用域会被销毁)

    (5)指针使用前检查有效性

五、指针数组

概念:本质上是数组,里面存放了指针(地址),定义方法为int* p[]

p是一个数组,数组里存放了3个元素,每个元素的类型是int*

这里p[3]存放的是数组名,而数组名就是数组首元素的地址,这个用法是帮助我们理解指针数组,实际上指针数组还有一个常用的方法:

#include
int main()
{
    int i=0;
    char* p[3]={"I","love","China"};
    for(i=0;i<3;i++)
    printf("%s ",*(p+i));
    return 0;
}

输出结果为:I love China

注意!很多同学在这里认为指针数组保存的是一整个字符串,其实不是,这里保存的是常量字符串的首地址

六、数组指针

概念:指向数组的指针,存放的是数组地址(重点是数组地址),本质上是一个指针,那么这里我们这样定义int* p[]是对的吗?答案是:完全错误!因为p首先和方括号结合,最后就成了数组了,这里我们需要加括号来指名他是数组指针,例如:int (*p)[],这样p就会首先和*号结合,牢牢记住!

p的类型是int(*)[],数组指针的基本用法为:

#include
int main()
{
    int i=0;
    int arr[10]={1,2,3,4,5,6,7,8,9,10};
    int (*p)[10]=&arr;
    for(i=0;i<10;i++)
    {
        //*p=arr,因为p相当于&arr,*p就等于*&arr,这样就抵消了,相当于获得了数组名
        printf("%d ",(*p)[i]);
    }
    return 0;
}

输出结果为:1 2 3 4 5 6 7 8 9 10

这里一定要注意我的注释,非常重要,这里注意是&arr,指针数组用的是arr,这两个的区别是arr是数组首元素的地址,而&arr是整个数组的地址,实际上我们不会这么用,我只是想用这个方法告诉你数组指针的基本概念,我们一般这样用:

#include
void print(int (*arr)[4],int row,int col)
{
    int i=0;
    int j=0;
    for(i=0;i

输出结果为:1 2 3 4

                    2 3 4 5

                    3 4 5 6

二维数组的数组名是首元素的地址,而二维数组的首元素就是第一行的地址!所以arr+i就是第i行的地址*(arr+i)相当于拿到了第i行,这又相当于数组名,因为*(p+i)==p[i],在二维数组中,这就是第i行的数组名,数组名又相当于数组首元素的地址,所以我们可以写成*(*(arr+i)+j)

七、函数指针

函数指针:指向函数的指针变量,存放函数地址

下面我们来看一个图:

C语言从指针入门到指针进阶_第4张图片

我们发现&Add和Add的地址一样的, 那么我们可以用指针保存起来,void (*pf)(),以下是代码

#include
int Add(int x,int y)
{
    return x+y;
}
int main()
{
    //这两个都可以用,因为他们指向的地址是一样的
    //数组&和没有&是有区别的,但是函数没有,因为函数没有首元素取地址的说法
    //int (*pf)(int x,int y)=&Add;
    int (*pf)(int x,int y)=Add;
    //这里可以没有*号的原因是Add=pf
    int ret=pf(2,3);
    //int ret=(*pf)(2,3);
    printf("%d \n",ret);
    return 0;
}

八、函数指针数组

概念:把函数地址存放到数组中成为函数指针数组,它的本质是数组,所以我们定义为void(*pf[])()

C语言从指针入门到指针进阶_第5张图片

在我们看函数指针数组实际运用之前,我们先做一个加减乘除的计算器,代码如下:

#include
int Add(int x,int y)
{
    return x+y;
}
int Sub(int x,int y)
{
    return x-y;
}
int Mul(int x,int y)
{
    return x*y;
}
int Div(int x,int y)
{
    return x/y;
}
void menu()
{
        printf("**** 0.exit 1.add ****\n");
 	    printf("**** 2.sub  3.mul ****\n");
 	    printf("****    4.div    ****\n");
}
int main()
{
    int input=0;
    int a=0;
    int b=0;
    int (*pfArr[5])(int x,int y)={0,Add,Sub,Mul,Div};
    do
    {
        menu();
        printf("请输入->\n");
        scanf("%d",&input);
        switch (input)
        {
        case 0:
            printf("exit\n");
            break;
        case 1:
            printf("请输入两个数字\n");
            scanf("%d %d",&a,&b);
            printf("%d\n",Add(a,b));
            break;
        case 2:
            printf("请输入两个数字\n");
            scanf("%d %d",&a,&b);
            printf("%d\n",Sub(a,b));
            break;
        case 3:
            printf("请输入两个数字\n");
            scanf("%d %d",&a,&b);
            printf("%d\n",Mul(a,b));
            break;
        case 4:
            printf("请输入两个数字\n");
            scanf("%d %d",&a,&b);
            printf("%d\n",Div(a,b));
            break;
        default:
            break;
        }
    }while(input);
    return 0;
}

这段代码冗余度很高对吧?这时函数指针数组的优势就体现出来了,我们可以用函数把main函数的代码改为:

int main()
{
    int input=0;
    int a=0;
    int b=0;
    int (*pfArr[5])(int x,int y)={0,Add,Sub,Mul,Div};
    do
    {
        menu();
        printf("请输入->\n");
        scanf("%d",&input);
        if(input==0)
        {
            printf("exit\n");
        }
        else if(input>=1&&input<=4)
        {
            printf("输入两个数字\n");
            scanf("%d %d",&a,&b);
            int ret=pfArr[input](a,b);
            printf("%d\n",ret);
        }
        else
        {
            printf("重新输入\n");
        }
    }
    while(input);
    return 0;
}

九、指向函数指针数组的指针

概念:简单说就是存放函数指针数组的地址,重心在于指针!

int main()
{
    int (*pfArr[5])(int x,int y)={0,Add,Sub,Mul,Div};
    //重心在于指针,所以我们需要先写成这样(*ppfArr)
    int (*(*ppfArr)[5])(int x,int y)={&pfArr};
}

十、函数的回调

概念:函数的(指针)地址作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们称为函数的回调。

可能看这个文字描述会让我们云里雾里的,所以我们直接用代码去解释,就用之前的计算器的代码:

//既然我们传过来了函数的地址,那么我们里面的参数就可以用函数指针来接受
void calc(int (*pf)(int x,int y))
{
    printf("请输入两个数字->");
    int a=0;
    int b=0;
    scanf("%d %d",&a,&b);
    int ret=pf(a,b);
    printf("%d\n",ret);
}
int main()
{
    int input=0;
    do
    {
        menu();
        printf("请输入->\n");
        scanf("%d",&input);
        switch (input)
        {
        case 0:
            printf("exit");
            break;
        case 1:
            calc(Add);
            break;
        case 2:
            calc(Sub);
            break;
        case 3:
            calc(Mul);
            break;
        case 4:
            calc(Div);
            break;
        default:
            break;
        }
    }
    while(input);
    return 0;
}

这里我们在回过头来想想是不是很好理解了?我们把函数的地址作为参数,把这个参数给另一个函数,这个指针被用来调用了我们定义的加减乘除函数。

指针的所有用法基本都在这里介绍了,如果有错误希望指正!

你可能感兴趣的:(C语言指针,c语言)