事件模型是被广泛使用的好东西,但是C++标准库里没有现成的,其他实现又复杂或者不优雅,比如需要使用宏。现在VC11可以用在XP下了,那么就痛快的拿起C++11提供的先进设施组合出一个轻便的实现吧。

  为了达到简洁的目的,需要放弃一些特性:

  1、不支持判断函数是否已经绑定过(因为std::function不提供比较方法,自己实现function的话代码又变多了)

  2、需要使用者接收返回的回调函数标识来移除事件绑定(原因同上)

  3、事件没有返回值,不支持回调函数优先级、条件回调等事件高级特性(比如返回所有处理结果中的最大最小值;只回调与指定参数匹配的事件处理函数)

  4、事件参数理论上无限,实际上有限,一般支持0~10个参数(VC11还没有支持变长模板参数,GCC有了。不过可以通过缺省模板参数和偏特化来模拟,所以理论上无限制)

  5、不是线程安全的

  注:3、5两条可以通过引入策略模式来提供灵活支持,就像标准库和Loki做的那样,实现一个完整的事件机制。

最简单的实现

 1 #include <map>
 2 #include <functional>
 3
 4 using namespace std;
 5
 6
 7 template<class Param1, class Param2>
 8 class Event
 9 {
10     typedef void HandlerT(Param1, Param2);
11     int m_handlerId;
12
13 public:
14     Event() : m_handlerId(0) {}
15
16     template<class FuncT> int addHandler(FuncT func)
17     {
18         m_handlers.emplace(m_handlerId, forward<FuncT>(func));
19         return m_handlerId++;
20     }
21
22     void removeHandler(int handlerId)
23     {
24         m_handlers.erase(handlerId);
25     }
26
27     void operator ()(Param1 arg1, Param2 arg2)
28     {
29         for ( const auto& i : m_handlers )
30             i.second(arg1, arg2);
31     }
32
33 private:
34     map<int, function<HandlerT>> m_handlers;
35 };

addHandler把回调函数完美转发给std::function,让标准库来搞定各种重载,然后返回一个标识符用来注销绑定。试一下,工作的不错:

 1 void f1(int, int)
 2 {
 3     puts("f1()");
 4 }
 5
 6 struct F2
 7 {
 8     void f(int, int)
 9     {
10         puts("f2()");
11     }
12
13     void operator ()(int, int)
14     {
15         puts("f3()");
16     }
17 };
18
19 int _tmain(int argc, _TCHAR* argv[])
20 {
21     Event<int, int> e;
22
23     int id = e.addHandler(f1);
24
25     e.removeHandler(id);
26
27     using namespace std::placeholders;
28
29     F2 f2;
30
31     e.addHandler(bind(&F2::f, f2, _1, _2));
32     e.addHandler(bind(f2, _1, _2));
33
34     e.addHandler([](int, int) {
35         puts("f4()");
36     });
37
38     e(1, 2);
39
40     return 0;
41 } 

虽然这里有一个小小的缺点,对于仿函数,如果想使用它的指针或引用是不可以直接绑定的,需要这样做:

1 e.addHandler(ref(f2));
2 e.addHandler(ref(*pf2));    // pf2是指向f2的指针

但是使用仿函数对象指针的情形不多,也不差多敲几个字符,何况在有Lambda表达式的情况下呢?

改进

1、有人不喜欢bind,用起来麻烦,放到addhandler里面去:

1         template<class ObjT, class FuncT> int addHandler(ObjT obj, FuncT func)
2         {
3             using namespace std::placeholders;
4             m_handlers.emplace(m_handlerId, std::bind(func, std::forward<ObjT>(obj), _1, _2));
5             return m_handlerId++;
6         }

2、扩展参数个数。没有变长模板参数,变通一下:

 1 struct NullType {};
 2
 3 template<class P1 = Private::NullType, class P2 = Private::NullType>
 4 class Event
 5 {
 6 public:
 7     template<class ObjT, class FuncT> int addHandler(ObjT obj, FuncT func)
 8     {
 9         using namespace std::placeholders;
10         m_handlers.emplace(m_handlerId, std::bind(func, std::forward<ObjT>(obj), _1, _2));
11         return m_handlerId++;
12     }
13
14     void operator ()(P1 arg1, P2 arg2)
15     {
16         for ( const auto& i : m_handlers )
17             i.second(arg1, arg2);
18     }
19 };
20
21 template<>
22 class Event<Private::NullType, Private::NullType>
23 {
24 public:
25     template<class ObjT, class FuncT> int addHandler(ObjT obj, FuncT func)
26     {
27         using namespace std::placeholders;
28         m_handlers.emplace(m_handlerId, std::bind(func, std::forward<ObjT>(obj)));
29         return m_handlerId++;
30     }
31
32     void operator ()()
33     {
34         for ( const auto& i : m_handlers )
35             i.second();
36     }
37 };
38
39 template<class P1>
40 class Event<P1, Private::NullType>
41 {
42 public:
43     template<class ObjT, class FuncT> int addHandler(ObjT obj, FuncT func)
44     {
45         using namespace std::placeholders;
46         m_handlers.emplace(m_handlerId, std::bind(func, std::forward<ObjT>(obj), _1));
47         return m_handlerId++;
48     }
49
50     void operator ()(P1 arg1)
51     {
52         for ( const auto& i : m_handlers )
53             i.second(arg1);
54     }
55 };

现在支持0~2个参数了。注意到各个模板里有公共代码,提取出来放进基类,然后要做的就是打开文本生成器了

补充一下:VC里std::function默认最多5个参数,最多支持10个,要在编译开关里设置一下宏_VARIADIC_MAX=10

完整代码

代码下载

View Code

  1 #pragma once
  2
  3 #include <map>
  4 #include <functional>
  5
  6
  7 namespace Utility
  8 {
  9     namespace Private
 10     {
 11         struct NullType {};
 12
 13         template<class HandlerT>
 14         class EventBase
 15         {
 16         public:
 17             EventBase() : m_handlerId(0) {}
 18
 19             template<class FuncT> int addHandler(FuncT func)
 20             {
 21                 m_handlers.emplace(m_handlerId, std::forward<FuncT>(func));
 22                 return m_handlerId++;
 23             }
 24
 25             void removeHandler(int handlerId)
 26             {
 27                 m_handlers.erase(handlerId);
 28             }
 29
 30         protected:
 31             int m_handlerId;
 32             std::map<int, std::function<HandlerT>> m_handlers;
 33         };
 34     }
 35
 36     template<class P1 = Private::NullType, class P2 = Private::NullType, class P3 = Private::NullType, class P4 = Private::NullType, class P5 = Private::NullType, class P6 = Private::NullType, class P7 = Private::NullType, class P8 = Private::NullType, class P9 = Private::NullType, class P10 = Private::NullType>
 37     class Event
 38         : public Private::EventBase<void (P1, P2, P3, P4, P5, P6, P7, P8, P9, P10)>
 39     {
 40     public:
 41         using Private::EventBase<void (P1, P2, P3, P4, P5, P6, P7, P8, P9, P10)>::addHandler;
 42
 43         template<class ObjT, class FuncT> int addHandler(ObjT obj, FuncT func)
 44         {
 45             using namespace std::placeholders;
 46             m_handlers.emplace(m_handlerId, std::bind(func, std::forward<ObjT>(obj), _1, _2, _3, _4, _5, _6, _7, _8, _9, _10));
 47             return m_handlerId++;
 48         }
 49
 50         void operator ()(P1 arg1, P2 arg2, P3 arg3, P4 arg4, P5 arg5, P6 arg6, P7 arg7, P8 arg8, P9 arg9, P10 arg10)
 51         {
 52             for ( const auto& i : m_handlers )
 53                 i.second(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10);
 54         }
 55     };
 56
 57     template<>
 58     class Event<Private::NullType, Private::NullType, Private::NullType, Private::NullType, Private::NullType, Private::NullType, Private::NullType, Private::NullType, Private::NullType, Private::NullType>
 59         : public Private::EventBase<void ()>
 60     {
 61     public:
 62         using Private::EventBase<void ()>::addHandler;
 63
 64         template<class ObjT, class FuncT> int addHandler(ObjT const obj, FuncT func)
 65         {
 66             m_handlers.emplace(m_handlerId, std::bind(func, obj));
 67             return m_handlerId++;
 68         }
 69
 70         void operator ()()
 71         {
 72             for ( const auto& i : m_handlers )
 73                 i.second();
 74         }
 75     };
 76
 77     template<class P1>
 78     class Event<P1, Private::NullType, Private::NullType, Private::NullType, Private::NullType, Private::NullType, Private::NullType, Private::NullType, Private::NullType, Private::NullType>
 79         : public Private::EventBase<void (P1)>
 80     {
 81     public:
 82         using Private::EventBase<void (P1)>::addHandler;
 83
 84         template<class ObjT, class FuncT> int addHandler(ObjT obj, FuncT func)
 85         {
 86             using namespace std::placeholders;
 87             m_handlers.emplace(m_handlerId, std::bind(func, std::forward<ObjT>(obj), _1));
 88             return m_handlerId++;
 89         }
 90
 91         void operator ()(P1 arg1)
 92         {
 93             for ( const auto& i : m_handlers )
 94                 i.second(arg1);
 95         }
 96     };
 97
 98     template<class P1, class P2>
 99     class Event<P1, P2, Private::NullType, Private::NullType, Private::NullType, Private::NullType, Private::NullType, Private::NullType, Private::NullType, Private::NullType>
100         : public Private::EventBase<void (P1, P2)>
101     {
102     public:
103         using Private::EventBase<void (P1, P2)>::addHandler;
104
105         template<class ObjT, class FuncT> int addHandler(ObjT obj, FuncT func)
106         {
107             using namespace std::placeholders;
108             m_handlers.emplace(m_handlerId, std::bind(func, std::forward<ObjT>(obj), _1, _2));
109             return m_handlerId++;
110         }
111
112         void operator ()(P1 arg1, P2 arg2)
113         {
114             for ( const auto& i : m_handlers )
115                 i.second(arg1, arg2);
116         }
117     };
118
119     template<class P1, class P2, class P3>
120     class Event<P1, P2, P3, Private::NullType, Private::NullType, Private::NullType, Private::NullType, Private::NullType, Private::NullType, Private::NullType>
121         : public Private::EventBase<void (P1, P2, P3)>
122     {
123     public:
124         using Private::EventBase<void (P1, P2, P3)>::addHandler;
125
126         template<class ObjT, class FuncT> int addHandler(ObjT obj, FuncT func)
127         {
128             using namespace std::placeholders;
129             m_handlers.emplace(m_handlerId, std::bind(func, std::forward<ObjT>(obj), _1, _2, _3));
130             return m_handlerId++;
131         }
132
133         void operator ()(P1 arg1, P2 arg2, P3 arg3)
134         {
135             for ( const auto& i : m_handlers )
136                 i.second(arg1, arg2, arg3);
137         }
138     };
139
140     template<class P1, class P2, class P3, class P4>
141     class Event<P1, P2, P3, P4, Private::NullType, Private::NullType, Private::NullType, Private::NullType, Private::NullType, Private::NullType>
142         : public Private::EventBase<void (P1, P2, P3, P4)>
143     {
144     public:
145         using Private::EventBase<void (P1, P2, P3, P4)>::addHandler;
146
147         template<class ObjT, class FuncT> int addHandler(ObjT obj, FuncT func)
148         {
149             using namespace std::placeholders;
150             m_handlers.emplace(m_handlerId, std::bind(func, std::forward<ObjT>(obj), _1, _2, _3, _4));
151             return m_handlerId++;
152         }
153
154         void operator ()(P1 arg1, P2 arg2, P3 arg3, P4 arg4)
155         {
156             for ( const auto& i : m_handlers )
157                 i.second(arg1, arg2, arg3, arg4);
158         }
159     };
160
161     template<class P1, class P2, class P3, class P4, class P5>
162     class Event<P1, P2, P3, P4, P5, Private::NullType, Private::NullType, Private::NullType, Private::NullType, Private::NullType>
163         : public Private::EventBase<void (P1, P2, P3, P4, P5)>
164     {
165     public:
166         using Private::EventBase<void (P1, P2, P3, P4, P5)>::addHandler;
167
168         template<class ObjT, class FuncT> int addHandler(ObjT obj, FuncT func)
169         {
170             using namespace std::placeholders;
171             m_handlers.emplace(m_handlerId, std::bind(func, std::forward<ObjT>(obj), _1, _2, _3, _4, _5));
172             return m_handlerId++;
173         }
174
175         void operator ()(P1 arg1, P2 arg2, P3 arg3, P4 arg4, P5 arg5)
176         {
177             for ( const auto& i : m_handlers )
178                 i.second(arg1, arg2, arg3, arg4, arg5);
179         }
180     };
181
182     template<class P1, class P2, class P3, class P4, class P5, class P6>
183     class Event<P1, P2, P3, P4, P5, P6, Private::NullType, Private::NullType, Private::NullType, Private::NullType>
184         : public Private::EventBase<void (P1, P2, P3, P4, P5, P6)>
185     {
186     public:
187         using Private::EventBase<void (P1, P2, P3, P4, P5, P6)>::addHandler;
188
189         template<class ObjT, class FuncT> int addHandler(ObjT obj, FuncT func)
190         {
191             using namespace std::placeholders;
192             m_handlers.emplace(m_handlerId, std::bind(func, std::forward<ObjT>(obj), _1, _2, _3, _4, _5, _6));
193             return m_handlerId++;
194         }
195
196         void operator ()(P1 arg1, P2 arg2, P3 arg3, P4 arg4, P5 arg5, P6 arg6)
197         {
198             for ( const auto& i : m_handlers )
199                 i.second(arg1, arg2, arg3, arg4, arg5, arg6);
200         }
201     };
202
203     template<class P1, class P2, class P3, class P4, class P5, class P6, class P7>
204     class Event<P1, P2, P3, P4, P5, P6, P7, Private::NullType, Private::NullType, Private::NullType>
205         : public Private::EventBase<void (P1, P2, P3, P4, P5, P6, P7)>
206     {
207     public:
208         using Private::EventBase<void (P1, P2, P3, P4, P5, P6, P7)>::addHandler;
209
210         template<class ObjT, class FuncT> int addHandler(ObjT obj, FuncT func)
211         {
212             using namespace std::placeholders;
213             m_handlers.emplace(m_handlerId, std::bind(func, std::forward<ObjT>(obj), _1, _2, _3, _4, _5, _6, _7));
214             return m_handlerId++;
215         }
216
217         void operator ()(P1 arg1, P2 arg2, P3 arg3, P4 arg4, P5 arg5, P6 arg6, P7 arg7)
218         {
219             for ( const auto& i : m_handlers )
220                 i.second(arg1, arg2, arg3, arg4, arg5, arg6, arg7);
221         }
222     };
223
224     template<class P1, class P2, class P3, class P4, class P5, class P6, class P7, class P8>
225     class Event<P1, P2, P3, P4, P5, P6, P7, P8, Private::NullType, Private::NullType>
226         : public Private::EventBase<void (P1, P2, P3, P4, P5, P6, P7, P8)>
227     {
228     public:
229         using Private::EventBase<void (P1, P2, P3, P4, P5, P6, P7, P8)>::addHandler;
230
231         template<class ObjT, class FuncT> int addHandler(ObjT obj, FuncT func)
232         {
233             using namespace std::placeholders;
234             m_handlers.emplace(m_handlerId, std::bind(func, std::forward<ObjT>(obj), _1, _2, _3, _4, _5, _6, _7, _8));
235             return m_handlerId++;
236         }
237
238         void operator ()(P1 arg1, P2 arg2, P3 arg3, P4 arg4, P5 arg5, P6 arg6, P7 arg7, P8 arg8)
239         {
240             for ( const auto& i : m_handlers )
241                 i.second(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8);
242         }
243     };
244
245     template<class P1, class P2, class P3, class P4, class P5, class P6, class P7, class P8, class P9>
246     class Event<P1, P2, P3, P4, P5, P6, P7, P8, P9, Private::NullType>
247         : public Private::EventBase<void (P1, P2, P3, P4, P5, P6, P7, P8, P9)>
248     {
249     public:
250         using Private::EventBase<void (P1, P2, P3, P4, P5, P6, P7, P8, P9)>::addHandler;
251
252         template<class ObjT, class FuncT> int addHandler(ObjT obj, FuncT func)
253         {
254             using namespace std::placeholders;
255             m_handlers.emplace(m_handlerId, std::bind(func, std::forward<ObjT>(obj), _1, _2, _3, _4, _5, _6, _7, _8, _9));
256             return m_handlerId++;
257         }
258
259         void operator ()(P1 arg1, P2 arg2, P3 arg3, P4 arg4, P5 arg5, P6 arg6, P7 arg7, P8 arg8, P9 arg9)
260         {
261             for ( const auto& i : m_handlers )
262                 i.second(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9);
263         }
264     };
265
266
267 } // namespace Utility

测试代码

各种绑定方式

View Code

  1 #include "Event.h"
  2
  3 using namespace std;
  4
  5
  6 void f1(int, int)
  7 {
  8     puts("f1()");
  9 }
 10
 11 struct F2
 12 {
 13     F2() { puts("F2 construct"); }
 14     F2(const F2 &) { puts("F2 copy"); }
 15     F2(F2 &&) { puts("F2 move"); }
 16     F2& operator=(const F2 &)  { puts("F2 copy assign"); return *this; }
 17     F2& operator=(F2 &&)  { puts("F2 move assign"); return *this; }
 18
 19     void f(int, int)
 20     {
 21         puts("f2()");
 22     }
 23
 24     void fc(int, int) const
 25     {
 26         puts("f2c()");
 27     }
 28 };
 29
 30 struct F3
 31 {
 32     F3() { puts("F3 construct"); }
 33     F3(const F3 &) { puts("F3 copy"); }
 34     F3(F3 &&) { puts("F3 move"); }
 35     F3& operator=(const F3 &)  { puts("F3 copy assign"); return *this; }
 36     F3& operator=(F3 &&)  { puts("F3 move assign"); return *this; }
 37
 38     void operator ()(int, int) const
 39     {
 40         puts("f3()");
 41     }
 42 };
 43
 44 int _tmain(int argc, _TCHAR* argv[])
 45 {
 46     Utility::Event<int, int> e;
 47
 48     // 一般函数
 49     e.addHandler(f1);
 50
 51     int id = e.addHandler(&f1);
 52     e.removeHandler(id);                                                // 移除事件处理函数
 53
 54
 55     // 成员函数
 56     using namespace std::placeholders;
 57
 58     F2 f2;
 59     const F2 *pf2 = &f2;
 60
 61     e.addHandler(bind(&F2::f, &f2, _1, _2));        // std::bind
 62     e.addHandler(&f2, &F2::f);
 63
 64     e.addHandler(pf2, &F2::fc);                                    // 常量指针
 65
 66     puts("----addHandler(f2, &F2::f)----");
 67     e.addHandler(f2, &F2::f);                                        // 对象拷贝构造
 68
 69     puts("----addHandler(F2(), &F2::f)----");
 70     e.addHandler(F2(), &F2::f);                                    // 对象转移构造
 71
 72     puts("--------");
 73
 74
 75     // 仿函数
 76     F3 f3;
 77     const F3 *pf3 = &f3;
 78
 79     puts("----addHandler(f3)----");
 80     e.addHandler(f3);                                                        // 对象拷贝构造
 81
 82     puts("----addHandler(F3())----");
 83     e.addHandler(F3());                                                    // 对象转移构造
 84     puts("--------");
 85
 86     e.addHandler(ref(f3));                                            // 引用仿函数对象
 87     e.addHandler(ref(*pf3));                                        // 引用仿函数常量对象
 88
 89     puts("--------");
 90
 91     // Lambda表达式
 92     e.addHandler([](int, int) {
 93         puts("f4()");
 94     });
 95
 96     // 激发事件
 97     e(1, 2);
 98
 99     return 0;
100 }

转载于:https://www.cnblogs.com/lierlier/archive/2013/02/01/cppevent.html

C++中事件机制的简洁实现相关推荐

  1. 探索.NET中事件机制(续)——虚事件和事件重写问题,微软的Bug?!

    上一篇写到了微软经典的事件处理机制是通过EventHandlerList方式进行事件保存并且处理的.本节讲一个奇怪的问题--虚事件和事件重写问题(具体提问在:http://social.msdn.mi ...

  2. 「前端面试题系列7」Javascript 中的事件机制(从原生到框架)

    前言 这是前端面试题系列的第 7 篇,你可能错过了前面的篇章,可以在这里找到: 理解函数的柯里化 ES6 中箭头函数的用法 this 的原理以及用法 伪类与伪元素的区别及实战 如何实现一个圣杯布局? ...

  3. jQuery中的事件机制深入浅出

    昨天呢,我们大家一起分享了jQuery中的样式选择器,那么今天我们就来看一下jQuery中的事件机制,其实,jQuery中的事件机制与JavaScript中的事件机制区别是不大的,只是,JavaScr ...

  4. Java中事件监听机制

    Java中事件监听机制 一.事件监听机制的定义 要想了解Java中的事件监听机制,首先就要去了解一下在Java中事件是怎样去定义的呢!在使用Java编写好一个界面后,我们就会对界面进行一些操作,比如, ...

  5. 10.QT事件机制源码时序分析(中)

    接上一篇文章https://blog.csdn.net/Master_Cui/article/details/109162220,上篇文章已经说过,在Ubuntu18.04,QT的事件机制实际上是采用 ...

  6. Spring 与 Spring Boot 中的事件机制

    点击上方蓝色"程序猿DD",选择"设为星标" 回复"资源"获取独家整理的学习资料! 作者 | 温安适 来源 | https://my.osc ...

  7. Javascript基础与面向对象基础~第六讲 Javascript中的事件机制

    回到目录 事件机制,在JS中感觉很容易让人接受,一个鼠标被按下时会发生一些事情,一个键盘的键被抬起时同样可以发生一些事情,这种比喻很容易让人接受,不是吗,呵呵. 下面我将JS中几个主要的事件说一下,然 ...

  8. Spring中ApplicationContext的事件机制

    ApplicationContext事件机制是观察者设计模式的实现,通过ApplicationEvent类和ApplicationListener接口,可以实现ApplicationContext事件 ...

  9. java实现分发_关于JAVA中事件分发和监听机制实现的代码实例

    [实例简介] 关于JAVA中事件分发和监听机制实现的代码实例,绝对实用代码,有说明. [实例截图] [核心代码] JavaEventDispatch ├── bin │   └── com │   └ ...

最新文章

  1. layui如何存在多个弹窗_layui常见弹窗使用方法
  2. 顶会ASPLOS 新成果解析:用“弹性异构”防御DNN加速器对抗攻击
  3. $.ajax返回的JSON格式的数据后无法执行success的解决方法
  4. 782B The Meeting Place Cannot Be Changed(二分)
  5. java mvc 断点续传_用SpringMVC 实现断点续传 (HTTP)
  6. 无限驾驶汉化后黑屏问题
  7. Nodejs连接mysql的增、删、改、查操作
  8. android 微信分享gif图,android后台动态创建图片并实现微信分享
  9. ERROR 1045 (28000): Access denied for user root@localhost (using password: NO)
  10. ServiceComb的开放性设计
  11. [USACO08JAN]牛大赛Cow Contest
  12. 每天2小时,吃透 985博士总结的这份目标检测、卷积神经网络和OpenCV学习资料笔记(20G高清/PPT/代码)...
  13. 【目标检测】(12) 非极大值抑制 NMS 和 Soft-NMS,附TensorFlow完整代码
  14. python网课答案查询_网课答案查询助手v1.0 官方版
  15. 网易游戏笔试【2020暑期实习生】游戏研发工程师第二批在线笔试4月6日
  16. Java游戏项目之黄金矿工
  17. 锆石科技的FPGA教学视频
  18. 【Python 3.7】序数:序数表示位置,如 1st和 2nd。大多数序数都以 th结尾,只有 1、2和 3 例外。
  19. 关于Qt bindValue函数出错问题
  20. HTML做一个简单的页面(纯html代码)地球专题学习网站

热门文章

  1. 关于重写equals()与hashCode()
  2. 保护程序猿滴眼睛-----修改VS 2008 编辑器颜色 (修改 chrome浏览器的背景色)
  3. [Android] Android学习手记(二)
  4. flex基于svn协同开发
  5. 太极创客ESP8266 - NodeMCU、JSON、MQTT教程(基于Arduino)
  6. python组合数据分类_Python 数据可视化:分类特征统计图
  7. radare2 常用操作总结
  8. xampp mysql 备份_Linux Xampp计划任务自动备份Mysql数据库和所有网站
  9. 计算机网络上网时间,电脑怎么限制上网时间
  10. 电脑常见的VGA、DVI、PS/2、USB等接口知识笔记,值得收藏!