C++ 汎用プログラミング/拡張プログラミング/C++ 入門

1 関数テンプレート

テンプレートは一般的な型を作成するもので、再利用性が大幅に向上します。

テンプレートの特徴:

  • テンプレートは直接使用することはできません。これは単なるフレームワークです
  • テンプレートの汎用性は万能薬ではありません

1.1 関数テンプレート

  • C++ もう 1 つのプログラミングのアイデアは次のように呼ばれます。汎用プログラミング、使用される主なテクノロジーはテンプレートです。
  • C++ は、関数テンプレートとクラス テンプレートという 2 つのテンプレート メカニズムを提供します。

関数テンプレートの役割:一般的な関数を作成する場合、関数の戻り値の型や仮引数の型は指定できず、仮想型を使って表現します。

template<typename T> 函数声明或定义

template — 作成するテンプレートを宣言します

typename — その背後にあるシンボルはデータ型であり、クラスに置き換えることができます。

T — 汎用データ型。名前は置換可能 (通常は大文字)

テンプレートとはコンパイラに多数の関数を自動生成させるもので、テンプレートから関数を生成する処理をテンプレートのインスタンス化と呼びます。

  • 2つの変数の値を交換する場合、型が異なるため2つの交換関数を記述する必要がありますが、2つの関数は変数の型が異なるだけで、その他の点はすべて一貫しているため、テンプレートの概念が使用できます。コードを単純化するために
  • テンプレートの目的は、再利用性を向上させるために型をパラメータ化することです。コンパイラは、呼び出しの実際のパラメータ型に応じて、仮パラメータと構造体に対応する型を自動的に生成します。
#include <iostream>
using namespace std;

// 交换整型变量
void swapInt(int& a, int& b)
{
    
    
	int t = a;
	a = b;
	b = t;
}

// 交换 double 型变量
void swapInt(double& a, double& b)
{
    
    
	double t = a;
	a = b;
	b = t;
}

// 利用模板交换变量
template<typename T> void mySwap(T& a, T&b)
{
    
    
	T t = a;
	a = b;
	b = t;
}

void test01()
{
    
    
	int a = 10, b = 20;
	double c = 1.1, d = 2.2;

	// 两种使用模板的方式
	// 1.自动类型推导
	mySwap(a, b);  // 使用自动类型推导时候,要确保a和b的类型是一致的,否则编译错误

	cout << "a = " << a << endl;
	cout << "b = " << b << endl;

	// 2.显示指定类型
	mySwap<double>(c, d);  // 但是这里<>里的类型必须与 c d 类型一直,否则编译不通过
	cout << "c = " << c << endl;
	cout << "d = " << d << endl;
}
  • テンプレートは、使用する前に T のデータ型を決定する必要があります。
template<class T> void func()
{
    
    
	cout << "func 调用" << endl;
}

void test01()
{
    
    
	//func();  // 错误,模板不能独立使用,必须确定出 T 的类型
	func<int>();  // 利用显示指定类型的方式,给 T 一个类型,才可以使用该模板
}
  • ケース: 関数テンプレートを使用して、さまざまなデータ型のデータを大きいものから小さいものまで並べ替えます。
#include <iostream>

using namespace std;


template<typename T> void mySwap(T& a, T& b)
{
    
    
	T t = a;
	a = b;
	b = t;
}

template<typename T> void mySort(T arr[], int len)
{
    
    
	for (int i = 0; i < len; i++)
	{
    
    
		int max = i;
		for (int j = i + 1; j < len; j++)
		{
    
    
			if (arr[max] < arr[j])	max = j;
		}
		if (max != i) mySwap(arr[max], arr[i]);
	}
}

template<typename T> void printArray(T arr[], int len)
{
    
    
	for (int i = 0; i < len; i++) cout << arr[i] << ' ';
	cout << endl;
}

void test01()
{
    
    
	char charArr[] = "bdcfeagh";
	int number = sizeof(charArr) / sizeof(char);
	mySort(charArr, number);
	printArray(charArr, number);
}

void test02()
{
    
    
	int intArr[] = {
    
     7, 5, 8, 1, 3, 9, 2, 4, 6 };
	int num = sizeof(intArr) / sizeof(int);
	mySort(intArr, num);
	printArray(intArr, num);
}

1.2 通常の関数と関数テンプレートの違い

通常の関数と関数テンプレートの違いは次のとおりです。

  • 関数テンプレートが呼び出されたときに、自動型推論が使用されている場合、暗黙的な型変換は発生しません。

  • 明示的な型が指定されている場合、暗黙的な型変換が発生する可能性があります

  • 通常の関数呼び出し時に自動型変換 (暗黙的な型変換) が発生する可能性があります

#include <iostream>
using namespace std;

int myAdd01(int a, int b)
{
    
    
	return a + b;
}

template<typename T> T myAdd02(T a, T b)
{
    
    
	return a + b;
}

void test01()
{
    
    
	int a = 10, b = 20;
	char c = 'c';  // c 对应的ASCII码 99

	// 要注意和实参被 & 修饰的区别:如果是引用修饰的实参,这里类型不一致也会报错
	cout << myAdd01(a, c) << endl;  // 109

	// 自动类型转换,不会发生隐式类型转换
	// cout << myAdd02(a, c) << endl;  // 使用模板,不会发生自动类型转换,这里报错

	// 显示指定类型
	cout << myAdd02<int>(a, c) << endl;  // 这里如果指定类型,则不会报错
} 

1.3 通常関数と関数テンプレートの呼び出し規則

  1. 関数テンプレートと通常の関数の両方が実装できる場合は、通常の関数が最初に呼び出されます。ステートメントのみで関数本体の実装がない場合は、エラーが報告されます。
  2. 空のテンプレートパラメータリストを使用して関数テンプレートを強制的に呼び出すことができます
  3. 関数テンプレートは、仮パラメータ リストまたは型パラメータ リストが異なる限り、オーバーロードすることもできます。
  4. 関数テンプレートの方がより適切な一致を生成できる場合、関数テンプレートが最初に呼び出されます。

関数テンプレートと関数の呼び出し順序は次のとおりです。

  1. まず、完全に一致するパラメータを持つ通常の関数を見つけます。
  2. 次に、完全に一致するパラメータを持つテンプレート関数を見つけます。
  3. 次に、自動型変換後に実際のパラメータと一致する通常の関数を見つけます。
  4. すべて見つかりました、エラー
// 普通函数与函数模板调用规则
void myPrint(int a, int b)
{
    
    
	cout << "调用的普通函数" << endl;
}

template<typename T> void myPrint(T a, T b) 
{
    
     
	cout << "调用的模板" << endl;
}

template<typename T> void myPrint(T a, T b, T c) 
{
    
     
	cout << "调用重载的模板" << endl; 
}

void test01()
{
    
    
	// 1、如果函数模板和普通函数都可以实现,优先调用普通函数
	// 注意 如果告诉编译器,普通函数是有的,但只是声明没有实现,或者不在当前文件内实现,就会报错找不到
	int a = 10;
	int b = 20;
	myPrint(a, b);  // 调用普通函数

	// 2、可以通过空模板参数列表来强制调用函数模板
	myPrint<>(a, b);  // 调用函数模板

	// 3、函数模板也可以发生重载
	int c = 30;
	myPrint(a, b, c);  // 调用重载的函数模板

	// 4、 如果函数模板可以产生更好的匹配,优先调用函数模板
	char c1 = 'a';  // 因为这里 char 类型的会被
	char c2 = 'b';
	myPrint(c1, c2);  // 调用函数模板
}

まとめ: 関数テンプレートが提供されているため、通常の関数は提供しないほうが良いです。そうしないと曖昧さが生じやすくなります。


1.4 テンプレートの制限事項

  • テンプレートの汎用性は万能薬ではありません。次の例で渡されるパラメーターが 2 つの配列の場合、それを実現することはできません。
template<class T>
void f(T a, T b)
{
    
     
	if(a > b) {
    
     ... }
}
  • 上記のコードでは、T のデータ型が Person などのカスタム データ型で渡されると、正しく動作しません。したがって、この問題を解決するために、C++ はこれらの特定の型に特定のテンプレートを提供できるテンプレート オーバーロードを提供します。
#include<iostream>
#include <string>

using namespace std;

class Person
{
    
    
public:
	Person(string name, int age)
	{
    
    
		this->m_Name = name;
		this->m_Age = age;
	}
	string m_Name;
	int m_Age;
};

// 普通函数模板
template<class T> bool myCompare(T& a, T& b)
{
    
    
	if (a == b)	return true;
	else	return false;
}

// 具体化,优先于普通模板。以 template<> 开头,并通过名称指出类型
template<> bool myCompare(Person& p1, Person& p2)
{
    
    
	if (p1.m_Name  == p2.m_Name && p1.m_Age == p2.m_Age)	return true;
	else	return false;
}

void test01()
{
    
    
	int a = 10;
	int b = 20;
	// 内置数据类型可以直接使用通用的函数模板
	bool ret = myCompare(a, b);
	if (ret)	cout << "a == b " << endl;
	else	cout << "a != b " << endl;
}

void test02()
{
    
    
	Person p1("Tom", 10);
	Person p2("Tom", 10);
	// 自定义数据类型,不会调用普通的函数模板
	// 可以创建具体化的 Person 数据类型的模板,用于特殊处理这个类型
	bool ret = myCompare(p1, p2);
	if (ret)	cout << "p1 == p2 " << endl;
	else	cout << "p1 != p2 " << endl;
}

要約:

  • カスタムタイプの一般化は、具体的なテンプレートを使用することで解決できます
  • テンプレートの学習とは、テンプレートを書くことではなく、システムが提供するテンプレートを STL で使用することです。

2種類のテンプレート

  • 機能: 一般クラスを作成します。クラス内のメンバーのデータ型は指定できず、それを表すために仮想型が使用されます。
template<typename T>
#include <iostream>
#include <string>

using namespace std;

// 类模板
template<class NameType, class AgeType> class Person
{
    
    
public:
	Person(NameType name, AgeType age)  // 使用构造函数赋初值
	{
    
    
		this->mName = name;
		this->mAge = age;
	}
	void showPerson()
	{
    
    
		cout << "name: " << this->mName << " age: " << this->mAge << endl;
	}
public:
	NameType mName;
	AgeType mAge;
};

void test01()
{
    
    
	// 指定 NameType 为 string 类型,AgeType 为 int 类型
	Person<string, int> p("孙悟空", 99);
	P1.showPerson();
}

2.1 クラステンプレートと関数テンプレートの違い

クラス テンプレートと関数テンプレートの間には、主に 2 つの違いがあります。

  1. クラス テンプレートに自動型推論を使用する方法はなく (つまり、int や double などのパラメーター リストで自動的に変換できません)、型を明示的に指定する必要があります。
  2. クラス テンプレートには、テンプレート パラメータ リストにデフォルト パラメータを含めることができます
#include <iostream>
#include <string>

using namespace std;

// 类模板
template<class NameType, class AgeType = int>  class Person
{
    
    
public:
	Person(NameType name, AgeType age)
	{
    
    
		this->mName = name;
		this->mAge = age;
	}
	void showPerson()
	{
    
    
		cout << "name: " << this->mName << " age: " << this->mAge << endl;
	}
public:
	NameType mName;
	AgeType mAge;
};

// 1、类模板没有自动类型推导的使用方式
void test01()
{
    
    
	// Person p("孙悟空", 1000); // 错误;类模板使用时候,不可以用自动类型推导
	Person<string, int> p("孙悟空", 1000);  // 必须使用显示指定类型的方式,使用类模板
	p.showPerson();
}

// 2、类模板在模板参数列表中可以有默认参数
void test02()
{
    
    
	Person<string> p("猪八戒", 999);  // 类模板中的模板参数列表,可以指定默认参数
	p.showPerson();
}

2.2 クラステンプレートでのメンバ関数作成のタイミング

クラステンプレートのメンバ関数と通常のクラスのメンバ関数の作成タイミングの違いは以下のとおりです。

  • 通常クラスのメンバー関数は最初から作成可能
  • クラス テンプレート内のメンバー関数は、呼び出されたときに作成されます。
class Person1
{
    
    
public:
	void showPerson1()
	{
    
    
		cout << "Person1 show" << endl;
	}
};

class Person2
{
    
    
public:
	void showPerson2()
	{
    
    
		cout << "Person2 show" << endl;
	}
};

template<class T> class MyClass
{
    
    
public:
	T obj;

	// 可以编译成功
	// 类模板中的成员函数,并不是一开始就创建的,而是在模板调用时再生成
	void fun1() {
    
    
		obj.showPerson1();
	}
	void fun2() {
    
    
		obj.showPerson2();  // Person1 中没有 showPerson2()
	}
};

void test01()
{
    
    
	MyClass<Person1> m;  // 说明 m 是 Person1 的对象

	m.fun1();
	// showPerson2 不是 showPerson1 的成员
	// m.fun2();  // 编译会出错,说明函数调用才会去创建成员函数
}

2.3 関数パラメータとしてのクラステンプレートオブジェクト

入力方法は 3 つあります。

  1. 受信タイプ (直接明示的なオブジェクト データ タイプ) を指定します。
  2. パラメータのテンプレート化 - オブジェクト内のパラメータを渡すためのテンプレートに変換します。
  3. クラス全体をテンプレート化 — このオブジェクト タイプを渡すためにテンプレート化します
#include <iostream>
#include <string>

using namespace std;

// 类模板
template<class NameType, class AgeType = int> class Person
{
    
    
public:
	Person(NameType name, AgeType age)
	{
    
    
		this->mName = name;
		this->mAge = age;
	}

	void showPerson()
	{
    
    
		cout << "name: " << this->mName << " age: " << this->mAge << endl;
	}

public:
	NameType mName;
	AgeType mAge;
};

// 1、指定传入的类型(最常用)
void printPerson1(Person<string, int>& p)
{
    
    
	p.showPerson();
}

void test01()
{
    
    
	Person<string, int > p("孙悟空", 100);
	printPerson1(p);
}

// 2、参数模板化
template <class T1, class T2> void printPerson2(Person<T1, T2>& p)
{
    
    
	p.showPerson();
	cout << "T1的类型为: " << typeid(T1).name() << endl;  // string
	cout << "T2的类型为: " << typeid(T2).name() << endl;  // int
}
void test02()
{
    
    
	Person<string, int> p("猪八戒", 90);
	printPerson2(p);
}

// 3、整个类模板化
template<class T> void printPerson3(T& p)
{
    
    
	cout << "T的类型为: " << typeid(T).name() << endl;  // Person string int
	p.showPerson();

}
void test03()
{
    
    
	Person<string, int> p("唐僧", 30);
	printPerson3(p);
}

2.4 クラステンプレートと継承

クラス テンプレートで継承が発生した場合は、次の点に注意する必要があります。

  • サブクラスが継承する親クラスがクラステンプレートの場合、サブクラスは宣言時に親クラスのTの型を指定する必要があります。指定しない場合、コンパイラはサブクラスにメモリを割り当てることができません。
  • 親クラスでTの型を柔軟に指定したい場合は、サブクラスもクラステンプレートにする必要があります
template<class T> class Base
{
    
    
	T m;
};

// 错误,c++ 编译需要给子类分配内存,必须知道父类中 T 的类型才可以向下继承
// class Son: public Base
class Son: public Base<int>  // 必须指定一个类型
{
    
    
};

void test01()
{
    
    
	Son c;
}

// 如果想灵活指定出父类中 T 的类型,子类也需变为类模板
// 类模板继承类模板,用 T2 指定父类中的 T 类型
template<class T1, class T2> class Son2: public Base<T2>
{
    
    
public:
	Son2()
	{
    
    
		cout << typeid(T1).name() << endl;  // int
		cout << typeid(T2).name() << endl;  // char
	}
};

void test02()
{
    
    
	Son2<int, char> child1;
}

2.5 クラステンプレートメンバー関数のクラス外実装

#include <string>

// 类模板中成员函数类外实现
template<class T1, class T2> class Person {
    
    
public:
	// 成员函数类内声明
	Person(T1 name, T2 age);
	void showPerson();
public:
	T1 m_Name;
	T2 m_Age;
};

// 构造函数类外实现
template<class T1, class T2>
Person<T1, T2>::Person(T1 name, T2 age) {
    
      // Person<T1, T2>:: 指定作用域(也就是谁的构造函数)
	this->m_Name = name;
	this->m_Age = age;
}

// 成员函数类外实现
template<class T1, class T2>
void Person<T1, T2>::showPerson() {
    
    
	cout << "姓名: " << this->m_Name << " 年龄:" << this->m_Age << endl;
}

void test01()
{
    
    
	Person<string, int> p("Tom", 20);
	p.showPerson();
}

2.6 クラステンプレートファイルの書き込み

  • 複数のクラスがある場合、通常、クラスは別のファイルに記述されます。

  • 問題: クラス テンプレートのメンバー関数の作成時が呼び出しフェーズにあるため、サブファイルの書き込み時にリンクを書き込むことができません。

  • 解決:

    • 解決策 1: .cpp ソース ファイルを直接インクルードする (通常は使用されません)
    • 解決策 2: クラスの宣言と実装を .hpp という名前のファイルに書き込むことは必須ではありません。

person.hpp

#pragma once
#include <iostream>
#include <string>

using namespace std;

template<class T1, class T2> class Person {
    
    
public:
	Person(T1 name, T2 age);
	void showPerson();
public:
	T1 m_Name;
	T2 m_Age;
};

// 构造函数类外实现
template<class T1, class T2> Person<T1, T2>::Person(T1 name, T2 age) {
    
    
	this->m_Name = name;
	this->m_Age = age;
}

// 成员函数类外实现
template<class T1, class T2> void Person<T1, T2>::showPerson() {
    
    
	cout << "姓名: " << this->m_Name << " 年龄:" << this->m_Age << endl;
}

main.cpp

#include <iostream>
using namespace std;

// 解决方式1,包含cpp源文件
#include "person.cpp" 

// 解决方式2,将声明和实现写到一起,文件后缀名改为.hpp
#include "person.hpp"

void test01()
{
    
    
	Person<string, int> p("Tom", 10);
	p.showPerson();
}

2.7 クラステンプレートと友達

  • クラス内でのグローバル関数の実装 - クラス内で直接フレンドを宣言するだけです (これが最善です)

  • グローバル関数のクラス外実装 - グローバル関数の存在を事前にコンパイラに知らせる必要がある

#include <iostream>
#include <string>

using namespace std;

// 2、全局函数配合友元  类外实现 - 先做函数模板声明,下方在做函数模板定义,在做友元
template<class T1, class T2> class Person;

// 类模板函数定义,因为这里需要 Person 类,所以,需要在该定义之前,声明 Person 类
template<class T1, class T2> void printPerson2(Person<T1, T2>& p)  // 全局函数,没有必要加作用域
{
    
    
	cout << "类外实现 ---- 姓名: " << p.m_Name << " 年龄:" << p.m_Age << endl;
}

template<class T1, class T2> class Person
{
    
    
	// 1、全局函数配合友元   类内实现
	friend void printPerson(Person<T1, T2> p)
	{
    
    
		cout << "姓名: " << p.m_Name << " 年龄:" << p.m_Age << endl;
	}

	// 2、全局函数配合友元  类外实现
	// 全局函数类外实现,需要提前让编译器知道全局函数的存在
	friend void printPerson2<>(Person<T1, T2>& p);  // 必须要加空模板参数列表,否则只是普通函数,与类外的函数定义不匹配

public:
	Person(T1 name, T2 age)
	{
    
    
		this->m_Name = name;
		this->m_Age = age;
	}

private:
	T1 m_Name;
	T2 m_Age;
};

// 1、全局函数在类内实现
void test01()
{
    
    
	Person<string, int> p("Tom", 20);
	printPerson(p);
}

// 2、全局函数在类外实现
void test02()
{
    
    
	Person<string, int> p("Jerry", 30);
	printPerson2(p);
}

3 STL の概要

3.1 STLの誕生

  • ソフトウェアの世界は長い間、再利用可能なものを構築したいと考えてきました。
  • C++ のオブジェクト指向および汎用プログラミングのアイデアは、再利用性の向上を目的としています。
  • ほとんどの場合、データ構造とアルゴリズムに関する一連の標準が存在せず、多くの反復作業が発生します。
  • データ構造とアルゴリズムの一連の標準を確立するために、STL が誕生しました。

3.2 STLの基本概念

  • STL(標準テンプレートライブラリ、標準テンプレートライブラリ
  • STLはコンテナ(container)、アルゴリズム(algorithm)、イテレータ(iterator)に大別されます。
  • イテレータを介したコンテナとアルゴリズム間のシームレスな接続
  • STL のほとんどすべてのコードは、テンプレート クラスまたはテンプレート関数を使用します。

3.3 STL の 6 つのコンポーネント

  1. コンテナ: データを保存するために使用される、ベクター、リスト、デック、セット、マップなどのさまざまなデータ構造
  2. アルゴリズム: 並べ替え、検索、コピー、for_each など、一般的に使用されるさまざまなアルゴリズム。
  3. Iterator : コンテナとアルゴリズムの間の接着剤として機能します
  4. ファンクター: 関数のように動作し、アルゴリズムのある種の戦略として使用できます。
  5. アダプター: コンテナー、ファンクター、またはイテレーターのインターフェースを変更するもの
  6. スペースコンフィギュレーター: スペースの構成と管理を担当します。

3.4 STL のコンテナ、アルゴリズム、イテレータ

  • コンテナ:保管場所

  • STL コンテナは、最も広く使用されているデータ構造、一般的に使用されるデータ構造 (配列、リンク リスト、ツリー、スタック、キュー、セット、マッピング テーブルなど) を実現します。これらのコンテナは、順次コンテナと連想コンテナの 2 つのタイプに分類されます。

    • シーケンシャル コンテナ: 値の順序を強調し、シーケンシャル コンテナ内の各要素の位置は固定されています。
    • 連想コンテナ: バイナリ ツリー構造。要素間に厳密な物理的な順序関係はありません (ただし、値を並べ替える可能性はあります)
  • アルゴリズム: 問題の解決策

  • 論理的または数学的問題を解決するための限定された手順は、アルゴリズム (アルゴリズム) と呼ばれます。アルゴリズムは、質的変化アルゴリズムと非質的変化アルゴリズムに分けられます。

    • 質的変更アルゴリズム: コピー、置換、削除などの操作中に、区間内の要素の内容が変更されることを意味します。
    • 非定性アルゴリズム:区間内の要素の内容が、検索、カウント、トラバース、極値の検索などの操作中に変更されないことを意味します。
  • イテレータ: コンテナとアルゴリズムの間の接着剤。コンテナの内部表現を公開することなく、コンテナに含まれる要素を順番に検索する方法を提供します。

  • 各コンテナには独自のイテレータがあり、ポインタに非常によく似ています。

イテレータの型:

タイプ 関数 サポート業務
入力反復子 データへの読み取り専用アクセス 読み取り専用。++、==、! をサポートします。=
出力反復子 データへの書き込み専用アクセス 書き込みのみ、++ をサポート
前方反復子 読み取りおよび書き込み操作を実行し、イテレータを進めることができます 読み取りと書き込み、++、== をサポート! =
双方向反復子 読み取りおよび書き込み操作、および順方向および逆方向の操作が可能
読み取りおよび書き込み操作、ジャンプ方式で任意のデータにアクセスできる、最も強力なイテレータ 読み取りと書き込み、++、-、をサポート
ランダムアクセス反復子 読み取りと書き込み、++、-、[n]、-n、<、<=、>、>= をサポート

3 つの関係: アルゴリズムにはコンテナ内の要素にアクセスするための反復子が必要です


3.5 コンテナアルゴリズムイテレータの概要

STL で最も一般的に使用されるコンテナは Vector であり、配列として理解できます。

容器: vector

アルゴリズム: for_each

イテレータ: ベクトル::イテレータ

#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

void MyPrint(int val)
{
    
    
	cout << val << endl;
}

void test01()
{
    
    
	// 创建 vector 类型的容器对象,并且通过模板参数指定容器中存放的数据的类型
	vector<int> v;
	// 向容器中放数据
	v.push_back(10);
	v.push_back(20);
	v.push_back(30);
	v.push_back(40);

	// v.begin() 起始迭代器:返回指向容器中的第一个数据
	// v.end() 结束迭代器:返回指向容器的最后一个元素的下一个位置
	// vector<int>::iterator 中的 iterator 是迭代器名称

	vector<int>::iterator pBegin = v.begin();
	vector<int>::iterator pEnd = v.end();

	// 第一种遍历方式
	while (pBegin != pEnd)	cout << (*pBegin) ++ << endl;

	// 第二种遍历方式
	for (vector<int>::iterator it = v.begin(); it != v.end(); it ++)
		cout << *it << endl;

	// 第三种遍历方式
	// 使用 STL 提供标准遍历算法  头文件 algorithm
	for_each(v.begin(), v.end(), MyPrint);  // 写函数名即可,因为利用了回调机制
}

カスタム データ型をベクターに保存する

#include <vector>
#include <string>

// 自定义数据类型
class Person
{
    
    
public:
	Person(string name, int age) {
    
    
		mName = name;
		mAge = age;
	}
public:
	string mName;
	int mAge;
};

// 存放自定义数据类型对象
void test01()
{
    
    
	vector<Person> v;

	// 创建数据
	Person p1("aaa", 10);
	Person p2("bbb", 20);
	Person p3("ccc", 30);
	Person p4("ddd", 40);
	Person p5("eee", 50);

	v.push_back(p1);
	v.push_back(p2);
	v.push_back(p3);
	v.push_back(p4);
	v.push_back(p5);

	// 两种遍历方式都行
	// for (vector<Person>::iterator it = v.begin(); it != v.end(); it++) cout << "Name:" << (*it).mName << " Age:" << (*it).mAge << endl;
	for (vector<Person>::iterator it = v.begin(); it != v.end(); it++) cout << "Name:" << it->mName << " Age:" << it->mAge << endl;
}

// 存放自定义数据类型指针
void test02()
{
    
    
	vector<Person*> v;

	// 创建数据
	Person p1("aaa", 10);
	Person p2("bbb", 20);
	Person p3("ccc", 30);
	Person p4("ddd", 40);
	Person p5("eee", 50);

	// 向容器中添加指针数据
	v.push_back(&p1);
	v.push_back(&p2);
	v.push_back(&p3);
	v.push_back(&p4);
	v.push_back(&p5);

	// 遍历容器中的数据,看 <> 中的是啥类型,取出来的就是啥类型的数据
	for (vector<Person*>::iterator it = v.begin(); it != v.end(); it++) {
    
    
		Person* p = (*it);
		cout << "Name:" << p->mName << " Age:" << (*it)->mAge << endl;
	}
}

ベクトル コンテナー 2 次元配列に似たネストされたコンテナー

#include <vector>

// 容器嵌套容器
void test01()
{
    
    
	vector<vector<int>> v;

	// 创建小容器
	vector<int> v1;
	vector<int> v2;
	vector<int> v3;
	vector<int> v4;

	// 向小容器中添加数据
	for (int i = 0; i < 4; i++)
	{
    
    
		v1.push_back(i + 1);  // 1 2 3 4
		v2.push_back(i + 2);  // 2 3 4 5
		v3.push_back(i + 3);  // 3 4 5 6
		v4.push_back(i + 4);  // 4 5 6 7
	}

	// 将小容器元素插入到大容器中
	v.push_back(v1);
	v.push_back(v2);
	v.push_back(v3);
	v.push_back(v4);

	// 因为 *it 对应的 <> 也是一个小容器,所以需要再次遍历
	for (vector<vector<int>>::iterator it = v.begin(); it != v.end(); it++) {
    
    
		for (vector<int>::iterator vit = (*it).begin(); vit != (*it).end(); vit++) {
    
    
			cout << *vit << " ";
		}
		cout << endl;
	}
}

4つの共通コンテナ

4.1 文字列コンテナ

自然:

  • string は C++ スタイルの文字列であり、string は本質的にクラスです。

string と char* の違い:

  • char * はポインタです
  • string はクラスであり、クラス内で char* をカプセル化し、 * char 型のコンテナであるこの文字列を管理します。

特徴:

文字列クラスは多くのメンバー メソッドを内部的にカプセル化します。

例: find 検索、copy コピー、delete 削除、replace 置換、insert 挿入

String は char* によって割り当てられたメモリを管理するため、範囲外のコピーや範囲外の値のコピーなどを心配する必要はなく、クラスが責任を負います。


4.1.1 文字列コンストラクター

  • string();// 空の文字列を作成します。例: string str;
  • string(const char* s);** **// 文字列 s で初期化します
  • string(const string& str);// 文字列オブジェクトを使用して別の文字列オブジェクトを初期化します
  • string(int n, char c);// n 文字で初期化します c
#include <string>

// string 构造
void test01()
{
    
    
	string s1;  // 创建空字符串,调用无参构造函数
	const char* str = "hello world";

	string s2(str);  // 把 c_string 转换成了string

	cout << "str2 = " << s2 << endl;  // hello world

	string s3(s2);  // 调用拷贝构造函数
	cout << "str3 = " << s3 << endl;  // hello world

	string s4(10, 'a');
	cout << "str3 = " << s3 << endl;  // aaaaaaaaaa
}

4.1.2 文字列代入操作

代入の関数プロトタイプ:

  • string& operator=(const char* s);// char* 型の文字列が現在の文字列に代入されます
  • string& operator=(const string &s);// 文字列 s を現在の文字列に代入します
  • string& operator=(char c);// 文字は現在の文字列に割り当てられます
  • string& assign(const char *s);// 文字列 s を現在の文字列に代入します
  • string& assign(const char *s, int n);// 文字列 s の最初の n 文字を現在の文字列に代入します
  • string& assign(const string &s);// 文字列 s を現在の文字列に代入します
  • string& assign(int n, char c);// n 文字 c を現在の文字列に代入します
//赋值
void test01()
{
    
    
	string str1;
	str1 = "hello world";
	cout << "str1 = " << str1 << endl;  // hello world

	string str2;
	str2 = str1;
	cout << "str2 = " << str2 << endl;  // hello world

	string str3;
	str3 = 'a';  // 用的比较少
	cout << "str3 = " << str3 << endl;  // a

	string str4;
	str4.assign("hello c++");
	cout << "str4 = " << str4 << endl;  // hello c++

	string str5;
	str5.assign("hello c++",5);
	cout << "str5 = " << str5 << endl;  // hello

	string str6;
	str6.assign(str5);
	cout << "str6 = " << str6 << endl;  // hello

	string str7;
	str7.assign(5, 'x');
	cout << "str7 = " << str7 << endl;  // xxxxx
}

4.1.3 文字列の連結

関数プロトタイプ:

  • string& operator+=(const char* str);// += 演算子をオーバーロードします。
  • string& operator+=(const char c);// += 演算子をオーバーロードします。
  • string& operator+=(const string& str);// += 演算子をオーバーロードします。
  • string& append(const char *s);// 文字列 s を現在の文字列の末尾に接続します
  • string& append(const char *s, int n);// 文字列 s の最初の n 文字を現在の文字列の末尾に接続します
  • string& append(const string &s);// 同演算子+=(const string& str)
  • string& append(const string &s, int pos, int n);// 文字列 s 内の pos から始まる n 文字が文字列の末尾に接続されます
void test01()
{
    
    
	string str1 = "我";

	str1 += "爱玩游戏";

	cout << "str1 = " << str1 << endl;  // str1 = 我爱玩游戏

	str1 += ':';

	cout << "str1 = " << str1 << endl;  // str1 = 我爱玩游戏:

	string str2 = "LOL DNF";

	str1 += str2;

	cout << "str1 = " << str1 << endl;  // str1 = 我爱玩游戏:LOL DNF

	string str3 = "I";
	str3.append(" love ");
	str3.append("game abcde", 4);  // I love game
	// str3.append(str2);
	str3.append(str2, 4, 3);  // 从下标 4 位置开始 ,截取 3 个字符,拼接到字符串末尾
	cout << "str3 = " << str3 << endl;  // I love gameDNF
}

4.1.4 文字列の検索と置換

  • 検索: 指定された文字列が存在するかどうかを検索します。
  • 置換: 指定された位置の文字列を置換します。

関数プロトタイプ:

  • int find(const string& str, int pos = 0) const;// pos から開始して、str が最初に出現する位置を検索します。
  • int find(const char* s, int pos = 0) const;// pos から始めて最初に出現する s を検索します。
  • int find(const char* s, int pos, int n) const;// pos 位置から s の最初の n 文字の最初の位置を検索します。
  • int find(const char c, int pos = 0) const;// 文字 c の最初の出現を検索します。
  • int rfind(const string& str, int pos = npos) const;// pos から開始して str の最後の位置を検索します。
  • int rfind(const char* s, int pos = npos) const;// pos から開始して、最後に出現する s を検索します。
  • int rfind(const char* s, int pos, int n) const;// pos から s の最初の n 文字の最後の位置を検索します
  • int rfind(const char c, int pos = 0) const;// 文字 c の最後の出現を検索します。
  • string& replace(int pos, int n, const string& str);// pos から始まる n 文字を文字列 str に置き換えます
  • string& replace(int pos, int n,const char* s);// pos から始まる n 文字を文字列 s に置き換えます
// 查找
void test01()
{
    
    
	string str1 = "abcdefgde";

	// 从左往右查
	int pos = str1.find("de");  // 如果正确查找,返回下标;否则,返回 -1

	// 未找到
	if (pos == -1) cout << "未找到" << endl;
	// 正确查找
	else cout << "pos = " << pos << endl;

	pos = str1.rfind("de");  // 从右往左查找

	cout << "pos = " << pos << endl;
}

// 替换
void test02()
{
    
    
	string str1 = "abcdefgde";
	str1.replace(1, 3, "1111");  // 从下标 1 起 3 个字符,替换为 1111

	cout << "str1 = " << str1 << endl;  // a1111efg
}

要約:

  • find は左から後ろに検索し、rfind は右から左に検索します。
  • find 文字列を検索した後に見つかった最初の文字の位置を返し、見つからない場合は -1 を返します。
  • replace 置換する場合、どの位置から、何文字、どのような文字列を置換するかを指定します

4.1.5 文字列の比較

  • 比較方法:文字のASCIIコードに従って比較

等しい = 0 を返す

> より大きい場合は 1 を返します

< 未満の場合は -1 を返します

関数プロトタイプ:

  • int compare(const string& s) const;// 文字列 s と比較します
  • int compare(const char* s) const;// 文字列 s と比較します
// 字符串比较
void test01()
{
    
    
	string s1 = "hello";
	string s2 = "aello";

	int ret = s1.compare(s2);

	if (ret == 0) 	cout << "s1 等于 s2" << endl;
	else if (ret > 0) 	cout << "s1 大于 s2" << endl;
	else 	cout << "s1 小于 s2" << endl;
}

要約: 文字列比較は主に 2 つの文字列が等しいかどうかを比較するために使用され、どちらが大きいか小さいかを判断することはあまり意味がありません。


4.1.6 文字列文字アクセス

文字列内の単一の文字にアクセスするには 2 つの方法があります

  • char& operator[](int n);// []で文字を取得
  • char& at(int n);// atメソッドで文字を取得
void test01()
{
    
    
	string str = "hello world";

	// 通过 [] 方式取字符
	for (int i = 0; i < str.size(); i++)	cout << str[i] << " ";  // h e l l o
	cout << endl;

	// 通过 at 方法获取字符
	for (int i = 0; i < str.size(); i++)	cout << str.at(i) << " ";  // h e l l o
	cout << endl;

	// 修改单个字符
	str[0] = 'x';
	str.at(1) = 'x';
	cout << str << endl;  // x x l l o
}

4.1.7 文字列の挿入と削除

関数プロトタイプ:

  • string& insert(int pos, const char* s);// 文字列を挿入
  • string& insert(int pos, const string& str);// 文字列を挿入
  • string& insert(int pos, int n, char c);// 指定された位置に n 文字を挿入します。 c
  • string& erase(int pos, int n = npos);// Pos から始まる n 文字を削除
// 字符串插入和删除
void test01()
{
    
    
	string str = "hello";
	str.insert(1, "111");

	cout << str << endl;  // h111ello

	str.erase(1, 3);  // 从 1 号位置开始删除 3 个字符
	cout << str << endl;  // hello
}

4.1.8 文字列の部分文字列の取得

関数プロトタイプ:

string substr(int pos = 0, int n = npos) const;// pos から始まる n 文字からなる文字列を返します

// 子串
void test01()
{
    
    
	string str = "abcdefg";
	string subStr = str.substr(1, 3);
	cout << "subStr = " << subStr << endl;  // bcd

	string email = "[email protected]";
	int pos = email.find("@");  // 按字符查找,返回开始的下标
	cout << pos << endl;  // 8
	string username = email.substr(0, pos);  // 截取名字子串
	cout << "username: " << username << endl;  // username: zhangsan
}

4.2 vector 容器

  • 機能: ベクトル データ構造は、シングルエンド配列とも呼ばれる配列に非常に似ています。
  • ベクトルと通常の配列の違い: 違いは、配列が静的空間であるのに対し、ベクトルは動的に拡張できることです。
  • 動的拡張: 元のスペースの後に新しいスペースを追加する代わりに、より大きなメモリ スペースを見つけて、元のデータを新しいスペースにコピーして元のスペースを解放します。ベクトル コンテナーの反復子はランダム アクセスをサポートします。

ここに画像の説明を挿入


4.2.1 ベクトルコンストラクター

関数プロトタイプ:

  • vector<T> v;// テンプレートを使用してクラス実装、デフォルトのコンストラクターを実装します
  • vector(v.begin(), v.end());// 区間 v[begin(), end()) 内の要素をそれ自体にコピーします
  • vector(n, elem);// コンストラクターは n 個の要素をそれ自体にコピーします
  • vector(const vector &vec);// コンストラクターをコピー
void printVector(vector<int>& v)
{
    
    
	for (vector<int>::iterator it = v.begin(); it != v.end(); it++)	cout << *it << " ";
	cout << endl;
}

void test01()
{
    
    
	vector<int> v1;  // 无参构造
	for (int i = 0; i < 10; i++)	v1.push_back(i);

	printVector(v1);  // 0 1 2 3 4 5 6 7 8 9

	// 通过区间方式进行构造
	vector<int> v2(v1.begin(), v1.end());
	printVector(v2);  // 0 1 2 3 4 5 6 7 8 9

	// n 个 elem 方式构造
	vector<int> v3(10, 100);
	printVector(v3);  // 100 100 100 100 100 100 100 100 100 100

	vector<int> v4(v3);
	printVector(v4);  // 100 100 100 100 100 100 100 100 100 100
}

4.2.2 ベクトル代入演算

関数プロトタイプ:

  • vector& operator=(const vector &vec);// オーバーロードされた等号演算子
  • assign(beg, end);// 区間 [beg, end) のデータのコピーをそれ自体に割り当てます** (ここでは文字列との違いに注意してください)**
  • assign(n, elem);// n 個の要素をコピーしてそれ自体に割り当てます
#include <vector>

void printVector(vector<int>& v)
{
    
    
	for (vector<int>::iterator it = v.begin(); it != v.end(); it++)	cout << *it << " ";
	cout << endl;
}

// 赋值操作
void test01()
{
    
    
	vector<int> v1;  // 无参构造
	for (int i = 0; i < 10; i++)	v1.push_back(i);
	printVector(v1);  // 0 1 2 3 4 5 6 7 8 9

	// 赋值
	vector<int> v2;
	v2 = v1;
	printVector(v2);  // 0 1 2 3 4 5 6 7 8 9

	vector<int> v3;
	v3.assign(v1.begin(), v1.end());
	printVector(v3);  // 0 1 2 3 4 5 6 7 8 9

	vector<int> v4;
	v4.assign(10, 100);
	printVector(v4);  // 100 100 100 100 100 100 100 100 100 100
}

4.2.3 ベクトルの容量とサイズ

関数プロトタイプ:

  • empty();// コンテナが空かどうかを確認する
  • capacity();// コンテナの容量 (ランダムにオープン)
  • size();// コンテナ内の要素の数を返します
  • resize(int num);// コンテナの長さを num として再指定します。コンテナが長くなった場合は、新しい位置をデフォルト値で埋めます。コンテナが短くなると、コンテナの長さを超える最後の要素が削除されます
  • resize(int num, elem);// コンテナの長さを num として再指定し、コンテナが長くなった場合は、新しい位置を elem の値で埋めます。コンテナが短くなると、コンテナの長さを超える最後の要素が削除されます
#include <vector>

void printVector(vector<int>& v)
{
    
    
	for (vector<int>::iterator it = v.begin(); it != v.end(); it++)	cout << *it << " ";
	cout << endl;
}

void test01()
{
    
    
	vector<int> v1;
	for (int i = 0; i < 10; i++)	v1.push_back(i);
	printVector(v1);  // 0 1 2 3 4 5 6 7 8 9

	// 容器为空
	if (v1.empty())	cout << "v1 为空" << endl;
	else
	{
    
    
		cout << "v1 不为空" << endl;
		cout << "v1 的容量 = " << v1.capacity() << endl;  // 13
		cout << "v1 的大小 = " << v1.size() << endl;  // 10
	}

	// 重新指定大小,若指定的更大,用 10 填充,默认用 0 填充新位置
	v1.resize(15, 10);
	printVector(v1);  // 0 1 2 3 4 5 6 7 8 9 10 10 10 10 10

	// 重新指定大小 ,若指定的更小,超出部分元素被删除
	v1.resize(5);
	printVector(v1);  // 0 1 2 3 4
}

4.2.4 ベクトルの挿入と削除

関数プロトタイプ:

  • push_back(ele);// 要素 ele を最後に挿入します
  • pop_back();// 最後の要素を削除します
  • insert(const_iterator pos, ele);// イテレータは要素 ele を挿入する位置 pos を指します。
  • insert(const_iterator pos, int count,ele);// イテレータは位置 pos を指し、count 要素 ele を挿入します。
  • erase(const_iterator pos);// イテレータが指す要素を削除します
  • erase(const_iterator start, const_iterator end);// イテレータの要素を最初から最後まで削除します。
  • clear();// コンテナ内のすべての要素を削除します

#include <vector>

void printVector(vector<int>& v)
{
    
    
	for (vector<int>::iterator it = v.begin(); it != v.end(); it++)	cout << *it << " ";
	cout << endl;
}

// 插入和删除
void test01()
{
    
    
	vector<int> v1;
	//尾插
	v1.push_back(10);
	v1.push_back(20);
	v1.push_back(30);
	v1.push_back(40);
	v1.push_back(50);
	printVector(v1);  // 10 20 30 40 50

	// 尾删
	v1.pop_back();
	printVector(v1);  // 10 20 30 40

	// 插入
	v1.insert(v1.begin(), 100);  // 把 100 插入到第一个参数的位置
	printVector(v1);  // 100 10 20 30 40

	v1.insert(v1.begin(), 2, 1000);  // 限定插入长度
	printVector(v1);  // 1000 1000 100 10 20 30 40

	// 删除,参数也是迭代器
	v1.erase(v1.begin());
	printVector(v1);  // 1000 100 10 20 30 40

	// 清空
	v1.erase(v1.begin(), v1.end());  // 从头到尾全部删除
	v1.clear();  // 不用提供参数
	printVector(v1);
}

4.2.5 ベクトルデータアクセス

関数プロトタイプ:

  • at(int idx);// インデックス idx が指すデータを返します。
  • operator[];// インデックス idx が指すデータを返します。
  • front();// コンテナ内の最初のデータ要素を返します
  • back();// コンテナ内の最後のデータ要素を返します
#include <vector>

void test01()
{
    
    
	vector<int> v1;
	for (int i = 0; i < 10; i++)	v1.push_back(i);

	// 利用 [] 访问数组中元素
	for (int i = 0; i < v1.size(); i++)	cout << v1[i] << " ";
	cout << endl;

	// 利用 at 方式访问元素
	for (int i = 0; i < v1.size(); i++)	cout << v1.at(i) << " ";
	cout << endl;

	cout << "v1 的第一个元素为: " << v1.front() << endl;  // 0
	cout << "v1 的最后一个元素为: " << v1.back() << endl;  // 9
}

4.2.6 ベクトルスワップコンテナ

関数プロトタイプ:

  • swap(vec); ​// vec を独自の要素と交換します (実際の使用方法: メモリ空間を縮小するためにスワップを使用します)
#include <vector>

void printVector(vector<int>& v)
{
    
    
	for (vector<int>::iterator it = v.begin(); it != v.end(); it++)	cout << *it << " ";
	cout << endl;
}

void test01()
{
    
    
	vector<int> v1;
	for (int i = 0; i < 10; i++)	v1.push_back(i);
	printVector(v1);  // 0 1 2 3 4 5 6 7 8 9

	vector<int> v2;
	for (int i = 10; i > 0; i--)	v2.push_back(i);
	printVector(v2);  // 10 9 8 7 6 5 4 3 2 1

	// 互换容器
	cout << "互换后" << endl;
	v1.swap(v2);
	printVector(v1);  // 10 9 8 7 6 5 4 3 2 1
	printVector(v2);  // 0 1 2 3 4 5 6 7 8 9
}

// 实际用途:使用 swap 可以收缩内存空间
void test02()
{
    
    
	vector<int> v;
	for (int i = 0; i < 100000; i++)	v.push_back(i);

	cout << "v的容量为:" << v.capacity() << endl;  // 138255
	cout << "v的大小为:" << v.size() << endl;  // 100000

	v.resize(3);  // 重新指定大小

	cout << "v 的容量为:" << v.capacity() << endl;  // 138255
	cout << "v 的大小为:" << v.size() << endl;  // 3

	// 收缩内存
	vector<int>(v).swap(v);
	// vector<int>(v) -- 匿名对象:当前行使用完,系统立马回收内存空间

	cout << "v 的容量为:" << v.capacity() << endl;  // 3
	cout << "v 的大小为:" << v.size() << endl;  // 3
}

概要: スワップにより 2 つのコンテナーを交換可能にし、メモリを縮小するという実質的な効果を実現できます。


4.2.7 ベクトル予約スペース

  • 機能: 動的に容量を拡張する際のベクトル展開の数を削減します。

関数プロトタイプ:

  • reserve(int len);// コンテナは len 要素の長さを予約しますが、予約された位置は初期化されておらず、要素にはアクセスできません
#include <vector>

void test01()
{
    
    
	vector<int> v;

	// 预留空间
	v.reserve(100000);

	int num = 0;  // 统计开辟次数
	int* p = NULL;

	// 这里说的是,在遍历创建容器的时候,并不是一次性就把内存空间确定好了的
	// 因为 vector 会动态扩展,如果内存满了的话,下面的遍历会进行多次开辟内存操作
	for (int i = 0; i < 100000; i++) {
    
    
		v.push_back(i);
		if (p != &v[0]) {
    
    
			p = &v[0];
			num++;
		}
	}

	// 如果没预留空间的话,num 是 30,开辟了 30 次内存空间
	// 如果预留空间,num 是 1,只开辟了一次内存空间
	cout << "num:" << num << endl;
}

概要: データ量が多い場合は、reserve を使用して最初にスペースを確保できます。


4.3 デックコンテナ

関数:

  • ヘッドエンドで挿入および削除操作を実行できる両端アレイ

特徴:

  • ランダムアクセスをサポート

デックとベクターの違い:

  • 先頭のベクターの挿入・削除効率が低く、データ量が大きくなるほど効率が低下する
  • 比較的言えば、deque はベクターよりも速くヘッドの挿入と削除を行います。
  • Vector は deque よりも速く要素にアクセスします (Vector は連続したメモリ空間です)

ここに画像の説明を挿入

deque が内部的にどのように機能するか:

  • deque 内には中央コントローラーがあり、各バッファーの内容を維持し、実際のデータをバッファーに保存します。
  • 中央コントローラーは各バッファーのアドレスを維持し、連続したメモリー空間のようにデキューを作成します。

ここに画像の説明を挿入


4.3.1 DEQUE コンストラクター

関数プロトタイプ:

  • deque<T> deqT;// デフォルトのコンストラクター
  • deque(beg, end);// コンストラクターは範囲 [beg, end) 内の要素をそれ自体にコピーします。
  • deque(n, elem);// コンストラクターは n 個の要素をそれ自体にコピーします
  • deque(const deque &deq);// コンストラクターをコピー
#include <deque>

void printDeque(const deque<int>& d)  // 限定只读状态
{
    
    
	// 不能从 const iterator -> iterator,所以,需要更改 const_iterator
	// 添加了 const 之后,元素的值就不能修改了
	for (deque<int>::const_iterator it = d.begin(); it != d.end(); it++)
		cout << *it << " ";
	cout << endl;
}

// deque 构造
void test01()
{
    
    
	deque<int> d1;  // 无参构造函数
	for (int i = 0; i < 10; i++)	d1.push_back(i);
	printDeque(d1);  // 0 1 2 3 4 5 6 7 8 9

	// 区间方式构造
	deque<int> d2(d1.begin(), d1.end());
	printDeque(d2);  // 0 1 2 3 4 5 6 7 8 9

	// n 个 elem 元素拷贝给本身
	deque<int> d3(10,100);
	printDeque(d3);  // 100 100 100 100 100 100 100 100 100 100

	// 拷贝构造
	deque<int> d4 = d3;
	printDeque(d4);  // 100 100 100 100 100 100 100 100 100 100
}

4.3.2 デキュー代入操作

関数プロトタイプ:

  • deque& operator=(const deque& deq);// オーバーロードされた等号演算子
  • assign(beg, end);// 区間 [beg, end) 内のデータのコピーをそれ自体に割り当てます
  • assign(n, elem);// n 個の要素をコピーしてそれ自体に割り当てます
#include <deque>

void printDeque(const deque<int>& d)  // 限定只读状态
{
    
    
	for (deque<int>::const_iterator it = d.begin(); it != d.end(); it++) {
    
    
		cout << *it << " ";
	}
	cout << endl;
}

// 赋值操作
void test01()
{
    
    
	deque<int> d1;
	for (int i = 0; i < 10; i++)	d1.push_back(i);
	printDeque(d1);

	deque<int> d2;
	d2 = d1;
	printDeque(d2);  // 0 1 2 3 4 5 6 7 8 9

	deque<int> d3;
	d3.assign(d1.begin(), d1.end());
	printDeque(d3);  // 0 1 2 3 4 5 6 7 8 9

	deque<int> d4;
	d4.assign(10, 100);
	printDeque(d4);  // 100 100 100 100 100 100 100 100 100 100
}

4.3.3 デキューサイズ操作

関数プロトタイプ:

  • deque.empty();// コンテナが空かどうかを確認する
  • deque.size();// コンテナ内の要素の数を返します
  • deque.resize(num);// コンテナの長さを num として再指定します。コンテナが長くなった場合は、新しい位置をデフォルト値で埋めます。コンテナが短くなると、コンテナの長さを超える最後の要素が削除されます
  • deque.resize(num, elem);// コンテナの長さを num として再指定し、コンテナが長くなった場合は、新しい位置を elem の値で埋めます。コンテナが短くなると、コンテナの長さを超える最後の要素が削除されます

知らせ:

  • deque は中間制御方式を使用するため、容量はなく、コンテナーのサイズのみが異なります。
#include <deque>

void printDeque(const deque<int>& d) 
{
    
    
	for (deque<int>::const_iterator it = d.begin(); it != d.end(); it++) {
    
    
		cout << *it << " ";
	}
	cout << endl;
}

// 大小操作
void test01()
{
    
    
	deque<int> d1;
	for (int i = 0; i < 10; i++)	d1.push_back(i);
	printDeque(d1);

	if (d1.empty())	cout << "d1为空!" << endl;
	else {
    
    
		cout << "d1 不为空!" << endl;
		cout << "d1 的大小为:" << d1.size() << endl;
	}

	// 重新指定大小
	d1.resize(15, 1);
	printDeque(d1);

	d1.resize(5);
	printDeque(d1);
}

4.3.4 deque の挿入と削除

両端での挿入操作:

  • push_back(elem);// コンテナの最後にデータを追加します
  • push_front(elem);// コンテナの先頭にデータを挿入します
  • pop_back();// コンテナ内の最後のデータを削除します
  • pop_front();// コンテナ内の最初のデータを削除します

指定位置動作:

  • insert(pos,elem);// elem 要素のコピーを pos の位置に挿入し、新しいデータの位置を返します
  • insert(pos,n,elem);// n elem データを pos の位置に挿入します。戻り値はありません
  • insert(pos,beg,end);// 範囲 [beg, end) の位置 pos にデータを挿入します。戻り値はありません
  • clear();// コンテナ内のすべてのデータをクリアします
  • erase(beg,end);// 区間[beg, end)のデータを削除し、次のデータの位置を返す
  • erase(pos);// pos の位置のデータを削除し、次のデータの位置を返す
#include <deque>

void printDeque(const deque<int>& d) 
{
    
    
	for (deque<int>::const_iterator it = d.begin(); it != d.end(); it++) {
    
    
		cout << *it << " ";
	}
	cout << endl;
}

// 两端操作
void test01()
{
    
    
	deque<int> d;
	// 尾插
	d.push_back(10);  
	d.push_back(20);
	// 头插
	d.push_front(100);
	d.push_front(200);

	printDeque(d);  // 200 100 10 20 

	// 尾删
	d.pop_back();  // 200 100 10
	// 头删
	d.pop_front();
	printDeque(d);  // 100 10
}

// 插入
void test02()
{
    
    
	deque<int> d;
	d.push_back(10);
	d.push_back(20);
	d.push_front(100);
	d.push_front(200);
	printDeque(d);  // 200 100 10 20 

	d.insert(d.begin(), 1000);
	printDeque(d);  // 1000 200 100 10 20 

	d.insert(d.begin(), 2,10000);
	printDeque(d);  // 10000 10000 1000 200 100 10 20

	deque<int> d2;
	d2.push_back(1);
	d2.push_back(2);
	d2.push_back(3);

	d.insert(d.begin(), d2.begin(), d2.end());
	printDeque(d);  // 1 2 3 10000 10000 1000 200 100 10 20
}

// 删除
void test03()
{
    
    
	deque<int> d;
	d.push_back(10);
	d.push_back(20);
	d.push_front(100);
	d.push_front(200);
	printDeque(d);  // 200 100 10 20

	d.erase(d.begin());  
	printDeque(d);  // 100 10 20

	d.erase(d.begin(), d.end());
	d.clear();
	printDeque(d);
}

4.3.5 デキューデータアクセス

関数プロトタイプ:

  • at(int idx);// インデックス idx が指すデータを返します。
  • operator[];// インデックス idx が指すデータを返します。
  • front();// コンテナ内の最初のデータ要素を返します
  • back();// コンテナ内の最後のデータ要素を返します
#include <deque>

void printDeque(const deque<int>& d) 
{
    
    
	for (deque<int>::const_iterator it = d.begin(); it != d.end(); it++) {
    
    
		cout << *it << " ";
	}
	cout << endl;
}

// 数据存取
void test01()
{
    
    
	deque<int> d;
	d.push_back(10);
	d.push_back(20);
	d.push_back(30);
	d.push_front(100);
	d.push_front(200);
	d.push_back(300);

	for (int i = 0; i < d.size(); i++)	cout << d[i] << " ";
	cout << endl;

	for (int i = 0; i < d.size(); i++)	cout << d.at(i) << " ";
	cout << endl;

	cout << "front:" << d.front() << endl;  // 300
	cout << "back:" << d.back() << endl;  // 30
}

4.3.6 デックソート

アルゴリズム:

  • sort(iterator beg, iterator end)// 開始間隔と終了間隔で要素を並べ替えます (ランダム アクセス イテレータをサポートするコンテナは並べ替えアルゴリズムを使用できます)
#include <deque>
#include <algorithm>

void printDeque(const deque<int>& d)
{
    
    
	for (deque<int>::const_iterator it = d.begin(); it != d.end(); it++)	cout << *it << " ";
	cout << endl;
}

void test01()
{
    
    
	deque<int> d;
	d.push_back(10);
	d.push_back(20);
	d.push_front(100);
	d.push_front(200);

	printDeque(d);  // 200 100 10 20
	// 排序算法:默认从小到大排
	sort(d.begin(), d.end());
	printDeque(d);  // 10 20 100 200
}

知らせ:

  • ベクトルは配列に似ているため、deque よりも 1 つ多い容量属性があります。

4.4 スタックコンテナとキューコンテナ

4.4.1 スタックコンテナ

  • 概念: スタックは先入れ後出し (FILO) データ構造です。

ここに画像の説明を挿入

  • スタック内の最上位の要素のみが外部から使用できるため、スタックではトラバーサル動作が許可されません。

  • スタックにデータを入力することをスタッキングと呼びます push

  • スタックからデータをポップすることは、スタックのポップと呼ばれます。 pop


共通インターフェース:

コンストラクタ:

  • stack<T> stk;// スタックはテンプレート クラスとスタック オブジェクトのデフォルトの構築形式を使用して実装されます
  • stack(const stack &stk);// コンストラクターをコピー

代入操作:

  • stack& operator=(const stack &stk);// オーバーロードされた等号演算子

データアクセス:

  • push(elem);// スタックの先頭に要素を追加します
  • pop();// スタックの先頭から最初の要素を削除します
  • top();<span> </span>// スタックの最上位要素を返します

サイズ操作:

  • empty();// スタックが空かどうかを確認する
  • size();// スタックのサイズを返す
#include <stack>

// 栈容器常用接口
void test01()
{
    
    
	// 创建栈容器,栈容器必须符合先进后出
	stack<int> s;

	// 向栈中添加元素,叫做压栈、入栈
	s.push(10);
	s.push(20);
	s.push(30);

	while (!s.empty()) {
    
    
		// 输出栈顶元素
		cout << "栈顶元素为: " << s.top() << endl;
		// 弹出栈顶元素
		s.pop();
	}

	cout << "栈的大小为:" << s.size() << endl;  // 0
}

4.4.2 キューコンテナ

概念: キューは先入れ先出し (FIFO) データ構造であり、2 つの出口があります。

ここに画像の説明を挿入

  • キュー コンテナでは、一方の端から要素を追加し、もう一方の端から要素を削除できます。

  • キューの先頭と末尾のみが外部から使用できるため、キューのトラバーサル動作は許可されません

  • キューにデータを入力することをエンキューといいます push

  • キュー内のデータは、デキューと呼ばれます。 pop


共通インターフェース:

コンストラクタ:

  • queue<T> que;// キューは、キュー オブジェクトのデフォルトの構築形式であるテンプレート クラスによって実装されます。
  • queue(const queue &que);// コンストラクターをコピー

代入操作:

  • queue& operator=(const queue &que);// オーバーロードされた等号演算子

データアクセス:

  • push(elem);// 要素をキューの最後に追加します
  • pop();// キューの先頭から最初の要素を削除します
  • back();// 最後の要素を返す
  • front();<span> </span>// 最初の要素を返す

サイズ操作:

  • empty();// スタックが空かどうかを確認する
  • size();// スタックのサイズを返す
#include <queue>
#include <string>


class Person
{
    
    
public:
	Person(string name, int age)
	{
    
    
		this->m_Name = name;
		this->m_Age = age;
	}

	string m_Name;
	int m_Age;
};

void test01()
{
    
    
	// 创建队列
	queue<Person> q;
	// 准备数据
	Person p1("唐僧", 30);
	Person p2("孙悟空", 1000);
	Person p3("猪八戒", 900);
	Person p4("沙僧", 800);

	// 向队列中添加元素,入队操作
	q.push(p1);
	q.push(p2);
	q.push(p3);
	q.push(p4);

	// 队列不提供迭代器,更不支持随机访问
	while (!q.empty()) {
    
    
		// 输出队头元素
		cout << "队头元素-- 姓名: " << q.front().m_Name << " 年龄: "<< q.front().m_Age << endl;
		cout << "队尾元素-- 姓名: " << q.back().m_Name << " 年龄: " << q.back().m_Age << endl;
  
		cout << endl;
		// 弹出队头元素
		q.pop();
	}
	cout << "队列大小为:" << q.size() << endl;
}

4.5 リストコンテナ

機能: データのチェーンストレージ

リンク リスト (リスト) は物理ストレージ ユニット上の不連続なストレージ構造であり、データ要素の論理的順序はリンク リスト内のポインタ リンクを通じて実現されます。

リンク リストの構成: リンク リストは一連のノードで構成されます。

ノードの構成: 1 つはデータ要素を格納するデータ フィールド、もう 1 つは次のノードのアドレスを格納するポインタ フィールドです。

STL のリンク リストは二重循環リンク リストです。

ここに画像の説明を挿入

リンク リストの格納方法は連続したメモリ空間ではないため、リンク リスト リスト内のイテレータは、双方向イテレータである前方および後方への移動のみをサポートします。

リストの利点:

  • 動的ストレージ割り当てを使用すると、メモリの無駄やオーバーフローが発生しません。
  • リンクされたリストは、多数の要素を移動せずにポインタを変更するだけで、挿入および削除操作を実行するのに非常に便利です。

リストの欠点:

  • リンクされたリストは柔軟ですが、スペース (ポインター フィールド) と時間 (トラバース) が余分にかかります。

リストには重要なプロパティがあり、挿入操作と削除操作は元のリスト反復子を無効にしませんが、これはベクトルでは当てはまりません。

要約: リストとベクターは STL で最もよく使用される 2 つのコンテナですが、それぞれに独自の長所と短所があります。


4.5.1 リストコンストラクター

関数プロトタイプ:

  • list<T> lst;// リストは、オブジェクトのデフォルトの構築形式であるテンプレート クラスを使用して実装されます。
  • list(beg,end);// コンストラクターは範囲 [beg, end) 内の要素をそれ自体にコピーします。
  • list(n,elem);// コンストラクターは n 個の要素をそれ自体にコピーします
  • list(const list &lst);// コンストラクターをコピー
#include <list>

void printList(const list<int>& L) {
    
    
	for (list<int>::const_iterator it = L.begin(); it != L.end(); it++)	cout << *it << " ";
	cout << endl;
}

void test01()
{
    
    
	list<int> L1;  // ,默认构造

	L1.push_back(10);
	L1.push_back(20);
	L1.push_back(30);
	L1.push_back(40);

	printList(L1);  // 10 20 30 40

	// 按照区间方式构造
	list<int> L2(L1.begin(),L1.end());
	printList(L2);  // 10 20 30 40

	// 拷贝构造
	list<int> L3(L2);
	printList(L3);  // 10 20 30 40

	// n 个 elem 拷贝方式
	list<int>L4(10, 100);
	printList(L4);  // 100 100 100 100 100 100 100 100 100 100
}

4.5.2 リストの割り当てと交換

関数プロトタイプ:

  • assign(beg, end);// 区間 [beg, end) 内のデータのコピーをそれ自体に割り当てます
  • assign(n, elem);// n 個の要素をコピーしてそれ自体に割り当てます
  • list& operator=(const list &lst);// オーバーロードされた等号演算子
  • swap(lst);// lst を独自の要素と交換します
#include <list>

void printList(const list<int>& L)
{
    
    
	for (list<int>::const_iterator it = L.begin(); it != L.end(); it++) cout << *it << " ";
	cout << endl;
}

// 赋值和交换
void test01()
{
    
    
	list<int> L1;
	L1.push_back(10);
	L1.push_back(20);
	L1.push_back(30);
	L1.push_back(40);
	printList(L1);

	// 赋值
	list<int> L2;
	L2 = L1;
	printList(L2);

	list<int> L3;
	L3.assign(L2.begin(), L2.end());
	printList(L3);

	list<int> L4;
	L4.assign(10, 100);
	printList(L4);
}

// 交换
void test02()
{
    
    
	list<int> L1;
	L1.push_back(10);
	L1.push_back(20);
	L1.push_back(30);
	L1.push_back(40);

	list<int> L2;
	L2.assign(10, 100);

	L1.swap(L2);
	printList(L1);
	printList(L2);
}

4.5.3 リストサイズの操作

関数プロトタイプ:

  • size();// コンテナ内の要素の数を返します
  • empty();// コンテナが空かどうかを確認する
  • resize(num);// コンテナの長さを num として再指定します。コンテナが長くなった場合は、新しい位置をデフォルト値で埋めます。コンテナが短くなると、コンテナの長さを超える最後の要素が削除されます
  • resize(num, elem);// コンテナの長さを num として再指定し、コンテナが長くなった場合は、新しい位置を elem の値で埋めます。コンテナーが短くなると、コンテナーの長さを超える最後の要素が削除されます。
#include <list>

void printList(const list<int>& L)
{
    
    
	for (list<int>::const_iterator it = L.begin(); it != L.end(); it++)	cout << *it << " ";
	cout << endl;
}

// 大小操作
void test01()
{
    
    
	list<int> L1;
	L1.push_back(10);
	L1.push_back(20);
	L1.push_back(30);
	L1.push_back(40);

	if (L1.empty())	cout << "L1为空" << endl;
	else
	{
    
    
		cout << "L1不为空" << endl;
		cout << "L1的大小为: " << L1.size() << endl;
	}

	// 重新指定大小
	L1.resize(10);
	printList(L1);

	L1.resize(2);
	printList(L1);
}

4.5.4 リストの挿入と削除

関数プロトタイプ:

  • push_back(elem);// コンテナの最後に要素を追加します
  • pop_back();// コンテナ内の最後の要素を削除します
  • push_front(elem);// コンテナの先頭に要素を挿入します
  • pop_front();// コンテナの先頭から最初の要素を削除します
  • insert(pos,elem);// elem 要素のコピーを pos の位置に挿入し、新しいデータの位置を返します
  • insert(pos,n,elem);// n elem データを pos の位置に挿入します。戻り値はありません
  • insert(pos,beg,end);// [beg,end) 区間の pos 位置にデータを挿入します。戻り値はありません
  • clear();// コンテナからすべてのデータを削除します
  • erase(beg,end);// 区間[beg, end)のデータを削除し、次のデータの位置を返す
  • erase(pos);// pos の位置のデータを削除し、次のデータの位置を返す
  • remove(elem);// elem の値に一致するコンテナ内の要素をすべて削除します
#include <list>

void printList(const list<int>& L)
{
    
    
	for (list<int>::const_iterator it = L.begin(); it != L.end(); it++)	cout << *it << " ";
	cout << endl;
}

// 插入和删除
void test01()
{
    
    
	list<int> L;
	// 尾插
	L.push_back(10);
	L.push_back(20);
	L.push_back(30);
	// 头插
	L.push_front(100);
	L.push_front(200);
	L.push_front(300);

	printList(L);

	// 尾删
	L.pop_back();
	printList(L);

	// 头删
	L.pop_front();
	printList(L);

	// 插入
	list<int>::iterator it = L.begin();
	L.insert(++it, 1000);
	printList(L);

	// 删除
	it = L.begin();
	L.erase(++it);
	printList(L);

	// 移除
	L.push_back(10000);
	L.push_back(10000);
	L.push_back(10000);
	printList(L);
	L.remove(10000);
	printList(L);
  
    	// 清空
	L.clear();
	printList(L);
}

注: list には、指定された要素を削除するための Remove メソッドがあります。


4.5.5 リストデータアクセス

関数プロトタイプ:

  • front();// 最初の要素を返す
  • back();// 最後の要素を返す
#include <list>

// 数据存取
void test01()
{
    
    
	list<int> L1;
	L1.push_back(10);
	L1.push_back(20);
	L1.push_back(30);
	L1.push_back(40);

	// cout << L1.at(0) << endl;  // 错误,不支持 at 访问数据
	// cout << L1[0] << endl;  // 错误,不支持 [] 方式访问数据
	cout << "第一个元素为: " << L1.front() << endl;
	cout << "最后一个元素为: " << L1.back() << endl;

	// list 容器的迭代器是双向迭代器,不支持随机访问
	list<int>::iterator it = L1.begin();
	// it = it + 1;  // 错误,不可以跳跃访问,即使是 +1
}

概要: リスト コンテナーは、[] または次の場所を介してデータにアクセスできません。


4.5.6 リストの反転とソート

関数プロトタイプ:

  • reverse();// 逆リンクリスト
  • sort();// リストのソート
void printList(const list<int>& L)
{
    
    
	for (list<int>::const_iterator it = L.begin(); it != L.end(); it++)	cout << *it << " ";
	cout << endl;
}

bool myCompare(int val1 , int val2)	{
    
     return val1 > val2; }

// 反转和排序
void test01()
{
    
    
	list<int> L;
	L.push_back(90);
	L.push_back(30);
	L.push_back(20);
	L.push_back(70);
	printList(L);  // 90 30 20 70

	// 反转容器的元素
	L.reverse();
	printList(L);  // 70 20 30 90

	// 排序
	L.sort();  // 默认的排序规则,从小到大
	printList(L);  // 20 30 70 90

	L.sort(myCompare);  // 指定规则,从大到小
	printList(L);  // 90 70 30 20
}

4.6 set/multiset 容器

セットの紹介:

  • すべての要素は挿入時に自動的にソートされます

セットの本質:

  • set/multiset は連想コンテナであり、基礎となる構造はバイナリ ツリーで実装されます。

セットとマルチセットの違い:

  • set ではコンテナ内で要素の重複を許可しません
  • マルチセットではコンテナ内で重複した要素が許可されます

4.6.1 セットの構築と代入

構造:

  • set<T> st;// デフォルトのコンストラクター
  • set(const set &st);// コンストラクターをコピー

割り当て:

  • set& operator=(const set &st);// オーバーロードされた等号演算子
#include <set>

void printSet(set<int> & s)
{
    
    
	for (set<int>::iterator it = s.begin(); it != s.end(); it++)	cout << *it << " ";
	cout << endl;
}

// 构造和赋值
void test01()
{
    
    
	set<int> s1;

	s1.insert(10);
	s1.insert(30);
	s1.insert(20);
	s1.insert(40);
	printSet(s1);

	// 拷贝构造
	set<int>s2(s1);
	printSet(s2);

	// 赋值
	set<int>s3;
	s3 = s2;
	printSet(s3);
}

要約:

  • セットコンテナにデータを挿入する場合は、insert を使用します。
  • 設定したコンテナに挿入されたデータは自動的にソートされます

4.6.2 セットサイズとスワップ

関数プロトタイプ:

  • size();// コンテナ内の要素の数を返します
  • empty();// コンテナが空かどうかを確認する
  • swap(st);// 2 つのコレクション コンテナを交換します
#include <set>

void printSet(set<int> & s)
{
    
    
	for (set<int>::iterator it = s.begin(); it != s.end(); it++)	cout << *it << " ";
	cout << endl;
}

// 大小
void test01()
{
    
    
	set<int> s1;

	s1.insert(10);
	s1.insert(30);
	s1.insert(20);
	s1.insert(40);

	if (s1.empty())	cout << "s1 为空" << endl;
	else
	{
    
    
		cout << "s1 不为空" << endl;
		cout << "s1 的大小为: " << s1.size() << endl;
	}
}

// 交换
void test02()
{
    
    
	set<int> s1;

	s1.insert(10);
	s1.insert(30);
	s1.insert(20);
	s1.insert(40);

	set<int> s2;

	s2.insert(100);
	s2.insert(300);
	s2.insert(200);
	s2.insert(400);

	cout << "交换前" << endl;
	printSet(s1);
	printSet(s2);
	cout << endl;

	cout << "交换后" << endl;
	s1.swap(s2);
	printSet(s1);
	printSet(s2);
}

4.6.3 セットの挿入と削除

関数プロトタイプ:

  • insert(elem);// 要素をコンテナに挿入します
  • clear();// すべての要素をクリアします
  • erase(pos);// pos イテレータが指す要素を削除し、次の要素のイテレータを返す
  • erase(beg, end);// 区間 [beg, end) 内のすべての要素を削除し、次の要素へのイテレータを返します。
  • erase(elem);// コンテナ内の値が elem である要素を削除します
#include <set>

void printSet(set<int> & s)
{
    
    
	for (set<int>::iterator it = s.begin(); it != s.end(); it++)	cout << *it << " ";
	cout << endl;
}

// 插入和删除
void test01()
{
    
    
	set<int> s1;
	// 插入
	s1.insert(10);
	s1.insert(30);
	s1.insert(20);
	s1.insert(40);
	printSet(s1);

	// 删除
	s1.erase(s1.begin());
	printSet(s1);

	s1.erase(30);
	printSet(s1);

	// 清空
	// s1.erase(s1.begin(), s1.end());
	s1.clear();
	printSet(s1);
}

4.6.4 セットルックアップと統計

関数プロトタイプ:

  • find(key);// キーが存在するかどうかを調べ、存在する場合はキーの要素のイテレータを返し、存在しない場合は set.end() を返します。
  • count(key);// キー内の要素の数をカウントします。
#include <set>

// 查找和统计
void test01()
{
    
    
	set<int> s1;
	// 插入
	s1.insert(10);
	s1.insert(30);
	s1.insert(20);
	s1.insert(40);

	// 查找
	set<int>::iterator pos = s1.find(30);  // 注意:因为返回的是元素的迭代器,取值的话要用 * 

	if (pos != s1.end())	cout << "找到了元素 : " << *pos << endl;
	else	cout << "未找到元素" << endl;

	// 统计
	int num = s1.count(30);
	cout << "num = " << num << endl;
}

4.6.5 セットとマルチセットの違い

違い:

  • セットでは重複データを挿入できませんが、マルチセットでは重複データを挿入できます。
  • set がデータを挿入すると、挿入が成功したかどうかを示す挿入結果が返されます。
  • マルチセットはデータを検出しないため、重複が挿入される可能性があります
#include <set>

// set 和 multiset 区别
void test01()
{
    
    
	set<int> s;
	pair<set<int>::iterator, bool>  ret = s.insert(10);  // 第一次插入成功!

	if (ret.second)	cout << "第一次插入成功!" << endl;
	else	cout << "第一次插入失败!" << endl;

	ret = s.insert(10);  // 第二次插入失败!
	if (ret.second)	cout << "第二次插入成功!" << endl;
	else	cout << "第二次插入失败!" << endl;

	// multiset
	multiset<int> ms;
	ms.insert(10);
	ms.insert(10);

	for (multiset<int>::iterator it = ms.begin(); it != ms.end(); it++)	cout << *it << " ";  // 10 10
	cout << endl;
}

4.6.6 ペアペアの作成

関数:

  • ペアで表示されるデータ。ペアを使用すると 2 つのデータを返すことができます

作成する 2 つの方法:

  • pair<type, type> p(value1, value2);
  • pair<type, type> p = make_pair(value1, value2);
#include <string>

// 对组创建
void test01()
{
    
    
	pair<string, int> p("Tom", 20);
	cout << "姓名: " <<  p.first << " 年龄: " << p.second << endl;

	pair<string, int> p2 = make_pair("Jerry", 10);
	cout << "姓名: " << p2.first << " 年龄: " << p2.second << endl;
}

4.6.7 コンテナのソートを設定する

主な技術的なポイント:

  • ファンクターを使用すると、並べ替えルールを変更できます

データを保存する前に、照合ルールを決定してください。

例 1: set は組み込みデータ型を格納します

#include <set>

class MyCompare 
{
    
    
public:
	bool operator()(int v1, int v2) {
    
     return v1 > v2; }
};

void test01() 
{
    
      
	set<int> s1;
	s1.insert(10);
	s1.insert(40);
	s1.insert(20);
	s1.insert(30);
	s1.insert(50);

	// 默认从小到大
	for (set<int>::iterator it = s1.begin(); it != s1.end(); it++)	cout << *it << " ";
	cout << endl;

	// 指定排序规则,从大到小
	set<int, MyCompare> s2;  // 在还未存放数据之前,确定好排序规则!
	s2.insert(10);
	s2.insert(40);
	s2.insert(20);
	s2.insert(30);
	s2.insert(50);

	for (set<int, MyCompare>::iterator it = s2.begin(); it != s2.end(); it++)	cout << *it << " ";
	cout << endl;
}

例 2: set はカスタム データ型を格納します

#include <set>
#include <string>

class Person
{
    
    
public:
	Person(string name, int age)
	{
    
    
		this->m_Name = name;
		this->m_Age = age;
	}

	string m_Name;
	int m_Age;

};

class comparePerson
{
    
    
public:
	// 按照年龄进行排序,降序
	bool operator()(const Person& p1, const Person &p2) {
    
     return p1.m_Age > p2.m_Age; }
};

void test01()
{
    
    
	set<Person, comparePerson> s;

	Person p1("刘备", 23);
	Person p2("关羽", 27);
	Person p3("张飞", 25);
	Person p4("赵云", 21);

	s.insert(p1);
	s.insert(p2);
	s.insert(p3);
	s.insert(p4);

	for (set<Person, comparePerson>::iterator it = s.begin(); it != s.end(); it++)
	{
    
    
		cout << "姓名: " << it->m_Name << " 年龄: " << it->m_Age << endl;
	}
}

概要: カスタム データ型の場合、データを挿入するには照合順序を指定する必要があります。


4.7 マップ/マルチマップコンテナ

マップの紹介:

  • マップ内のすべての要素はペアです
  • ペアの最初の要素はインデックスとして機能するキー (キー値) で、2 番目の要素は値 (実際の値) です。
  • すべての要素は、要素のキー値に従って自動的にソートされます。

地図の本質:

  • map/multimap は連想コンテナであり、基礎となる構造はバイナリ ツリーで実装されます。

マップの利点:

  • キー値に基づいて値をすばやく見つけることができます

マップとマルチマップの違い:

  • マップはコンテナ内で重複するキー値要素を許可しません
  • マルチマップではコンテナ内で重複したキー値要素を許可します

4.7.1 マップの構築と割り当て

構造:

  • map<T1, T2> mp;// マップのデフォルトコンストラクター
  • map(const map &mp);// コンストラクターをコピー

割り当て:

  • map& operator=(const map &mp);// オーバーロードされた等号演算子
#include <map>

void printMap(map<int,int>&m)
{
    
    
	for (map<int, int>::iterator it = m.begin(); it != m.end(); it++)	cout << "key = " << it->first << " value = " << it->second << endl;
	cout << endl;
}

void test01()
{
    
    
	map<int, int> m;  // 默认构造
	m.insert(pair<int, int>(1, 10));
	m.insert(pair<int, int>(2, 20));
	m.insert(pair<int, int>(3, 30));
	printMap(m);

	map<int, int> m2(m);  // 拷贝构造
	printMap(m2);

	map<int, int> m3;
	m3 = m2;  // 赋值
	printMap(m3);
}

概要: マップ内のすべての要素はペアで表示され、データを挿入するときにペアが使用されます。


4.7.2 マップのサイズとスワッピング

関数プロトタイプ:

  • size();// コンテナ内の要素の数を返します
  • empty();// コンテナが空かどうかを確認する
  • swap(st);// 2 つのコレクション コンテナを交換します
#include <map>

void printMap(map<int, int>& m)
{
    
    
	for (map<int, int>::iterator it = m.begin(); it != m.end(); it++)	cout << "key = " << it->first << " value = " << it->second << endl;
	cout << endl;
}

void test01()
{
    
    
	map<int, int> m;
	m.insert(pair<int, int>(1, 10));
	m.insert(pair<int, int>(2, 20));
	m.insert(pair<int, int>(3, 30));

	if (m.empty())	cout << "m 为空" << endl;
	else
	{
    
    
		cout << "m 不为空" << endl;
		cout << "m 的大小为: " << m.size() << endl;
	}
}

// 交换
void test02()
{
    
    
	map<int, int> m;
	m.insert(pair<int, int>(1, 10));
	m.insert(pair<int, int>(2, 20));
	m.insert(pair<int, int>(3, 30));

	map<int, int> m2;
	m2.insert(pair<int, int>(4, 100));
	m2.insert(pair<int, int>(5, 200));
	m2.insert(pair<int, int>(6, 300));

	cout << "交换前" << endl;
	printMap(m);
	printMap(m2);

	cout << "交换后" << endl;
	m.swap(m2);
	printMap(m);
	printMap(m2);
}

4.7.3 マップの挿入と削除

関数プロトタイプ:

  • insert(elem);//要素をコンテナに挿入します
  • clear();// すべての要素をクリアします
  • erase(pos);// pos イテレータが指す要素を削除し、次の要素のイテレータを返す
  • erase(beg, end);// 区間 [beg, end) 内のすべての要素を削除し、次の要素へのイテレータを返します。
  • erase(key);// コンテナ内の値がキーである要素を削除します
#include <map>

void printMap(map<int,int>&m)
{
    
    
	for (map<int, int>::iterator it = m.begin(); it != m.end(); it++)	cout << "key = " << it->first << " value = " << it->second << endl;
	cout << endl;
}

void test01()
{
    
    
	// 插入
	map<int, int> m;
	// 第一种插入方式
	m.insert(pair<int, int>(1, 10));
	// 第二种插入方式
	m.insert(make_pair(2, 20));
	// 第三种插入方式
	m.insert(map<int, int>::value_type(3, 30));
	// 第四种插入方式
	m[4] = 40; 
	printMap(m);

	// 删除
	m.erase(m.begin());
	printMap(m);

	m.erase(3);
	printMap(m);

	// 清空
	m.erase(m.begin(),m.end());
	m.clear();
	printMap(m);
}

4.7.4 地図の検索と統計

関数プロトタイプ:

  • find(key); //キーが存在するかどうかを調べ、存在する場合はキーの要素のイテレータを返し、存在しない場合は set.end() を返します。
  • count(key); //キー内の要素の数をカウントします
#include <map>

// 查找和统计
void test01()
{
    
    
	map<int, int>m; 
	m.insert(pair<int, int>(1, 10));
	m.insert(pair<int, int>(2, 20));
	m.insert(pair<int, int>(3, 30));

	// 查找
	map<int, int>::iterator pos = m.find(3);

	if (pos != m.end())	cout << "找到了元素 key = " << (*pos).first << " value = " << (*pos).second << endl;
	else	cout << "未找到元素" << endl;

	// 统计
	int num = m.count(3);
	cout << "num = " << num << endl;
}

4.7.5 マップコンテナのソート

主な技術的なポイント:

  • ファンクターを使用すると、並べ替えルールを変更できます
#include <map>

class MyCompare {
    
    
public:
	bool operator()(int v1, int v2) {
    
     return v1 > v2; }
};

void test01() 
{
    
    
	// 利用仿函数实现从大到小排序
	map<int, int, MyCompare> m;

	m.insert(make_pair(1, 10));
	m.insert(make_pair(2, 20));
	m.insert(make_pair(3, 30));
	m.insert(make_pair(4, 40));
	m.insert(make_pair(5, 50));

	for (map<int, int, MyCompare>::iterator it = m.begin(); it != m.end(); it++)	cout << "key:" << it->first << " value:" << it->second << endl;
}

概要: カスタム データ型の場合、マップではセット コンテナーと同様に照合順序を指定する必要があります。


5 つの関数オブジェクト

コンセプト:

  • 関数呼び出し演算子をオーバーロードするクラス。そのオブジェクトは関数オブジェクトと呼ばれることがよくあります。
  • 関数オブジェクトがオーバーロード () を使用すると、ファンクターとも呼ばれる関数呼び出しのように動作します。

自然:

関数オブジェクト (ファンクター) は関数ではなくクラスです

特徴:

  • 関数オブジェクトを使用すると、通常の関数と同じように呼び出すことができ、パラメータを指定したり、戻り値を指定したりできます。
  • 関数オブジェクトは通常の関数の概念を超え、関数オブジェクトは独自の状態を持つことができます
  • 関数オブジェクトは引数として渡すことができます
#include <string>

// 1、函数对象在使用时,可以像普通函数那样调用, 可以有参数,可以有返回值
class MyAdd
{
    
    
public:
	int operator()(int v1, int v2) {
    
     return v1 + v2; }
};

void test01()
{
    
    
	MyAdd myAdd;
	cout << myAdd(10, 10) << endl;  // 20
}

// 2、函数对象可以有自己的状态
class MyPrint
{
    
    
public:
	MyPrint() {
    
     count = 0; }

	void operator()(string test)
	{
    
    
		cout << test << endl;
		count++;  // 统计使用次数
	}

	int count;  // 内部自己的状态
};

void test02()
{
    
    
	MyPrint myPrint;
	myPrint("hello world");
	myPrint("hello world");
	myPrint("hello world");
	cout << "myPrint 调用次数为: " << myPrint.count << endl;
}

// 3、函数对象可以作为参数传递
void doPrint(MyPrint& mp , string test)	{
    
     mp(test); }

void test03()
{
    
    
	MyPrint myPrint;

	doPrint(myPrint, "Hello C++");
	cout << "myPrint 调用次数为: " << myPrint.count << endl;  // 1
}

5.1 述語

コンセプト:

  • bool 型を返すファンクタは述語と呼ばれます
  • Operator() が 1 つの引数を取る場合、それは単項述語と呼ばれます
  • Operator() が 2 つの引数を取る場合、それはバイナリ述語と呼ばれます

単項述語:

#include <vector>
#include <algorithm>

// 二元谓词
class MyCompare
{
    
    
public:
	bool operator()(int num1, int num2)
	{
    
    
		return num1 > num2;
	}
};

void test01()
{
    
    
	vector<int> v;
	v.push_back(10);
	v.push_back(40);
	v.push_back(20);
	v.push_back(30);
	v.push_back(50);

	// 默认从小到大
	sort(v.begin(), v.end());
	for (vector<int>::iterator it = v.begin(); it != v.end(); it++)
	{
    
    
		cout << *it << " ";
	}
	cout << endl;
	cout << "----------------------------" << endl;

	// 使用函数对象改变算法策略,排序从大到小
	sort(v.begin(), v.end(), MyCompare());
	for (vector<int>::iterator it = v.begin(); it != v.end(); it++)
	{
    
    
		cout << *it << " ";
	}
	cout << endl;
}

バイナリ述語:

#include <vector>
#include <algorithm>

// 二元谓词
class MyCompare
{
    
    
public:
	bool operator()(int num1, int num2)
	{
    
    
		return num1 > num2;
	}
};

void test01()
{
    
    
	vector<int> v;
	v.push_back(10);
	v.push_back(40);
	v.push_back(20);
	v.push_back(30);
	v.push_back(50);

	// 默认从小到大
	sort(v.begin(), v.end());
	for (vector<int>::iterator it = v.begin(); it != v.end(); it++)
	{
    
    
		cout << *it << " ";
	}
	cout << endl;
	cout << "----------------------------" << endl;

	// 使用函数对象改变算法策略,排序从大到小
	sort(v.begin(), v.end(), MyCompare());
	for (vector<int>::iterator it = v.begin(); it != v.end(); it++)
	{
    
    
		cout << *it << " ";
	}
	cout << endl;
}

5.2 組み込み関数オブジェクト

コンセプト:

  • STL にはいくつかの組み込み関数オブジェクトがあります

分類:

  • 算術関数
  • 関係関数
  • 論理関数

使用法:

  • これらのファンクターによって生成されたオブジェクトは、通常の関数とまったく同じ方法で使用されます。
  • 組み込み関数オブジェクトを使用するには、ヘッダー ファイルをインポートする必要があります#include<functional>

5.2.1 算術関手

機能の説明:

  • 4つの操作を実現
  • このうち、negate は単項演算、その他は二項演算です

ファンクターのプロトタイプ:

  • template<class T> T plus<T>// 加算関数
  • template<class T> T minus<T>// 減算関数
  • template<class T> T multiplies<T>// 乗算関数
  • template<class T> T divides<T>// 除算関数
  • template<class T> T modulus<T>// モック関数を取得します
  • template<class T> T negate<T>// 逆関数を取得します
#include <functional>

// negate
void test01()
{
    
    
	negate<int> n;
	cout << n(50) << endl;  // -50
}

// plus
void test02()
{
    
    
	plus<int> p;
	cout << p(10, 20) << endl;  // 30
}

概要: 組み込み関数オブジェクトを使用する場合、ヘッダー ファイルを導入する必要があります#include <functional>


5.2.2 関係関数

機能の説明:

  • 関係比較を実現する

ファンクターのプロトタイプ:

  • template<class T> bool equal_to<T>// に等しい
  • template<class T> bool not_equal_to<T>// 等しくない
  • template<class T> bool greater<T>// 以上
  • template<class T> bool greater_equal<T>// 以上
  • template<class T> bool less<T>// 未満
  • template<class T> bool less_equal<T>// 以下
#include <functional>
#include <vector>
#include <algorithm>

class MyCompare
{
    
    
public:
	bool operator()(int v1,int v2)	{
    
     return v1 > v2; }
};
void test01()
{
    
    
	vector<int> v;

	v.push_back(10);
	v.push_back(30);
	v.push_back(50);
	v.push_back(40);
	v.push_back(20);

	for (vector<int>::iterator it = v.begin(); it != v.end(); it++)	cout << *it << " ";
	cout << endl;

	// 自己实现仿函数
	// sort(v.begin(), v.end(), MyCompare());
	// STL 内建仿函数  大于仿函数
	sort(v.begin(), v.end(), greater<int>());

	for (vector<int>::iterator it = v.begin(); it != v.end(); it++)	cout << *it << " ";
	cout << endl;
}

概要: 最も一般的に使用されるリレーショナル関手は、「より大きい<> より大きい」です。


5.2.3 論理関数

関数プロトタイプ:

  • template<class T> bool logical_and<T>// 論理積
  • template<class T> bool logical_or<T>// 論理和
  • template<class T> bool logical_not<T>// 論理否定
#include <vector>
#include <functional>
#include <algorithm>

void test01()
{
    
    
	vector<bool> v;
	v.push_back(true);
	v.push_back(false);
	v.push_back(true);
	v.push_back(false);

	for (vector<bool>::iterator it = v.begin();it!= v.end();it++)	cout << *it << " ";
	cout << endl;

	// 逻辑非  将 v 容器搬运到 v2 中,并执行逻辑非运算
	vector<bool> v2;
	v2.resize(v.size());
	transform(v.begin(), v.end(),  v2.begin(), logical_not<bool>());
	for (vector<bool>::iterator it = v2.begin(); it != v2.end(); it++)	cout << *it << " ";
	cout << endl;
}

6 STL 共通アルゴリズム

概要:

  • アルゴリズムは主にヘッダー ファイルで<algorithm> <functional> <numeric>構成されます
  • <algorithm>これはすべての STL ヘッダー ファイルの中で最大であり、その範囲には比較、交換、検索、走査操作、コピー、変更などが含まれます。
  • <numeric>サイズが小さく、シーケンスに対して単純な数学演算を実行するためのいくつかのテンプレート関数のみが含まれています
  • <functional>一部のテンプレート クラスは関数オブジェクトを宣言するために定義されています。

6.1 一般的に使用されるトラバーサル アルゴリズム

アルゴリズムの紹介:

  • for_each// コンテナを走査する
  • transform// コンテナを別のコンテナに移動します

6.1.1 for_each

関数プロトタイプ:

  • for_each(iterator beg, iterator end, _func);

    // 走査アルゴリズムはコンテナ要素を走査します

    // beg はイテレータを開始します

    // 終了反復子

    // _func 関数または関数オブジェクト

#include <algorithm>
#include <vector>

// 普通函数
void print01(int val)	{
    
     cout << val << " "; }

// 函数对象
class print02 
{
    
    
 public:
	void operator()(int val) {
    
     cout << val << " "; }
};

// for_each 算法基本用法
void test01()
{
    
    
	vector<int> v;
	for (int i = 0; i < 10; i++)	v.push_back(i);

	// 遍历算法
	for_each(v.begin(), v.end(), print01);
	cout << endl;

	for_each(v.begin(), v.end(), print02());
	cout << endl;
}

注: for_each は実際の開発で最も一般的に使用されるトラバーサル アルゴリズムであり、習得する必要があります。


6.1.2 変換

関数:

  • コンテナを別のコンテナに移動する

関数プロトタイプ:

  • transform(iterator beg1, iterator end1, iterator beg2, _func);

    // beg1 ソースコンテナ開始イテレータ

    // end1 ソースコンテナの終了イテレータ

    // beg2 ターゲットコンテナ開始イテレータ

    // _func 関数または関数オブジェクト

#include<vector>
#include<algorithm>

// 常用遍历算法  搬运 transform
class TransForm
{
    
    
public:
	int operator()(int val)	{
    
     return val; }
};

class MyPrint
{
    
    
public:
	void operator()(int val) {
    
     cout << val << " "; }
};

void test01()
{
    
    
	vector<int> v;
	for (int i = 0; i < 10; i++)	v.push_back(i);

	vector<int> vTarget;  // 目标容器

	vTarget.resize(v.size());  // 目标容器需要提前开辟空间

	transform(v.begin(), v.end(), vTarget.begin(), TransForm());

	for_each(vTarget.begin(), vTarget.end(), MyPrint());  // 0 1 2 3 4 5 6 7 8 9
}

注:輸送対象のコンテナは事前にスペースを空けておかなければ正常に輸送できません


6.2 一般的な検索アルゴリズム

アルゴリズムの紹介:

  • find// 要素を検索
  • find_if// 条件で要素を検索
  • adjacent_find// 隣接する重複要素を検索します
  • binary_search// 二分探索法
  • count// 要素の数をカウントします
  • count_if// 条件ごとに要素数をカウントする

6.2.1 検索

関数:

  • 指定された要素を検索し、指定された要素を返すイテレータを検索します。見つからない場合は、終了イテレータを返します。 end()

関数プロトタイプ:

  • find(iterator beg, iterator end, value);

    // 要素を値で検索し、見つかった場合は指定された位置のイテレータを返し、見つからない場合は終了イテレータの位置を返します。

    // beg はイテレータを開始します

    // 終了反復子

    // value が探している要素

#include <algorithm>
#include <vector>
#include <string>

void test01()
{
    
    
	vector<int> v;
	for (int i = 0; i < 10; i++)	v.push_back(i + 1);

	// 查找容器中是否有 5 这个元素
	vector<int>::iterator it = find(v.begin(), v.end(), 5);
	if (it == v.end())	cout << "没有找到!" << endl;
	else	cout << "找到:" << *it << endl;
}

class Person {
    
    
public:
	Person(string name, int age) 
	{
    
    
		this->m_Name = name;
		this->m_Age = age;
	}
	// 重载 ==
	bool operator==(const Person& p) 
	{
    
    
		if (this->m_Name == p.m_Name && this->m_Age == p.m_Age)	return true;
		return false;
	}

public:
	string m_Name;
	int m_Age;
};

void test02()
{
    
    
	vector<Person> v;

	// 创建数据
	Person p1("aaa", 10);
	Person p2("bbb", 20);
	Person p3("ccc", 30);
	Person p4("ddd", 40);

	v.push_back(p1);
	v.push_back(p2);
	v.push_back(p3);
	v.push_back(p4);

	vector<Person>::iterator it = find(v.begin(), v.end(), p2);
	if (it == v.end())	cout << "没有找到!" << endl;
	else	cout << "找到姓名:" << it->m_Name << " 年龄: " << it->m_Age << endl;
}

概要: find を使用してコンテナ内の指定された要素を検索します。戻り値はイテレータです。


6.2.2 find_if

関数:

  • 条件による要素の検索

関数プロトタイプ:

  • find_if(iterator beg, iterator end, _Pred);

    // 要素を値で検索し、見つかった場合は指定された位置のイテレータを返し、見つからない場合は終了イテレータの位置を返します。

    // beg はイテレータを開始します

    // 終了反復子

    // _Pred 関数または述語 (bool 型を返す関数)

#include <algorithm>
#include <vector>
#include <string>

// 内置数据类型
class GreaterFive
{
    
    
public:
	bool operator()(int val) {
    
     return val > 5; }
};

void test01()
{
    
    
	vector<int> v;
	for (int i = 0; i < 10; i++)	v.push_back(i + 1);

	vector<int>::iterator it = find_if(v.begin(), v.end(), GreaterFive());
	if (it == v.end())	cout << "没有找到!" << endl;
	else	cout << "找到:" << *it << endl;
}

// 自定义数据类型
class Person {
    
    
public:
	Person(string name, int age)
	{
    
    
		this->m_Name = name;
		this->m_Age = age;
	}
public:
	string m_Name;
	int m_Age;
};

class Greater20
{
    
    
public:
	bool operator()(Person& p) {
    
     return p.m_Age > 20; }
};

void test02()
{
    
    
	vector<Person> v;

	// 创建数据
	Person p1("aaa", 10);
	Person p2("bbb", 20);
	Person p3("ccc", 30);
	Person p4("ddd", 40);

	v.push_back(p1);
	v.push_back(p2);
	v.push_back(p3);
	v.push_back(p4);

	vector<Person>::iterator it = find_if(v.begin(), v.end(), Greater20());
	if (it == v.end())	cout << "没有找到!" << endl;
	else	cout << "找到姓名:" << it->m_Name << " 年龄: " << it->m_Age << endl;  // 找到姓名:ccc 年龄: 30
}

概要: find_if は条件によって検索して検索をより柔軟にし、提供されたファンクターはさまざまな戦略を変更できます。


6.2.3 隣接検索

関数:

  • 隣接する重複要素を検索する

関数プロトタイプ:

  • adjacent_find(iterator beg, iterator end);

    // 隣接する繰り返し要素を検索し、隣接する要素の最初の位置にイテレータを返します。

    // beg はイテレータを開始します

    // 終了反復子

#include <algorithm>
#include <vector>

void test01()
{
    
    
	vector<int> v;
	v.push_back(1);
	v.push_back(2);
	v.push_back(5);
	v.push_back(2);
	v.push_back(4);
	v.push_back(4);
	v.push_back(3);

	// 查找相邻重复元素
	vector<int>::iterator it = adjacent_find(v.begin(), v.end());
	if (it == v.end())	cout << "找不到!" << endl;
	else cout << "找到相邻重复元素为:" << *it << endl;
}

概要: 面接の質問で隣接して繰り返される要素を見つけた場合は、STL の隣接検索アルゴリズムを使用することを忘れないでください。


6.2.4 バイナリ検索

二分探索機能:

  • 指定された要素が存在するかどうかを調べる

関数プロトタイプ:

  • bool binary_search(iterator beg, iterator end, value);

    // 指定された要素を検索し、見つかった場合は true を返し、それ以外の場合は false を返します。

    // 注: 順序付けされていないシーケンスでは使用できません

    // beg はイテレータを開始します

    // 終了反復子

    // value が探している要素

#include <algorithm>
#include <vector>

void test01()
{
    
    
	vector<int>v;

	for (int i = 0; i < 10; i++)	v.push_back(i);
	// 二分查找
	bool ret = binary_search(v.begin(), v.end(), 2);
	if (ret)	cout << "找到了" << endl;
	else	cout << "未找到" << endl;
}

要約: 二分探索法は非常に効率的ですが、検索されたコンテナ内の要素には順序付けされたシーケンスが必要であることに注意してください。


6.2.5カウント

関数:

  • 統計要素の数

関数プロトタイプ:

  • count(iterator beg, iterator end, value);

    // 要素の出現数をカウントします

    // beg はイテレータを開始します

    // 終了反復子

    // 値統計の要素

#include <algorithm>
#include <vector>

// 内置数据类型
void test01()
{
    
    
	vector<int> v;
	v.push_back(1);
	v.push_back(2);
	v.push_back(4);
	v.push_back(5);
	v.push_back(3);
	v.push_back(4);
	v.push_back(4);

	int num = count(v.begin(), v.end(), 4);

	cout << "4 的个数为: " << num << endl;
}

// 自定义数据类型
class Person
{
    
    
public:
	Person(string name, int age)
	{
    
    
		this->m_Name = name;
		this->m_Age = age;
	}
	bool operator==(const Person & p)
	{
    
    
		if (this->m_Age == p.m_Age)	return true;
		else	return false;
	}
	string m_Name;
	int m_Age;
};

void test02()
{
    
    
	vector<Person> v;

	Person p1("刘备", 35);
	Person p2("关羽", 35);
	Person p3("张飞", 35);
	Person p4("赵云", 30);
	Person p5("曹操", 25);

	v.push_back(p1);
	v.push_back(p2);
	v.push_back(p3);
	v.push_back(p4);
	v.push_back(p5);
  
	Person p("诸葛亮", 35);

	int num = count(v.begin(), v.end(), p);
	// 注意重载运算符里的逻辑
	cout << "num = " << num << endl;  // num = 3
}

概要: カスタム データ型をカウントするときは、オーバーロード**operator==**


6.2.6 count_if

関数:

  • 条件ごとに要素数をカウントする

関数プロトタイプ:

  • count_if(iterator beg, iterator end, _Pred);

    // 条件ごとに要素の出現数をカウントします

    // beg はイテレータを開始します

    // 終了反復子

    // _Pred述語

#include <algorithm>
#include <vector>

class Greater4
{
    
    
public:
	bool operator()(int val) {
    
     return val >= 4; }
};

// 内置数据类型
void test01()
{
    
    
	vector<int> v;
	v.push_back(1);
	v.push_back(2);
	v.push_back(4);
	v.push_back(5);
	v.push_back(3);
	v.push_back(4);
	v.push_back(4);

	int num = count_if(v.begin(), v.end(), Greater4());

	cout << "大于4的个数为: " << num << endl;
}

// 自定义数据类型
class Person
{
    
    
public:
	Person(string name, int age)
	{
    
    
		this->m_Name = name;
		this->m_Age = age;
	}

	string m_Name;
	int m_Age;
};

class AgeLess35
{
    
    
public:
	bool operator()(const Person &p) {
    
     return p.m_Age < 35; }
};

void test02()
{
    
    
	vector<Person> v;

	Person p1("刘备", 35);
	Person p2("关羽", 35);
	Person p3("张飞", 35);
	Person p4("赵云", 30);
	Person p5("曹操", 25);

	v.push_back(p1);
	v.push_back(p2);
	v.push_back(p3);
	v.push_back(p4);
	v.push_back(p5);

	int num = count_if(v.begin(), v.end(), AgeLess35());
	cout << "小于35岁的个数:" << num << endl;
}

概要:によるカウントと条件によるcount_if


6.3 一般的な並べ替えアルゴリズム

アルゴリズムの紹介:

  • sort// コンテナ内の要素を並べ替えます
  • random_shuffle// シャッフルし、指定された範囲内の要素の順序をランダムに調整します
  • merge// コンテナ要素はマージされ、別のコンテナに保存されます
  • reverse// 指定された範囲の要素を反転します

6.3.1 並べ替え

関数:

  • コンテナ内の要素を並べ替える

関数プロトタイプ:

  • sort(iterator beg, iterator end, _Pred);

    // 要素を値で検索し、見つかった場合は指定された位置のイテレータを返し、見つからない場合は終了イテレータの位置を返します。

    // beg はイテレータを開始します

    // 終了反復子

    // _Pred述語

#include <algorithm>
#include <vector>

void myPrint(int val) {
    
     cout << val << " ";}

void test01()
{
    
    
	vector<int> v;
	v.push_back(10);
	v.push_back(30);
	v.push_back(50);
	v.push_back(20);
	v.push_back(40);

	// sort 默认从小到大排序
	sort(v.begin(), v.end());
	for_each(v.begin(), v.end(), myPrint);
	cout << endl;

	// 从大到小排序
	sort(v.begin(), v.end(), greater<int>());
	for_each(v.begin(), v.end(), myPrint);
	cout << endl;
}

要約: ソートは開発において最も一般的に使用されるアルゴリズムの 1 つであり、習得する必要があります。


6.3.2 ランダムシャッフル

関数:

  • カードをシャッフルし、指定された範囲内の要素の順序をランダムに調整します

関数プロトタイプ:

  • random_shuffle(iterator beg, iterator end);

    // 指定された範囲内の要素の順序をランダムに調整します

    // beg はイテレータを開始します

    // 終了反復子

#include <algorithm>
#include <vector>
#include <ctime>

class myPrint
{
    
    
public:
	void operator()(int val) {
    
     cout << val << " "; }
};

void test01()
{
    
    
	srand((unsigned int)time(NULL));  // 记得加随机数种子
	vector<int> v;
	for(int i = 0 ; i < 10;i++)	v.push_back(i);

	for_each(v.begin(), v.end(), myPrint());
	cout << endl;

	// 打乱顺序
	random_shuffle(v.begin(), v.end());
	for_each(v.begin(), v.end(), myPrint());
	cout << endl;
}

概要: randan_shuffle シャッフル アルゴリズムはより実用的です。使用する場合は乱数シードを追加することを忘れないでください。


6.3.3 マージ

関数:

  • 2 つのコンテナ要素がマージされ、別のコンテナに格納されます。

関数プロトタイプ:

  • merge(iterator beg1, iterator end1, iterator beg2, iterator end2, iterator dest);

    // コンテナ要素はマージされ、別のコンテナに保存されます

    // 注: 2 つのコンテナは順番に並んでいる必要があります

    // beg1 コンテナ 1 開始イテレータ

    // end1 コンテナ 1 終了イテレータ

    // beg2 コンテナ 2 開始イテレータ

    // end2 コンテナ 2 の終了イテレータ

    // dest 宛先コンテナ開始イテレータ

#include <algorithm>
#include <vector>

class myPrint
{
    
    
public:
	void operator()(int val) {
    
     cout << val << " "; }
};

void test01()
{
    
    
	vector<int> v1;
	vector<int> v2;
	for (int i = 0; i < 10 ; i++) 
    	{
    
    
		v1.push_back(i);
		v2.push_back(i + 1);
	}

	vector<int> vtarget;
	// 目标容器需要提前开辟空间
	vtarget.resize(v1.size() + v2.size());
	// 合并  需要两个有序序列
	merge(v1.begin(), v1.end(), v2.begin(), v2.end(), vtarget.begin());
	for_each(vtarget.begin(), vtarget.end(), myPrint());
	cout << endl;
}

概要: マージによってマージされた 2 つのコンテナの順序付けされたシーケンス


6.3.4 リバース

関数:

  • コンテナ内の要素を反転します

関数プロトタイプ:

  • reverse(iterator beg, iterator end);

    // 指定された範囲の要素を反転します

    // beg はイテレータを開始します

    // 終了反復子

#include <algorithm>
#include <vector>

class myPrint
{
    
    
public:
	void operator()(int val) {
    
     cout << val << " "; }
};

void test01()
{
    
    
	vector<int> v;
	v.push_back(10);
	v.push_back(30);
	v.push_back(50);
	v.push_back(20);
	v.push_back(40);

	cout << "反转前: " << endl;
	for_each(v.begin(), v.end(), myPrint());
	cout << endl;

	reverse(v.begin(), v.end());

	cout << "反转后: " << endl;

	for_each(v.begin(), v.end(), myPrint());
	cout << endl;
}

概要: reverse は、間隔内の要素を逆にします。面接の質問には以下のような内容が含まれる場合があります。


6.4 一般的なコピーおよび置換アルゴリズム

アルゴリズムの紹介:

  • copy// コンテナ内の指定範囲の要素を別のコンテナにコピーします
  • replace// コンテナ内の指定された範囲内の古い要素を新しい要素に変更します
  • replace_if<span> </span>// コンテナ内の指定範囲内の条件を満たす要素を新しい要素に置き換えます
  • swap// 2 つのコンテナの要素を交換します

6.4.1 コピー

関数:

  • コンテナ内の指定された範囲の要素を別のコンテナにコピーします

関数プロトタイプ:

  • copy(iterator beg, iterator end, iterator dest);

    // 要素を値で検索し、見つかった場合は指定された位置のイテレータを返し、見つからない場合は終了イテレータの位置を返します。

    // beg はイテレータを開始します

    // 終了反復子

    // dest ターゲットの開始イテレータ

#include <algorithm>
#include <vector>

class myPrint
{
    
    
public:
	void operator()(int val) {
    
     cout << val << " "; }
};

void test01()
{
    
    
	vector<int> v1;
	for (int i = 0; i < 10; i++)	v1.push_back(i + 1);

	vector<int> v2;
	v2.resize(v1.size());
	copy(v1.begin(), v1.end(), v2.begin());

	for_each(v2.begin(), v2.end(), myPrint());
	cout << endl;
}

概要: コピー アルゴリズムを使用してコピーする場合、ターゲット コンテナーは事前にスペースを空けておくことを記憶します。


6.4.2 置換

関数:

  • コンテナ内の指定された範囲内の古い要素を新しい要素に変更します

関数プロトタイプ:

  • replace(iterator beg, iterator end, oldvalue, newvalue);

    // 間隔内の古い要素を新しい要素に置き換えます

    // beg はイテレータを開始します

    // 終了反復子

    // oldvalue 古い要素

    // newvalue 新しい要素

#include <algorithm>
#include <vector>

class myPrint
{
    
    
public:
	void operator()(int val) {
    
     cout << val << " "; }
};

void test01()
{
    
    
	vector<int> v;
	v.push_back(20);
	v.push_back(30);
	v.push_back(20);
	v.push_back(40);
	v.push_back(50);
	v.push_back(10);
	v.push_back(20);

	cout << "替换前:" << endl;
	for_each(v.begin(), v.end(), myPrint());
	cout << endl;

	// 将容器中的 20 替换成 2000
	replace(v.begin(), v.end(), 20,2000);

	cout << "替换后:" << endl;

	for_each(v.begin(), v.end(), myPrint());
	cout << endl;
}

概要: replace は、条件を満たす間隔内の要素を置き換えます。


6.4.3 replace_if

関数:

  • 条件を満たす区間内の要素を指定された要素に置き換えます

関数プロトタイプ:

  • replace_if(iterator beg, iterator end, _pred, newvalue);

    // 条件に従って要素を置き換え、条件を満たす要素を指定された要素に置き換えます

    // beg はイテレータを開始します

    // 終了反復子

    // _pred述語

    // 新しい要素は newvalue に置き換えられます

#include <algorithm>
#include <vector>

class myPrint
{
    
    
public:
	void operator()(int val) {
    
     cout << val << " "; }
};

class ReplaceGreater30
{
    
    
public:
	bool operator()(int val) {
    
     return val >= 30; }
};

void test01()
{
    
    
	vector<int> v;
	v.push_back(20);
	v.push_back(30);
	v.push_back(20);
	v.push_back(40);
	v.push_back(50);
	v.push_back(10);
	v.push_back(20);

	cout << "替换前:" << endl;
	for_each(v.begin(), v.end(), myPrint());
	cout << endl;

	// 将容器中大于等于的 30 替换成 3000
	cout << "替换后:" << endl;
	replace_if(v.begin(), v.end(), ReplaceGreater30(), 3000);
	for_each(v.begin(), v.end(), myPrint());
	cout << endl;
}

概要: replace_if は条件によって検索します。ファンクターを使用して、満たされた条件を柔軟にフィルターできます。


6.4.4 スワップ

関数:

  • 2 つのコンテナの要素を交換します

関数プロトタイプ:

  • swap(container c1, container c2);

    // 2 つのコンテナの要素を交換します

    // c1 コンテナ 1

    // c2 コンテナ 2

#include <algorithm>
#include <vector>

class myPrint
{
    
    
public:
	void operator()(int val) {
    
     cout << val << " "; }
};

void test01()
{
    
    
	vector<int> v1;
	vector<int> v2;

	for (int i = 0; i < 10; i++) {
    
    
		v1.push_back(i);
		v2.push_back(i+100);
	}

	cout << "交换前: " << endl;
	for_each(v1.begin(), v1.end(), myPrint());
	cout << endl;
	for_each(v2.begin(), v2.end(), myPrint());
	cout << endl;

	cout << "交换后: " << endl;
	swap(v1, v2);
	for_each(v1.begin(), v1.end(), myPrint());
	cout << endl;
	for_each(v2.begin(), v2.end(), myPrint());
	cout << endl;
}

概要: コンテナを交換する場合、交換されるコンテナは同じタイプである必要があることに注意してください


6.5 一般的な算術生成アルゴリズム

知らせ:

  • 算術生成アルゴリズムは小規模なアルゴリズムであり、使用時にインクルードされるヘッダファイルは#include <numeric>

アルゴリズムの紹介:

  • accumulate// コンテナ要素の累積合計を計算します
  • fill// コンテナに要素を追加します

6.5.1 蓄積

関数:

  • 範囲内のコンテナ要素の累積合計を計算します。

関数プロトタイプ:

  • accumulate(iterator beg, iterator end, value);

    // コンテナ要素の累積合計を計算します

    // beg はイテレータを開始します

    // 終了反復子

    // 値の開始値

#include <numeric>
#include <vector>

void test01()
{
    
    
	vector<int> v;
	for (int i = 0; i <= 100; i++)	v.push_back(i);

	int total = accumulate(v.begin(), v.end(), 0);

	cout << "total = " << total << endl;
}

概要: 累算を使用する場合、ヘッダー ファイルは数値であることに注意してください。このアルゴリズムは非常に実用的です。


6.5.2 塗りつぶし

関数:

  • コンテナに指定された要素を埋め込みます

関数プロトタイプ:

  • fill(iterator beg, iterator end, value);

    // コンテナに要素を埋め込みます

    // beg はイテレータを開始します

    // 終了反復子

    // 値を埋めた値

#include <numeric>
#include <vector>
#include <algorithm>

class myPrint
{
    
    
public:
	void operator()(int val) {
    
     cout << val << " "; }
};

void test01()
{
    
    
	vector<int> v;
	v.resize(10);
	// 填充
	fill(v.begin(), v.end(), 100);

	for_each(v.begin(), v.end(), myPrint());
	cout << endl;
}

概要: fill を使用して、コンテナー間隔内の要素を指定された値で埋めます。


6.6 共通集合アルゴリズム

アルゴリズムの紹介:

  • set_intersection// 2 つのコンテナの交差部分を見つけます
  • set_union// 2 つのコンテナの結合を見つけます
  • set_difference<span> </span>// 2 つのコンテナの違いを見つけます

6.6.1 set_intersection

関数:

  • 2 つのコンテナの交差部分を見つける

関数プロトタイプ:

  • set_intersection(iterator beg1, iterator end1, iterator beg2, iterator end2, iterator dest);

    // 2 つのセットの共通部分を見つけます

    // 注: 2 つのコレクションは順序付けられたシーケンスである必要があります

    // beg1 コンテナ 1 開始イテレータ

    // end1 コンテナ 1 終了イテレータ

    // beg2 コンテナ 2 開始イテレータ

    // end2 コンテナ 2 の終了イテレータ

    // dest 宛先コンテナ開始イテレータ

#include <vector>
#include <algorithm>

class myPrint
{
    
    
public:
	void operator()(int val) {
    
     cout << val << " "; }
};

void test01()
{
    
    
	vector<int> v1;
	vector<int> v2;
	for (int i = 0; i < 10; i++)
    	{
    
    
		v1.push_back(i);
		v2.push_back(i+5);
	}

	vector<int> vTarget;
	// 取两个里面较小的值给目标容器开辟空间
	vTarget.resize(min(v1.size(), v2.size()));

	// 返回目标容器的最后一个元素的迭代器地址
	vector<int>::iterator itEnd = set_intersection(v1.begin(), v1.end(), v2.begin(), v2.end(), vTarget.begin());

	// 注意:结束迭代器参数是 set_intersection 返回值,只有存放了交集个数个数量,开辟了空间但是没有存值的空间用 0 填充
	for_each(vTarget.begin(), itEnd, myPrint());
	cout << endl;
}

要約:

  • 2 つのセットの共通部分に必要な順序付けされたシーケンス
  • ターゲット コンテナは、スペースを空けるために 2 つのコンテナから小さい値を取得する必要があります。
  • set_intersection の戻り値は、交差部分の最後の要素の位置です。

6.6.2 set_union

関数:

  • 2 つの集合の和集合を求めます

関数プロトタイプ:

  • set_union(iterator beg1, iterator end1, iterator beg2, iterator end2, iterator dest);

    // 2 つの集合の和集合を求めます

    // 注: 2 つのコレクションは順序付けられたシーケンスである必要があります

    // beg1 コンテナ 1 開始イテレータ

    // end1 コンテナ 1 終了イテレータ

    // beg2 コンテナ 2 開始イテレータ

    // end2 コンテナ 2 の終了イテレータ

    // dest 宛先コンテナ開始イテレータ

#include <vector>
#include <algorithm>

class myPrint
{
    
    
public:
	void operator()(int val) {
    
     cout << val << " "; }
};

void test01()
{
    
    
	vector<int> v1;
	vector<int> v2;
	for (int i = 0; i < 10; i++)
	{
    
    
		v1.push_back(i);
		v2.push_back(i+5);
	}

	vector<int> vTarget;
	// 取两个容器的和给目标容器开辟空间
	vTarget.resize(v1.size() + v2.size());

	// 返回目标容器的最后一个元素的迭代器地址
	vector<int>::iterator itEnd = set_union(v1.begin(), v1.end(), v2.begin(), v2.end(), vTarget.begin());

	for_each(vTarget.begin(), itEnd, myPrint());
	cout << endl;
}

要約:

  • 2 つのセットの和集合を見つけるために必要な順序付けされたシーケンス
  • ターゲット コンテナはスペースを空けるために 2 つのコンテナを追加する必要があります
  • set_union の戻り値は、共用体の最後の要素の位置です。

6.6.3 set_difference

関数:

  • 2 つのセットの差を求めます

関数プロトタイプ:

  • set_difference(iterator beg1, iterator end1, iterator beg2, iterator end2, iterator dest);

    // 2 つのセットの差を求めます

    // 注: 2 つのコレクションは順序付けられたシーケンスである必要があります

    // beg1 コンテナ 1 開始イテレータ

    // end1 コンテナ 1 終了イテレータ

    // beg2 コンテナ 2 開始イテレータ

    // end2 コンテナ 2 の終了イテレータ

    // dest 宛先コンテナ開始イテレータ

#include <vector>
#include <algorithm>

class myPrint
{
    
    
public:
	void operator()(int val) {
    
     cout << val << " "; }
};

void test01()
{
    
    
	vector<int> v1;
	vector<int> v2;
	for (int i = 0; i < 10; i++)
	{
    
    
		v1.push_back(i);
		v2.push_back(i+5);
	}

	vector<int> vTarget;
	// 取两个里面较大的值给目标容器开辟空间
	vTarget.resize(max(v1.size() , v2.size()));

	// 返回目标容器的最后一个元素的迭代器地址
	cout << "v1与v2的差集为: " << endl;
	vector<int>::iterator itEnd = set_difference(v1.begin(), v1.end(), v2.begin(), v2.end(), vTarget.begin());
	for_each(vTarget.begin(), itEnd, myPrint());
	cout << endl;

	cout << "v2与v1的差集为: " << endl;
	itEnd = set_difference(v2.begin(), v2.end(), v1.begin(), v1.end(), vTarget.begin());
	for_each(vTarget.begin(), itEnd, myPrint());
	cout << endl;
}

要約:

  • 2 つのセットを減算するために必要な順序付けされたシーケンス
  • ターゲット コンテナは、スペースを空けるために 2 つのコンテナから大きい方の値を取得する必要があります。
  • set_difference の戻り値は、差分セット内の最後の要素の位置です。

おすすめ

転載: blog.csdn.net/Coder_J7/article/details/130857081