一段C++练习代码小评(1)
来源:互联网 发布:win arp mac地址绑定 编辑:程序博客网 时间:2024/05/19 00:14
还是大本营内同学的写的一段练习代码,原文见王富涛的:《练习使用模板》。
- #include <iostream>
- #include <vector>
- #include <string>
- #include <set>
- #include <algorithm>
- #include <utility>
- using namespace std;
- template<typename T>
- int Count(set< pair<T,int> > st, T val)
- {
- set< pair<T,int> >::iterator it = st.begin();
- while (it != st.end())
- {
- if (*it->first == *val)
- {
- return 1;
- }
- ++it;
- }
- return 0;
- }
- template<typename T>
- set< pair<T,int> >::iterator Find(set< pair<T,int> > &st, const T& val)
- {
- set< pair<T,int> >::iterator it = st.begin();
- while (it != st.end())
- {
- if (*it->first == *val)
- {
- return it;
- }
- ++it;
- }
- return st.end();
- }
- template<typename T>
- T frequent(T first,T end)
- {
- set< pair<T,int> > st;
- while (first != end)
- {
- if (!Count(st,first))
- {
- int ct = 1;
- pair<T,int> p(first,ct);
- st.insert(p);
- }
- else
- {
- set< pair<T,int> >::iterator it = Find(st,first);
- it->second += 1;
- }
- ++first;
- }
- set< pair<T,int> >::iterator it1 = st.begin(),it2 = it1;
- while (it1 != st.end())
- {
- if (it1->second > it2->second)
- {
- it2 = it1;
- }
- ++it1;
- }
- return it2->first;
- }
- int main()
- {
- int a[] = {4,3,2,2,2,3,};
- vector<int> vec(a,a+6);
- vector<int>::iterator it = frequent(vec.begin(),vec.end());
- cout << "数字出现最频繁的是: " << *it << endl;
- string str[] = {"hello","world","world","love","love","love","you"};
- vector<string> strval(str,str+7);
- vector<string>::iterator st = frequent(strval.begin(),strval.end());
- cout << "字符串出现最频繁的是: " << *st << endl;
- return 0;
- }
基于教学目的,先完全不管这段代码是干什么的,从基础开始,看看代码有哪些可以改进。
第一改:exist 函数第2版(名实相符)
1) 首先看到三个主要函数名字分别为:Count、Find、frequent。 这是第一个不该犯的问题。函数名风格不一致。暂且就依了stl的风格,都改成小写吧。
2) 接着,第一个函数:Count,发现辞不达意,名字叫“计数”,但其实干的是判断“是否存在”的活。于是将它改名为“exist”,返回类型改变“bool”。现在,原来Count函数变成:
- template<typename T>
- bool exist(set< pair<T,int> > st, T val)
- {
- typename set< pair<T,int> >::iterator it = st.begin();
- while (it != st.end())
- {
- if (*it->first == *val)
- {
- return true;
- }
- ++it;
- }
- return false;
- }
注:04行的 “typename”说明一下,是为了避免在某些编译器上,识别不出set<pair<T, int> >::iterator是一个类型的问题。
第二改:exist 函数第3版(尽量使用常量引用入参;用for实现迭代,而不是while)
再来看看这个exist函数,查找一个值,是否在一个容器里存在,这个过程通常既不会修改容器的内容,也不会修改待查的那个值。这样一看,原来的函数:
- template<typename T>
- bool exist(set< pair<T,int> > st, T val);
3) 对入参采用的是“传值”形式,也就是把容器和要查找的值都复制了一遍,这样太低效了。就像有一个水桶,里面养好多条鱼,我给你一条“独眼鱼”,让你帮找一下桶里是否存在长一模样的另一条。结果你先是把动手做了一个桶,再动用克隆技术,把桶里的鱼全复制一份,再把要找的“独眼鱼”也克隆一只……然后才开始查找过程——是不是太耗体的过程?所以,得都改成常量引用。“引用”保证不会有复制(克隆)的行为发生,“常量”则保证你不会在函数里打破我的桶或吃了哪几条鱼。
4) 对应的,函数体内的迭代器, 也应该是常量迭代器,因为你只是想“查遍”容器中的所有元素,这一过程是“只读”的,肯定不会去修改元素内容。(或许在现实中,在查找鱼时,一不小心会搞死几条鱼,但在程序中,不允许这样的马虎事)。一句话,iterator改成const_iterator。
(好学的同学这时会思考,为什么STL中的常量迭代器都是 const_iterator,而不直接用 const 关键字?王同学手脚真快,立马就响应了一篇《const_iterator 和 const..iterator》)
注:相信练习者理解这些知识,只是还没有养成习惯,不信大家看看另一个函数Find()。
5) 然后,我开始对while不顺眼了,这这离STL大师风格太远了嘛 :),好!改成用for,并不是个人爱好,也不是吹毛求疵。当我们非要对一个STL的容器进行迭代时,for循环通常是最清晰的表达。
- template<typename T>
- bool exist(set< pair<T,int> > const& st, T const& val)
- {
- for (typename set< pair<T,int> >::const_iterator it = st.begin()
- ; it != st.end(); ++it)
- {
- if (*it->first == *val)
- {
- return false;
- }
- }
- return false;
- }
第三改:泛型有多泛?
永远是多远? 泛型有多泛?练习者是在练习模板编程,但看到上版代码中07行时,大问题来了。
if (*it->first == *val)
咦,这就奇怪了,这里用到了“*”,也就是说,it->first和val,肯定是一个迭代器,指针也算是迭代器,就以指什为例吧:
- //取指针值比较示例代码:
- int *p1 = new int(0);
- int *p2 = new int(1);
- //进行值比较时:
- if (*p == *p2) //p1,p2是指针,所以需要 “*”来完成取值操作。
- {
- //...
- }
但此处,没有什么必定的原因,说要限定传进来的 val,必须是一个指针或一个迭代器。一定是exist函数的调用处,就造成了这个误解。往上看frequent()调用exist,以及main调用frequent,果然都是心里事先知道:参数会是一个迭代器了。这对典型的“由顶至底”的开发过程,倒也自然而然。不好强求初学者,只是心里还是要有个方向的:“型”要“泛”到什么程度?这才是在学习GP编程,否则一次次练习由顶至底的编程,结果只是在一次次强化“面向过程”的能力(当然也是一得)。
- template<typename T>
- bool exist(set< pair<T,int> > const& st, T const& val)
- {
- for (typename set< pair<T,int> >::const_iterator it = st.begin()
- ; it != st.end(); ++it)
- {
- if (it->first == val)
- {
- return false;
- }
- }
- return false;
- }
如此一改,函数签名没有变化,但调用exist的地方,必须变化:
- //原来:
- //if (!exist(st, first))
- //现在:
- if (!exist(st, *first))
- {
- /* ... */
- }
我相信会有太多的人,对我这个改变感到不以为然了:“拷!我还以为南老师多英明呢,原来是只卖弄!看,不一样要写一个'*'吗? 我呢,将'*'是写在函数实现里,一劳永逸,所有调用这个函数的地方,都不用写了,而南老师的方案正好相反,麻烦啊。”
这个问题,光用什么“良好习惯”的啊,“经典的做法”啦,那太矫情。我用两个方法来说明,第一是理论法,依据是明文规定的“明规则”;第二是“大势法”,依据的则是“潜规则”。
明规则:“值”是一阶数据,“指针”是二阶数据。
- //函数1:采用值类型:
- void foo_1(int const & a)
- {
- }
- //函数2:采用指针类型:
- void foo_2(int const* pa)
- {
- }
- //调用示例:
- int m = 0;
- int* pm = &m;
- foo_1(m); //使用值对象调用,OK
- foo_1(*pm); //通过'*',取得指针的值,调用, OK
- foo_1(0); //直接通过立即数(字面常量),调用, OK
- //----------
- foo_2(pm); //使用指针调用,OK
- foo_2(&m); //通过'&'取得对象的地址,调用,OK
- foo_2(&0); //不行,不能对一个字面常量取址,ERROR
看! foo_2 没有 foo_1好用了吧?假设让你写一个exits函数,用于判断指定整数数组中,是否存在某个特点的值:
- bool exists(vector<int> const& v, int* i)
- {...}
然后有人查一下是不是有3个这个数,可怜,他非得这么写:
- int a = 3;
- if (exists(v, &a))
- {...}
- /*
- 期望的调用方法: exists(v, 3)
- */
如果这还不能说服您的话……那我们就只好祭出“潜规则”了。现在,我将目光上移,移到本文最后一版exist函数的具体实现,然后盯着不放,然后我仔细地想,认真的想……突然间我想通了,见证奇迹的时候到了……
欲知后事如何,且听下回分解。
------------------------------------- 如果您想与我交流,请点击如下链接成为我的好友:
http://student.csdn.net/invite.php?u=112600&c=f635b3cf130f350c
- 一段C++练习代码小评(1)
- C练习小代码-20151012
- C练习小代码-20151108
- 一段汇编代码翻译成c语言的练习
- 一段小代码
- 一段小代码
- 一段小代码
- 一段小代码
- 一段小代码
- 一段小代码
- c语言小练习(1)
- 一段找出URL的代码(C#)
- C语言小练习 1
- 调试的一段小代码
- 一段小代码的思考
- 一段小程序(1)
- 一段经典C代码分析
- 一段经典的c代码
- php学习
- Android:在TabHost中删除某一项TAB
- 关于逆向引用
- 0723
- 微软求职攻略之笔试答疑
- 一段C++练习代码小评(1)
- 微软求职攻略之决胜面试
- 老师介绍
- ORA-00054: 资源正忙,要求指定 NOWAIT
- 做自己喜欢做的事情;做自己擅长做的事情 ...
- DataList编辑,更新,删除及模板的使用
- 编程能力的四种心理境界
- java 访问.net webservice返回的数据集
- 唐骏