文章

C++标准库读书笔记

1.vector

1.1 元素访问

只有at检查范围,如果索引越界,会抛出out_of_range,剩下三个会引发不明确的错误

  • c[idx]
  • c.at[idx]
  • c.front()
  • c.back()

对一个空的vector调用operator[],front(),back()都会引发不明确的错误

1
2
3
4
5
std::vector v;

v[5] = 1; // undefined behavior

std::cout << coll.front()  // undefined behavior

所以调用operator[]必须确保索引有效,调用front()和back()必须确保容器不为空

正确应该这样

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
std::vector v;

if(v.size() >5)

{

v[5] = 1;

}

if(!v.empty())

{

cout << v.front();

}

v.at(5) = 1;  // out_of_range exception

1.2 非更易性操作

c.size()返回目前元素个数

c.max_size()返回元素个数之最大可能量 //根据系统或库的实现不一样

返回的是向量可以容纳的元素的最大数量。这个值通常取决于系统或库实现的限制,而不是向量实际分配的空间大小

c.capacity()如果不进行控件分配情况下,元素的最大容纳量

c.shrink_to_fit,把容量缩小到现在的vector里面元素的个数

另外:

resize是会改变vector的大小的,而reserve不改变

resize如果比现在的大, 则会把现在的类型初始化为resize的大小,比如原来有vector里面有5个,现在resize(8),则会把vector里面的元素增加3个

但是reserve(8),只是把空间变大为8,但是vector的大小不会变,还是5个

如果resize(3)则会把后两个vector数据删除,虽然vector大小变为3,但其容量还是5

1
2
3
4
5
6
7
8
9
int main() {
    std::vector<int> vec(5);
    std::cout << "Initial size of vector = " << vec.size() << ", capacity = " << vec.capacity() << std::endl;
    vec.resize(10);
    std::cout << "Size of vector after resizing to 10 = " << vec.size() << ", capacity = " << vec.capacity() << std::endl;
    vec.reserve(20);
    std::cout << "Size of vector after reserving space for 20 elements = " << vec.size() << ", capacity = " << vec.capacity() << std::endl;
    return 0;
}

1.3 迭代器

迭代器的函数是:begin()

vector的第一个元素是front(),返回指向第一个元素的引用

begin返回指向vector第一个元素的迭代器

*v.begin() 和v.front()结果是一样的

Vector自己没有提供任何可以删除与某个值相同的元素,这时候用到算法

1
2
3
std::vector<int> v;
// 算法开始 val就是要删除的
v.erase( remove(v.begin(), v.end(), val), v.end() );

这个表达式 v.erase( remove(v.begin(), v.end(), val), v.end() ) 是 C++ 中常用的一种模式,被称为 “erase-remove” 惯用法。

std::remove 函数并不实际删除元素,而是将所有不等于 val 的元素移动到向量的前部,并返回一个迭代器,指向新的 “逻辑结束” 位置。也就是说,remove 会将所有需要保留的元素移动到向量的前部,并返回一个迭代器,指向第一个需要删除的元素。

然后,vector::erase 函数接受两个迭代器参数,表示要删除的元素范围,并实际删除这些元素2在这个例子中,remove 函数返回的迭代器和 v.end() 一起定义了要删除的范围。

所以,remove 函数的作用是重新排列向量中的元素,并确定出需要删除的元素范围;而 erase 函数则根据这个范围来实际删除元素。

如果只是移除与某值相等的第一个元素

1
2
3
4
5
6
7
8
std::vector<int> v;
//算法开始
std::vector<int>::iterator pos;
pos = find(v.begin(), v.end(), val);
if(pos != v.end())
{
    v.erase(pos);
}

1.4 综合运用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
	vector<string> sentence;
	sentence.reserve(5);

	sentence.push_back("Hello,");
	sentence.insert(sentence.end(), { "how","are","you","?" });

	// 将容器sentence中的所有元素以空格分隔的形式输出到控制台
	copy(sentence.cbegin(), sentence.cend(), ostream_iterator<string>(cout, " "));

	cout << endl;

	cout << "  max_size(): " << sentence.max_size() << endl;
	cout << "  size(): " << sentence.size() << endl;
	cout << "  capacity(): " << sentence.capacity() << endl;

	//swap second and fourth element
	swap(sentence[1], sentence[3]);
	
	// 在?前面添加always
	sentence.insert(find(sentence.begin(), sentence.end(), "?"), "always");

	//最后一个元素插入"!"
	sentence.back() = "!";

	copy(sentence.cbegin(), sentence.cend(), ostream_iterator<string>(cout, " "));

	cout << endl;

	cout << "  size():  " << sentence.size() << endl;
	cout << "  capacity():  " << sentence.capacity() << endl;

	// delete last two elements
	sentence.pop_back();
	sentence.pop_back();
 
	sentence.shrink_to_fit();

	cout << "  size():  " << sentence.size() << endl;
	cout << "  capacity():  " << sentence.capacity() << endl;

输出为

1
2
3
4
5
6
7
8
9
Hello, how are you ?
  max_size(): 461168601842738790
  size(): 5
  capacity(): 5
Hello, you are how always !
  size():  6
  capacity():  7
  size():  4
  capacity():  4

因为原来是5,现在增加了always和!,所以会vector自动扩容,不同的stl实现扩容不一样,导致了capacity()可能是7,也可能是10

2.map

1.1 非更易性操作

map的成员函数find()可以查找第一个“拥有某key”的元素,并返回一个迭代器指向该位置,如果没找到这样的元素,则返回容器的end()

但是不能用find()查找拥有某特定value的元素,必须改用stl算法如find_if()或者干脆自己写一个循环,像下面这样就是对拥有特定value的所有元素进行某项操作

1
2
3
4
5
6
7
8
9
std::multimap<std::string, float> m;
std::multimap<std::string, float>::iterator pos;
for(pos = m.begin(); pos != m.end(); ++pos)
{
    if(pos->second == value)
    {
        do_something();
    }
}

如果用find_if的话,可能会更复杂,因为需要提供一个函数对象

1.2 元素访问

map和multimap不支持元素直接访问,音速元素的访问通常是由range-base for循环或迭代器进行,不过有个例外:map提供at()和下标操作符可以直接访问元素

  • range-base for循环访问map元素
1
2
3
4
5
6
std::map<std::string, float> m;
for(auto elem& : m)
{
    std::cout << "key:" << m.first << "\t"
              << "value:" << m.second << std::endl; 
}
  • 迭代器访问元素(C++11之前必须使用这种方法)
1
2
3
4
5
6
7
std::map<std::string, float> m;
std::map<Std::string, float>::iterator pos;
for(pos = m.begin(); pos != m.end(); ++pos)
{
    std::cout << "key:" << pos->first << "\t"
              << "value:" << pos->second << std::endl; 
}

1.3元素安插和移除

从C++11开始,安插元素最方便的就是把他们以初始列的形式传递进去

1
2
std::map<std::string, float> m;
m.insert({"otto", 22.3});

有三种不同的方法可以将value传入map或者multimap内

1.用value_type

为了避免隐式类型转换,可以用它,它是容器本身提供的类型定义

1
2
3
4
std::map<std::string, float> m;
m.insert(std::map<std::string, float>::value_type("otto", 22.3));

m.insert(decltype(m)::value_type("otto", 22.3));

2.用pair<>

1
2
3
std::map<std::string, float> m;
m.insert(std::pair<std::string, float>("otto", 22.3));
m.insert(std::pair<const std::string, float>("otto", 22.3));

在这个特定的情况下,使用std::pair<std::string, float>和std::pair<const std::string, float>来插入元素到std::map中并没有区别。

这是因为在std::map中,键(key)总是常量,无论你是否在插入时指定了它为常量。这是因为一旦一个元素被插入到std::map中,你就不能改变它的键,否则会破坏std::map的内部结构。

所以,无论你是使用std::pair<std::string, float>还是std::pair<const std::string, float>来插入元素,结果都是一样的。

3.用make_pair()

C++11之前都是用的它

1
2
std::map<std::string, float> m;
m.insert(std::make_pair("otto", 22.3));

这三种从效率上来说微乎其微,主要是为了更多的灵活性和方便性,让我的话我还是用make_pair吧

其中也可以用数组插入元素

1
m[key] = value;

优点:

简洁性:数组方式的语法更简洁,更易于理解和使用。 覆盖性:如果键已经存在于map中,数组方式会覆盖原有的值。而使用insert函数插入元素时,如果键已经存在于map中,则不会覆盖原有的值。

缺点:

效率:当map的value是对象时,使用数组方式可能会导致额外的构造和析构调用,从而降低效率。而使用insert或emplace函数可以避免这种情况。 安全性:当map的value是指针时,如果不进行存在性检查而直接使用数组方式,可能会导致运行时错误。因为你直接调用 [] 就直接给 map 插入了一个 key,并且其对应的值为空指针 nullptr。

1.4把map当做关联式数组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
#include <map>
#include <string>
#include <iostream>
#include <iomanip>
using namespace std;

int main()
{
	typedef map<string, float> StringFloatMap;
	StringFloatMap stocks;

	stocks["BASF"] = 360.23;
	stocks["SAF"] = 361.23;
	stocks["WE"] = 362.23;
	stocks["TYU"] = 363.23;
	stocks["PHG"] = 364.23;

	StringFloatMap::iterator pos;
	cout << left; //左对齐
	for (pos = stocks.begin(); pos != stocks.end(); ++pos)
	{
		cout << "stock: " << setw(12) << pos->first
			<< "price: " << pos->second << endl;
	}
	cout << endl;

	for (pos = stocks.begin(); pos != stocks.end(); ++pos)
	{
		pos->second *= 2;
	}

	for (pos = stocks.begin(); pos != stocks.end(); ++pos)
	{
		cout << "stock: " << setw(12) << pos->first
			<< "price: " << pos->second << endl;
	}
	cout << endl;

	stocks["SAFSAF"] = stocks["SAF"];
	stocks.erase("SAF");

	for (pos = stocks.begin(); pos != stocks.end(); ++pos)
	{
		cout << "stock: " << setw(12) << pos->first
			<< "price: " << pos->second << endl;
	}
}

1.5 查找具有某特定Value的元素

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
#include <map>
#include <string>
#include <iostream>
#include <iomanip>
using namespace std;

class CompareThree
{
public:
	bool operator()(const std::pair<float, float>& p)
	{
		return p.second == 3.0;
	}
};

int main()
{
	map<float, float> m = { {1,7}, {2,4}, {3,2}, {4,3},
							{5,6}, {6,1}, {7,3} };

	// 找key
	auto posKey = m.find(3.0);
	if (posKey != m.end())
	{
		cout << "key 3.0 found ("
			<< posKey->first << ":"
			<< posKey->second << ")" << endl;
	}

	// 找value  方法1
	/*auto posVal = find_if(m.begin(), m.end(), CompareThree());
	if (posVal != m.end())
	{
		cout << "key 3.0 found ("
			<< posVal->first << ":"
			<< posVal->second << ")" << endl;
	}*/

	// 找value 方法2
	auto posVal = find_if(m.begin(), m.end(), 
		[] (const pair<float, float>& elem) {
			return elem.second == 3.0;
		});
	if (posVal != m.end())
	{
		cout << "key 3.0 found ("
			<< posVal->first << ":"
			<< posVal->second << ")" << endl;
	}
	return 0;
}

1.6综合运用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
#include <map>
#include <string>
#include <iostream>
#include <iomanip>
#include <algorithm>
#include <cctype>
using namespace std;

class RuntimeStringCmp
{
public:
	enum cmp_mode {normal, nocase};

private:
	const cmp_mode mode;

	static bool nocase_compare(char c1, char c2)
	{
		return toupper(c1) < toupper(c2);
	}

public:
	RuntimeStringCmp(cmp_mode m = normal) : mode(m)
	{

	}

	bool operator() (const string& s1, const string& s2) const {
		if (mode == normal)
		{
			return s1 < s2;
		}
		else
		{
			return lexicographical_compare(s1.begin(), s1.end(),
				s2.begin(), s2.end(),
				nocase_compare);
		}
	}
};

typedef map<string, string, RuntimeStringCmp> StringStringMap;

void fillAndPrint(StringStringMap& coll);



int main()
{
	StringStringMap coll1;
	fillAndPrint(coll1);

	RuntimeStringCmp ignorecase(RuntimeStringCmp::nocase);

	StringStringMap coll2(ignorecase);
	fillAndPrint(coll2);
}

void fillAndPrint(StringStringMap& coll)
{
	coll["Deutschland"] = "Germany";
	coll["deutsch"] = "German";
	coll["Haken"] = "snag";
	coll["arbeien"] = "work";
	coll["Hund"] = "dog";

	coll["gehen"] = "go";
	coll["Unternehmen"] = "ebterprise";
	coll["unternehmen"] = "undertake";
	coll["gehen"] = "walk";
	coll["Bestatter"] = "undertaker";

	cout.setf(ios::left, ios::adjustfield);
	for (const auto& elem : coll)
	{
		cout << setw(15) << elem.first << " "
			<< elem.second << endl;
	}
	cout << endl;
}
本文由作者按照 CC BY 4.0 进行授权