当前位置: 首页 > news >正文

c++源码阅读__smart_ptr__正文阅读

文章目录

    • 简介
    • 源码
    • 解析
      • 1. 引用计数的实现方式
      • 2. deleter静态方法的赋值时间节点
      • 3.make_smart的实现方式 与 好处
      • 4. 几种构造函数
        • 4.1 空构造函数
        • 4.2 接收指针的构造函数
        • 4.3 接收指针和删除方法的构造函数 , 以及auto进行模板lambda的编写
        • 4.4 拷贝构造函数
        • 4.5 赋值运算符
      • 5. release函数, 指针的delete 和 设置为nullptr
      • 6. 获取内部变量, 指针和引用

简介

  • git地址: shimachao/smart_ptr
  • 说明: 这是一个仿写shared_ptr的库, 只有简短的200行, 实现了shared_ptr的大部分功能, 上手简单, 非常适合新手阅读.
  • 本文说明: 由于本项目没有那么多难点, 就不再像上篇开源代码阅读中c++源码阅读__ThreadPool__正文阅读一样, 不单独开一个前提知识部分了, 由于本项目比较简单, 大家就当作对shared_ptr的一次复习. 看一乐呵就行.

源码

此代码在c++20环境下直接可以运行, 源码如下

#pragma once#include <functional>// 模仿shared_ptr实现一个智能指针
template <typename T>
class smart_ptr
{
public:smart_ptr();explicit smart_ptr(T*);smart_ptr(const smart_ptr&);smart_ptr(T*, std::function<void(T*)>);smart_ptr& operator=(const smart_ptr&);T& operator*() const;T* operator->() const;~smart_ptr();// 向bool的类型转换explicit operator bool() const;bool unique();void reset();void reset(T*);void reset(T*, std::function<void(T*)>);T* release();T* get() const;private:// 默认的deleterstatic std::function<void(T*)> default_del;private:unsigned* m_p_use_count = nullptr;T* m_pobject = nullptr;std::function<void(T*)> m_del = default_del;
};template <typename T>
std::function<void(T*)> smart_ptr<T>::default_del = [](T*p) {delete p; p = nullptr; };template <typename T, typename... Args>
smart_ptr<T> make_smart(Args&&... args)
{smart_ptr<T> sp(new T(std::forward<Args>(args)...));return sp;
}template <typename T>
smart_ptr<T>::smart_ptr():m_pobject(nullptr), m_p_use_count(new unsigned(1))
{
}template <typename T>
smart_ptr<T>::smart_ptr(T *p):m_pobject(p), m_p_use_count(new unsigned(1))
{
}template <typename T>
smart_ptr<T>::smart_ptr(T *p, std::function<void(T*)> del):m_pobject(p), m_p_use_count(new unsigned(1)), m_del(del)
{
}template <typename T>
smart_ptr<T>::smart_ptr(const smart_ptr& rhs):m_pobject(rhs.m_pobject), m_p_use_count(rhs.m_p_use_count), m_del(rhs.m_del)
{(*m_p_use_count)++;
}template <typename T>
smart_ptr<T>& smart_ptr<T>::operator =(const smart_ptr &rhs)
{// 使用rhs的deleterm_del = rhs.m_del;// 递增右侧运算对象的引用计数++(*rhs.m_p_use_count);// 递减本对象的引用计数if (--(*m_p_use_count) == 0){// 如果管理的对象没有其他用户了,则释放对象分配的成员m_del(m_pobject);delete m_p_use_count;}m_p_use_count = rhs.m_p_use_count;m_pobject = rhs.m_pobject;return *this; // 返回本对象
}template <typename T>
T& smart_ptr<T>::operator*() const
{return *m_pobject;
}template <typename T>
T* smart_ptr<T>::operator->() const
{return &this->operator*();
}template <typename T>
smart_ptr<T>::~smart_ptr()
{if (--(*m_p_use_count) == 0){m_del(m_pobject);m_pobject = nullptr;delete m_p_use_count;m_p_use_count = nullptr;}
}template <typename T>
bool smart_ptr<T>::unique()
{return *m_p_use_count == 1;
}template <typename T>
void smart_ptr<T>::reset()
{(*m_p_use_count)--;if (*m_p_use_count == 0){m_del(m_pobject);}m_pobject = nullptr;*m_p_use_count = 1;m_del = default_del;
}template <typename T>
void smart_ptr<T>::reset(T* p)
{(*m_p_use_count)--;if (*m_p_use_count == 0){m_del(m_pobject);}m_pobject = p;*m_p_use_count = 1;m_del = default_del;
}template <typename T>
void smart_ptr<T>::reset(T *p, std::function<void(T*)> del)
{reset(p);m_del = del;
}template <typename T>
T* smart_ptr<T>::release()
{(*m_p_use_count)--;if (*m_p_use_count == 0){*m_p_use_count = 1;}auto p = m_pobject;m_pobject = nullptr;return p;
}template <typename T>
T* smart_ptr<T>::get() const
{return m_pobject;
}template <typename T>
smart_ptr<T>::operator bool() const
{return m_pobject != nullptr;
}

解析

这里会把smart_ptr类中每一部分都单独拿出来说明, 并进行举例

1. 引用计数的实现方式

我们可以看到他的实现方式

unsigned* m_p_use_count = nullptr;

使用的是整数的指针, 如果不使用指针的话, 那么是打不到公用引用计数的效果的, 如下

int* a1 = new int(1);
int* a2 = a1;
*a2 += 1;
cout << *a1 << endl;
cout << *a2 << endl;int a3 = 1;
int a4 = a3;
a3 += 1;
cout << a4 << endl;
cout << a4 << endl;int a5 = 1;
int& a6 = a5;
a5++;
cout << a5 << endl;
cout << a6 << endl;

执行结果
在这里插入图片描述
那么我们是不是也可以用引用&来实现引用计数的共用呢? 我觉得是可以的

2. deleter静态方法的赋值时间节点

template <typename T>
class smart_ptr
{
private:// 默认的deleterstatic std::function<void(T*)> default_del;std::function<void(T*)> m_del = default_del;
}
template <typename T>
std::function<void(T*)> smart_ptr<T>::default_del = [](T*p) {delete p; p = nullptr; };

静态方法的赋值, 是 程序启动并进入主函数之前进行赋值的, 具体地这个初始化是在包含这行代码的翻译单元被加载时完成的, 所以 default_del在任何smart_ptr的构造函数调用之前就赋值了.
m_del的赋值是在smart_ptr的构造函数被调用时赋值的

3.make_smart的实现方式 与 好处

template <typename T, typename... Args>
smart_ptr<T> make_smart(Args&&... args)
{smart_ptr<T> sp(new T(std::forward<Args>(args)...));return sp;
}

这里右值引用完美转发相关的知识在左值右值, 左值引用右值引用,完美转发这篇博客中有详细介绍.
这里和shared_ptr一样, 也是接收目标类的构造函数的参数, 直接返回智能指针的实现方式
好处: 我们使用make_smart的方式, 创建智能指针非常好, 因为不用我们手动new一个指针出来, 那么智能指针外部就没有该指针的变量, 就不会造成一些未知错误, 保证了在指针的生命周期内, 都是被智能指针安全管理的.

4. 几种构造函数

常用的构造函数和赋值运算符 在这篇文章里有介绍
C++基础知识,对象移动,拷贝构造函数,移动拷贝构造函数,赋值运算符,移动赋值运算符

4.1 空构造函数
template <typename T>
smart_ptr<T>::smart_ptr():m_pobject(nullptr), m_p_use_count(new unsigned(1))
{}

这个方法是构造一个内容为空的智能指针, 声明方式如下
smart_prt<MyClass> sp();
这个可以配合后面的reset方法使用, 给智能指针重新赋值

4.2 接收指针的构造函数
template <typename T>
smart_ptr<T>::smart_ptr(T *p):m_pobject(p), m_p_use_count(new unsigned(1))
{}

使用方式如下, 但是不推荐这种方式, 因为外部有了指针变量s1, 这就给智能指针的管理带来了未知的风险

MyStruct* s1 = new MyStruct(1, 2);
smart_ptr<MyStruct> sp(s1);
4.3 接收指针和删除方法的构造函数 , 以及auto进行模板lambda的编写
template <typename T>
smart_ptr<T>::smart_ptr(T *p, std::function<void(T*)> del):m_pobject(p), m_p_use_count(new unsigned(1)), m_del(del)
{}

这个构造方法就是上面4.2多加一个参数, 没什么好说的, 但是其中delete方法的传入, 我们可以通过auto来编写模板lambda, 这是c++新的特性, 如下

auto deleter = [](auto* p) {delete *p; p = nullptr;};
MyStruct* s = new MyStruct(1, 2);
smart_ptr<MyStruct> sp(s, deleter);

这样, 我们就不用template写一大堆, 而直接构造出了一个模板函数

4.4 拷贝构造函数

拷贝构造函数, 会造成引用计数+1, 两个智能指针指向的是同一块地址

template <typename T>
smart_ptr<T>::smart_ptr(const smart_ptr& rhs) :m_pobject(rhs.m_pobject), m_p_use_count(rhs.m_p_use_count), m_del(rhs.m_del)
{(*m_p_use_count)++;
}

=符号的赋值, 是赋值的内容, 不是变量本身的地址设置为一致(这是引用的=), 所以指针的=, 是指针变量内指向的地址设置为相同

MyStruct* s1 = new MyStruct(1, 2);
smart_ptr<MyStruct> sp(s1);
smart_ptr<MyStruct> sp2(sp);
cout << std::boolalpha;
cout << (sp.get() == sp2.get()) << endl;

执行结果
在这里插入图片描述

4.5 赋值运算符

赋值运算符 和 拷贝构造函数就大不一样了, 赋值运算符会把等号左边的智能指针内的变量引用计数-1, 如果引用计数为0了, 还会释放资源

template <typename T>
smart_ptr<T>& smart_ptr<T>::operator =(const smart_ptr &rhs)
{// 使用rhs的deleterm_del = rhs.m_del;// 递增右侧运算对象的引用计数++(*rhs.m_p_use_count);// 递减本对象的引用计数if (--(*m_p_use_count) == 0){// 如果管理的对象没有其他用户了,则释放对象分配的成员m_del(m_pobject);delete m_p_use_count;}m_p_use_count = rhs.m_p_use_count;m_pobject = rhs.m_pobject;return *this; // 返回本对象
}

所以根据代码, 我们合理预测, 如果=左边的智能指针引用计数如果是1的话, 那么使用了赋值运算符, 就会造成内部的指针析构
测试代码

struct MyStruct
{MyStruct() = default;MyStruct(int a, int b) :a(a), b(b) {}~MyStruct() { cout << "~MyStruct ("  << a << "," << b << ")" << endl;}int a;int b;
};int main()
{smart_ptr<MyStruct> sp1 = make_smart<MyStruct>(1, 2);smart_ptr<MyStruct> sp2 = make_smart<MyStruct>(3, 4);sp1 = sp2;cout << "==========" << endl;return 0;
}

执行结果
在这里插入图片描述

可以看到, 是sp1在程序结束之前析构的

5. release函数, 指针的delete 和 设置为nullptr

这里我们要明确,delete是把指针指向的内容进行析构, 而直接把指针设置为nullptr, 只是把当前这个指针变量设置为nullptr, 对于指针指向的内容, 不做任何处理
比如release函数

template <typename T>
T* smart_ptr<T>::release()
{(*m_p_use_count)--;if (*m_p_use_count == 0){*m_p_use_count = 1;}auto p = m_pobject;m_pobject = nullptr;return p;
}

在这个函数里, 只是把m_pojbect这个指针变量本身设置为了nullptr, 而指针指向的变量没有做任何操作, 最后把这个指针变量copy一份, 返回出去了
测试代码

struct MyStruct
{MyStruct() = default;MyStruct(int a, int b) :a(a), b(b) {}~MyStruct() { cout << "~MyStruct ("  << a << "," << b << ")" << endl;}int a;int b;
};int main()
{smart_ptr<MyStruct> sp1 = make_smart<MyStruct>(1, 2);MyStruct* s1 = sp1.release();cout << "=========" << endl;delete s1;return 0;
}

执行结果 可以看到, release确实没有造成析构
在这里插入图片描述

6. 获取内部变量, 指针和引用

有两个方法, 一个是重载的操作符*, 一个是get方法
只不过get获取的指针
*获取的是引用

template <typename T>
T& smart_ptr<T>::operator*() const
{return *m_pobject;
}
template <typename T>
T* smart_ptr<T>::get() const
{return m_pobject;
}

测试代码

int main()
{smart_ptr<MyStruct> sp1 = make_smart<MyStruct>(1, 2);cout << std::boolalpha << endl;// (*sp1)获取了引用: &MyStruct, 转为指针 *(&MyStruct)cout << (&(*sp1) == sp1.get()) << endl;return 0;
}

执行结果
在这里插入图片描述

相关文章:

c++源码阅读__smart_ptr__正文阅读

文章目录 简介源码解析1. 引用计数的实现方式2. deleter静态方法的赋值时间节点3.make_smart的实现方式 与 好处4. 几种构造函数4.1 空构造函数4.2 接收指针的构造函数4.3 接收指针和删除方法的构造函数 , 以及auto进行模板lambda的编写4.4 拷贝构造函数4.5 赋值运算符 5. rele…...

图形化界面MySQL(MySQL)(超级详细)

1.官网地址 MySQL :: Download MySQL Workbench 1.1在Linux直接点击NO thanks..... 下载完后是这个页面 1.2任何远端登录&#xff0c;再把jj数据库给授权 1.3建立新用户 进行连接 点击这个就运行了 只执行show tables&#xff1b;要先选中 圆圈处支持自己输入 点击这个就执…...

【2024 Optimal Control 16-745】Julia语法

Lecture 2 θ和它的导数符号是通过 Julia 中的变量命名方式实现的 变量 θ 的输入&#xff1a; 在 Julia 中&#xff0c;θ 是一个合法的变量名&#xff0c;就像普通的字母 x 或 y 一样。要输入 θ&#xff0c;可以使用以下方法&#xff1a; 在 Jupyter Notebook 或 Julia REP…...

Opencv+ROS实现摄像头读取处理画面信息

一、工具 ubuntu18.04 ROSopencv2 编译器&#xff1a;Visual Studio Code 二、原理 图像信息 ROS数据形式&#xff1a;sensor_msgs::Image OpenCV数据形式&#xff1a;cv:Mat 通过cv_bridge()函数进行ROS向opencv转换 cv_bridge是在ROS图像消息和OpenCV图像之间进行转…...

网络安全,文明上网(2)加强网络安全意识

前言 在当今这个数据驱动的时代&#xff0c;对网络安全保持高度警觉已经成为每个人的基本要求。 网络安全意识&#xff1a;信息时代的必备防御 网络已经成为我们生活中不可或缺的一部分&#xff0c;信息技术的快速进步使得我们对网络的依赖性日益增强。然而&#xff0c;网络安全…...

深度学习实战图像缺陷修复

这里写目录标题 概述1. 图像缺陷修复的研究背景2. 传统图像缺陷修复方法的局限性(1) 基于纹理合成的方法(2) 基于偏微分方程&#xff08;PDE&#xff09;的方法 3. 深度学习在图像缺陷修复中的兴起(1) 深度学习的基本思路(2) 深度学习方法的优势(3) 关键技术的引入 4. 深度学习…...

jenkins 2.346.1最后一个支持java8的版本搭建

1.jenkins下载 下载地址&#xff1a;Index of /war-stable/2.346.1 2.部署 创建目标文件夹&#xff0c;移动到指定位置 创建一个启动脚本&#xff0c;deploy.sh #!/bin/bash set -eDATE$(date %Y%m%d%H%M) # 基础路径 BASE_PATH/opt/projects/jenkins # 服务名称。同时约定部…...

【数据库原理】创建与维护表,DDL数据定义语言

数据描述语言&#xff08;数据定义语言&#xff09; 就是管理数据库整个库&#xff0c;整个表&#xff0c;表的属性列的语句。 常用词儿就是数据库或表的增删改查&#xff1a;CREATE创建、DROP删除、ALTER修改、SHOW查看、USE进入表。 表的字段控制&#xff1a;PRIMARY KEY主键…...

驾驭Go语言中的不确定性:深入错误处理机制

驾驭Go语言中的不确定性:深入错误处理机制 在Go语言的编程世界中,错误处理是确保程序健壮性的关键。Go语言通过显式的错误返回值和panic/recover机制,提供了一套独特的错误处理策略。本文将深入探讨Go语言中的错误处理,包括原理、技术细节和实际案例,帮助读者在实际编程中…...

3D Gaussian Splatting在鱼眼相机中的应用与投影变换

paper:Fisheye-GS 1.概述 3D 高斯泼溅 (3DGS) 因其高保真度和实时渲染而备受关注。然而,由于独特的 3D 到 2D 投影计算,将 3DGS 适配到不同的相机型号(尤其是鱼眼镜头)带来了挑战。此外,基于图块的泼溅效率低下,尤其是对于鱼眼镜头的极端曲率和宽视野,这对于其更广泛…...

【Unity踩坑】在Mac上安装Cocoapods失败

在集成Unity Ad时&#xff0c;如果是第一次在iOS上集成&#xff0c;会在Mac上安装Cocoapods。 安装时提示下面的错误&#xff1a; Error installing cocoapods:The last version of drb (> 0) to support your Ruby & RubyGems was 2.0.5. Try installing it with gem…...

uni-app 认识条件编译,了解多端部署

一. 前言 在使用 uni-app 进行跨平台开发的过程中&#xff0c;经常会遇到需要针对不同平台或不同环境进行条件编译的情况。条件编译是一种在编译过程中根据指定条件选择不同代码路径的技术&#xff0c;可以帮助我们在不同平台或环境下编写不同的代码&#xff0c;以适应不同的平…...

SPA 首屏加载慢的原因及解决方案:结合实际项目的详细讲解

在现代前端开发中,单页面应用程序 (SPA) 的首屏加载速度是用户体验的关键因素之一。首屏加载慢会直接影响用户对网站的第一印象,甚至导致用户流失。因此,优化首屏加载速度是每个前端开发者需要重点关注的内容。 1. 什么是首屏加载? 首屏加载指的是用户访问一个网站或应用…...

vue3+ts el-tabel 搜索组件

爷爷页面 <template> <searchstyle"z-index: 9999":options"options"placeholder"请选择时间&#xff0c;或输入名称、单选、多个勾选、模糊查询"search"onSearch"></search> </template> <script lan…...

leetcode 排序算法汇总

快速排序 def quicksort(arr): if len(arr) < 1: return arr else: pivot arr[len(arr) // 2] # 选择中间值作为基准 left [x for x in arr if x < pivot] # 小于基准的放左边 middle [x for x in arr if x pivot] # 等…...

【C】错误的变量定义导致sprintf()‌输出错误

问题描述 刚刚写一个用AT指令透传相关的函数&#xff0c;需要用到sprintf()‌拼接字符串。 结果发现sprintf()‌拼接出来的内容是错误的&#xff0c;简化后的代码如下&#xff1a; const char AT_CIPSEND_FIX_LENGTH_HEADER[11] "ATCIPSEND"; // 错误的&#xff0…...

python基础导包

Python项目代码结构与导包详解 目录 引言 Python项目的基本结构 2.1 单文件项目2.2 多模块项目2.3 包结构项目2.4 示例项目结构 模块与包 3.1 模块&#xff08;Module&#xff09;3.2 包&#xff08;Package&#xff09;3.3 子包&#xff08;Subpackage&#xff09; 导包&a…...

【含开题报告+文档+PPT+源码】基于SSM的电影数据挖掘与分析可视化系统设计与实现

开题报告 随着互联网的普及和数字娱乐产业的蓬勃发展&#xff0c;电影作为一种重要的娱乐方式&#xff0c;已经深入人们的日常生活。然而&#xff0c;面对海量的电影资源&#xff0c;用户在选择观影内容时常常感到困惑和无所适从。传统的电影推荐方式&#xff0c;如人工筛选、…...

strlwr(arr);的模拟实现(c基础)

hi , I am 36 适合对象c语言初学者 strlwr(arr)&#xff1b;函数是把arr数组变为小写字母,并返回arr 链接介绍一下strlwr(arr)&#xff1b;(c基础)-CSDN博客 下面进行My__strlwr(arr);模拟实现 #include<stdio.h> //返回值为arr(地址),于是用指针变量,原数组为字符型…...

LCR 002. 二进制求和

一.题目&#xff1a; . - 力扣&#xff08;LeetCode&#xff09; 二.原始解法-利用二进制逢二进一&#xff1a; 自己实现的时候忽略了一点&#xff0c;就是进位是会滚动的&#xff0c;不是进位一次就结束&#xff0c;很复杂跳过 三.正确解法及好的讲解、力扣解法参考&#xf…...

告别繁琐输入:基于SmartConfig与微信的ESP8266/ESP32一键配网实战

1. 为什么我们需要一键配网技术&#xff1f; 每次拿到新的智能设备&#xff0c;最头疼的就是怎么把它连上家里的Wi-Fi。传统的配网方式通常需要你在手机App里手动输入Wi-Fi名称和密码&#xff0c;这个过程不仅繁琐&#xff0c;还容易出错。想象一下&#xff0c;你要给10个智能灯…...

Audio2Face深度解析:音频驱动面部动画的技术架构与实战指南

Audio2Face深度解析&#xff1a;音频驱动面部动画的技术架构与实战指南 【免费下载链接】FACEGOOD-Audio2Face http://www.facegood.cc 项目地址: https://gitcode.com/gh_mirrors/fa/FACEGOOD-Audio2Face 在虚拟数字人快速发展的今天&#xff0c;如何让虚拟角色拥有自然…...

安卓应用按钮样式问题及解决方案

在开发安卓应用的过程中,我们常常会遇到一些看似简单但实际上隐藏着复杂问题的样式问题。今天我们来探讨一个在更换设备后按钮样式发生变化的问题。 问题描述 一位开发者在Android Studio中开发了一个食谱应用。当他从一台手机切换到另一台手机运行应用时,发现所有的按钮都…...

从HC-SR04老用户视角,实测2020新版:盲区更小、功耗更低,但这两点不注意容易翻车

HC-SR04新版深度评测&#xff1a;老用户必看的5个升级细节与3个隐藏陷阱 第一次拿到2020版HC-SR04时&#xff0c;我差点以为发错了货——外观几乎和老版本一模一样&#xff0c;连螺丝孔位都分毫不差。但当我用示波器捕捉到仅2.1mA的工作电流时&#xff0c;才确信这确实是用上了…...

FPGA新手避雷指南:你的第一个呼吸灯项目可能卡在这几个Vivado仿真和引脚分配问题上

FPGA新手避雷指南&#xff1a;从仿真到引脚分配的完整呼吸灯实战 第一次在FPGA上实现呼吸灯效果&#xff0c;本该是充满成就感的时刻。但当你按照教程一步步操作&#xff0c;点击"Generate Bitstream"后&#xff0c;板子上的LED却毫无反应——这种挫败感我太熟悉了。…...

3步解锁Windows运行安卓应用:APK-Installer轻量解决方案

3步解锁Windows运行安卓应用&#xff1a;APK-Installer轻量解决方案 【免费下载链接】APK-Installer An Android Application Installer for Windows 项目地址: https://gitcode.com/GitHub_Trending/ap/APK-Installer 在数字化办公与娱乐融合的今天&#xff0c;安卓应用…...

Youtu-VL-4B-Instruct-GGUF模型安全考量:在网络安全领域的潜在应用与风险

Youtu-VL-4B-Instruct-GGUF模型安全考量&#xff1a;在网络安全领域的潜在应用与风险 最近和几个做安全的朋友聊天&#xff0c;他们都在头疼一个问题&#xff1a;现在的网络攻击越来越“花里胡哨”了。以前可能就是一段恶意代码&#xff0c;现在呢&#xff1f;一张精心设计的钓…...

500+精选RSS源如何解决信息获取难题:Awesome RSS Feeds全解析

500精选RSS源如何解决信息获取难题&#xff1a;Awesome RSS Feeds全解析 【免费下载链接】awesome-rss-feeds Awesome RSS feeds - A curated list of RSS feeds (and OPML files) used in Recommended Feeds and local news sections of Plenary - an RSS reader, article dow…...

如何彻底解决文献格式混乱?Zotero格式规范化处理工具的创新方案

如何彻底解决文献格式混乱&#xff1f;Zotero格式规范化处理工具的创新方案 【免费下载链接】zotero-format-metadata Linter for Zotero. A plugin for Zotero to format item metadata. Shortcut to set title rich text; set journal abbreviations, university places, and…...

2026年,山东专业联想服务器解决方案,涵盖SR858 V3等众多型号!

在当今数字化飞速发展的时代&#xff0c;服务器作为企业数据处理和存储的核心设备&#xff0c;其性能和可靠性至关重要。联想服务器凭借其卓越的性能、丰富的功能和广泛的应用场景&#xff0c;成为众多企业的首选。今天&#xff0c;我们就来详细了解一下联想SR858 V3服务器。联…...