30

我对 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;
}
4

7 回答 7

22

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

  • 当您使用 syntax 创建数组时int arr[5];,它会分配在堆栈上并且是函数本地的。
  • C++ 允许您返回指向此数组的指针,但在其本地范围之外使用此指针指向的内存是未定义的行为。使用现实世界的类比阅读这个很好的答案,以获得比我所能解释的更清晰的理解。
  • 如果您可以保证数组的内存没有被清除,您仍然可以在范围之外使用该数组。在您的情况下,当您传递arrtest().
  • 如果您想在不担心内存泄漏的情况下传递指向动态分配数组的指针,您应该阅读std::unique_ptr/ std::shared_ptr<>

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

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

编辑 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. 因此,答案中概述了如何处理它的方法。

于 2012-10-20T21:54:21.623 回答
17

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

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

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

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

如果你想要一些可以保留并且不能超出范围的东西,你应该分配它new

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

返回的指针将始终有效。请记住,当你完成它时再次删除它,使用delete[]

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

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

于 2012-10-20T22:00:18.903 回答
2

新问题的新答案:

您不能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])
于 2012-10-20T22:09:13.693 回答
2

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

#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
}
于 2012-10-20T22:15:00.960 回答
0

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

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

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

double (*p)[42];

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

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

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

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

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

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

于 2012-10-20T22:18:00.293 回答
0

你可以(有点)返回一个数组

代替

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;
}

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

于 2012-10-20T23:02:57.290 回答
0

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

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

于 2012-10-20T22:08:21.480 回答