前言
打怪升级:第15天 |
---|
一、string
string类对string变量、字符串(char*)以及单个字符(char)一般都有对应的函数。
string本质上就是字符串,内部原理是char*(了解过C语言的朋友应该可以更好地理解下面的内容)
(一)初始化 、 赋值 、 拼接
c++重载的函数是非常多的,我们需要做的是先来见识一番,之后根据自己的习惯和需求掌握其中几种即可。
// string四种初始化方式
string s1; // 无参构造
string s2("hello string"); // 使用字符串初始化
string s3(s2); // 使用同为string的s2初始化(拷贝构造)
string s4(10, 'w'); // 使用字符初始化
// 补充: string s5(s2.substr(0,10)); 拷贝构造:s2从下标为0的位置开始往后10个字符
示例:
// string赋值方式 -- 对 operator= 的重载
char* str="hello string";
string s6;
s6=str;
s6=s2;
s6='w';
// 使用函数赋值 assign()
s6.assign(str); // 使用字符串赋值
s6.assign(str, n1); // 从下标为n1处开始
s6.assign(str, n1, n2); // 从下标为n1处开始,前n2个字符
s6.assign(s2);
//s6.assign(s2, n1); 这个函数在vs中可以使用,但是在Dev上识别不出来,应该是vs对assign函数做了扩展。
//s6.assign(s2, n1, n2);
s6.assign(10, 'w');
(二)单个字符的访问和修改
[]、at()
string s1("hello");
for(int i = 0; i < s1.size(); i++)
{
cout << s1.at(i) << ' '; // 等同于 s1[i]
}
cout << endl;
(三)插入和删除
insert 、erase
string s1("hello");
s1.insert(s1.size(), " 靓仔");
cout << s1 << endl;
s1.erase(5, s1.size() - 5); // 如果第二个数据大于后面剩余的字符个数,那就只会把后面的字符全部删掉
cout << s1 << endl;
运行实例:
(四)查找和替换
find、rfind、replace
find函数在找不到时会返回 string::npos
string s7;
int pos = s7.find("bit", n1, n2); // 在s7中,从下标为n1的位置开始,前n2个字符中是否存在字符串"bit"
// 找到了返回下标,找不到就返回EOF
s7.rfind(); // 从右往左查找,找最后一次出现的位置
s7="abcdefghigklmn";
s7.replace(2, 0, "0000"); // 替换下标为2的位置0个字符为字符串"0000",等价于:插入
运行实例:
(五)比较
compare
等于:返回 0
大于: 返回 1
小于:返回 -1
void test05()
{
string s1("hello");
string s2("hello");
cout << s1.compare(s2) << endl;
}
(六)获取子串
substr
string s1 = "panda@csdn.com";
int pos = s1.find('@');
string s2 = s1.substr(0, pos);
cout << "用户名:" << s2 << endl;
运行实例:
二、vector
不定长的单头数组
(一)初始化和赋值 --(4+3)
=、assign
// 初始化 -- 4种
vector<int>v1; // 无参构造
vector<int>v2(v1); // 拷贝构造
vector<int>v3(v1.begin(), v1.end()); // 通过区间初始化
vector<int>v4(10, 8); // 使用n个数字初始化
// 赋值 -- 3种
cout << "赋值" << endl;
vector<int>v5;
v5 = v1; // operator=
vector<int>v6;
v6.assign(v1.begin(), v1.end()); // assign()函数,区间赋值
vector<int>v8;
v8.assign(10, 8); // 使用n个数字赋值
运行实例:
(二)访问数据 – (4)
[]、at()、front、back
vector<int>v1;
InitVector(v1);
// operator[]
for (int i = 0; i < v1.size(); i++)
{
cout << v1[i] << ' ';
}
cout << endl;
// at()
for (int i = 0; i < v1.size(); i++)
{
cout << v1.at(i) << ' ';
}
cout << endl;
// front(), back()
cout << v1.front() << ' ' << v1.back() << endl;
//补充: begin(), end() iterator:迭代器
for(vector<int>::iterator it = v1.begin(); it < v1.end(); it++)
{
cout << *it << ' ';
}
cout << endl;
运行实例:
(三)插入、删除和清空
insert、erase、clear
vector<int>v1;
InitVector(v1);
PrintVector(v1);
v1.pop_back(); // 尾删
PrintVector(v1);
v1.insert(v1.begin() + 2, 10); // 在下标为2的位置插入一个 10
PrintVector(v1);
v1.insert(v1.begin(), 2, 100); // 在最前面插入两个 100
PrintVector(v1);
v1.erase(v1.end() - 1); // 删除最后一个位置的数据
PrintVector(v1);
v1.erase(v1.begin(), v1.begin() + 2); // 删除 [0,2)之间的数据
PrintVector(v1);
v1.clear(); // 清空
PrintVector(v1);
运行实例:
(四)大小和容量
size、capacity、resize
vector<int>v1;
InitVector(v1);
v1.resize(12); // 改变容器大小,空位默认用 0 填充
bool temp = v1.empty(); // 判空
cout << "size->" << v1.size() << endl;
cout << "capacity->" << v1.capacity() << endl;
v1.push_back(12);
PrintVector(v1);
运行实例:
(五)交换
swap
vector<int>v1;
InitVector(v1);
PrintVector(v1);
vector<int>v2(6, 'w');
PrintVector(v2);
swap(v1, v2);
PrintVector(v1);
PrintVector(v2);
// 收缩内存 -- 借助匿名容器
v1.assign(100000, 1);
cout << "交换前:size->" << v1.size() << " capacity->" << v1.capacity() << endl;
v1.resize(10);
cout << "resize:size->" << v1.size() << " capacity->" << v1.capacity() << endl;
vector<int>(v1).swap(v1); // 匿名容器执行完当前语句后被系统回收
cout << "交换后:size->" << v1.size() << " capacity->" << v1.capacity() << endl;
运行实例:
(六)预开辟空间
reserve
cout << "系统自动更新capacity" << endl;
vector<int>v1;
int* pi = NULL;
int num = 0;
for (int i = 0; i < 100000; i++)
{
v1.push_back(i);
if (pi != &v1[0])
{
pi = &v1[0];
num++;
}
}
cout << "重新开辟内存次数:" << num << endl;
cout << "提前开辟好空间" << endl;
vector<int>v2;
v2.reserve(100000); // reserve是预开辟空间,但是实际大小还是0
int* ppi = NULL;
num = 0;
for (int i = 0; i < 100000; i++)
{
v2.push_back(i);
if (ppi != &v2[0])
{
ppi = &v2[0];
num++;
}
}
cout << "重新开辟内存次数:" << num << endl;
运行实例:
注意:如果发生了重新开辟内存,那么之前声明的迭代器就需要进行更新才能使用。
三、deque
双端数组(双端队列) – 操作选项同上面的vector
四、stack
栈:stack是一种非常简单的数据结构,只能从栈顶进出数据,
它和vector并不一样,
vector是一个数组,虽然是单端数组,但是数组有的操作vector也有,比如遍历、排序、POS位置的插入和删除等
而stack则有更多的限制,只能访问栈顶的数据,如果想要访问后面的数据就需要将栈顶数据出栈,那么栈顶数据就丢失了,
由于栈的限制很多,所以它操作起来非常简单。(因为它能做的操作少啊)
class Person
{
public:
Person(string name, int age)
{
this->_name = name;
this->_age = age;
}
string _name;
int _age;
};
// stack
void test03()
{
stack<Person>s;
Person p1("宁姚", 32);
Person p2("陈平安", 33);
Person p3("天真", 5000);
s.push(p1); // 入栈
s.push(p2);
s.push(p3);
cout << "size->" << s.size() << endl; // 获取大小
while (!s.empty()) // 判空
{
cout << "name->" << s.top()._name << "\tage->" << s.top()._age << endl; // 获取栈顶数据
s.pop(); // 出栈
}
cout << "size->" << s.size() << endl;
}
举个栗子:
五、queue
队列:queue和stack一样也是很简单的容器,它的要求是只能在队头出数据,只能在队尾入数据。
六、list
链表:list (底层为双向循环链表)
构造、赋值、交换、大小和容量、插入、删除和清空、访问数据、
vector有的list基本上都有,并且用法完全相同(但是底层原理是不同的)。
list更多的操作:
- 删除:remove
- 翻转:reverse
- 排序:sort
bool Mycompare(int a, int b)
{
// 升序
//return a < b;
// 降序
return a > b;
}
void test04()
{
list<int>l1;
for (int i = 10; i > 5; i--)
{
l1.push_back(i);
l1.push_front(1);
}
cout << "改变前:";
for_each(l1.begin(), l1.end(), Print<int>); // Print()是自己写的函数模板
cout << endl;
cout << "翻转后:";
l1.reverse(); // 翻转
for_each(l1.begin(), l1.end(), Print<int>);
cout << endl;
// 所有不支持随机访问的容器,不可以使用标准算法,既sort(),因此这里容器自己提供了 list.sort()算法
// 对于自定义类型必须指定排序函数,不然编译器不知道如何排序
cout << "升序列:";
l1.sort(); // 排序 -- 升序
for_each(l1.begin(), l1.end(), Print<int>);
cout << endl;
cout << "降序列:";
l1.sort(MyCompare); // 排序 -- 降序:自己提供一个排序函数
for_each(l1.begin(), l1.end(), Print<int>);
cout << endl;
cout << "移除 1:";
l1.remove(1); // 移除
for_each(l1.begin(), l1.end(), Print<int>);
cout << endl;
}
运行实例:
list没有的操作:
- 对数据的访问:at()、[ ]
因为list底层是链表,无法像数组一样通过下标进行访问。
list<int>::iterator it = li.begin(); // iterator 是迭代器,这里it可以当做一个指针使用,但是它并不是指针
it = it + 1; // error ,链表不支持随机访问
it++; // right
it--; // right, 双向链表可以找到前后节点
// 这里说的是设立的思想,不过至于为什么支持 ++ 不支持 it + 1,那是因为大佬们在设计的时候只重载了 ++,而没有重载 + 号,
// 这是为了防止使用者 有了 +1, 就想用 +2 、 +300等的随机访问操作,这在链表中是无法使用的。
七、set、multiset
集合:set – (底层为红黑树)
特点:在加入数据时会自动对数据进行排序,因此也叫关联式容器;
使用时只需包含头文件< set >;
set中不能出现重复的数据(重复数据不会被插入);
multiset可以出现重复数据。
(一)初始化、赋值
=
set的初始化和赋值比较简单,初始化只有默认构造和拷贝构造,赋值只有:=
set<int>s1; // 默认构造
set<int>s2(s1); // 拷贝构造
// 赋值
set<int>s3;
s3=s2;
(二)插入、删除
insert、erase、clear
由于set在放入数据时会自动排序,无需设置将数据放到首部、尾部还是中间某个位置,
因此set容器放入数据只需要一个insert即可。
set<int>s4;
s4.insert(10);
s4.insert(20);
s4.insert(50);
s4.insert(40);
s4.insert(30);
for(set<int>::iterator it=s4.begin(); it!=s4.end(); ++it)
cout<< *it<<' ';
cout<<endl;
运行实例
(三)查找、统计
find、count
在set中由于不能出现重复数据,因此count(key)只有0或1两个值。
void test06()
{
set<int>s;
s.insert(1);
s.insert(5);
s.insert(4);
s.insert(2);
s.insert(3);
set<int>::iterator it = s.find(2);
if (it == s.end())
cout << "did not find" << endl;
else
cout << *it << endl;
it = s.find(20);
if (it == s.end())
cout << "did not find" << endl;
else
cout << *it << endl;
cout << "number of 2->" << s.count(2) << endl;
}
运行实例:
(四)大小、交换
size、swap
(五)排序
set在插入数据时直接对数据进行排序,默认是升序序列,如果我们要改变排序规则就需要在插入数据之前进行,
这里我们可以写一个仿函数(重载小括号),这里需要将仿函数写在类里面,因为set后面的括号里需要的是一个类型。
class MyCompare
{
public:
template<typename t>
bool operator()(t v1, t v2)const // vs中这个 const不能省略
{
return v1 > v2;
}
};
int main()
{
set<int, MyCompare>s1;
s1.insert(50);
s1.insert(20);
s1.insert(40);
s1.insert(30);
s1.insert(10);
for (set<int, MyCompare>::iterator it = s1.begin(); it != s1.end(); it++)
cout << *it << ' ';
cout << endl;
return 0;
}
结果:
对于自定义类型数据,我们必须设置比较函数
class Person
{
public:
Person(string name, int age)
{
this->_name = name;
this->_age = age;
}
string _name;
int _age;
};
class MyCompare
{
public:
bool operator()(const Person& s1, const Person& s2)const // const都不可省
{
return s1._name.compare(s2._name) >= 0;
}
};
int main()
{
set<Person, MyCompare>s2;
Person p1("陈平安", 32);
Person p2("宁姚", 31);
Person p3("裴钱", 24);
s2.insert(p1);
s2.insert(p2);
s2.insert(p3);
for (set<Person, MyCompare>::iterator it = s2.begin(); it != s2.end(); it++)
cout << "name->" << (*it)._name << "age->" << it->_age << endl;
return 0;
}
示例:
拓展:
pair
对组:pair,内置函数模板很少,这里我们了解一下它的初始化、赋值以及访问。
vector<pair<string, int> >array;
pair<string, int>Person("崔巉", 240); // 初始化
array.push_back(Person);
Person = make_pair("左右", 230); // 赋值
array.push_back(Person);
Person = make_pair("刘十六", 2000);
array.push_back(Person);
Person = make_pair("齐静春", 200);
array.push_back(Person);
Person = make_pair("老秀才", 300);
array.push_back(Person);
for(int i=0; i<array.size(); ++i)
cout << array[i].first << ' ' << array[i].second << endl; // 访问数据
运行实例:
八、map、multimap
图、查找表:map (底层为红黑树)
map中所以元素都是pair;
pair中的第一个元素为key(键值),起到索引作用,第二个元素为value(实值);
map在插入数据时会按照键值进行升序排序,因此map也是关联式容器。
优点:
可以通过键值快速查找value。
map和multimap的区别:
map中不能出现重复的key,而multimap中可以(与set和multiset区别一样);
注意这里只限制key,而map中如果出现重复的value是可以的。
(一)初始化、赋值
初始化:默认构造和拷贝构造
赋值:operator=
void PrintMap(map<const int, int>m)
{
for (map<int, int>::iterator it = m.begin(); it != m.end(); ++it)
{
cout << "key->" << it->first << " value->" << it->second << endl;
}
cout << endl;
}
void test04()
{
// 创建map数组 -- 底层为二叉树
map<const int, int>m1; // vs2022中不加const会报错
// pair<int, int>tmp;
//tmp = make_pair(4, 40);
m1.insert(pair<const int, int> (1, 10)); // 插入数据,插入过程中会自动按照key值排序
m1.insert(pair<const int, int>(4, 40));
m1.insert(pair<const int, int>(2, 20));
m1.insert(pair<const int, int>(3, 30));
PrintMap(m1);
map<const int, int>m2(m1); // 拷贝构造
PrintMap(m2);
// 赋值
map<const int, int>m3;
m3 = m1;
PrintMap(m3);
}
运行实例:
(二)插入、删除
insert、erase、clear
void test05()
{
// 这里讲解4中插入的方法
map<const int, int>m1;
// 第一种
m1.insert(pair<const int, int>(1, 10));
// 第二种
m1.insert(make_pair(3, 30));
// 第三种
m1.insert(map<const int, int>::value_type(2, 20));
// 第四种
m1[4] = 40; // key=4, value=40
// 第四种一般用来插入数据,因为可以通过键值来访问value
// 不建议用来访问数据,因为如果访问的键值不存在,编译器会自动创建 value为0的对组
cout << m1[10] << endl;
PrintMap(m1);
}
运行实例:
(三)查找、统计
find、count
同set
(四)大小、交换
size、empty、swap
同set
(五)排序
class MyCompare
{
public:
bool operator()( int v1, int v2)const
{
return v1 > v2;
}
};
void test08()
{
map<const int, int, MyCompare>m1;
m1.insert(make_pair(1, 10));
m1.insert(make_pair(2, 20));
m1.insert(make_pair(3, 30));
for (map<int, int, MyCompare>::iterator it = m1.begin(); it != m1.end(); ++it)
{
cout << "key->" << it->first << " value->" << it->second << endl;
}
cout << endl;
}
运行实例:
总结
-
查找:find
只有string类模板的find函数找到时返回值为下标,找不到则返回 string::npos
其他自带find函数以及标准函数返回值都为迭代器,找不到则返回 end() 。 -
单个数据访问:at
我们一般会觉得有了下标访问操作符就足够了,at会有些多余,
但是对于set、map这些容器是不能通过 [ ] 进行访问的,这时就需要at() 了,由此可知at() 更加通用。 -
迭代器
eg: vector v1;
vector::iterator it = v1.begin();
这里 it 就是一个迭代器,可以当做指针使用,但是实际上并不是指针,而是类模板,
迭代器通过重载指针的操作符 -> 、++、–等封装了一个指针;
是一个“可遍历STL( Standard Template Library)容器内全部或部分元素”的对象。
因此 it 不能使用 NULL初始化,而且v1.begin() 的返回值也不能赋给整形指针。
参考文章:迭代器和指针 -
insert、erase后的迭代器失效
失效原因:
- insert扩容后会出现野指针;
- erase后指针指向改变(可以接收返回值);
解决方法:使用insert和erase之后为了防止迭代器失效,我们应该防止再次使用,下次使用时应再次查找。
-
resize 和 reserve
resize是调整大小,往大了调整后空位默认补0,
reserve是预开辟空间,但是此时的大小时没有改变的,比如说:
你现在有100块钱,银行说可以给你贷款50万,那么这50万就是你可以使用的,但是这些钱实际上还是属于银行的,你实际拥有的钱只有100块,你去相亲的时候不能说你现在拥有50万存款,
这里也是一样的,我们重新创建的变量,此时size=0, 我们reserve(100),这是“贷款”,并不是真的给你100的内存空间,你的实际大小还是0,
只有使用resize(100),调整了自身的大小之后才能够说我拥有50万的存款。
因此,在使用copy、merge等需要将数据移动到一个新的容器时,需要确保该容器拥有足够的空间,如果不够,就使用resize调整容器的大小。 -
大小
只有顺序表(数组)有容量这一说,链表是想要添加数据就找一块区域即可,因此可以说整个内存都是它的容量。
所以list、set、map这些只有size()函数,但都没有内置capacity() 函数。 -
声明:函数原型截图来源于黑马程序员的c++教学视频,本文也是在看过了视频之后进行的自我总结。