Elegir el tipo de variables de índice

11

Usamos el tipo Integer para representar variables de índice la mayor parte del tiempo. Pero en algunas situaciones, nos vemos obligados a elegir

std::vector<int> vec;
....

for(int i = 0; i < vec.size(); ++i)
....

Esto hará que el compilador genere la advertencia de que el uso mixto de variables con signo / sin signo. si hago que el índice sea variable como for( size_t i = 0; i < vec.size(); i++ ), (o an unsigned int) solucionará los problemas.

Cuando es más específico usar tipos de Windows, la mayoría de las API de Windows se ocupan de DWORD (que se definió como sin signo durante mucho tiempo).

Entonces, cuando uso una iteración similar, nuevamente causaré la misma advertencia. Ahora si lo reescribo como

DWORD dwCount;
....

for(DWORD i = 0; i < dwCount; ++i)
....

Esto me parece un poco raro. Puede ser el problema con las percepciones.

Estoy de acuerdo en que se supone que debemos usar el mismo tipo de variable de índice para evitar que los problemas de rango puedan ocurrir con las variables de índice. Por ejemplo, si estamos usando

_int64 i64Count; // 
....

for(_int64 i = 0; i < i64Count; ++i)
....

Pero en el caso de DWORD, o enteros sin signo, ¿hay algún problema para reescribirlo como

for(int i = 0; (size_t)i < vec.size(); ++i)

¿Cómo trabaja la mayoría de las personas con problemas similares?

sarat
fuente
44
¿Por qué usarías un entero con signo para representar el índice? Es como usar el vector de enteros para almacenar una cadena.
Šimon Tóth
3
@Let_Me_Be: Porque hace que las condiciones de contorno sean más fáciles de probar. Por ejemplo, en una cuenta regresiva a cero, una prueba menor que antes de ejecutar el cuerpo del bucle no puede funcionar con un valor sin signo. Del mismo modo, un recuento hasta el máximo no funciona. Por supuesto, en ese caso, un entero con signo tampoco funcionará (porque no puede representar un valor tan grande).
Yttrill
"Esto hará que el compilador genere la advertencia de que el uso mixto de variables con signo / sin signo". Ese es solo uno de los dos problemas con los que tendrá que lidiar. En muchos casos std::size_tes un rango más alto que int (o incluso largo). Si el tamaño del vector excede alguna vez std::numeric_limits<int>::max(), lamentará haber usado int.
Adrian McCarthy

Respuestas:

11

vector tiene un typedef que le dice el tipo correcto a usar: -

for(std::vector<int>::size_type i = 0; i < thing.size(); ++i)
{
}

Sin embargo, casi siempre se define como size_t, pero no puede confiar en eso

JohnB
fuente
8
No es realmente una mejora en la legibilidad, en mi humilde opinión.
Doc Brown
No, pero ya que es la única manera de saber lo que es el tipo correcto para utilizar para un índice en el vector, que en realidad no importa ...
JohnB
44
En estos días en c ++ 11 solo se usa auto
JohnB
66
@JohnB ¿Quieres decir como auto i = 0? Eso no ayuda en absoluto, iconvertirse en un int.
cenit
1
La legibilidad se puede mejorar con using index_t = std::vector<int>::size_type;.
Toby Speight
4
std::vector<int> vec;

for(int i = 0; i < vec.size(); ++i)

Use un iterador para esto, no un forbucle.

Para los demás, siempre que el tipo de variable sea del mismo tamaño, static_castdebería funcionar bien (es decir, DWORDa int16_t)

Demian Brecht
fuente
2
for (std::vector<int>::iterator i = vec.begin(); i != vec.end(); ++i)Es un dolor de escribir. Tener for (auto i = vec.begin();...es mucho más legible. Por supuesto, foreachtambién está en C ++ 11.
David Thornley
3

El caso que describiste también es una de las cosas que no me gustan en C ++. Pero he aprendido a vivir con eso, ya sea usando

for( size_t i = 0; i < vec.size(); i++ )

o

for( int i = 0; i < (int)vec.size(); i++ )

(por supuesto, esto último solo cuando no hay riesgo de tener un desbordamiento int).

Doc Brown
fuente
3

La razón por la que le advierte sobre la comparación entre firmado y sin signo es porque el valor con signo probablemente se convertirá en sin signo, que podría no ser lo que espera.

En su ejemplo (en comparación intcon size_t), intse convertirá implícitamente a size_t(a menos que de intalguna manera tenga un rango mayor que size_t). Por lo tanto, si el valor intes negativo, es probable que sea mayor que el valor con el que lo está comparando debido a la envoltura. Esto no será un problema si su índice nunca es negativo, pero igual recibirá esa advertencia.

En su lugar, utilice un tipo sin signo (por ejemplo unsigned int, size_to, como John B recomienda , std::vector<int>::size_type) para la variable de índice:

for(unsigned int i = 0; i < vec.size(); i++)

Sin embargo, tenga cuidado al hacer la cuenta regresiva:

for(unsigned int i = vec.size()-1; i >= 0; i--) // don't do this!

Lo anterior no funcionará porque i >= 0siempre es cierto cuando ino está firmado. En su lugar, use el " operador de flecha " para los bucles que cuentan hacia atrás:

for (unsigned int i = vec.size(); i-- > 0; )
    vec[i] = ...;

Como señalan otras respuestas, normalmente desea utilizar un iterador para atravesar a vector. Aquí está la sintaxis de C ++ 11:

for (auto i = vec.begin(); i != vec.end(); ++i)
Joey Adams
fuente
1
Esto todavía corre el riesgo de que un unsigned intno sea lo suficientemente grande como para contener el tamaño.
Adrian McCarthy
2

Una nueva opción para C ++ 11, puede hacer cosas como las siguientes

for(decltype(vec.size()) i = 0; i < vec.size(); ++i) {...}

y

for(decltype(dWord) i = 0; i < dWord; ++i) {...}

Si bien se repite un poco más de lo que lo haría el bucle for básico, no es tan extenso como las formas anteriores a '11 de especificar valores, y usar este patrón de manera consistente funcionará para la mayoría, si no todos, los posibles términos que quiere comparar, lo que lo hace ideal para la refactorización de código. Incluso funciona para casos simples como este:

int x = 3; int final = 32; for(decltype(final) i = x; i < final; ++i)

Además, si bien debe usarlo autosiempre que esté configurando iun valor inteligente (como vec.begin()), decltypefunciona cuando está configurando una constante como cero, donde auto solo resolvería eso intporque 0 es un literal entero simple.

Para ser honesto, me gustaría ver un mecanismo compilador para extender la autodeterminación de tipo para los incrementadores de bucle para ver el valor que se compara.

Matías
fuente
1

Yo uso un elenco para int, como en for (int i = 0; i < (int)v.size(); ++i). Si, es feo. Lo culpo por el estúpido diseño de la biblioteca estándar donde decidieron usar enteros sin signo para representar tamaños. (Para ... ¿qué? ¿Extender el rango un bit?)

zvrba
fuente
1
¿En qué situación sería significativo un tamaño de colección de algo negativo? Para mí, usar enteros sin signo para varios tamaños de colección me parece una opción sensata . Que yo sepa, tomando la longitud de una cadena rara vez devuelto un resultado negativo tampoco ...
una CVn
1
¿En qué situación sería significativo para una prueba simple como if(v.size()-1 > 0) { ... }devolver verdadero para un contenedor vacío? El problema es que los tamaños también se usan a menudo en aritmética, especialmente. con contenedores basados ​​en índices, que están pidiendo problemas dado que no están firmados. Básicamente, el uso de tipos sin signo para otra cosa que no sea 1) manipulaciones bit a bit, o 2) la aritmética modular es un problema.
zvrba
2
buen punto. Si bien realmente no veo el punto de su ejemplo en particular (probablemente solo escribiría, if(v.size() > 1) { ... }ya que eso aclara la intención, y como una ventaja adicional, el tema de firmado / no firmado se anula), sí veo cómo en algunos casos específicos La firma puede ser útil. Estoy corregido.
un CVn
1
@Michael: Estoy de acuerdo en que fue un ejemplo artificial. Aún así, a menudo escribo algoritmos con bucles anidados: for (i = 0; i <v.size () - 1; ++ i) for (j = i + 1; j <v.size (); ++ j) .. si v está vacío, el bucle externo se ejecuta (size_t) -1 veces. Entonces, tengo que verificar v.empty () antes del bucle o enviar v.size () a un tipo con signo, lo que personalmente creo que son soluciones feas. Elijo un lanzamiento ya que es menos LOC, no if () s => menos posibilidades de error. (Además, en el segundo complemento, el desbordamiento de conversión devuelve un número negativo, por lo que el bucle no se ejecuta en absoluto.)
zvrba
Ampliar el rango en 1 bit fue (y sigue siendo) muy útil en sistemas de 16 bits.
Adrian McCarthy