C++STL哪些内容是必须学习的
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C++STL哪些内容是必须学习的相关的知识,希望对你有一定的参考价值。
STL很庞大,我想问了除了最常用的容器,迭代器,还有什么知识需要掌握,中大型软件公司的要求。
这个具体看你的公司要求吧。C++ STL六大组件:
1、容器(Containers)
2、算法(Algorithms)
3、迭代器(Iterators)
4、仿函数(Functors)
5、配接器(Adapters)
6、分配器(Allocators)
前4个用的比较多吧。当然全面掌握的话也没有坏处。所谓“技多不压身”嘛。 参考技术A STL也只是一个工具,别人面试的时候肯定不会问你你会泛型编程里有什么什么
,STL只是提供一些更便捷高效的方式去简化程序,关键在于程序内部模块结构的组成,算法才是核心,不用STL,用C一样可以实现,但是泛型算法提供的东西可以节省不少的时间去写一些很复杂冗余的东西 参考技术B 第一阶段知道怎么用就行了,第二阶段知道如何设计。只要你学过数据结构和算法,学起来很快的。。。。 参考技术C stl其实并不庞大,好多的东西都是一样的。
STL值string学习
写在前面
我们总算是接触到C++的STL了,这是C++模块中很重要的一部分,可以这么说,如果你学C++没有学过STL,那么你的C++很大概率是残缺的,我们学习STL主要学习两个层次,第一个是可以使用,第二个是知道它们的底层,至于更高层次的,现在的我还没有达到,就不和大家分享了。我们先来简单的认识一下STL,有一个大致的了解,今天主要的内容时string。
什么是STL
STL(standard template libaray-标准模板库):是 C++ 标准库的重要组成部分,不仅是一个可复用的组件库,而且是一个包罗数据结构与算法的软件框架。我之前和大家分享过数据结构的知识,也和大家用C语言写过,但是它们的可用性有点低,你在写OJ题时,使用C语言,你会发现有的时候还要自己实现数据结构,大佬们也是有这样的困扰,所以出现STL,使用的便是泛型编程,这也是我们前面谈模板的原因。
STL版本
我们学习STL共有三个版本,里面的功能都大致一样。
- 原始版本 Alexander Stepanov、Meng Lee 在惠普实验室完成的原始版本,这是最初的STL
- P. J. 版本 继承HP版本,被微的VS系列使用,代码可读性底
- SGI版本 继承自HP版 本。被GCC(Linux)采用,可移植性好,阅读性非常高。我们后面学习STL要阅读部分源代码,主要参考的就是这个版本。
STL 组件
STL的六大组件,大家直接看着图片吧,后面的内容都会涉及到,这里大家先来认识一下。
string
我们先来认识一下什么是string,简单来说,string在C语言里我们认识的字符串,在OJ中,有关字符串的题目基本以string类的形式出现,而且在常规工作中,为了简单、方便、快捷,基本都使用string类,很少有人去使用C库中的字符串操作函数 。由于我们比较常用,所以大佬们就把这个给变成了模板类了,某种意义上来说,string不属于STL,它的出现要早的多。
认识string
先看看STL里面关于string的分类,我们可能会感到疑惑,这里怎么会有四种string,我们要学习的是哪一种,SLT不是模板吗,为何这里string看着不像...这些问题我们一个一个解决.
字符编码表
在谈为何会有四种string之前?我们需要谈谈什么是字符编码表.我们都知道在计算机世界中,它们只认识0和1,但是现实世界中,我们是有类似汉语,英语的语言的,我们该如何把现实世界和计算机世界给联系起来呢?这就是字符编码表的作用.最初的字符编码表是由美国提出的,叫做ASCII码,是不支持中文的.
为了编码属于各自国家的字符表,中国编出了GBK,使用的是16位的二进制,但是各国还没有一个统一的标准,这时候国际组织ISO制定了Unicode,也叫万国码.
我们已经知道了字符表的历史,所谓的四种string就是为了支持不同位的字符,是的,我们不是只有char这一个字符类型,我们主要学习的就似乎string,他们四个用法大概差不多,学会了一个,另外几个查查文档就可以了.
int main()
cout << "string : " << sizeof(char) << endl;
cout << "u16string : " << sizeof(char16_t) << endl;
cout << "u32string : " << sizeof(char32_t) << endl;
cout << "wstring : " << sizeof(wchar_t) << endl;
return 0;
string 是模板码
是的,我们看到的string是typedef过的,也就是它是参数为char类型的模板.
string 的底层
string的底层是一个可以动态开辟的字符数组,这一点大家一定要记住,后面我们模拟实现的时候就是按照这个来的.
int main()
string s("hello");
return 0;
使用string
现在我们就可以使用string,我们使用的时候需要包头文件,而且还需要放出std里面的string,少说多做,我们看看就明白了.
#include <iostream>
#include <string>
using std::string;
int main()
std::string s1; // 或者直接放出
string s2;
return 0;
构造函数
现在我们就可以正式接触string里面的内容了,注意C98中string里面存在七个构造函数,但是我们不是每一个都常用,这里我来简绍的有四个.
构造函数 | 函数说明 |
string() | 构造一个空字符串 |
string(const string& str) | 拷贝构造,深拷贝 |
string(const char* s) | 通过一个字符串来构造 |
string(size_t n, char c) | 构造一个含有 n 个 c字符的字符串 |
我们现在分别来演示一下.
string()
int main()
string s;
return 0;
string(const char* s)
int main()
string s("hello string");
return 0;
string(size_t n, char c)
int main()
string s(10,a);
return 0;
string(const string& str)
int main()
string s1("hello");
string s2(s1);
return 0;
长度 & 容量
我们知道了string底层是一个动态开辟的数组,那么string支持了计算我们string对象的有效长度和容量的函数我们直接看看吧.
size() & length()
这两个都是计算string对象的有效长度的,唯一的区别就是函数名不同罢了,length()是最初的方法,我们字符串的有效长度可以形容为length,但是后面的哈希表等就有点说不过去了,所以增加了size()这个函数,为了保持命名的规范性,仅此而已.
int main()
string s("hello");
cout << s.size() << endl;
cout << s.length() << endl;
return 0;
capacity()
计算是当前对象的容量,也就是数组的长度.
int main()
string s("hello");
cout << s.capacity() << endl;
return 0;
max_size()
这个函数就是我们字符串最大能开多长,一般是没有人用的,这里我就提一下就可以了.
int main()
string s;
cout << s.max_size() << endl;
return 0;
operator[] & at()
string重载了[\\]这运算符,可以支持下标访问,这里的at()的作用和[\\]作用是一样的,只不过是早期版本,功能上没有什么区别.
int main()
string s("hello");
cout << s[1] << endl;
cout << s.at(1) << endl;
return 0;
越界访问
我们访问的如果是有效字符的下一个位置,返回的是一个\\0,大家现在还不知道底层,但是我们知道空字符串里面是包含一个\\0 的,这是为了兼容C语言.
int main()
string s("hello");
if (s[5] == \\0)
cout << "s[5] == \\\\0 " << endl;
return 0;
但是如果我们越界访问了,VS编译器会出现报错.
int main()
string s("hello");
s[16];
return 0;
遍历string
遍历string我们存在三种方法,这三种方法也是我们经常用到的。
- 使用 下标
- 使用 迭代器 这个我们重点谈
- 使用 范围for
### 下标访问
string这个类是重载了[\\]这个运算符的,再加上string底层是一个数组,它是支持随机访问的,
int main()
string s("hello");
for (size_t i = 0; i < s.size(); i++)
cout << s[i] << " ";
cout << endl;
return 0;
迭代器访问
或许你还对迭代器有点疑惑,不知道它是什么,这样吧,我简单的说一下,所谓的迭代器就是我们遍历STL最常用的方法.是的,你没有看错,不是所有的STL底层都是连续的空间,也就是说使用下标访问是不具有普遍性的.
string中的迭代器就是一个原生指针,其中迭代器又分为四种.我们来看看吧.
正向迭代器
这里我直接用两种方式来访问,一个是const修饰的,一个可修改的.
下面的是正向迭代器指向的地方,注意end()指向的有效字符的下一个位置.
可以修改迭代器指向的内容
int main()
string s("hello");
string::iterator it = s.begin();
while (it != s.end())
cout << *it << " ";
it++;
cout << endl;
return 0;
如果我们想要修改迭代器指向的内容,这里就可以知道了.
int main()
string s("hello");
string::iterator it = s.begin();
while (it != s.end())
(*it)++;
cout << *it << " ";
it++;
cout << endl;
return 0;
如果我们不想修改迭代器指向的数据,可以直接调用const修饰的迭代器
这个迭代器的主要应用是给那些不想修改的string,我们看看应用.
void func(const string& str)
string::const_iterator it = str.cbegin();
while (it != str.cend())
cout << *it << " ";
it++;
cout << endl;
int main()
string s("hello");
func(s);
return 0;
反向迭代器
谈完了正向的迭代器,这里就要谈谈什么是反向迭代器,它的作用也是遍历string,只不过是反这遍历的.
这个迭代器是可以修改的,这里就不修改了.
int main()
string str("hello");
string::reverse_iterator rit = str.rbegin();
while (rit != str.rend())
cout << *rit << " ";
rit++;
cout << endl;
return 0;
现在还有可以反向的const修饰的迭代器,我们用用就可以了,具体的就不谈了.
int main()
string str("hello");
string::const_reverse_iterator rit = str.crbegin();
while (rit != str.crend())
cout << *rit << " ";
rit++;
cout << endl;
return 0;
范围 for
范围 for就比较简单了,但是有一个缺陷,就是一次肯定遍历完
int main()
string str("hello");
for (auto ch : str)
cout << ch << " ";
cout << endl;
return 0;
它看着比较高大上,实际上底层也是对迭代器的复用,这里的复用的迭代器是可以修改的那种
push_back()
string支持尾部插入一个字符,如果容量不够,编译器会自动扩容.
int main()
string s;
s.push_back(a);
s.push_back(b);
s.push_back(c);
s.push_back(d);
cout << s << endl;
return 0;
string 扩容机制
我们知道了string的底层是一个动态数组,我们现在想看看它是如何扩容的.
从这里我们可以看出,不同编译器的扩容规则是不一样的,VS下是1.5倍扩容,g++是2倍扩,而且刚开始VS就开辟了15个空间,g++没有开
reserve()
大家都知道,扩容是代价的,有的时候需要再次开辟空间和进行数组的拷贝,如果我们要是知道总共开辟的空间,那么提前开辟好不是更好吗,这就是这个函数的作用.
- 只改变容量
- 不改变 size,也就是你可以理解为它是只扩容.
注意,reserve()函数开辟的空间不一定和我们要的一样,需要内存对齐的,但是大差不差.
int main()
string s;
s.reserve(100);
return 0;
但是这里就有两个疑问了,如果我们的N大于现在存在的存在的容量会怎么样,小于又会怎么样?这里我们来讨论一下.
N > capacity
这个和上面的一样,会自动扩容,只改变capacity,不改变size.
int main()
string s("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa");
int size = s.size();
cout << size << endl;
s.reserve(s.capacity() + 20);
return 0;
N < capacity
这种情况如果发生,不会出现任何变化.
int main()
string s("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa");
cout << "原始 size " << s.size() << endl;
cout << "原始 capacity() " << s.capacity() << endl;
s.reserve(15);
cout << "size " << s.size() << endl;
cout << "capacity() " << s.capacity() << endl;
return 0;
resize()
这个函数的作用从名字上面就可以看出它是重置size,也就是说,我们重新指定尾插数据的地方,如果空间不够,编译器会自动扩容.但是这里有两个函数,本质上是一个缺省函数.
int main()
string s;
cout << "size " << s.size() << endl;
cout << "capacity() " << s.capacity() << endl;
s.resize(20);
cout << "size " << s.size() << endl;
cout << "capacity() " << s.capacity() << endl;
return 0;
void resize (size_t n)
这个是重置 N个空间,从开始的那里开始数,数到下标为N的地方,这个及其以后都重置的空间变成\\0.这里分为三种情况.这里我们就来讨论两种共情况,第三种就是扩容的情况.
情况一
int main()
string s;
s.push_back(a);
s.push_back(b);
s.push_back(b);
s.push_back(b);
s.push_back(b);
s.resize(2);
return 0;
情况二
int main()
string s;
s.push_back(a);
s.push_back(b);
s.push_back(b);
s.resize(7);
return 0;
这里我就给一个总结,所谓的resize就是把有效元素的最后一个位置的后一个位置给改变,改成下标是N,凡是和原来的有效位置之间的距离,都给我变成\\0.
void resize (size_t n, char c)
这个就更加简单了,没有什么可以说的,也符合上面的两种规则,也符合不够的扩容规则.就是把默认的\\0改成我们想要的字符,这里就不解释其他的情况了.
int main()
string s;
s.push_back(a);
s.push_back(b);
s.resize(20,1);
cout << s << endl;
return 0;
clear()
清理有效字符,VS下不会进行缩容,看编译器自己的选择.
int main()
string s("hello");
s.reserve(100);
s.clear(); // 只改变 size 缩容不缩容看编译器
return 0;
append()
我们发现,push_back尾插是可以的,但是它支持插入一个字符,我们想要插入一个字符串,可不可以,string也提供了这个接口.我们把最长用的的几个个大家简绍一下.
append() | 函数说明 |
C++ STL学习笔记-C++ STL基础 |