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