1 std::string

头文件:<string>

功能:提供一系列字符串的操作

生成一个std::string对象

using namespace std;
string strs; // 生成一个空的字符串
string s(str); //生成一个和str内容完全相同的字符串(浅拷贝)
string s(str,idx);//生成一个从str[idx]到str末尾内容的字符串
string s(str,idx,length);//生成一个从str[idx]到str[idx+length]的字符串
string s(c_str);//生成一个接受C风格字符串的string对象,如string s("123");

其它的两种相对不太常用,就不列举了。

查询std::string的长度

using namespace std;
string str;
str.size();// 返回str的长度
str.length();// 返回str的长度

其它的不太常用,略过。

查询std::string的元素

str.at(idx); //位于idx索引的字符,越界会抛出异常
str[idx];// 越界不会抛出异常

两个std::string的比较

string A;
string B;
A.compare(B); //A和B完整比较
A.compare(A_begin,A_end,B,B_begin,B_end); //比较A[A_begin:A_end]和B[B_begin:B_end]
A == B; //也可以用重载过后的运算符直接比较

修改和替换std::string

string str1("123456");
string str;
str.assign(str1); //直接赋值
str.assign(str1,0,3); //把str1[0:3]赋值给str
str = "123"; //直接赋值
str.replace(str_pos,length,s);//从str[str_pos]开始,替换成s
str.replace(str_pos,length,s,s_idx);//从str[str_pos]开始,替换成length[s_idx:]

还有一种迭代器用法和其他不常用用法,但是迭代器本质上和索引是等价的,就不再赘述了。

string str1("123456");
str1.erase(str1.begin(),str1.begin()+1); // 使用迭代器删除从开始到开始偏移1个字节的子串。

erase()函数还接受索引直接删除,这里也不赘述。

string str1("123");
string str2("321");
str1.swap(str2);// str1和str2交换

增加字符到std::string中

string A("ello");
string B("HA");
B.insert(1, A); // B变为"HelloA",意思是从B的第一位开始插入A字符串
B.insert(1, "yanchy ", 3); // 表示从B的索引为1的地方开始插入"yanchy"的前3位
B.insert(1,A,2,2);//从B的第一位开始插入A的第二位开始的前2位
B.append("123"); //追加"123"到B的末尾
B.append("123",1);//追加"123"的前1位到B的末尾

append的其他用法和insert类似,少一个插入位置就可以了。其它的用法比较少用,不赘述。这里的所有的索引都可以用迭代器替代,不赘述。

std::string的输入输出

string s1,s2;
cin >> s1;// 使用istream的>>重载运算符读入s1
getline(cin,s1);//使用函数getline读入
getline(cin,s2,' ');//使用getline读入,并且以空格分界,空格及之后的东西全部会被忽略

std::string的查找

string s1("123456");
s1.find('1',0);//从索引0开始寻找字符1
s1.find("12",0);//从索引0开始寻找目标串12
s1.find(s2,0);//从索引0寻找s2表示的字符串

而其中,find_first_of()函数用于寻找目标串第一次出现的位置,find_last_of()函数用于寻找最后一次出现的位置。

std::string中迭代器的几种特殊用法

string s("123");
string s2(s.begin(),s.end());//通过迭代器构造s2
transform(s.begin(),s.end(),toupper);//通过模板类函数来把函数作用于迭代器指示的区域。
for (auto iter = s.begin();iter!=s.end();iter++)
    cout << *iter;// 通常遍历s的方法

2 std::array

头文件:<array>

功能:提供类似于C的数组,长度恒定,且不需要自己管理内存。访问时不需要事先知道长度(长度储存在array模板类中)。

std::array的创建

using namespace std;
array<double,100> data;// 创建一个double类型的,长度为100的数组,值是未定义的。
array<double,100> data{};// 创建一个double类型的,长度为100的,各个值均为0的数组。
array<double,100> data{1.0,2.0,3.0,4.0};// 前四个值被赋值为1.0,2.0,3.0,4.0,其余全为0。
// 同样地,调用成员函数fill也可以实现某个值的全员初始化,但这不在创建的讨论范围之内。

std::array的访问

data[1]; // 用索引直接访问,会有越界风险
data.at(1); //避免了越界风险
std::get<idx>(array_obj); //使用模板函数get<n>()获取第n位的数据,这个n不可以是循环变量,必须是编译时可以确定的值

还有使用迭代器的访问方法,此处不再赘述。

std::array的比较和赋值

std::array<double,4> these {1.0, 2.0, 3.0, 4.0};
std::array<double,4> those {1.0, 2.0, 3.0, 4.0};
std::array<double,4> them {1.0, 3.0, 3.0, 2.0};
these == those; // 判断所有元素是否都相等
them = those; //只要类型相同,长度相同,就可以直接赋值。
them[0] = 2.0; //可以直接通过索引修改值,也可以使用at成员函数修改,避免越界。

3 std::vector

头文件:<vector>

功能:提供一种自变长的类栈数据结构,适用于LIFO型数据结构。

std::vector的创建

using namespace std;
vector<double>values; //创建一个double类型的vector
vector<unsigned int> primes {2u, 3u, 5u, 7u, 11u, 13u, 17u, 19u}; //创建一个unsigned int的vector并初始化
values.reserve(20); //给values开辟内存空间,使其可以多存入20个元素,但是其size依然是0,只有capacity是20。
vector<double>values(20); //创建一个double类型的vector,size为20,并且所有的元素值都为0。
vector<double>values(size,init_value);//创建一个大小为size,初值为init_value的double-vector

std::vector的size(大小)和capacity(容量)

values.resize(5);//把当前vector的size变为5,capacity不变。
values.resize(size,init_value);//变成size长度,并用init_value初始化。

std::vector的访问

values[2];//访问第2位的元素,可能会越界
values.at(2);//越界抛出异常
values.front();//返回第0个元素的引用
values.back();//返回最后一个元素的引用
values.data();//返回指向内部存储数据的数组的指针
values.shrink_to_fit();//使capacity和size相等

std::vector插入、删除元素

values.push_back(20);//vector一般只可以在末尾插入元素
values.pop_back();//vector只可以在末尾删除元素,而且不会返回末尾的值。
values.clear();//清空整个vector
vector<string> str_vec;
std::string str {"alleged"};
str_vec.emplace_back(str,2,3);// emplace_back可以调用对应数据类型的构造函数

还有一种用随机访问迭代器来批量插入元素到vector的,比较花里胡哨,这里不赘述。

还有emplace和insert方法都可以在vector的任意位置插入元素,erase通过接受迭代器删除任意范围的元素,remove通过接收迭代器和特定的值来删除特定的元素。我认为这破坏了vector作为我认为的类栈的特性,如果真要用,那还不如直接用Python的list呢。

3.std::deque

头文件:<deque>

功能:提供双向队列的数据结构。

deque的创建

using namespace std;
deque<int> a_deque;//创建一个size=0的deque
deque<int> deques(10);//创建一个size=10的deque
deque<std::string> words { "1","2","3' };//使用初始化方法来初始化一个deque
deque<int> deque_2 {deques}; //使用另一个deque来初始化deque

deque是没有capacity属性的,deque的capacity总是和size相等。

deque的访问和大小修改

words.at(idx); //返回word[idx]伴随着越界检查
words[idx];
words.resize(size);
words.resize(size,init_value);

这些基本都是和vector一样的,就不再赘述了。

deque添加和删除元素

deque<int> numbers {2,3,4};
numbers.push_front(11); //向队头插入数据
numbers.push_back(12); //向队尾插入数据
numbers.pop_front(); //弹出队首数据,同样地,这个方法不返回队首数据
numbers.pop_back(); //弹出队尾数据,同样地,这个方法不返回队尾数据

其它的insert()/erase()方法我认为破坏了双向队列的性质,不在这赘述。

4.std::list

头文件:<list>

功能:提供双向链表的构建方法。

list的创建

using namespace std;
list<std::string> words; //创建一个空的双向链表
list<std::string> words {20};//创建一个有20个结点的双向链表
list<double>values(size,value);//创建一个有size个结点,每个结点的值为value的双向链表
list<double>save_values{values};//使用另一个list来初始化

list的元素插入、删除

std::list<std::string> names { "Jane", "Jim", "Jules", "Janet"};
names.push_front("Ian"); // 在表首插入一个字符串
names.push_back("Kitty"); // 在表尾插入一个字符串
names.emplace_front("Ian");// 更高效的插入
names.emplace_back("Kitty");// 更高效的插入
names.insert(++begin(data),"Me"); //在第一个位置插入字符串"Me"

list元素的删除

std::list<int> numbers { 2, 5, 2, 3, 6, 7, 8, 2, 9};
numbers.clear(); //清空整个双向链表
numbers.remove(value);//清除整个双向链表中所有值为value的结点
numbers.remove_if([](int n) {return n%2==0;}) //接受一个一元断言,并以一元断言的结果进行清除
numbers.unique(); //清楚整个双向链表中重复的元素

list元素的排序和合并

std::list<std::string> names { "Jane", "Jim", "Jules", "Janet"};
names.sort(); //无参排序,将所有元素升序排列(如果可能)
names.sort(std::greater<std::string>()); //接受一个二元断言来排序
names.sort(std::greater<>());//简洁一点
names.sort([](string s1, string s2) 
{
    return s1.length() > s2.length() ? (s1[0] == s2[0]) : (s1 > s2)
});
std::list<int> my_values {2, 4, 6, 14};
std::list<int> your_values{ -2, 1, 7, 10};
my_values.merge (your_values);// 合并两个链表,两个链表都必须是升序,合并后,your_values为空

// 以下代码把your_words的一部分,粘贴到了my_words的指定位置
std::list<std::string> my_words {"three", "six", "eight"};
std::list<std::string> your_words {"seven", "four", "nine"};
my_words.splice(++std::begin(my_words), your_words, ++std::begin(your_words));
// my_words的内容为:"three","four","six","eight"
// your_words的内容为:"seven","nine"

list元素的访问

std::list<std::string> names { "Jane", "Jim", "Jules", "Janet"};
for (const auto&name : names)
    cout << name << endl;
// 可以通过迭代的方式迭代,也可以通过迭代器的方法迭代,但是list不支持随机访问
list.front(); //返回第一个元素的引用
list.back(); //返回最后一个元素的引用

5.std::forward_list

头文件:<forward_list>

功能:提供单链表的实现。

forward_list的创建

std::forward_list<std::string> my_words {"three", "six", "eight"}; //创建一个forward_list并初始化它
std::forward_list<int> nums(20); //创建一个有20个结点的单链表

forward_list的大小

auto count = std::distance(std::begin(my_words),std::end(my_words));// forward_list没有size()成员函数

forward_list的插入和修改

std::forward_list<int> nums(20);
nums.front() = 2; //直接通过引用修改
nums.push_front(3); //把3插入到链表头

// 把your_words的最后一个元素插入到my_words的开头
std::forward_list<std::string> my_words {"three", "six", "eight"}
std::forward_list<std::string> your_words {"seven", "four", "nine"};
my_words.splice_after(my_words.before_begin(), your_words, ++std::begin(your_words));

6.std::stack

头文件:<stack>

功能:提供一个栈的实现

stack的创建

std::stack<std::string> words; //创建一个存储std::string的栈
std::stack<std::string,std::list<std::string>>fruit; 
//stack的底层也是一种模板容器,只要它具有back(),push_back(),pop_back(),empty(),size()这些栈的基本操作
// 就可以用作stack的容器
std::list<double> values {1.414, 3.14159265, 2.71828};
std::stack<double,std::list<double>> my_stack (values); // 底层容器赋值
std::stack<double,std::list<double>>copy_stack {my_stack}; //拷贝复制

stack的系列操作

stack<int,list<int>>stk;
stk.push(10);
stk.top(); //返回10
stk.size(); //返回1
stk.empty();//返回false
stk.pop();//不返回任何东西,弹出栈顶元素
stk.empty();//返回true
stk.emplace(10);//在栈顶调用构造函数生成对象
stack<int,list<int>>stk2;
stk2.push(100);
stk.swap(stk2);//把stk和stk2的东西进行交换
stk = stk2;//使用operator=()重载进行赋值

7.std::queue

头文件:<queue>

功能:提供标准的FIFO容器的实现。

queue的创建

std::queue<std::string> words;
std::queue<std::string> copy_words {words};
// 也可以指定底层容器,底层容器必须满足front(),back(),push_back(),pop_front(),empty()和size()这几个操作
std::queue<std::string, std::list<std::string>>words;

queue的一系列操作

queue<int,list<int>>q;
q.push(10);
q.front(); //返回10
q.size(); //返回1
q.empty();//返回false
q.push(20);
q.back();//返回20
q.pop();//不返回任何东西,弹出队首元素
q.pop();
q.empty();//返回true
q.emplace(10);//在队尾调用构造函数生成对象
queue<int,list<int>>q2;
q2.push(100);
q.swap(q2);//把q和q2的东西进行交换
q = q2;//使用operator=()重载进行赋值

要注意的是,queue和stack都不支持随机迭代器访问操作。

8.std::priority_queue

头文件:<priority_queue>

功能:提供一个类队列的,满足头元素出队的数据结构,优先队列中的所有元素都按指定的规则排列。

priority_queue的创建

std::priority_queue<std::string> words;
std::string wrds[] { "one", "two", "three", "four"};
std::priority_queue<std::string> words { std::begin(wrds),std:: end(wrds)};
std::priority_queue<std::string> copy_words {words}; // copy of words
std:: string wrds[] {"one", "two", "three", "four"};
std::priority_queue<std::string, std::vector<std::string>,std::greater<std::string>> words1 {std::begin (wrds) , std:: end (wrds) };
std::priority_queue<std::string, std:dequeue<std::string>,std::greater<std::string>> words1 {std::begin (wrds) , std:: end (wrds) };

第一种创建一个空的优先队列。

第二种创建一个使用数组+迭代器批量创建的优先队列。

第三种使用构造复制来复制一个优先队列。

第四种指定了一元谓词std::greater,用于指定排序规则。

第五种指定了底层容器,只要底层容器有front(),push_back(),pop_back(),size(),empty()的实现就可以。

priority_queue的操作

对priority_queue的所有操作都和queue的操作是一样的,插入时的位置搜索是由容器自动完成的,不需要使用者去写。

9.堆操作

在标准容器及容器适配器中,并没有对堆结构的定义,而是在algorithm头文件里面定义了基于vector的一系列堆操作。堆结构指的是一棵完全二叉树结构中,父节点均大于等于其叶子结点或均小于等于其叶子结点。

头文件:<algorithm>

功能:提供基于堆结构的一系列算法。

堆的创建

std::vector<double>numbers{2.5,10.0,3.5,6.5,8.0,12.0,1.5,6.0};
std::make_heap(std::begin(numbers),std::end(numbers));
// 这个时候的numbers会变成{12,10,3.5,6.5,8,2.5,1.5.6}
// 是按照层次遍历存储的完全二叉树
std::make_heap(std::begin(numbers),std::end(numbers),std::greater<>());
// 上述语句创建了一个小根堆

堆的维护

// 当数组的元素分布不符合堆的定义的时候,使用push_heap来把它重新组织成一个堆
numbers.push_back(1.2);
push_heap(std::begin(numbers),std::end(numbers));
//如果make_heap指定了一元谓词,在push_heap的时候也要指定同样的一元谓词
pop_heap(std:begin(numbers),std::end(numbers));
//把第一个元素移到最后,然后把前面的元素重新组合成一个堆,这个时候调用pop_back就可以把这个根弹出。

堆的判断

std::is_heap(std::begin(numbers),std::end(numbers),std::greater<>());
auto iter = std::is_heap_until(std::begin(numbers),std::end(numbers),std::greater<>());
if(iter != std::end(numbers))
    std::cout << "numbers is a heap up to "<< *iter << std::endl;

第一种判断整个vector是否为heap,第二种判断第一个导致整个vector不为heap的迭代器位置。

发表回复