问小白 wenxiaobai
资讯
历史
科技
环境与自然
成长
游戏
财经
文学与艺术
美食
健康
家居
文化
情感
汽车
三农
军事
旅行
运动
教育
生活
星座命理

【指针】内含超多图解!通俗易懂!(入门必看!)

创作时间:
作者:
@小白创作中心

【指针】内含超多图解!通俗易懂!(入门必看!)

引用
CSDN
1.
https://blog.csdn.net/2401_82641862/article/details/137143496

指针是C语言中的一个重要概念,它允许程序员直接操作内存地址,从而实现更高效的数据处理。本文将通过通俗易懂的语言和大量图解,帮助读者系统地学习和掌握指针的基本概念、操作符、运算、使用场景、与数组的关系、二级指针、函数指针等多个方面。

1、初始指针

首先,我们通过一个例子认识一下指针:

假设你要到朋友的学校找他,你不知道他的宿舍号是多少,你只能一个一个宿舍的找,知道你找到他位置。

但是如果你提前知道了他的宿舍号,你就可以直接到对应的楼层,对应的房间。这样就可以大大节省了时间。

同理,我们知道计算机上CPU(中央处理器)在处理数据的时候,需要的数据是在内存中读取的,现在内存被划分为一个个的内存单元,每个内存单元的大小取1个字节。当我们需要寻找内存中的某个数值时,一个一个内存单元挨着去找,这样的效率会很低。

但是如果知道了该数值在内存中的地址,此时是不是就可以直接访问该地址,从而大大提高了效率!

这个大大提高效率的地址就是这篇文章要讲的指针了!

2、指针变量和两个关键操作符

取地址操作符 &

解引用操作符 *

2.1 “&”取地址操作符

我们知道a为int类型,在内存中占4个字节,而这四个字节的地址分别是:

0x0000003CC494F794

0x0000003CC494F795

0x0000003CC494F796

0x0000003CC494F797

那我们如何能得到a的地址呢?

这里就得学习一个操作符(&)-取地址操作符

此时,&a取到的是四个地址中最小的地址,虽然整型变量占用4个字节,我们只要知道了第一个字节地址,顺藤摸瓜访问到4个字节的数据也是可行的。

2.2 指针变量

那我们通过取地址操作符(&)拿到的地址是一个数值,比如:0x006FFD70,这个数值有时候也是需要存储起来,方便后期再使用的,那我们把这样的地址值存放在哪里呢?答案是:指针变量中。

指针变量也是一种变量,这种变量就是用来存放地址的,存放在指针变量中的值都会理解为地址。

int a=1;
int* p=&a;

2.3 “ * ”解引用操作符

现在我们数值的地址存放在指针变量中,那么我们如何使用它呢?

在现实生活中,我们使用地址要找到一个房间,在房间里可以拿去或者存放物品。

C语言中其实也是一样的,我们只要拿到了地址(指针),就可以通过地址(指针)找到地址(指针)指向的对象,这里必须学习一个操作符叫解引用操作符(*)。

int main()
{
    int a = 1;
    int* p = &a;
    printf("%d", *p);//p存放a的地址,*p就找到了a存放的空间,*p其实就是a变量了
    return 0;
}

2.4 小结

被指向的类型 指针类型

int int*

char char*

… …

3.指针运算

int arr[5]={1,2,3,4,5};
int * p=&arr[0];

下面以这一个数组为例讲解指针运算。

3.1 指针±整数

*p=arr[0]=1

*(p+1) =arr[1]=2

*(p+2)=arr[2]=3

此时,由于p为int类型的指针,所以p+1跳过4个字节。

同理,如果p为char类型的指针,p+1跳过1个字节。

3.2 指针-指针

3.3 指针的关系运算

假设有指针变量 p1、p2。

1.p1 > p2 表示 p1指向的存储地址是否大于 p2 指向的地址

2.p1 == p2 表示 p1 和 p2 是否指向同一个存储单元

3.p1 == 0 (p1==NULL)和 p1 != 0 (p1!=NULL)表示 p1是否为空指针

int main()
{
    int arr[5] = { 1,2,3,4,5 };
    int* p1 = arr[0];
    int* p2 = NULL;
    if (p1 > p2&&p1!=0&&p2!=0)
        printf("p1指向的地址大于p2指向的地址。");
    else if (p1 == p2&&p1!=NULL)
        printf("p1和p2指向同一存储单元。");
    else if (p1 == NULL || p2 == NULL)
        printf("存在空指针。");
    else
        printf("p2指向的地址大于p1指向的地址。");
    return 0;
}

4 . 指针的使用和传址调用

首先通过一个例子给大家讲解。

4.1 strlen函数的模拟实现

int My_strlen(char* p, int sz)//实参为char*类型的指针,形参为char*类型
{
    int count = 0;
    while (*p++)//运算符的优先级,先解引用,再++。p+1跳过一个字节。
        //当p指向'\0'时循环停止
    {
        count++;
    }
    return count;
}
int main()
{
    char arr[] = "asdff";
    int sz = sizeof(arr) / sizeof(arr[0]);
    printf("%d", My_strlen(arr, sz));//arr代表首元素地址,类型为char*
    return 0;
}

4.2 传值调用和传址调用

传值调用

学习指针的目的使用指针解决问题,那什么问题,非指针不可呢?

例如:写一个函数,交换两个整型变量的值一番思考后,我们可能写出这样的代码:

void Swap1(int x, int y)
{
    int tmp = x;
    x = y;
    y = tmp;
}
int main()
{
    int a = 0;
    int b = 0;
    scanf("%d %d", &a, &b);
    printf("交换前:a=%d b=%d\n", a, b);
    Swap1(a, b);
    printf("交换后:a=%d b=%d\n", a, b);
    return 0;
}

当我们运行代码时会发现,代码并没有达到交换的效果。

该函数进行的是形参的交换,而形参只是实参的一份拷贝,形参的数值改变不会使实参数值发生改变。

传址调用

如何使用函数实现交换呢?

在main函数中将a和b的地址传递给Swap函数,Swap函数面边通过地址间接的操作main函数中的a和b,并达到交换的效果就好了。

void Swap2(int* px, int* py)
{
    int tmp = 0;
    tmp = *px;
    *px = *py;
    *py = tmp;
}
int main()
{
    int a = 0;
    int b = 0;
    scanf("%d %d", &a, &b);
    printf("交换前:a=%d b=%d\n", a, b);
    Swap2(&a, &b);
    printf("交换后:a=%d b=%d\n", a, b);
    return 0;
}

5. 指针与数组

5.1数组名的理解

数组名为首元素的地址。

5.2 冒泡排序

冒泡排序的核心思想就是:两两相邻的元素进行比较。

void bubble_sort(int arr[], int sz) 
{
    int i = 0;
    for (i = 0; i < sz - 1; i++)
    {
        int j = 0;
        for (j = 0; j < sz - i - 1; j++)
        {
            if (arr[j] > arr[j + 1])
            {
                int tmp = arr[j];
                arr[j] = arr[j + 1];
                arr[j + 1] = tmp;
            }
        }
    }
}
int main()
{
    int arr[] = { 3,1,7,5,8,9,0,2,4,6 };
    int sz = sizeof(arr) / sizeof(arr[0]);
    bubble_sort(arr, sz);
    int i = 0;
    for (i = 0; i < sz; i++)
    {
        printf("%d ", arr[i]);
    }
    return 0;
}

注意:一维数组传参,形参部分可以写成数组的形式(本质上传递的仍是数组首元素的地址),也可以写成指针的形式。

6. 二级指针和指针数组

6.1 二级指针

指针变量也是变量,是变量就有地址,那指针变量的地址存放在哪里?

这就是 二级指针 。

6.2 指针数组

指针数组,顾名思义就是存放指针的数组。

指针数组的每个元素都是地址,又可以指向一块区域。

6.3 数组指针

int(*p)[10];

解释:p先和 * 结合,说明p是一个指针变量变量,然后指针指向的是一个大小为10个整型的数组。所以

p是一个指针,指向一个数组,叫数组指针。

这里要注意:[]的优先级要高于* 号的,所以必须加上()来保证p先和*结合。

6.4 二级指针实现指针数组

#include <stdio.h>
int main()
{
    int arr1[] = { 1,2,3,4,5 };
    int arr2[] = { 2,3,4,5,6 };
    int arr3[] = { 3,4,5,6,7 };
    //数组名是数组首元素的地址,类型是int*的,就可以存放在parr数组中
    int* parr[3] = { arr1, arr2, arr3 };
    int i = 0;
    int j = 0;
    for (i = 0; i < 3; i++)
    {
        for (j = 0; j < 5; j++)
        {
            printf("%d ", parr[i][j]);
        }
        
            printf("\n");
    }
    return 0;
}

上述代码模拟出了二维数组的效果,实际上并非完全是二维数组,因为每一行并非是连续的。

7. 函数指针

函数指针变量应该是调用来存放函数地址的,未来通过地址能够调用函数的。

下面是函数指针类型的格式:

了解了函数指针类型,下面尝试一下使用函数指针吧!

#include <stdio.h>
int Add(int x, int y)
{
    return x + y;
}
int main()
{
    int(*pf)(int, int) = Add;
    printf("%d\n", (*pf)(1, 3));//输出结果 4
    printf("%d\n", pf(3, 4));//输出结果 7
    return 0;
}

8. const的使用

const int *p *p不可改变(指针指向的值不可改变),p可以改变(指针可以指向不同地址)

int * const p *p可以改变(指针指向的值可以改变),p不可改变(指针不可指向不同地址)

int const * p *p不可改变(指针指向的值不可改变),p可以改变(指针可以指向不同地址)

总结:const在*左边时 *p不可改变(指针指向的值不可改变),p可以改变(指针可以指向不同地址)。

const在右边时p可以改变(指针指向的值可以改变),p不可改变(指针不可指向不同地址)。

好了,这篇文章结束了!但是指针的学习还没有结束,还有结构体和动态内存方面的知识!

希望大家点赞+收藏,有什么疑问可以评论区讨论呦~~~~~

本文原文来自CSDN博客

© 2023 北京元石科技有限公司 ◎ 京公网安备 11010802042949号