目录
前言
当我们不再全身心的投入到冗杂的学习工作中,静下心来有没有想过日常使用的函数为什么有时候已经传入指针参数情况下返回值仍然可能是同类型的指针,按理来说只要不声明为const指针作为传入参数,函数内对指针做改动是可以保留影响到函数外的,其改变作用的生命周期可以超出函数本体,所以我们会想对函数返回值依然是指针是有什么别样的需求或是其他,下面我将讲述几种常见情境下需要用到返回值仍然是指针类型的情况。
一、函数内部动态分配内存
先给出遍历数组代码,以便后续使用:
void traversal(int* arr, const int n)
{
if (arr == nullptr)
{
cout << "空指针遍历" << endl; return;
}
for (int i = 0; i < n; i++)
{
cout << arr[i] << "\t";
}
cout << endl;
}
int* newArr(int* arr, const int n) // 动态分配内存情况
{
arr = new int[n];
return arr;
}
下面展示该函数调用实例:
void test4()
{
int* arr = nullptr;
arr = newArr(arr, 100);
constexpr int n = 10; // 写入数组位数
for (int i = 0; i < n; i++) // 赋值
{
arr[i] = i;
}
traversal(arr, n); // 遍历数组到第n位
const int size = _msize(arr) / sizeof(arr[0]); // _msize()用于求堆区创建的动态数组大小,类似于sizeof()求一般静态数组
cout << "arr.size() = " << size << endl;
delete[] arr;
}
这里求动态数组长度size是为了检验利用函数实现动态分配内存是否能通过指针返回值将影响保留到函数外,同时应注意动态数组求长度的方法不同于静态数组(利用_msize()函数)。
运行结果:
发现数组可用且大小被明确分配为100,实现了完整的动态内存分配。
二、修改指针指向
int* increase_p(int* p)
{
p++; // 修改指针指向下一个对象
return p;
}
下面展示该函数调用实例:
void test3()
{
int* p = new int[10];
for (int i = 0; i < 10; i++)
{
p[i] = i;
}
cout << *p << endl; // 访问p指向的元素
p = increase_p(p);
cout << *p << endl; // 访问p指向的元素
}
运行结果:
将运行结果和调试窗口进行对比,发现在经过 increase_p() 函数后,p指针的指向发生了变化,从指向数组第一位元素(下标为0)变为指向第二位元素(下标为1),所以同样进行解引用读取p指向的值会由0变1。
但是出于原则,我们必须在程序结束前手动释放p指向的内存空间,所以我们加上delete[] p;语句:
发现运行出错,delete语句处发生了释放内存错误的警告,回过头想想,对动态数组做释放时都是直接对指向数组首位指针执行 delete[] 操作,这里我们指针变量p已经经过increase_p()函数处理后发生了后移,指向的位置已不再是数组首位,当然释放报错了,只需要进行退位操作,使得p重新指向数组首元素即可。
具体更改后代码如下:
void test3()
{
int* p = new int[10];
for (int i = 0; i < 10; i++)
{
p[i] = i;
}
cout << *p << endl;
p = increase_p(p);
cout << *p << endl;
delete[] (p-1); // 注意此处的“退位”操作
}
重新运行:
通过最后两行内存泄露检测工具显示,所有内存泄露点均已完美解决 !
三、函数执行失败或需要返回特定状态
1.返回特定状态
int* findVal(int* arr, const int n, const int target)
{
if (arr == nullptr) { return nullptr; }
for(int i = 0; i<n;++i)
{
if (target == arr[i]) { return &arr[i]; } // 函数返回的指针指向找到的值所在的地址
}
return nullptr; // 未找到返回空
}
下面展示该函数调用实例:
void test5()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
constexpr int target = 7;
const int* index = findVal(arr, 10, target);
cout << "Index_value = " << *index << endl;
}
可能有人觉得说不好听点这不是脱裤子放屁吗,我要那个找到的值为何不直接拿target用。但是,当我们需要利用target找到对应的值同时还要进行修改等操作时,简单传值或是直接利用target就行不通了,我们利用传回的指针对齐做修改时可以直接影响对应数组元素的变化。
不妨看以下代码:
void test5()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
constexpr int target = 7;
int* index = findVal(arr, 10, target); // 注意后续改动指针指向值时需要去掉const修饰
cout << "Index_value = " << *index << endl;
*index = 100; // 利用传回指针修改数组元素
traversal(arr, 10); // 遍历检验
}
我们添加了改值操作,运行结果:
2.返回执行失败或未达到预期标志
当我们需要的target值在数组中并未找到对应时,就需要通过对函数返回值判空来确认是否找到对应值。
void test5()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
constexpr int target_1 = 15;
const int* index_1 = findVal(arr, 10, target_1);
if (index_1 == nullptr) { cout << "未找到target值" << endl; }
else { cout << "Index_1_value = " << *index << endl; }
}
运行结果:
总结
在C++中,当函数的传入参数为指针类型时,返回值需要是同类型的指针型返回值的情况有三种:动态分配内存、函数内部修改指针指向的对象以及函数执行失败或需要返回特定状态。在使用函数返回指针时,需要注意几点。首先,确保返回的指针不会指向函数内部的局部变量,以避免返回无效指针导致错误。其次,为了避免内存泄漏,应在适当的时候对返回的指针进行内存释放,特别是在使用 new 或者 malloc 运算符动态分配内存时。
总之,理解函数传入参数为指针类型时同类型指针型返回值的情况,可以更好地处理指针操作和内存管理,确保代码的正确性和健壮性。在具体应用中,需要根据实际需求和情况选择合适的返回方式,并注意内存管理的规范和安全性。