这堆东西该如何重用?

 

1、问题的提出

我在做一个自己用的小软件用来管理和浏览树状信息。左侧是一个或多个树控件,右侧是用来显示左侧树控件上当前选中节点的具体信息的子窗体(一个usercontrol)及listview和其他辅助的treeview。树节点的相关信息是存在ACCESS里的,所以可以理解上述所有控件是“数据绑定”的(注意,并不是通常我们所说的那种数据绑定控件),当在其中任意一个控件对树节点的信息做了改动,这个改动就会反映在其他控件上,也会更新到数据库里。

之后,我发现我有若干个小应用都需要这个小软件。于是,我把这一组用来对树状信息进行浏览和管理的代码写成一套类,然后每次开发一个新的小应用的时候,我就把类模块拷过来,再针对特定的应用做相应的变动。这样做也算是一种重用了,“拷贝粘贴型”的重用。麻烦的是,每次我对类有了改动的时候,我需要在所有拷贝过它的地方都做这个改动。这不仅麻烦,有时还会带来八哥。

有没有什么更好的重用方法呢

2、想要怎样重用?

在进一步讨论之前,得说清楚想怎么重用(我试着列出几条如下,但其实我不大想得清楚):

首先,想重用的是用这一堆控件协同工作来展示树状数据代码和界面

其次,虽然每个需要用这套东西的应用都有相似之处,但也各自有区别。它们在界面上可能也会各自不同,有的控件多几个、有的控件少几个。但总的来说,这个同步显示和更新树状信息的功能是都需要的。

再次,希望有个属性页之类的东西,可以在设计模式下设置属性。

3、解决思路

一种想法是:把这整体做成一个控件,把它往窗体上一拖,它就自动生成一个小型的应用。在属性页里设置一些属性后,它就可以展示指定树表里的信息。但是,这就要求这个控件通用性非常好,要考虑到各种不同应用的细微差别,允许用户(二次开发的程序员)进行定制,要抽象出合适的接口。我觉得现在脑子里还抽象不到这种程度,许多需求不做是不能明白的。

另外一种想法是:在设计视图下用户根据自己的想法拖放各种标准控件到窗体上,然后告诉我的“这堆东西”谁是谁,之后这堆东西就可以在自己内部的类里控制这些控件,当用户在运行时通过这些控件改动数据的时候,这堆东西也会得到事件通知,负责数据更新的相关业务逻辑。这样的话,这堆东西也许应该是个不带界面但是带属性页的ActiveX控件。

注:这种想法里强调有属性页的目的,是为了让开发者(其实就是我自己了)不用写代码就可以告诉“那堆东西”谁是谁以及从哪里取数据。

我倾向于第二种想法。比如我在应用里放一个这堆东西的实例AAA,一通设置后,运行程序,它就可以从指定的表里取出数据并展示还支持增删改。假如我希望用到AAA里用来控制树控件的成员对象变量TTT的方法MMM,那我就在程序里写Call AAA.TTT.MMM。

这是一个非常好的问题,触及了 C++ 中 **性能优化与资源管理的核心思想**。我们来深入回答: > ❓“为什么不直接复制 `current` 到 `previous`,而要用 `swap()`?” > ✅ 答案是:**用 `swap()` 是为了高效复用内存、避免昂贵的拷贝操作。** --- ### ✅ 问题回答:为什么使用 `swap()` 而不是复制? #### 原始想法(错误方式): ```cpp previous = current; // 复制整个 vector ``` 这在逻辑上是对的——你想把当前行变成下一行的“上一行”。 但问题是:**赋值操作会进行深拷贝(deep copy)**,即逐个元素复制。 对于长度为 $n$ 的 `vector<int>`,每次都要执行 $O(n)$ 时间的拷贝。 而我们的外层循环有 $m$ 次 → 总时间变为: $$ O(m \times n) + O(m \times n) = O(mn) $$ 虽然渐近复杂度一样,但常数翻倍,在大数据下非常慢! --- ### 🔁 正确做法:`current.swap(previous);` ```cpp current.swap(previous); ``` #### 💡 `swap()` 干了什么? 它交换的是两个 `vector` 的内部指针(指向内存),而不是复制数据! 具体来说: - `vector` 内部通常包含三个指针:`_M_start`, `_M_finish`, `_M_end_of_storage` - `swap()` 只交换这三个指针,耗时 **常数时间 $O(1)$**,不碰实际数据 📌 效果等价于“换名字”: - 原来的 `current` 数据 → 现在叫 `previous` - 原来的 `previous` 数据 → 现在叫 `current`,准备被覆盖 然后我们在新的 `current` 上重新填写新一行的数据。 --- ### 🧠 类比理解:两块白板轮流擦写 想象你有两个白板: - 白板 A:写着上一行的结果(相当于 `previous`) - 白板 B:空着,准备写当前行(相当于 `current`) 流程如下: 1. 把白板 B 和 A 对调名字(`swap`)→ 现在 A 是旧结果,B 是空板 2. 在 B 上写新一行(利用 A 上的数据做计算) 3. 下次再 swap,继续用 ✅ 这样就不用每次都抄一遍内容,只需“换个标签”。 --- ### ⚖️ 对比两种方式 | 方法 | 操作 | 时间复杂度 | 是否推荐 | |------|------|------------|---------| | `previous = current;` | 深拷贝所有元素 | $O(n)$ per row → $O(mn)$ total | ❌ 不推荐 | | `current.swap(previous);` | 交换指针 | $O(1)$ | ✅ 强烈推荐 | 👉 所以 `swap()` 是一种 **零成本的角色互换**,是实现滚动数组的关键技巧。 --- ### ✅ 实际代码行为详解 让我们看这段关键代码: ```cpp current.swap(previous); for (int j = 1; j <= Y.size(); ++j) { if (X[i-1] == Y[j-1]) current[j] = previous[j-1] + 1; else current[j] = max(previous[j], current[j-1]); } ``` #### 第一步:`swap` - 作用:让 `previous` 指向刚刚算好的 `current`(即上一行) - 现在 `current` 被清空(实际上是旧的 `previous`,可以安全覆盖) #### 第二步:用 `previous`(原 `current`)作为上一行数据来更新新的 `current` > 所以 `swap` 实际上是在“把当前结果升格为 previous”,以便下一轮使用 --- ### 💬 更直观的名字解释(助记) 你可以把变量重命名来帮助理解: ```cpp std::vector<int> new_row(Y.size() + 1, 0); // 当前行 std::vector<int> prev_row(Y.size() + 1, 0); // 上一行 ``` 但我们不能一直保留所有行,所以每轮要滚动: ```cpp for (int i = 1; i <= X.size(); ++i) { new_row.swap(prev_row); // 现在:prev_row = 旧的 new_row(上一行),new_row = 旧的 prev_row(可写)) for (int j = 1; j <= Y.size(); ++j) { // 使用 prev_row[j] 和 prev_row[j-1] 来更新 new_row[j] ... } } ``` 这样你就明白:`swap` 是为了让 `prev_row` 拿到最新的已完成的那一行。 --- ### ✅ 总结:为什么要 swap?因为快! | 特性 | 说明 | |------|------| | ✔️ 高效 | `swap()` 是常数时间,仅交换指针 | | ✔️ 安全 | 不涉及动态内存分配/释放 | | ✔️ STL 设计哲学 | “不要为不需要的东西付出代价” —— `vector::swap` 就是为此设计 | | ❌ 赋值拷贝 | 会导致每行都复制一次,严重拖慢速度 | --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值