IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> C语言从指针入门到指针进阶 -> 正文阅读

[C++知识库]C语言从指针入门到指针进阶

目录

一、???????了解指针

二、二级指针

三、字符指针

四、野指针

1.未初始化

2.指针越界访问

3.动态内存释放

4.规避野指针

五、指针数组

六、数组指针

七、函数指针

八、函数指针数组

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

十、函数的回调


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

一、???????了解指针

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

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

#include<stdio.h>
int main()
{
    int a=10;
    int* p=&a;
    return 0;
}

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

我们调试便可以知道p和a的地址是相同的

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

二、二级指针

#include<stdio.h>
int main()
{
    int a=10;
    int* p=&a;
    int** pp=&p;
    return 0;
}

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

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

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

三、字符指针

概念:指向字符的指针

#include<stdio.h>
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;
}

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

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<stdio.h>
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<stdio.h>
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<stdio.h>
void print(int (*arr)[4],int row,int col)
{
    int i=0;
    int j=0;
    for(i=0;i<row;i++)
    {
        for(j=0;j<col;j++)
        //printf("%d ",arr[i][j]);
        printf("%d ",*(*(arr+i)+j));
        printf("\n");
    }
}
int main()
{
    int arr[3][4]={{1,2,3,4},{2,3,4,5},{3,4,5,6}};
    print(arr,3,4);
    return 0;
}

输出结果为: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)

七、函数指针

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

下面我们来看一个图:

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

#include<stdio.h>
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[])()

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

#include<stdio.h>
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++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2022-09-13 10:54:41  更:2022-09-13 10:55:58 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/27 11:48:44-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码