Подтвердить что ты не робот

С++ STL: Могут ли массивы использоваться прозрачно с функциями STL?

Я был в предположении, что функции STL могут использоваться только с контейнерами данных STL (например, vector), пока я не увижу этот фрагмент кода:

#include <functional>
#include <iostream>
#include <numeric>
using namespace std;

int main()
{
    int a[] = {9, 8, 7};
    cerr << "Sum: " << accumulate(&a[0], &a[3], 0, plus<int>()) << endl;
    return 0;
}

Он компилируется и запускается без каких-либо предупреждений или ошибок с g++, давая правильную выходную сумму 24.

Является ли такое использование массивов с функциями STL разрешено стандартом С++/STL ? Если да, то как архаичные структуры, такие как массивы, вписываются в грандиозный план STL шаблонных итераторов, контейнеров и функций? Кроме того, существуют ли какие-либо оговорки или подробности в таком использовании, что программист должен осторожно?

4b9b3361

Ответ 1

Ну, вы спрашиваете о массиве. Вы можете просто получить указатель на его элементы, поэтому в основном сводится к вопросу о том, могут ли указатели использоваться прозрачно с функциями STL. Указатель фактически является самым мощным видом итератора. Существуют разные типы

  • Входной итератор: только вперед и один проход, и только читать
  • Итератор вывода: только вперед и один проход, и только пишите

  • Forward iterator: только вперед и чтение/запись
  • Двунаправленный итератор: вперед и назад и чтение/запись
  • Итератор произвольного доступа. Произвольные шаги вперед и назад на одном дыхании и чтение/запись.

Теперь каждый итератор во второй группе поддерживает все вещи всех итераторов, упомянутых перед ним. Указатель моделирует последний тип итераторов - итератор с произвольным доступом. Вы можете добавить/вычесть произвольное целое число, и вы можете читать и писать. И все, кроме выходного итератора, имеет operator->, который можно использовать для доступа к элементу типа элемента, который мы перебираем.

Обычно итераторы имеют несколько typedefs как членов

  • value_type - то, что итератор выполняет итерацию (int, bool, string,...)
  • ссылка - ссылка на value_type
  • указатель - указатель на value_type
  • difference_type - в каком типе расстояние между двумя итераторами (возвращается std::distance).
  • iterator_category - это тип тега: он набирается типом, который представляет тип итератора. либо std::input_iterator_tag,..., std::random_access_iterator_tag. Алгоритмы могут использовать его для перегрузки на разных итераторах (например, std::distance быстрее для итераторов с произвольным доступом, потому что он может просто вернуть a - b)

Теперь указатель, конечно, не имеет этих членов. С++ имеет шаблон iterator_traits и специализируется на указателях. Поэтому, если вы хотите получить тип значения любого итератора, вы делаете

iterator_traits<T>::value_type

И будь то указатель или какой-либо другой итератор, он даст вам значение_имя этого итератора.

Итак - да, указатель может быть очень хорошо использован с алгоритмами STL. Как уже упоминалось, даже std::vector<T>::iterator может быть T*. Указатель - очень хороший пример итератора. Потому что он настолько прост, но в то же время настолько силен, что он может перебирать диапазон.

Ответ 2

Стандарт разработал итераторы, чтобы чувствовать и вести себя как можно больше, как указатели. Кроме того, поскольку итераторы основаны на шаблонах, единственное, что важно, это то, что тип итератора имеет соответствующие операторы. Результатом является то, что указатели будут работать точно так же, как итераторы произвольного доступа.

Фактически, возможная реализация std::vector<T>::iterator заключается в том, чтобы просто сделать ее T*.

Конечно, для массива у вас не будет полезных методов begin() и end(), чтобы найти допустимый диапазон итераторов, но это проблема, которую вы всегда имеете с массивами стилей C.

Изменить: На самом деле, как уже упоминалось в комментариях и других ответах, вы можете реализовать эти функции для массивов, если массив не является динамическим и не разложился в указатель. Но основной момент заключался в том, что вы должны быть более осторожны, чем при использовании стандартных контейнеров.

Ответ 3

Краткий ответ: алгоритмы STL обычно определяются для работы с итераторами разного рода. Итератор определяется его поведением: он должен быть разыменован с помощью *, он должен быть инкрементным c++ и другими вещами, которые также определяют, какой именно итератор он (наиболее общим является случайный доступ). Помните, что алгоритмы STL - это шаблоны, поэтому вопрос является одним из синтаксисов. Точно так же экземпляр класса с указанным operator() работает синтаксически точно так же, как функция, поэтому их можно использовать взаимозаменяемо.

Указатель делает все необходимое для итератора с произвольным доступом. Таким образом, это итератор с произвольным доступом и может использоваться как таковой в алгоритмах STL. Вы можете посмотреть на векторные реализации; вы, скорее всего, обнаружите, что vector<whatever>::iterator является whatever *.

Это не делает массив допустимым контейнером STL, но делает его действительными итераторами STL.

Ответ 4

Является ли такое использование массивов с функциями STL, разрешенными стандартом?

да

Если да, то как архаичные структуры, такие как массивы, вписываются в грандиозный план STL шаблонных итераторов, контейнеров и функций?

Итераторы были разработаны с похожими семантическими указателями.

Кроме того, существуют ли какие-либо оговорки или подробности в таком использовании, о которых следует помнить программисту?

Я предпочитаю следующее использование:

int a[] = {9, 8, 7};
const size_t a_size = lengthof( a );
cerr << "Sum: " << accumulate( a, a + a_size , 0, plus<int>()) << endl;

Или это намного лучше и безопаснее использовать boost:: array:

boost::array< int, 3 > a = { 9, 8, 7 };
cerr << "Sum: " << accumulate( a.begin(), a.end(), 0, plus<int>()) << endl;

Ответ 5

Просто комментарий о Николае answer:

Массивы не являются указателями, даже если они имеют тенденцию к разложению в указатели очень легко. Компилятор имеет больше информации о массиве, чем в контейнере:

namespace array {
   template <typename T, int N>
   size_t size( T (&a)[N] ) {
      return N;
   }
   template <typename T, int N>
   T* begin( T (&a)[N] ) {
      return &a[0];
   }
   template <typename T, int N>
   T* end( T (&a)[N] ) {
      return &a[N];
   }
}
int main()
{
   int theArray[] = { 1, 2, 3, 4 };
   std::cout << array::size( theArray ) << std::endl; // will print 4
   std::cout 
      << std::accumulate( array::begin( theArray ), array::end( theArray ), 0, std::plus<int>() )
      << std::endl; // will print 10
}

Пока вы не можете задать размер массива, компилятор разрешит его при вызове заданных шаблонов.

Теперь, если вы вызываете функцию, которая принимает int a[] (обратите внимание, что нет размера), это похоже на определение параметра int*, и информация о размере теряется. Компилятор не сможет определить размер массива внутри функции: массив распался на указатель.

Если , с другой стороны, вы определяете параметр как int a[10], тогда информация теряется , но вы не сможете вызвать функцию с массивом другого размер. Это полностью отличается от версии C, по крайней мере до C99 в последнее время не проверяется [*]. В C компилятор будет игнорировать число в аргументе и подпись будет эквивалентна предыдущей версии.

@litb: Вы правы. У меня был этот тест, но он ссылается на массив, а не на массив. Спасибо, что указали.

[email protected]:array_size$ cat test.cpp 
void f( int (&x)[10] ) {}
int main()
{
    int array[20];
    f( array ); // invalid initialization of reference of type 'int (&)[10]' from...
}

Ответ 6

Вместо того, чтобы использовать массивы, а затем беспокоиться о передаче их в функции STL (что можно назвать "передовой совместимостью" и, следовательно, хрупким), IMO вы должны использовать std::vector и использовать его (стабильную и надежную) обратную совместимость с функции, которые принимают массивы, если вам когда-либо понадобится их использовать.

Итак, ваш код будет выглядеть следующим образом:

#include <functional>
#include <iostream>
#include <numeric>
#include <vector>
using namespace std;

int main()
{
    vector<int> a(3);
    a[0] = 9;
    a[1] = 8;
    a[2] = 7;
    cerr << "Sum: " << accumulate(a.begin(), a.end(), 0, plus<int>()) << endl;
    return 0;
}

И если вам когда-либо понадобится передать 'a' в C API, вы можете это сделать, благодаря совместимости векторов с двоичной совместимостью с массивами.

Ответ 7

введение в boost::array (простая шаблонная оболочка для обычных массивов, которая также определяет STL-совместимые типы итераторов и begin()/end() и т.д.) содержит некоторое интересное обсуждение их степени совместимости с STL.

Ответ 8

Да, и это специально. Итераторы могут быть реализованы как указатели, и поэтому вы можете использовать указатели в качестве итераторов.

Ответ 9

Модель указателей Trivial Iterator и указатель из модели массивов Random Итератор доступа. Так что да, это совершенно законно.

Если вас интересуют ограничения использования каждого алгоритма S (T) L, ознакомьтесь с итераторами.

Ответ 10

Так как int a [] можно рассматривать как указатель. А в С++ указатели могут увеличиваться и указывать после этого на следующий элемент. И как можно сравнивать указатели, указатели могут использоваться как итераторы.

Существуют требования к итераторам, указанным в стандартном разделе 24.1. И указатели встречают их. Вот некоторые из них

Все итераторы я поддерживают выражение * i

Так же, как обычный указатель на массив гарантирует, что есть указатель значение, указывающее за последним элементом массива, поэтому для любого типа итератора есть значение итератора, которое указывает мимо последнего элемента соответствующий контейнер.

Ответ 11

у STL есть скрытые вещи. Большинство из них работает благодаря итераторам, рассмотрим этот код:

std::vector<int> a = {0,1,2,3,4,5,6,7,8,9};
// this will work in C++0x use -std=c++0x with gcc
// otherwise use push_back()

// the STL will let us create an array from this no problem
int * array = new int[a.size()];
// normally you could 'iterate' over the size creating
// an array from the vector, thanks to iterators you
// can perform the assignment in one call
array = &(*a.begin());

// I will note that this may not be considered an array
// to some. because it only a pointer to the vector.
// However it comes in handy when interfacing with C
// Instead of copying your vector to a native array
// to pass to a C function that accepts an int * or
// anytype for that matter, you can just pass the
// vector iterators .begin().

// consider this C function
extern "C" passint(int *stuff) { ... }

passint(&(*a.begin())); // this is how you would pass your data.

// lets not forget to delete our allocated data
delete[] a;