2009-04-28 47 views
43

Estoy un poco confundido acerca de la diferencia entre el uso del algoritmo std :: remove. Específicamente, no puedo entender qué se elimina cuando uso este algoritmo. Escribí un pequeño código de prueba como esta:Diferencia entre borrar y eliminar

std::vector<int> a; 
a.push_back(1); 
a.push_back(2); 

std::remove(a.begin(), a.end(), 1); 


int s = a.size(); 

std::vector<int>::iterator iter = a.begin(); 
std::vector<int>::iterator endIter = a.end(); 

std::cout<<"Using iter...\n"; 
for(; iter != endIter; ++iter) 
{ 
    std::cout<<*iter<<"\n"; 
} 

std::cout<<"Using size...\n"; 
for(int i = 0; i < a.size(); ++i) 
{ 
    std::cout<<a[i]<<"\n"; 
} 

La salida fue 2,2 en los dos casos.

Sin embargo, si uso borrar con los quite algo como esto:

a.erase(std::remove(a.begin(), a.end(), 1), a.end()); 

me sale la salida como 2.

Así que mis preguntas son:

(1). ¿Hay algún uso de std :: remove que no sea usarlo con la función borrar?

(2). Incluso después de hacer std :: remove, ¿por qué a.size() devuelve 2 y no 1?

Leí el artículo en el libro Effective STL de Scott Meyer sobre la expresión borrar-eliminar. Pero todavía estoy teniendo esta confusión.

+2

Encuentro que la parte más difícil de esto es borrar y eliminar significar casi lo que se guarda en inglés, por lo que es fácil olvidar cuál es cuál –

Respuesta

47

remove() en realidad no elimina los elementos del contenedor - solo deshace los elementos no eliminados hacia adelante sobre los elementos eliminados. La clave es darse cuenta de que remove() está diseñado para funcionar no solo en un contenedor sino en cualquier par de iteradores directos arbitrarios: eso significa que no puede eliminar realmente los elementos, porque un par de iteradores arbitrarios no necesariamente tiene el capacidad de eliminar elementos.

Por ejemplo, punteros al comienzo y al final de una matriz regular C son los iteradores de avance y, como tal, se puede utilizar con remove():

int foo[100]; 

... 

remove(foo, foo + 100, 42); // Remove all elements equal to 42 

Aquí es obvio que no se puede cambiar el tamaño de remove() la matriz!

17

std::remove no elimina los objetos reales, sino que los empuja hacia el final del contenedor. La eliminación y desasignación real de la memoria se realiza mediante borrado. Entonces:

(1). ¿Hay algún uso de std :: remove que no sea usarlo con la función borrar?

Sí, ayuda a conseguir un par de iteradores a una nueva secuencia sin tener la preocupación acerca de la asignación adecuada de-etc

(2). Incluso después de hacer std :: remove, ¿por qué a.size() devuelve 2 y no 1?

El contenedor aún se mantiene en esos objetos, solo tiene un nuevo conjunto de iteradores para trabajar. Por lo tanto, el tamaño sigue siendo lo que solía ser.

+4

Hmm. En realidad, std :: remove() no mueve los elementos eliminados al final del contenedor; las posiciones restantes del contenedor contendrán sus valores originales. (Debe funcionar de esta forma para conservar el tiempo de O (n) con iteradores de avance.) –

+0

j_random_hacker, hmm en realidad parece que después de que hizo el trabajo, el rango después del iterador devuelto tampoco debe contener ningún elemento "eliminado", que es un requisito adicional que aún no he considerado, por lo que esos elementos no son del todo indeterminados. Entonces mi respuesta fue incorrecta y la eliminé –

+1

dice: "Elimina todos los elementos referidos por el iterador i en el rango [primero, último] para los que se cumplen las siguientes condiciones correspondientes: * i == valor" que interpreto como ese ese rango ya no contiene ningún valor "eliminado" al final. Interesante, no lo he sabido Pero no creo que uno pueda confiar en que esos elementos sean los mismos que antes. Uno ciertamente no puede si ese rango hubiera contenido un valor "eliminado", al menos. Así que creo que cplusplus.com está equivocado de nuevo imo: D –

6

más simple que se puede topar con:

erase() es algo que puede hacer a un elemento en un recipiente. Dado un iterador/índice en un contenedor, erase(it) elimina aquello a lo que hace referencia el iterador desde el contenedor.

remove() es algo que puede hacer a un rango, reorganiza ese rango pero no borra nada del rango.

2

eliminar no "realmente" eliminar nada, porque no puede.

Para "realmente" eliminar los elementos del contenedor, necesita acceder a las API del contenedor. Donde remove solo funciona con iteradores independientemente de a qué contenedores apuntan esos iteradores. Por lo tanto, incluso si remove quiere una "eliminación real", no puede.

Quitar sobreescritura "removidos" elementos por los elementos siguientes que no fueron extraídas y entonces le corresponde a la persona que llama para decidir utilizar la nueva lógica de regresar end en lugar del original end.

En su caso, elimine lógicamente eliminado 1 de vector a pero el tamaño se mantuvo en 2. Borrar borró realmente los elementos del vector. [A partir de vector new end a old end]

La idea principal de remove es que no puede cambiar el número de elementos y que sólo eliminar los elementos de una gama según criterios.

6

me enfrenté al mismo problema, tratando de entender la diferencia. las explicaciones que se han dado hasta ahora son correctas sobre el dinero, pero solo las entendí después de ver un ejemplo;

#include <algorithm> 
#include <string> 
#include <iostream> 
#include <cctype> 

int main() 
{ 
    std::string str1 = "Text with some spaces"; 
    std::string::iterator it = remove(str1.begin(), str1.end(), 't'); 
    std::cout << str1 << std::endl;// prints "Tex wih some spaceses" 
    for (str1.begin();it != str1.end(); ++it) 
    { 
     std::cout << *it; //prints "es" 
    } 

} 

como se puede ver, la eliminación, sólo se mueve la minúscula 't' al final de la cadena, mientras que el retorno de una nueva iterador hasta el final de la nueva cadena (nueva cadena es la cadena antigua hasta a donde se inserta el elemento eliminado) es por eso que cuando se imprime el iterador que recibió de "eliminar"

"Text with some spaces" 
    ^ ^removes both 't', then shift all elements forward -1 //what we want to remove 
    "Text with some spaces" 
         ^end of string     -2 //original state of string 
    "Tex with some spacess" 
          ^end of string      -3 //first 't' removed 
    "Tex wih some spaceses" 
          ^end of string      -4 //second 't' removed 
    "Tex wih some spaceses" 
         ^new iterator that remove() returned -5 // the state of string after "remove" and without "erase" 

si pasa el iterador que obtuvo del paso 5 para "borrado()" se sabrá para borrar desde allí hasta el final de la cadena cambiar el tamaño de la cadena en proceso

5

¿Qué hace std :: remove?

Aquí está el código de seudo de std::remove. Tómese unos segundos para ver qué está haciendo y luego lea la explicación.

Iter remove(Iter start, Iter end, T val) { 
    Iter destination = start; 

    //loop through entire list 
    while(start != end) { 
     //skip element(s) to be removed 
     if (*start == val) { 
      start++; 
     } 
     else //retain rest of the elements 
      *destination++ = *start++; 
    } 

    //return the new end of the list 
    return destination; 
} 

Observe que eliminar simplemente movió hacia arriba los elementos de la secuencia, sobrescribiendo los valores que desea eliminar. Entonces, los valores que quería eliminar se han ido, pero ¿cuál es el problema? Digamos que tienes vector con valores {1, 2, 3, 4, 5}. Después de llamar a quitar para val = 3, el vector ahora tiene {1, 2, 4, 5, 5}. Es decir, 4 y 5 se movieron hacia arriba de modo que 3 se ha ido del vector, pero el tamaño del vector no ha cambiado. Además, el final del vector contiene ahora una copia restante adicional de 5.

¿Qué hace vector :: borrar?

std::erase toma el inicio y el final del rango que desea deshacerse.No toma el valor que desea eliminar, solo el inicio y el final del rango. Aquí está pseudo código para saber cómo funciona:

erase(Iter first, Iter last) 
{ 
    //copy remaining elements from last 
    while (last != end()) 
     *first++ = *last++; 

    //truncate vector 
    resize(first - begin()); 
} 

lo tanto la operación de borrado realmente cambia el tamaño del envase y así libera la memoria.

El idioma remove-borrado

La combinación de std::remove y std::erase le permite eliminar elementos coincidentes del recipiente de manera que recipiente en realidad obtener truncado si se eliminaran elementos. Aquí se muestra cómo hacerlo:

//first do the remove 
auto removed = std::remove(vec.begin(), vec.end(), val); 

//now truncate the vector 
vec.erase(removed, vec.end()); 

Esto se conoce como eliminar-borrar idioma. ¿Por qué está diseñado así? La idea es que la operación de encontrar elementos es más genérica e independiente del contenedor subyacente (solo depende de los iteradores). Sin embargo, la operación de borrado depende de cómo el contenedor está almacenando la memoria (por ejemplo, puede haber enlazado la lista en lugar de la matriz dinámica). Por lo tanto, STL espera que los contenedores hagan su propio borrado a la vez que proporcionan una operación genérica de "eliminación" para que todos los contenedores no tengan que implementar ese código. En mi opinión, el nombre es muy engañoso y std::remove debería haberse llamado std::find_move.

Nota: El código anterior es estrictamente pseudocódigo. La implementación real de STL es más inteligente, por ejemplo, al usar std::move en lugar de copiar.