我正在使用一个外部库,它在某些时候给我一个指向整数数组的原始指针和一个大小。
现在我想用std::vector
来访问和修改这些值,而不是用原始指针来访问它们。
下面是一个解释这一点的例子。
size_t size = 0;
int * data = get_data_from_library(size); // raw data from library {5,3,2,1,4}, size gets filled in
std::vector<int> v = ????; // pseudo vector to be used to access the raw data
std::sort(v.begin(), v.end()); // sort raw data in place
for (int i = 0; i < 5; i++)
{
std::cout << data[i] << "\n"; // display sorted raw data
}
预期的输出。
1
2
3
4
5
原因是我需要在这些数据上应用<algorithm>
的算法(排序、交换元素等)。
另一方面,改变该向量的大小将永远不会被改变,所以push_back
、erase
、insert
不需要在该向量上工作。
我可以根据库中的数据构造一个向量,使用修改该向量并将数据复制到库中,但这将是两个完整的副本,我想避免这种情况,因为数据集可能非常大。
std::span
。如果你能够使用C++20,你可以使用std::span
,它是一个指针--长度对,给用户提供一个视图到一个连续的元素序列。它是某种 "std::string_view",虽然 "std::span "和 "std::string_view "都是非拥有视图,但 "std::string_view "是只读视图。
来自文档。
类模板 span 描述了一个对象,这个对象可以引用一个 连续的对象序列,序列中的第一个元素是:>连续的对象序列 在零点位置 > 。一个跨度可以有一个静态的范围,其中 如果序列中的元素数是已知的,并以以下方式编码。 类型,或动态范围。
所以下面的内容就可以了。
#include <span>
#include <iostream>
#include <算法>
int main() {
int data[] = { 5, 3, 2, 1, 4 };
std::span<int> s{data, 5};
std:::sort(s.begin(), s.end())。
for (auto const i : s) {
std::cout << i << "</n";
}
返回0。
}
看看吧[直播][1]
由于 "std::span "基本上是指针-长度对,所以你也可以用下面的方式来使用。
size_t size = 0.int *data = get_data_from_library(size);。
int *data = get_data_from_library(size);
std::span<int> s{data, size};
注意:并非所有编译器都支持std::span
。请查看编译器支持情况此处。
更新
如果你不能使用C++20,你可以使用gsl::span
,它基本上是C++标准的std::span
的基础版本。
如果你仅限于C++11标准,你可以尝试实现自己的简单的span
类。
模板<typename T>
class span {
T* ptr_;
std::size_t len_;
public.span(T* ptr, std::size_t len)
span(T* ptr, std::size_t len) noexcept
ptr_{ptr}, len_{len}。
{}
T& operator[](int i) noexcept {。
return *ptr_[i];
}
T const& operator[](int i) const noexcept {
return *ptr_[i];
}
std::size_t size() const noexcept {
return len_;
}
T* begin() noexcept {
return ptr_;
}
T* end() noexcept {
return ptr_ + len_;
}
};
查看C++11版本直播 。
[1]: https://godbolt.org/#g:!((g:!(g:! ((h:codeEditor,i:(fontScale:14,j:1,lang:c%2B%2B,selection:(endColumn:2,endLineNumber:16,positionColumn:1,positionLineNumber:1,selectionStartColumn:2,selectionStartLineNumber:16,startColumn:1,startLineNumber:1),源。 '%23include+%3Cspan%3E%0A%23include+%3Ciostream%3E%0A%23include+%3Calgorithm%3E%0A%0Aint+main()+%7B%0A++++int+data%5B%5D+%3D+%7B+5,+3,+2,+1,+4+%7D%3B%0A++++std: :span%3Cint%3E+s%7Bdata,+5%7D%3B%0A%0A++++std::sort(s. begin(),+s.end())%3B%0A%0A++++for+(auto+i+:+s)+%7B%0A++++++++std::cout+%3C%3C+i+%3C%3C+%22%5Cn%22%3B%0A++++%7D%0A%0A++++return+0%3B%0A%7D'),l:'5',n:'0',o:'C%2B%2B+source+%231',t:'0')),k:49. 85494633014216,l:'4',n:'0',o:'',s:0,t:'0'),(g:!((g:! ((h:compiler,i:(compiler:gsnapshot,filter:(b:'0',binary:'1',commentOnly:'0',demangle:'0',directives:'0',execute:'0',intel:'0',librm:'1'),fontScale:14,j:2,lang:c%2B%2B,libs:! (),options:'-std%3Dc%2B%2B2a',selection:(endColumn:1,endLineNumber:1,positionColumn:1,positionLineNumber:1,selectionStartColumn:1,selectionStartLineNumber: 1,startColumn:1,startLineNumber:1),source:1),l:'5',n:'0',o:'x86-64+gcc+(trunk)+(Editor+%231,+Compiler+%232)+C%2B%2B',t:'0')),k:50. 145053669857845,l:'4',m:50,n:'0',o:'',s:0,t:'0'),(g:! ((h:output,i:(compiler:2,editor:1,fontScale:14,wrap:'1'),l:'5',n:'0',o:'%232+with+x86-64+gcc+(trunk)',t:'0')),header:(),l:'4',m:50,n:'0',o:'',s:0,t:'0')),k:50. 145053669857845,l:'3',n:'0',o:'',t:'0')),l:'2',n:'0',o:'',t:'0')),version:4
问题是,std::vector
必须从你初始化它的数组中复制元素,因为它拥有它所包含的对象的所有权。
为了避免这个问题,你可以为数组使用一个slice对象(即,类似于std::string_view
对std::string
的作用)。你可以编写你自己的array_view
类模板实现,其实例是通过获取一个指向数组第一个元素的原始指针和数组长度来构造的。
#include <cstdint>
template<typename T>
class array_view {
T* ptr_;
std::size_t len_;
public:
array_view(T* ptr, std::size_t len) noexcept: ptr_{ptr}, len_{len} {}
T& operator[](int i) noexcept { return ptr_[i]; }
T const& operator[](int i) const noexcept { return ptr_[i]; }
auto size() const noexcept { return len_; }
auto begin() noexcept { return ptr_; }
auto end() noexcept { return ptr_ + len_; }
};
array_view
并不存储一个数组;它只是持有一个指向数组开始的指针和数组的长度。因此,array_view
对象的构造和复制都很便宜。
由于array_view
提供了begin()
和end()
成员函数,所以你可以在上面使用标准的库算法(例如std::sort
、std::find
、std::lower_bound
等)。
#define LEN 5
auto main() -> int {
int arr[LEN] = {4, 5, 1, 2, 3};
array_view<int> av(arr, LEN);
std::sort(av.begin(), av.end());
for (auto const& val: av)
std::cout << val << ' ';
std::cout << '\n';
}
产出:
1 2 3 4 5
上面的实现暴露了slice对象背后的概念。但是,从C++20开始,你可以直接使用std::span
代替。 无论如何,从C++14开始,你可以使用gsl::span
。
由于算法库与迭代器一起工作,你可以保留数组。
这里你可以使用原始指针作为迭代器。它们支持迭代器所支持的所有操作(增量、平等的比较、值等...)。
#include <iostream>
#include <algorithm>
int *get_data_from_library(int &size) {
static int data[] = {5,3,2,1,4};
size = 5。
返回数据。
}
int main()
{
int size;
int *data = get_data_from_library(size);
std::sort(data, data + size)。
for (int i = 0; i < size; i++)
{
std::cout << data[i] << "n"。
}
data
像begin()
返回的迭代器一样指向第一个数组成员,data + size
像end()
返回的迭代器一样指向数组中最后一个元素之后的元素。
这里你可以使用std::begin()
和std::end()
。
#include <iostream>
#include <algorithm>
int main()
{
int data[] = {5,3,2,1,4}; // 来自图书馆的原始数据
std::sort(std::begin(data), std::end(data)); // 对原始数据进行原地排序
for (int i = 0; i < 5; i++)
{
std::cout << data[i] << "\n"; // 显示排序后的原始数据
}
}
但请记住,这只在data
不衰减为指针的情况下有效,因为这样长度信息就会丢失。
你可以在原始数组上获得迭代器并在算法中使用它们。
int data[] = {5,3,2,1,4};
std::sort(std::begin(data), std::end(data))。
for (auto i : data) {
std::cout << i << std::endl;
}
如果你正在处理原始指针(ptr + size),那么你可以使用以下技术。
size_t size = 0;
int * data = get_data_from_library(size)。
auto b = data;
auto e = b + size。
std::sort(b, e)。
for (auto it = b; it != e; ++it) {
cout << *it << endl;
}
UPD: 然而,上面的例子设计得不好。库返回给我们一个原始指针,我们不知道底层缓冲区在哪里分配,谁应该释放它。
通常情况下,调用者会提供一个缓冲区给函数来填充数据。在这种情况下,我们可以预先分配向量并使用其底层缓冲区。
std::vector<int> v;
v.resize(256); // 分配一个256个整数的缓冲区
size_t size = get_data_from_library(v.data(), v.size() );
// 缩减为实际数据。注意,这里没有做任何内存的重新分配或复制。
v.resize(size)。
std::sort(v.begin(), v.end())。
for (auto i : v) {
cout << i << endl;
}
当使用C++11或以上版本时,我们甚至可以让get_data_from_library()返回一个向量。由于移动操作,将不会有内存拷贝。
现在我想使用std::vector来访问和修改这些值。
你不能这样做。这不是std::vector
的作用。std::vector
管理它自己的缓冲区,它总是从一个分配器中获得。它从来不会获取另一个缓冲区的所有权(除了从另一个相同类型的向量中获取)。
另一方面,你也不需要这样做,因为......。
原因是我需要在该数据上应用<算法>的算法(排序、交换元素等)。
这些算法工作在迭代器上。一个指针就是一个数组的迭代器。你不需要一个向量。
std::sort(data, data + size);
与"<算法>"中的函数模板不同,一些工具如range-for,std::begin
/std::end
和C++20的range虽然不能只对一对迭代器工作,但它们可以对容器如向量工作。可以为iterator+size创建一个包装类,它的行为就像一个范围,并与这些工具一起工作。C++20将在标准库中引入这样的封装类。std::span
。
实际上,你几乎可以使用 "std::vector "来做这件事,通过滥用自定义分配器功能来返回一个指向你想查看的内存的指针。标准并不能保证这一点(填充、对齐、返回值的初始化;你必须在分配初始大小时费尽心思,对于非基元,你还需要砍掉你的构造函数),但在实践中,我希望通过足够的调整来实现。
永远不要这样做。这是丑陋的、令人惊讶的、黑客的和不必要的。标准库的算法已经被设计成可以像对待向量一样对待原始数组。关于这一点,请看其他答案。
除了关于 [tag:c++20] 和 [gsl:span] 中出现的 std::span
的另一个好建议之外,包括您自己的 (轻量级) span
类,在那之前已经很容易了 (请随意复制)。
template<class T>
struct span {
T* first;
size_t length;
span(T* first_, size_t length_) : first(first_), length(length_) {};
using value_type = std::remove_cv_t<T>;//primarily needed if used with templates
bool empty() const { return length == 0; }
auto begin() const { return first; }
auto end() const { return first + length; }
};
static_assert(_MSVC_LANG <= 201703L, "remember to switch to std::span");
特别值得一提的是,如果你对更通用的范围概念感兴趣的话,还有一个提升范围库[tag:boost-range]:https://www.boost.org/doc/libs/1_60_0/libs/range/doc/html/range/reference/utilities/iterator_range.html。
范围概念也会在[tag:c++20]中出现。
你可以使用从C++11开始提供的std::reference_wrapper
。
#include <iostream>
#include <iterator>
#include <vector>
#include <algorithm>
int main()
{
int src_table[] = {5, 4, 3, 2, 1, 0};
std::vector< std::reference_wrapper< int > > dest_vector;
std::copy(std::begin(src_table), std::end(src_table), std::back_inserter(dest_vector));
// if you don't have the array defined just a pointer and size then:
// std::copy(src_table_ptr, src_table_ptr + size, std::back_inserter(dest_vector));
std::sort(std::begin(dest_vector), std::end(dest_vector));
std::for_each(std::begin(src_table), std::end(src_table), [](int x) { std::cout << x << '\n'; });
std::for_each(std::begin(dest_vector), std::end(dest_vector), [](int x) { std::cout << x << '\n'; });
}
正如其他人所指出的,std::vector
必须拥有底层内存(短于搞一个自定义分配器),所以不能使用。
其他人也推荐了c++20的span,然而显然那需要c++20。
我推荐span-lite的span。引用它的副标题。
span lite - 一个类似于C++20的span,适用于C++98、C++11及以后的单文件头库。
它提供了一个非所有权和可变异的视图(例如,你可以变异元素和它们的顺序,但不能插入它们),而且正如引文所说,它没有依赖性,可以在大多数编译器上工作。
你的例子。
#include <algorithm>
#include <cstddef>
#include <iostream>
#include <nonstd/span.hpp>
static int data[] = {5, 1, 2, 4, 3};
// For example
int* get_data_from_library()
{
return data;
}
int main ()
{
const std::size_t size = 5;
nonstd::span<int> v{get_data_from_library(), size};
std::sort(v.begin(), v.end());
for (auto i = 0UL; i < v.size(); ++i)
{
std::cout << v[i] << "\n";
}
}
印刷品
1
2
3
4
5
这样做还有一个好处,如果有一天你真的换成了c++20,你应该可以直接把这个nonstd::span
换成std::span
。