百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术流 > 正文

C语言指针与函数(c语言指针函数和函数指针的区别)

citgpt 2024-07-10 14:45 10 浏览 0 评论

C语言指针函数

C语言指针函数就是函数中用到了指针的函数,主要是有以下两种方式

  • 以指针为参数的函数
  • 以指针为返回值的函数

指针做函数参数

C语言指针与函数(c语言指针函数和函数指针的区别)

学习函数的时候,讲了函数的参数都是值拷贝,在函数里面改变形参的值,实参并不会发生改变。如下图:

每个函数都有一个独立的栈区,在函数传参的过程中,是把实参的值拷贝给形参,修改形参的值并不能作用到实参。如果想要通过形参改变实参的值,就需要传入实参的地址,可以通过寻址方式作用到实参上,如下图:

想要修改实参的值,需要传入实参的地址,故想要修改该指针变量的指向需要传入指针变量的地址,也就是二级指针。多级指针中也是依次类推,数据结构中常有二级指针传参。

示例程序| 传参的方式动态申请一维数组

传参的方式修改一级指针的值,需要传入二级指针,通过寻址的方式修改一级指针,如下测试代码:

#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
void createArray(int** parray, int arrayNum) 
{
  *parray = (int*)calloc(arrayNum,sizeof(int));
  assert(parray);
}

int main()
{
  int* p = NULL;
  createArray(&p, 3);
  for (int i = 0; i < 3; i++) 
  {
    printf("%d\t", p[i]);
  }
  printf("\n");
  return 0;
}

运行结果如下:

示例程序| 封装函数操作数组

通常在封装函数操作数字类(int ,float,double,…)数组一定要传入数组长度,操作字符串类通常不需要,因为字符串存在字符串结束标记。例如封装遍历数组函数和字符串比较函数,代码如下:

#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
#include <stdbool.h>
//等效void printArray(int array[], int arrayNum) 
void printArray(int* array, int arrayNum) 
{
  for (int i = 0; i < arrayNum; i++) 
  {
    printf("%d\t", array[i]);
  }
  printf("\n");
}
int myStrcmp(const char* str1, const char* str2) 
{
  int i = 0;
  int j = 0;
  //字符串比较从左往右比,找到不同的字符即可得到比较结果
  while (str1[i] == str2[j]&&str1[i] != '\0')
  {
    i++;
    j++;
  }
  return str1[i] - str2[j];
}
int main()
{
  int array[5] = { 1,2,3,4,5 };
  printArray(array, 5);
  printf("%d\n", myStrcmp("string1", "string")>0);
  printf("%d\n", myStrcmp("string", "string")==0);
  printf("%d\n", myStrcmp("string", "string1")<0);
  return 0;
}

运行结果如下:

当然比较函数你也可以返回0,-1,1,只需要在字符串比较函数中分类讨论下即可。

指针做函数返回值

指针当做函数返回值和普通函数一样,只是返回值类型不同而已,既然返回是一个指针,*指针等效变量,故*函数调用也可以等效变量。把指针当做函数返回值注意项:

  • 不要返回临时变量的地址
  • 可以返回动态申请的空间的地址
  • 可以返回静态变量和全局变量的地址

当函数返回临时变量的地址时,地址中存储的数据随着函数调用完会被回收掉,导致获取垃圾值。如下测试代码:

#include <stdio.h>
int* testFunc() 
{
  int number = 1314;
  return &number;
}
int main()
{
  int* result=testFunc();
  //第一次数据做了保留
  printf("%d\n", *result);
  //后续数据被回收了,垃圾值
  printf("%d\n", *result);
  printf("%d\n", *result);
  return 0;
}

运行结果如下:

在vs开发工具中会友善给予提醒,希望看到这类提醒当做错误处理,及时改善,友善提醒如下:

示例程序| 返回值的方式动态申请一维数组

可以返回动态申请的空间的地址,堆区内存需要调用free函数手动释放,如下测试代码:

#include <stdio.h>
#include <stdlib.h>
int* createArray(int arrayNum) 
{
  int* p = (int *)calloc(arrayNum, sizeof(int));
  return p;
}
int main()
{
  int* p = NULL;
  p = createArray(3);
  for (int i = 0; i < 3; i++) 
  {
    printf("%d\t", p[i]);
  }
  free(p);
  p = NULL;
  return 0;
}

运行结果如下:

示例程序| 用字符串初始化堆区内存并返回首地址

其实和数字类的操作没什么太大区别,唯一要注意的是字符串申请统计长度用strlen,申请是可见长度加1,拷贝赋值用strcpy完成,如下测试代码:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
char* createArray(const char* str) 
{
  //申请长度是可见度长度+1
  unsigned int length = strlen(str)+1;
  char* p = (char *)calloc(length, sizeof(int));
  assert(p);
  //不能直接 p=str,语法没问题但是意义不同
  strcpy(p, str);
  return p;
}
int main()
{
  char* pstr = NULL;
  pstr = createArray("coolmoying");
  puts(pstr);
  free(pstr);
  pstr = NULL;
  return 0;
}

运行结果如下:

C语言函数指针

什么是函数指针

如果在程序中定义了一个函数,那么在运行时系统就会为这个函数代码分配一段存储空间,这段存储空间的首地址称为这个函数的地址。获取函数地址有以下两种方式:

  • 函数名
  • &函数名

既然是地址我们就可以定义一个指针变量来存放,这个指针变量就叫作函数指针变量,简称函数指针。函数指针的唯一作用就是调用函数,函数指针没有++和 –运算

如何创建函数指针

函数返回值类型 (*指针变量名) (函数参数列表);

简单来说一句话,用(*变量名) 替换函数名,剩下的照抄即可,形参名可写可不写就是函数指针变量。如下函数的函数指针创建:

如何通过函数指针调用函数

函数指针可以通过不同的初始化方式,调用除了函数名不同,其他类型相同的所有函数。调用方式有以下两种:

  • 直接函数指针名替换函数名去调用函数
  • (*函数指针)替换函数名的方式去调用函数

推荐使用第一种方式,代码看起来比较简单。如下测试代码:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
void test() 
{
  printf("Test\n");
}
void test2() 
{
  printf("Test2\n");
}
int Max(int a, int b) 
{
  return a > b ? a : b;
}
void printArray(int(*p)[3], int row, int cols) 
{
  for (int i = 0; i < row; i++) 
  {
    for (int j = 0; j < cols; j++) 
    {
      printf("%d ", p[i][j]);
    }
    printf("\n");
  }
}
int main()
{
  //创建函数指针变量
  void (*pTest)() = NULL;
  int(*pMax)(int a, int b) = NULL;
  //参数名可省略
  void (*pprint)(int(*)[3], int, int) = NULL;
  //函数指针赋值
  //两种方式即可
  pTest = test;
  pTest = &test;
  pMax = Max;
  pprint = printArray;
  //函数指针变量调用函数
  //两种方式即可
  pTest();
  (*pTest)();
  printf("%d\n",pMax(1, 2));
  int array[2][3] = { 1,2,3,4,5,6 };
  pprint(array, 2, 3);
  //调用除了函数名不同,其他类型相同的所有函数
  pTest = &test2;
  pTest();
  return 0;
}

运行结果如下:

回调函数

回调函数就是以函数指针作为某个函数的参数,函数指针比较重要的应用就是回调函数,在Windows SDK,多线程,事件处理中大量用到回调函数。函数指针变量可以作为某个函数的参数来使用的,回调函数就是一个通过函数指针调用的函数。简单讲:回调函数是由别人的函数执行时调用你实现的函数。通俗的讲:你到一个商店买东西,没有货,留给店员电话,有货了,打电话给你,然后你去取货。在这个例子里,你的电话号码就叫回调函数,你把电话留给店员就叫登记回调函数,店里后来有货了叫做触发了回调关联的事件,店员给你打电话叫做调用回调函数,你到店里去取货叫做响应回调事件。如下测试代码:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
void get() 
{
  printf("取货成功!!!\n");
}
void wait() 
{
  printf("等待售货员电话!...\n");
}
void salesperson(bool flag, void(*Doing)()) 
{
  if (flag == true)  //有货 
  {
    printf("通知取货\n");
    Doing();
  }
  else         //无货
  {
    printf("无货\n");
    Doing();
  }
}
int main()
{
  //通常回调函数有关联的事件
  //这里简单用有无货物来做
  salesperson(false, wait);
  salesperson(true, get);
  return 0;
}

通常salesperson是第三方封装好的,我们只需要实现salesperson函数指针,通过salesperson去调用自己的函数,通常别人设计的回调函数都会绑定事件,目前初步接触了解下。运行结果如下:

C语言万能指针充当函数指针

万能指针充当函数指针使用前必须要强制类型转换,函数指针的类型就是去掉变量名即可 ,如下测试代码:

#include <stdio.h>
#include <stdlib.h>
void test() 
{
  printf("调用成功!!!\n");
}
int main()
{
  void* p = test;
  //正常指针调用:p();
  //test类型: void(*)()
  //强转语法:  (类型)(表达式)
  ((void(*)())p)();
  return 0;
}

运行结果如下:

复杂函数指针解析

右左法则

首先找到标识符,然后往右看,再往左看,每当遇到圆括号时,就应该调转阅读方向,一旦解析完圆括号里面的所有东西,就跳出圆括号,重复这个过程直到整个声明解析完毕。

示例1| int (*func)(int *p)

首先找到那个标识符,就是func,它的外面有一对圆括号,而且左边是一个*号,这说明func是一个指针,然后跳出这个圆括号,先看右边,也是一个圆括号,这说明(*func)是一个函数,而func是一个指向这类函数的指针,就是一个函数指针,这类函数具有int*类型的形参,返回值类型是 int。

示例2| int (*func)(int *p, int (*f)(int*))

func被一对括号包含,且左边有一个*号,说明func是一个指针,跳出括号,右边也有个括号,那么func是一个指向函数的指针,这类函数具有int *和int (*)(int*)这样的形参,返回值为int类型。再来看一看func的形参int (*f)(int*),类似前面的解释,f也是一个函数指针,指向的函数具有int*类型的形参,返回值为int。

示例3| int (*func[5])(int *p)

func右边是一个[]运算符,说明func是一个具有5个元素的数组,func的左边有一个*,说明func的元素是指针,要注意这里的*不是修饰 func的,而是修饰func[5]的,原因是[]运算符优先级比*高,func先跟[]结合,因此*修饰的是func[5]。跳出这个括号,看右边,也是一对圆括号,说明func数组的元素是函数类型的指针,它所指向的函数具有int*类型的形参,返回值类型为int。

示例4| int (*(*func)[5])(int *p)

func被一个圆括号包含,左边又有一个*,那么func是一个指针,跳出括号,右边是一个[]运算符号,说明func是一个指向数组的指针,现在往左看,左边有一个*号,说明这个数组的元素是指针,再跳出括号,右边又有一个括号,说明这个数组的元素是指向函数的指针。总结一下就是:func是一个指向数组的指针,这个数组的元素是函数指针,这些指针指向具有int*形参,返回值为int类型的函数。

示例5| int (*(*func)(int *p))[5]

func是一个函数指针,这类函数具有int*类型的形参,返回值是指向数组的指针,所指向的数组的元素是具有5个int元素的数组。

示例6| int (*(*(*func)(int *))[5])(int *)

func是一个函数指针,这类函数的返回值是一个指向数组的指针,所指向数组的元素也是函数指针,指向的函数具有int*形参,返回值为int。

实际当中,需要声明一个复杂指针时,如果把整个声明写成上面所示的形式,对程序可读性是一大损害。应该用typedef来对声明逐层分解,增强可读性,如果对typedef不懂的,后续讲解。

客观请留步

如果阁下正好在学习C/C++,看文章比较无聊,不妨关注下关注下小编的视频教程,通俗易懂,深入浅出,一个视频只讲一个知识点。视频不深奥,不需要钻研,在公交、在地铁、在厕所都可以观看,随时随地涨姿势。

相关推荐

js中arguments详解

一、简介了解arguments这个对象之前先来认识一下javascript的一些功能:其实Javascript并没有重载函数的功能,但是Arguments对象能够模拟重载。Javascrip中每个函数...

firewall-cmd 常用命令

目录firewalldzone说明firewallzone内容说明firewall-cmd常用参数firewall-cmd常用命令常用命令 回到顶部firewalldzone...

epel-release 是什么

EPEL-release(ExtraPackagesforEnterpriseLinux)是一个软件仓库,它为企业级Linux发行版(如CentOS、RHEL等)提供额外的软件包。以下是关于E...

FullGC详解  什么是 JVM 的 GC
FullGC详解 什么是 JVM 的 GC

前言:背景:一、什么是JVM的GC?JVM(JavaVirtualMachine)。JVM是Java程序的虚拟机,是一种实现Java语言的解...

2024-10-26 08:50 citgpt

使用Spire.Doc组件利用模板导出Word文档
  • 使用Spire.Doc组件利用模板导出Word文档
  • 使用Spire.Doc组件利用模板导出Word文档
  • 使用Spire.Doc组件利用模板导出Word文档
  • 使用Spire.Doc组件利用模板导出Word文档
跨域(CrossOrigin)

1.介绍  1)跨域问题:跨域问题是在网络中,当一个网络的运行脚本(通常时JavaScript)试图访问另一个网络的资源时,如果这两个网络的端口、协议和域名不一致时就会出现跨域问题。    通俗讲...

微服务架构和分布式架构的区别

1、含义不同微服务架构:微服务架构风格是一种将一个单一应用程序开发为一组小型服务的方法,每个服务运行在自己的进程中,服务间通信采用轻量级通信机制(通常用HTTP资源API)。这些服务围绕业务能力构建并...

深入理解与应用CSS clip-path 属性
深入理解与应用CSS clip-path 属性

clip-pathclip-path是什么clip-path 是一个CSS属性,允许开发者创建一个剪切区域,从而决定元素的哪些部分可见,哪些部分会被隐...

2024-10-25 11:51 citgpt

HCNP Routing&Switching之OSPF LSA类型(二)
  • HCNP Routing&Switching之OSPF LSA类型(二)
  • HCNP Routing&Switching之OSPF LSA类型(二)
  • HCNP Routing&Switching之OSPF LSA类型(二)
  • HCNP Routing&Switching之OSPF LSA类型(二)
Redis和Memcached的区别详解
  • Redis和Memcached的区别详解
  • Redis和Memcached的区别详解
  • Redis和Memcached的区别详解
  • Redis和Memcached的区别详解
Request.ServerVariables 大全

Request.ServerVariables("Url")返回服务器地址Request.ServerVariables("Path_Info")客户端提供的路...

python操作Kafka

目录一、python操作kafka1.python使用kafka生产者2.python使用kafka消费者3.使用docker中的kafka二、python操作kafka细...

Runtime.getRuntime().exec详解

Runtime.getRuntime().exec详解概述Runtime.getRuntime().exec用于调用外部可执行程序或系统命令,并重定向外部程序的标准输入、标准输出和标准错误到缓冲池。...

promise.all详解 promise.all是干什么的
promise.all详解 promise.all是干什么的

promise.all详解promise.all中所有的请求成功了,走.then(),在.then()中能得到一个数组,数组中是每个请求resolve抛出的结果...

2024-10-24 16:21 citgpt

Content-Length和Transfer-Encoding详解
  • Content-Length和Transfer-Encoding详解
  • Content-Length和Transfer-Encoding详解
  • Content-Length和Transfer-Encoding详解
  • Content-Length和Transfer-Encoding详解

取消回复欢迎 发表评论: