una - C++ STL: ¿Se pueden usar los arreglos de forma transparente con las funciones STL?
imprimir un vector en c (11)
Supongo que las funciones de STL solo podrían usarse con contenedores de datos de STL (como vector
) hasta que vi este fragmento de código:
#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;
}
Se compila y ejecuta sin advertencias ni errores con g ++, lo que da una suma de salida correcta de 24.
¿Este uso de matrices con funciones STL está permitido por el estándar C ++ / STL? En caso afirmativo, ¿cómo encajan las estructuras arcaicas como las matrices en el gran plan STL de iteradores, contenedores y funciones con plantilla? Además, ¿existen advertencias o detalles en tal uso que el programador debe tener cuidado ?
¿Este uso de matrices con funciones STL está permitido por la norma?
sí
En caso afirmativo, ¿cómo encajan las estructuras arcaicas como las matrices en el gran plan STL de iteradores, contenedores y funciones con plantilla?
Los iteradores fueron diseñados con semánticos similares a los punteros.
Además, ¿existen advertencias o detalles en tal uso que el programador debe tener cuidado?
Prefiero el siguiente uso:
int a[] = {9, 8, 7};
const size_t a_size = lengthof( a );
cerr << "Sum: " << accumulate( a, a + a_size , 0, plus<int>()) << endl;
O es mucho mejor y más seguro usar boost :: array:
boost::array< int, 3 > a = { 9, 8, 7 };
cerr << "Sum: " << accumulate( a.begin(), a.end(), 0, plus<int>()) << endl;
Bueno, preguntas por una matriz. Simplemente puede obtener fácilmente un puntero a sus elementos, por lo que básicamente se reduce a la cuestión de si los punteros se pueden usar de forma transparente con las funciones STL. Un puntero en realidad es el iterador más poderoso. Hay diferentes tipos
- Iterador de entrada : solo reenviar y una pasada, y solo leer
- Iterador de salida : solo reenviar y una pasada, y solo escribir
- Reenviar iterador : Sólo reenviar, y leer / escribir
- Iterador bidireccional : hacia adelante y hacia atrás, y lectura / escritura
- Iterador de acceso aleatorio : pasos arbitrarios hacia adelante y hacia atrás en una respiración, y lectura / escritura
Ahora cada iterador en el segundo grupo soporta todas las cosas de todos los iteradores mencionados antes. Un puntero modela el último tipo de iteradores: un iterador de acceso aleatorio. Puede sumar / restar un entero arbitrario y puede leer y escribir. Y todos excepto el iterador de salida tiene un operator->
que puede usarse para acceder a un miembro del tipo de elemento que repetimos.
Normalmente, los iteradores tienen varios typedefs como miembros
- value_type: lo que itera el iterador (int, bool, string, ...)
- referencia - referencia al tipo_valor
- puntero - puntero al tipo de valor
- Difference_type: qué tipo de distancia tiene dos iteradores (devuelto por
std::distance
). - iterator_category: este es un tipo de etiqueta: está tipificado en un tipo que representa el tipo de iterador. o bien
std::input_iterator_tag
, ...,std::random_access_iterator_tag
. Los algoritmos pueden usarlo para sobrecargar en diferentes tipos de iteradores (comostd::distance
es más rápido para los iteradores de acceso aleatorio, porque solo puede devolvera - b
)
Ahora, un puntero por supuesto no tiene esos miembros. C ++ tiene una plantilla iterator_traits
y la especializa para punteros. Entonces, si quieres obtener el tipo de valor de cualquier iterador, lo haces
iterator_traits<T>::value_type
Y ya sea un puntero o algún otro iterador, le dará el value_type de ese iterador.
Entonces, sí, un puntero se puede usar muy bien con algoritmos STL. Como alguien más mencionó, incluso std::vector<T>::iterator
puede ser un T*
. Un puntero es un muy buen ejemplo de un iterador incluso. Porque es muy simple pero al mismo tiempo tan poderoso que puede iterar en un rango.
Como int a [] se puede tratar como un puntero. Y en C ++, los punteros pueden incrementarse y luego apuntar al siguiente elemento. Y como los punteros se pueden comparar, los punteros se pueden usar como iteradores.
Hay requisitos para los iteradores señalados en la sección estándar 24.1. Y los punteros se encuentran con ellos. Aquí hay algunos de ellos.
Todos los iteradores son compatibles con la expresión * i
Del mismo modo que un puntero normal a una matriz garantiza que hay un valor de puntero que apunta más allá del último elemento de la matriz, para cualquier tipo de iterador hay un valor de iterador que apunta más allá del último elemento de un contenedor correspondiente.
El STL tiene cosas ocultas. La mayoría de esto funciona gracias a los iteradores, considera este código:
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''s 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''s 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;
El estándar ha diseñado iteradores para que se sientan y se comporten de la mejor manera posible. Además, como los iteradores se basan en plantillas, lo único relevante es que el tipo de iterador tiene los operadores adecuados definidos. El resultado es que los punteros se comportarán de forma inmediata como los iteradores de acceso aleatorio.
De hecho, una posible implementación de std::vector<T>::iterator
es simplemente convertirlo en un T*
.
Por supuesto, para una matriz no tendrá los métodos de begin()
y end()
útiles para encontrar el rango de iteradores válido, pero ese es el problema que siempre tiene con las matrices de estilo C.
Edición: En realidad, como se ha mencionado en los comentarios y otras respuestas, puede implementar esas funciones para matrices si la matriz no es dinámica y no se ha desintegrado en un puntero. Pero mi punto básico fue que hay que tener más cuidado que al usar los contenedores estándar.
En lugar de usar matrices y luego preocuparse por pasarlas a las funciones de STL (lo que podríamos llamar ''compatibilidad hacia adelante'', y por lo tanto es frágil), debe usar std :: vector y usar su compatibilidad hacia atrás (estable y confiable) con funciones que toma matrices, si alguna vez necesitas usarlas.
Entonces tu código se convierte en:
#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;
}
Y si alguna vez necesita pasar ''a'' a una API de C, puede hacerlo, gracias a la compatibilidad binaria de vectores con matrices.
La introducción a boost::array
(un envoltorio con plantilla simple para arreglos convencionales, que también defines tipos de iteradores compatibles con STL y begin()
/ end()
etc.) contiene una interesante discusión de su grado de compatibilidad con STL.
Los punteros modelan el iterador trivial y el puntero de las matrices modelan el iterador de acceso aleatorio . Así que sí, es perfectamente legal.
Si está interesado en las restricciones de uso de cada algoritmo S (T) L, familiarícese con los modelos de iteradores .
Respuesta corta: los algoritmos STL generalmente se definen para trabajar con iteradores de varios tipos. Un iterador se define por su comportamiento: debe ser desreferible con *, debe ser incrementable con ++, y varias otras cosas que también definen qué tipo de iterador es (el más general es el acceso aleatorio). Recuerde que los algoritmos STL son plantillas, por lo que la pregunta es de sintaxis. De manera similar, una instancia de clase con operador () definida funciona de manera sintáctica como una función, por lo que se pueden usar indistintamente.
Un puntero hace todo lo necesario para ser un iterador de acceso aleatorio. Por lo tanto, es un iterador de acceso aleatorio, y se puede utilizar como tal en los algoritmos STL. Podrías mirar implementaciones vectoriales; es muy probable que encuentre que el vector<whatever>::iterator
es un whatever *
.
Esto no hace que una matriz sea un contenedor STL válido, pero sí hace que los punteros sean iteradores STL válidos.
Sí y esto es a propósito. Los iteradores se pueden implementar como punteros y, por lo tanto, se pueden usar punteros como iteradores.
Solo un comentario sobre la answer de Mykola:
Las matrices no son punteros, incluso si tienden a descomponerse en punteros realmente fácilmente. El compilador tiene más información en una matriz que en un contenedor:
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
}
Si bien no puede preguntar sobre el tamaño de la matriz, el compilador lo resolverá cuando llame a las plantillas dadas.
Ahora, si llama a una función que toma un int a[]
(tenga en cuenta que no hay tamaño), es similar a definir un parámetro int*
, y la información de tamaño se pierde en el camino. El compilador no podrá determinar el tamaño de la matriz dentro de la función: la matriz se ha desintegrado en un puntero.
Si , por otro lado, define el parámetro como int a[10]
entonces la información se pierde , pero no podrá llamar a la función con una matriz de un tamaño diferente. Esto es completamente diferente a la versión C, al menos antes de C99 no se ha verificado últimamente [*]. En C, el compilador ignorará el número en el argumento y la firma será equivalente a la versión anterior.
@litb: Tienes razón. Tuve esta prueba, pero es con una referencia a una matriz, no con una matriz. Gracias por mencionarlo.
dribeas@golden: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...
}