• C语言入门Day_24 函数与指针


    目录

    前言:

    1.指针和数组

    2.函数和指针

    3.易错点

    4.思维导图


    前言:

    我们知道数组是用来存储多个数据的,以及我们可以用指针来指向一个变量。那么我们可以用指针来指向一个数组中的数据么?

    指针除了可以像指向一个变量一样指向一个数组的元素以外,还可以有更灵活的使用方法。

    1.指针和数组

    1.1

    我们先来看看指针如何指向一个数组中的元素

    1. int num_list[5] = {0, 1, 2, 3, 4};
    2. int *p1;
    3. p1 = &num_list[2];
    4. printf("%d\n", *p1);
    5. *p1 = 20;
    6. printf("%d\n", *p1);

    1.定义一个有五个整型元素的数组num_list,以及一个整型指针p1

    int num_list[5] = {0, 1, 2, 3, 4};

    int *p1;

    2.把指针指向数组的第三个元素num_list[2],然后打印这个指针指向的数据,输出2

    p1 = &num_list[2];

    printf("%d\n", *p1);

    3.修改这个指针指向的数据,重新赋值20,然后打印这个指针指向的数据,输出20

    *p1 = 20;

    printf("%d\n", *p1);

    1.2

    可以看到,把指针指向一个数组的特定元素的使用方式,和把指针指向一个变量的使用方式,是一样的

    都是在用指针指向它们的时候:

    第一,需要使用取地址符号&来完成给这个指针变量赋值一个内存地址的过程。

    第二,需要保证指针的类型和它所指向的数组的类型,是一致的。

    1.3

    我们把指针数组的第一个元素的地址赋给指针,然后重新给它赋值,并且打印它。

    1. int num_list2[4] = {11, 22, 33, 44};
    2. int *p1;
    3. p1 = &num_list2[0];
    4. *p1 = 111;
    5. printf("%d\n", num_list2[0]);

    1.首先初始化一个整型指针p1:

    int *p1;

    2.把数组第一个元素的内存地址num_list2[0]赋值给指针p1:

    p1=&num_list2[0];

    1.4

    除了把指针指向一个具体的数组元素,我们还可以把指针指向一整个数组!

    1. int num_list[5] = {10, 20, 30, 40, 50};
    2. int *p1;
    3. p1 = num_list;
    4. printf("%d\n", *p1);
    5. printf("%d\n", *(p1+1));

    1.定义一个有五个元素的整型数组num_list,和一个整型指针p1

    int num_list[5] = {10, 20, 30, 40, 50};

    int *p1;

    2.把指针指向数组num_list

    p1 = num_list;

    3.打印指针指向的值,这时候默认打印数组的第一个值num_list[0]

    printf("%d\n", *p1);

    4.打印指针*(p1+1),这是打印数组的第二个值

    printf("%d\n", *(p1+1));

    1.5

    1. printf("%d\n",*p1);
    2. printf("%d\n",*(p1+1));

     我们注意到,当用指针指向一个数组的时候,*p就是数组的第一个元素*(p+1)就是数组的第二个元素,同理*(p+2)是数组的第三个元素

    为了方便记忆,可以记忆为*(p+0)是数组的第一个元素,*(p+1)是数组的第二个元素,*(p+2)是数组的第三个元素;

    指针后面加的数字,等同于数组的下标

    1.6

    同时,细致的你可能也注意到了指针指向数组的时候,我们是没有使用取地址符&的 

    1. int num_list[5] = {10, 20, 30, 40, 50};
    2. int *p1;
    3. p1 = &num_list[1];
    4. printf("%d\n", *p1);
    5. p1 = num_list;
    6. printf("%d\n", *p1);

    1.数组名num_list

    int num_list[5] = {10, 20, 30, 40, 50};

    2.指针指向单个数组元素的时候有取地址符&

    p1 = &num_list[1];

    3.指针指向整个数组的时候没有取地址符&

    p1 = num_list;

    1.6

     这是因为,数组本质上就是一种指针,这也就是说数组名存储的就是一个内存地址,因此把指针指向数组名的过程,也就是把数组名的内存地址赋值给一个指针的过程。

    已经是内存地址了,当然就不需要再用取地址符

    现在再看*(p+0)是数组的第一个元素,*(p+1)是数组的第二个元素是不是更清楚了,其实我们也可以写成*(num_list+0)*(num_list+1)

    由于数组名num_list和指针p都存储的是一个内存地址,所以这两者是等价的。

     1.7

    以及指针和数组都需要注意的事也类似,就是数组是有边界的,不能越界访问

    比如数组的长度是10,*(p1+20)num_list[20]都是会报错的。

    我们学过函数,函数通过参数来接收外界(调用函数的地方)的输入;

    通过返回值来向外界(调用函数的地方)输出。

    当指针作为函数参数的时候,给函数的这种输入输出机制增加了一个例外

    2.函数和指针

     2.1

    由于指针指向的一个数据的实际存储的内存地址,因此在函数中对指针指向的数据的修改,是会直接修改到函数外部的变量数据。 

    也就是说,指针作为函数参数的时候,即使是在函数内部对指针的数据进行修改,也会穿透到函数外部。

    如果说一般函数参数的数据改变,是在函数内部的暂时的改变;

    那么指针函数参数的数据改变,就是一种“永久的改变”

    2.2

    我们来看看指针作为函数参数时候的使用:

    1. void AddThree(int *p1){
    2. *p1 = *p1 + 3;
    3. }
    4. int main(){
    5. int number_1 = 10;
    6. int *p;
    7. p = &number_1;
    8. AddThree(p);
    9. printf("%d\n", *p);
    10. }

     1.定义一个无返回值的函数,且这个函数的参数是一个指针

    void AddThree(int *p1){

    *p1 = *p1 + 3;

    }

    2.这是用指针做参数时候的语法

    void AddThree(int *p1){         *

    3.在main函数中调用这个函数

    AddThree(p);

    4.需要特别注意的是,传入的是指针(内存地址),而不是指针指向的数据(*p)

    AddThree(p);

    5.输出计算以后的结果13

    printf("%d\n", *p);

    2.3

    可以看到,虽然函数AddThree()并没有返回值,但是变量number_1的值永久的改变了,这是为什么呢,答案还要回到内存地址上面。

    一般的函数参数,只是把一个数据的复制品传入了函数,就像我抄写了一份数据交给你(函数),原始数据还在我(函数外部)这里。

    你在函数内部对数据的修改,作用于这个数据的复制品,原始数据是不会改变的。

    但是指针作为函数参数的时候,传递给函数的是一个内存地址,你修改了这个内存地址上面的数据,那就是永久的修改。

    相当于我把原始数据给你了,而不是给你一个数据的复制品,你在函数内部的操作是直接作用于原始数据的。

    2.4 

    我们再来看看这张图片,是不是理解会深一点,我们用指针就是在用内存地址,通过对这个内存地址里存储的数据的改变,会永久的修改这个数据;因为它就是原始数据存储的地方

    现在我们可能能够更能体会为什么说指针是C语言中很灵活很底层的机制了。

    因为有些机制是基于指针的,比如数组数组名

    有些机制在使用指针的时候,会方便快捷,甚至打破规则的操作原始数据,比如函数的指针参数

    3.易错点

    数组本质上也是一个指针

    当指针指向整个数组时,不需要到取地址运算符&

    但是指针指向数组的某个具体元素时,要用到取地址运算符&

    对于一个指向数组的指针p,*(p+0)是数组的第一个元素,*(p+1)是数组的第二个元素,*(p+2)是数组的第三个元素;但是*p+1 是先取出数组第一个元素的值再加1。

    4.思维导图

    最后我想说的是:

     在撰写这篇文章时,我参考了《白纸编程》这个app的观点和思想,我要感谢他们对我的启发和帮助。

  • 相关阅读:
    Java学习笔记——面向对象
    集美大学 - 2840 - 实验1
    git使用ssh和https方式有什么区别
    Linux系统下数据同步服务RSYNC
    10. SAP ABAP OData 服务如何支持修改(Update)操作
    Linux开发工具(4)——Makefile
    c++基础知识-运算符与表达式1(详解)
    OpenCV的Mat对象如何定义数组?
    策略模式-实战
    PowerBI 8月更新,数据标签条件格式
  • 原文地址:https://blog.csdn.net/fantastic_little/article/details/133158018