C++——探索智能指针的设计原理
前言: RAII是资源获得即初始化, 是一种利用对象生命周期来控制程序资源地手段。 智能指针是在对象构造时获取资源, 并且在对象的声明周期内控制资源, 最后在对象析构的时候释放资源。注意, 本篇文章参考——C++ 智能指针 - 全部用法详解-CSDN博客
看完博主的文章的友友们, 可以去看一下该篇文章, 该作者写的比博主通俗易懂。
目录
为什么需要智能指针
智能指针
auto_ptr
auto_ptr的用法:
auto_ptr的模拟实现:
unique_ptr
unique_ptr的用法
unique_ptr的模拟实现
weak_ptr
weak_ptr的用法:
weak_ptr的模拟实现
智能指针坑点
为什么需要智能指针
首先我们来看一下这一个简单的程序:
void func()
{int* p = new int;
}int main()
{func();return 0;
}
在这个程序里面, func中定义了一个指向堆区一块空间的p。 但是当出了作用域后, p指针就被销毁了, 但是p指针指向的空间没有被销毁,这个时候就发生了内存泄漏。
另外一种情况就是我们虽然手动释放了内存, 但是中途发生了异常, 程序发生跳转, 手动释放内存被截胡了。 也会导致发生内存泄漏。
void test()
{int* ptr = new int;if (1) {throw "发生异常"; //这里发生截胡, 无法走到下一行。}delete ptr; //这里没有释放资源
}int main()
{try {test();}catch (const char* str){cout << str << endl;}catch (...) {cout << "未知异常" << endl;}return 0;
}
而只能指针就是为了这种情况设计出来的。也就是说, 智能指针就是为了我们能够方便管理动态内存分配的资源, 它能够在对象的声明周期结束时自动释放这些资源。
如图为一个简单的智能指针
在这个智能指针当中, 当我们创建对象时,可以使用一块资源初始化。 然后这块资源就会在这个对象的生命周期结束时自动销毁。
这就是智能指针的基本原理, 虽然我们使用指针时, 指针指向的空间不会被自动释放。但是对象在生命周期结束时会自动释放, 所以我们把指针指向的资源放到对象里, 让对象在释放自身的时候将资源一起释放掉。
智能指针
现在有三个智能指针的解决方案:
- auto_ptr C++98
- unique_ptr C++11
- share_ptr C++11
另外, 还有一个用来解除share_ptr中的循环引用问题的解决方案。
- wake_ptr C++11
auto_ptr
auto_ptr的用法:
使用智能指针需要包含头文件memory, 具体使用方法如下:
#include<memory> //只用智能指针需要包含memory头文件int main()
{auto_ptr<int> p(new int); //利用auto_ptr创建一个管理int指针资源的对象auto_ptr<list<int>> pl(new list<int>); //利用auto_ptr创建一个管理list<int>类型的指针资源的对象*p = 4; //auto_ptr<int>类型也能进行解引用操作(*pl).push_back(16); //容器的指针, 解引用后就是容器本身。(*pl).push_back(15);(*pl).push_back(14);(*pl).push_back(13);(*pl).push_back(12);cout << *p << endl; //打印*pauto it = (*pl).begin(); //pl解引用获得list<int>对象, 可以像使用指针while (it != (*pl).end()) {cout << *it << endl;++it;}return 0;
}
这里创建智能指针对象是: auto_ptr<类型名> p(new 类型名) , 这里创建的时候不能使用 ’ = ‘, 只能使用 ' ( ) ';
auto_ptr是在C++98创建出来的, 但是这个智能指针在之后很少被人用。 因为它有一个弊端, 就是当进行拷贝的时候, 该智能指针管理的资源会被 ”抛弃“, 另一个智能指针进行接收。 也就是如图:
这个模式存在一些弊端。如果我们使用一个容器进行插入操作的时候,插入操作一定会赋值。 那么赋值就会导致原本智能指针对象中的资源被转移。
另外, auto_ptr的另一个弊端就是auto_ptr不支持对象数组的操作。所以在C++11出现更好的unique_ptr和share_ptr后,auto_ptr已经很少被使用。
auto_ptr有三个常用接口。 get, release, reset。
三个函数的主要功能
1. get()
get是获取对象中管理的资源:
2.release
release, 是取消对象中管理的资源:
3.reset
reset, 是重新分配对象中管理的资源:
auto_ptr的模拟实现:
//首先, 对于智能指针来说,他们的模板类名是这样的:
template<class T> //模板类, 可以接收T类型的资源class auto_ptr //智能指针类名{};
//然后, 在类里面定义资源类型的指针, 用来维护这块资源:
template<class T>class auto_ptr{private:T* _ptr; //T类型资源的指针, 用来维护一块资源};
//所有智能指针的构造都是一样的, 就是使用一块资源交给智能指针里面的指针变量进行维护。
auto_ptr(T* ptr):_ptr(ptr){}
//auto_ptr的拷贝构造, 其实就是把一个智能指针管理的资源 “抛弃”,然后另一个智能指针进行接收。 至于这样做的弊端, 上面已经提到过, 这里不赘述。
auto_ptr(auto_ptr<T>& ptr):_ptr(ptr._ptr) {ptr._ptr = nullptr;}
//最重要的就是智能指针的销毁, 销毁时, 要将管理的资源一块释放掉, 代码如下:
~auto_ptr() {delete _ptr;_ptr = nullptr;}
//然后智能指针还要像普通指针一样能够进行基本运算——加加, 减减, 解引用等。那么就要重载这些运算符, 如下图:
T& operator*() //解引用{return *_ptr;}T* operator->() //箭头{return _ptr;}auto_ptr<T> operator++() //加加重载{++_ptr;return *this;}
unique_ptr
unique_ptr的用法
unique_ptr相交于auto_ptr更加严谨, 它相对于auto_ptr做了一下改变:
两块指针不能指向同一块资源(否则在释放空间时多次释放空间报错)。 同时它也不能赋值(注意, 右值可以赋值,但是右值赋值后, 如果该右值为一个左值临时转化的, 那么使用赋值后和auto_ptr的效果一样)
左值赋值:
右值赋值:
要注意的坑点就是不能一块资源给多个对象赋值, 不然会报错:
auto_ptr也一样
这是unique_ptr和auto_ptr中使用的坑。 后面的share_ptr解决了这个坑点(引用计数)
unique_ptr的模拟实现
有前面auto_ptr的基础, 这里unique_ptr的细节不再讲解, 只讲解重要的部分:
//首先模板类:
template<class T>class unique_ptr {private:T* _ptr; //管理资源};
//然后就是主要的地方 , unique_ptr不能进行赋值, 所以要将拷贝构造和赋值重载封起来。
template<class T>class unique_ptr {public:unique_ptr(T* ptr):_ptr(ptr) {}private:unique_ptr(unique_ptr<T>& ) = delete; //将拷贝构造删掉unique_ptr<T>& operator=(unique_ptr<T>& ) = delete; //将赋值重载删掉private:T* _ptr;};
shared_ptr
shared_ptr的用法
shared_ptr解决了unique_ptr和auto_ptr的排他性, shared_ptr即使多个智能指针指向同一块空间也能正常工作。
shared_ptr采用了引用计数, 当一个新的share_ptr指针管理一块资源的时候, 引用计数就+1, 当一个shared_ptr指针过期时, 引用计数就-1。当一块资源的引用计数到0时, 这块资源就可以被释放。
1. use_count() : 获得当前资源的引用计数:
2.conductor: 有多种构造形式——直接赋值一块资源、 赋值一块数组资源、传送定制删除器。
定义如下:
//带有定制删除器
template<class U, class D>shared_ptr(U* p, D del)
//普通构造
template<class U>shared_ptr(U* p)
//数组构造
shared_ptr<T[]> p(new T[5]{1, 2, 3, 4, 5}); //据说是C++17后支持, 但是vsC++14也能跑
3.make_shared
make_shared可以用来分配一块空间并且初始化这块空间, 效率更加高效。make_shared是一个函数模板, 并且需要指定分配资源的类型, 如图:
shared_ptr的模拟实现(v1版本)
首先,类名, 解引用之类和其他智能指针相差不大, 这里不做赘述, 然后有区别的就是成员变量以及构造, 拷贝, 赋值。
首先看成员变量, 成员变量需要有一块空间来作为引用计数。
template<class T>class shared_ptr {private:T* _ptr;int* _pcount;};
*_pcount作为引用计数。
//构造
//引用计数ptrshared_ptr(T* ptr):_ptr(ptr) {*_pcount = 1;}
//拷贝构造
//引用计数地拷贝构造shared_ptr(shared_ptr<T>& ptr) :_ptr(ptr._ptr),_pcount(ptr._pcount){++(*_pcount);}
//析构
~shared_ptr() {if (--(*_pcount) == 0) {delete _ptr;delete _pcount;}}
赋值, 这里赋值要先将原本的管理的资源取消托管, 那么引用计数就要减一, 还要判断引用计数是否为0, 为0就要释放资源。
shared_ptr<T>& operator=(shared_ptr<T>& p) {if (_ptr != p._ptr) //如果不判断, 当自己给自己赋值的时候, 自己会先将自己的资源释放, 然后就变成了野指针。 自己再给自己赋值一个野指针。 就会报错。{if (--(*_pcount) == 0){delete _ptr;delete _pcount;}//_ptr = p._ptr;_pcount = p._pcount;++(*_pcount);}return *this;}
shared_ptr中的坑
1. 其实, shared_ptr还有一种情况同样具有排他性, 和unique_ptr、auto_ptr一样, 当没有调用拷贝构造, 而是直接使用构造函数的时候, 引用计数不回加一, 那么就会多次释放资源。 这个无法避免。
2.第二个坑就是循环引用的问题,为了方便观察, 我们使用我们自己定义的shared_ptr进行测试, 现在看如下一个例子:
#include"share_ptr.h" //自己写的shared_ptr头文件
struct chicken; //前置声明struct fish //定义一个鱼类对象, 里面有一个鸡的智能指针实例
{cws_RAII::shared_ptr<chicken> _chicken;
};struct chicken //定义一个鸡类对象, 里面有一个鱼的智能指针实例。
{cws_RAII::shared_ptr<fish> _fish;
};int main()
{cws_RAII::shared_ptr<fish> f1(new fish);cws_RAII::shared_ptr<chicken> c1(new chicken);(*f1)._chicken = c1;//(*c1)._fish = f1;return 0;
}
在当前状态下, 我们如果运行程序, f1的资源和c1的资源可以被释放
但是如过图中(*c1)._fish = f1取消注释, 那么f1的资源和c1的资源就不能被释放。
这是为什么?
其实, 这就是循环引用的问题。 在这里面, 如果只定义了f1和c1. 这个时候是这样的:
但是如果给执行两条赋值语句后, 就变成了这样:
这个时候当f1和c1的生命周期结束时, f1的_pcount--, c1的_pcount--。 这两个_pcount都只能变成1, 变不成0, 所以不能释放资源。
所以, 我们在使用shared_ptr时, 要避免交叉赋值的情况。否则会出现内存泄漏。
weak_ptr
weak_ptr是用来解决shared_ptr的循环引用问题。 当两个类需要交叉进行赋值的时候, 类中所定义的智能指针可以使用weak_ptr(原本使用shared_ptr), 因为weak_ptr不会增加资源的引用计数。
weak_ptr的用法:
weak_ptr本人觉得最重要的一点就是可以和shared_ptr进行相互转化:
int main()
{shared_ptr<fish> f2(new fish);weak_ptr<fish> f3(f2); //将一个shared_ptr给给f3f3 = f2; //将shared_ptr赋值给weak_ptrf2 = f3.lock(); //将一个weak_ptr使用lock接口转化为shared_ptr; return 0;
}
同时, 还可以构造出一个空指针:
int main()
{weak_ptr<fish> f3; //构造一个空指针return 0;
}
但是, wear_ptr不支持解引用以及->
weak_ptr也能使用use_count函数查看引用计数:
weak_ptr的模拟实现
template<class T>class weak_ptr {public://不支持RAII, 也就是不能初始化管理资源weak_ptr(const shared_ptr<T>& ptr) {_ptr = ptr._ptr; //这里可以将weak_ptr弄成shared_ptr的友元, 就能访问私有_ptr_pcount = ptr._pcount;}weak_ptr<T>& operator=(const shared_ptr<T>& ptr) {_ptr = ptr._ptr;_pcount = ptr._pcount;return *this;}int use_count() {return (*_pcount);}T* get() {return _ptr;}private:T* _ptr;int* _pcount;};
shared_ptr中的定制删除器
在shared_ptr中, 可以管理一块连续的数组空间, 也可以管理一个单独的一块空间。 这两种资源类型需要不同的销毁方法。 单独的使用delete, 而数组空间需要使用delete[], 库里面的默认是使用delete, 但是如果我们想使用delete[]来销毁一块数组空间。 或者我们使用shared_ptr管理一块文件, shared_ptr生命周期结束时关闭文件。 那么就需要我们自己传一个定制删除器。
定制删除器是放在如图所示红框框的代码块中:
定制删除器的用法:
如何实现定制删除器?
其实定制删除器就是添加一个模板类, 如图:
下面是shared_ptr完整的版本(v2)
template<class T>class shared_ptr {public://引用计数ptrshared_ptr(T* ptr = nullptr):_ptr(ptr) ,_pcount(new int(0)){if (_ptr != nullptr) {*_pcount = 1;}}//引用计数地拷贝构造shared_ptr(shared_ptr<T>& ptr) :_ptr(ptr._ptr),_pcount(ptr._pcount){++(*_pcount);}//添加一个带有定制删除器的构造函数template<class D> //D就是定制删除器的模板类shared_ptr(T* ptr, D del):_Del(del), _ptr(ptr) {}void destroy() {if (--(*_pcount) == 0) {_Del; //将定制删除器放在这就好了}}~shared_ptr() {destroy();}//赋值shared_ptr<T>& operator=(shared_ptr<T>& p) {if (_ptr != p._ptr) {destroy();//_ptr = p._ptr;_pcount = p._pcount;++(*_pcount);}return *this;}T* operator->() {return _ptr;}T& operator*() {return *_ptr;}T* _ptr;int* _pcount;function<void(T*)> _Del = [](T*) {delete _Del};};
智能指针坑点
在看完大佬们的博客之后, 本人也总结了一些智能指针的 “坑点“, 这个坑点其实都是围绕 原生指针 展开的。
第一个:原生指针不能用来初始化智能指针, 否则两个智能指针指向同一块资源, 引用计数不增加。智能指针过期时会报错
第二个:get获得的原生指针, 不能delete掉, 否则智能指针在过期后还会delete。会报错
第三个:get获得的原生指针,也是原生指针, 不能初始化另一个智能指针。
第四个:release返回后的原生指针要被delete掉。 否则内存泄漏。
----以上, 就是本篇全部内容。
相关文章:

C++——探索智能指针的设计原理
前言: RAII是资源获得即初始化, 是一种利用对象生命周期来控制程序资源地手段。 智能指针是在对象构造时获取资源, 并且在对象的声明周期内控制资源, 最后在对象析构的时候释放资源。注意, 本篇文章参考——C 智能指针 - 全部用法…...

办公效率新高度:利用办公软件实现文件夹编号批量复制与移动,轻松管理文件
在数字化时代,我们的工作和生活都围绕着海量的数据和文件展开。然而,随着数据量的不断增加,如何高效地管理这些数字资产成为了摆在我们面前的一大难题。今天,我要向您介绍一种革命性的方法——利用办公软件实现文件夹编号批量复制…...

Windows kubectl终端日志聚合(wsl+ubuntu+cmder+kubetail)
Windows kubectl终端日志聚合 一、kubectl终端日志聚合二、windows安装ubuntu子系统1. 启用wsl支持2. 安装所选的 Linux 分发版 三、ubuntu安装kubetail四、配置cmder五、使用 一、kubectl终端日志聚合 k8s在实际部署时,一般都会采用多pod方式,这种情况下…...

【MySQL】数据库——事务
一.事务概念 事务是一种机制、一个操作序列,包含了一组数据库操作命令,并且把所有的命令作为一个整体一起向系统提交或撤销操作请求,即这一组数据库命令要么都执行,要么都不执行事务是一个不可分割的工作逻辑单元,在数…...
python代码缩进规范(2空格或4空格)
C、C、Java、C#、Rust、Go、JavaScript 等常见语言都是用"{“和”}"来标记一个块作用域的开始和结束,而Python 程序则是用缩进来表示块作用域的开始和结束: 作用域是编程语言里的一个重要的概念,特别是块作用域,编程语言…...

前后端分离的后台管理系统开发模板(带你从零开发一套自己的若依框架)上
前言: 目前,前后端分离开发已经成为当前web开发的主流。目前最流行的技术选型是前端vue3后端的spring boot3,本次。就基于这两个市面上主流的框架来开发出一套基本的后台管理系统的模板,以便于我们今后的开发。 前端使用vue3ele…...

【C++ | 委托构造函数】委托构造函数 详解 及 例子源码
😁博客主页😁:🚀https://blog.csdn.net/wkd_007🚀 🤑博客内容🤑:🍭嵌入式开发、Linux、C语言、C、数据结构、音视频🍭 🤣本文内容🤣&a…...
iCloud邮件全攻略:设置与使用终极指南
标题:iCloud邮件全攻略:设置与使用终极指南 摘要 iCloud邮件是Apple提供的一项邮件服务,允许用户在所有Apple设备上访问自己的邮件。本文将详细介绍如何在各种设备和邮件客户端上设置和使用iCloud邮件账户,确保用户能够充分利用…...

【计算机毕业设计】基于微信小程序的电子购物系统的设计与实现【源码+lw+部署文档】
包含论文源码的压缩包较大,请私信或者加我的绿色小软件获取 免责声明:资料部分来源于合法的互联网渠道收集和整理,部分自己学习积累成果,供大家学习参考与交流。收取的费用仅用于收集和整理资料耗费时间的酬劳。 本人尊重原创作者…...
CSS实现动画
CSS实现动画主要有三种方式:transition,transform,和animation1。以下是一些详细的逻辑,实例和注意事项: Transition:transition可以为一个元素在不同状态之间切换的时候定义不同的过渡效果。例如ÿ…...

Python+Pytest+Allure+Yaml+Jenkins+GitLab接口自动化测试框架详解
PythonPytestAllureYaml接口自动化测试框架详解 编撰人:CesareCheung 更新时间:2024.06.20 一、技术栈 PythonPytestAllureYamlJenkinsGitLab 版本要求:Python3.7.0,Pytest7.4.4,Allure2.18.1,PyYaml6.0 二、环境配置 安装python3.7&…...

[OtterCTF 2018]Bit 4 Bit
我们已经发现这个恶意软件是一个勒索软件。查找攻击者的比特币地址。** 勒索软件总喜欢把勒索标志丢在显眼的地方,所以搜索桌面的记录 volatility.exe -f .\OtterCTF.vmem --profileWin7SP1x64 filescan | Select-String “Desktop” 0x000000007d660500 2 0 -W-r-…...

计算机视觉全系列实战教程 (十四):图像金字塔(高斯金字塔、拉普拉斯金字塔)
1.图像金字塔 (1)下采样 从G0 -> G1、G2、G3 step01:对图像Gi进行高斯核卷积操作(高斯滤波)step02:删除所有的偶数行和列 void cv::pyrDown(cv::Mat &imSrc, //输入图像cv::Mat &imDst, //下采样后的输出图像cv::Si…...
正确重写equals和hashcode方法
1. 重写的原因 如有个User对象如下: public class User {private String name;private Integer age; }如果不重写equals方法和hashcode方法,则: public static void main(String[] args) {User user1 new User("userA", 30);Us…...

数据质量管理-时效性管理
前情提要 根据GB/T 36344-2018《信息技术 数据质量评价指标》的标准文档,当前数据质量评价指标框架中包含6评价指标,在实际的数据治理过程中,存在一个关联性指标。7个指标中存在4个定性指标,3个定量指标; 定性指标&am…...
python 实例002 - 数据转换
题目: 有一组用例数据如下: cases [[case_id, case_title, url, data, excepted],[1, 用例1, www.baudi.com, 001, ok],[4, 用例4, www.baudi.com, 002, ok],[2, 用例2, www.baudi.com, 002, ok],[3, 用例3, www.baudi.com, 002, ok],[5, 用例5, www.ba…...

1.k8s:架构,组件,基础概念
目录 一、k8s了解 1.什么是k8s 2.为什么要k8s (1)部署方式演变 (2)k8s作用 (3)Mesos,Swarm,K8S三大平台对比 二、k8s架构、组件 1.k8s架构 2.k8s基础组件 3.k8s附加组件 …...

动态规划基础练习
我们需要先从数组较大的开始进行处理,每次考察上下左右的,比较当前存储的最大值和转移来的值,哪一个大一点 #define _CRT_SECURE_NO_WARNINGS #include<bits/stdc.h> using namespace std;int n, m; int a[105][105]; int addx[] { 0,…...

基于Java的地方废物回收机构管理系统
你好呀,我是计算机学姐码农小野!如果有相关需求,可以私信联系我。 开发语言:Java 数据库:MySQL 技术:Java技术,MIS的总体思想,MySQL数据库 工具:Eclipse,…...
Leetcode 450:删除二叉搜索树中的节点
给定一个二叉搜索树的根节点 root 和一个值 key,删除二叉搜索树中的 key 对应的节点,并保证二叉搜索树的性质不变。返回二叉搜索树(有可能被更新)的根节点的引用。 一般来说,删除节点可分为两个步骤: 首先…...
设计模式和设计原则回顾
设计模式和设计原则回顾 23种设计模式是设计原则的完美体现,设计原则设计原则是设计模式的理论基石, 设计模式 在经典的设计模式分类中(如《设计模式:可复用面向对象软件的基础》一书中),总共有23种设计模式,分为三大类: 一、创建型模式(5种) 1. 单例模式(Sing…...
进程地址空间(比特课总结)
一、进程地址空间 1. 环境变量 1 )⽤户级环境变量与系统级环境变量 全局属性:环境变量具有全局属性,会被⼦进程继承。例如当bash启动⼦进程时,环 境变量会⾃动传递给⼦进程。 本地变量限制:本地变量只在当前进程(ba…...

聊聊 Pulsar:Producer 源码解析
一、前言 Apache Pulsar 是一个企业级的开源分布式消息传递平台,以其高性能、可扩展性和存储计算分离架构在消息队列和流处理领域独树一帜。在 Pulsar 的核心架构中,Producer(生产者) 是连接客户端应用与消息队列的第一步。生产者…...

家政维修平台实战20:权限设计
目录 1 获取工人信息2 搭建工人入口3 权限判断总结 目前我们已经搭建好了基础的用户体系,主要是分成几个表,用户表我们是记录用户的基础信息,包括手机、昵称、头像。而工人和员工各有各的表。那么就有一个问题,不同的角色…...

Springcloud:Eureka 高可用集群搭建实战(服务注册与发现的底层原理与避坑指南)
引言:为什么 Eureka 依然是存量系统的核心? 尽管 Nacos 等新注册中心崛起,但金融、电力等保守行业仍有大量系统运行在 Eureka 上。理解其高可用设计与自我保护机制,是保障分布式系统稳定的必修课。本文将手把手带你搭建生产级 Eur…...

Psychopy音频的使用
Psychopy音频的使用 本文主要解决以下问题: 指定音频引擎与设备;播放音频文件 本文所使用的环境: Python3.10 numpy2.2.6 psychopy2025.1.1 psychtoolbox3.0.19.14 一、音频配置 Psychopy文档链接为Sound - for audio playback — Psy…...

视频行为标注工具BehaviLabel(源码+使用介绍+Windows.Exe版本)
前言: 最近在做行为检测相关的模型,用的是时空图卷积网络(STGCN),但原有kinetic-400数据集数据质量较低,需要进行细粒度的标注,同时粗略搜了下已有开源工具基本都集中于图像分割这块,…...

招商蛇口 | 执笔CID,启幕低密生活新境
作为中国城市生长的力量,招商蛇口以“美好生活承载者”为使命,深耕全球111座城市,以央企担当匠造时代理想人居。从深圳湾的开拓基因到西安高新CID的战略落子,招商蛇口始终与城市发展同频共振,以建筑诠释对土地与生活的…...

Git 3天2K星标:Datawhale 的 Happy-LLM 项目介绍(附教程)
引言 在人工智能飞速发展的今天,大语言模型(Large Language Models, LLMs)已成为技术领域的焦点。从智能写作到代码生成,LLM 的应用场景不断扩展,深刻改变了我们的工作和生活方式。然而,理解这些模型的内部…...

从 GreenPlum 到镜舟数据库:杭银消费金融湖仓一体转型实践
作者:吴岐诗,杭银消费金融大数据应用开发工程师 本文整理自杭银消费金融大数据应用开发工程师在StarRocks Summit Asia 2024的分享 引言:融合数据湖与数仓的创新之路 在数字金融时代,数据已成为金融机构的核心竞争力。杭银消费金…...