C++ 如何从函数中返回动态分配的数组

3

我刚开始学习C++。我正在网上阅读教程,现在我已经在函数中创建了一个动态分配的数组。

void array_() {
    int size;
    cin >> size;

    int * Array = new int[size];


    for (int i = 0; i < size; ++i)
        cin >> Array[i];
}

如何将该函数返回到主函数中?

1
返回指针?返回指针和返回“int”之间没有区别。 - Some programmer dude
1
@Someprogrammerdude 愉快地丢失了大小信息。 - iksemyonov
@iksemyonov 是的,但我会把这个问题留到另一个时间再讨论... :) - Some programmer dude
首先,你如何从函数中返回一个 int - juanchopanza
1
不知道,所以我才问你。我只想将数组返回给main()函数。 - KonradDos
尽管这是一个简单的问题,而且很可能违反规则,但我已经发布了一个答案。只是觉得有必要解释一下。 - iksemyonov
4个回答

10

首先,返回指针的同时也返回大小是明智的,否则无法安全地使用结果数组。

因此,我们正在寻找一种从函数中返回两个值的方法。可以通过采用引用参数来完成此操作,通过该参数分配其中一个值:

int *array_(int &size) {
    std::cin >> size;

    int *Array = new int[size];

    for (int i = 0; i < size; ++i)
        std::cin >> Array[i];

    return Array;
}

int main() {
    int size;
    int *arr = array_(size);
    // ...
    delete[] arr; // Don't forget to delete[] what has been new[]'d!
}

或者,您可以返回一个包含两个值的std::pair

std::pair<int *, int> array_() {
    int size;
    std::cin >> size;

    int * Array = new int[size];

    for (int i = 0; i < size; ++i)
        std::cin >> Array[i];

    return {Array, size};
}

int main() {
    auto arr = array_(size);
    // ...
    delete[] arr.second; // Don't forget still!
}

但是这两种方法都是非常糟糕的想法,所以你可以立刻开始编写真正的C++代码,使其不像畸形的C语言,并使用标准容器:

std::vector<int> array_() {
    int size = 0;
    std::cin >> size;

    std::vector<int> vec(size);

    for(int &i : vec)
        std::cin >> i;

    return vec;
}

int main() {
    auto arr = array_(size);
    // ...
    // Now you can forget delete: no raw owning pointer, no worries.
}

简单,无泄漏和异常安全(理想情况下您也应该对用户输入进行消毒)。


RVO是否保证可用?std::vector<T>不是Qt风格的隐式共享类。 - iksemyonov
@iksemyonov 任何合理质量的实现都将在此执行NRVO。除此之外,向量将被移出函数。在C++17及以后版本中,RVO将由语言保证。 - Quentin

4
在C++中,你不能直接返回一个数组类型(例如`int arr[]`)的变量,但你可以返回该数组的引用或指针。虽然这种语法相当笨拙。在所示代码中,没有数组,而是指向动态分配内存块的指针。然而,主要问题是由于内存是动态分配的,因此当你返回指向该内存的指针时,你只给客户端提供了一半信息:即数组的大小仍然未知。
因此,如果你真的想使用传统的动态数组,你可以将数组的大小作为引用参数("输出值")传递给函数,如下所示:
int* func(std::size_t& size_out) {
    // int n = ...;
    int* array = new int[n];
    size_out = n;
    return array;
}

int main() {
    int size;
    int* result = func(size);
    delete[] result;
    result = nullptr;
}

正如您所看到的,手动分配内存的副作用是客户端需要承担责任,并且您必须在其被分配的函数之外手动删除它。
但是,“out-values”真的是一种不好的风格,特别是在API中。尽可能避免使用它们!
当然,最好使用适当的动态数组设施,例如std::vector<T>,但这似乎不是本次练习的重点。
参考资料:Return array in a function

为什么第一段一半讲述了数组衰减和数组指针,但这里并没有涉及这些内容?另一半则说指针+大小的数组传递与动态分配有关,但事实并非如此。 - Quentin
@Quentin 这个例子中没有提到衰减(decay)一词。也没有数组(array)。我原来的草稿中提到了衰减,但后来看到了指针就将其删除了。 - iksemyonov
你没有写下“衰减”这个词,但这正是你无法从函数中返回实际数组的原因。 - Quentin
@Quentin Oh,别这样啊,我刚刚确切地回答了楼主的问题。我并不认为我说返回该对是严格相关于动态内存分配,我只是在回应这篇帖子中的问题。 - iksemyonov
std::array<T,n> 是适用于 C++ 的正确数组语法糖,可以直接替换传统的 C 语言原始数组语法。std::vector 是一种不同类型的数据结构 [非常相似]。std::array 具有与原始数组相同的运行时行为和性能。(std::array 在编译期间会被转换为原始数组。) - Max Power
显示剩余3条评论

4
在C++中,大多数情况下我们不需要使用operator new手动分配资源。
我建议使用std::vector<int>。它比动态分配的普通数组具有以下优点:
  • 当作用域退出时(您不必调用delete)时,它将自动释放保留的内存。
  • 它有一个size()方法,因此您始终知道向量中包含多少个元素。将其与动态普通数组进行比较,在其中您需要在另一个变量中存储大小。
  • 您可以在构建后动态更改“数组”的大小(例如使用resize()方法)。

示例

std::vector<int> array_() 
{
    int size;
    cin >> size;

    // Create a dynamic "array" with the given size.
    std::vector<int> result( size );

    for (int i = 0; i < size; ++i)
        cin >> result[i];

    return result;
}

要在main()中使用那个“数组”:
int main()
{
    // Call function array_() and store the result in variable v.
    std::vector<int> v = array_();

    // Print all elements of v using range-based loop.
    for( int x : v )
    {
        std::cout << x << '\n';
    }

    // Alternatively print all elements using classic loop with index.
    for( int i = 0; i < v.size(); ++i )
    {
        std::cout << v[i] << '\n';
    }
}

0

您可以返回动态分配的数组并返回其指针:

int* ProduceArray() {
    ...
    int* arrayPtr = new int[size];
    ...
    return arrayPtr;
}

然而,请注意,这样做会丢失数组大小的重要信息(对于使用数组的代码来说,避免缓冲区溢出非常重要)。

在现代C++中,更好的方法是返回标准库容器,如std::vector,而不是原始指针。

请注意,std::vector实例知道它们的大小,并自动释放它们的内存(相反,当你有原始拥有指针时,必须显式调用delete[])。


现代化的C++?std::vector现在是新宠了吗? :p - Quentin
在C++11/14/17中,“现代”编程风格已经成为主流。在C++98中,返回容器时存在一些犹豫不决的情况,常见的做法是使用引用参数。通过移动语义,返回可能非常庞大的可移动数据是可以接受的。 - Mr.C64
1
据我所知,RVO和NRVO从一开始就存在,因此实际上没有复制或移动。 - Quentin
RVO和NRVO在某些情况下不能保证,至少在MSVC中如此。 - Mr.C64
嗯,MSVC的RVO问题确实让人头疼。好吧 :) - Quentin

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接