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

C++11:lambda表达式 包装器

C++11:lambda表达式 & 包装器

    • lambda表达式
    • 包装器
      • function
      • bind


lambda表达式

在C++98中,如果想对一个结构体数组使用sort排序,那么我们就需要自己些仿函数。

比如以下结构体:

struct Goods
{string _name; // 名字double _price; // 价格int _evaluate; // 评价Goods(const char* str, double price, int evaluate):_name(str), _price(price), _evaluate(evaluate){}
};

如果我们希望以价格排序,就可以写出如下仿函数:

struct ComparePriceLess
{bool operator()(const Goods& gl, const Goods& gr){return gl._price < gr._price;}
};struct ComparePriceGreater
{bool operator()(const Goods& gl, const Goods& gr){return gl._price > gr._price;}
};

随着C++语法的发展,人们开始觉得上面的写法太复杂了,每次为了实现一个algorithm算法,都要重新去写一个类,如果每次比较的逻辑不一样,还要去实现多个类,特别是相同类的命名,这些都给编程者带来了极大的不便。因此,在C++11语法中出现了Lambda表达式

lambda语法如下:

[capture_list] (parameters) mutable -> return_type {statement}

这个语法看起来比较复杂,我先简单讲拆分一下各个部分:

  • [capture_list]:捕捉列表
  • (parameters):参数列表
  • mutable:一个关键字
  • -> return_type:返回值类型
  • {statement}: 函数体

比如这是一个完整的lambda表达式:

auto add = [](int a, int b)mutable -> int { return a + b; };

很明显的看出,以上函数就是传入两个整数,然后返回两数之和。

lambda表达式有很多种省略情况

  1. muteble可以省略,改关键字的具体功能后续讲解
auto add = [](int a, int b)-> int { return a + b; };
  1. 函数的返回值-> return_type可以省略,lambda表达式可以自己推导返回类型
auto add = [](int a, int b) { return a + b; };
  1. 当函数没有参数时,(parameters)参数列表可以省略
auto say_hello = [] { cout << "hello world!" << endl; };

以上函数,就已经是一个非常简单的lambda表达式了。那么lambda表达式有什么用呢?

 lambda会返回一个仿函数对象

比如auto add = [](int a, int b) { return a + b; };,其实add就是一个仿函数对象了,我们可以直接按照调用函数的方式来调用这个仿函数:add(1, 2);。但是要注意, lambda表达式返回的仿函数对象,其类名是随机的,因此必须使用auto来接受这个仿函数对象

现在我们再讲讲lambda表达式最前面的[]的作用,其名称为捕获列表,可以捕获父作用域中所有变量

比如这样:

int x = 1;
int y = 2;auto add = [x, y] {return x + y; };

以上代码中,[x, y]就是在捕获父作用域中的两个变量,那么函数体中就可以直接使用这两个变量了。如果直接通过变量名捕获,此时是传值调用,修改函数体内部的变量,不会影响父作用域的变量

但是通过直接传值捕获的变量,自带const属性,不允许修改,比如以下代码:

int x = 1;
int y = 2;auto add = [x, y] {x += 5;y += 5;};

此时代码就会报错,因为xy是通过捕获列表捕获的变量,传入的参数带有const属性,不允许修改。此时就要用到mutable了,mutable可以让被捕获的参数可以修改。

auto add = [x, y] mutable{x += 5;y += 5;};

但是这个写法还是错误的,如果使用了mutable,就算没有通过参数列表传参,()也不可以省略:

auto add = [x, y] () mutable{x += 5;y += 5;};

我们也可以以传引用的方式来捕获变量,只需要在变量名前加上&操作符:

int x = 1;
int y = 2;auto add = [&x, &y]{x += 5;y += 5;};

此时修改函数内部的xy,就是在修改父作用域的xy了。这里要注意,如果使用了传引用捕获变量,就算没有mutable也可以修改参数

另外的,lambda还提供了一次性捕获所有父作用域变量的语法,只需要在捕获列表中写=即可:

int x = 1;
int y = 2;auto add = [=]{return x + y;};

[=]就是一次性捕获了所有父作用域变量的过程,我们可以直接在函数体内部使用父作用域的所有变量。

不过[=]是以传值的形式捕获父作用域所有变量,而[&]是以传引用的形式捕获父作用域所有变量:

int x = 1;
int y = 2;auto add = [&]{x += 5;y += 5;};

另外的,我们还可以把传值和传引用混合使用,让部分参数传参,部分参数传引用。

[x, &y]:以传值的形式捕获x,以传引用的形式捕获y
[=, &x]:以传值的形式捕获父作用域所有变量,以传引用的形式捕获x
[&, x]:以传值的形式捕获x,以传引用的形式捕获父作用域所有变量

接下来我再次汇总一下lambda的语法:

各个部分:

  • [capture_list]:捕捉列表,可以捕获父作用域的任意变量,有传参和传引用两种形式
  • (parameters):参数列表,如果没有参数可以省略
  • mutable:如果以传参形式捕获参数,不可修改参数,加上该关键字后可以修改
  • -> return_type:返回值类型,可以省略,lambda会自动推导
  • {statement}: 函数体,不可省略

有了lambda表达式后,我们在需要仿函数的地方,就无需额外写一个仿函数的类,而是直接写一个lambda表达式,比如最开始的按照价格排序:

vector<Goods> v = { { "苹果", 2.1, 5 }, { "香蕉", 3, 4 }, { "橙子", 2.2, 3 }, { "菠萝", 1.5, 4 } };sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2) {return g1._price < g2._price; });sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2) {return g1._price > g2._price; });

因为省略了返回值,我们以比函数还简短的方式完成了仿函数的书写。

但是有一个情况,那就是模板参数中的lambda表达式。

如果我们想要给一个优先级队列priority_queue传入一个less仿函数:

priority_queue<int, vector<int>, less<int>> q;

其中less<int>就是我们的仿函数,但是less<int>不是仿函数实例化出的对象,而是一个仿函数类型。也就是说,模板参数中需要的不是仿函数对象,而是仿函数类型。但是lambda表达式整体返回的类型是仿函数对象,因此以下写法是错误的:

priority_queue<int, vector<int>, [](const int& i1, const int& i2) {return i1 - i2; } > q;

我们不能直接把lambda当作模板参数传入,此时就要使用decltype来推导原先的类型:

auto intLess = [](const int& i1, const int& i2) {return i1 - i2; };
priority_queue<int, vector<int>, decltype(intLess)> q;

包装器

在寄快递的时候,快递会进行一次包装,这样我们就可以统一的在上面贴上快递信息,随后以统一的形式管理所有快递。包装器也是如此,包装器可以将具有相似属性的东西包装起来成为一个整体。

function

如果一个变量f,可以按照f()的形式调用函数,那么称f是一个可调用对象

回顾一下,现在我们有那些可调用对象

  1. 函数指针,函数名(函数名的本质就是函数指针)
  2. 仿函数实例化出的对象
  3. lambda表达式

这三者,都可以直接加一对()进行函数调用。它们都有各自的缺点:

  • 函数指针,函数名:类型复杂,不好用
  • 仿函数实例化出的对象:哪怕参数返回值都相同,仿函数之间的类型也不同
  • lambda表达式:类型是随机的,必须用auto接收

可以看到,这三者都有类型方面的大问题,我们也没有一种方式可以把所有参数类型和返回值类型相同的函数,统一的管理起来,让它们都变成一个类型?

包装器function就可以做到该工作,function被包含在头文件<functional>中,是一个类模板,模板原型如下:

template <class T> function;template <class Ret, class... Args>
class function<Ret(Args...)>;

其语法为:function<返回值(参数列表)>只要所有返回值和参数列表相同的可调用对象,经过这一层封装,都会变成相同的类型

比如我们现在有如下三个函数:

double func(double x)
{return x / 2;
}struct Functor
{double operator()(double x){return x / 3;}
};int main()
{auto lambadaFunc = [](double d) {return d / 4; };return 0;
}

分别是func函数,Functor仿函数,以及lambda表达式lambadaFunc 。它们的返回值都是double,参数类型也是double,因此可以经过包装器包装为function<double<double>>

如下:

function<double(double)> func1 = func;
function<double(double)> func2 = Functor();
function<double(double)> func3 = lambadaFunc;

此时,三者的类型就都是function<double(double)> 了。

有了这一层包装器,在需要统一管理函数时,就很方便了。比如说我现在要搞一个计算器的map,往map中输入哪一个操作符,就调用哪一个函数:

map<char, function<int(int, int)>> opFuncMap = {{'+', [](int x, int y) {return x + y; }},{'-', [](int x, int y) {return x - y; }},{'*', [](int x, int y) {return x * y; }},{'/', [](int x, int y) {return x / y; }}
};

由于+ - * /的函数都是lambda表达式,四个表达式的类型都是不可知的,map的第二个模板参数就不知道是啥了。不过我们可以通过function进行包装,把所有函数都包装成function<int(int, int)>类型,最后就可以通过map统一管理了。

我们最后就可以这样调用函数:

opFuncMap['+'](1, 2);
opFuncMap['-'](1, 2);
opFuncMap['*'](1, 2);
opFuncMap['/'](1, 2);

bind

bind翻译后为绑定,其可以对参数进行绑定。其主要有两个功能:改变参数顺序给指定参数绑定固定值

语法
bind是一个函数模板,其接收多个参数,第一个参数为可调用对象,后续参数为该可调用对象的参数。这个参数的语法比较特别,C++11后新增一个命名空间域placeholders,其内部会存储很多变量,这些变量用于函数的传参,变量的名字为_x表示第x个参数。

比如以下代码中:

int sub(int a, int b)
{return a - b;
}int main()
{auto f1 = bind(sub, placeholders::_2, placeholders::_1);f1(3, 5);return 0;
}

对于bind(sub, placeholders::_2, placeholders::_1);来说,sub这个参数是一个可调用对象。
placeholders::_2表示第二个参数,placeholders::_1表示第一个参数。

比如这个f1最后拿到了这个bind封装的函数,那么f1(3, 5)执行的并不是3 - 5,而是5 - 3

这是因为我们特地把placeholders::_2写在前面,f1(3, 5)把第二个5传给了placeholders::_2,把第一个3传给了placeholders::_1

而最后调用sub函数的时候,placeholders::_1会被传给sub的第一个参数,placeholders::_2则会传给sub的第而个参数。这样我们就完成了函数参数顺序的改变。

再比如以下代码:

int sub(int a, int b)
{return a - b;
}int main()
{auto f2 = bind(sub, 3.14, placeholders::_1);f2(10);return 0;
}

bind(sub, 3.14, placeholders::_1)第一个参数为可调用对象sub,第二个参数是一个固定值3.14,那么如果通过f2调用该sub函数,参数a都固定为3.14。比如f2(10)就只传了一个参数,再去调用sub时,就完成3.14 - 10的操作。因此我们可以通过sub把某个参数绑定为固定值。


相关文章:

C++11:lambda表达式 包装器

C11&#xff1a;lambda表达式 & 包装器 lambda表达式包装器functionbind lambda表达式 在C98中&#xff0c;如果想对一个结构体数组使用sort排序&#xff0c;那么我们就需要自己些仿函数。 比如以下结构体&#xff1a; struct Goods {string _name; // 名字double _pric…...

Node.js HTTP/2 CONTINUATION 拒绝服务漏洞(CVE-2024-27983)

Node.js 是开源、跨平台的 JavaScript 运行时环境。CONTINUATION泛洪攻击被发现存在于多个HTTP/2协议实现中。 在受影响版本中&#xff0c;由于Node.js针对HTTP/2协议的实现不当&#xff0c;未正确处理多个CONTINUATION帧的情况&#xff0c;在node::http2::Http2Session::~Htt…...

YOLOV8 + 双目测距

YOLOV8 双目测距 1. 环境配置2. 测距流程和原理2.1 测距流程2.2 测距原理 3. 代码部分解析3.1 相机参数stereoconfig.py3.2 测距部分3.3 主代码yolov8-stereo.py 4. 实验结果4.1 测距4.2 测距跟踪4.3 测距跟踪分割4.4 视频展示 相关文章 1. YOLOv5双目测距&#xff08;python&…...

前端:SVG绘制流程图

效果 代码 html代码 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><title>SVG流程图示例</title><style>/* CSS 样式 */</style><script src"js/index.js"></script…...

【Linux系列】如何确定当前运行的是 RHEL 9 还是 RHEL 8?

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…...

vscode开发java的插件和配置

推荐插件 .vscode/extensions.json {"recommendations": ["redhat.fabric8-analytics","ms-azuretools.vscode-docker","vscjava.vscode-java-pack","eamodio.gitlens","obkoro1.korofileheader","redhat.j…...

Mysql启动报错:本地计算机上的mysql服务启动后停止,某些服务在未由其他服务或程序使用时将自动停止

Mysql启动报错&#xff1a;本地计算机上的mysql服务启动后停止,某些服务在未由其他服务或程序使用时将自动停止 文章目录 Mysql启动报错&#xff1a;本地计算机上的mysql服务启动后停止,某些服务在未由其他服务或程序使用时将自动停止1. 备份mysql的data文件夹2. 重新构建 Wind…...

WPF程序添加托盘图标

程序添加托盘图标 UI层 //添加handycontrol的引用xmlns:hc"https://handyorg.github.io/handycontrol"//添加NotifyIcon图标 实现单击 双击 二级菜单点击功能<hc:NotifyIconText"通知"Token"Info"><hc:NotifyIcon.ContextMenu><…...

工业4g路由器联网后迅速掉线是什么原因?

工业4G路由器连接上网后迅速掉线可能是由多种因素造成的。以下是一些建议的检查和解决步骤&#xff1a; 1、信号问题&#xff1a; 信号强度&#xff1a;检查工业路由器信号强度指示灯&#xff0c;如果信号弱&#xff0c;尝试移动路由器位置或添加外部天线来增强信号。 网络拥…...

腾讯云4核8G服务器12M带宽646元1年零3个月,4C8G使用场景说明

腾讯云4核8G服务器多少钱&#xff1f;腾讯云4核8G轻量应用服务器12M带宽租用价格646元15个月&#xff0c;活动页面 txybk.com/go/txy 活动链接打开如下图所示&#xff1a; 腾讯云4核8G服务器优惠价格 这台4核8G服务器是轻量应用服务器&#xff0c;详细配置为&#xff1a;轻量4核…...

java - 读取配置文件

文章目录 1. properties2. XML(1) dom4j(2) XPath 1. properties // 创建properties对象用于读取properties文件Properties properties new Properties();properties.load(new FileReader("src/main/resources/test.properties"));String name properties.getPrope…...

Ubuntu22.04平台编译完美解决问题“error: GLSL 4.5 is not supported.”【GLSL(OpenGL着色器语言)】

GLSL介绍 GLSL&#xff08;OpenGL着色器语言&#xff09;是用于编写OpenGL着色器程序的语言。GLSL 4.5 是 GLSL 的一个版本&#xff0c;引入了许多新的特性和改进&#xff0c;旨在提高着色器编程的灵活性和性能。GLSL 4.5 工具通常是用于编写、调试和优化 GLSL 4.5 着色器代码…...

数据结构之搜索二叉树与关联性容器初接触

一、搜索二叉树 1>、前言 1. map和set特性需要先铺垫二叉搜索树&#xff0c;而二叉搜索树也是一种树形结构 2. 二叉搜索树的特性了解&#xff0c;有助于更好的理解map和set的特性。 2>、概念 二叉搜索树又称二叉排序树&#xff0c;它或者是一棵空树&#xff0c;或者…...

C语言整数和小数的存储

1.整数在内存中的存储 计算机使用二进制进行存储、运算&#xff0c;整数在内存中存储使用的是二进制补码 1.1原码、反码、补码 整数的2进制表⽰⽅法有三种&#xff0c;即 原码、反码和补码 三种表⽰⽅法均有符号位和数值位两部分&#xff0c;符号位都是⽤0表⽰“正”&am…...

Games101Homework【6】Acceleration structure(Including framework analysis)

Code Analysis&#xff1a; friend&#xff1a; C中友元&#xff08;友元函数和友元类&#xff09;的用法和功能_friend class a<b>-CSDN博客 [C&#xff1a;不如Coding]&#xff08;11&#xff09;&#xff1a;友元函数与友元类_哔哩哔哩_bilibili Here is a simple…...

应用运维文档1

统一nginx接入配置指南 Nginx配置规范 1:不带微服务编码上下文至后端,以metadata-ui为例 location段配置信息,location配置中维护微服务编码上下文信息 # app_code: metadata-ui 流水线名称: metadata-ui location ~ ^/metadata-ui/(?P.*) {set $app_code metadata-ui;p…...

手机如何在线制作gif?轻松一键在线操作

现在大家都喜欢使用手机来拍摄记录有趣的事物&#xff0c;但是时间长了手机里的视频越来越多导致手机存储空间不够了&#xff0c;这些视频又不想删除时应该怎么办呢&#xff1f;这个很简单&#xff0c;下面就给大家分享一款不用下载手机就能操作的视频转gif网站-GIF中文网&…...

ChatGPT 在做什么,为什么有效?

原文&#xff1a;What Is ChatGPT Doing … and Why Does It Work? 译者&#xff1a;飞龙 协议&#xff1a;CC BY-NC-SA 4.0 序言 这本简短的书试图从第一原理解释 ChatGPT 是如何工作的。在某种程度上&#xff0c;这是关于技术的故事。但它也是关于科学的故事。以及关于哲学…...

Linux实验2 初步使用shell

一&#xff1a;实验目的 学习Linux下的文件系统结构&#xff0c;了解最基本的Linux下的shell命令操作&#xff0c;例如ls, cd, cat等各种指令操作。 学习vim编辑器的使用方式&#xff0c;学习如何使用ssh连接远程服务器。 二&#xff1a;实验内容 1&#xff0e;请指出下面每…...

甘特图/横道图制作技巧 - 任务组

在甘特图中通过合理的任务分组可以让项目更加清晰&#xff0c;修改也更方便。 列如下面的甘特图一眼不太容易看清楚整体的进度。或者需要把所有的任务整体的延迟或者提前只能这样一个一个的任务调整&#xff0c;就比较麻烦。 通过给任务分组&#xff0c;看这上面整体的进度就…...

Web题记

反序列化补充知识&#xff1a; private变量会被序列化为&#xff1a;\x00类名\x00变量名 protected变量会被序列化为: \x00\*\x00变量名 public变量会被序列化为&#xff1a;变量名web254 这个逻辑不难&#xff0c;自己刚看的时候还奇怪是不是自己哪里想错了&#xff0c;因为…...

学习java第三十六天

Spring 官网列出的 Spring 的 6 个特征: 核心技术 &#xff1a;依赖注入(DI)&#xff0c;AOP&#xff0c;事件(events)&#xff0c;资源&#xff0c;i18n&#xff0c;验证&#xff0c;数据绑定&#xff0c;类型转换&#xff0c;SpEL。 测试 &#xff1a;模拟对象&#xff0c;…...

0205矩阵分块法-矩阵及其运算-线性代数

文章目录 1 分块矩阵的定义2 分块矩阵的运算&#xff08;性质&#xff09;3 按列分块与按行分块 结语 1 分块矩阵的定义 将矩阵A用若干条纵线和横线分成许多个小矩阵&#xff0c;每一个小矩阵称为A的子快&#xff0c;以子块为元素的形式上的矩阵称为分块矩阵。 2 分块矩阵的运算…...

1、java语法入门(找工作版)

文章目录 一、Java简介二、Java常量与变量1、标识符2、关键字3、变量4、类的命名规则5、数据类型6、基本数据类型字面值7、变量的定义与初始化8、ASCII码和Unicode编码9、转义字符10、类型转换11、常量 三、Java运算符1、算术运算符2、赋值运算符3、关系运算符4、逻辑运算符5、…...

arm的状态寄存器

目录 一、arm 的 PSRs二、CPSR2.1 CPSR_cxsf 三、SPSR四、APSR 一、arm 的 PSRs arm 中有很多程序状态寄存器&#xff08;Program Status Registers&#xff0c;PSRs&#xff09;用于存储处理器的状态信息&#xff0c;包括 CPSR\SPSR\FPSR\APSR 等&#xff1a; CPSR&#xff…...

2024 蓝桥打卡Day34

20240406蓝桥杯备赛 1、学习蓝桥云课省赛冲刺课 【1-手写与思维】【2-递归与递推】2、学习蓝桥云课Java省赛无忧班 【1-语言基础】3、代码练习字符串排序大小写转换 &#xff08;ccfcsp之前要是学了我就能上200了 啊啊啊啊 错过啊&#xff09;斐波那契数列 递归解法纸张尺寸问题…...

华为海思校园招聘-芯片-数字 IC 方向 题目分享——第九套

华为海思校园招聘-芯片-数字 IC 方向 题目分享&#xff08;有参考答案&#xff09;——第九套 部分题目分享&#xff0c;完整版获取&#xff08;WX:didadidadidida313&#xff0c;加我备注&#xff1a;CSDN huawei数字芯片题目&#xff0c;谢绝白嫖哈&#xff09; 单选 1&…...

如何创建虚拟环境打包py文件

Python 项目通常依赖于特定的库和版本。不同的项目可能依赖于相同库的不同版本&#xff0c;这可能导致冲突。使用虚拟环境&#xff0c;你可以为每个项目创建一个独立的 Python 环境&#xff0c;每个环境都有自己的库和版本&#xff0c;从而避免了依赖冲突。 采用虚拟环境打包P…...

CSS 学习笔记 总结

CSS 布局方式 • 表格布局 • 元素定位 • 浮动布局&#xff08;注意浮动的负效应&#xff09; • flex布局 • grid布局&#xff08;感兴趣的可以看下菜鸟教程&#xff09; 居中设置 元素水平居中 • 设置宽度后&#xff0c;margin设置为auto • 父容器设置text-alig…...

基于Swin Transformers的乳腺癌组织病理学图像多分类

乳腺癌的非侵入性诊断程序涉及体检和成像技术&#xff0c;如乳房X光检查、超声检查和磁共振成像。成像程序对于更全面地评估癌症区域和识别癌症亚型的敏感性较低。 CNN表现出固有的归纳偏差&#xff0c;并且对于图像中感兴趣对象的平移、旋转和位置有所不同。因此&#xff0c;…...

网站设置为起始页/软文文案范文

1 #以下代码是利用方法1 2 #-*- coding: utf-8 -*- 3 importrequests;4 importsys;5 importio;6 #重点&#xff1a;标准解析库 7 from bs4 importBeautifulSoup;8 sys.stdout io.TextIOWrapper(sys.stdout.buffer,encodingutf8); #改变标准输出的默认编码 9 #根据cookies访问后…...

网站关键词库是怎么做的/免费网页代码大全

总结&#xff0c;TCP/IP是传输层协议&#xff0c;主要解决数据如何在网路中传输&#xff0c;socket是TCP/IP协议的具体实现&#xff0c;是对TCP/IP协议的封装和应用&#xff0c;属于程序员层面&#xff0c;HTTP是应用层协议&#xff0c;应用层协议很多&#xff0c;类似的像HTTP…...

wordpress怎样修改页脚版权信息/可口可乐网络营销案例

前言&#xff1a;目的&#xff1a;&#xff08;vmware ubuntu16虚拟机&#xff09;由于新的任务需要&#xff0c;原有ubuntu虚拟机sda1的磁盘空间剩余太少&#xff0c;需要进行磁盘空间拓展。搜集了网上大多数的方案很多都不奏效&#xff08;有的方法本身不可行、有的情况与我的…...

网站统计系统 怎么做/360搜索引擎下载

Moved tohttp://blog.tangcs.com/2008/08/13/behavioral-design-patterns/ 转载于:https://www.cnblogs.com/WarrenTang/archive/2008/08/13/1266939.html...

做私活 网站/合肥网站seo

参考函数&#xff1a; substring_index(str, keyword, position)substring_index(被截取字符串&#xff0c;关键字&#xff0c;关键字的位置)PS&#xff1a;如果关键字不存在&#xff0c;则返回整个字符串。...

网站seo推广seo教程/杭州网站优化搜索

101、一套完整的测试应该由哪些阶段组成&#xff1f;分别阐述一下各个阶段。102、软件测试的类型有那些&#xff1f;分别比较这些不同的测试类型的区别与联系。103、测试用例通常包括那些内容&#xff1f;着重阐述编制测试用例的具体做法 104、在分别测试winform的C/S结构与测试…...