C++通用容器
容器简介
1.1 容器的分类
序列容器 vector, list, deque
容器适配器 queue, stack, priority_queue
关联容器 set, map, multiset, multimap
序列容器是提供一组线性储存的容器,而容器适配器负责将它们按照数据结构的方式组织起来,关联容器提供关键字与值之间的关系可以用来快速访问。
vector是一种允许快速随机访问其中元素的线性序列,在末尾添加新的元素比较快
deque是双端队列,两边可以同时添加元素,而且访问速度与vector差不多快
list是双向链表,插入操作比vector和deque要快,但是随机访问要慢得多
1.2 容器与指针
在容器中存放指针,容器并不会帮忙调用指针的析构函数,所以程序员必须自己管理存放的指针。
vector<int> v;
v.push_back(new int(4));
必须自己去删除里面的指针所指向的内存
delete v[i];
2.迭代器
2.1 普通迭代器
一般来讲,容器都有用于遍历的迭代器,除了容器适配器外,因为容器适配器的行为和迭代器的行为是冲突的。
定义一个用于某种容器的迭代器的方法如下:
<ContainerType>::iterator
<ContainerType>::const_iterator
第一个是普通迭代器,第二个是只读迭代器
容器的begin和end方法分别会产生一个指向开头和指向超越末尾的迭代器,如果容器是const,则产生的迭代器也会是const
迭代器支持++和!=,==,等运算,可以用迭代器遍历容器
#include <iostream>
#include<vector>
#include<iterator>
using namespace std;
int main(int argc, char** argv) {int buf[] = {1,2,3,4,5,6,7,8,9,10};vector<int> v;copy(begin(buf), end(buf), back_inserter(v));vector<int>::iterator v_it = v.begin();while(v_it != v.end()){cout << *v_it << " "; v_it++;}cout << endl;return 0;
}
2.2可逆迭代器
可逆迭代器从末尾反向移动,产生可逆迭代器的方法:
<ContainerType>reverse_iterator
<ContainerType>const_revese_iterator
容器的rbegin和rend会产生相应的反向迭代器
#include <iostream>
#include<vector>
#include<iterator>
using namespace std;
int main(int argc, char** argv) {int buf[] = {1,2,3,4,5,6,7,8,9,10};vector<int> v;copy(begin(buf), end(buf), back_inserter(v));vector<int>::reverse_iterator v_it = v.rbegin();while(v_it != v.rend()){cout << *v_it << " "; //输出10 9 8 7 6 5 4 3 2 1v_it++;}cout << endl;return 0;
}
2.3迭代器的种类
1.输入迭代器
定义:istream_iterator 和 istreambuf_iterator
只能读取,而是是一次传递,只对每一个值做一次解析,只允许前向移动。
2.输出迭代器
ostream_iterator,ostreambuf_iterator
基本输入迭代器一样,只是用于写入的
3.前向迭代器
它既可以读,也可以写,而且支持多次读写解析,但是只允许前向移动
4.双向迭代器
它比前向迭代器多一个后向移动的功能
5.随机访问迭代器
它和普通指针一样,但是没有空的概念
输入输出迭代器的程序案例:
istream_iterator<int> it(cin);istream_iterator<int> eof;vector<int> v;copy(it, eof, back_inserter(v));ostream_iterator<int> out(cout," ");copy(v.begin(), v.end(), out);
2.4迭代器的定义与继承体系
struct input_iterator_tag{};//输入迭代器
struct output_iterator_tag{};//输出迭代器
struct forward_iterator_tag: public input_iterator{};//前向迭代器
struct bidirectional_iterator_tag: public forward_iterator_tag{};//双向迭代器
struct random_access_iterator_tag: public bidirectional_iterator_tag{};//随机迭代器
2.5预定义迭代器
C++STL有一个便利的预定义迭代器集合,比如rend和rbegin会返回一个reverse_iterator迭代器。
而插入迭代器则替代operator=给容器赋值,它是一种插入或者压入容器的操作。比如当容器已经满了的时候,这种操作就会分配新的空间。
back_insert_iterator和 front_insert_iterator迭代器的构造函数的参数就是一个序列,然后调用push_back和push_front进行赋值。
back_insert和front_insert函数会产生这样这样对应的迭代器
2.6 流迭代器
istream_iterator有一个默认的构造函数,指向流的末尾的迭代器,所以可用默认的流迭代器对象标注末尾的位置
ostream_iterator没有末尾迭代器,需要一个办法指示末尾
int main(int argc, char** argv) {ifstream in("main.cpp");if(!in)return 0;istream_iterator<char> in_it(in), in_end;ostream_iterator<char> out_it(cout);while(in_it != in_end){*out_it = *in_it++;}return 0;}
2.7 操作未初始化的储存区
raw_storage_iterator是一个输出迭代器,它可以把未初始化的去储存区赋值
int* ptr = new int[10];raw_storage_iterator<int* , int> raw_int_ptr(ptr);for(int i = 0; i < 10; i++)*raw_int_ptr++ = i*i;copy(ptr, ptr+10, ostream_iterator<int>(cout, " "));delete [] ptr
raw_storage_iterator使用的是operator=来给未初始化的储存区赋值,同时,储存区的类型必须与写入的对象要一致,不一致就要进行类型转化。
3.基本序列容器
3.1 vector
这个容器是一个连续的数组,可以快速访问,但是向其中插入新的元素,是基本不可能的,除了push_back(),使用插入算法会造成巨大的代价
并且,vector储存区满了的时候,也会有很多令人诟病的问题.
当vector满了的时候,会自动扩充容量,然后把原来储存的对象复制到新的储存区,原来如果使用迭代器做了某些操作,迭代器将会失效。
当然,这里不是指不能对vector进行删除和插入,而是代价过大,相对而言不值得。
对容器末尾使用插入和删除是可以的。对其他地方,则会造成巨大的开销,比如在vector中间部分插入一个元素,则会导致vector中间开始的元素全体向后移动。
struct ex* p = (struct ex*)malloc(sizeof(struct ex));p->s = (char*)malloc(sizeof(char) * 10);free(p->s);free(p);
3.2 deque
这是一种类似于vector的容器,但是和vector的不同之处在于,deque实际上不是连续的储存区,而是分散的若干连续块储存,然后用一个映射关系来记录各部分。
它可以在两端进行插入和删除,vector只能进行末端的插入的删除。除此之外,deque的push_back要比vector更为高效。vector的随机访问要比deque更快
3.3 序列之间的转换
比如要把deque的内容放到vector之中
这些基本序列都有一个函数assign,只需要传入起点迭代器和终点迭代器,就可以复制给新的容器
3.4 迭代器失效
比如对vector插入一个值,原本的迭代器将会出现未知的错误
template<typename Container>
void print(Container first, Container last, const string& title = "", const string& space = " "){if(title != "")cout << title << ":";while(first != last){cout << *first++ << space;}cout << endl;
}int main(int argc, char** argv) {int buf[] = {1,2,3,4,5,6,7,8,9,10};vector<int> v;v.assign(begin(buf), end(buf));vector<int>::iterator first, last;first = v.begin(), last = v.end();print(first,last);v.push_back(11);//使用push_back之后,迭代器将会失效 print(first,last);return 0;
}
3.5 随机访问
vector和deque都可以通过operator[],和at方法访问,但是operator[]没有边界检查,at有边界检查,如果超过边界,则会抛出异常,但是operator[]的访问速度要比at快
3.6 list
这是一个双向链表,它没办法进行随机访问,也就是不能存在operator[],但是它方便进行删除和插入,这方面的效率远高于vector和deque,list对象被创建之后,绝对不会
被移动,也不会被删除,因为移动意味着链表的结构会被改变。
template<typename Container>
void print(Container first, Container last, const string& title = "", const string& space = " "){if(title != "")cout << title << ":";while(first != last){cout << *first++ << space;}cout << endl;
}int main(int argc, char** argv) {int buf[] = {1,2,3,4,5,6,7,8,9,10};list<int> v;v.assign(begin(buf), end(buf));list<int>::iterator first, last;first = v.begin(), last = v.end();print(first,last);v.push_back(11);//list即使是使用了push_back也不会失效 print(first,last);return 0;
}
对list的排序和反转,最好使用其自带的方法,这样效率更高
3.7 链表与集合
对链表使用sort和unique之后,链表其实就有了集合的性质,不同的是,链表的效率没有集合高,集合是使用平衡树。
3.8 交换序列
这里的交换序列,指的是类型相同的序列进行交换,虽然STL有通用的swap算法,但是,自带的效率要更高一些
4.集合
集合只保留一份副本,按照顺序对元素进行排序,底层使用平衡树,查找速度是对数级。
#include <iostream>
#include<vector>
#include<iterator>
#include<string>
#include<algorithm>
#include<fstream>
#include<memory>
#include<list>
#include<set>
#include<cctype>
#include<cstring>
#include<sstream>
using namespace std;template<typename Container>
void print(Container first, Container last, const string& title = "", const string& space = " "){if(title != "")cout << title << ":";while(first != last){cout << *first++ << space;}cout << endl;
}
//用空格替换字母和'以外的字符
char replaceJunk(char c){return isalpha(c) || c=='\'' ? c : ' ';
}int main(int argc, char** argv) {ifstream in("test.txt",ios::in);if(!in){cerr << "open fail\n";return 0;}set<string> wordList;string line;while(getline(in, line)){transform(line.begin(), line.end(), line.begin(), replaceJunk);istringstream is(line);string word;while(is >> word){wordList.insert(word);}}print(wordList.begin(), wordList.end(),"set","\n");return 0;
下面的程序将使用更加简单的办法,引入流缓冲迭代器
#include <iostream>
#include<iterator>
#include<string>
#include<algorithm>
#include<fstream>
#include<memory>
#include<set>
#include<cctype>
#include<cstring>
#include<sstream>
using namespace std;template<typename Container>
void print(Container first, Container last, const string& title = "", const string& space = " "){if(title != "")cout << title << ":";while(first != last){cout << *first++ << space;}cout << endl;
}int main(int argc, char** argv) {ifstream in("test.txt",ios::in);if(!in){cerr << "open fail\n";return 0;}set<string> wordList;istreambuf_iterator<char> p(in),end;//输入流迭代器没有末尾迭代器的标志while(p != end){string word;insert_iterator<string> ii(word,word.begin());//这个迭代器需要一个容器和插入容器中的起始位置while(p != end && !isalpha(*p)) //跳过所有的空格++p;while(p != end && isalpha(*p))//如果是字符,则插入到word中*ii++ = *p++;if(word.size() != 0)wordList.insert(word);} print(wordList.begin(), wordList.end(),"set","\n");return 0;
}
istreambuf_iterator是一个输入流迭代器,所以需要一个默认构造的迭代器作为结束的标志,而插入迭代器是输出迭代器,它包含了容器的指针和一个指向容器的某处的迭代器,
初始化之后,对迭代器的输入,则是从指向此处的迭代器的位置开始的。
5.可完全重用的标识符识别器
创建自己的迭代器
关于迭代器的定义
template<typename _Category, typename _Tp, typename _Distance = ptrdiff_t,typename _Pointer = _Tp*, typename _Reference = _Tp&>struct iterator{/// One of the @link iterator_tags tag types@endlink.typedef _Category iterator_category;/// The type "pointed to" by the iterator.typedef _Tp value_type;/// Distance between iterators is represented as this type.typedef _Distance difference_type;/// This type represents a pointer-to-value_type.typedef _Pointer pointer;/// This type represents a reference-to-value_type.typedef _Reference reference;};
//创建自己的迭代器需要以上的类型说明
#ifndef __MY_ITERATOR__
#define __MY_ITERATOR__
#include <iostream>
#include<algorithm>
#include<functional>
#include<iterator>
#include<string>
#include<cctype>
using namespace std;
//这个程序可以在一个字符流上运算,可以根据分割符来提取单词 //判定字符是否为数字或者字母
struct Isalpha : public unary_function<char, bool>{bool operator()(char c){return isalpha(c);}
};//分割符合的判定
class Delimiters : public unary_function<char, bool>{
private:string exclude;
public:Delimiters(){}Delimiters(string excl) : exclude(excl){}bool operator()(char c){return exclude.find(c) == string::npos;}
};//类型,函数对象
template<class InputIter, class Pred = Isalpha>
class TokenIterator : public iterator<input_iterator_tag,string, ptrdiff_t>{
private:InputIter first;InputIter last;string word;Pred predicate;
public:TokenIterator(){}//End sentialTokenIterator(InputIter begin, InputIter end, Pred pred = Pred()) : first(begin), last(end), predicate(pred){}//i++TokenIterator& operator++(){word.resize(0);first = std::find_if(first, last, predicate);while(first != last && predicate(*first)){word += *first++;}return *this;}//new classclass CaptureState{private:string word;public:CaptureState(string& w): word(w){}string operator*(){ return word;} };//++iCaptureState operator++(int){CaptureState d(word);++*this;return d;}//取值 string operator*() const{return word;}string* operator->()const{return &word;} //比较迭代器 --这里只关心是否到了迭代器的末尾bool operator==(const TokenIterator&){return word.size() == 0 && first == last;} bool operator!=(const TokenIterator& rv){return !(*this == rv);}
};
#endif
int main(int argc, char** argv){ifstream in("test.txt");assert(in != NULL);istreambuf_iterator<char> cBegin(in), end;Delimiters delimiters(" ,.\t\n\\?!");TokenIterator<istreambuf_iterator<char>, Delimiters> wordIter(cBegin, end, delimiters), tend;vector<string> v;copy(wordIter, tend, back_inserter(v));print(v.begin(), v.end(),"vector","\n");return 0;
}
以上程序实际上是做了一个单词的读取程序,test.txt文档中读取单词,迭代器会自动查询delimiters对象,这个对象会存放固定的分隔符号,比如空格,读取到这个分隔符,代表一个单词就结束了,然后写入word,然后插入vector中,vector负责存放单词。
TokenIterator类是一个封装好的迭代器,它的是从iterator类继承而来,只使用了一个输入迭代器,由于输入迭代器没有指示末尾的标志,所以需要一个last迭代器来指示末尾
下面将要介绍适配器,适配器有stack, priority_queue, queue三种, 他们是通过调整某一适配器来符合自己的性质,容器适配器不能使用迭代器进行遍历,因为这与他们的性质不符。
5. 堆栈stack
stack适配器的默认容器是deque,定义如下,这是直接复制粘贴的源代码。
template<class T,class Container = std::deque<T>
> class stack;
template<typename _Tp, typename _Sequence = deque<_Tp> >class stack{// concept requirementstypedef typename _Sequence::value_type _Sequence_value_type;template<typename _Tp1, typename _Seq1>friend booloperator==(const stack<_Tp1, _Seq1>&, const stack<_Tp1, _Seq1>&);template<typename _Tp1, typename _Seq1>friend booloperator<(const stack<_Tp1, _Seq1>&, const stack<_Tp1, _Seq1>&);public:typedef typename _Sequence::value_type value_type;typedef typename _Sequence::reference reference;typedef typename _Sequence::const_reference const_reference;typedef typename _Sequence::size_type size_type;typedef _Sequence container_type;protected:// See queue::c for notes on this name._Sequence c;public:explicitstack(const _Sequence& __c = _Sequence()): c(__c) { }explicitstack(_Sequence&& __c = _Sequence()): c(std::move(__c)) { }/*** Returns true if the %stack is empty.*/boolempty() const{ return c.empty(); }/** Returns the number of elements in the %stack. */size_typesize() const{ return c.size(); }/*** Returns a read/write reference to the data at the first* element of the %stack.*/referencetop(){__glibcxx_requires_nonempty();return c.back();}/*** Returns a read-only (constant) reference to the data at the first* element of the %stack.*/const_referencetop() const{return c.back();}/*** @brief Add data to the top of the %stack.* @param __x Data to be added.** This is a typical %stack operation. The function creates an* element at the top of the %stack and assigns the given data* to it. The time complexity of the operation depends on the* underlying sequence.*/voidpush(value_type&& __x){ c.push_back(std::move(__x)); }template<typename... _Args>voidemplace(_Args&&... __args){ c.emplace_back(std::forward<_Args>(__args)...); }/*** @brief Removes first element.** This is a typical %stack operation. It shrinks the %stack* by one. The time complexity of the operation depends on the* underlying sequence.** Note that no data is returned, and if the first element's* data is needed, it should be retrieved before pop() is* called.*/void pop(){c.pop_back();}void swap(stack& __s){using std::swap;swap(c, __s.c);}};
它使用的方式是has-a,也就是内部的容器定义为_Swqueue c;
可以使用一个容器对其初始化
我们也可以更改它的底层容器
固定需要的方法如下:
(1)T& top()获取栈顶元素
(2)void pop() 弹出栈顶元素
(3)void push()向栈顶添加元素
(4)bool empty() 判断是否为空
(5)int size() 返回元素个数
int buf[10] = {1,2,3,4,5,6,7,8,9,10};vector<int> v(begin(buf), end(buf));stack<int,vector<int>> s(v);while(!s.empty()){cout << s.top() << endl;s.pop();}
栈的概念就是先进后出。所以对栈的操纵都是对栈顶的操作
6.队列 queue
队列的除了行为和栈不一样,实现方式其实和stack差不多,所以这里就不写定义了。它默认使用deque,一般来讲,也不需要改变默认容器
队列是一种先进先出的数据结构,其行为如下
(1)bool empty()判断是否为空
(2)size_type size() 获取元素数量
(3)T& front() 获取队列首位的元素
(4)T& back()获取队列的末尾元素
(5)void push()入队
(6)void swap()交换两个序列
(7)void pop()删除首个元素
7.优先队列 priority_queue
定义如下:
template<typename _Tp, typename _Sequence = vector<_Tp>,typename _Compare = less<typename _Sequence::value_type> >class priority_queue;
其中的保护成员是:
protected:
_Sequence c;//优先队列使用的容器--我们可以通过公有继承来获取这个序列
_Compare comp;//优先队列使用的比较函数
以上是优先队列的定义,默认容器是vector,默认使用的比较函数对象是less<T>,所以默认的优先队列是升序排列
内部使用的底层算法全都是make_heap, push_heap,pop_heap;
优先队列实际上就是一个堆
常用的方法如下:
(1)bool empty()
(2)size_type size()
(3)const T& top()const 获取堆顶的元素
(4)void push() 插入堆
(5)void pop() 删除堆顶的元素
(5)void swap() 交换两个队列
需要注意的是,优先队列所比较函数的比较函数operator<在没有默认提供的前提下必须自己提供,
int buf[10] = {1,2,3,4,5,6,7,8,9,10};priority_queue<int,vector<int>,greater<int>> q(begin(buf), end(buf));//修改比较函数,使其升序排列while(!q.empty()){cout << q.top() << endl;q.pop();}
接下来是直接输出优先队列的容器内容,这里我们使用公有继承
template<typename Container>
void print(Container first, Container last, const string& title = "", const string& space = " "){if(title != "")cout << title << ":";while(first != last){cout << *first++ << space;}cout << endl;
}class Test_priority_queue: public priority_queue<int>{
public:vector<int>& getContainer(){return c;}
};int main(int argc, char** argv){int buf[10] = {1,2,3,4,5,6,7,8,9,10};Test_priority_queue testQ;for(int i =0 ; i < 10; i++){testQ.push(i);}vector<int> v = testQ.getContainer();print(begin(v), end(v));return 0;
}
8.持有二进制位
C++提供了bitset和vector<bool>来表示二进制,但是bitset和STL实际上没有多大关系,和其他的STL组织方式相去甚远,只有vector<bool>是vector的一种特殊形式
8.1 bitset<n>
bitset模板接受一个无符号整型的参数,参数n用来表示二进制的位数,另外参数不同就代表类型不同,两者相当于属于不同的类。
将bitset转换为整数的方法是to_ulong
超出设置范围会抛出异常
(1)可以只含有01的字符串初始化,如果超出了范围n,则只取前面的部分
(2)支持各种位移运算,>> << | & ^~
移位运算会补0
(3)bitset& set() 全部置为1, set(N)左边第N位置位1
(4)bool test(n) 第n位如果置位了,则返回true
(5)flip() 反转全部的位 flip(N)第N位反转
(6)count 置位的位数
(7)none() 不存在置位的二进制吗?存在则返回0,不存在返回1
(8)any() 是否存在置位的二进制
(9)all() 是否全置位
(10)operator[] 返回第N位的结果
constexpr int SZ = 32;
typedef bitset<SZ> BS;//产生随机的32位bit
template<int bit>
bitset<bit> randBitset(){bitset<bit> r(rand());for(int i = 0; i < bit/16-1; i++){r <<= 16;r |= bitset<bit>(rand());}return r;
}int main(int argc, char** argv){srand(time(NULL));cout << " sizeof(bitset<16>) " << sizeof(bitset<16>) << endl;//4cout << " sizeof(bitset<32>) " << sizeof(bitset<32>) << endl;//4cout << " sizeof(bitset<48>) " << sizeof(bitset<48>) << endl;//8cout << " sizeof(bitset<64>) " << sizeof(bitset<64>) << endl;//8cout << " sizeof(bitset<65>) " << sizeof(bitset<65>) << endl;//12//因为最低4个字节,一个字节8位BS a(randBitset<SZ>()), b(randBitset<SZ>());cout <<"a = " << a << endl;cout <<"b = " << b << endl;unsigned long ul = a.to_ulong();//转化为整数 cout << ul << endl;//用只含有01的字符串初始哈string cbits("0101");cout << BS(cbits) << endl;//不足32位则会补0cout << BS(cbits,0,3) << endl;//设置0-2位a>>=1;cout <<"右移一位 = "<< a <<endl; cout << "a.set() = " << a.set() << endl;//全部置为1 bitset<10> c;cout <<"c = " << c << endl;cout << "c.set(2) = " << c.set(2) << endl;cout << "c.test(2) =" << c.test(2) << " c.test(1) = "<< c.test(1) << endl;cout << " c.flip() = " << c.flip() << endl; cout << " ~c = " << ~c << endl;cout <<"c.count() = " <<c.count()<<endl;cout <<"c.any() = "<< c.any() << endl;cout <<"c.none() = " << c.none() << endl;cout << "c.reset() = " << c.reset() << endl;return 0;
}
8.2 vector<bool>
里面的内容不是按字节存放的,而是按位存放,所以它的性质与其他STL不相同,比如以下方式就无法用
T& r = vb.front();
T* = &vb[0];
因为它返回的是一个二进制的位,无法索引这个位置
它比普通的vector多一个flip函数。
建议不要使用这个东西
9.关联容器
set, map, multiset, multimap被称为关联式容器,而set可以看作没有关键字只有值的map
关联式容器都有方法count(value),返回有多少个value,对于set和map会返回0或者1,但是杜宇multimap和multiset则会返回多个
set底层的数据结构是平衡树,根据它的定义我们知道,它的键与值是一样的,所以也算是关联容器
template<typename _Key, typename _Compare = std::less<_Key>,
typename _Alloc = std::allocator<_Key> >
class set{
public:
typedef _Key key_type;//键的类型
typedef _Key value_type;//值的类型--set的键和值的类型一样
typedef _Compare key_compare;//键的比较函数
typedef _Compare value_compare;//值的比较函数--比较函数也是一样的
typedef _Alloc allocator_type;//空间分配器
private://下面是空间分配的类型和性质
typedef typename __gnu_cxx::__alloc_traits<_Alloc>::template
rebind<_Key>::other _Key_alloc_type;
typedef _Rb_tree<key_type, value_type, _Identity<value_type>,
key_compare, _Key_alloc_type> _Rep_type;
_Rep_type _M_t; // Red-black tree representing set.--set的核心数据,一棵红黑树
typedef __gnu_cxx::__alloc_traits<_Key_alloc_type> _Alloc_traits;
......
};
set和map都有insert方法。
而对于operator[],map如果使用这个方法的时候,超出范围,则是在这里创建一个关联值
#include<iostream>
#include<fstream>
#include<deque>
#include<vector>
#include<list>
#include<set>
#include<cassert>
#include<stack>
#include<queue>
#include<map>
#include<functional>
#include"p4.h"
#include<bitset>
#include<ctime>
#include<cstdlib>
using namespace std;template<typename Container>
void print(Container first, Container last, const string& title = "", const string& space = " "){if(title != "")cout << title << ":";while(first != last){cout << *first++ << space;}cout << endl;
}class Noisy{
private:static long create, assign, copycons, destroy;long id;
public:Noisy(): id(create++){cout << "d[" << id << "]" << endl;}Noisy(const Noisy& rv): id(rv.id){cout << "c[" << id << "]" << endl;copycons++;}~Noisy(){cout << "~[" << id << "]" << endl;++destroy;}Noisy& operator=(const Noisy& rv){cout << "(" << id << ")=[" << rv.id << "]" << endl;id = rv.id;++assign;return *this;}friend bool operator<(const Noisy& lv, const Noisy& rv){return lv.id < rv.id;}friend bool operator>(const Noisy& lv, const Noisy& rv){return lv.id > rv.id;}friend bool operator==(const Noisy& lv, const Noisy& rv){return lv.id == rv.id;}friend ostream& operator<<(ostream& out, const Noisy& n){return out << n.id;}friend class NoisyReport;
};long Noisy::create = 0;
long Noisy::assign = 0;
long Noisy::copycons = 0;
long Noisy::destroy = 0;struct NoisyGen{Noisy operator()(){return Noisy();}
};class NoisyReport{static NoisyReport nr;NoisyReport(){}NoisyReport& operator=(NoisyReport&);NoisyReport(const NoisyReport&);
public:~NoisyReport(){cout <<"Noisy create:" << Noisy::create<<endl;cout <<"Noisy copy:" << Noisy::copycons<<endl;cout << "Noisy assignments: " << Noisy::assign << endl;cout << "Noisy Destroy:" << Noisy::destroy << endl;}
};int main(int argc, char** argv){Noisy na[7];//构造setset<Noisy> ns(na, na + sizeof(na)/sizeof(Noisy));Noisy n;//set插入一个新元素 ns.insert(n); //count查看某个元素存在多少个 cout << "ns.count(n) = " << ns.count(n) << endl;//findif(ns.find(n) != ns.end()) cout << n << "存在" << endl;//打印全部元素copy(ns.begin(), ns.end(), ostream_iterator<Noisy>(cout," "));cout << endl;//map容器map<int,Noisy> nm;for(int i = 0; i < 10;i++){//自动创建键值对nm[i]; }for(int i = 0; i < 10; i++){cout << "nm["<<i<<"]"<<nm[i] << endl;}//自动创建一个键值对nm[10] = n;//插入一个键值对 nm.insert(make_pair(47,n));//输出map<int, Noisy>::iterator it;for(it = nm.begin(); it != nm.end(); ++it)cout << it->first << it->second << endl;return 0;
}
map和pair一样有两个迭代器,first指向键,second指向值
而map的insert返回一个pair迭代器,first指向被插入对的迭代器,second指向bool元素,如果插入成功,则bool值为真
9.1 对关联式容器使用生成器和填充器
对于普通的填充器fill,fill_n,generate,generate_n,用在基本容器上有效,但是对于关联式容器就无法使用了。这里就需要根据实际情况来构造自己的生成器
比如下面的程序。实际上c++已经有很多相近的方法了,可以使用类似的方式来
#include<iostream>
#include<deque>
#include<vector>
#include<list>
#include<set>
#include<cassert>
#include<stack>
#include<queue>
#include<map>
#include<functional>
#include"p4.h"
#include<bitset>
#include<ctime>
#include<cstdlib>
using namespace std;int randInt(){int i = rand() %10;return i;
}//针对map容器的插入
template<typename Assoc, typename Count, typename key_type, typename value_type>
void assocFill_n(Assoc& a, Count n, const key_type& key, const value_type& val){while(n > 0){a.insert(make_pair(key,val));--n;}
}//针对set容器的插入
template<typename Assoc, typename Count, typename value_type>
void assocFill_n(Assoc& a, Count n, const value_type& val){while(n > 0){a.insert(val);--n;}
}int main(int argc, char** argv){int buf[] = {10, 9, 8, 7, 6, 5, 4, 3, 2, 1};vector<int> v(10);list<int> l(10);deque<int> d(10); srand(time(0));generate(begin(v), end(v), randInt); generate(begin(l), end(l), randInt); generate(begin(d), end(d), randInt); copy(begin(v), end(v), ostream_iterator<int>(cout," "));cout << endl;copy(begin(l), end(l), ostream_iterator<int>(cout," "));cout << endl;copy(begin(d), end(d), ostream_iterator<int>(cout," "));cout << endl;//上面是使用基本容器//但是使用关联容器则是一个难点 ,则需要自己定义类似得生成算法//但是,generate和fill这样的算法是一种复制方法,对于关联式容器没有效果//可以使用类似generate_n和fill_n map<int, int> m;assocFill_n(m, 10, 10, 10);map<int, int>::iterator it = m.begin();for( ;it != m.end(); ++it){cout <<it->first << " " << it->second << endl;}//同样的方法可以用在set上 set<int> s;assocFill_n(s, 10, 7);copy(s.begin(), s.end(), ostream_iterator<int>(cout," "));return 0;
}
9.2多重映像和重复的关键字 multimap
这是一个包含重复关键字的关联容器,这就好像身份证一样,身份证号码是唯一的,但是人名却可以重复
//生成值
template <class Map_type, class Key_type, class Value_type>
void MapGenerate(Map_type& m,const Key_type* keyArr, const Value_type* valueArr, int n){while(n > 0){m.insert(pair<Key_type,Value_type>(keyArr[n-1],valueArr[n-1]));--n;}
}//输出模板
template<class key_type, class value_type>
void print(const pair<key_type, value_type>& p){cout << p.first << " " << p.second << endl;
}int main(int argc, char** argv){
//假设有这么几个名字 string name[] = {"aaa", "bbb", "aaa", "aaa", "ccc","fff","ddd"};int number[] = {1,2,3,4,5,6,7};multimap<string, int> person;MapGenerate(person, name, number, 7);for_each(begin(person), end(person), print<string, int>); //查找关键字--返回值是multimap指向开头和末尾的迭代器 //而每一个map存储的是一个pair<first,second>//所以返回值的first是一个map<pair,pair> typedef multimap<string, int>::iterator RangeIterator;pair<RangeIterator,RangeIterator> ptr = person.equal_range("aaa");auto it = ptr.first;while(it!= ptr.second){cout<< it->first << " " << it->second <<endl;it++;}}
对于multiset其实差不多,没有什么好说的了
10 STL的扩充
STL用其他方式实现了set,map 这是因为现在的set和map使用的是红黑树,查找方面已经是对数级别的速度,但是仍然显得不尽如人意,而使用hash算法,索引速度能达到常数级,
但是这种方式消耗的内存要高于红黑树。hash算法实现的STL有hash_map, hash_set, hash_multiset, hash_multimao,slist(单链表), rope(这是一个string的变种,相当于string的优化)
11.非STL容器
前面说过的bitset就是一种非STL容器,另外还有valarray容器,这是一种类vector容器,非STL容器都不支持迭代器,这两个容器的作用是对数值计算进行的特化
#include <iostream>
#include <valarray>
#include <cstddef>using namespace std;template <class T>
void print(const valarray<T>& a, const string& s = ""){if(s != "")cout << s << ":";for(size_t i = 0; i < a.size(); ++i){cout << a[i] << " ";} cout << endl;
}double f(double x){return 2.0 * x - 1;}int main(){double n[] = {1.0, 2.0, 3.0, 4.0};valarray<double> v(n, sizeof(n)/sizeof(n[0]));//数组+数组数量初始化 print(v, "v");valarray<double> sh(v.shift(1));///左移动,空出来的位置补0,cshift是循环移动 print(sh,"sh");valarray<double> acc(v + sh);///维度相同的相加 print(acc,"acc");valarray<double> trig(sin(v) + sin(sh));//所有的数学函数和运算符号都进行了重载 print(trig, "trig");valarray<double> p(pow(v, 3.0));//同类型初始化 print(p, "p");valarray<double> app(v.apply(f));//apply调用函数 print(app, "app");valarray<bool> eq(v == app);//比较返回一个结果数组 print(eq, "bool eq");double x = v.max();double y = v.min();double z = v.sum();cout << x << " " << y << " " << z << endl; return 0;
}
切片
#include <iostream>
#include <valarray>
#include <cstddef>using namespace std;template <class T>
void print(const valarray<T>& a, const string& s = ""){if(s != "")cout << s << ":";for(size_t i = 0; i < a.size(); ++i){cout << a[i] << " ";} cout << endl;
}double f(double x){return 2.0 * x - 1;}int main(){int data[] = {1,2,3,4,5,6,7,8,9,10,11,12};valarray<int> v(data, 12); //数组初始化 print(v,"v");valarray<int> r(v[slice(0,4,3)]);//起点,个数,距离 print(r, "v[slice[0,4,3]");valarray<int> r2(v[v>6]);//只能用在初始化中 print(r2, "v>6");int buf[] = {1, 4, 7, 10};valarray<int> save(buf, 4);v[slice(0,4,3)] = save;print(v,"v");//valarray<size_t> siz(2);siz[0] = 2;siz[1] = 3;print(siz,"siz"); valarray<size_t> gap(2);gap[0] = 6;gap[1] = 2;print(gap,"gap"); //相当于提取一个2D数组/**/ valarray<int> r3(v[gslice(0,siz,gap)]); print(r3,"v[gslice(0,siz,gap)");return 0;
}
相关文章:
C++通用容器
容器简介1.1 容器的分类序列容器 vector, list, deque容器适配器 queue, stack, priority_queue关联容器 set, map, multiset, multimap序列容器是提供一组线性储存的容器,而容器适配器负责将它们按照数据结构的方式组织起来,关联容器提供关键字与值之间…...
字符串的特殊读取——基于蓝桥杯两道题目(C/C++)
目录 1 例题 1.1 卡片换位 1.2 人物相关性分析 2 字符串的读取 2.1 综述 2.2 scanf 2.3 getline/getchar/get 2.4 注意 2.5 说明 先看例题 1 例题 1.1 卡片换位 问题描述 你玩过华容道的游戏吗? 这是个类似的,但更简单的游戏。 看…...
[足式机器人]Part3机构运动微分几何学分析与综合Ch01-4 平面运动微分几何学——【读书笔记】
本文仅供学习使用 本文参考: 《机构运动微分几何学分析与综合》-王德伦、汪伟 《微分几何》吴大任 Ch01-4 平面运动微分几何学1.2.3-2 点轨迹的Euler-Savary公式1.2.4 高阶曲率理论1.2.3-2 点轨迹的Euler-Savary公式 例1-7: 平面曲柄摇杆机构的 Euler-Sa…...
【每日一题Day120】LC2341数组能形成多少数对 | 哈希表 排序
数组能形成多少数对【LC2341】 给你一个下标从 0 开始的整数数组 nums 。在一步操作中,你可以执行以下步骤: 从 nums 选出 两个 相等的 整数从 nums 中 移除这两个整数,形成一个 数对 请你在 nums 上多次执行此操作直到无法继续执行。 返回一…...
win11/10+opencv3.x/4.x配置 VS2019方法(简单使用,亲测)
首先下载 opencv,去官网下载百度》输入opencv,点击opencv|home,进入官网。点击 “Library”---->Release点击 对应版本下的 window版本,点击 --安装--extract---》设置路径。这个就是把库文件扩展到指定的路径下,扩…...
HTTP协议---详细讲解
目录 一、HTTP协议 1.http 2.url url的组成: url的保留字符: 3.http协议格式编辑 ①http request ②http response 4.对request做出响应 5.GET与POST方法 ①GET ②POST 7.HTTP常见Header ①Content-Type:: 数据类型(text/html等)在上文…...
Syntax-Aware Aspect-Level Sentiment Classification with PWCN 论文阅读笔记
一、作者 Chen Zhang, Qiuchi Li, and Dawei Song. 2019. Syntax-Aware Aspect-Level Sentiment Classification with Proximity-Weighted Convolution Network. In Proceedings of the 42nd International ACM SIGIR Conference on Research and Development in Information …...
hadoop考试应急
概述 四大特点:大量化、快速化、多元化、价值化 关键技术:采集、存储管理、处理分析、隐私和安全 计算模式:批处理、流、图、查询分析计算 Hadoop处理架构 了解就好 2007年,雅虎在Sunnyvale总部建立了M45——一个包含了4000…...
【React】Hooks
🚩🚩🚩 💎个人主页: 阿选不出来 💨💨💨 💎个人简介: 一名大二在校生,学习方向前端,不定时更新自己学习道路上的一些笔记. 💨💨💨 💎目…...
升级Room引发的惨案!!
kotlin升级 在升级kotlin的时候,直接升级到大版本的kotlin(比如1.7以上),直接报错,只是报错不知道原因。 koltin Release details 后来把koltin版本改成1.6.0,报如下的错,我们才知道gradle是需…...
RPC框架:一文带你搞懂RPC
RPC是什么(GPT答) ChatGPT回答: RPC(Remote Procedure Call)是一种分布式应用程序的编程模型,允许程序在不同的计算机上运行。它以一种透明的方式,将一个程序的函数调用定向到远程系统上的另一个程序,而使…...
电子招标采购系统源码—企业战略布局下的采购寻源
智慧寻源 多策略、多场景寻源,多种看板让寻源过程全程可监控,根据不同采购场景,采取不同寻源策略, 实现采购寻源线上化管控;同时支持公域和私域寻源。 询价比价 全程线上询比价,信息公开透明࿰…...
P16 激活函数与Loss 的梯度
参考:https://www.ngui.cc/el/507608.html?actiononClick这里面简单回顾一下PyTorch 里面的两个常用的梯度自动计算的APIautoGrad 和 Backward, 最后结合 softmax 简单介绍一下一下应用场景。目录:1 autoGrad2 Backward3 softmax一 autoGrad输入 x输出损…...
ThinkPHP5美食商城系统
有需要请私信或看评论链接哦 可远程调试 ThinkPHP5美食商城系统一 介绍 此美食商城系统基于ThinkPHP5框架开发,数据库mysql,前端bootstrap。系统角色分为用户和管理员。用户注册登录后可购买美食,个人中心,评论和反馈等ÿ…...
Vue3 - $refs 使用教程,父组件调用获取子组件数据和方法(setup() / <script setup>)
前言 在 Vue2 中父组件使用 $refs 调用子组件数据和方法非常简单,但在 Vue3 中这种方法行不通了。 本文实现了 Vue3 中父组件使用 $refs 获取调用子组件数据和方法教程, 并且提供了 setup() 与 <script setup> 两种 “开发模式” 的示例代码,请根据需要进行选择。 网…...
华为OD机试 - 众数和中位数(Python)| 真题+思路+考点+代码+岗位
众数和中位数 题目 众数是指一组数据中出现次数多的数 众数可以是多个中位数是指把一组数据从小到大排列,最中间的那个数, 如果这组数据的个数是奇数,那最中间那个就是中位数 如果这组数据的个数为偶数,那就把中间的两个数之和除以 2 就是中位数查找整型数组中元素的众数并…...
一眼万年的 Keychron 无线机械键盘
一眼万年的 Keychron 无线机械键盘 一款好的键盘对于程序员或者喜欢码字的人来说是非常重要的,而最近博主入手了自己的第一款机械键盘——Keychron 无线机械键盘。 机械键盘特点 有独立轴体,通过两个簧接触,来触发信号,价格相对贵…...
自动化测试高频面试题(含答案)
Hello,你们的好朋友来了!今天猜猜我给大家带来点啥干货呢?最近很多小伙伴出去面试的时候经常会被问到跟自动化测试相关的面试题。所以,今天特意给大家整理了一些经常被公司问到的自动化测试相关的面试题。停,咱先收藏起…...
3、按键扫描检测处理
说明:本文处理按键的短按、长按检测执行,非矩阵按键 硬件可以类似如下连接即可,无需放置上下拉电阻; 按键动作分长按、短按(弹起时执行)两种 按下不放执行长按,但松开按键时不予执行短按函数 多个按键可以同时操作 按…...
集中式存储和分布式存储
分布式存储是相对于集中式存储来说的,在介绍分布式存储之前,我们先看看什么是集中式存储。不久之前,企业级的存储设备都是集中式存储。所谓集中式存储,从概念上可以看出来是具有集中性的,也就是整个存储是集中在一个系…...
【机器学习数据集】如何获得机器学习的练习数据?
一、scikit-learn自带数据集Scikit-learn内置了很多可以用于机器学习的数据,可以用两行代码就可以使用这些数据。自带的小的数据集为:sklearn.datasets.load_<name>load_bostonBoston房屋价格回归506*13fetch_california_housing加州住房回归20640…...
【编程实践】使用 Kotlin HTTP 框架 Fuel 实现 GET,POST 接口 kittinunf.fuel【极简教程】
目录 Fuel 简介 实现代码 GET网络请求用法(有三种写法...
大数据DataX(一):DataX的框架设计和插件体系
文章目录 DataX的框架设计和插件体系 一、DataX是什么...
软考高级信息系统项目管理师系列之十一:项目进度管理
软考高级信息系统项目管理师系列之十一:项目进度管理 一、进度管理领域输入、输出、工具和技术表二、项目进度管理1.项目进度管理过程2.项目进度管理三、项目进度管理过程1.项目进度管理2.工作包和活动3.活动清单4.活动属性5.项目进度网络图6.资源日历7.活动资源需求8.资源分解…...
vue2版本《后台管理模式》(下)
文章目录前言一、home 页以下都属于home子组件二、header 头部 组件二、Menu 页面三、Bread 面包屑四、Footer五 、分页器: Pageing![在这里插入图片描述](https://img-blog.csdnimg.cn/fbe9bb7e84a04ccda4d3fc9f4ab9c36b.png#pic_center)六、权限管理总结前言 这章…...
软考中级-程序设计语言
(1)解释器解释源程序时不生成独立的目标代码,源程序和解释程序都参与到程序执行中。(2)编译器编译时生成独立的目标代码,运行时是运行与源程序等价的目标程序,源程序不参与执行。阶段补充&#…...
Sphinx : 高性能SQL全文检索引擎
Sphinx是一款基于SQL的高性能全文检索引擎,Sphinx的性能在众多全文检索引擎中也是数一数二的,利用Sphinx,我们可以完成比数据库本身更专业的搜索功能,而且可以有很多针对性的性能优化。 Sphinx的特点 快速创建索引:3分…...
ansible实战应用系列教程6:管理ansible变量
ansbile实战应用系列教程6:管理ansible变量 Ansible VariablesNaming VariablesDefining Variables在playbook中定义变量Defining Variables in Playbooks在playbooks中使用VariablesHost Variables and Group Variables使用group_vars和host_vars目录命令行定义全局变量Varia…...
java8新特性Stream流中anyMatch和allMatch和noneMatch的区别详解
1、anyMatch 判断数据列表中是否存在任意一个元素符合设置的predicate条件,如果是就返回true,否则返回false。 接口定义: boolean anyMatch(Predicate<? super T> predicate); 方法描述: 在anyMatch 接口定义中是接收 Pr…...
双网卡(有线和wifi)同时连接内网和外网
双网卡(有线和wifi)同时连接内网和外网 Win10技巧:如何修改有线/WiFi网络优先级:https://www.ithome.com/html/win10/253612.htm双网卡实现两个网络的自由访问:https://blog.51cto.com/ghostlan/1299090Linux服务器安…...
优秀网站建设哪家便宜/seo在线优化技术
什么是IPython?可能很多人已经在用,却不知道它到底是什么。 根据维基百科的解释:IPython是一种基于Python的交互式解释器,提供了强大的编辑和交互功能。IPython拥有:满足你各种需求的交互式shell火爆数据科学社区的Jup…...
金陵热线 网站备案/站长交流平台
使用新浪SAE架构搭建自己的网站。将自己在本地编写的PHP程序上传到SAE上。如果要正常使用需要链接MySQL数据库(如果你的网站使用了MySQL数据库服务)。新浪SAE提供了对PHP访问MySQL的程序支持。所以这个过程要实现起来并不困难。只需要修改用户名和密码。创建完应用后࿰…...
做网站编辑好吗/百度应用商店app下载安装
如果你感觉累,那就对了那是因为你在走上坡路。。这句话似乎有点道理的样子,时常提醒自己无论走到哪都不要忘记自己当初为什么出发。有时想想感觉有的东西可以记录一下,就把它记录下来吧,这次想写一下关于单张图片点击全屏预览的问…...
建设网站一定要会代码吗/关键词是网站seo的核心工作
客服微信:meijing8001您只管发布,我们来为您宣传你好香河、香河新鲜事、香河招聘网指尖香河、香河限号、香河生活通等无论您在哪里发布,这些平台都将同步显示从此找工作,招人才就是这么简单!2020年10月31日统计全新打造…...
南京高端网站建设工作室/百度搜索引擎推广步骤
web应用程序 本质 socket服务端 浏览器本质是一个socket客户端1. 服务器程序 socket请求 接受HTTP请求,发送HTTP响应。比较底层,繁琐,有专用的服务器软件,如:Apache Nginx2. 应用程序,实现具体逻辑WSGI&…...
ic千库网/seo专员是指什么意思
首先感慨下 vivizhyy 现在正在看的这本书——《Flex 完全自学手册》,这本书会让你看后相当有自信心,因为一般你会发现里面的代码不是太 cuo 就是太冗余……好吧,拿书里面给出的单选控件与用户交互的例子来说,书里给的 ① 个解决方…...