【问题标题】:C++ correct way to return pointer to array from functionC ++从函数返回指向数组的指针的正确方法
【发布时间】:2012-10-11 04:05:25
【问题描述】:

我对 C++ 还很陌生,并且一直在避免使用指针。根据我在网上阅读的内容,我无法返回数组,但可以返回指向它的指针。我做了一个小代码来测试它,想知道这是否是正常/正确的方法:

#include <iostream>
using namespace std;

int* test (int in[5]) {
    int* out = in;
    return out;
}

int main() {
    int arr[5] = {1, 2, 3, 4, 5};
    int* pArr = test(arr);
    for (int i = 0; i < 5; i++) cout<<pArr[i]<<endl;
    cout<<endl;
    return 0;
}

编辑:这似乎不好。我应该如何重写它?

int* test (int a[5], int b[5]) {
    int c[5];
    for (int i = 0; i < 5; i++) c[i] = a[i]+b[i];
    int* out = c;
    return out;
}

【问题讨论】:

  • 请查看std::array
  • 它似乎有效,我只是想知道它是否有什么问题。我听说不正确地使用指针会很危险。
  • 数组可以毫无问题地作为指针传递,但不能使用指针分配给数组。
  • 在编码业务中,如果有什么可行的,那就对了。
  • 只是不要尝试返回指向本地堆栈数组的指针。像int* funct() { int arr[5]; return arr; } 这样的东西会很糟糕。

标签: c++ arrays function pointers


【解决方案1】:

您的代码是正确的,但我很难弄清楚它可以/将如何在现实世界的场景中使用。话虽如此,请注意从函数返回指针时的一些注意事项:

  • 当您使用语法 int arr[5]; 创建数组时,它会分配在堆栈上并且是函数的本地数组。
  • C++ 允许您返回指向此数组的指针,但在其本地范围之外使用此指针指向的内存是未定义的行为。阅读this great answer using a real world analogy 以获得比我所能解释的更清晰的理解。
  • 如果你能保证数组的内存没有被清除,你仍然可以在作用域之外使用数组。在您的情况下,当您将 arr 传递给 test() 时,这是正确的。
  • 如果您想传递指向动态分配数组的指针而不担心内存泄漏,您应该阅读std::unique_ptr/std::shared_ptr&lt;&gt;

编辑 - 回答矩阵乘法的用例

你有两个选择。天真的方法是使用std::unique_ptr/std::shared_ptr&lt;&gt;。现代 C++ 方法是在其中重载 operator *Matrix 类,如果要避免复制乘法结果以使其脱离函数,则绝对必须使用新的 rvalue references。除了拥有copy constructoroperator =destructor,您还需要拥有move constructormove assignment operator。仔细阅读this search 的问题和答案,以更深入地了解如何实现这一目标。

编辑 2 - 对附加问题的回答

int* test (int a[5], int b[5]) {
    int *c = new int[5];
    for (int i = 0; i < 5; i++) c[i] = a[i]+b[i];
    return c;
}

如果您将其用作int *res = test(a,b);,那么稍后在您的代码中,您应该调用delete []res 来释放在test() 函数中分配的内存。您现在看到的问题是手动跟踪何时拨打delete 非常困难。因此,答案中概述了如何处理它的方法。

【讨论】:

  • 但是,返回一个用static int arr[5];在本地声明的数组很好,因为它不在堆栈上。
  • 是的。确切地。但由于 static int arr[5] 只有一份副本,因此用例受限于您可以使用它执行的操作。
  • 未来我打算做一个矩阵乘法器。两个矩阵将作为输入,输出将是一个新矩阵。
  • 没错,@wnraman。如果您想维护某种全局状态,则声明为 static 的变量最有用。像strtok 这样的东西浮现在脑海中,尽管它可能使用static char*
【解决方案2】:

您的代码没问题。请注意,如果您返回一个指向数组的指针,并且该数组超出范围,则不应再使用该指针。示例:

int* test (void)
{
    int out[5];
    return out;
}

上述方法永远不会起作用,因为当test() 返回时,out 不再存在。不能再使用返回的指针。如果你确实使用它,你将读/写你不应该的内存。

在您的原始代码中,arr 数组在main() 返回时超出范围。显然这没问题,因为从main() 返回也意味着您的程序正在终止。

如果您想要一些可以保留并且不会超出范围的东西,您应该使用new 分配它:

int* test (void)
{
    int* out = new int[5];
    return out;
}

返回的指针总是有效的。请记住,使用delete[]

int* array = test();
// ...
// Done with the array.
delete[] array;

删除它是回收它使用的内存的唯一方法。

【讨论】:

  • 比这更糟糕 - 它有时会起作用,这取决于正在运行的其他内容、运行时间和月相
  • 这是我现在遇到的问题。我想在参数中有两个数组并返回一个新数组(将来将是两个输入的相乘)。似乎答案是在参数中包含三个数组,其中第三个不重要但用于可返回的目的。有没有更好的方法来做到这一点?
  • 嘿@asimes。你有正确的想法。最好发送第三个数组,例如boolean matMult(int* A, int* B, int* C, size_t m, size_t n, size_t p);,其中Am x nBm x pCn x p
  • @asimes 但是,您也可以按照 Nikos 的建议使用 new 声明一个数组。但是,使用矩阵乘法进行预分配相对容易,因为提前知道维度。
  • @Geoff_Montee,我会进行实验,看看什么是有意义的。只使用三个参数而不用担心删除可能会更容易。
【解决方案3】:

新问题的新答案:

您不能从函数返回指向自动变量 (int c[5]) 的指针。自动变量以返回封闭块(在本例中为函数)结束其生命周期 - 因此您将返回指向不存在数组的指针。

要么让你的变量动态化:

int* test (int a[5], int b[5]) {
    int* c = new int[5];
    for (int i = 0; i < 5; i++) c[i] = a[i]+b[i];
    return c;
}

或者改变你的实现以使用std::array

std::array<int,5> test (const std::array<int,5>& a, const std::array<int,5>& b) 
{
   std::array<int,5> c;
   for (int i = 0; i < 5; i++) c[i] = a[i]+b[i];
   return c;
}

如果您的编译器没有提供std::array,您可以将其替换为包含数组的简单结构:

struct array_int_5 { 
   int data[5];
   int& operator [](int i) { return data[i]; } 
   int operator const [](int i) { return data[i]; } 
};

旧问题的旧答案:

你的代码是正确的,而且……嗯,嗯,……没用。由于可以将数组分配给没有额外功能的指针(请注意,您已经在函数中使用了它):

int arr[5] = {1, 2, 3, 4, 5};
//int* pArr = test(arr);
int* pArr = arr;

你的函数的更多签名:

int* test (int in[5])

相当于:

int* test (int* in)

所以你看它没有意义。

但是这个签名需要一个数组,而不是指针:

int* test (int (&in)[5])

【讨论】:

  • 谢谢,我使用了您发布的第一个函数(使用新的 int),它似乎可以满足我的需求。我应该将它用作int* arr3 = test(arr1, arr2) 对吗?此外,这将处于一个永远循环的循环中(OpenGL 绘制基于这些数组中的内容)。当我不再需要 m3 时,我只需要删除它,对吗?
  • 是的——你需要删除这个返回的数组——记住这必须由delete[]完成,而不仅仅是delete
【解决方案4】:

引用数组的变量基本上是指向其第一个元素的指针,所以是的,您可以合法地返回指向数组的指针,因为它们本质上是相同的。自己检查一下:

#include <assert.h>

int main() {
  int a[] = {1, 2, 3, 4, 5}; 

  int* pArr = a;
  int* pFirstElem = &(a[0]);

  assert(a == pArr);
  assert(a == pFirstElem);

  return 0;
}

这也意味着将数组传递给函数应该通过指针(而不是通过int in[5])完成,并且可能连同​​数组的长度一起完成:

int* test(int* in, int len) {
    int* out = in;
    return out;
}

也就是说,您是对的,使用指针(没有完全理解它们)是非常危险的。例如,引用在堆栈上分配并超出范围的数组会产生 未定义的行为

#include <iostream>

using namespace std;

int main() {
  int* pArr = 0;
  {
    int a[] = {1, 2, 3, 4, 5};
    pArr = a; // or test(a) if you wish
  }
  // a[] went out of scope here, but pArr holds a pointer to it

  // all bets are off, this can output "1", output 1st chapter
  // of "Romeo and Juliet", crash the program or destroy the
  // universe
  cout << pArr[0] << endl; // WRONG!

  return 0;
}

所以如果你觉得自己不够称职,就用std::vector吧。

[对更新问题的回答]

编写test 函数的正确方法是:

void test(int* a, int* b, int* c, int len) {
  for (int i = 0; i < len; ++i) c[i] = a[i] + b[i];
}
...
int main() {
   int a[5] = {...}, b[5] = {...}, c[5] = {};
   test(a, b, c, 5);
   // c now holds the result
}

或者这个(使用std::vector):

#include <vector>

vector<int> test(const vector<int>& a, const vector<int>& b) {
  vector<int> result(a.size());
  for (int i = 0; i < a.size(); ++i) {
    result[i] = a[i] + b[i];
  }
  return result; // copy will be elided
}

【讨论】:

  • @dorsrg,谢谢你的解释。我决定选择new int,但很高兴知道您对未定义行为的解释
  • @asimes,如果您决定使用纯 C 数组,我建议您使用接受输出数组作为函数参数的版本。首先,这可以节省您编写delete[] p 的时间(因为输出数组是在堆栈上分配的)。其次,它将确保只有一部分代码需要知道常量 5(现在您在 maintest 中都使用它)。另外,如果您从这个答案中学到了一些有用的东西,愿意投票吗?谢谢:)
【解决方案5】:

在实际应用中,返回数组的方式称为使用输出参数。当然你实际上不必返回一个指向数组的指针,因为调用者已经有了它,你只需要填写数组。传递另一个指定数组大小的参数也很常见,以免溢出。

使用 out 参数的缺点是调用者可能不知道数组需要多大才能存储结果。在这种情况下,您可以返回 std::vector 或类似的数组类实例。

【讨论】:

    【解决方案6】:

    您的代码(看起来不错)没有返回指向数组的指针。它返回一个指向数组的第一个元素的指针。

    事实上,这通常是您想要做的。大多数数组操作都是通过指向单个元素的指针完成的,而不是通过指向整个数组的指针。

    可以定义一个指向数组的指针,例如:

    double (*p)[42];
    

    p 定义为指向doubles 的42 元素数组的指针。一个大问题是您必须将数组中元素的数量指定为类型的一部分——并且该数字必须是编译时常量。大多数处理数组的程序都需要处理不同大小的数组;给定数组的大小在创建后不会发生变化,但其初始大小不一定在编译时已知,并且不同的数组对象可以有不同的大小。

    指向数组第一个元素的指针允许您使用指针算术或索引运算符[] 来遍历数组元素。 但是指针并没有告诉你数组有多少元素;您通常必须自己跟踪。

    如果一个函数需要创建一个数组并返回一个指向其第一个元素的指针,您必须自己管理该数组的存储,方法有多种。您可以让调用者传入一个指向数组对象(的第一个元素)的指针,可能还有另一个指定其大小的参数——这意味着调用者必须知道数组需要多大。或者函数可以返回指向函数内部定义的静态数组(的第一个元素)的指针——这意味着数组的大小是固定的,并且同一个数组将被第二次调用函数破坏。或者函数可以在堆上分配数组——这使得调用者负责稍后释放它。

    到目前为止,我所写的所有内容对于 C 和 C++ 来说都是通用的,实际上它的风格更像是 C 而非 C++。 comp.lang.c FAQ 的第 6 节讨论了 C 中数组和指针的行为。

    但是,如果您使用 C++ 编写代码,那么使用 C++ 惯用语可能会更好。例如,C++ 标准库提供了许多定义容器类的头文件,例如&lt;vector&gt;&lt;array&gt;,它们将为您处理大部分这些内容。除非你有特殊的理由使用原始数组和指针,否则最好只使用 C++ 容器。

    编辑:我想你在我输入这个答案时编辑了你的问题。作为观察者,您问题末尾的新代码不好;它返回一个指向对象的指针,该对象在函数返回后立即停止存在。我想我已经介绍了替代方案。

    【讨论】:

      【解决方案7】:

      你可以(某种程度上)返回一个数组

      而不是

      int m1[5] = {1, 2, 3, 4, 5};
      int m2[5] = {6, 7, 8, 9, 10};
      int* m3 = test(m1, m2);
      

      struct mystruct
      {
        int arr[5];
      };
      
      
      int m1[5] = {1, 2, 3, 4, 5};
      int m2[5] = {6, 7, 8, 9, 10};
      mystruct m3 = test(m1,m2);
      

      测试的样子

      struct mystruct test(int m1[5], int m2[5])
      {
        struct mystruct s;
        for (int i = 0; i < 5; ++i ) s.arr[i]=m1[i]+m2[i];
        return s;
      }
      

      效率不高,因为复制它会提供数组的副本

      【讨论】:

        猜你喜欢
        • 2015-08-07
        • 2011-01-12
        • 1970-01-01
        • 2022-11-21
        • 2020-04-29
        • 2017-03-22
        • 2012-02-02
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多