文章目录
前言
c++文章连载:
1.C++基础
1.C++基础
2.C++新增和有变化的关键字
3.C++的内存管理
2.面向对象
1.C++的封装和访问权限
2.C++继承和多态特性
3.C++的运算符重载
4.C++静态类和静态成员
5.C++的友元函数和友元类
3.模板编程和STL
1.C++模板编程入门
2.STL的容器类和迭代器
3.STL的泛型算法
4.模板特化与类型萃取
5.STL的其他容器讲解
6.智能指针与STL查漏补缺
4.杂项
1.c++各种流操作
2.依赖,关联,聚合,组合,继承
3.一些技巧性的代码设计
1.STL的容器类介绍
(1)序列容器。元素在容器中的位置同元素的值无关,即容器不是排序的(类似于C数组)。包括array、vector、deque、list、forward_list等几个。
(2)排序容器。数据插入时即自动按照值从小到大排列好。包括set、multiset、map、mutilmap等。
(3)哈希容器。哈希容器中的元素是未排序的,元素的位置由哈希函数确定,即遵守一定规则的<key,value>对式存储。包括unordered_set、unordered_map、hash_set、hash_multiset、hash_map、hash_multimap等
STL的本质其实就是一套模板技术泛化类型的C++基本数据结构和算法类库
2.容器类array的初步使用
2.1、array的特性
(1)array是定长、同类型多元素、内存中连续排布的一种容器
(2)array其实就是C语言数组的C++ template封装
2.2、array的学习方法
(1)参考文档:https://zh.cppreference.com/w/cpp/container/array
(2)需要C++11或以上标准来支持
大致长这样:
template< class T, std::size_t N > struct array{
public:
T value_type ;
std::size_t size_type ;
std::ptrdiff_t difference_type;
value_type& reference;
const value_type& const_reference;
value_type* pointer;
const value_type* const_pointer;
iterator; //遗留随机访问迭代器 (LegacyRandomAccessIterator) 兼常量表达式迭代器 (ConstexprIterator) (C++20 起)且为字面类型 (LiteralType) (C++17 起)
const_iterator; //常随机访问迭代器兼常量表达式迭代器 (ConstexprIterator) (C++20 起)且为字面类型 (LiteralType) (C++17 起)
std::reverse_iterator<iterator> reverse_iterator;
std::reverse_iterator<const_iterator> const_reverse_iterator;
//元素访问
reference at( size_type pos ); //若 !(pos < size()) 则抛出 std::out_of_range
reference operator[]( size_type pos );
reference front();
reference back();
T* data() noexcept;
const T* data() const noexcept; //返回指向作为元素存储工作的底层数组的指针
//迭代器(指针)
iterator begin() noexcept; //返回指向 array 首元素的迭代器。若 array 为空,则返回的迭代器将等于 end() 。
const_iterator begin() const noexcept;
const_iterator cbegin() const noexcept; //注意 'cbegin' 前面的 'c' 代表 'const'
iterator end() noexcept; //返回指向 array 末元素后一元素的迭代器。此元素表现为占位符;试图访问它导致未定义行为。
const_iterator end() const noexcept;
const_iterator cend() const noexcept;
reverse_iterator rbegin() noexcept; //返回指向逆向 array 首元素的逆向迭代器。它对应非逆向 array 的末元素。若 array 为空,则返回的迭代器等于 rend() 。
const_reverse_iterator rbegin() const noexcept;
const_reverse_iterator crbegin() const noexcept;
reverse_iterator rend() noexcept; //返回指向逆向 array 末元素后一元素的逆向迭代器。它对应非逆向 array 首元素的前一元素。此元素表现为占位符,试图访问它导致未定义行为。
const_reverse_iterator rend() const noexcept;
const_reverse_iterator crend() const noexcept;
constexpr bool empty() const noexcept;
onstexpr size_type size() const noexcept;
constexpr size_type max_size() const noexcept;
void fill( const T& value ); //将定值 value 赋给容器中的所有元素。
void swap( array& other ) noexcept(/* see below */); //将容器内容与 other 的内容交换。不导致迭代器和引用关联到别的容器。
};
//非成员函数,!= < <= > >=类似
template< class T, std::size_t N >
bool operator==( const std::array<T,N>& lhs,
const std::array<T,N>& rhs );
template< class T, std::size_t N >
void swap( std::array<T,N>& lhs,
std::array<T,N>& rhs );
template< std::size_t I, class T, std::size_t N >
T& get( std::array<T,N>& a ) noexcept;
template< std::size_t I, class T, std::size_t N >
T&& get( std::array<T,N>&& a ) noexcept;
template< std::size_t I, class T, std::size_t N >
const T& get( const std::array<T,N>& a ) noexcept;
template< std::size_t I, class T, std::size_t N >
const T&& get( const std::array<T,N>&& a ) noexcept; //从 array 提取第 I 个元素。I 必须是范围 [0, N) 中的整数值。与 at() 或 operator[] 相反,这在编译时强制。
array<int, 3> a1; // 定义了但是未初始化,猜测值应该是随机的
// array<int ,3> a2(1, 3, 5); // 不行,原因:个数不同,C++无法提供无数个构造函数来匹配
array<int, 3> a2{1, 3, 5}; // 可以,属于聚合初始化
array<int, 3> a3 = {1, 3, 5}; // 初始化赋值,和a2效果一样
array<int, 3> a4 = a3; // 拷贝构造
3.容器类array的初步使用
3.1、array的元素访问
(1)at方法
(2)operator[]实现的C数组式访问
(3)front和back方法返回第1个和最后1个元素
(4)data返回真实存储内存中首元素首地址的值
如果array的元素访问越界了,那么编译时没问题,但是运行时会抛出异常
3.2、array的容量设置和获取
(1)容量设置只能在定义时一次设定,且必须设定,设定后再不能改
(2)empty
(3)size
(4)max_size
4.容器类array的初步使用
4.1、操作
(1)fill
(2)swap
4.2、非成员函数
(1)operator重载函数
(2)get
(3)swap
(4)to_array
5_6.容器类array的初步使用
5.1、辅助类tuple_size
c语言array必须初始化时指定其大小,但是有时候想先不指定,也就是说完成功能的函数和main函数不是同一个人写的,在main函数才想指定数组大小,则可以用tuple_size
#include <iostream>
#include <array>
template<class T>
void test(T t)
{
int a[std::tuple_size<T>::value]; // 能用于编译时
std::cout << std::tuple_size<T>::value << '\n';
}
int main()
{
std::array<float, 3> arr;
test(arr);
}
//也可以用:
template<class T> void test(T t)
{
int a[t.size()]; // 能用于编译时
std::cout << t.size() << '\n';
//t.size()可以改成std::tuple_size<T>::value
}
5.2、辅助类tuple_element
#include <array>
#include <iostream>
#include <tuple>
#include <type_traits>
int main()
{
// 定义 array 并获取位于位置 0 的元素类型
std::array<int, 10> data {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
using T = std::tuple_element<0, decltype(data)>::type; // int
//这里的using类似于typedef,重命名类型
std::cout << std::boolalpha;
std::cout << std::is_same<T, int>::value << '\n';
const auto const_data = data;
using CT = std::tuple_element<0, decltype(const_data)>::type; // const int
// tuple_element 的结果取决于仿 tuple 类型的 cv 限定
std::cout << std::is_same<T, CT>::value << '\n';
std::cout << std::is_same<CT, const int>::value << '\n';
}
5.3、被取代的make_array
7.迭代器的引入
7.1、迭代器是干嘛的
(1)迭代器就是能通过移动来遍历处理的一种机制
(2)C语言中遍历数组元素,用指针*p++方式,指针变量就是遍历迭代器
(3)思考:结构体元素能够通过指针运算来遍历?
7.2、关于迭代器的分析
(1)每种容器理论上都可以被遍历,不存在不能被遍历的容器
(2)每种容器的遍历实现都可能不同,要结合容器和元素的特点来具体实现
(3)迭代器内部原理肯定是通过指针操作(地址运算)来实现
(4)迭代器就是C++为我们设计的一个高层次的“指针”,高层指针是面向容器中的元素的
7.3、C++实际是这么设计迭代器的
(1)所有的迭代器有一个共同基类(接口),规定了迭代器的基本行为规范接口
(2)每个容器类中均包含了一个专属化迭代器成员变量,这个专属化迭代器专门针对该容器的特点实现了迭代器应该有的所有接口
(3)需要遍历某STL容器时,只需要直接调出该容器的这个迭代器成员变量直接用即可,固定名字为iterator
7.4、典型的迭代器用法
(1)代码实战,用迭代器来实现遍历array
(2)begin()和end()方法是得到容器遍历的开始和结尾的套路化方法
8.迭代器的几个细节问题
8.1、const与非const
(1)begin和end返回可读可写的迭代器,而cbegin和cend返回const的只读迭代器
(2)代码验证迭代器的读写权限
8.2、begin和end的半开半闭区间
(1)begin返回第0个元素的迭代器(类似于C数组的首元素首地址指针)
(2)end指向的不是末尾元素的迭代器,而是末尾元素的(实际不存在的)下一个元素的迭代器
(3)前闭后开区间,经常记做[begin end),这样设计是为了写循环遍历时方便
8.3、正向和逆向迭代器
(1)rbegin和rend返回逆向迭代器
(2)逆向迭代器的begin是最末尾元素,而end是第0个元素去(实际不存在的)前面1个元素的迭代器
(3)逆向迭代器++是向前移动,而–是向后移动
8.4、迭代器越界会怎么样
(1)和数组越界类似,编译时不审查,运行时会崩溃
(2)不管是正向还是逆向迭代器,++不到end,–不到begin,就不会越界
9.STL的不同类型迭代器
9.1、C++17前共有5种迭代器
(1)InputIterator,输入迭代器。只能从容器内读出而不能向容器内写入,只能单次读出(读出过一次后不保证再次操作仍然可以,想想流输入输出),只能++走不能–走(就是单向的),不能保证第二次遍历容器时,顺序不变。输入迭代器适用于单通只读型算法。
(2)OutputIterator,输出迭代器。用于将信息传输给容器(修改容器中元素的值),但是不能读取。(显示器就是只能写不能读的设备,可用输出容器来表示它)只能++走不能–走(就是单向的),输出迭代器适用于单通只写型算法。
(3)ForwardIterator,前向迭代器。只能++走不能–走(就是单向的)
(4)BidirectionalIterator,双向迭代器。既能++也可以–,双向移动。
(5)RandomAccessIterator,随机访问迭代器。能双向移动,并且可以单次跨越多个元素移动。
9.2、C++17新增1种迭代器
(1)contiguousIterator,连续迭代器。所指向的逻辑相邻元素也在内存中物理上相邻。
9.3、STL的6种迭代器总结
(1)每种迭代器更应该被看作是具有某些预定义特征(或者满足某接口要求)的一个迭代器的实现。
(2)这些迭代器彼此之间有功能重叠,譬如随机访问迭代器可由双向迭代器扩展而来,详见文档
(3)为何定义多种迭代器?是为了适配容器特性和泛型算法,后面会看到
9.4、C++20的新迭代器
(1)C++20中重新实现了基于concept的新的迭代器体系
(2)原有的模板都被加了前缀Legecy,但很长时间仍然可用,甚至还是主流
(3)基于concept的新迭代器主要在类型约束和模板特化方面做了优化
(4)C++20目前还刚开始,可以先不管,先学好原有的,后面再扩展去学C++20新特性
10_11.序列容器之Vector1_2
10.1、Vector的特征
(1)Vector和Array相同点是:都是数组、都是contiguousIterator、容器内元素种类都相同
(2)Vector和Array不同点是:Array是固定数组;Vector是动态数组,可以按需扩展数组大小
(3)vector 的存储是自动管理的,按需扩张收缩。
(4)vector 通常占用多于静态数组的空间,因为要分配更多内存以管理将来的增长
(5)vector 所用的方式不在每次插入元素时,而只在额外内存耗尽时重分配。
10.2、构造拷贝和赋值
(1)多种参数的构造
(2)拷贝构造
(3)assign
10.3、容器元素的遍历
(1)最本质的笨办法:获取容器迭代器,再写for/while循环来遍历
(2)C++11:Range-Based for循环,案例参考assign函数页面
12_13.序列容器之list1_2
14_15.序列容器之deque1_2
见:https://blog.csdn.net/u010710458/article/details/79540505