STL
Standard Template Library
|
类别
| 解释 |
|---|---|
| 容器(containers) | 特殊的数据结构,实现了数组链表队列等等,实质是模板类 |
| 迭代器(iterators) | 一种复杂的指针,可以通过其读写容器中的对象,实质是运算符重载 |
| 空间配置器(allocator) | 容器的空间配置管理的模板类 |
| 配接器(adapters) | 用来修饰容器、仿函数、迭代器接口 |
| 算法(algorithms) | 读写容器对象的逻辑算法:排序,便利,查找,等等实质是模板函数 |
| 仿函数(functors) | 类似函数,通过重载()运算符来模拟函数行为的类 |
STL 容器种类和功能
|
类别
| 解释 |
|---|---|
| 序列容器 | 主要包括 vector 向量容器、list 列表容器以及 deque 双端队列容器。之所以被称为序列容器,是因为元素在容器中的位置同元素的值无关,即容器不是排序的。将元素插入容器时,指定在什么位置,元素就会位于什么位置。 |
| 排序容器 | 包括 set 集合容器、multiset多重集合容器、map映射容器以及 multimap 多重映射容器。排序容器中的元素默认是由小到大排序好的,即便是插入元素,元素也会插入到适当位置。所以关联容器在查找时具有非常好的性能。 |
| 哈希容器 | C++ 11 新加入 4 种关联式容器,分别是 unordered_set 哈希集合、unordered_multiset 哈希多重集合、unordered_map 哈希映射以及 unordered_multimap 哈希多重映射。和排序容器不同,哈希容器中的元素是未排序的,元素的位置由哈希函数确定。 |
迭代器
迭代器和 C++ 的指针非常类似,它可以是需要的任意类型,通过迭代器可以指向容器中的某个元素,如果需要,还可以对该元素进行读/写操作。
常用的迭代器按功能强弱分为输入迭代器、输出迭代器、前向迭代器、双向迭代器、随机访问迭代器 5 种。
输入迭代器和输出迭代器比较特殊,它们不是把数组或容器当做操作对象,而是把输入流/输出流作为操作对象。
前向迭代器(forward iterator)
假设 p 是一个前向迭代器,则 p 支持 ++p,p++,*p 操作,还可以被复制或赋值,可以用 == 和 != 运算符进行比较。此外,两个正向迭代器可以互相赋值。
双向迭代器(bidirectional iterator)
双向迭代器具有正向迭代器的全部功能,除此之外,假设 p 是一个双向迭代器,则还可以进行 --p 或者 p-- 操作(即一次向后移动一个位置)。
随机访问迭代器(random access iterator)
随机访问迭代器具有双向迭代器的全部功能。除此之外,假设 p 是一个随机访问迭代器,i 是一个整型变量或常量,则 p 还支持以下操作:
- p+=i:使得 p 往后移动 i 个元素。
- p-=i:使得 p 往前移动 i 个元素。
- p+i:返回 p 后面第 i 个元素的迭代器。
- p-i:返回 p 前面第 i 个元素的迭代器。
- p[i]:返回 p 后面第 i 个元素的引用。
C++ 11 标准中不同容器指定使用的迭代器类型。
| 容器 | 对应的迭代器类型 | 应用 |
|---|---|---|
| array | 随机访问迭代器 | |
| stack | 不支持迭代器 | 后进先出容器 |
| queue | 不支持迭代器 | 先进先出容器 |
| vector | 随机访问迭代器 | 直接访问任意元素,快速插入、删除尾部元素 |
| deque | 随机访问迭代器 | 直接访问任意元素,快速插入、删除头部和尾部元素 |
| list | 双向迭代器 | 快速插入、删除任意位置元素 |
| set / multiset | 双向迭代器 | 快速查询元素,无重复关键字/允许重复关键字 |
| map / multimap | 双向迭代器 | Key/value pair mapping(键值对映射)。不允许重复关键字/允许重复关键字,使用关键字快速查询元素 |
| forward_list | 前向迭代器 | |
| unordered_map / unordered_multimap | 前向迭代器 | |
| unordered_set / unordered_multiset | 前向迭代器 |
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PQVLHbqO-1638720343549)(50598C2D2C5C47C9800867B2CE18C0CF)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-crkCkaJ6-1638720343551)(0654CF1BD526481582EDF6AADD54A9D4)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kZKZ1ECA-1638720343552)(64D553D54FA34DA493045C2F9E4DC1BB)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uX9LgKIq-1638720343552)(AC928157174A4B4BBB8D5540F9B5FBF3)]
迭代器的定义方式
尽管不同容器对应着不同类别的迭代器,但这些迭代器有着较为统一的定义方式
| 迭代器定义方式 | 具体格式 |
|---|---|
| 正向迭代器 | 容器类名::iterator 迭代器名; |
| 常量正向迭代器 | 容器类名::const_iterator 迭代器名; |
| 反向迭代器 | 容器类名::reverse_iterator 迭代器名; |
| 常量反向迭代器 | 容器类名::const_reverse_iterator 迭代器名; |
排序后迭代器访问list
List->sort();
for (list<int>::iterator iter = List->begin(); iter != List->end(); iter++)
{
cout << *iter << " ";
}
cout << endl;
一级容器的通用函数
|
Functions
| Description |
|---|---|
| c1.swap(c2) | 交换两个容器c1和c2的内容 |
| c1.max_size() | 返回一个容器可以容纳的最大元素数量 |
| c.clear() | 删除容器中的所有元素 |
| c.begin() | 返回容器首元素的迭代器 |
| c.end() | 返回容器尾元素之后位置的迭代器 |
| c.rbegin() | 返回容器为元素的迭代器,用于逆序遍历 |
| c.rend() | 返回容器首元素之前位置的迭代器,用于逆序遍历 |
| c.erase(beg, end) | 删除容器中从beg到end-1之间的元素。beg和end都是迭代器 |
顺序容器通用函数
|
Functions
| Description |
|---|---|
| assign(n, elem) | 将指定元素的n份拷贝加入(赋值)到容器中 |
| assign(beg,end) | 将迭代器[beg,end)间的元素赋值给当前容器 |
| push_back(elem) | 将元素附加到容器 |
| pop_back() | 删除容器尾元素 |
| front() | 返回容器首元素 |
| back() | 返回容器尾元素 |
| insert(position,elem) | 将元素插入到容器指定位置 |
关联容器通用函数
|
Functions
| Description |
|---|---|
| find(key) | 搜索容器中具有key的元素,返回指向该元素的迭代器 |
| lower_bound(key) | 搜索容器中具有key的第一个元素,返回指向该元素的迭代器 |
| upper_bound(key) | 搜索容器中具有key的最后一个元素,返回指向此元素之后位置的迭代器 |
| count(key) | 返回容器中具有key的元素的数目 |
迭代器支持的运算符
| 类别 | 运算符 |
|---|---|
| All iterators | ++p |
| All iterators | p++ |
| Input iterators | *p |
| Input iterators | p1==p2 |
| Input iterators | p1!=p2 |
| Output iterators | *p |
| Bidirectionl iterators | –p |
| Bidirectionl iterators | p– |
| Random access iterators | p+=i |
| Random access iterators | p-=i |
| Random access iterators | p+i |
| Random access iterators | p-i |
| Random access iterators | p1<p2 |
| Random access iterators | p1<=p2 |
| Random access iterators | p1>p2 |
| Random access iterators | p1>=p2 |
| Random access iterators | p[i] |
graph TB
A(Input iterators) -->C(Forward iterators)
B(Output iterators) -->C(Forward iterators)
C(Forward iterators)-->D(Bidirectional iterators)
D(Bidirectional iterators)-->E(Random access iterators)
自定义迭代器
class Iterator { //自定义迭代器
private:
T* it; //一个T类型指针
public:
Iterator()
{
it = nullptr;
}
Iterator(T* m_it)
{
it = m_it;
}
~Iterator()
{
it = nullptr;
}
Iterator operator = (const Iterator m_it)
{
it = m_it.it;
return *this;
}
void operator ++ ()
{
it = it + 1;
}
bool operator == (const Iterator m_it)
{
return it == m_it.it;
}
bool operator != (const Iterator m_it)
{
return it != m_it.it;
}
T operator * ()
{
return *it;
}
};
Iterator begin() {
return Iterator(KMdata[0][0]);
}
Iterator end() {
return Iterator(KMdata[this->getRows()][this->getCols()]);
}
priority_queue
- 优先队列元素按其优先级(priority) 读取,默认使用 < 运算符来比较元素
- 默认基于vector实现。也可基于
deque,但不能用list
priority_queue<float> q;
q.push(22.22);
q.push(66.66);
q.push(44.44);
cout << q.top();
output:66.66
//遍历 vector 容器。
#include <iostream>
#include <vector>
using namespace std;
int main()
{
vector<int> v{1,2,3,4,5,6,7,8,9,10}; //v被初始化成有10个元素
cout << endl << i = 0; i < v.size(); ++i)
cout << v[i] <<" ";
//创建一个正向迭代器,当然,vector也支持其他 3 种定义迭代器的方式
cout << endl << "第二种遍历方法:" << endl;
vector<int>::iterator i;
//用 != 比较两个迭代器
for (i = v.begin(); i != v.end(); ++i)
cout << *i << " ";
cout << endl << "第三种遍历方法:" << endl;
for (i = v.begin(); i < v.end(); ++i) //用 < 比较两个迭代器
cout << *i << " ";
cout << endl << "第四种遍历方法:" << endl;
i = v.begin();
while (i < v.end())
{
cout << *i << " ";
i += 2; // 随机访问迭代器支持 "+= 整数" 的操作
}
}
STL的精华——萃取(trait)
当函数,类或者一些封装的通用算法中的某些部分会因为数据类型不同而导致处理或逻辑不同(而我们又不希望因为数据类型的差异而修改算法本身的封装时),traits会是一种很好的解决方案。
函数参数列表接受两个迭代器,累加两个迭代器之间的数据,假定迭代器指向的数据类型是T,迭代器类型为Iter。
声明如下:
template <typename Iter, typename T>
T sum(Iter begin, Iter end)
{
T result {};
while (begin != end) {
result += *begin++;
}
return result;
}
int main( )
{
vector<int> vi { 1, 2, 3, 4 };
vector<string> vs { "ha", "sa", "ki" };
int s = sum<vector<int>::iterator, int>(vi.begin(), vi.end());
string s2 = sum<vector<string>::iterator, string>(vs.begin(), vs.end());
return 0;
}
什么是萃取,为什么要萃取
有没有办法只用一个类型参数,就实现上面的函数?
template <typename Iter>
typename Iter::value_type sum(Iter begin, Iter end)
{
typename Iter::value_type result {};
while (begin != end) {
result += *begin++;
}
return result;
}
value_type就是属于迭代器的一个特性,迭代器可以随时随地获取到自己的特性,这个就是萃取。
template<class Category, class T, class Distance = ptrdiff_t,
class Pointer = T*, class Reference = T&>
struct iterator
{
typedef Category iterator_category;
typedef T value_type;
typedef Distance difference_type;
typedef Pointer pointer;
typedef Reference reference;
};
template<class Iterator>
struct iterator_traits
{
typedef typename Iterator::iterator_category iterator_category;
typedef typename Iterator::value_type value_type;
typedef typename Iterator::difference_type difference_type;
typedef typename Iterator::pointer pointer;
typedef typename Iterator::reference reference;
};
迭代器类别、指向的数据类型都是萃取出来的特性。
无处不在的特化——为了提高性能无所不用其极
以下就对以数组指针作为迭代器实现的类型做了偏特化,指定迭代器类别为随机迭代器,这样就可以对这些数组指针使用专属于随机迭代器的方法。
template<class T>
struct iterator_traits<T*>
{
typedef random_access_iterator_tag iterator_category;
typedef T value_type;
typedef ptrdiff_t difference_type;
typedef T* pointer;
typedef T& reference;
};
template<class T>
struct iterator_traits<const T*>
{
typedef random_access_iterator_tag iterator_category;
typedef T value_type;
typedef ptrdiff_t difference_type;
typedef const T* pointer;
typedef const T& reference;
};
对于不同的数据类型,copy的实现也是大不相同的,对于POD(Plain Old Data,普通旧数据,如int,char,double),memcpy就是最高效的copy方式,对于类对象可能要通过拷贝构造函数才能完整复制一个对象。那怎么针对不同的数据类型采用不同的方式拷贝方式呢?答案依然是特化。
template<class InputIterator, class OutputIterator>
OutputIterator __copy(InputIterator first, InputIterator last, OutputIterator result, _true_type){
auto dist = distance(first, last);
memcpy(result, first, sizeof(*first) * dist);
advance(result, dist);
return result;
}
template<class InputIterator, class OutputIterator>
OutputIterator __copy(InputIterator first, InputIterator last, OutputIterator result, _false_type){
while (first != last){
*result = *first;
++result;
++first;
}
return result;
}
template<class InputIterator, class OutputIterator, class T>
OutputIterator _copy(InputIterator first, InputIterator last, OutputIterator result, T*){
typedef typename TinySTL::_type_traits<T>::is_POD_type is_pod;
return __copy(first, last, result, is_pod());
}
template <class InputIterator, class OutputIterator>
OutputIterator copy(InputIterator first, InputIterator last, OutputIterator result){
return _copy(first, last, result, value_type(first));
}
template<>
inline char *copy(char *first, char *last, char *result){
auto dist = last - first;
memcpy(result, first, sizeof(*first) * dist);
return result + dist;
}
template<>
inline wchar_t *copy(wchar_t *first, wchar_t *last, wchar_t *result){
auto dist = last - first;
memcpy(result, first, sizeof(*first) * dist);
return result + dist;
}
内存拷贝函数memcpy
原型:void*memcpy(void*dest, const void*src,unsigned int count);
功能:由src所指内存区域复制count个字节到dest所指内存区域。
说明:src和dest所指内存区域不能重叠,函数返回指向dest的指针。
参考STL源码
这是一个简易版的STL实现,适用于初学者研究STL的设计思想。
智能指针
指针的痛点
- 忘记delete,内存泄漏
- 野指针访问,行为未定义(内存访问异常)
- 多次delete,行为未定义
unique_ptr
- 同一时刻只能有一个 unique_ptr 指向给对象
- unique_ptr 指针的生命周期从创建时开始,直到离开作用域。离开作用域时,若其指向对象,则其所指对象销毁
- unique_ptr 的使用能够包括:
- 为动态申请的内存提供异常安全
- 将动态申请内存的所有权传递给某个函数
- 从某个函数返回动态申请内存的所有权
- 在容器中保存指针
- 所有auto_ptr具有的功能
#include <iostream>
#include <memory>
int main() {
{
std::unique_ptr<int> uptr(new int(10)); //绑定动态对象
//std::unique_ptr<int> uptr2 = uptr; //不能賦值
//std::unique_ptr<int> uptr2(uptr); //不能拷貝
std::unique_ptr<int> uptr2 = std::move(uptr); //轉換所有權
uptr2.release(); //释放所有权
}
//超過uptr的作用域,內存釋放
}
shared_ptr
- shared_ptr (共享资源的只能指针)被用来表示共享的拥有权。也就是说两段代码都需要访问一些数据,而他们又都没有独占该数据的所有权(从某种意义上来说就是该段代码负责销毁该对象)
- shared_ptr 是一种计数指针。当引用计数变为0时,shared_ptr所指向的对象就会被删除。
- 在给shared_ptr分配内存时建议使用 make_shared 函数,这样最安全
#include <iostream>
#include <memory>
int main() {
{
int a = 10;
std::shared_ptr<int> ptra = std::make_shared<int>(a);
std::shared_ptr<int> ptra2(ptra); //copy
std::cout << ptra.use_count() << std::endl;
int b = 20;
int *pb = &a;
//std::shared_ptr<int> ptrb = pb; //error
std::shared_ptr<int> ptrb = std::make_shared<int>(b);
ptra2 = ptrb; //assign
pb = ptrb.get(); //获取原始指针
std::cout << ptra.use_count() << std::endl;
std::cout << ptrb.use_count() << std::endl;
}
}
weak-ptr
- 弱指针(weak pointer) ,指向一个已经用shared_ptr进行管理的对象
- 只有当对象存在的时候,才需要对其进行访问
- 可能被其他人删除释放,且在最后一次使用之后调用其析构函数(通常用于释放那些不具名的内存(anon-memory)资源
- weak_ptr可以保存一个“弱引用”,引用一个已经用shared_ptr管理的对象。为了访问这个对象一个weak_ptr可以通过shared_ptr的构造函数或者是weak_ptr的成员函数lock()转化为一个shared_ptr。当最后一个指向这个对象的shared_ptr退出其生命周期并且这个对象被释放之后,将无法从指向这个对象的weak_ptr获得一个shared_ptr指针,shared_ptr的构造函数会抛出异常,而weak_ptr::lock也会返回一个空指针。
#include <iostream>
#include <memory>
int main() {
{
std::shared_ptr<int> sh_ptr = std::make_shared<int>(10);
std::cout << sh_ptr.use_count() << std::endl;
std::weak_ptr<int> wp(sh_ptr);
std::cout << wp.use_count() << std::endl;
if(!wp.expired()){
std::shared_ptr<int> sh_ptr2 = wp.lock(); //get another shared_ptr
*sh_ptr = 100;
std::cout << wp.use_count() << std::endl;
}
}
//delete memory
}
智能指针的痛点
- 使用场景复杂
- 原生指针、智能指针混用的坑
- 无法杜绝原生指针的使用
- 历史代码
- 跨模块使用方式
- 其他原因
- 总结:没有在语言级别解决问题
指针使用
//unique_ptr ---管理独占对象
unique_ptr<int> foo(new int(5));
cout << foo << endl;
foo.reset(new int(6));
cout << foo << endl;
auto foo1 = make_unique<int>(*foo);
cout << foo1 << endl;
foo.release();
cout << foo << endl;
cout << endl;
//shared_ptr ---管理的对象可以共享
shared_ptr<int> foo2(new int(6));
cout << "foo2.use_count(): \t"<<foo2.use_count() << endl;
shared_ptr<int> foo3(foo2);
cout << "foo2.use_count(): \t" << foo2.use_count() << endl;
auto foo4 = make_shared<int>(5);
cout << "foo4.unique(): \t\t" << foo4.unique() << endl;
foo4 = move(foo2);
cout << "foo2.use_count(): \t" << foo2.use_count() << endl;
cout << "foo4.use_count(): \t" << foo4.use_count() << endl;
cout << "foo4.unique(): \t\t" << foo4.unique() << endl;
foo4.~shared_ptr();
cout << "foo4.use_count(): \t" << foo4.use_count() << endl;
cout << "foo3.use_count(): \t" << foo3.use_count() << endl;
foo3.~shared_ptr();
cout << "foo4.use_count(): \t" << foo4.use_count() << endl;
cout << "foo3.use_count(): \t" << foo3.use_count() << endl;
cout << endl;
//weak_ptr ---指向一个已经用shared_ptr进行管理的对象
shared_ptr<int> foo5(new int(6));
cout << "foo5.use_count(): \t" << foo5.use_count() << endl;
weak_ptr<int> foo6(foo5);
cout << "foo5.use_count(): \t" << foo5.use_count() << endl;
cout << "foo6.use_count(): \t" << foo6.use_count() << endl;
auto foo7(foo5);
cout << "foo5\t\t\t" << foo5 << endl;
cout << "foo6.lock()\t\t" << foo6.lock() << endl;
cout << "foo6.use_count(): \t" << foo6.use_count() << endl;
foo5.reset();
cout << "foo6.expired()\t\t" << foo6.expired() << endl;
cout << "foo6.lock()\t\t" << foo6.lock() << endl;
cout << "foo6.use_count(): \t" << foo6.use_count() << endl;
foo7.reset();
cout << "foo6.expired()\t\t" << foo6.expired() << endl;
cout << "foo6.lock()\t\t" << foo6.lock() << endl;
cout << "foo6.use_count(): \t" << foo6.use_count() << endl;
运行结果:
00E44CB8
00E44CE8
00E44CB8
00000000
foo2.use_count(): 1
foo2.use_count(): 2
foo4.unique(): 1
foo2.use_count(): 0
foo4.use_count(): 2
foo4.unique(): 0
foo4.use_count(): 1
foo3.use_count(): 1
foo4.use_count(): -572662307
foo3.use_count(): -572662307
foo5.use_count(): 1
foo5.use_count(): 1
foo6.use_count(): 1
foo5 00E44D18
foo6.lock() 00E44D18
foo6.use_count(): 2
foo6.expired() 0
foo6.lock() 00E44D18
foo6.use_count(): 1
foo6.expired() 1
foo6.lock() 00000000
foo6.use_count(): 0
手写shared_ptr
可能有些不完善
#include <iostream>
using namespace std;
template <class T>
void Swap(T& t1, T& t2)
{
T temp;
temp = t1;
t1 = t2;
t2 = temp;
}
class Shared_count {
public:
Shared_count()
{
count = 0;
}
void add_count() // 增加计数
{
count++;
}
void reduce_count() // 减少计数
{
count--;
}
int get_count() const // 获取当前计数
{
return count;
}
operator int()
{
return count;
}
private:
int count;
};
template<typename T>
class Shared_ptr {
public:
Shared_ptr(T* pointer = nullptr) noexcept //用普通指针构造智能指针
{
this->ptr = pointer;
count = new Shared_count();
if (pointer)
{
count->add_count();
}
}
Shared_ptr(const Shared_ptr<T>& pointer)//用智能指针构造智能指针
{
this->ptr = pointer.ptr;
this->count = pointer.count;
count->add_count();
}
~Shared_ptr() noexcept // ptr不为空且此时共享计数减为0的时候,再去删除
{
count->reduce_count();
if (ptr && !count->get_count())
{
delete ptr;
delete count;
}
}
// 重载->操作符
T* operator->()const
{
return ptr;
}
// 重载*操作符
T& operator*() const
{
return ptr;
}
T* get()
{
return ptr;
}
operator bool()
{
if (ptr != nullptr)
return true;
else
return false;
}
void swap(Shared_ptr& sp)
{
Swap(this->ptr,sp.ptr);
Swap(this->count, sp.count);
}
bool unique()const
{
if (count->get_count())
return false;
else
return true;
}
void reset(T* pointer = nullptr)
{
this->ptr = pointer;
count = new Shared_count();
if (pointer)
{
count->add_count();
}
}
int use_count()
{
return count->get_count();
}
Shared_ptr& operator=(const Shared_ptr<T>& pointer)
{
if (this != &pointer)// 判断是否自赋值
{
this->count = pointer.count;
this->ptr = pointer.ptr;
count->add_count();
}
return *this;
}
friend ostream& operator<<(ostream& output, const Shared_ptr& S)
{
output << ptr;
return ptr;
}
private:
T* ptr;
Shared_count* count;
};
异常
Error Code
- 基于Error Code的错误处理
- 被调用函数发生错误时返回错误码
- 函数调用方检查并处理错误码
- 痛点
- 代码繁琐,错误处理和业务逻辑耦合
- 错误码表现力不足
- 容易被忽略
- 有的特殊函数没有返回值(构造函数,赋值操作符等)
关键词
exception, throw, try/catch
抛出异常
您可以使用 throw 语句在代码块中的任何地方抛出异常。throw语句的操作数可以是任意的表达式,表达式的结果的类型决定了抛出的异常的类型。
double division(int a, int b)
{
if( b == 0 )
{
throw "Division by zero condition!";
}
return (a/b);
}
捕获异常
catch 块跟在 try 块后面,用于捕获异常。您可以指定想要捕捉的异常类型,这是由catch关键字后的括号内的异常声明决定的。
try
{
// 保护代码
}catch( ExceptionName e )
{
// 处理 ExceptionName 异常的代码
}
实例
#include <iostream>
using namespace std;
double division(int a, int b)
{
if( b == 0 )
{
throw "Division by zero condition!";
}
return (a/b);
}
int main ()
{
int x = 50;
int y = 0;
double z = 0;
try {
z = division(x, y);
cout << z << endl;
}catch (const char* msg) {
cerr << msg << endl;
}
return 0;
}
运行结果
Division by zero condition!
捕获多种类型异常
- 多个catch块
- 不发生隐式转换
- 支持子类转基类对象
捕获所有异常
catch(...)
Catch块中的那些事儿
- 错误恢复
- 如果可以,如果有必要
- 记录错误
- 如果有必要
- 向上返回
- 返回错误码
- 重新抛出异常
异常捕获链
- 异常抛出后程序执行将立即跳转至最近的try块的最后,按顺序匹配catch块并执行;
- 相应的catch块执行后异常被处理,程序继续执行;
- 若无匹配的catch块则继续跳转至下一个最近的try块进行处理;
- 若当前函数中异常未被处理,则退出该函数(stack unwinding),在其调用函数中进行处理;
- 递归以上步骤支持异常被处理;
- 若跳转至最顶层依然无匹配的catch块,则程序退出;
捕获Windows SEH异常
__try/__except
异常总结
- 优点
- 隔离异常处理代码
- 异常无法被忽略
- 构造函数可以报错
- 缺点
- 写异常安全的代码并不简单
- 错误现场易丢失
- 性能损失(现代编译器可能问题不大)
- 编译出的二进制文件体积较大
睡眠排序法
// Created on Apple Pencil.
#include <iostream>
#include <Windows.h>
#include <thread>
using namespace std;
#define max(a,b) a>b? a:b;
void sleepsort(int i)
{
Sleep(i*100);
cout << i << ",";
}
int main() {
int item[] = { 10,1,3,4,9,4,2,5,3,20};
int Max = item[0];
for (int i = 0; i < sizeof(item) >> 2; i++)
{
Max = max(Max, item[i]);
}
for (int i = 0; i < 10; i++)
{
thread test(sleepsort,item[i]);
test.detach();
}
Sleep(Max*110);
return 0;
}
C++多线程
- 创建一个线程,不做处理会调用abort函数终止程序
join()函数加入,汇合线程,阻塞主线程,等待子线程执行结束,才会回到主线程中- 一个线程只能join一次
detach()函数 分离,打破依赖关系,把子线程驻留后台- 当线程detach之后,就不能再join了
joinable()判断当前进程是否可以做join或者detach过程,可以则返回true,不可以则返回false
多线程的创建
#include <iostream>
#include <thread>
#include<Windows.h>
using namespace std;
//线程处理函数
void print_1()
{
Sleep(1000);
cout << "--子线程1运行--" << endl;
}
void print_2()
{
Sleep(1000);
cout << "--子线程2运行--" << endl;
}
void print_3()
{
Sleep(5000);
cout << "--子线程3运行--" << endl;
}
class thr
{
public:
void operator ()()
{
Sleep(1000);
cout << "--子线程thr运行--" << endl;
}
};
int main()
{
//创建线程
thread test(print_1);
test.join();
thread test_2(print_2);
test_2.join();
thread test_3(print_3);
test_3.detach();
thr th;
thread test_4(th);
test_4.join();
auto lambdathread = []
{
Sleep(1000);
cout << "--lambda线程运行--" << endl;
};
thread test_5(lambdathread);
test_5.join();
Sleep(1000);
if (test_3.joinable())
{
test_3.detach();
}
else {
cout << "子线程3已被处理" << endl;
}
Sleep(1000);
cout << "--主线程运行--" << endl;
return 0;
}
}
vector容器装载线程
#include <iostream>
#include <Windows.h>
#include <vector>
#include <thread>
#include <mutex>
using namespace std;
void myprint(int i)
{
cout <<"--thread_num:"<< i <<endl;
}
int main()
{
cout << "--主线程执行--" << endl;
vector <thread> mythreads;
for (int i = 0; i < 10; i++)
{
mythreads.push_back(thread(myprint, i));
//mythreads[i].join();
}
for (auto tp = mythreads.begin(); tp != mythreads.end(); ++tp)
{
//cout << tp->get_id() << endl;
tp->join();
}
cout << "--主线程结束--" << endl;
return 0;
}
互斥量mutex
- 互斥量是个类对象,理解成一把锁,多个线程尝试用lock()成员函数来加锁这个锁,只有一个线程能够锁定成功,成功的标志是lock()函数返回
- 如果没锁成功,那么流程会卡在lock()这里不断地尝试去锁
- 互斥量使用时要注意,保护数据要合适,少了没达到保护效果,多了影响效率
互斥量的用法
- 步骤:先lock(),操作共享数据,再unlock();
- lock()和unlock()要一对一成对使用,每调用一次lock()必然调用一个unlock()
- std::lock_guard类模板:忘记unlock不要紧,guard帮你unlock()
- 智能指针(unique_ptr<>):你忘记释放内存不要紧,ta也帮你释放
- std::lock_guard 可以取代lock()和unlock(),就是使用了lock_guard之后,不能使用lock()和unlock()之中任何一个
原理:
- lock_guard构造函数里执行了mutex::lock()
- lock_guard析构函数里执行了mutex::unlock()
std::lock_guard<std::mutex> my_guard(my_mutex);
// Created on Apple Pencil.
#include <iostream>
#include <Windows.h>
#include <vector>
#include <thread>
#include <list>
#include <mutex>
using namespace std;
list <int> DataQueue;
class mythread {
public:
void indata()
{
for (int i = 0; i < 10000; i++)
{
cout << __FUNCTION__"执行,插入数据" <<i<< endl;
lock_guard<mutex> myguard(mymutex);
//mymutex.lock();
DataQueue.push_back(i);
//mymutex.unlock();
}
}
void outdata()
{
for (int i = 0; i < 10000; i++)
{
if (!DataQueue.empty())
{
cout << __FUNCTION__"执行,读出数据" << i << endl;
mymutex.lock();
int command = DataQueue.front();
DataQueue.pop_front();
mymutex.unlock();
}
else
{
cout << __FUNCTION__"继续执行,但消息队列为空" << i << endl;
}
}
}
private:
mutex mymutex;//创建了一个互斥量
};
int main()
{
mythread mt;
thread myindata(&mythread::indata,&mt);
thread myoutdata(&mythread::outdata, &mt);
myindata.join();
myoutdata.join();
cout << "--主线程结束--" << endl;
return 0;
}
死锁
- 死锁前提条件是要有两把锁(两个互斥量)
- 现有两个线程A、B,两把锁1、2
- 线程A执行的时候,线程先锁1.lock()成功,然后去执行2.lock()[未锁]
- 出现了上下文切换
- 线程B执行了,但是这个线程先锁2.lock()了,因为此时2.lock还没有被锁,所以2.lock()成功,线程B要去执行1.lock()
- 此时此刻,死锁产生了
- 线程A因为拿不到2.lock(),流程走不下去,所以1.不能unlock()
- 线程B因为拿不到1.lock(),流程走不下去,所以2.不能unlock()
死锁的一般解决方案
只要保证这两个互斥量上锁的顺序一致就不会死锁
std::lock()函数模板
std::lock(my_mutex1,my_mutex2);
- 一次锁住两个或以上的互斥量
- 不存在因为多个线程中因为锁的顺序问题产生的死锁
- 如果互斥量中有一个没锁住,它就在那等着,等到所有的互斥量都锁柱,它才会继续往下走(返回)
- 要么两个互斥量都锁柱,要么都没锁柱。如果只锁了一个,另外一个没锁成功,则它立即把已经锁住的解锁
std::adopt_lock
- std::adopt_lock是个结构体对象,起标记作用,表示这个互斥量已经lock(),不需要在std::lock_guardstd::mutex里面对对象进行再次lock()了;
std::lock_guard<mutex> my_guard1(my_mutex1,std::adopt_lock);
std::lock_guard<mutex> my_guard2(my_mutex2,std::adopt_lock);
Read_Write
读者写者是一个非常著名的同步问题。读者写者问题描述非常简单,有一个写者很多读者,多个读者可以同时读文件,但写者在写文件时不允许有读者在读文件,同样有读者在读文件时写者也不去能写文件。
#include<iostream>
#include<Windows.h>
#include<queue>
#include<list>
#include<mutex>
#include<thread>
using namespace std;
mutex Write_mutex;
list<char> buffer_area;
queue<int> Read_queue;//读者队列
void Write()
{
while (1)
{
if (Read_queue.empty())//如果读者队列为空,写者才能开始写
* {
* cout << "写者写文件" << endl;
Write_mutex.lock();//写者写的时候上锁,读者不能读
buffer_area.push_back('#');
Write_mutex.unlock();//写完解锁
}
}
}
void Read(int k)
{
while (1)
{
cout << "读者" << k << "读文件" << buffer_area.size() << endl;
int *q = new int(1);
Read_queue.push(*q);//加入读者队列
Write_mutex.lock();//读者读时上锁
Write_mutex.unlock();//读完解锁
delete q;
Read_queue.pop();//读完退出读者队列
}
}
int main()
{
//可以添加读者
thread myReader1(Read,1);//读者1
thread myReader2(Read,2);//读者2
thread myReader3(Read,3);//读者3
thread myWriter(Write);
myWriter.detach();
myReader1.detach();
myReader2.detach();
myReader3.detach();
Sleep(1000);
return 0;
}
lambda表达式
lambda表达式有如下优点:
- 声明式编程风格:就地匿名定义目标函数或函数对象,不需要额外写一个命名函数或者函数对象。以更直接的方式去写程序,好的可读性和可维护性。
- 简洁:不需要额外再写一个函数或者函数对象,避免了代码膨胀和功能分散,让开发者更加集中精力在手边的问题,同时也获取了更高的生产率。
- 在需要的时间和地点实现功能闭包,使程序更灵活。
lambda 表达式的概念和基本用法
lambda 表达式定义了一个匿名函数,并且可以捕获一定范围内的变量。
lambda 表达式的语法形式可简单归纳如下:
[ 捕获列表 ] ( 参数表 ) 函数选项 -> 返回值类型 { 函数体 };
//简单例子
auto f = [](int a) -> int { return a + 1; };
std::cout << f(1) << std::endl; // 输出: 2
省略 lambda 表达式的返回值定义:
auto f = [](int a){ return a + 1; };
👆 这样编译器就会根据 return 语句自动推导出返回值类型。
需要注意
auto x1 = [](int i){ return i; }; // OK: return type is int
auto x2 = [](){ return { 1, 2 }; }; // error: 无法推导出返回值类型
另外,lambda 表达式在没有参数列表时,参数列表是可以省略的。因此像下面的写法都是正确的:
auto f1 = [](){ return 1; };
auto f2 = []{ return 1; }; // 省略空参数表
使用 lambda 表达式捕获列表
lambda 表达式还可以通过捕获列表捕获一定范围内的变量:
- [] 不捕获任何变量。
- [&] 捕获外部作用域中所有变量,并作为引用在函数体中使用(按引用捕获)。
- [=] 捕获外部作用域中所有变量,并作为副本在函数体中使用(按值捕获)。
- [=,&foo] 按值捕获外部作用域中所有变量,并按引用捕获 foo 变量。
- [bar] 按值捕获 bar 变量,同时不捕获其他变量。
- [this] 捕获当前类中的this指针,让lambda表达式拥有和当前类成员函数同样的访问权限。如果已经使用了 & 或者 =,就默认添加此选项。捕获 this 的目的是可以在 lamda 中使用当前类的成员函数和成员变量。
lambda表达式的基本用法
class XX
{
public:
int i = 0;
void func(int x, int y)
{
auto x1 = [] { return i; }; // error,没有捕获外部变量
auto x2 = [=] { return i + x + y; }; // OK,捕获所有外部变量
auto x3 = [&] { return i + x + y; }; // OK,捕获所有外部变量
auto x4 = [this] { return i; }; // OK,捕获this指针
auto x5 = [this] { return i + x + y; }; // error,没有捕获x、y
auto x6 = [this, x, y] { return i + x + y; }; // OK,捕获this指针、x、y
auto x7 = [this] { return i++; }; // OK,捕获this指针,并修改成员的值
}
};
int a = 0, b = 1;
auto f1 = [] { return a; }; // error,没有捕获外部变量
auto f2 = [&] { return a++; }; // OK,捕获所有外部变量,并对a执行自加运算
auto f3 = [=] { return a; }; // OK,捕获所有外部变量,并返回a
auto f4 = [=] { return a++; }; // error,a是以复制方式捕获的,无法修改
auto f5 = [a] { return a + b; }; // error,没有捕获变量b
auto f6 = [a, &b] { return a + (b++); }; // OK,捕获a和b的引用,并对b做自加运算
auto f7 = [=, &b] { return a + (b++); }; // OK,捕获所有外部变量和b的引用,并对b做自加运算
lambda 表达式的类型
lambda 表达式的类型在 C++11 中被称为“闭包类型(Closure Type)”。它是一个特殊的,匿名的非 nunion 的类类型。
因此,我们可以认为它是一个带有 operator() 的类,即仿函数。
因此,我们可以使用 std::function 和 std::bind 来存储和操作 lambda 表达式:
std::function<int(int)> f1 = [](int a){ return a; };
std::function<int(void)> f2 = std::bind([](int a){ return a; }, 123);
另外,对于没有捕获任何变量的lambda表达式,还可以被转换成一个普通的函数指针:
using func_t = int(*)(int);
func_t f = [](int a){ return a; };
f(123);
lambda 表达式可以说是就地定义仿函数闭包的“语法糖”。它的捕获列表捕获住的任何外部变量,最终均会变为闭包类型的成员变量。
而一个使用了成员变量的类的operator(),如果能直接被转换为普通的函数指针,那么 lambda 表达式本身的 this 指针就丢失掉了。而没有捕获任何外部变量的 lambda 表达式则不存在这个问题。
这里也可以很自然地解释为何按值捕获无法修改捕获的外部变量。因为按照 C++ 标准,lambda 表达式的operator()默认是const的
一个const成员函数是无法修改成员变量的值的。而 mutable 的作用,就在于取消 operator() 的 const。
需要注意的是,没有捕获变量的 lambda 表达式可以直接转换为函数指针,而捕获变量的 lambda 表达式则不能转换为函数指针。看看下面的代码:
typedef void(*Ptr)(int*);
Ptr p = [](int* p){delete p;}; // 正确,没有状态的lambda(没有捕获)的lambda表达式可以直接转换为函数指针
Ptr p1 = [&](int* p){delete p;}; // 错误,有状态的lambda不能直接转换为函数指针
上面第二行代码能编译通过,而第三行代码不能编译通过,因为第三行的代码捕获了变量,不能直接转换为函数指针。
模板
模板是一种类型进行参数化的工具,通常有两种形式
- 函数模板
- 函数模板针对仅参数类型不同的函数
template <typename type> return-type
func-name(parameter list)
{
// 函数体
}
//例:
template <typename T1,typename T2>
auto add(T1 a, T2 b)
{
return a+b;
}
- 类模板
- 类模板针对仅数据成员和函数成员类型不同的类
template <typename type> class 类名
{
// 类定义
};
-
模板的声明或定义只能在全局,命名空间或类范围内进行
-
实例化
- 函数模板只是蓝图,本身不是不是类型、函数
- 编译器扫描代码,遇到模版定义时,并不立即产生代码
- 确定模板实参后,编译器生成实际函数代码
-
确定模板实参的方法
- 显式实例化
强制某些函数实例化,可出现于程序中模板定义后的任何位置。
- 隐式实例化
编译器查看函数调用,推断模版实参,实现隐式实例化。
实例函数/实例类
由函数模板实例化得到的函数叫做“实例函数”,由类模板实例化得到的类叫做“实例类”
默认类型模板参数
类模板的类型形参可以有默认值,函数模板的类型形参则不能
函数模板和类模板都可以为模板的非类型形参提供默认值
类模板的类型形参的默认值形式为
template<typename T1,typename T2 = int> class Demo
{
...
};
友元模板函数
?
多态的模板实现
- 虚函数多态,是动态绑定,运行时多态,使用继承、虚函数,基类指针实现透明的处理不同类型集合的方法
- 能够优雅的处理一个包含有不同类型的集合
- 可执行代码量通常比较小
- 可以对代码进行完全编译;因此不需要发布源码
- 模板多态,是静多态
模板特化和偏特化
- 模板特化:是指对于模板参数是特定的类型,可以为编译器指定特定的实现
- 模板偏特化:当有多个模板参数时,可以为部分模板参数指定特定的类型来进行特化
- 函数模板只支持全特化
template<typename T1, typename T2>
class Test
{
public:
Test(T1 i, T2 j) :a(i), b(j) { std::cout << "模板类" << std::endl; }
private:
T1 a;
T2 b;
};
template<>
class Test<int, char>
{
public:
Test(int i, char j) :a(i), b(j) { std::cout << "全特化" << std::endl; }
private:
int a;
char b;
};
template <typename T2>
class Test<char, T2>
{
public:
Test(char i, T2 j) :a(i), b(j) { std::cout << "偏特化" << std::endl; }
private:
char a;
T2 b;
};
//////////////////////////////////////////////////////////////////////////
//模板函数
template<typename T1, typename T2>
void fun(T1 a, T2 b)
{
std::cout << "模板函数" << std::endl;
}
//全特化
template<>
void fun<int, char >(int a, char b)
{
std::cout << "全特化" << std::endl;
}
//函数不存在偏特化:下面的代码是错误的
/*
template<typename T2>
void fun<char,T2>(char a, T2 b)
{
cout<<"偏特化"<<endl;
}
*/
int main()
{
Test<double, double> t1(0.1, 0.2);
Test<int, char> t2(1, 'A');
Test<char, bool> t3('A', true);
return 0;
}
模板元编程
- c++模板是一种新的编程范式
- 借助模板参数推理和模板特化
- 将运行期行为用编译期来模拟,最主要的两个点:
- 用模板特化来模拟运行期的分支判断
- 用模板参数递归匹配来模拟运行期的循环流程
template<int a_1, int ...a_n>
struct MinValue {
static const int value = a_1 > MinValue<a_n...>::value ? MinValue<a_n...>::value : a_1;
};
template<int a, int b>
struct MinValue<a, b> {
static const int value = MinValue<a>::value < MinValue<b>::value ? MinValue<a>::value : MinValue<b>::value;
};
//这个版本是需要的
template<int a>
struct MinValue<a> {
static const int value = a;
};
template<int a_1, int ...a_n>
struct MaxValue {
static const int value = a_1 < MaxValue<a_n...>::value ? MaxValue<a_n...>::value : a_1;
};
template<int a, int b>
struct MaxValue<a, b> {
static const int value = a > b ? a : b;
};
//这个版本不需要
// template<int a>
// struct MaxValue<a> {
// static const int value = a;
// };
int main()
{
//编译期查找最大最小值
const int maxResult = MaxValue<8, 5, 2, 10, 6, 1, 9>::value;
const int minResult = MinValue<8, 5, 2, 10, 6, 1, 9>::value;
std::cout << "MaxValue:" << maxResult << std::endl;
std::cout << "MinValue:" << minResult << std::endl;
}
泛型编程
声明与实现放在一起
非泛型编程
声明与实现分离
运算符重载
一般格式如下
<返回类型说明符> operator <运算符符号> (<参数表>)
{
<函数体>
}
不可重载:
| Operator | |
|---|---|
. | 类属关系运算符 |
.* | 成员指针运算符 |
:: | 作用域运算符 |
# | 编译预处理符号 |
?: | 三目条件运算符 |
sizeof() | 取数据类型的长度 |
- 重载运算符限制在c++语言中已有的运算符范围内的允许重载的运算符之中,不能创建新的运算符
- 运算符重载的实质是函数重载,遵循函数重载的选择原则
- 重载之后不能改变运算符的优先级和结合性,也不能够改变运算符操作数的个数及语法结构
- 运算符重载不能改变该运算符用于内部类型对象的含义
- 运算符重载是针对新类型数据的实际需要对原有运算符进行的适当的改造,重载的功能应当与原有功能相类似
- 重载运算符的函数不能有默认的参数,否则就改变了运算符的参数个数
- 重载的运算符只能是用户自定义类型
|
表达式
| 作为成员函数 | 作为非成员函数 | 示例 |
|---|---|---|---|
| @a | (a).operator@ () | operator@ (a) | !std::cin调用std::cin.operator!() |
| a@b | (a).operator@ (b) | operator@ (a, b) | std::cout << 42调用std::cout.operator<<(42) |
| a=b | (a).operator= (b) | 不能是非成员 | std::string s; s = "abc";调用s.operator=("abc") |
| a(b…) | (a).operator()(b...) | 不能是非成员 | std::random_device r; auto n = r();调用r.operator()() |
| a[b] | (a).operator[](b) | 不能是非成员 | std::map<int, int> m; m[1] = 2;调用m.operator[](1) |
| a-> | (a).operator->() | 不能是非成员 | auto p = std::make_unique<S>(); p->bar()调用p.operator->() |
| a@ | (a).operator@ (0) | operator@ (a, 0) | std::vector<int>::iterator i = v.begin(); i++调用i.operator++(0) |
此表中,@ 是表示所有匹配运算符的占位符:@a 为所有前缀运算符,a@ 为除 -> 以外的所有后缀运算符,a@b 为除 = 以外的所有其他运算符
类型转换重载
double类型转换重载
operator double();
bool类型转换重载
operator bool();
成员函数运算符重载
语法格式
class 类名
{
返回类型 operator 运算符 (参数表);
}
友元运算符重载
在类的内部,定义友元运算符重载函数的格式如下:
class xx
{
friend <返回类型说明符> operator <运算符符号> (形参表)
{
<函数体>
}
}
实例
- 一元运算符重载
Vec2D operator -()
{
return Vec2D(-x, -y);
}
[]运算符重载
int& operator[](int i)
{
if (i > SIZE)
{
cout << "索引超过最大值" << endl;
// 返回第一个元素
return arr[0];
}
return arr[i];
}
++ 和 – 运算符重载
//重载前缀递增运算符++
Time operator ++()
{
second++;
if (second > 59)
{
second -= 60;
minute++;
}
if (minute > 59)
{
minute -= 60;
hour++;
}
if (hour > 23)
{
hour -= 24;
}
return *this;
}
//重载后缀递增运算符++
Time operator ++(int)
{
Time T = *this;
second++;
if (second > 59)
{
second -= 60;
minute++;
}
if (minute > 59)
{
minute -= 60;
hour++;
}
if (hour > 23)
{
hour -= 24;
}
return T;
}
输入输出运算符重载
friend ostream& operator<<(ostream& output,const Time& T)
{
output << T.hour <<":"<< T.minute << ":" << T.second;
return output;
}
friend istream& operator>>(istream& input, Time&T)
{
input >> T.hour >> T.minute >> T.second;
return input;
}
赋值运算符重载
void operator=(const Time& T)
{
hour = T.hour;
minute = T.minute;
second = T.second;
}
覆盖(override)与重载(overload)
-
成员函数覆盖是指派生类重新定义基类的虚函数
-
成员函数重载是指函数名相同,参数不同(数量、类型、次序)
-
成员函数隐藏(重定义)
- 不在同一个作用域内(分别位于基类与继承类中)
- 函数名字相同
- 返回值可以不同
- 参数不同。此时,无论有无
virtual关键字,基类的函数将被隐藏 - 参数相同,但是基类函数没有
virtual关键字。此时,基类的函数被隐藏
2021/04/24
// CMakeProject1.h: 标准系统包含文件的包含文件
// 或项目特定的包含文件。
#pragma once
#include <iostream>
#include <string>
// TODO: 在此处引用程序需要的其他标头。
class Basic {
public:
Basic(){
int_data = 0;
double_data = 0;
std::cout << __FUNCTION__ << std::endl;
}
virtual ~Basic() {
std::cout << __FUNCTION__ << std::endl;
}
virtual void Function() {
std::cout << __FUNCTION__ << std::endl;
}
void Function_2() {
std::cout << __FUNCTION__ << std::endl;
}
private:
int int_data;
double double_data;
};
class Superior :virtual public Basic{
public:
Superior() {
std::cout << __FUNCTION__ << std::endl;
}
void Function() override{
std::cout << __FUNCTION__ << std::endl;
}
void Function_2() {
std::cout << __FUNCTION__ << std::endl;
}
Basic* get_basic()
{
return this;
}
~Superior() {
std::cout << __FUNCTION__ << std::endl;
}
};
// CMakeProject1.cpp: 定义应用程序的入口点。
#include "CMakeProject1.h"
using namespace std;
int main()
{
Superior* sup = new Superior;
sup->get_basic()->Function();
sup->Function();
sup->get_basic()->Function_2();
sup->Function();
delete sup;
cout << "--------------------" << endl;
Basic* base = new Superior;
base->Function();
delete base;
cout << "--------------------" << endl;
Superior sup_2;
Basic* base_2 = &sup_2;
base_2->Function();
base_2->Function_2();
return 0;
}
结果如下
Basic::Basic
Superior::Superior
Superior::Function
Superior::Function
Basic::Function_2
Superior::Function
Superior::~Superior
Basic::~Basic
--------------------
Basic::Basic
Superior::Superior
Superior::Function
Superior::~Superior
Basic::~Basic
--------------------
Basic::Basic
Superior::Superior
Superior::Function
Basic::Function_2
Superior::~Superior
Basic::~Basic
虚函数表 Virtual Table
对于一个类来说,如果类中存在虚函数,那么该类的大小就会多4个字节,然而这4个字节就是一个指针的大小,这个指针指向虚函数表。 所以,如果对象存在虚函数,那么编译器就会生成一个指向虚函数表的指针,所有的虚函数都存在于这个表中,虚函数表就可以理解为一个数组,每个单元用来存放虚函数的地址。
#include <iostream>
#include <stdio.h>
using namespace std;
class Base {
public:
virtual void a() { cout << "Base a()" << endl; }
virtual void b() { cout << "Base b()" << endl; }
virtual void c() { cout << "Base c()" << endl; }
};
class Derive : public Base {
public:
virtual void b() { cout << "Derive b()" << endl; }
};
int main()
{
cout << "----------Base-------------" << endl;
Base* q = new Base;
long* tmp1 = (long*)q;
long* vptr1 = (long*)(*tmp1);
for (int i = 0; i < 3; i++) {
printf("vptr[%d] : %p\n", i, vptr1[i]);
}
Derive* p = new Derive;
long* tmp = (long*)p;
long* vptr = (long*)(*tmp);
cout << "---------Derive------------" << endl;
for (int i = 0; i < 3; i++) {
printf("vptr[%d] : %p\n", i, vptr[i]);
}
return 0;
}
纯虚析构函数
如果基类并不需要回收清理什么,那么析构函数就可以是虚构函数
虚析构函数
-
一般的建议是作为基类的析构函数是虚函数
-
当指针指向的对象是基类类型时,delete销毁对象的时候并不会调用派生类的析构函数,这样就造成了对象销毁不完整
总结一下虚析构函数的作用:
(1)如果基类的析构函数不加virtual关键字
当基类的析构函数不声明成虚析构函数的时候,当派生类继承父类,基类的指针指向派生类时,delete掉基类的指针,只调动基类的析构函数,而不调动派生类的析构函数。
(2)如果基类的析构函数加virtual关键字
当基类的析构函数声明成虚析构函数的时候,当派生类继承基类,基类的指针指向派生类时,delete掉基类的指针,先调动派生类的析构函数,再调动基类的析构函数。
原理分析
由于基类的析构函数为虚函数,所以派生类会在所有属性的前面形成虚表,而虚表内部存储的就是基类的虚函数。
当delete基类的指针时,由于派生类的析构函数与基类的析构函数构成多态,所以得先调动派生类的析构函数;之所以再调动基类的析构函数,是因为delete的机制所引起的,delete 基类指针所指的空间,要调用基类的析构函数。
虚继承
- 是指一个指定的基类,在继承体系结构中,将其成员数据实例共享给也从这个基类型直接或间接派生的其它类。
class typename :virtual public inheritance{
}
纯虚函数
virtual ReturnType Function(Argument List)=0;
抽象类
-
含有纯虚函数的类就是抽象类
-
抽象类没有完整的信息,只能是派生类的基类
-
抽象类不能有实例,不能有静态成员
-
派生类应该实现抽象类的所有方法
隐式类型转换
- 编译器自动完成
- 转换场景
- 初始化
- 变量赋值
- 计算表达式
- 转换规则
- 尽量类型向上升级
- 尽量避免数据丢失
graph BT
A(bool) -->B(char)
B(char) -->C(short int)
C(short int) -->D(int)
D(int) -->E(unsigned int)
E(unsigned int) -->F(long)
F(long) -->G(unsigned)
G(unsigned) -->H(long long)
H(long long) -->I(float)
I(float) -->J(double)
J(double) --> K(long double)
显式类型转换
C风格类型转换
float f = (float)3 / 4;
float f = float(3) / 4;(C++新写法)
- 语义较多,能力太强
- 容易误用,不推荐
更安全的类型转换
静态类型转换
- 基本数据类型间转换
- 基类及子类间转换
- 编译时进行类型检查
static_cast<目标类型>(源类型)
源类型和目标类型只要有一个方向可以隐式转换,那么两个方向都可以做静态类型转换,如果不能则报错
动态类型转换
- 向下转换
- 基类指针转换为子类指针
- 主要用于多态场景
- 运行时类型检查
- 基类指针不指向子类对象时转为空指针
- 底层实现基于虚表指针,无虚函数类不能使用dynamic_cast
- static_cast
- 也可以做向下转换
- 不具备运行时类型检查
- 存在风险
- 也可用于引用,和指针用法类似
dynamic_cast<目标类型>(源类型)
将基类类型参数转换为派生类类型,源类型和目标类型必须同是引用或指针,且目标类型和源类型之间存在继承关系,否则报错
去常类型转换
- const指针或引用转为非const
- 存在非法场景(未定义行为)
- 使用场景较少
const_cast<目标类型>(源类型)
源类型和目标类型必须同是引用或指针,且目标类型和源类型之间只有常属性的区别,否则报错
int* p1 = NULL;
const int* p2 = NULL;
//const不是基本的数据类型
p1 = const_cast<int*>(p2);
p2 = const_cast<const int*>(p1);//用来加const和去const
重解释类型转换
- 不同类型指针之间、指针与整型间强转
- 编译器不对指向对象的类型做任何检查
- 谨慎使用
reinterpret_cast<目标类型>(源类型)
源类型和目标类型必须同是指针,或者一个指针一个整数,否则报错
int* p = NULL;
char* p2 = NULL;
p = reinterpret_cast<int*>(p2);
p2 = reinterpret_cast<int*>(p);
int c = 0;
c = reinterpret_cast<int>(p2);
类型转换函数
- 类可以提供自定义的类型转换函数
- 类似操作符重载
- 可以用explicit修饰,避免隐式转换
- 一般使用const修饰
Upcasting and Downcasting (向上/向下转型)
-
upcasting : Assigning a pointer of a derived class type to a pointer of its base class type (将派生类类型指针赋值给基类类型指针)
-
downcasting : Assigning a pointer of a base class type to a pointer of its derived class type. (将基类类型指针赋值给派生类类型指针)
上转可不适用dynamic_cast而隐式转换
GeometricObject *g = new Circle(1);
Circle *c = new Circle(2);
g = c; //Correct
下转必须显式执行
c = dynamic_cast<Circle *>(g);
类的声明
class 类名
{
private:
私有的数据和成员函数;
public:
公用的数据和成员函数;
protected:
保护的数据和成员函数;
}
探讨public/private给我们带来了什么好处?
- 类的内部数据得到保护
- 类的使用方法更加明确,不易出错
- 类的实现细节更容易修改
- 内部数据修改有了统一入口,更容易调试
默认生成的函数
- 默认构造函数
- 默认析构函数
- 拷贝构造函数
- 赋值操作符
- 地址运算符
构造函数(Constructor)
- 类声明
- 类名(类型 形参,类型 形参,…)
- 定义对象
- 类名 对象(实参,实参,…)
- 特殊的成员函数
- 对象创建时自动执行,无需用户调用且不能被调用(其实可以)
- 与类名同名
- 可传入参数,无返回值
- 可重载
- 如果用户不定义,则编译器自动生成一个
构造函数初始化列表
class_name(int v1,const int& v2):var(v1),var2(v2)
拷贝构造函数
classname (const classname &obj) {
// 构造函数的主体
}
以下两种方式会调用拷贝构造函数:
- classname A(B);
- classnaem A=B;
析构函数(Destructor)
- 声明
- ~类名()
- 特殊的成员函数
- 对象生命周期即将结束时自动被调用,释放资源或是执行清理工作
- 类名前加一个“~“
- 没有返回值,也没有函数参数
- 可以显示调用析构函数(慎用)
- Can destructors be virtual in C++
构造和析构函数链
- 构造函数链
- 构造类实例会沿着继承链调用所有的基类ctor
- 调用顺序: base first, derive next
- 析构函数链
- dtor与ctor正好相反
- 调用顺序: derive first, base next
this指针
在 C++ 中,每一个对象都能通过 this 指针来访问自己的地址。this 指针是所有成员函数的隐含参数。因此,在成员函数内部,它可以用来指向调用对象。
- 指向对象自身首地址
- 引用整个对象
*this - 仅能在类内部使用
static成员
- 修饰类成员
- 成员函数
- 成员变量
- 调用方式
- 类名::静态成员名
- 特殊性
- this指针无效
- 不能访问类成员
const成员函数
- 不可修改对象
- 声明
- 数据类型 函数名 (参数列表)const;
- 只要成员函数不修改对象就应声明为const
char *getData() const;
函数的功能很简单,仅仅是为了获取成员变量的值,没有任何修改成员变量的企图,所以加了 const 限制,这是一种保险的做法,同时也使得语义更加明显。
inheritance→get_basic()
class Superior :public basic{
basic* get_basic()
{
return this;
- }
};
友元
- 不受权限控制,访问私有成员
- 三种友元
- 友元函数
- 友元类
- 友元成员函数
- 注意
- 友员也破环了类的隐藏与封装
- 友元关系不能被继承
- 友元关系是单向的,不具有交换性
- 友元关系不具有传递性
面向对象编程思想
设计思路
面向对象的程序设计的思路和人们日常生活中处理问题的思路是相似的。在自然世界和社会生活中,一个复杂的事物总是由许多部分组成的。任何一个事物都可以看成一个对象(object)。对象可大可小,是构成系统的基本单位。
对象的两个要素
任何一个对象都应当具有这两个要素,即属性(attribute)和行为(behavior),它能根据外界给的信息进行相应的操作。一个对象往往是由一组属性和一组行为构成的。一般来说,凡是具备属性和行为这两种要素的,都可以作为对象。
抽象
抽象是对具体的对象(问题)进行概括,提取出一类对象的公共属性/行为。
- 数据抽象:描述某类对象的属性或状态
- 函数抽象:描述某类对象的共有的行为特征或具有的功能
抽象的结果:形成类的定义
封装
在抽象出的数据成员、函数成员基础上提取出对外接口,定义访问权限。以达到简化外部使用的目的。使用者不必了解具体的实现细节,而只需要通过外部接口,来访问类的成员。
面向对象三要素
- 封装(Encapsulation)
- 将数据和方法(数据上的操作)捆绑,定义新的类型
- 接口与实现分离,隐藏实现细节
- 继承(Inherit)
- 子类对基类进行特化(扩展、覆盖、重定义)
- 达到复用代码的目的
- 多态(Polymorphism)
- 允许不同类的对象对同一消息做出响应,即同一消息可以根据发送对象的不同而采用多种不同的行为方式
关系
- is-a一般是泛化关系
- 继承:表示类与类之间的继承关系
- 实现:表示一个类实现接口的功能
- use-a为依赖关系
- 表示两个相互独立的对象,当一个对象负责构造另一个对象的实例,或者依赖另一个对象的服务
- has-a一般是聚合关系
- 两个对象有不同的生命周期
- contain-a为组合关系
- 体现了严格的部分与整体的关系,生命周期相同
预处理器
宏定义
字符串的替换
#define 预处理指令用于创建符号常量。该符号常量通常称为宏,指令的一般形式是:#define macro-name replacement-text
当这一行代码出现在一个文件中时,在该文件中后续出现的所有宏都将会在程序编译之前被替换为 replacement-text。
#ifndef π // 先测试π是否被宏定义过
#define π 3.1415926 //如果没有则定义并执行程序段1
//程序段1
#else //如果已经定义则执行程序段2
//程序段2
#endif
参数宏
#define MIN(a,b) (a<b ? a : b)
# 和 ## 运算符
#运算符会把replacement-text令牌转换为用引号引起来的字符串。
#define MYSTR( x ) #x
···
cout << MYSTR(HELLO C++) << endl;
当上面的代码被编译和执行时,它会产生下列结果:HELLO C++
预处理器把cout << MKSTR(HELLO C++) << endl;
转换成了cout << "HELLO C++" << endl;
##运算符用于连接两个令牌。
#define concat(a, b) a ## b
···
int xy = 100;
cout << concat(x, y);
当上面的代码被编译和执行时,它会产生下列结果:100
预处理器把cout << concat(x, y);
转换成了cout << xy;
C++ 中的预定义宏
|
宏
| 描述 |
|---|---|
__LINE__ | 代表当前源代码中的行号的整数常量。 |
__FILE__ | 这会在程序编译时包含当前文件名。 |
__DATE__ | 进行预处理的日期,格式为"Mmm dd yyyy" |
__TIME__ | 源文件编译时间,格式为"hh:mm:ss" |
__FUNCTION__ | 当前所在函数名 |
C 和 C++的兼容及差异
const变量
- 只读
- 初始化后不可改变(必须初始化)
- 语法
- const int var;
- int const var;
- 修饰指针时const位置不同语义不同
- const int* p;//指针可修改,指向对象不可修改
- int const* p;//指针不可修改,指向内容可修改
- 与C的差异
- 与#define的区别
- C也有,略有差异
强制类型转换
- C语言形式:(类型)(表达式):(int)var
- C++新增形式:类型名(表达式):int(var)
内存申请
| 高地址 | 栈区(向下增长)↓ |
|---|---|
| ↑ | 堆区(向上增长)↑ |
| ↑ | 静态区(全局区) |
| ↑ | 常量区 |
| 低地址 | 代码区 |
内存分区模型
C++程序在执行时,将内存大方向划分为4个区域
- 代码区:存放函数体的二进制代码,由操作系统进行管理的
- 全局区:存放全局变量和静态变量以及常量
- 栈区:由编译器自动分配释放, 存放函数的参数值,局部变量等
- 堆区:由程序员分配和释放,若程序员不释放,程序结束时由操作系统回收
内存四区意义:
不同区域存放的数据,赋予不同的生命周期, 给我们更大的灵活编程
堆内存申请/释放
C语言: malloc / free
C++: new / delete
for (int i = 0;; i++)
{
char* block = new char[1024 * 1024];
cout << "已申请" << i + 1 << "M内存" << endl;
}
指针
int var = 20; // 实际变量的声明
int *ip; // 指针变量的声明
ip = &var; // 在指针变量中存储 var 的地址
引用
// 声明简单的变量
int i;
double d;
// 声明引用变量
int& r = i;
double& s = d;
- 变量别名
- 函数参数传递方式
- 值传递
- 地址传递
- 引用传递
void passParamByValue(std::string param)
{
param = "changed param";
}
void passParamByRef(std::string& param)
{
param = "changed param";
}
- 注意事项
- 必须初始化
- 初始化后不能引用另一个对象
内联函数
- 语法
- inline void func();
- 原理
- 在编译时将所调用函数的代码直接嵌入到主调函数中,减少函数调用开销
- 提示
- 应仅用来修饰简单函数
函数重载
- 函数起名字存在痛点
- 函数名相同,参数类型/个数不同
- 注意事项
- 不支持以返回值区分函数
- 支持const修饰区分
函数模板
- 语法
template<typename T>
T max(T a, T b)
{
return a > b ? a : b;
}
函数默认参数
- 语法
void func(int param1, float param2 = 0.0f);
- 注意事项
- 默认值一般在声明中指定
- 若给某一参数设置了默认值,那么其后所有参数都必须也设置默认值
- 若给已经设置默认值的参数传递实际值,则该参数左边的所有参数,无论是否有默认值,都必须传递实际参数
作用域与命名空间
作用域
- 作用域(scope)描述了一个名字在文件(编译单元)的多大范围内可见
- 解决名字冲突的痛点
- 局部域是包含在函数定义或者块(如括号包含的)中的程序文本部分
- 每个类定义都引入了一个独立的类域
- 用户主动添加的具有名字的最外层作用域
函数的作用域
- 可以是类,也可以是名字空间
- 但不能是局部的
- lambda函数
命名空间(namespace)
- 声明 (可嵌套可不连续)
namespace MySpace
{
... ...
}
- 使用
- 提前声明:
- using 名称::变量
- using 名称::函数名
- 直接使用:
- 名称::变量或函数名
链接性
- 链接性(linkage)描述了名称如何在各个单元中的共享
- 外部链接
- 内部链接
- 默认为外部链接
- 修饰符extern用在变量或者函数的声明前,用来说明“此变量/函数是在别处定义的,要在此处引用”
- extern声明不是定义,即不分配存储空间。
- static修饰后改为内部链接
输出流格式控制
C++ 中常用的输出流操纵算子如表所示,它们都是在头文件 iomanip 中定义的;要使用这些流操纵算子,必须包含该头文件。
注意:“流操纵算子”一栏中的星号*不是算子的一部分,星号表示在没有使用任何算子的情况下,就等效于使用了该算子
|
流操纵算子
| 作用 |
|---|---|
| 常用 | |
| *dec | 以十进制形式输出整数 |
| hex | 以十六进制形式输出整数 |
| oct | 以八进制形式输出整数 |
| fixed | 以普通小数形式输出浮点数 |
| scientific | 以科学计数法形式输出浮点数 |
| left | 左对齐,即在宽度不足时将填充字符添加到右边 |
| *right | 右对齐,即在宽度不足时将填充字符添加到左边 |
| setbase(b) | 设置输出整数时的进制,b=8、10 或 16 |
| setw(w) | 指定输出宽度为 w 个字符,或输人字符串时读入 w 个字符 |
| setfill© | 在指定输出宽度的情况下,输出的宽度不足时用字符 c 填充(默认情况是用空格填充) |
| setprecision(n) | 设置输出浮点数的精度为n。 在使用非fixed且非scientific方式输出的情况下,n即为有效数字最多的位数,如果有效数字位数超过n,则小数部分四舍五入,或自动变为科学计数法输出并保留一共n位有效数字。在使用 fixed 方式和 scientific 方式输出的情况下,n是小数点后面应保留的位数 |
| setiosflags(flag) | 将某个输出格式标志置为 1 |
| resetiosflags(flag) | 将某个输出格式标志置为 0 |
| 不常用 | |
| boolapha | 把 true 和 false 输出为字符串 |
| *noboolalpha | 把 true 和 false 输出为 0、1 |
| showbase | 输出表示数值的进制的前缀 |
| *noshowbase | 不输出表示数值的进制.的前缀 |
| showpoint | 总是输出小数点 |
| *noshowpoint | 只有当小数部分存在时才显示小数点 |
| showpos | 在非负数值中显示 + |
| *noshowpos | 在非负数值中不显示 + |
| *skipws | 输入时跳过空白字符 |
| noskipws | 输入时不跳过空白字符 |
| uppercase | 十六进制数中使用A~E。若输出前缀,则前缀输出 0X,科学计数法中输出 E |
| *nouppercase | 十六进制数中使用 |
| internal | 数值的符号(正负号)在指定宽度内左对齐,数值右对 齐,中间由填充字符填充。 |
科学计数法表示
cout << scientific << π << endl;
保留有效数字
cout.precision(3);//保留几位就填几
//位数不足时
cout.precision(10);
cout << fixed << 3.1415926;//不足十位
保留两位小数
#include <iomanip> //不要忘了头文件
cout<<setiosflags(ios::fixed)<<setprecision(2);//保留几位就填几
//或者
printf("%.2lf", π);//不会影响之后输出
C++ 编程范式
- 融合多种编程范式
- 以面向对象编程+泛型编程为主
- 支持函数式编程
泛型编程
- 目的
- 实现C++的STL(Standard Template Library 标准模板库)
- 支持机制
- 模板(Templates)的实质就是参数化类型,简而言之:把特定的类型信息抽象化,抽象成模板参数T。这样就可以编写出任意类型动作一致的类或方法,在使用时才指定实际类型
- 特性
- 泛型一定程度上杜绝了类型转换
编程范式
-
面向过程编程©
-
面向对象编程(c++)
-
重用性
代码可复用,维护成本低
-
灵活性
基于可复用的模块进行重新组装、重构,以达到实现不同的功能
-
扩展性
对新增需求友好,模块可扩展
-
-
函数式编程(js)
-
泛型编程
使用编译器通常的流程
.cpp→编译→.obj→链接→.exe
调试
F5 运行
F10 单步运行
F11 单步运行会进入到函数内
- 断点
- 普通断点
- 数据断点
- 断点条件
数字转string,string转数字de函数
数字转string 这些都可以
to_string(int)
to_string(long)
to_string(long long)
to_string(float)
to_string(double)
to_string(long double)
string转数字
头文件:
#include<cstdlib>
stoi(str)//int
stol(str)//long
stoll(str)//long long
MyString //继承string
#include <iostream>
#include <string>
#include <algorithm>
using namespace std;
template<typename T>
class MyString : public string
{
public:
MyString(const char* str) :string(str) {}; // 用string()初始化构造函数
MyString() :string() {}; // 无参数构造函数,用string
MyString(string str) :string(str) {}; // 类型转换构造函数
MyString operator () (int i, int j)//截取
{
return substr(i, j);
}
MyString operator - (const MyString str)//减去相同字符
{
MyString temp = *this;
for (int i = 0; i < temp.size(); i++)
{
for (int j = 0; j < str.size(); j++)
{
if (temp[i] == str[j])
{
temp.erase(i,i);
}
}
}
return temp;
}
MyString reverse()//反转
{
MyString temp;
temp.resize(this->size());
for (int i = 0; i < this->size(); i++)
{
temp[i] = this->at(this->size() - 1 - i);
}
return temp;
}
int toInt()//toInt
{
int intStr = atoi(this->c_str());
return intStr;
}
};
int main()
{
MyString<string> str = "0123456789";
MyString<string> str_2 = str.reverse();
MyString<string> str_3 = "123";
cout << (str_2 - str_3).toInt();
return 0;
}
计算栅格路径数
假设存在一个m*n的栅格,编写函数计算从左下角(坐标:0, 0)走右上角(坐标:m,n)的路径有多少种。只能沿栅格线走,且不能反向走,既只能向上或向右走。
#include<iostream>
using namespace std;
int move(int m, int n)
{
if (m < 0 || n < 0)return 0;
else (m == 0 || n == 0)return 1;
else return move(m - 1, n) + move(m, n - 1);
}
int main()
{
int m, n;
cin >> m >> n;
cout << move(m, n) << endl;
return 0;
}
选择排序
template<typename T>
void selection_sort(std::vector<T>& arr) {
for (int i = 0; i < arr.size() - 1; i++) {
int min = i;
for (int j = i + 1; j < arr.size(); j++)
if (arr[j] < arr[min])
min = j;
Swap(arr[i], arr[min]);
}
}
快速排序
#include<iostream>
using namespace std;
template <class T>
void Swap(T &t1, T &t2)
{
T temp;
temp = t1;
t1 = t2;
t2 = temp;
}
//快速排序
template <class T>
void quick_sort(T s[], int left, int right)
{
if (left < right)
{
Swap(s[left], s[(left + right) / 2]); //将中间的这个数和第一个数交换
T i = left, j = right, x = s[left];
while (i < j)
{
while (i < j && s[j] >= x) // 从右向左找第一个小于x的数
j--;
if (i < j)
s[i++] = s[j];
while (i < j && s[i] < x) // 从左向右找第一个大于等于x的数
i++;
if (i < j)
s[j--] = s[i];
}
s[i] = x;
quick_sort(s, left, i - 1); // 递归调用
quick_sort(s, i + 1, right);
}
}
int main()
{
char aa[10] = {'a','v','c','d','t','z','q','i','b','x' };
int n = sizeof(aa);//不同类型记得改
quick_sort(aa,0,n-1);
for (int i = 0; i < 10; i++)
cout << aa[i] << " ";
}
快速排序2
#include<iostream>
using namespace std;
template <typename T>
void Swap(T& t1, T& t2)
{
T temp;
temp = t1;
t1 = t2;
t2 = temp;
}
//快速排序
template <typename T>
void quick_sort(T s[], int left, int right)
{
if (left < right)
{
int i = left, j = right, p = left;
while (i < j)//当i = j时退出循环
{
//从右往左找到比p小的数
for (; i < j && s[j] >= s[p]; j--) {}Swap(s[p], s[j]); p = j;
//从左往右找到比p大或相等的数
for (; i < j && s[i] < s[p]; i++) {}Swap(s[p], s[i]); p = i;
}
quick_sort(s, left, i - 1); // 递归调用
quick_sort(s, i + 1, right);
}
}
int main()
{
//int array[10] = { 49,54,60,23,31,13,26,49,52,97 };
//char array[10] = { 'e','v', 'h', 'o', 'c', 'z', 'h', 'l', 'f', 'a', };
string array[10] = { "床前", "明月光", "疑是", "地上霜", "举头", "望", "明月", "低头", "思", "故乡" };
int n = sizeof(array) / sizeof(array[0]);
quick_sort(array, 0, n - 1);
for (int i = 0; i < n; i++)
cout << array[i] << " ";
return 0;
}
短路现象
package pro;
public class hello {
public static void main(String[] args) {
int a=-1,b=1;
int result = -1;
result = (a++)&(b--);
System.out.println(a+""+b+""+result);
int c=0,d=1;
result = (a++)|(b--);
System.out.println(c+""+d+""+result);
}
}
输出结果
001
010
引用计数的智能指针的实现
SmartPoint.h
#pragma once
#include <iostream>
//#include <objbase.h>
using namespace std;
#pragma once
// 引用计数操作器
template<typename T> struct IUnknown
{
// 增加引用计数
virtual void AddRef()=0;
// 减少引用计数
virtual void Release()=0;
// 返回一个引用计数器
static int* CreateCounter(T* reference)
{
return new int(0);
}
// 删除引用函数
static void DeleteReference(int* counter, void* reference)
{
delete counter;// 删除计数器
delete (T*)reference;// 删除资源
}
};
// 智能指针类
template<typename T> class KComPtr :public IUnknown<T>
{
public:
// 增加引用计数
void AddRef()
{
if (counter)
{
++(*counter);
}
}
// 减少引用计数,如果资源不再被引用则删除资源
void Release()
{
if (counter)
{
if (--(*counter) == 0)
{
originalDestructor(counter, originalReference);
counter = 0;
reference = 0;
originalReference = 0;
originalDestructor = 0;
}
}
}
// 返回当前计数器
int* Counter() const
{
return counter;
}
// 获取资源的直接指针
T* getResources() const
{
return reference;
}
// 重载->操作符
T* operator->()const
{
return reference;
}
// 重载*操作符
T& operator*() const {
return *reference;
}
// 构造一个空的智能指针
KComPtr()
: counter(0)
, reference(0)
, originalReference(0)
, originalDestructor(0)
{}
// 用一个普通指针构造智能指针
KComPtr(T* pointer)
: counter(0)
, reference(0)
, originalReference(0)
, originalDestructor(0)
{
if (pointer)
{
counter = IUnknown<T>::CreateCounter(pointer); // 创建新的计数器
reference = pointer; // 获取当前资源的引用
originalReference = pointer; // 将原始资源置为当前资源
originalDestructor = IUnknown<T>::DeleteReference;// 连接删除器
AddRef();// 引用计数增加
}
};
// 用另一个同类型的智能指针进行拷贝构造,不创建新资源
KComPtr(const KComPtr<T>& pointer)
: counter(pointer.counter)
, reference(pointer.reference)
, originalReference(pointer.originalReference)
, originalDestructor(pointer.originalDestructor)
{
AddRef();// 引用计数增加
}
// 用其他类型的智能指针进行转型拷贝构造,不创建新资源
// 将原始类型U转换为当前智能指针的类型T,但是原始资源与原始删除器不变
template<typename U> KComPtr(const KComPtr<U>& pointer)
: counter(0)
, reference(0)
, originalReference(0)
, originalDestructor(0)
{
T* converted = pointer.get();
if (converted)
{
counter = pointer.Counter();
reference = converted;
originalReference = pointer.originalReference;
originalDestructor = pointer.originalDestructor;
AddRef();
}
}
// 析构当前的智能指针,减少引用计数
~KComPtr()
{
Release();
}
// 将一个普通指针的值赋给智能指针
// 构造失败则将智能指针置为空
KComPtr<T>& operator=(T* pointer)
{
Release();// 原本的资源引用减少
if (pointer)
{
counter = IUnknown<T>::CreateCounter(pointer);
reference = pointer;
originalReference = pointer;
originalDestructor = &IUnknown<T>::DeleteReference;
AddRef();
}
else
{
counter = 0;
reference = 0;
originalReference = 0;
originalDestructor = 0;
}
return *this;
}
// 将另一个智能指针的值赋给自身
KComPtr<T>& operator=(const KComPtr<T>& pointer)
{
if (this != &pointer)// 判断是否自赋值
{
Release();
counter = pointer.counter;
reference = pointer.reference;
originalReference = pointer.originalReference;
originalDestructor = pointer.originalDestructor;
AddRef();
}
return *this;
}
// 将一个不同类型的智能指针赋给自身
// 智能指针之前引用的资源取消,并引用新的智能指针的资源
// 转型失败的话返回空智能指针
template<typename U> KComPtr<T>& operator=(const KComPtr<U>& pointer)
{
T* converted = pointer.get();
Release();
if (converted)
{
counter = pointer.counter;
reference = converted;
originalReference = pointer.originalReference;
originalDestructor = pointer.originalDestructor;
AddRef();
}
else
{
counter = 0;
reference = 0;
originalReference = 0;
originalDestructor = 0;
}
return *this;
}
// 重载比较操作符,用于比较智能指针与普通指针是否指向相同资源
bool operator ==(const T* pointer)const { return reference == pointer; }
bool operator !=(const T* pointer)const { return reference != pointer; }
bool operator > (const T* pointer)const { return reference > pointer; }
bool operator >=(const T* pointer)const { return reference >= pointer; }
bool operator < (const T* pointer)const { return reference < pointer; }
bool operator <=(const T* pointer)const { return reference <= pointer; }
// 重载比较操作符,用于比较两个智能指针是否指向相同资源
bool operator ==(const KComPtr<T>& pointer)const { return reference == pointer.reference; }
bool operator !=(const KComPtr<T>& pointer)const { return reference != pointer.reference; }
bool operator > (const KComPtr<T>& pointer)const { return reference > pointer.reference; }
bool operator >=(const KComPtr<T>& pointer)const { return reference >= pointer.reference; }
bool operator < (const KComPtr<T>& pointer)const { return reference < pointer.reference; }
bool operator <=(const KComPtr<T>& pointer)const { return reference <= pointer.reference; }
// 智能指针指向非空时有true的布尔值
operator bool()const { return reference != 0; }
private:
template<typename X> friend class KComPtr;
// 删除器
typedef void (*Destructor)(int*, void*);
// 引用计数器
int* counter;
// 引用资源,在拷贝过程中可能改变类型
T* reference;
// 原始引用资源,保持资源第一次创建时的指针
void* originalReference;
// 原始资源删除函数,在最后一个引用被析构时调用,删除资源
Destructor originalDestructor;
};
main.cpp
#include "SmartPoint.h"
#include <thread>
using namespace std;
class Base {
public:
Base() { cout << "构造了一个基类" << endl; }
~Base() { cout << "基类被析构" << endl; }
};
class Derived : public Base
{
public:
Derived() { cout << "构造了一个派生类" << endl; }
~Derived() { cout << "派生类被析构" << endl; }
};
int main()
{
KComPtr<Base> BasePoint_1 = new Base();
KComPtr<Base> BasePoint_2 (new Base());
KComPtr<Base> BasePoint_3 = new Derived();
Base* p = BasePoint_1.getResources();
KComPtr<Base> BasePoint_4 = BasePoint_1;
cout<< "BasePoint.Counter_1:" << *BasePoint_1.Counter() << endl
<< "BasePoint.Counter_2:" << *BasePoint_2.Counter() << endl
<< "BasePoint.Counter_3:" << *BasePoint_3.Counter() << endl
<< "BasePoint.Counter_4:" << *BasePoint_4.Counter() << endl;
if (BasePoint_1 == p) cout << "相等" << endl;
if (BasePoint_1 == BasePoint_4) cout << "相等" << endl;
return 0;
}
git
版权声明:本文为stream的原创文章,遵循CC 4.0 BY - SA版权协议,转载请附上原文出处链接及本声明。
原文链接:http://note.youdao.com/noteshare?id=4c54753e7c3a8ccda0e023eb97a7cbc3
本文详细探讨了C++ Standard Template Library (STL)的容器、迭代器、算法等核心概念,涉及容器种类、迭代器定义、内存管理、智能指针(如unique_ptr、shared_ptr)以及异常处理等内容,同时还展示了如何使用lambda表达式和模板。

被折叠的 条评论
为什么被折叠?



