一、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
for(vector::iterator iter= vec.begin();iter != vec.end();)
{
if(*iter == 3)
iter = vec.erase(iter);
else
iter++;
//不删除的时候才++
}
//erase返回的是删除的元素的后一个迭代器
//P83 好好体会这段程序
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
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的删除
//三种函数原型
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的STL定义
template
class 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;
//定义一个
比较函数
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()