11.3 指针和函数

news2024/11/18 9:38:44

11.3 指针和函数

本节必须掌握的知识点:

        指针作为函数的参数

        数组作为函数的参数

        指针作为函数的返回值

在C语言中,指针的一个重要作用就是作为函数参数使用,本节将介绍这一重要作用。

11.3.1 指针作为函数的参数

实验一百一十三:通过指针间接地修改身高

在VS中新建项目11-3-1.c:

/*

   通过指针间接地修改身高

*/

#include <stdio.h>

#include <stdlib.h>

void cmp(int* height)

{

    if (*height < 178)

    {

        *height = 178;

    }

}

int main(void)

{

    int n_XiaoMing = 176;

    //如果小明身高小于178,则该为178

    cmp(&n_XiaoMing);

    printf("小明的身高:%d\n", n_XiaoMing);

    system("pause");

    return 0;

}

●运行结果:

小明的身高:178

请按任意键继续. . .

●代码分析:

首先分析main函数:定义了一个变量n_XiaoMing;并给n_XiaoMing赋初始值176;紧接着执行cmp函数,cmp(&n_XiaoMing);传递给cmp函数的实参是n_XiaoMing 的地址,形参int* height接收,此时指针*height指向了n_XiaoMing。这里*height可以看作是n_XiaoMing的别名。

若*height的值小于178,就执行*height = 178;这个操作,也就是把178赋值给*height,也相当于对n_XiaoMing进行赋值。如图11-4所示:

图11-4 函数调用中指针的传递

   

我们可以通过分析反汇编代码验证:

void cmp(int* height)

{

00401820  push        ebp 

00401821  mov         ebp,esp 

 

    if (*height < 178)

00401848  mov         eax,dword ptr [height]  ;取出身高

0040184B  cmp         dword ptr [eax],0B2h 

00401851  jge         cmp+3Ch (040185Ch) 

    {

        *height = 178;

00401853  mov         eax,dword ptr [height]  ;取出身高

00401856  mov         dword ptr [eax],0B2h   ;修改身高为178

    }

}

0040185C  pop         edi 

0040185D  pop         esi 

0040185E  pop         ebx 

0040185F  add         esp,0C0h 

00401865  cmp         ebp,esp 

00401867  call        __RTC_CheckEsp (0401221h) 

    }

}

0040186C  mov         esp,ebp 

0040186E  pop         ebp 

0040186F  ret 

int main(void)

{

 

    int n_XiaoMing = 176;

004018C2  mov         dword ptr [n_XiaoMing],0B0h 

    //如果小明身高小于178,则该为178

    cmp(&n_XiaoMing);

004018C9  lea         eax,[n_XiaoMing]  堆栈传递实参- n_XiaoMing变量地址

004018CC  push        eax 

004018CD  call        _cmp (040132Ah) 函数调用

004018D2  add         esp,4 

    printf("小明的身高:%d\n", n_XiaoMing);

004018D5  mov         eax,dword ptr [n_XiaoMing] 

004018D8  push        eax 

004018D9  push        offset string "\xd0\xa1\xc3\xf7\xb5\xc4\xc9\xed\xb8\xdf:%d\n" (0407B30h) 

004018DE  call        _printf (040104Bh) 

004018E3  add         esp,8 

    system("pause");

004018E6  mov         esi,esp 

    system("pause");

004018E8  push        offset string "pause" (0407B44h) 

004018ED  call        dword ptr [__imp__system (040B168h)] 

004018F3  add         esp,4 

004018F6  cmp         esi,esp 

004018F8  call        __RTC_CheckEsp (0401221h) 

    return 0;

004018FD  xor         eax,eax 

}

    cmp(int* height)函数调用时传递的实参为0x0054fcf4为变量n_XiaoMing的地址,即int* height形参0x0054fc20地址处存储的值0x0054fcf4。

名称

类型

&n_XiaoMing

0x0054fcf4 {176}

int *

&height

0x0054fc20 {0x0054fcf4 {176}}

int * *

00401853  mov eax,dword ptr [height]  ;取出身高

00401856  mov dword ptr [eax],0B2h   ;修改身高为178

    注意这两条语句,第一条语句先从height地址(0x0054fc20)处存储的值0x0054fcf4存入eax。第二条语句再将修改后的值178存入eax(0x0054fcf4)地址处。

 

总结

如果指针作为函数的参数,修改参数就相当于对传进来的实参进行修改。传入的是指针,但操作的是指针指向的对象进行操作,比如代码中:int* height指向的对象是&n_XiaoMing,间接的对n_XiaoMin进行操作。我们将函数的地址传参称为引用调用,被调函数可以直接修改主调函数的原值。

实验一百一十四:将用户输入的整数进行交换

在VS中新建项目11-3-2.c:

/*

   将用户输入的整数进行交换

*/

#include <stdio.h>

#include <stdlib.h>

void swap(int* px, int* py)

{

    int temp = *px;

    *px = *py;

    *py = temp;

}

int main(void)

{

    int n_x;

    int n_y;

    printf("请输入两个整数:\n");

    printf("第一个整数n_x:\t");

    scanf_s("%d", &n_x);

    printf("第二个整数n_y:\t");

    scanf_s("%d", &n_y);

    swap(&n_x, &n_y);

    printf("交换后的结果:\n");

    printf("n_x:%d\n", n_x);

    printf("n_y:%d\n", n_y);

    system("pause");

    return 0;

}

●运行结果:

请输入两个整数:

第一个整数n_x:  1

第二个整数n_y:  2

交换后的结果:

n_x:2

n_y:1

请按任意键继续. . .

●代码分析:

swap(&n_x,&n_y);

传入实参&n_x,&n_y;

实现两个数进行交换的动作在swap函数里处理的,

void swap(int* px,int* py)  

{

       int temp = *px;  //定义一个变量temp,存放*px的数据

    *px = *py;       //把*py的数据赋值给*px,此时*px 的数据与*py的数据相同

       *py = temp;    //temp存放的是*px的数据,把temp的数据给*py,此时完成交换。

}

11.3.2 数组作为函数的参数

在前面我们已经介绍了,指针与数组之间的互相转换,这里将介绍把数组的内容传递给函数,那么该如何传递呢?我们知道数组名就是地址,因此,只要把数组名及数组的长度传给函数就可以了,我们来做一个实验。

实验一百一十五:函数使用数组名作为形参

在VS中新建项目11-3-3.c:

/*

   函数使用数组名作为形参

*/

#include <stdio.h>

#include <stdlib.h>

void Array(int* pArr, int len)

{

    for (int i = 0; i < len; i++)

    {

        printf("arr[%d] = %d\n", i, *(pArr + i));

    }

}

int main(void)

{

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

    Array(arr, 5);

    system("pause");

    return 0;

}

●运行结果:

arr[0] = 1

arr[1] = 2

arr[2] = 3

arr[3] = 4

arr[4] = 5

请按任意键继续. . .

●代码分析:

Array(arr,5);//这是传递的arr数组名,也就是数组起始地址,5是数组的长度。

void Array(int* pArr,int len) //使用int* pArr接收数组地址,int len = 5

{

    for( int i = 0;i < len; i++)  //遍历数组

    {

//通过数组首地址+偏移的方式获取到数组中每一个元素。

        printf("arr[%d] = %d\n",i,*(pArr+i));

    }

}

实验一百一十六:函数使用指针作为形参

在VS中新建项目11-3-4.c:

/*

   函数使用指针作为形参

*/

#include <stdio.h>

#include <stdlib.h>

void sortArr(int* pArr, int len)

{//控制循环轮数(数组长度-1)轮

    for (int i = 0; i < len - 1; i++)

    {//控制每轮次数//(数组长度-1)-当前轮数

        for (int j = 0; j < len - i - 1; j++)

        {

            if (*(pArr + j) > *(pArr + j + 1))

            {

                //使用冒泡排序算法

                int temp = *(pArr + j);

                *(pArr + j) = *(pArr + j + 1);

                *(pArr + j + 1) = temp;

            }

        }

    }

}

void printArr(int* pArr, int len)

{

    for (int i = 0; i < len; i++)

    {

        printf("arr[%d]=%d\n", i, *(pArr + i));

    }

}

int main(void)

{

    int arr[10] = { 4,2,8,11,3,6,9,33,23,15 };

    sortArr(arr, 10);

    printArr(arr, 10);

    system("pause");

    return 0;

}

●运行结果:

arr[0]=2

arr[1]=3

arr[2]=4

arr[3]=6

arr[4]=8

arr[5]=9

arr[6]=11

arr[7]=15

arr[8]=23

arr[9]=33

请按任意键继续. . .

●代码解析:

sortArr函数是对数组进行从小到大的顺序排序;

printfArr函数是对数组进行遍历输出的操作;

sortArr函数使用了冒泡排序算法,此处冒泡排序的算法请读者尝试理解,我们将在第十四章详细讲解。

思考

请读者思考sortArr函数和printfArr函数中的形参int* pArr是否与数组名pArr[]形式的形参是否完全等价呢?

       数组名pArr[]表示的是数组的起始地址,即第0个数组元素pArr[0]的地址。

       而int* pArr指针存储的是数组起始地址,使用前需要使用解运算符‘*’取出数组元素地址处的值。二者的区别如图11-5所示:

图11-5 指针形参与数组名形参

11.3.3 指针作为函数的返回值

在C语言里,允许函数的返回值是一个指针,我们称这样的函数为指针函数。

指针函数: 当一个函数声明其返回值为一个指针时,实际上就是返回一个地址给调用函数,以用于需要指针或地址的表达式中。

格式:

类型说明符* 函数名(参数)

实验一百一十七:指针作为函数的返回值

在VS中新建项目11-3-5.c:定义一个函数pFun,用来比较两个数的大小,并返回比较的结果。

/*

   指针作为函数的返回值

*/

#include <stdio.h>

#include <stdlib.h>

int* pFun(int* px, int* py)//定义了一个指针函数,接收传入的实参

{

    if (*px >= *py)//比较两个数的大小

        return px;

    else

        return py;

}

int main()

{

    int x, y, *p;

    printf("请输入两个整数:\n");

    scanf_s("%d", &x);

    scanf_s("%d", &y);

    p = pFun(&x, &y);//传入x,y的地址

    //p是接收pFun函数的返回结果,此时返回的是一个地址。

    printf("%d\n", *p);//使用*p是打印该地址里指向的值。

    system("pause");

    return 0;

}

●运行结果:

请输入两个整数:

1

2

2

请按任意键继续. . .

    ●代码分析:

    p = pFun(&x, &y);//传入x,y的地址

int* pFun(int* px, int* py)//定义了一个指针函数,接收传入的实参,函数返回值类型int*

    return px;或return py;//返回一个指针。

提示

【scanf函数遗留的问题:

在刚接触scanf函数时,我们已经在使用“&”取地址符,在接触时,由于没有介绍指针这个概念,此处我们来理解scanf函数是怎么运作的。

scanf函数是为主调函数中定义的对象保存的值,假如它只接收到变量的值,是无法进行保存的,scanf函数接收的是指针(指向对象的地址),由该指针指向的对象保存我们从键盘上键入的值。

scanf("%d",&x);

004010B5   lea         eax,[ebp-4]

首先读取[ebp-4]里的内容给eax,也就是ebp-4的地址

004010B8   push        eax

将地址压入堆栈

004010B9   push        offset string "%d" (00420020)

%d压入堆栈

004010BE   call        scanf (0040da90)

调用scanf函数

004010C3   add         esp,8

最后以把 存放该地址指向的内容以%d的形式输出。这就是scanf函数。】

上述代码中,我们使用的函数是scanf_s,这是scanf函数的安全形式,scanf_s函数只有当参数是字符串时,需要再增加一个存放字符串缓冲区长度的参数,防止缓冲区大小不足时发生溢出。如果参数是其他数据类型,则二者参数不变。下面是两个函数的原型:

int scanf(const char * restrict format,...); 函数调用scanf("%d %d",&a,&b);

int scanf_s(const char * restrict format, . . . ); 函数调用scanf_s("%c", &c, 1);

 

总结

指针是灵活多变的,但只要我们知道它每一步都去做了什么,哪里改变了,为什么改变,围绕这三个问题展开分析,只要想明白这些,那么指针对你来说就不是什么难事了。每当我们对于指针认识模糊的时候,请将“指针”两个字改为“地址”,再次尝试怎样理解。

练习

1、下面代码实现:调用_A111改变main里的x的值。

int _A111(int* x){

    }

void main(){

int x = 0;

//调用_A111

_A111();

}

  1. 把数组 arr[5]={1,2,3,4,5};作为函数的参数传递,并逆序输出数组元素。
  2. 输入10个数,存入一个数组,并找出数组中的最大值。(请用指针相关的知识)。
  3. 判断某个值是否在该数组中(数组已经排好序):{2,3,6,8,11,13,16,17,20,22}( 要求:请使用指针相关知识,尽可能高效,提示:二分查找)。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1721165.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Spring事务管理进阶-rollbackFor propagation

黑马程序员JavaWeb开发教程 文章目录 一、rollbackFor二、propagation2.1 事务传播行为2.2 场景 一、rollbackFor 默认情况下&#xff0c;只有初选RuntimeException才会回滚异常。roolbackFor属性用于控制出现何种异常类型&#xff0c;回滚事务。 二、propagation 用来配置事…

【蓝桥杯国赛】动态规划

“动态规划”在蓝桥杯中的出题类型&#xff0c;主要为两种&#xff0c; 要格外注意&#xff0c;每一次 dp 的迭代更新&#xff0c;都是针对于当前位置下的“所有情况”进行的&#xff0c; 应着眼于当前位置的每一种情况。 类型一&#xff1a;一共有多少种情况&#xff1f; 1…

海外媒体通稿:9个极具创意的旅游业媒体推广案例分享-华媒舍

如今&#xff0c;旅游业正迅速发展&#xff0c;媒体推广成为吸引游客的关键。为了更好地展示旅游目的地&#xff0c;许多创意而富有创新的媒体推广策略应运而生。本文将介绍九个极富创意的旅游业媒体推广案例&#xff0c;为广大从业者带来灵感和借鉴。 1. 视频系列&#xff1a;…

参数设置错误导致的 OOM

参数设置错误导致的 OOM 前言事故分析事故原因事故复盘 前言 2024 年 5 月 10 日 14 时 19 分&#xff0c;C 公司开发人员向 A 公司开发人员反映某开放接口从 2024 年 5 月 10 日 14 时许开始无法访问和使用。该系统为某基础数据接口服务&#xff0c;基于 HTTP 协议进行通信。…

【吊打面试官系列】Java高并发篇 - 什么是线程调度器(Thread Scheduler)和时间分片(TimeSlicing)?

大家好&#xff0c;我是锋哥。今天分享关于 【什么是线程调度器(Thread Scheduler)和时间分片(TimeSlicing)&#xff1f;】面试题&#xff0c;希望对大家有帮助&#xff1b; 什么是线程调度器(Thread Scheduler)和时间分片(TimeSlicing)&#xff1f; 线程调度器是一个操作系统服…

【从零开始部署SAM(Segment Anything Model )大模型 3 Ubuntu20 离线部署 C++】

这里是目录 总览环境配置模型准备Moble SAM onnx模型获取Moble SAM pre onnx模型获取 运行cmakelist 运行结果 总览 相比于使用python离线部署SAM大模型&#xff0c;C要麻烦的多&#xff0c;本篇的部署过程主要基于项目&#xff1a;https://github.com/dinglufe/segment-anyth…

数据结构与算法02-排序算法

介绍 排序算法是计算机科学中被广泛研究的一个课题。历时多年&#xff0c;它发展出了数十种算法&#xff0c;这些 算法都着眼于一个问题&#xff1a;如何将一个无序的数字数组整理成升序&#xff1f;先来学习一些“简单排序”&#xff0c;它们很好懂&#xff0c;但效率不如其他…

Git系列:rev-parse 使用技巧

&#x1f49d;&#x1f49d;&#x1f49d;欢迎莅临我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:「stormsha的主页」…

vmware workstation 17.0.0 ubuntu删除快照导致无法启动的问题打不开磁盘xxxxxxx或它所依赖的某个快照磁盘

在使用vmware workstation的时候 在我删除多余的快照的时候&#xff0c;发现删除快照后打不开虚拟机了&#xff0c; 提示&#xff1a; 打不开此虚拟磁盘的父磁盘打不开磁盘“D:\Virtual Machines\Ubuntu 64 位\Ubuntu 64 位-000003.vmdk”或它所依赖的某个快照磁盘。模块“Dis…

曾巩,散文的艺术与哲思

曾巩&#xff0c;字子固&#xff0c;世称南丰先生&#xff0c;南丰&#xff08;今江西&#xff09;人&#xff0c;生于北宋真宗天禧三年&#xff08;公元1019年&#xff09;&#xff0c;卒于北宋元丰六年&#xff08;公元1083年&#xff09;&#xff0c;享年64岁。他是中国北宋…

低功耗蓝牙模块在便携式医疗设备上的应用前景

随着科技的不断发展&#xff0c;医疗设备的便携性和智能化已经成为了一种趋势。在这个背景下&#xff0c;低功耗蓝牙模块(Bluetooth Low Energy,简称BLE)作为一种先进的无线通信技术&#xff0c;正逐渐在便携式医疗设备中发挥着越来越重要的作用。本文美迅物联网MesoonRF将探讨…

Debian和ubuntu 嵌入式的系统的 区别

随着开源操作系统的日益流行&#xff0c;Debian和Ubuntu这两个基于Linux的发行版本成为了众多开发者和系统管理员的首选。它们各自拥有独特的优势和特点&#xff0c;那么&#xff0c;在选择时&#xff0c;哪一个更适合你呢&#xff1f;接下来&#xff0c;我们将深入探讨两者的关…

计算机网络——如何保证 TCP 传输的可靠性

TCP 是传输层上的协议&#xff0c;它是可靠的&#xff0c;面向连接的。 概括 1. 设置传输格式&#xff0c;包括分为 TCP 段、使用校验和、使用序列号 2. 数据丢失之后的重传&#xff0c;超时重传、快速重传、SACK 选择确认、D-SACK 重复选择确认 3. 流量控制&#xff0c;控…

python编程:SQLite 管理图片数据库

在本博客中&#xff0c;我们将介绍如何使用 wxPython 和 sqlite3 模块构建一个 GUI 应用程序&#xff0c;该程序可以遍历指定文件夹中的所有图片&#xff0c;并将其信息存储到 SQLite 数据库中。 C:\pythoncode\new\InputImageOFFolderTOSqlite.py 项目简介 我们的目标是创建…

linux可观测性ebpf(一) ----------- 环境搭建

参考书籍 开发环境 Ubuntu 18.04.6 LTS (GNU/Linux 5.4.0-150-generic x86_64) 1.1 下载内核源码 cd /usr/src/ sudo git clone -b v5.4 https://github.com/torvalds/linux.git1.2 下载书中代码 git clone https://github.com/bpftools/linux-observability-with-bpf1.3 编…

Java常用API(三)

一、Arrays类 1.定义 Arrays是一个用于操作数组的工具类。 2.常用方法 1.toString方法 public class Demo {public static void main(String[] args) {//toString 将数组变成字符串int[] arr {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};System.out.println(Arrays.toString(arr));…

arduino 与 nodeMcu 之间的通信

一、前言 当在 arduino 板子处理好了传感器的数据应该发送给远程服务器这时候就需要用 nodeMcu 了&#xff0c;但是怎么把 arduino 的数据发送到 nodeMcu 呢&#xff0c;这就是本文要实现的。 两个板子之间通信很简单&#xff0c;直接使用 arduino IDE 提供的 Serial.println…

Java web应用性能分析之【压测工具ab】

常用的性能测试工具有&#xff1a;JMeter、loadRunner、ab&#xff1b;对于开发人员来说用的多的是免费的Jmeter和ab&#xff0c;对于测试来说可能用收费的商业软件loadRunner多。在这里我们就说说ab压测工具&#xff0c;因为ab基本满足web接口测试要求&#xff0c;jmeter后面再…

详解生成式人工智能的开发过程

回到机器学习的“古老”时代&#xff0c;在您可以使用大型语言模型&#xff08;LLM&#xff09;作为调优模型的基础之前&#xff0c;您基本上必须在所有数据上训练每个可能的机器学习模型&#xff0c;以找到最佳&#xff08;或最不糟糕&#xff09;的拟合。 开发生成式人工智能…

学习网站地址汇总

本文用于记录学习路上遇到问题&#xff0c;各位大神的分享笔记&#xff0c;内容包括数据库的安装卸载&#xff0c;基础使用&#xff0c;sql语句编写&#xff0c;计算机网络知识等&#xff0c;属于知识大杂烩 1.动态行转列&#xff1a;https://www.cnblogs.com/gaizai/p/375329…