当前位置: 首页 > news >正文

C++ 从函数或方法返回动态内存(函数指针与指针函数)

函数指针与指针函数

函数指针:
如果在程序中定义了一个函数,那么编译时系统就会为这个函数分配一段存储空间, 这段存储空间的首地址称为这个 函 数 的 地 址。而且函数名表示的就是这个地址。既然是地址,我们就可以定义一个 指针变量来存放,这个指针变量就叫做 函 数 指 针 变 量,简称为 函 数 指 针。指向函数的指针包含了函数的地址的入口地址(首地址),可以通过它来调用函数。

定义函数指针:
那么这种指针变量如何定义呢,虽然同样是指向一个地址,但指向函数的指针变量与指向变量的指针在定义方式上是不同的,例如:

int(*p)(int x,int y);

上述语句定义了一个指向函数的指针变量 “p”,首先它是一个指针变量,所以p的前缀为" * "符,即 " *p ",其次前面的int表示这个指针变量可以指向返回值类型为int类型的函数( int(*p) ),后面参数列表括号中的两个int( “(int x,int y)” )表示这个指针变量可以指向有两个参数,且参数类型都是int类型的函数

所以这个语句的意思是:定义一个指针变量p,该指针变量可以指向返回值类型为int型,且有两个整型参数的函数,函数指针p的类型为[ int(*)(int,int) ];

函数指针的定义方式为:

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

函数返回值类型:表示该指针变量可以指向具有何种返回类型的函数
函数参数列表:表示该指针变量可以指向具有何种参数列表的函数,这个参数列表中只需要写函数的参数类型即可,不需要写参数的名字。

总结:🌟
函数指针的定义就是将 [ 函数声明 ] 中的 [ 函数名 ] 改成了 [ (*指针变量名) ] 。但需要注意的是: [ (*指针变量名) ] 两端的括号是不能省略的。括号改变了运算符的优先级,如果省略括号,就不是定义函数指针而是声明了一个返回值类型为指针的指针函数。

如何判断一个指针变量是指向变量还是指向函数:首先看变量名前面是否有 “*” 如果有,说明其首先是指针变量,其次看变量名后面有没有带形参列表的圆括号,如果有就是指向函数的函数指针,否则就是指向变量的指针变量。

注意:🎯
1.指向函数的指针变量没有++--运算。
2.函数指针调用函数时,函数指针与 被调用的函数的返回值类型,参数列表中参数的个数,参数的类型必须完全一致

如何用函数指针调用函数:
例如:

fun(int x,int y);

void main()
{

int (*p)(int x,int y); //声明一个函数指针 p
p=fun; //给函数指针p赋值,使它指向函数fun(将func函数的首地址赋值给指针p)
a=(*p)(x,y); //通过指针p调用函数fun

}

赋值时函数fun不带括号,也不带参数。这是因为函数名fun代表存放这个函数的内存空间的首地址,因此经过赋值给函数指针p以后,指针变量p就指向函数fun()的代码首地址了(也就是可以通过函数指针p调用这个fun()函数)。

函数指针的实际应用:

int max(int x, int y);

int main()
{
	int(*fp)(int x,int y);
	int a, b, c;
	fp = max;
	printf("请输入两个需要进行比较的值: ");
	cin >> a >> b;
	c = (*fp)(a, b);
	//todo 也可以写作 c = fp(a, b);
	printf("a=%d   b=%d  %d是最大的数",a,b,c);

	return 0;
}

int max(int x, int y)
{
	int r;
	if (x > y)
	{
		return x;
	}
	else
		return y;
}

运行结果:
在这里插入图片描述
以数组形式的函数指针调用函数:

int fun1(int x, int y);
int fun2(int x, int y);
int fun3(int x, int y);

int main()
{
	int(*fp[3])(int x,int y);//todo定义一个函数指针数组
	
	fp[0] = fun1; //todo函数指针数组个元素指向不同的函数
	fp[1] = fun2;
	fp[2] = fun3;
	
	fp[0](5,5);//todo以数组形式的函数指针调用函数
	fp[1](5, 5);
	fp[2](5, 5);

	return 0;
}

为什么需要函数指针

为什么需要指针?

因为我们要访问一个对象,我们要改变一个对象。要访问一个对象,必须先知道它在哪,也就是它在内存中的地址。地址就是指针值。
所以我们有
函数指针:某块函数代码的起始位置(地址)
指针的指针:因为我要访问(或改变)某个变量,只是这个变量是指针罢了

为什么要有指针类型?

因为我们访问的对象一般占据多个字节,而代表它们的地址值只是其中最低字节的地址(首地址),我们要完整的访问对象,必须知道它们总共占据了多少字节,而指针的类型便向我们提供了这样的信息:
① 一个首字节的地址值(起始位置)
②这个指针的作用范围(步长)
③对这个范围中数位的解释规制(解码规则 强制转换类型就是根据这个规则进行转换的)
编译器就像一个以步数测量距离的盲人。故你要告诉它从哪开始走,走多少步,并且告诉他如何理解这里面的信息

函数指针的作用:

1.对于,一些返回参数一样,功能不一样的函数可以进行模块化调用,

2.例如写一个小的操作系统,有若干菜单可选,把各个菜单写成相应的函数,选择了一个菜单项后根据相应的函数指针去执行对应的操作,也就是转移表(转移表就是一个函数指针数组),即可用来实现“菜单驱动系统”。系统提示用户从菜单中选择一个选项,每个选项由不同的函数提供服务(void(*f[3])(int) = {function1, function2, function3} ; //定义一个转移表(*f[choice])( ) ; //根据用户的选择来调用相应的函数 )

3.回调函数中也使用的是函数指针,比如hash表的遍历,每找到一个key,就调用一个函数,(用函数指针做形参,用户根据自己的环境写个简单的函数模块,传给回调函数,这样回调函数就能在不同的环境下运行了,提高了模块的复用性),(回调函数实现与环境无关的核心操作,而把与环境有关的简单操作留给用户完成,在实际运行时回调函数通过函数指针调用用户的函数,这样其就能适应多种用户需求

4.使用函数指针的好处在于:
可以将实现同一功能的多个模块统一起来标识,这样一来更容易后期维护,系统结构更加清晰。或者归纳为:便于分层设计、利于系统抽象、降低耦合度以及使接口与实现分开

来自https://www.ctolib.com/docs-c-advance-c-funpointer.html


指针函数:

指针函数是一个可以返回一个 整型数据的值,字符类型值和实型类型的值,还可以返回 指针类型的数据,使其指向某个地址的单元的函数,简单的来说,就是一个返回指针的函数,其本质是一个函数,而该函数的返回值是一个指针(地址值),函数 返回值必须用同类型的指针变量来接受,而且在主调函数中,函数返回值必须赋值给同类型的指针变量。用于需要指针或地址的表达式中。

定义指针函数:

声明的格式为:

类型标识符 * 函数名(参数表)

例如:

int fun(int x,int y);普通函数

int *fun(int x,int y); 指针函数声明

int *fun(int x,int y); 指针函数定义
{
	int *p=&x;
	return p;
}

int *pp;
pp=fun(x,y); 利用类型与指针函数返回的指针类型一致的指针p  来调用指针函数fun 并接收fun这个函数返回的返回值(指针) 

🔥此时这个pp这个指针存放的是指针函数fun返回的指针p的地址,如果对pp进行解引用(*pp) pp的值将不会是pp指向的指针返回值p的值,
而是一个随机值,如果打印pp的地址,pp的地址则是fun这个指针函数所返回的指针地址.


通过和普通函数的对比我们可以发现,指针函数和普通函数的唯一的区别就是在函数名前面多了一个 " * " 号,函数名前有这个符号的函数便是一个指针函数。以上述函数为例,其返回值是一个int类型的指针,是一个地址。且所谓的指针函数也没有什么特别的,和 普通函数对比不过就是其返回了一个指针(地址值)而已,这一特性让指针函数经常使用在返回数组的某一元素地址上。

注意:🎯
在调用指针函数的时候, 需要一个同类型的指针来接收其函数的返回值,由于返回的是一个地址,所以类型说明符一般都是int。不过也可以将其返回值 定义为 " void* "类型,在调用的时候强制转换( static_cast<int*> )返回值为自己想要的类型。不过不推荐这么做,因为强制转换可能会带来风险。

例如下述示例代码中:


int* fun(int x,int y);//todo声明指针函数 fun 返回值为int类指针 参数列表有两个整型参数(x,y)

int main()
{	
	//todo调用指针函数
	int a = *fun(5, 6); //建立整型变量a 并赋值为 解引用了指针函数fun的指针返回值的值
	int* b = fun(5, 6); //建立整型指针变量b 并将指针b指向 指针函数fun返回的地址

	//todo注意:🔥
    //在调用指针函数时,需要一个同类型的指针来接收其函数的返回值
	//不过也可以在定义指针函数的时候将其返回值定义为 void* 类型,在调用的时候强制转换返回值为自己想要的类型
	//例如 :int* b = static_cast<int*> (fun(5, 6));
	//其输出结果是一样的,不过不建议这么使用,因为强制转换可能会带来风险

	cout <<"结果是: "<< a << "   结果的地址是: " <<b<< endl;
}

int* fun(int x, int y)
{
	cout << "输入参数x的值为: " << x << "   输入参数x的地址为:  " << &x << endl;
	int* a = &x;//todo将输入的整型参数x的地址赋值给整型指针 a
	return a;//todo返回获取了参数x的指针的 整型指针a
}

运行结果:
在这里插入图片描述


为什么需要从函数或方法中返回动态内存

动态内存的另一个常见用途是让 函数申请并返回一个指向内存块的指针,也就是从函数返回内存(掌握这个技巧是很重要的,尤其是当你打算使用由别人编写的库文件的时候)。

如果不知道这个技巧,就 只能让函数返回一个简单的标量值,如整型,浮点型或字符型。换句话说,它既不能返回一个以上的值,也不能返回数组值之类比较复杂的数据结构。

也就是说,只要你想让函数返回的东西不是一个简单的值,就需要从函数或方法中返回内存。


如何从函数或方法中返回动态内存

基本思路:在函数里调用new语句为某种对象或某种基本数据类型分配一块内存,再把那块内存的地址返回给程序的主代码,主代码将使用那块的内存,并在完成相关操作后将该内存立刻释放。

int* newInt(int value);
//定义一个返回值为指向整型的指针地址的函数newInt,有个唯一的传入参数 为整型类参数


int main()
{
	int* x = newInt(20);
	//todo定义一个指向整型的指针变量x 赋值为 newInt这个函数的返回值(返回值为一个地址)

	cout << *x;
	delete x;
	x = NULL;
	return 0;
}

int* newInt(int value)
{
	int* myInt = new int;
	//todo在堆中申请整型的内存块 此内存块被整型指针myInt指向 

	*myInt = value;
	//给指针myInt指向的值赋值

	return myInt;
	//从函数返回内存
}

运行结果:在这里插入图片描述


让函数返回一个指向局部变量的指针

我们曾讨论过变量作用域的概念:函数或方法有他们自己的变量,这些变量只能在这个函数的内部使用,这些变量我们称之为局部变量(local variable)

我们又知道如何 利用指针在某个函数的内部改变另一个函数局部变量的值(例如传地址引用)
这是绕开变量作用域的一种手段,在某些场合是非常必要的。

void swap(int* x, int* y);

int main()
{
	int a, b;
	a = 3;
	b = 5;

	swap(&a, &b);

	cout << "a=" << a << "\n";
	cout << "b=" << b << "\n";
	
	return 0;
}


void swap(int* x, int* y)
{

#if 0
	int temp;
	temp = *x;
	*x = *y;
	*y = temp;
#endif //todo #if 0 #endif这对宏语句相当于/* */注释符

	//todo ^= 按位异或 操作符 相当于 *x=*x^*y;
	cout << *x << "     " << *y<<"\n\n";
	* x ^= *y;
	//todo此时 *x为3  二进制码为  0011  
	//todo此时 *y为5  二进制码为  0101  
	//按位或与后为  0110 转换成十进制也就是 6 即此时*x为 6

	cout << *x << "     " << *y << "\n\n";
	*y ^= *x;
	//todo此时 *x为6 二进制码为   0110    
	//todo此时 *y为5  二进制码为  0101   
	//按位或与后为  0011 转换成十进制也就是 3 即此时*y为 3


	cout << *x << "     " << *y << "\n\n";
	*x ^= *y;
	//todo此时 *x为6 二进制码为   0110     
	//todo此时 *y为3  二进制码为  0011   
	//按位或与后为  0101 转换成十进制也就是 5 即此时*x为 5

	//todo此时 从传入进来时的 *x=3 *y=5 利用按位异或操作 成功将 *x=5,*y=3
	cout << *x << "     " << *y << "\n\n";
}

运行结果:
在这里插入图片描述
注意: 任何一个函数都不应该把它自己的局部变量的指针作为它的返回值因为局部变量在栈里,栈里的数据在函数结束的时候会被自动释放。

按位异或运算符 “^” :双目运算符,用于将左右两操作数的二进制数进行按位异或操作。
例如:

int a=3;//3的二进制数为0011
int b=5;//5的二进制数为0101

int c=a^b;
进行按位异或,结果为0110,转换为十进制为 6 ,c此时为6

如果你想让一个函数在不会留下任何隐患的情况下返回一个指针,那它只能是一个 动态分配的内存块 的基地址(首地址)


什么情况下我们需要函数返回一个地址值
1.经常使用在返回数组的某一元素地址上
2.比如我们在函数中需要返回一个数组,就可以直接返回这个数组的内存地址

来自:
https://blog.csdn.net/luoyayun361/article/details/80428882
https://www.cnblogs.com/lvjunjie/p/8961503.html
https://www.cnblogs.com/ligiggy/p/12988663.html
http://c.biancheng.net/view/228.html

相关文章:

  • PHP实现多web服务器共享SESSION数据-session数据写入mysql数据库
  • C++ 副本构造器
  • C++常识之——C++中堆和栈的区别,自由存储区、全局/静态存储区和常量存储区...
  • C++ 高级强制类型转换
  • thinkphp的一些类库
  • C++ 指针和引用
  • 9月24号忘记
  • C++ 避免内存泄漏
  • [转]SQL Server DBCC用法大全
  • C++ 命名空间和模块化编程
  • FG终于投出去了~
  • 资源仓库
  • open cv建立一个标准的opencv程序
  • 协议与委托(Protocol and Delegate)实例解析
  • C++ 链接和作用域
  • eclipse(luna)创建web工程
  • ES10 特性的完整指南
  • ES6, React, Redux, Webpack写的一个爬 GitHub 的网页
  • gops —— Go 程序诊断分析工具
  • Laravel深入学习6 - 应用体系结构:解耦事件处理器
  • MySQL几个简单SQL的优化
  • PHP面试之三:MySQL数据库
  • React+TypeScript入门
  • session共享问题解决方案
  • Spring Cloud Alibaba迁移指南(一):一行代码从 Hystrix 迁移到 Sentinel
  • spring学习第二天
  • TypeScript迭代器
  • vue2.0开发聊天程序(四) 完整体验一次Vue开发(下)
  • 记一次和乔布斯合作最难忘的经历
  • 简单实现一个textarea自适应高度
  • 聚类分析——Kmeans
  • 理解在java “”i=i++;”所发生的事情
  • 如何实现 font-size 的响应式
  • Java总结 - String - 这篇请使劲喷我
  • 阿里云API、SDK和CLI应用实践方案
  • #162 (Div. 2)
  • #我与Java虚拟机的故事#连载02:“小蓝”陪伴的日日夜夜
  • (07)Hive——窗口函数详解
  • (3)STL算法之搜索
  • (8)STL算法之替换
  • (javascript)再说document.body.scrollTop的使用问题
  • (十三)Maven插件解析运行机制
  • (四)docker:为mysql和java jar运行环境创建同一网络,容器互联
  • (转)PlayerPrefs在Windows下存到哪里去了?
  • (转)利用ant在Mac 下自动化打包签名Android程序
  • .NET 的静态构造函数是否线程安全?答案是肯定的!
  • .NET 指南:抽象化实现的基类
  • .netcore如何运行环境安装到Linux服务器
  • .net通用权限框架B/S (三)--MODEL层(2)
  • .one4-V-XXXXXXXX勒索病毒数据怎么处理|数据解密恢复
  • /bin、/sbin、/usr/bin、/usr/sbin
  • /var/spool/postfix/maildrop 下有大量文件
  • [1]-基于图搜索的路径规划基础
  • [20160807][系统设计的三次迭代]
  • [20180312]进程管理其中的SQL Server进程占用内存远远大于SQL server内部统计出来的内存...