常用STL的使用

一、STL是什么
  • STL是一个标准模板库,是一个高效的C++程序库
  • qsort():C标准库的快速排序
二、string
char str[12] = "Hello";
char *p = str;
*p = 'h';            //可以赋值,因为创建了数组 ,在内存中又开辟了一块空间,然后把字符串赋值给该数组,是可以改变的量

char *ptr = "Hello";
*ptr = 'h';          //不能赋值。ptr指向内存中的一个临时常量。常量不能赋值
//更标准的用法
const char *cptr = "Hello";
  • string是一个字符串的类
  • string.h和cstring都不是string类的头文件。都是定义的C风格字符串的一些操作方法,如strcpy()、strlen()等。只是string.h是c语言的头文件,cstring是C++对应的头文件,是为了和c语言进行兼容。
1.string类的实现
  • string类的底层是一个字符串指针,即char *m_data;
1)普通构造函数   P68
//构造函数见P68
传入的是一个char *类型的字符串;
如果传入是一个空字符串,那么string就也是一个空字符串"\0";
如果str是非空,那么私有成员m_data就要预留length+1的长度,+1用来存放'\0';
//指针赋值只是把两个指针指向了同一个位置而已
//创建一个char *类型的变量,m_data需要首先分配内存,然后再拷贝
2)sting的析构函数
string::~string()
{
    if(m_data){                //判断m_data是否为空
        delete[] m_data;
        m_data = nullptr;      //删除之后需要赋空指针
    }
}
3)拷贝构造函数
string::string(const string &s)  // 传入的参数是一个常引用。一是保证传入参数不变,二是不进行拷贝,减少栈空间的消耗。(如果不使用引用将造成无限循环)
{
    if(!s.m_data)                //m_data的null判断
    {
        m_data = nullptr;
    }
    else{
         m_data = new char[strlen(s.m_data)+1];    //开辟空间
        strcpy(m_data,s.m_data);                  //复制
    }
}
4)赋值函数
string &string::operator=(const string &s)    //返回值是引用,而参数仍是常引用
{
    if(this != &s){                            //检查自赋值
        delete[] m_data;                      //释放原有的内存资源
        if(!s.m_data){                        //对m_data做null判断
            m_data = nullptr;
        }
        else{
            m_data = new char[strlen(s.m_data)+1];
            strcpy(m_data,s.m_data);
        }
    }
    return *this;                              //返回*this
}
5)字符串连接
//三种情况:1.传入指针为空 2.本身内容为空 3.两者均不为空
string &string::operator+(const string &s)
{
    string newstring;        //需要新建一个对象
    if(!s.m_data)            //检查传入指针为空
        newstring = this;
    else if(!m_data)         //
        newstring = s;
    else{
        newstring.m_data = new char[strlen(m_data)+strlen(s.m_data)+1];
        strcpy(newstring.m_data,m_data);
        strcat(newstring.m_data,s.m_data);
    }

    return newstring;        //返回新建的对象
}
6)判断相等
//先判断长度是否相等,再判断内容是否相等
bool string::operator==(const string &s)
{
    if(strlen(m_data) != strlen(s.m_data)){ 
        return false;
    }
    else{
        return strcmp(m_data,s.m_data)?false:true;
    }
}
7)返回长度
int string::getlength()
{
    return strlen(m_data);
}
2.string声明方式 P70
3.C++字符串(string)和C字符串的转换
  • C++字符串转换成对应的C字符串的方法:使用data()、c_str()、copy()来实现。
注意:
1.data和c_str在c++11之后好像就没有区别了
2.c_str()语句可以生成一个const char *指针,并指向空字符的数组。这个数组的数据是临时的,当有一个改变这些数据的成员函数被调用之后,其中的数据会失效
string s = "Hello";
const char *cptr = s.data();
cout << cptr << endl;
s = "World";
cout << cptr << endl;
//输出为:
Hello
World
//所以要么现用现转换,要么复制后再转换
copy(p,n,size_type_off = 0)
//从string拷贝到指针p中,拷贝n个字符,默认从首字符开始,也可以由size_type_off来进行指定。返回拷贝字符的个数,可以通过返回值来添加空字符'\0'
//用户确保p指向控件足够用来保存n个字符
//string::npos:指向string的尾置尾置或者表示string的大小或结束位置
4.C++字符串(string)和int类型的转换
1)int转string
//1.使用snprintf
//snprintf只能转换到C风格字符串
int length = snprintf(p,n,%format,i);
//p为指向char的指针,即char *
//n为输出到p的字符个数
//format为对i进行的格式转换
//i为要转换的整形变量
//注意先进行格式转换,再按照n的值来选择位数输出
//例子
static inline std::string i64tostr(long long a)
{
    char buf[32];
    snprintf(buf,sizeof(buf),"%lld",a);
    return std::string(buf);
}
//2.使用to_string
string s = to_string(i);
//i为要进行转换的整形变量
//3.使用stringstream(准确的说是ostringstream和.str()结合)
int tmp = 10;
ostringstream os;
os << tmp;
string str = os.str();
//str的值即是10
2)string转int的方法
//1.使用strtol、strtoll、strtoul和strtoull函数,其函数原型如下
long int strtol(const char *nptr,char **endptr,int base);
//四个函数的函数原型都差不多,除了返回类型分别问long int,long long int,unsigned long int和unsigned long long int
//形参nptr为要转换的字符串的的指针,endptr为转换到的位置,base为输出的进制
//函数会 跳过最前面的空格字符串遇上数字或者正负号才开始转换。遇到非数字或者空字符才结束
//nptr以0x开头,把nptr当作16进制处理,以0开头,则当作8进制处理
//具体见P75
//这种方法需要先将string转换称为char *,然后再转换称为数字
//将C++字符串转换成10进制数字的通用函数
static inline int64_t strtoint(const str::string &s)
{
    return strtol(s.c_str(),0,10);
}
//2.使用stringstream(准确的说是istringstream)
string str = "11 12 32 14 15";
istringstream is(str);
int tmp;
while(is >> tmp)
    cout << tmp << " ";
//tmp的值就可以分别取的11,12,32,14,15
三、vector
1.vector是什么
  • Vector是顺序容器,是一个动态数组,支持随机存取、插入、删除、查找等操作,在内存中是一块连续的空间。在原有空间不够情况下自动分配空间。vector随机存取效率高,但是在vector插入元素,需要移动的数目多,效率低下。
  • vector是线性容器,它的元素严格按照线性顺序排序,和动态数组很相似
  • 它的元素储存在一块连续的内存区域中
  • 不仅可以使用迭代器,还可以使用指针偏移的方式(即可以使用下标访问)
  • vector可以自动增长或缩小储存空间
优点:
1.下标访问
2.迭代器访问
3.容器末尾增加删除元素
缺点:
1.在其他位置添加删除元素比较慢
2.迭代器和引用没有list支持的好
3.重新分配内存会影响程序的性能
  • 容器的大小指元素的个数,容量是分配的内存大小,容量一般不小于大小。size返回容器的大小(元素的个数),capacity返回容器的容量(占用内存的大小)
2.vector的查增删
1)vector的初始化和遍历
  • 遍历可以使用迭代器
  • 使用for_each遍历vector
void print(int n)
{
    cout << n << " ";
}

vector ivec = {1,2,3,4,5};
for_each(ivec.begin(),ivec.end(),print);
  • 使用sort函数对存放有结构体的vector进行排序,需要重载结构体的比较运算符    P80
  • 除了在结构体内部重载比较函数之外,也可以在结构体之外定义比较函数    P81
bool cmp(Rect item1,Rect item2){... ...}  //定义的比较函数
sort(vec.begin(),vec.end(),cmp);          //使用sort进行排序,使用了定义的比较函数cmp
2)vector的查找
  • 使用find函数在vector中查找元素,注意返回值是一个迭代器
vector::iterator iter = find(vec.begin(),vec.end(),3);     //在vector中查找3
3)vector的删除
  • 使用erase或pop_back删除数据
//使用erase
for(vector::iterator iter= vec.begin();iter != vec.end();)
{
    if(*iter == 3)
        iter = vec.erase(iter);
    else
         iter++;                  //不删除的时候才++
}
//erase返回的是删除的元素的后一个迭代器
//P83   好好体会这段程序
  • pop_back则是弹出最后一个数据
4)vector的增加
  • 使用insert或者push_back来增加元素,其中insert是插入元素到某一个位置,而push_back是在最后添加一个元素
  • P84 insert的几种函数原型,注意插入发生在指定位置(迭代器)的前面,比如指定插入位置是尾后迭代器,那么会在vec的最后插入元素(尾后迭代器前面)
  • 注意删除的是迭代器指出的区间,则都是左闭右开的删除   P85
  • 可以使用vec.clear()来清空vector
3.vector的内存管理与效率
1)使用reserve()函数提前设定容量大小
  • 为了支持随机访问,vector内部是通过动态数组的方式来实现的
  • 总是按照指数边界来增大其内部缓冲区的,即重新分配的大小为1.5~2倍,一般就是2倍。一般都是重新申请一块更大的新内存,并把现有的元素逐个复制过去,同时销毁旧内存。所有迭代器也不能使用了,需要及时更新迭代器
  • 其访问速度和一般数组相比,只有在发生重新分配的时候,性能才会下降。因此如果有大量数据需要push_back,应该使用reserve()函数提前设定其容量大小
//几个相关的成员函数  P86
size():容器中含有多少个元素
capacity():容器在已经分配的内存中可以容纳多少个元素
resize(size_type n):强制容器可以容纳n个元素, 主要是n大于小于当前容量的情况
reserve(size_type n):强制容器把容量改为不小于n 主要是n大于小于当前容量的情况
  • 只要有元素需要插入且容器的容量不足时就会发生重新分配,这一过程包括维护的原始内存的分配和回收、对象的拷贝和析构、迭代器,指针和引用的失效
2)使用“交换技巧”来休整vector过剩空间和内存
  • 即收缩到合适:
vector(ivec).swap(ivec);
//把ivec收缩到了合适的大小
//使用ivec构造了一个临时的vector变量
//然后交换ivec和这个临时变量(其实交换的是指针)
//语句结束,临时变量指向的内存被释放(即之前有空余的内存),而ivec的大小被调整到了合适
3)用swap方法强行释放vector所占的内存
vector v = {1,2,3,4,5,6};
vector().swap(x);
//把vector和一个空临时变量交换,语句结束时临时变量被释放,即原来v的内存空间被释放。v现在为空
4.vector类的简单实现
  • assert(表达式):断言:表达式为真则继续执行余下的语句,表达式为假那么就退出程序,并输出一个错误信息
四、map
1.map是什么
1)map的本质
  • map本质是一类关联容器,属于模板类关联的本质在于元素的值与某个特定的键相关联
  • 增加和删除节点对节点的影响很小
  • 对于迭代器来说,不能修改键值,只能修改其对应的实值
  • map内部自建一棵红黑树(非严格意义的平衡二叉树),所以在map内部所有的数据都是有序的
2)map的功能
  • 自动建立key-value的一一对应的关系
  • map,其中key必须要能够支持<操作符(比较操作)
  • 根据key值进行快速查找,查找的时间复杂度是log(n)
2.map的查增删
1)map的插入
  • map插入:使用insert函数插入pair数据、使用insert函数插入value_type数据和用数组方式插入
  • value_type:模板类的迭代器所指向的对象的类型
  • 使用insert插入已有关键字的元素,插入操作会失败,map不会产生变化
//insert插入pair数据
map mapItem1;
mapItem1.insert(pair(1,"Hello"));
或mapItem1.insert(make_pair(1,"World"));

//insert插入value_type数据
mapItem1.insert( map::value_type (1,"nihao"));

//insert的返回值是一个pair类型,其中第一个元素是map的一个迭代器表示插入的位置,而 第二个元素表示是否插入成功
pair::iterator,bool> insert_resu = mapItem1.insert(make_pair(1,"kenijiwa"));
  • 数组方式插入元素,遇到相同关键字时则直接覆盖
//使用数组方式插入
map mapItem2;
mapItem2[1] = "Hello";
mapItem2[1] = "World";
mapItem2[2] = "nihao";
//插入的结果为world和你好,hello被覆盖
2)map的遍历
  • 三种方法:使用前向迭代器、反向迭代器数组方式遍历(应该不好用数组进行遍历)
//前向迭代器即是begin()、end()
//反向迭代器rbegin()、rend()
//对mapItem1进行反向迭代,从容器尾开始处理直到容器头
for( map::reverse_iterator riter = mapItem1.rbegin();riter != mapItem1.rend();riter++)    //注意这里迭代器仍然是++
{... ...}
3)map的查找
  • 两种方法:使用count函数来判定关键字是否出现、使用find函数来查找关键字的位置
  • count:判断关键字是否出现,但是无法返回关键字出现的位置,当关键字出现时返回1,未出现返回0
mapItem1.count(1);        //返回1
mapItem1.count(5);        //返回0
  • find:定位数据出现的位置,返回一个迭代器。数据出现时,返回数据所在位置的迭代器,否则返回尾后迭代器,即map.end()。
4)map的删除
  • 使用erase来删除函数
//三种函数原型
size_type map.erase(k);        //删除键值为k的元素,返回删除的元素个数
void map.erase(p);             //删除迭代器p所指的元素, p必须指向map中的元素不能越界
void map.erase(b,e);           //删除迭代器[b,e)所指范围内的元素, 必须是一个有效范围
//
for(map::iterator iter = mapItem1.begin();iter != mapItem1.end();){
    if((*iter).second == "Hello"){
        mapItem1.erase(iter++);                //这样使用才是正确的,删除之后迭代器本来应该要失效的,但是这里迭代器能够自动指向下一个元素
    }
}
5)map的排序
  • map的默认是按照key从小到大排序的
//map的STL定义
templateclass Compare = less, class Allocator = allocator>> class map;
//其中第三个参数是一个函数对象,有一个默认值less。函数对象本质上是对()运算符的重载
  • binary_function是二元函数对象的模板类,也是一个基类,本身不重载()运算符,交给派生类完成
template
struct binary_function
{
    typedef Arg1  first_argument_type;
     typedef Arg2  second_argument_type ;
    typedef Result result_type;
};
  • map的定义的第三个参数有一个默认参数less,定义如下
templatestruct less: binary_function        //这里是继承了基类binary_function
{
    bool operator()(const T &x,const T& y)const
    {
        return x < y;
    }
};
  • 同样定义了一个greater,所以如果要让map从大到小排序就要调用这个函数来定义map
//greater定义在头文件
#include
mapgreater> mapItem3;            //元素按key从大到小排序, great中的key值只能够使用键值
  • 可以自己定义一个函数对象的类,让map按照我们希望的方式进行排序
//先定义比较的函数对象
class cmp
{
public :
   bool operator() ( const string & item1 , const string & item2 )
  {
     return item1 . size () < item2 . size ();
  }
private :
};
//使用自己的比较函数对象来定义map
map<string, int, cmp > mapItem5;
//这样就实现了按照string的长度来排序
  • 如果key是结构体,那么必须重载该结构体的<符号操作。否则会导致编译出错
  • sort排序的对象必须是线性储存的,比如序列容器(vector,list,deque),也可以给sort指定比较方法,sort的第三个参数就是比较方法
  • 如果要按照value值对其进行排序,那么需要把map值转存到vector中,然后自己定义一个比较函数或者是函数对象来对vector进行排序
//可以把mapItem的元素存入pairVec中
map mapItem;
vector> pairVec;
//定义一个 比较函数
bool cmpByValue(const pair &item1,const pair &item2)
{
    return item1.second < item2.second;
}
//定义一个 函数对象
struct Cmp_By_Value
{
    bool operator()(const pair &lhs,const pair &rhs)
    {
        return lhs.second < rhs.second;
    }
};
3.map的原理
  • map内部自建一棵红黑树(一种非严格意义的平衡二叉树,本质上是二叉搜索/查找树),该树对数据自动排序,因此map内部的数据有序
  • 红黑书的性质:
1.节点非黑即红
2.根节点为黑色
3.叶子节点为黑色(叶子节点指树尾端的null或者NIL节点)
4.红色节点的子节点必为黑色
5.从任意节点出发,到其所有叶子节点的简单路径上的黑色节点数量相同
  • 红黑书确保没有一条路径会比其他路径长出两倍,因此是接近平衡的
  • 二叉搜索树的性质
1.左子树的所有节点值均小于根节点
2.右子树的所有节点的值都大于根节点
3.左、右子树分别也是二叉搜索树
4.没有两个节点键值相等
  • 红黑树的查找、插入、删除的时间复杂度最坏为O(lgn)
  • 树的左旋转和右旋转 《算法导论》P176
五、set
1.set是什么
  • vector封装了数组,list封装了链表,map和set封装了二叉树
  • set中数元素的值不能直接改变
  • STL中的标准关联容器set,multiset,map和multimap内部采用的都是红黑树
  • set的几个问题
1.为什么map和set的插入删除效率比用其他序列容器高?
答:对于关联容器来说,不需要做内存拷贝和内存移动。set容器内所有元素都是以节点的方式来储存的,插入删除的时候只需要改变指针指向的位置即可
2.为何insert之后,之前保存的迭代器不会失效?
答:迭代器iterator相当于指向节点的指针,内存没有变化,指向内存的指针也就不会失效,迭代器也就不会变化。 删除操作可能导致迭代器失效。
原则:不要使用过期的迭代器iterator
3.当数据元素增多时,set的插入和搜索速度变化如何?
答:数据量增大一倍,搜索次数增加1.O(lgn)
2.set的查增删
1)创建set对象
//共有5种方式
//1.创建一个空对象
set s1;
//2.根据自己的比较函数创建set对象
struct strLess
{
    bool operator()(const char *s1,const char *s2)
    {
        return strcmp(s1,s2) < 0;
    }
};
setstrLen> s2( strLen);        //注意这里要重复写两次
//3.用set对象来拷贝构造
set s3(s1);
//4.使用迭代区间[b,e)来构造
//5.使用迭代区间 [b,e)和自己创建的比较函数来构造
2)set对象的插入:使用insert函数
pair insert(value);
//插入value,返回一个pair对象,pair的第一个元素是插入的位置的迭代器,第二个表示插入是否成功(value不能重复)
3)使用copy对象
#include         //ostream_iterator包含在头文件< iterator>中
copy(s.begin(),s.end(),ostream_iterator(cout,","));
// copy: 把一个序列(sequence)拷贝到一个容器(container)中去
//ostream_iterator(cout,","):创建一个输出对象输出到标准输出,只能输出int型对象,分隔符使用“,”
4)set的删除
//1.size_type erase(value);    删除元素value,返回删除的元素个数
//2.void erase(&pos);          删除pos位置上的元素
//3.void erase(&first,&lasr);  删除[first,last)之间的元素
//4.void clear();              删除所有元素
5)set的查找
//1. count(value);              返回set内元素值为value的元素个数
//2.iterator find(value);      查找value的位置, 找不到将会返回end()
//3.lower_bound/upper_bound/equal_range
6)其他常用函数
//empty()
//size()
//set1.swap(set2);
//rbegin()
//rend()
//max_size()










你可能感兴趣的:(常用STL的使用)