c++中的智能指针详解(RAII, auto_ptr的原理及其改进,unique_ptr的原理,shared_ptr的原理,shared_ptr的缺陷及其改进)

为什么需要智能指针

代码中途退出,也能保证资源的合理释放,在c++中没有垃圾回收机制的情况下,智能指针就可以保证我们申请的资源,最后忘记释放的问题,防止内存泄露,也帮我们减少了一定的负担,不用再在所有可能退出的地方都进行是否释放的检测。

RAII

RAII(Resource Acquisition Is Initialization)是一种利用对象生命周期来控制程序资源(如内存、文件句 柄、网络连接、互斥量等等)的简单技术
在对象构造时获取资源,接着控制对资源的访问使之在对象的生命周期内始终保持有效,最后在对象析构的 时候释放资源。借此,我们实际上把管理一份资源的责任托管给了一个对象

两个好处
  1. 不需要显式地释放资源。
  2. 采用这种方式,对象所需的资源在其生命期内始终保持有效。

利用RAII的思想创建一个smartptr类替我们管理资源

template<class T>class smartptr
{
public:smartptr( T* ptr = nullptr):_ptr(ptr){}~smartptr(){if (_ptr){delete _ptr;}}
private:T *_ptr;
};void TestSmartPtr()
{smartptr<int>sp(new int);throw 1;
}int  main()
{try{TestSmartPtr();}catch (int err){cout << err << endl;}system("pause");return 0;
}

这个类虽然能利用自身的生命周期帮助我们管理资源的释放,但它本身还不能称作为一个智能指针,因为它并不能解引用,所以,我们要重载解引用运算符*和箭头运算符->

T& operator*() //重载解引用运算符{return *_ptr; //返回当前内容}
T* operator->(){return &(operator*()); //把指针所指向空间的地址返回}
struct A
{int a;int b;int c;
};
void TestSmartPtr()
{smartptr<int>sp1(new int);*sp1 = 100;cout << *sp1 << endl;smartptr<A>sp2(new A);//我们自己的这个类,也可以用指针的方式访问数据sp2->a = 10;sp2->b = 20;sp2->c = 30;throw 1;
}

这基本就是一个简单的智能指针,但是这个“指针”有一个巨大的缺陷,它并没有处理浅拷贝问题

smartptr<A>sp3(sp2);

在这里插入图片描述
sp3和sp2他们管理的资源都是一样的,但是我们这里并不能用深拷贝的方式来解决这个问题,因为资源是外部的用户提供的,本身写的类并没有申请空间的权力

智能指针基本原理

RAII(资源可以自动释放)+operator*()/operator->()(当成指针使用)+解决浅拷贝

C++98提供的 auto_ptr

RAII(资源可以自动释放)+operator*()/operator->()(当成指针使用)+资源管理权限的转移

资源管理权限的转移的图解

在这里插入图片描述

namespace bite  //自己写一个命名空间,为了防止和系统冲突
{//auto_ptr 利用资源管理权限的转移来解决浅拷贝template<class T>class auto_ptr{//RALLpublic:auto_ptr(T *_ptr = nullptr):_ptr(_ptr){}//资源的转移auto_ptr(auto_ptr<T>& ap) //要修改内容,所以不用加const:_ptr(ap._ptr){ap._ptr = nullptr;}~auto_ptr(){if (_ptr){delete _ptr;}}//像指针一样的方式使用T& operator*(){return *_ptr;}T* operator->(){//将指针所指向空间的地址返回就可以return &(operator*());}protected:T *_ptr;};
}void TestAuto_ptr()
{bite::auto_ptr<int>ap1(new int);bite::auto_ptr<int>ap2(ap1);
}

在这里插入图片描述
此时我们可以看到,ap1和ap2已经不再指向同一块内存空间了,但是赋值操作也是一个浅拷贝,所以我们还要重载赋值运算符

auto_ptr<T>& operator=(auto_ptr<T>& ap){if (this != &ap){  //是不是自己给自己赋值if (_ptr)     //当前对象有没有管理资源delete _ptr;  //如果有,释放_ptr = ap._ptr;  //当前对象接受ap的资源ap._ptr = nullptr; //ap 指空}return *this;}

在这里插入图片描述

缺陷

因为auto_ptr用的是资源的转移,所以不能同时对两个对象进行操作,就像下面这样
在这里插入图片描述

改进后的auto_ptr

//改进:资源管理权转移
namespace bite  //自己写一个命名空间,为了防止和系统冲突
{//auto_ptr 利用资源管理权限的转移来解决浅拷贝template<class T>class auto_ptr{//RAIIpublic:auto_ptr(T *_ptr = nullptr):_ptr(_ptr), _owner(false){if (_ptr)delete _ptr;}//资源的转移auto_ptr(auto_ptr<T>& ap) //要修改内容,所以不用加const:_ptr(ap._ptr), _owner(ap._owner){ap._owner = false;}auto_ptr<T>& operator=(auto_ptr<T>& ap){if (this != &ap){  //是不是自己给自己赋值if (_ptr && _owner)     //当前对象有没有管理资源的权限delete _ptr;  //如果有,释放_ptr = ap._ptr;  //当前对象接受ap的资源_owner = ap._owner;ap._owner = false;}return *this;}~auto_ptr(){if (_ptr && _owner){delete _ptr;_owner = false;}}//像指针一样的方式使用T& operator*(){return *_ptr;}T* operator->(){//将指针所指向空间的地址返回就可以return &(operator*());}protected:T *_ptr;bool _owner; //资源真正的管理权限};
}void TestAuto_ptr()
{bite::auto_ptr<int>ap1(new int);bite::auto_ptr<int>ap2(ap1);ap1 = ap2;
}int main()
{TestAuto_ptr();system("pause");return 0;
}

但是这个改进后又有一个更大的缺陷???,就是造成野指针,所以还是尽量不要用这个智能指针

c++ 11 unique_ptr

一个对象独占资源

实现原理

  1. unique_ptr 禁止调用拷贝构造函数
  2. unique_ptr 禁止调用赋值运算符重载
  3. 完成一份资源被一个对象独占
  4. c++98 中的方式:拷贝构造函数以及赋值运算符重载只声明不定义,访问权限设置为private,为了防止友员,不能定义,只能声明
  5. c++11 中的方式:通过delet 禁止编译器生成默认成员函数
unique_ptr(const unique_ptr<T>&) = delete;
unique_ptr<T>& operator=(const unique_ptr<T>&) = delete;

实现

namespace bite
{template<class T>class unique_ptr{public:unique_ptr(T *ptr = nullptr):_ptr(ptr){}~unique_ptr(){if (_ptr)  //如果提供资源delete _ptr; //释放掉资源}//具有指针的行为T &operator*(){return *_ptr;}T *operator->(){return _ptr;}///c++98:拷贝构造函数以及赋值运算符重载只声明不定义,访问权限设置为private//如果定义,如果用户把函数当成这个类的友员函数的话,私有权限就不复存在/*private:unique_ptr(const unique_ptr<T>&);unique_ptr<T>& operator=(const unique_ptr<T>&)*///c++11:将默认成员函数删除掉//delete:用来释放new申请出来的空间//       禁止编译器生成默认的成员函数unique_ptr(const unique_ptr<T>&) = delete;unique_ptr<T>& operator=(const unique_ptr<T>&) = delete;private:T * _ptr;};
}

c++ 11:shared_ptr

共享资源
RALL+operator()/operator->()+引用计数*

实现原理

shared_ptr的原理:是通过引用计数的方式来实现多个shared_ptr对象之间共享资源。

  1. shared_ptr在其内部,给每个资源都维护了着一份计数,用来记录该份资源被几个对象共享。
  2. 在对象被销毁时(也就是析构函数调用),就说明自己不使用该资源了,对象的引用计数减一。
  3. 如果引用计数是0,就说明自己是最后一个使用该资源的对象,必须释放该资源;
  4. 如果不是0,就说明除了自己还有其他对象在使用该份资源,不能释放该资源,否则其他对象就成野指 针了

模拟实现

namespace bite
{template<class T>class shared_ptr{public:shared_ptr(T *ptr = nullptr):_ptr(ptr), _pCount(nullptr){if (_ptr){_pCount = new int(1);}}~shared_ptr(){if (_ptr && 0 == --*_pCount){delete _ptr;delete _pCount;}}T & operator*(){return *_ptr;}T * operator->(){return _ptr;}//采用引用计数的方式解决浅拷贝的问题shared_ptr(const shared_ptr<T>&sp):_ptr(sp._ptr), _pCount(sp._pCount){++ *_pCount;}//sp2 = sp1//sp2 自己是否有资源---->没有----->直接共享//                       有 1.资源的计数是1   2.计数>1(多个资源共享)shared_ptr<T>& operator=(const shared_ptr<T>&sp){if (this != &sp)  //如果不是自己给自己赋值{//将this从资源中分离开if (_ptr  && 0 == --*_pCount){delete _ptr;delete _pCount;}//共享资源_ptr = sp._ptr;_pCount = sp._pCount;++*_pCount;}return *this;}private:T *_ptr;int *_pCount;};
}void TestSharedPtr()
{bite::shared_ptr<int >sp1(new int);bite::shared_ptr<int>sp2(sp1);//当前对象没有资源bite::shared_ptr<int>sp3;sp3 = sp2;//当前对象独立拥有资源bite::shared_ptr<int>sp4(new int);sp4 = sp3;bite::shared_ptr<int>sp5(new int);bite::shared_ptr<int>sp6(sp5);sp5 = sp4;
}

可以看到他们共用一份资源,计数也正常,sp6用的是sp5的资源,计数变成1了。释放的时候从后往前释放

在这里插入图片描述

只是我们现在实现的智能指针有一些缺陷

缺陷

  1. 不能管理任意类型的指针,比如:文件指针 析构函数:资源采用delete,而文件需要fclose进行关闭,但我们的析构函数已经写死了
  2. 没有保证线程安全
  3. 存在循环引用----怎么解决?

解决任意类型的指针

定制删除器

//new出来资源的释放
template<class T>
struct DFDel
{void operator()(T*& ptr){if (ptr){delete ptr;ptr = nullptr;}}
};
//malooc出来的资源的释放
template<class T>
struct Free
{void operator()(T*& ptr){if (ptr){free(ptr);ptr = nullptr;}}
};//处理文件指针的释放
struct Fclose
{void operator()(FILE *&pf){if (pf){fclose(pf);pf = nullptr;}}
};
namespace bite
{template < class T, class DF = DFDel<T>>class shared_ptr{public:shared_ptr(T *ptr = nullptr):_ptr(ptr), _pCount(nullptr){if (_ptr){_pCount = new int(1);}}~shared_ptr(){Release();}T & operator*(){return *_ptr;}T * operator->(){return _ptr;}//采用引用计数的方式解决浅拷贝的问题shared_ptr(const shared_ptr<T>&sp):_ptr(sp._ptr), _pCount(sp._pCount){++ *_pCount;}//sp2 = sp1//sp2 自己是否有资源---->没有----->直接共享//                       有 1.资源的计数是1   2.计数>1(多个资源共享)shared_ptr<T>& operator=(const shared_ptr<T>&sp){if (this != &sp)  //如果不是自己给自己赋值{//将this从资源中分离开if (_ptr && 0 == --*_pCount){Release();}//共享资源_ptr = sp._ptr;_pCount = sp._pCount;++*_pCount;}return *this;}private:void Release(){if (_ptr && 0 == --*_pCount){//delete _ptr;DF()(_ptr);  //无名对象delete _pCount;}}private:T *_ptr;int *_pCount;};
}void TestSharedPtr()
{bite::shared_ptr<int>sp1(new int);bite::shared_ptr<int,Free<int>>sp2((int *)malloc(sizeof(int)));bite::shared_ptr<FILE,Fclose>sp3(fopen(("List.h"), "r"));
}int main()
{TestSharedPtr();return 0;
}

解决线程安全

  1. 智能指针对象中引用计数是多个智能指针对象共享的,两个线程中智能指针的引用计数同时++--,这 个操作不是原子的,引用计数原来是1,++了两次,可能还是2.这样引用计数就错乱了。会导致资源未 释放或者程序崩溃的问题。所以只能指针中引用计数++、–是需要加锁的,也就是说引用计数的操作是 线程安全的。
  2. 智能指针管理的对象存放在堆上,两个线程中同时去访问,会导致线程安全问题。

我们需要加锁解决

#include<mutex>
namespace bite
{template < class T, class DF = DFDel<T>>class shared_ptr{public:shared_ptr(T *ptr = nullptr):_ptr(ptr), _pCount(nullptr), _pMutex(nullptr){if (_ptr){_pMutex = new mutex;_pCount = new int(1);}}~shared_ptr(){Release();}T & operator*(){return *_ptr;}T * operator->(){return _ptr;}//采用引用计数的方式解决浅拷贝的问题shared_ptr(const shared_ptr<T>&sp):_ptr(sp._ptr),_pCount(sp._pCount), _pMutex(sp._pMutex){AddRef();}//sp2 = sp1//sp2 自己是否有资源---->没有----->直接共享//                       有 1.资源的计数是1   2.计数>1(多个资源共享)shared_ptr<T>& operator=(const shared_ptr<T>&sp){if (this != &sp)  //如果不是自己给自己赋值{//将this从资源中分离开if (_ptr && 0 == SubRef()){Release();}//共享资源_ptr = sp._ptr;_pCount = sp._pCount;AddRef();}return *this;}size_t use_count()const{return *_pCount;}T *get(){return _ptr;  //返回地址}private:void Release(){if (_ptr && 0 == SubRef()){//delete _ptr;DF()(_ptr);  //无名对象delete _pCount;if (_pMutex)delete _pMutex;}}void AddRef()   //引用计数变更时需要进行加锁和解锁{_pMutex->lock();++*_pCount;_pMutex->unlock();}int SubRef(){_pMutex->lock();--*_pCount;_pMutex->unlock();return *_pCount;}private:T *_ptr;int *_pCount;mutex* _pMutex;};
}

加的锁可以保证shared_ptr是安全的,但无法保证shared_ptr所指向的内容是不是安全的,如果想要保证,必须用户自己来进行保证

解决循环引用

什么是循环引用

我们来看这个代码

struct ListNode
{
public:ListNode(int data):_data(data), _pPre(nullptr), _pNext(nullptr){cout << "ListNode(int)" << this << endl;}~ListNode(){cout << "~ListNode(int):" << this << endl;}int _data;shared_ptr<ListNode>_pPre;shared_ptr<ListNode>_pNext;//ListNode * _pPre;//ListNode * _pNext;
};
void TestSharedPtr()
{shared_ptr<ListNode>sp1(new ListNode(10));shared_ptr<ListNode>sp2(new ListNode(20));//查看引用计数cout << sp1.use_count() << endl;cout << sp2.use_count() << endl;sp1->_pNext = sp2;sp2->_pPre = sp1;//查看引用计数cout << sp1.use_count() << endl;cout << sp2.use_count() << endl; 
}

在这里插入图片描述

解决方法

weak_ptr

标准库引入了新的智能指针weak_ptr:RAII+operator*()/operator->()+引用计数,原理跟shared_ptr一样

  • 作用
    专门解决shared_ptr存在得循环引用问题,除此之外,没有其他别得用途
  • 限制
    不能单独管理资源,必须与shared_ptr配合使用

所以

  1. 解决方案:在引用计数的场景下,把结点中的_prev_next改成weak_ptr就可以了
  2. 原理就是,node1->_next = node2;node2->_prev = node1;weak_ptr_next_prev不会增加 node1node2的引用计数。
struct ListNode
{
public:ListNode(int data):_data(data)//, _pPre(nullptr)//, _pNext(nullptr){cout << "ListNode(int)" << this << endl;}~ListNode(){cout << "~ListNode(int):" << this << endl;}int _data;weak_ptr<ListNode>_pPre;weak_ptr<ListNode>_pNext;//ListNode * _pPre;//ListNode * _pNext;
};void TestSharedPtr()
{shared_ptr<ListNode>sp1(new ListNode(10));shared_ptr<ListNode>sp2(new ListNode(20));//查看引用计数cout << sp1.use_count() << endl;cout << sp2.use_count() << endl;sp1->_pNext = sp2;sp2->_pPre = sp1;//查看引用计数cout << sp1.use_count() << endl;cout << sp2.use_count() << endl; 
}
int main()
{TestSharedPtr();return 0;
}

shared_ptr 原型

在编译器中,shared_ptr 就是根据下图的方式定义
在这里插入图片描述

C++11和boost中智能指针的关系

  1. C++ 98 中产生了第一个智能指针auto_ptr.
  2. C++ boost给出了更实用的scoped_ptr和shared_ptr和weak_ptr.
  3. C++ TR1,引入了shared_ptr等。不过注意的是TR1并不是标准版。
  4. C++ 11,引入了unique_ptr和shared_ptr和weak_ptr。需要注意的是unique_ptr对应boost的 scoped_ptr。并且这些智能指针的实现原理是参考boost中的实现的。

RAII解决死锁

RAII思想除了可以用来设计智能指针,还可以用来设计守卫锁,防止异常安全导致的死锁问题
在这里插入图片描述

#include <thread> 
#include <mutex>
// C++11的库中也有一个lock_guard,下面的LockGuard造轮子其实就是为了学习他的原理 template<class Mutex> 
class LockGuard 
{public:    LockGuard(Mutex& mtx)        :_mutex(mtx)    {        _mutex.lock();    }~LockGuard()  {        _mutex.unlock();    } LockGuard(const LockGuard<Mutex>&) = delete;private:    
// 注意这里必须使用引用,否则锁的就不是一个互斥量对象    Mutex& _mutex; 
};mutex mtx; 
static int n = 0;void Func() 
{    for (size_t i = 0; i < 1000000; ++i)    {        LockGuard<mutex> lock(mtx);       ++n;    }
}
int main() 
{    int begin = clock();    thread  t1(Func);    thread  t2(Func);t1.join();    t2.join();int end = clock();cout << n << endl;    cout <<"cost time:" <<end - begin << endl;       return 0; 
} 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/383161.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

二叉树题目----5 平衡二叉树 AND 根据二叉树创建字符串

平衡二叉树 /*** Definition for a binary tree node.* struct TreeNode {* int val;* struct TreeNode *left;* struct TreeNode *right;* };*/int MAX(int a,int b) {return a > b ? a : b; }//求高度 int getHeight(struct TreeNode *root){if(root NULL){…

再写堆(堆的性质,向下调整,建堆,堆的插入删除初始化,堆排序,TopK问题)

堆的概念 如果有一个关键码的集合K{k0,k1,k2,…,kn-1},把它的所有元素按完全二叉树的顺序存储方式存储再一个一维数组中&#xff0c;并满足:Ki<K2i1且Ki<K2i1(Ki > K2i1 且 Ki > K2i2),i0,1,2,3…。则称为小堆(或大堆)。将根结点最大的堆叫做最大堆或大根堆&#…

二叉树题目----6 二叉树的最近公共祖先 AND 二叉树搜索树转换成排序双向链表

二叉树的最近公共祖先 思路 在左、右子树中分别查找是否包含p或q&#xff1a;如果以下两种情况&#xff08;左子树包含p&#xff0c;右子树包含q/左子树包含q&#xff0c;右子树包含p&#xff09;&#xff0c;那么此时的根节点就是最近公共祖先如果左子树包含p和q&#xff0c;…

二叉树题目 ----7 前序中序遍历构造二叉树

前序中序遍历构造二叉树 思路 在前序中找根结点根据根结点 中序&#xff0c;分成左右两棵子树根据子树长度&#xff0c;把前序分成左右两颗子树递归处理子树 /*** Definition for a binary tree node.* struct TreeNode {* int val;* struct TreeNode *left;* s…

关联式容器(map,set,multimap,multiset)

关联式概念 STL中的部分容器&#xff0c;比如&#xff1a;vector、list、deque、forward_list(C11)等&#xff0c;这 些容器统称为序列式容器&#xff0c;因为其底层为线性序列的数据结构&#xff0c;里面存储的是元素本身。关联式容器也是用来存储数据的&#xff0c;与序列式…

详解 二叉搜索树-----AVL树

二叉搜索树 根结点比左子树中所有结点都大根结点比右子树所有结点都小最小的元素在最左侧最大的元素在最右侧中序遍历有序 具有以上的特征的二叉树就是二叉搜索树也叫二叉排序数 二叉搜索树的操作 查找 要保存查找值的双亲&#xff0c;以便于后续执行插入操作 Node* Find(…

json格式与cJSON函数库

json的格式 键/值对 key:value&#xff0c;用半角冒号分割文档对象 JSON对象写在花括号中&#xff0c;可以包含多个键/值对。数组 JSON 数组在方括号中书写&#xff1a; 数组成员可以是对象&#xff0c;值&#xff0c;也可以是数组(只要有意义)。 { "stars":[ { &qu…

HTTP清晰的学习笔记

HTTP协议—应用层 请求消息(Request)—浏览器给服务器发 包含四部分 请求行&#xff1a;说明请求类型&#xff0c;要访问的资源&#xff0c;以及使用的http版本请求头&#xff1a;说明服务器要使用的附加信息,由键值对构成的空行&#xff1a;空行是必须要有的&#xff0c;即…

如何在 Centos7 x86_64下将vim一键配置为一款强大的C++,IDE

vim功能很强大&#xff0c;但是对于我这样的小白很不友好 然后就有一位大佬&#xff0c;为了辅助我这样的菜鸡&#xff0c;然后供我们一键搞定&#xff0c;在vim畅快编写代码的功能。 首先强烈不推荐在root用户下使用&#xff0c;确保电脑连着网。在普通用户下执行此命令 cur…

详解string容器(应用+模拟实现,string练习题)

为什么要有string容器 string&#xff1a;其实就是一个字符串,c对字符串进行了封装的&#xff0c;封装到一个类里面&#xff0c;这样用户就不用担心开辟空间的问题&#xff0c;只需要往string类里放字符串就可以了&#xff0c;string其实还可以自增长 很多人就会有一个疑问&am…

浅拷贝+引用计数--写时拷贝---模拟实现string容器

引用计数 深拷贝 多个对象共享同一份资源时&#xff0c;最后能够保证该资源只被释放一次 应该由哪个对象释放资源&#xff1f; 由最后一个使用该资源的对象去释放 怎么知道一个对象是最后一个使用该资源的对象&#xff1f; 给一个计数&#xff0c;记录使用该资源对象的个数 实…

详解vector容器(应用+模拟实现,vector相关练习题)

vector容器 动态的顺序表&#xff0c;数组。 vector操作 vector操作及其概念 构造 vector<int>v1;vector<int>v2(10, 5);vector<int>v3(v2);int array[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };vector<int>v4(array, array sizeof(array) / sizeof(a…

详解list容器(应用+模拟实现)

list容器 带头结点的双向循环链表 list操作 list容器的概念及其操作 构造和销毁 list<int>L1;list<int>L2(10, 5);vector<int>v{ 1, 2, 3, 4, 5, 6, 7, 8, 9 };list<int>L3(v.begin(), v.end());list<int>L4(L3);元素访问 cout << L3.…

vector和list容器有哪些区别

这个问题的本质还是在问顺序表和链表的区别 底层结构不同 vector容器list容器一段连续的空间带头结点的双向循环链表 元素访问方式 vector容器list容器支持随机访问—O(1)不支持随机访问—O(N)需要扩容不需要扩容任意位置插入元素----O(N)–搬移元素O(1) 迭代器不同 vector…

复习栈和队列,详解最小栈,栈的弹出压入序列,逆波兰表达式求值

栈和队列的概念 栈:吃进去吐出来 对列&#xff1a;吃进去拉出来 数据结构中的栈和内存中的区别 数据结构中的栈具有后进先出的特性&#xff0c;而内存中的栈是一个内存空间&#xff0c;只不过这个内存空间具与数据结构的栈具有相同的特性。 栈和队列操作 栈和队列基本操作…

详解优先级队列priority_queue(应用+模拟实现)

优先级队列的概念 优先队列是一种容器适配器&#xff0c;根据严格的弱排序标准&#xff0c;它的第一个元素总是它所包含的元素中最大的此上下文类似于堆&#xff0c;在堆中可以随时插入元素&#xff0c;并且只能检索最大堆元素(优先队列中位于顶部的元 素)。优先队列被实现为容…

私人博客定制

项目背景 可行性方面 需求分析&#xff1a; 详细设计&#xff1a; 数据库设计 博客管理API的设计 标签相关API 服务器端的实现 对数据库操作进行封装 对服务器操作进行封装 客户端实现 具体操作 使用markdown 具体实现 测试 项目效果展示 维护 完整代码 项目…

初识c++中的函数模板

函数模板 函数模板概念 函数模板:编译器生成代码的一个规则。函数模板代表了一个函数家族&#xff0c;该函数模板与类型无关&#xff0c;在使用时被参数化&#xff0c;根据实参类型产生函数的特定类型版本。 函数模板格式 //要让这个函数与类型无关 //Add函数模板 template…

深入理解c++中的函数模板

非类型模板参数 模板参数分类类型形参与非类型形参。 类型形参&#xff1a;出现在模板参数列表中&#xff0c;跟在class或者typename之类的参数类型名称。 非类型形参&#xff0c;就是用一个常量作为类(函数)模板的一个参数&#xff0c;在类(函数)模板中可将该参数当成常量来使…

c++中的IO流

c语言中的IO操作 标准类型的输入输出: 输入------>数据来源是通过键盘进行输入输出------>程序中的数据输出到控制台 c语言中: scanf:输入 printf:输出 两个函数的相同点 1 —格式串 2 —不定参数 两个函数的缺陷 1 —用户要提供数据的格式—用户要记忆大量的格式串—…