C++ STL: conjunto y multiconjunto de contenedores asociativos

1. conjunto

descripción general

El contenedor de mapas y el contenedor de mapas múltiples se introdujeron anteriormente. De manera similar, la biblioteca estándar STL de C ++ también proporciona dos contenedores, conjunto y conjunto múltiple, que también pertenecen a contenedores asociativos.

A diferencia de los contenedores de mapas y mapas múltiples, cada par clave-valor almacenado en el contenedor establecido requiere que la clave clave y el valor del valor sean iguales. Por ejemplo, hay 2 conjuntos de datos de pares clave-valor de la siguiente manera:

{<'a', 1>, <'b', 2>, <'c', 3>}
{<'a', 'a'>, <'b', 'b'>, <'c', 'c'>}

Obviamente, las claves y los valores de cada par clave-valor en el primer conjunto de datos no son iguales, mientras que las claves y los valores de cada par clave-valor en el segundo conjunto son iguales. Para el contenedor de conjuntos, solo se puede almacenar el segundo conjunto de pares clave-valor, pero no se puede almacenar el primer conjunto de pares clave-valor.

Según esta característica del contenedor de conjuntos, cuando se utiliza el contenedor de conjuntos para almacenar pares clave-valor, solo necesita proporcionarle el valor de cada par clave-valor (es decir, el valor de la clave). Aún tomando como ejemplo el almacenamiento del segundo grupo de pares clave-valor anterior, solo necesita proporcionar {'a','b','c'} para el contenedor establecido, y el contenedor podrá almacenarlos correctamente.

A través del estudio anterior, sabemos que los contenedores de mapas y mapas múltiples ordenarán los pares clave-valor almacenados de acuerdo con el tamaño de la clave, y el contenedor de conjuntos también hará lo mismo, excepto que la clave y el valor de cada clave-valor Los pares en el contenedor del conjunto son iguales, ordenar por clave es equivalente a ordenar por valor.

Además, los valores de cada elemento almacenado utilizando un contenedor establecido deben ser distintos. Más importante aún, desde un punto de vista gramatical, el contenedor del conjunto no obliga a que el tipo del elemento de almacenamiento se modifique constantemente, es decir, el valor del elemento almacenado en el contenedor del conjunto se puede modificar. Sin embargo, para evitar que el usuario modifique el valor de los elementos en el contenedor, el estándar C++ restringe todos los comportamientos posibles que pueden lograr esta operación, de modo que en circunstancias normales, el usuario no puede modificar el valor de los elementos en el conjunto. envase.

Para principiantes, no intente modificar directamente el valor de los elementos almacenados en el contenedor del conjunto, lo que probablemente destruirá el orden de los elementos en el contenedor del conjunto. La forma más correcta de modificar el valor de los elementos en el contenedor del conjunto es: eliminar el primer elemento y luego agregar un elemento modificado.

El contenedor establecido se define en el archivo de encabezado <set> y reside en el espacio de nombres estándar. Por lo tanto, si desea utilizar el contenedor establecido en el programa, el código del programa primero debe contener la siguiente declaración:

#include <set>
using namespace std;

Tenga en cuenta que la segunda línea de código no es necesaria, de lo contrario, deberá indicar manualmente el espacio de nombres estándar cuando utilice el contenedor configurado en programas posteriores (recomendado para principiantes).

La plantilla de clase para el contenedor establecido se define de la siguiente manera:

template < class T,                        // 键 key 和值 value 的类型
           class Compare = less<T>,        // 指定 set 容器内部的排序规则
           class Alloc = allocator<T>      // 指定分配器对象的类型
           > class set;

Tenga en cuenta que, dado que cada par clave-valor almacenado en el contenedor establecido tiene exactamente la misma clave y valor, lo que significa que tienen el mismo tipo, en la definición de la plantilla de clase de contenedor establecido, solo se usa el primer parámetro para configurar el valor almacenado. tipo de datos.

Para los 3 parámetros en la plantilla de clase establecida, los 2 últimos parámetros tienen sus propios valores predeterminados, y solo los 2 primeros parámetros son necesarios en casi todos los escenarios, y el tercer parámetro no se utilizará.

función miembro

La siguiente tabla enumera los métodos de miembros comunes proporcionados por el contenedor de conjuntos y sus respectivas funciones:

método miembro Función
comenzar() Devuelve un iterador bidireccional que apunta al primer elemento (nota, el primero ordenado) del contenedor. Si el contenedor establecido está calificado como constante, este método devuelve un iterador bidireccional de tipo constante.
fin() Devuelve un iterador bidireccional que apunta a la posición después del último elemento del contenedor (tenga en cuenta que es el último que se ha ordenado), generalmente utilizado junto con comenzar (). Si el contenedor establecido está calificado como constante, este método devuelve un iterador bidireccional de tipo constante.
comenzar() Devuelve un iterador bidireccional inverso que apunta al último elemento (nota, el último elemento ordenado). Si el contenedor establecido está calificado como constante, este método devuelve un iterador bidireccional inverso de tipo constante.
desgarrar() Devuelve un iterador bidireccional inverso que apunta a la posición anterior a la posición del primer elemento (nota, el primer elemento ordenado). Si el contenedor establecido está calificado como constante, este método devuelve un iterador bidireccional inverso de tipo constante.
comenzar() Tiene la misma función que comenzar (), excepto que se agrega el atributo const encima, que no se puede usar para modificar el valor del elemento almacenado en el contenedor.
algunos() Tiene la misma función que end(), pero sobre esta base se agrega un atributo constante, que no se puede utilizar para modificar el valor del elemento almacenado en el contenedor.
crbegin() Tiene la misma función que rbegin (), pero en base a ella, se agrega un atributo constante, que no se puede usar para modificar el valor del elemento almacenado en el contenedor.
crédito() Tiene la misma función que rend(), excepto que en base a él, se agrega un atributo constante, que no se puede usar para modificar el valor del elemento almacenado en el contenedor.
encontrar(valor) Busque el elemento cuyo valor es val en el contenedor establecido; si lo encuentra correctamente, devuelva el iterador bidireccional que apunta al elemento; de lo contrario, devuelva el mismo iterador que el método end(). Además, si el contenedor establecido está calificado como constante, este método devuelve un iterador bidireccional de tipo constante.
límite_inferior(valor) Devuelve un iterador bidireccional que apunta al primer elemento mayor o igual que val en el contenedor del conjunto actual. Si el contenedor establecido está calificado como constante, este método devuelve un iterador bidireccional de tipo constante.
límite_superior(valor) Devuelve un iterador que apunta al primer elemento mayor que val en el contenedor del conjunto actual. Si el contenedor establecido está calificado como constante, este método devuelve un iterador bidireccional de tipo constante.
rango_igual(valor) Este método devuelve un objeto de par (contiene dos iteradores bidireccionales), donde los valores de retorno de los métodos pair.first y lower_bound() son equivalentes, y los valores de retorno de los métodos pair.first y lower_bound() son equivalentes. Es decir, el método devolverá un rango que contiene elementos con el valor val (cada elemento en el contenedor establecido es único, por lo que el rango contiene como máximo un elemento).
vacío() Devuelve verdadero si el contenedor está vacío; en caso contrario, falso.
tamaño() Devuelve el número de elementos almacenados en el contenedor del conjunto actual.
tamaño máximo() Devuelve el número máximo de elementos que puede contener el contenedor establecido. Los diferentes sistemas operativos tienen diferentes valores de retorno.
insertar() Inserte elementos en el contenedor establecido.
borrar() Elimina los elementos almacenados en el contenedor establecido.
intercambio() Intercambia todos los elementos almacenados en 2 contenedores establecidos. Esto significa que los 2 contenedores utilizados deben ser del mismo tipo.
claro() Borre todos los elementos en el contenedor establecido, es decir, establezca el tamaño () del contenedor establecido en 0.
lugar() Construye un nuevo elemento directamente en la posición especificada en el contenedor del conjunto actual. Su efecto es el mismo que insert(), pero más eficiente.
emplace_hint() En esencia, es lo mismo que la forma en que emplace() construye nuevos elementos en el contenedor del conjunto, la diferencia es que el usuario debe proporcionar a este método un iterador que indique la posición donde se genera el nuevo elemento, y es el primer elemento. del método parámetro.
contar(valor) En el contenedor del conjunto actual, busque la cantidad de elementos cuyo valor es val y devuélvalo. Tenga en cuenta que dado que el valor de cada elemento en el contenedor establecido es único, el valor máximo de retorno de esta función es 1.

Varias formas de crear un contenedor de conjuntos de C++

Los métodos comunes para crear contenedores de conjuntos generalmente incluyen los cinco métodos siguientes.

  1. Llame al constructor predeterminado para crear un contenedor de conjuntos vacío. Por ejemplo:
std::set<std::string> myset;

Por lo tanto, se crea un contenedor de conjuntos, que adopta las std::less<T>reglas predeterminadas y ordena los elementos de tipo cadena almacenados en orden ascendente. Tenga en cuenta que dado que el contenedor de conjuntos admite la adición de nuevos elementos al interior en cualquier momento, a menudo se utiliza el método de crear un contenedor de conjuntos vacío.

  1. Además, la plantilla de clase establecida también admite la inicialización del contenedor establecido mientras se crea. Por ejemplo:
std::set<std::string> myset{"http://c.biancheng.net/java/",
                            "http://c.biancheng.net/stl/",
                            "http://c.biancheng.net/python/"};

De este modo, se crea el contenedor myset que contiene 3 elementos de cadena. Dado que adopta la std::less<T>regla predeterminada, el orden de sus elementos de cadena de almacenamiento interno es el siguiente:

"http://c.biancheng.net/java/"
"http://c.biancheng.net/python/"
"http://c.biancheng.net/stl/"
  1. La plantilla de clase de conjunto también proporciona un constructor de copia (replicación), que puede copiar todos los elementos almacenados en el contenedor de conjunto existente al nuevo contenedor de conjunto mientras crea un nuevo contenedor de conjunto.

Por ejemplo, según el contenedor myset creado de la segunda forma, ejecute el siguiente código:

std::set<std::string> copyset(myset);
// 等同于
//std::set<std::string> copyset = myset

Sobre la base de la creación del contenedor de conjunto de copias, esta línea de código también copiará todos los elementos almacenados en el contenedor myset al contenedor de conjunto de copias.

Además, el estándar C++ 11 también agrega un constructor de movimiento a la plantilla de clase de conjunto, cuya función es crear un nuevo contenedor de conjunto e inicializarlo con un contenedor de conjunto temporal. Por ejemplo:

set<string> retSet() {
    std::set<std::string> myset{ "http://c.biancheng.net/java/",
                            "http://c.biancheng.net/stl/",
                            "http://c.biancheng.net/python/" };
    return myset;
}
std::set<std::string> copyset(retSet());
// 或者
//std::set<std::string> copyset = retSet();

Tenga en cuenta que dado que el valor de retorno de la función retSet() es un contenedor de conjunto temporal, cuando se inicializa el contenedor de conjunto de copias, se llama al constructor de movimiento en la plantilla de clase de conjunto en lugar del constructor de copia.

Obviamente, ya sea que se llame al constructor de copia o al constructor de copia, es necesario asegurarse de que los tipos de los dos contenedores sean exactamente iguales.

  1. Sobre la base del tercer método, la plantilla de clase de conjunto también admite tomar algunos elementos en el contenedor de conjunto existente para inicializar el nuevo contenedor de conjunto. Por ejemplo:
std::set<std::string> myset{ "http://c.biancheng.net/java/",
                    "http://c.biancheng.net/stl/",
                    "http://c.biancheng.net/python/" };
std::set<std::string> copyset(++myset.begin(), myset.end());

El contenedor del conjunto de copias así inicializado contiene sólo las dos cadenas siguientes:

"http://c.biancheng.net/python/"
"http://c.biancheng.net/stl/"
  1. Todos los contenedores de conjuntos creados de la manera anterior adoptan las std::less<T>reglas predeterminadas. De hecho, con la ayuda del segundo parámetro en la definición de plantilla de la clase de conjunto, podemos modificar manualmente las reglas de clasificación en el contenedor de conjunto. Por ejemplo:
std::set<std::string,std::greater<string> > myset{
    "http://c.biancheng.net/java/",
    "http://c.biancheng.net/stl/",
    "http://c.biancheng.net/python/"};

Al seleccionar el orden descendente de std::greater<string>, el orden de almacenamiento de los elementos en el contenedor myset es:

"http://c.biancheng.net/stl/"
"http://c.biancheng.net/python/"
"http://c.biancheng.net/java/"

iterador

A diferencia del contenedor de mapas, la función miembro at() no se proporciona en la plantilla de clase de contenedor establecida en C++ STL, ni está sobrecargado el operador []. Por lo tanto, si desea acceder a los elementos almacenados en el contenedor establecido, solo puede usar el iterador del contenedor establecido.

Vale la pena mencionar que el tipo de iterador configurado por la biblioteca estándar C++ STL para el contenedor establecido es un iterador bidireccional. Esto significa que, suponiendo que p es un iterador de este tipo, solo se puede operar ++p、p++、--p、p--、*py la comparación entre dos iteradores bidireccionales solo puede usar ==el !=operador or.

Entre todas las funciones miembro proporcionadas por la plantilla de clase de contenedor establecida, las funciones miembro que devuelven iteradores se muestran en la siguiente tabla:

método miembro Función
comenzar() Devuelve un iterador bidireccional que apunta al primer elemento (nota, el primero ordenado) del contenedor. Si el contenedor establecido está calificado como constante, este método devuelve un iterador bidireccional de tipo constante.
fin() Devuelve un iterador bidireccional que apunta a la posición después del último elemento del contenedor (tenga en cuenta que es el último que se ha ordenado), generalmente utilizado junto con comenzar (). Si el contenedor establecido está calificado como constante, este método devuelve un iterador bidireccional de tipo constante.
comenzar() Devuelve un iterador bidireccional inverso que apunta al último elemento (nota, el último elemento ordenado). Si el contenedor establecido está calificado como constante, este método devuelve un iterador bidireccional inverso de tipo constante.
desgarrar() Devuelve un iterador bidireccional inverso que apunta a la posición anterior a la posición del primer elemento (nota, el primer elemento ordenado). Generalmente se usa junto con rbegin(). Si el contenedor establecido está calificado como constante, este método devuelve un iterador bidireccional inverso de tipo constante.
comenzar() Tiene la misma función que comenzar (), excepto que se agrega el atributo const encima, que no se puede usar para modificar el valor del elemento almacenado en el contenedor.
algunos() Tiene la misma función que end(), pero sobre esta base se agrega un atributo constante, que no se puede utilizar para modificar el valor del elemento almacenado en el contenedor.
crbegin() Tiene la misma función que rbegin (), pero en base a ella, se agrega un atributo constante, que no se puede usar para modificar el valor del elemento almacenado en el contenedor.
crédito() Tiene la misma función que rend(), excepto que en base a él, se agrega un atributo constante, que no se puede usar para modificar el valor del elemento almacenado en el contenedor.
encontrar(valor) Busque el elemento cuyo valor es val en el contenedor establecido; si lo encuentra correctamente, devuelva el iterador bidireccional que apunta al elemento; de lo contrario, devuelva el mismo iterador que el método end(). Además, si el contenedor establecido está calificado como constante, este método devuelve un iterador bidireccional de tipo constante.
límite_inferior(valor) Devuelve un iterador bidireccional que apunta al primer elemento mayor o igual que val en el contenedor del conjunto actual. Si el contenedor establecido está calificado como constante, este método devuelve un iterador bidireccional de tipo constante.
upper_bound(val) 返回一个指向当前 set 容器中第一个大于 val 的元素的迭代器。如果 set 容器用 const 限定,则该方法返回的是 const 类型的双向迭代器。
equal_range(val) 该方法返回一个 pair 对象(包含 2 个双向迭代器),其中 pair.first 和 lower_bound() 方法的返回值等价,pair.second 和 upper_bound() 方法的返回值等价。也就是说,该方法将返回一个范围,该范围中包含的值为 val 的元素(set 容器中各个元素是唯一的,因此该范围最多包含一个元素)。

注意,以上成员函数返回的迭代器,指向的只是 set 容器中存储的元素,而不再是键值对。另外,以上成员方法返回的迭代器,无论是 const 类型还是非 const 类型,都不能用于修改 set 容器中的值。

  1. 下面程序以 begin()/end() 为例,演示了如何使用相关迭代器遍历 set 容器:
#include <iostream>
#include <set>
#include <string>
using namespace std;

int main()
{
    // 创建并初始化set容器
    std::set<std::string> myset{ "http://c.biancheng.net/java/",
                                 "http://c.biancheng.net/stl/",
                                 "http://c.biancheng.net/python/"
    };
    // 利用双向迭代器,遍历myset
    for (auto iter = myset.begin(); iter != myset.end(); ++iter) {
        cout << *iter << endl;
    }
    return 0;
}

程序执行结果为:

http://c.biancheng.net/java/
http://c.biancheng.net/python/
http://c.biancheng.net/stl/

再次强调,正如程序第 15 行代码所示的那样,因为 iter 迭代器指向的是 set 容器存储的某个元素,而不是键值对,因此通过 *iter 可以直接获取该迭代器指向的元素的值。

  1. 除此之外,如果只想遍历 set 容器中指定区域内的部分数据,则可以借助 find()、lower_bound() 以及 upper_bound() 实现。通过调用它们,可以获取一个指向指定元素的迭代器。

需要特别指出的是,equal_range(val) 函数的返回值是一个 pair 类型数据,其包含 2 个迭代器,表示 set 容器中和指定参数 val 相等的元素所在的区域,但由于 set 容器中存储的元素各不相等,因此该函数返回的这 2 个迭代器所表示的范围中,最多只会包含 1 个元素。

举个例子:

#include <iostream>
#include <set>
#include <string>
using namespace std;

int main()
{
    // 创建并初始化set容器
    std::set<std::string> myset{ "http://c.biancheng.net/java/",
                                 "http://c.biancheng.net/stl/",
                                 "http://c.biancheng.net/python/"
    };
   
    set<string>::iterator iter = myset.find("http://c.biancheng.net/python/");
    for (;iter != myset.end();++iter)
    {
        cout << *iter << endl;
    }
    return 0;
}

程序执行结果为:

http://c.biancheng.net/python/
http://c.biancheng.net/stl/

值得一提的是,虽然 C++ STL 标准中,set 类模板中包含 lower_bound()、upper_bound()、equal_range() 这 3 个成员函数,但它们更适用于 multiset 容器,几乎不会用于操作 set 容器。

set insert()方法详解

如果想向 set 容器中继续添加元素,可以借助 set 类模板提供的 insert() 方法。为满足不同场景的需要,C++ 11 标准的 set 类模板中提供了多种不同语法格式的 insert() 成员方法,它们各自的功能和用法如下所示。

  1. 只要给定目标元素的值,insert() 方法即可将该元素添加到 set 容器中,其语法格式如下:
// 普通引用方式传参
pair<iterator,bool> insert (const value_type& val);
// 右值引用方式传参
pair<iterator,bool> insert (value_type&& val);

其中,val 表示要添加的新元素,该方法的返回值为 pair 类型。

以上 2 种格式的区别仅在于传递参数的方式不同,即第一种采用普通引用的方式传参,而第二种采用右值引用的方式传参。右值引用为 C++ 11 新添加的一种引用方式。

可以看到,以上 2 种语法格式的 insert() 方法,返回的都是 pair 类型的值,其包含 2 个数据,一个迭代器和一个 bool 值:

  • 当向 set 容器添加元素成功时,该迭代器指向 set 容器新添加的元素,bool 类型的值为 true;
  • 如果添加失败,即证明原 set 容器中已存有相同的元素,此时返回的迭代器就指向容器中相同的此元素,同时 bool 类型的值为 false。

举个例子:

#include <iostream>
#include <set>
#include <string>
using namespace std;

int main()
{
    // 创建并初始化set容器
    std::set<std::string> myset;
    // 准备接受 insert() 的返回值
    pair<set<string>::iterator, bool> retpair;
    // 采用普通引用传值方式
    string str = "http://c.biancheng.net/stl/";
    retpair = myset.insert(str);
    cout << "iter->" << *(retpair.first) << " " << "bool = " << retpair.second << endl;
    // 采用右值引用传值方式
    retpair = myset.insert("http://c.biancheng.net/python/");
    cout << "iter->" << *(retpair.first) << " " << "bool = " << retpair.second << endl;
    return 0;
}

程序执行结果为:

iter->http://c.biancheng.net/stl/ bool = 1
iter->http://c.biancheng.net/python/ bool = 1

通过观察输出结果不难看出,程序中两次借助 insert() 方法向 set 容器中添加元素,都成功了。

  1. insert() 还可以指定将新元素插入到 set 容器中的具体位置,其语法格式如下:
// 以普通引用的方式传递 val 值
iterator insert (const_iterator position, const value_type& val);
// 以右值引用的方式传递 val 值
iterator insert (const_iterator position, value_type&& val);

以上 2 种语法格式中,insert() 函数的返回值为迭代器:

  • 当向 set 容器添加元素成功时,该迭代器指向容器中新添加的元素;
  • 当添加失败时,证明原 set 容器中已有相同的元素,该迭代器就指向 set 容器中相同的这个元素。

举个例子:

#include <iostream>
#include <set>
#include <string>
using namespace std;

int main()
{
    // 创建并初始化set容器
    std::set<std::string> myset;
    // 准备接受 insert() 的返回值
    set<string>::iterator iter;
    // 采用普通引用传值方式
    string str = "http://c.biancheng.net/stl/";
    iter = myset.insert(myset.begin(),str);
    cout << "myset size =" << myset.size() << endl;
    // 采用右值引用传值方式
    iter = myset.insert(myset.end(),"http://c.biancheng.net/python/");
    cout << "myset size =" << myset.size() << endl;
    return 0;
}

程序执行结果为:

myset size =1
myset size =2

注意,使用 insert() 方法将目标元素插入到 set 容器指定位置后,如果该元素破坏了容器内部的有序状态,set 容器还会自行对新元素的位置做进一步调整。也就是说,insert() 方法中指定新元素插入的位置,并不一定就是该元素最终所处的位置。

  1. insert() 方法支持向当前 set 容器中插入其它 set 容器指定区域内的所有元素,只要这 2 个 set 容器存储的元素类型相同即可。insert() 方法的语法格式如下:
template <class InputIterator>
 void insert (InputIterator first, InputIterator last);

其中 first 和 last 都是迭代器,它们的组合 [first,last) 可以表示另一 set 容器中的一块区域,该区域包括 first 迭代器指向的元素,但不包含 last 迭代器指向的元素。举个例子:

#include <iostream>
#include <set>
#include <string>
using namespace std;

int main()
{
    // 创建并初始化set容器
    std::set<std::string> myset{ "http://c.biancheng.net/stl/",
                                "http://c.biancheng.net/python/",
                                "http://c.biancheng.net/java/" };
    // 创建一个同类型的空 set 容器
    std::set<std::string> otherset;
    // 利用 myset 初始化 otherset
    otherset.insert(++myset.begin(), myset.end());
    // 输出 otherset 容器中的元素
    for (auto iter = otherset.begin(); iter != otherset.end(); ++iter) {
        cout << *iter << endl;
    }
    return 0;
}

程序执行结果为:

http://c.biancheng.net/python/
http://c.biancheng.net/stl/

注意,程序第 15 行在初始化 otherset 容器时,选取的是 myset 容器中从第 2 个元素开始(包括此元素)直到容器末尾范围内的所有元素,所以程序输出结果中只有 2 个字符串。

  1. 采用如下格式的 insert() 方法,可实现一次向 set 容器中添加多个元素:
void insert ( {E1, E2,...,En} );

其中,Ei 表示新添加的元素。举个例子:

#include <iostream>
#include <set>
#include <string>
using namespace std;

int main()
{
    // 创建并初始化set容器
    std::set<std::string> myset;
    // 向 myset 中添加多个元素
    myset.insert({ "http://c.biancheng.net/stl/",
        "http://c.biancheng.net/python/",
        "http://c.biancheng.net/java/" });
    for (auto iter = myset.begin(); iter != myset.end(); ++iter) {
        cout << *iter << endl;
    }
    return 0;
}

程序执行结果为:

http://c.biancheng.net/java/
http://c.biancheng.net/python/
http://c.biancheng.net/stl/

以上即为 set 类模板中 insert() 成员方法的全部用法。另外,C++ 11 标准的 set 类模板中,还提供有另外 2 个成员方法,分别为 implace() 和 implace_hint() 方法,借助它们不但能实现向 set 容器添加新元素的功能,其实现效率也比 insert() 成员方法更高。

set emplace()和emplace_hint()方法详解

set 类模板提供的所有成员方法中,能实现向指定 set 容器中添加新元素的,只有 3 个成员方法,分别为 insert()、emplace() 和 emplace_hint()。其中 insert() 成员方法的用法已在上节做了详细的介绍,本节重点介绍剩下的这 2 个成员方法。

emplace() 和 emplace_hint() 是 C++ 11 标准加入到 set 类模板中的,相比具有同样功能的 insert() 方法,完成同样的任务,emplace() 和 emplace_hint() 的效率会更高。

  1. emplace() 方法的语法格式如下:
template <class... Args>
 pair<iterator,bool> emplace (Args&&... args);

其中,参数 (Args&&… args) 指的是,只需要传入构建新元素所需的数据即可,该方法可以自行利用这些数据构建出要添加的元素。比如,若 set 容器中存储的元素类型为自定义的结构体或者类,则在使用 emplace() 方法向容器中添加新元素时,构造新结构体变量(或者类对象)需要多少个数据,就需要为该方法传入相应个数的数据。

另外,该方法的返回值类型为 pair 类型,其包含 2 个元素,一个迭代器和一个 bool 值:

  • 当该方法将目标元素成功添加到 set 容器中时,其返回的迭代器指向新插入的元素,同时 bool 值为 true;
  • 当添加失败时,则表明原 set 容器中已存在相同值的元素,此时返回的迭代器指向容器中具有相同键的这个元素,同时 bool 值为 false。

下面程序演示 emplace() 方法的具体用法:

#include <iostream>
#include <set>
#include <string>
using namespace std;

int main()
{
    // 创建并初始化 set 容器
    std::set<string>myset;
    // 向 myset 容器中添加元素
    pair<set<string, string>::iterator, bool> ret = myset.emplace("http://c.biancheng.net/stl/");
    cout << "myset size = " << myset.size() << endl;
    cout << "ret.iter = <" << *(ret.first) << ", " << ret.second << ">" << endl;
    return 0;
}

程序执行结果为:

myset size = 1
ret.iter = <http://c.biancheng.net/stl/, 1>

显然,从执行结果可以看出,通过调用 emplace() 方法,成功向空 myset 容器中添加了一个元素,并且该方法的返回值中就包含指向新添加元素的迭代器。

  1. emplace_hint() 方法的功能和 emplace() 类似,其语法格式如下:
template <class... Args>
 iterator emplace_hint (const_iterator position, Args&&... args);

和 emplace() 方法相比,有以下 2 点不同:

  • 该方法需要额外传入一个迭代器,用来指明新元素添加到 set 容器的具体位置(新元素会添加到该迭代器指向元素的前面);
  • 返回值是一个迭代器,而不再是 pair 对象。当成功添加元素时,返回的迭代器指向新添加的元素;反之,如果添加失败,则迭代器就指向 set 容器和要添加元素的值相同的元素。

下面程序演示 emplace_hint() 方法的用法:

#include <iostream>
#include <set>
#include <string>
using namespace std;

int main()
{
    // 创建并初始化 set 容器
    std::set<string>myset;
    // 在 set 容器的指定位置添加键值对
    set<string>::iterator iter = myset.emplace_hint(myset.begin(), "http://c.biancheng.net/stl/");
    cout << "myset size = " << myset.size() << endl;
    cout << *iter << endl;
    return 0;
}

程序执行结果为:

myset size = 1
http://c.biancheng.net/stl/

注意,和 insert() 方法一样,虽然 emplace_hint() 方法中指定了添加新元素的位置,但 set 容器为了保持数据的有序状态,可能会移动其位置。

以上内容介绍了 emplace() 和 emplace_hint() 的用法,至于比 insert() 执行效率高的原因,可参照 map 容器 emplace() 和 emplace_hint() 比 insert() 效率高的原因,它们是完全一样的。

set删除数据:erase()和clear()方法

如果想删除 set 容器存储的元素,可以选择用 erase() 或者 clear() 成员方法。

  1. set 类模板中,erase() 方法有 3 种语法格式,分别如下:
// 删除 set 容器中值为 val 的元素
size_type erase (const value_type& val);
// 删除 position 迭代器指向的元素
iterator erase (const_iterator position);
// 删除 [first,last) 区间内的所有元素
iterator erase (const_iterator first, const_iterator last);

其中,第 1 种格式的 erase() 方法,其返回值为一个整数,表示成功删除的元素个数;后 2 种格式的 erase() 方法,返回值都是迭代器,其指向的是 set 容器中删除元素之后的第一个元素。

注意,如果要删除的元素就是 set 容器最后一个元素,则 erase() 方法返回的迭代器就指向新 set 容器中最后一个元素之后的位置(等价于 end() 方法返回的迭代器)。

下面程序演示了以上 3 种 erase() 方法的用法:

#include <iostream>
#include <set>
#include <string>
using namespace std;

int main()
{
    // 创建并初始化 set 容器
    std::set<int>myset{1,2,3,4,5};
    cout << "myset size = " << myset.size() << endl;
   
    //1) 调用第一种格式的 erase() 方法
    int num = myset.erase(2); // 删除元素 2,myset={1,3,4,5}
    cout << "1、myset size = " << myset.size() << endl;
    cout << "num = " << num << endl;
    //2) 调用第二种格式的 erase() 方法
    set<int>::iterator iter = myset.erase(myset.begin()); // 删除元素 1,myset={3,4,5}
    cout << "2、myset size = " << myset.size() << endl;
    cout << "iter->" << *iter << endl;
    //3) 调用第三种格式的 erase() 方法
    set<int>::iterator iter2 = myset.erase(myset.begin(), --myset.end()); // 删除元素 3,4,myset={5}
    cout << "3、myset size = " << myset.size() << endl;
    cout << "iter2->" << *iter2 << endl;
    return 0;
}

程序执行结果为:

myset size = 5
1、myset size = 4
num = 1
2、myset size = 3
iter->3
3、myset size = 1
iter2->5
  1. 如果需要删除 set 容器中存储的所有元素,可以使用 clear() 成员方法。该方法的语法格式如下:
void clear();

显然,该方法不需要传入任何参数,也没有任何返回值。举个例子:

#include <iostream>
#include <set>
#include <string>
using namespace std;

int main()
{
    // 创建并初始化 set 容器
    std::set<int>myset{1,2,3,4,5};
    cout << "1、myset size = " << myset.size() << endl;
    // 清空 myset 容器
    myset.clear();
    cout << "2、myset size = " << myset.size() << endl;
    return 0;
}

程序执行结果为:

1、myset size = 5
2、myset size = 0

2. multiset

概述

前面对 set 容器做了详细的介绍。回忆一下,set 容器具有以下几个特性:

  • 不再以键值对的方式存储数据,因为 set 容器专门用于存储键和值相等的键值对,因此该容器中真正存储的是各个键值对的值(value);
  • set 容器在存储数据时,会根据各元素值的大小对存储的元素进行排序(默认做升序排序);
  • 存储到 set 容器中的元素,虽然其类型没有明确用 const 修饰,但正常情况下它们的值是无法被修改的;
  • set 容器存储的元素必须互不相等。

在此基础上,C++ STL 标准库中还提供有一个和 set 容器相似的关联式容器,即 multiset 容器。所谓“相似”,是指 multiset 容器遵循 set 容器的前 3 个特性,仅在第 4 条特性上有差异。和 set 容器不同的是,multiset 容器可以存储多个值相同的元素。

也就是说,multiset 容器和 set 容器唯一的差别在于,multiset 容器允许存储多个值相同的元素,而 set 容器中只能存储互不相同的元素。

和 set 类模板一样,multiset 类模板也定义在<set>头文件,并位于 std 命名空间中。这意味着,如果想在程序中使用 multiset 容器,该程序代码应包含如下语句:

#include <set>
using namespace std;

注意,第二行代码不是必需的,如果不用,则后续程序中在使用 multiset容器时,需手动注明 std 命名空间(建议初学者使用)。

multiset 容器类模板的定义如下所示:

template < class T,                        // 存储元素的类型
           class Compare = less<T>,        // 指定容器内部的排序规则
           class Alloc = allocator<T> >    // 指定分配器对象的类型
           > class multiset;

显然,multiset 类模板有 3 个参数,其中后 2 个参数自带有默认值。值得一提的是,在实际使用中,我们最多只需要使用前 2 个参数即可,第 3 个参数不会用到。

成员函数

multiset 容器提供的成员方法,和 set 容器提供的完全一样,如下表所示:

成员方法 功能
begin() 返回指向容器中第一个(注意,是已排好序的第一个)元素的双向迭代器。如果 multiset 容器用 const 限定,则该方法返回的是 const 类型的双向迭代器。
end() 返回指向容器最后一个元素(注意,是已排好序的最后一个)所在位置后一个位置的双向迭代器,通常和 begin() 结合使用。如果 multiset 容器用 const 限定,则该方法返回的是 const 类型的双向迭代器。
rbegin() 返回指向最后一个(注意,是已排好序的最后一个)元素的反向双向迭代器。如果 multiset 容器用 const 限定,则该方法返回的是 const 类型的反向双向迭代器。
rend() 返回指向第一个(注意,是已排好序的第一个)元素所在位置前一个位置的反向双向迭代器。如果 multiset 容器用 const 限定,则该方法返回的是 const 类型的反向双向迭代器。
cbegin() 和 begin() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改容器内存储的元素值。
cend() 和 end() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改容器内存储的元素值。
crbegin() 和 rbegin() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改容器内存储的元素值。
crend() 和 rend() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改容器内存储的元素值。
find(val) 在 multiset 容器中查找值为 val 的元素,如果成功找到,则返回指向该元素的双向迭代器;反之,则返回和 end() 方法一样的迭代器。另外,如果 multiset 容器用 const 限定,则该方法返回的是 const 类型的双向迭代器。
lower_bound(val) 返回一个指向当前 multiset 容器中第一个大于或等于 val 的元素的双向迭代器。如果 multiset 容器用 const 限定,则该方法返回的是 const 类型的双向迭代器。
upper_bound(val) 返回一个指向当前 multiset 容器中第一个大于 val 的元素的迭代器。如果 multiset 容器用 const 限定,则该方法返回的是 const 类型的双向迭代器。
equal_range(val) 该方法返回一个 pair 对象(包含 2 个双向迭代器),其中 pair.first 和 lower_bound() 方法的返回值等价,pair.second 和 upper_bound() 方法的返回值等价。也就是说,该方法将返回一个范围,该范围中包含所有值为 val 的元素。
empty() 若容器为空,则返回 true;否则 false。
size() 返回当前 multiset 容器中存有元素的个数。
max_size() 返回 multiset 容器所能容纳元素的最大个数,不同的操作系统,其返回值亦不相同。
insert() 向 multiset 容器中插入元素。
erase() 删除 multiset 容器中存储的指定元素。
swap() 交换 2 个 multiset 容器中存储的所有元素。这意味着,操作的 2 个 multiset 容器的类型必须相同。
clear() 清空 multiset 容器中所有的元素,即令 multiset 容器的 size() 为 0。
emplace() 在当前 multiset 容器中的指定位置直接构造新元素。其效果和 insert() 一样,但效率更高。
emplace_hint() 本质上和 emplace() 在 multiset 容器中构造新元素的方式是一样的,不同之处在于,使用者必须为该方法提供一个指示新元素生成位置的迭代器,并作为该方法的第一个参数。
count(val) 在当前 multiset 容器中,查找值为 val 的元素的个数,并返回。

注意,虽然 multiset 容器和 set 容器拥有的成员方法完全相同,但由于 multiset 容器允许存储多个值相同的元素,因此诸如 count()、find()、lower_bound()、upper_bound()、equal_range()等方法,更常用于 multiset 容器。

创建C++ multiset容器的方法

创建 multiset 容器,无疑需要调用 multiset 类模板中的构造函数。值得一提的是,multiset 类模板提供的构造函数,和 set 类模板中提供创建 set 容器的构造函数,是完全相同的。这意味着,创建 set 容器的方式,也同样适用于创建 multiset 容器。

multiset 类模板中提供了 5 种构造函数,也就代表有 5 种创建 multiset 容器的方式,分别如下。

  1. 调用默认构造函数,创建空的 multiset 容器。比如:
std::multiset<std::string> mymultiset;

由此就创建好了一个 mymultiset 容器,该容器采用默认的std::less<T>规则,会对存储的 string 类型元素做升序排序。

注意,由于 multiset 容器支持随时向内部添加新的元素,因此创建空 multiset 容器的方法比较常用。

2)除此之外,multiset 类模板还支持在创建 multiset 容器的同时,对其进行初始化。例如:

std::multiset<std::string> mymultiset{ "http://c.biancheng.net/java/",
                                       "http://c.biancheng.net/stl/",
                                       "http://c.biancheng.net/python/" };

由此即创建好了包含 3 个 string 元素的 mymultiset 容器。由于其采用默认的std::less<T>规则,因此其内部存储 string 元素的顺序如下所示:

"http://c.biancheng.net/java/"
"http://c.biancheng.net/python/"
"http://c.biancheng.net/stl/"
  1. multiset 类模板中还提供了拷贝(复制)构造函数,可以实现在创建新 multiset 容器的同时,将已有 multiset 容器中存储的所有元素全部复制到新 multiset 容器中。

例如,在第 2 种方式创建的 mymultiset 容器的基础上,执行如下代码:

std::multiset<std::string> copymultiset(mymultiset);
// 等同于
//std::multiset<std::string> copymultiset = mymultiset;

该行代码在创建 copymultiset 容器的基础上,还会将 mymultiset 容器中存储的所有元素,全部复制给 copymultiset 容器一份。

另外,C++ 11 标准还为 multiset 类模板新增了移动构造函数,其功能是实现创建新 multiset 容器的同时,利用临时的 multiset 容器为其初始化。比如:

multiset<string> retMultiset() {
    std::multiset<std::string> tempmultiset{ "http://c.biancheng.net/java/",
                            "http://c.biancheng.net/stl/",
                            "http://c.biancheng.net/python/" };
    return tempmultiset;
}
std::multiset<std::string> copymultiset(retMultiset());
// 等同于
//std::multiset<std::string> copymultiset = retMultiset();

注意,由于 retMultiset() 函数的返回值是一个临时 multiset 容器,因此在初始化 copymultiset 容器时,其内部调用的是 multiset 类模板中的移动构造函数,而非拷贝构造函数。

显然,无论是调用复制构造函数还是调用拷贝构造函数,都必须保证这 2 个容器的类型完全一致。

  1. 在第 3 种方式的基础上,multiset 类模板还支持取已有 multiset 容器中的部分元素,来初始化新 multiset 容器。例如:
std::multiset<std::string> mymultiset{ "http://c.biancheng.net/java/",
                                       "http://c.biancheng.net/stl/",
                                       "http://c.biancheng.net/python/" };
std::set<std::string> copymultiset(++mymultiset.begin(), mymultiset.end());

以上初始化的 copyset 容器,其内部仅存有如下 2 个 string 字符串:

"http://c.biancheng.net/python/"
"http://c.biancheng.net/stl/"
  1. 以上几种方式创建的 multiset 容器,都采用了默认的std::less<T>规则。其实,借助 multiset 类模板定义中的第 2 个参数,我们完全可以手动修改 multiset 容器中的排序规则。

下面样例中,使用了 STL 标准库提供的 std::greater<T> 排序方法,作为 multiset 容器内部的排序规则:

std::multiset<std::string, std::greater<string> > mymultiset{
    "http://c.biancheng.net/java/",
    "http://c.biancheng.net/stl/",
    "http://c.biancheng.net/python/" };

通过选用std::greater<string>降序规则,mymultiset 容器中元素的存储顺序为:

"http://c.biancheng.net/stl/"
"http://c.biancheng.net/python/"
"http://c.biancheng.net/java/"

Supongo que te gusta

Origin blog.csdn.net/crossoverpptx/article/details/131743711
Recomendado
Clasificación