`
helloyesyes
  • 浏览: 1310288 次
  • 性别: Icon_minigender_2
  • 来自: 武汉
文章分类
社区版块
存档分类
最新评论

《被误解的C++——学习和使用》的案例

阅读更多
假设我们现在有个任务,要做一个程序,将一个数组中的每一个元素乘上100,然后赋值回去。为此,我写下了这样的C#代码:
int[] ai=new int[10];
//初始化ai。
foreach(int i in ai)
{
i*=100;
}
我又写了C++代码:
vector<int> ai(10);
//初始化ai。
for(vector<int>::iterator i=ai.begin(); i!=ai.end(); ++i)
{
(*i)*=100;
}
唔,代码比C#长多了。C++真复杂。这个或许好点:
for(int i=0; i<ai.size(); ++i)
{
ai[i]*=100;
}
现在任务升级了,有三个数组,把一个数组的元素乘上第二个数组相应元素,放入第三个数组中。我想写这样的代码(C#):
int[] ai1=new int[10],ai2=new int[10], ai3=new int[10];
//初始化ai1和ai2
foreach(int i in ai1, int j in ai2, int k in ai3) //语法错误, 要能这么写
{ // 多好,可惜
i=j*k;
}
没有这种语法,得用for:
for(int i=0; i<0; ++i)
{
ai1[i]=ai2[i]*ai3[i];
}
C++版的是这样:
vector<int> ai1(10), ai2(10), ai3(10);
//初始化ai1和ai2
for(int i=0; i<0; ++i)
{
ai1[i]=ai2[i]*ai3[i];
}
代码一样。但是,C++有个transform算法,可以不用循环:
int mul_them(int v,int u) {
returnv*u;
}
transform(ai1.begin(), ai1.end(), ai2.begin(), ai3.begin(), mul_them);
代码没有简单,可晦涩多了。不熟悉标准库的初学者,会觉得这像是天书。我们一会儿来讨论学习问题。
再加点难度,三个容器,第一个是队列,第二个是数组,第三个是链表。把队列里的元素乘上数组里的元素,放到链表里。C#队列和链表没有随机访问操作符[],必须利用IEnumerable<>泛型接口访问:
Queue<int> ai1=new stack<int>(10);
int[] ai2=new int[10]
LinkedList<int> ai3=new queue<int>(10);
//初始化ai1和ai2
IEnumerator<int> eai1=ai1.GetEnumerator();
IEnumerator<int> eai2=ai2.GetEnumerator();
IEnumerator<int> eai3=ai3.GetEnumerator();
while(eai1.MoveNext() && eai2.MoveNext() && eai3.MoveNext())
{
eai3.Current=eai1.Current*eai2.Current;
}
下面该C++代码了。C++标准库的队列和链表也没有随即操作符[],必须使用迭代器:
deque<int> ai1(10);
vector<int> ai2(10);
list<int> ai3(10);
//初始化ai1和ai2
int mul_them(int v,int u) {
returnv*u;
}
transform(ai1.begin(), ai1.end(), ai2.begin(), ai3.begin(), mul_them);
C++也可以用手工循环访问迭代器,就像C#那样。当然,代码会复杂得多,比C#的还复杂。
再提高难度,我发现这种运算在很多地方出现,想把它做成一个算法。但是,每次出现的三种容器的类型不一定,元素的类型也不同,我必须作一个泛型算法。先做C#的版本:
public class MyAlogrithm
{
public static void Caculate_Contain<C1,T1,C2,T2,C3,T3>(C1 c1, C2 c2, C3 c3)
where C1: IEnumerator<T1>
where C2: IEnumerator<T2>
where C3: IEnumerator<T3>
{
IEnumerator<T1> eai1 = c1.GetEnumerator();
IEnumerator<T2> eai2 = c2.GetEnumerator();
IEnumerator<T3> eai3 = c3.GetEnumerator();
while (eai1.MoveNext() && eai2.MoveNext() && eai3.MoveNext())
{
eai3.Current = eai1.Current * eai2.Current;//编译错误
}
}
}
有编译错误。eai1.Current和eai2.Current分别返回T1和T2类型的实例,因为C#编译器不知道T1和T2的类型,所以不能确定两者是否能够相乘。此路走不通,我正在努力解决这个问题。剩下一种办法,就只能采用RTTI(RunTime Type Info)解决:
public class MyAlogrithm
{
public static void Caculate_Contain<C1,C2,C3>(C1 c1, C2 c2, C3 c3)
where C1 : IEnumerable
where C2 : IEnumerable
where C3 : IEnumerable
{
IEnumerator eai1 = c1.GetEnumerator();
IEnumerator eai2 = c2.GetEnumerator();
IEnumerator eai3 = c3.GetEnumerator();
while (eai1.MoveNext() && eai2.MoveNext() && eai3.MoveNext())
{
if(eai3.Current.type ==typeof(int)
&& eai1.Current.type ==typeof(int)
&& eai2.Current.type ==typeof(int))
{
(int)eai3.Current = (int)eai1.Current * (int)eai2.Current;
}
else if(eai3.Current.type==typeof(float)
&& eai1.Current.type==typeof(int)
&& eai2.Current.type ==typeof(int))
{
(float)eai3.Current = (int)eai1.Current * (int)eai2.Current;
}
else if(eai3.Current.type ==typeof(double)
&& eai1.Current.type ==typeof(float)
&& eai2.Current.type ==typeof(int))
{
(double)eai3.Current = (float)eai1.Current
* (int)eai2.Current;
}
}
}
}
如果不喜欢大量的if…else…,那么可以用Dictionary做一个分派器,根据容器元素的类型分派操作。这种做法有个限制性要求:必须事先约定容器的元素类型,比如int、long、double等等,可以是用户定义的类型(重载过*操作符)。代码的缺点么,都应该看到了:如果约定的容器元素的类型有n个,那么if…else…的数量将是n的立方。
回过头来在看C++的实现:
template<typename T1, typename T2, typename R>
R mul_them(T1 v,T2 u) {
returnv*u;
}
template<typename C1, typename C2, typename C3>
Caculate_Container(const C1& c1, const C2& c2, C3& c3)
{
transform(c1.begin(), c1.end(), c2.begin(), c3.begin(),
mul_them<C1::value_type, C2::value_type, C3::value_type>);
}
就这么简单。由于模板是编译期的机制,所有未确定类型(模板参数),是否包含所需的成员,如begin()、end()等,以及是否能够相乘,编译时便会立刻明了。而C#的泛型(确切地说,是.net的泛型),是跨语言的机制,泛型算法可能会被其他语言使用,无法在编译期确定类型的特征,所以必须要求类型在编译前明确所应具备的条件,即类型约束(where字句)。但问题还不在这里。问题在于,C#的泛型的类型约束依赖于继承,即类型必须从…类处继承而来。而不是像C++09中的concept那样直接描述类型所需的特征。所以,C#版的泛型算法也就是因此而失败。
最后再做一个小小的扩展,要求Caculate_Catainer<>()算法可以接受一个算法,该算法指示了如何对这些容器进行操作。此时,我们会发现,C#的泛型算法反而能够实现了:
public delegate void alg<T1, T2, R>(T1 v1, T2 v2, R r);
public static void Caculate_Contain<C1, T1, C2, T2, C3, T3>
(C1 c1, C2 c2, C3 c3, alg<T1, T2, T3> a )
where C1: IEnumerable<T1>
where C2 : IEnumerable<T2>
where C3 : IEnumerable<T3>
{
IEnumerator<T1> eai1 = c1.GetEnumerator();
IEnumerator<T2> eai2 = c2.GetEnumerator();
IEnumerator<T3> eai3 = c3.GetEnumerator();
while (eai1.MoveNext() && eai2.MoveNext() && eai3.MoveNext())
{
a(eai1.Current, eai2.Current,eai3.Current);
}
}
//使用
public static void CaculThem(int v1, int v2,int r) {
r=v1*v2;
}
Caculate_Contain(ai1, ai2, ai3, new alg<int, int, int>(CaculThem));
public static void CaculThem2(float v1, int v2,double r) {
r=v1*v2;
}
Caculate_Contain(af1, ai2, ad3, new alg<float, int, double>(CaculThem2));
我使用了一个委托,作为传递处理容器元素的算法的载体。使用时,用具体的算法创建委托的实例。但具体的算法CaculThem()必须同相应的容器元素类型一致。
下面轮到C++:
template<typename C1, typename C2, typename C3, typename Alg>
Caculate_Container(const C1& c1, const C2& c2, C3& c3, Alg a)
{
transform(c1.begin(), c1.end(), c2.begin(), c3.begin(), a);
}
//使用
template<typename T1, typename T2, typename R>
R mul_them(T1 v,T2 u) {
returnv*u;
}
Caculate_Container(ai1, ai2, ai3, mul_them<int, int, int>);
Caculate_Container(af1, ad2, ad3, mul_them<float, double, double>);
如果容器元素有所变化,C#代码必须重写算法CaculThem()。但C++不需要,由于mul_them<>()本身是个函数模板,那么只需将这个函数模板用新的类型实例化一下即可。
C++的代码相对简单些,灵活性也更高些。但这还不是全部,C++还有一个最终极的解法,不需要循环,不需要创建模板算法:
transform(c1.begin(), c1.end(), c2.begin(), c3.begin(), _1*_2);
没看明白?我一开始也看不明白。这里用到了boost库的Lambda表达式。_1占位符对应c1的元素,_2的占位符对应c2的元素,_1*_2表示才c1的元素乘上c2的元素,其结果放在c3里。表达式可以写得更复杂,比如(_1*_2+3*_1)/(_1-_2)。Lambda表达式可以用在所有需要操作的算法中,比如我要去掉字符串中的“-”,可以这样写:
remove_if(s.begin(), s.end(), _1==’-’);
Lambda表达式基于一种叫做“模板表达式”的技术,通过操作符重载,将一个表达式一层一层地展开,构成一个解析树。然后作为一个函数对象传递给算法,算法在循环内调用函数对象,执行相应的计算。
分享到:
评论

相关推荐

    动态规划算法——C++实现经典案例——初级动态规划算法——C++实现经典案例——初级

    动态规划算法——C++实现经典案例——初级

    掌握Visual C++——MFC程序设计与剖析书附光盘

    提供了掌握Visual C++——MFC程序设计与剖析书的源代码

    Visual C++视频——音频开发实用工程案例精选

    《Visual C++视频——音频开发实用工程案例精选》是一份专为IT专业人士和对音频开发感兴趣的初学者设计的教程资源。这份教程通过视频形式,深入浅出地讲解了使用Visual C++进行音频开发的关键技术和实战技巧。Visual...

    C++——HIS排班系统for Neuedu

    在本项目"C++——HIS排班系统for Neuedu"中,...总之,"C++——HIS排班系统for Neuedu"项目结合了C++的强大编程能力和Qt的易用性,为医疗机构提供了高效且可靠的排班管理工具,从而提升了医疗服务的组织和管理效率。

    《Android C++高级编程——使用NDK》_源码.zip

    《Android C++高级编程——使用NDK》提供了Java原生接口(JNI)的概述、Bionic API、POSIX 线程和套接字、C++支持、原生图形和声音API以及NEON/SIMD优化,在一个游戏应用案例的帮助下,你将学到很多关键技能。...

    C++基于MFC课程设计——学习公社

    在本项目中,"C++基于MFC课程设计——学习公社"是一个利用Microsoft Foundation Classes (MFC)库进行开发的C++应用程序,旨在创建一个学习平台。MFC是微软为Windows操作系统提供的一套C++类库,它简化了Windows应用...

    C++资源——实验讲义

    C++资源——实验讲义是针对C++编程学习者的一份重要的参考资料,主要涵盖了使用Visual C++ 6.0开发环境进行程序设计的基础知识和实践技能。这份讲义旨在帮助学生熟悉并掌握C++编程的基本概念,以及在Windows环境下...

    Visual C++实践与提高——COM和COM+篇

    8. 组件的生命周期管理:解释组件的创建、使用和销毁过程,以及如何优化组件的性能和资源占用。 通过学习和实践“Visual C++实践与提高——COM和COM+篇”,开发者可以提升自己在Windows平台上构建高效、可复用的...

    visual c++ 6——24学时学习教程

    总的来说,《Visual C++ 6——24学时学习教程》是一本全面、实用的教材,旨在帮助读者在24个学时内掌握VC++ 6的基本操作和编程技巧。通过这本书,读者不仅可以学习到C++语言和MFC库的基础知识,还能了解到调试、异常...

    Visual C++数字图像识别技术典型案例——求是科技

    《Visual C++数字图像识别技术典型案例——求是科技》是一份深入探讨图像处理和模式识别技术的教程,特别强调在VC++ 6.0开发环境中的应用。这份资源旨在为学习者提供一个实用的平台,通过实例代码来理解和掌握这些...

    一个好玩的c++游戏——生死枪战!

    生死枪战游戏目前以3个模式运行(持续更新),玩家可以通过自己的喜好来选择,如:新手训练营,无限...如果想要更多c++游戏,请进入zzz工作室,网址http://zzz07.ysepan.com/ 我的聊天室网址:https://hack.chat/?zzz

    c++STL学习——各种容器的技术总结和用法代码实例

    在"c++STL学习——各种容器的技术总结和用法代码实例"中,我们将深入探讨C++ STL的容器部分。 1. 容器概览: C++ STL中的容器是一些类模板,用于存储和管理元素集合。常见的容器有向量(vector)、列表(list)、...

    Android C++高级编程——使用NDK完整版

    Android拥有广大的用户群体,市场前景也很好,所以学习Android的人很多。但是因为Android很容易上手,如果只是单纯的学一些简单的东西很明显没有竞争力。所以必须学一点深层次的东西来提升自己的核心竞争力。第1章 ...

    Visual C++游戏开发经典案例详解——源码

    《Visual C++游戏开发经典案例详解》是一本深入探讨C++在游戏开发领域的实践书籍,其源码提供了丰富的学习资源。C++作为一种强大的面向对象编程语言,被广泛应用于游戏开发,尤其是在高性能、低延迟的游戏系统中。这...

    10852C++案例教程源代码——清华大学的

    《10852C++案例教程源代码》是由清华大学出版社出版的一本C++编程学习资源,它提供了丰富的实例和源代码,旨在帮助学生和初学者深入理解和掌握C++编程语言。这本书的配套源代码是学习过程中极为重要的参考资料,通过...

    C++游戏——泡泡堂

    《C++游戏——泡泡堂》是一款使用C++编程语言实现的经典休闲游戏,它以其独特的玩法和趣味性吸引了众多玩家。这个实习项目旨在让开发者通过实际操作来深入理解C++编程和游戏开发的基本原理。 首先,C++是面向对象的...

    C++——复数——运算符的重载

    对复数进行运算,主要是练习运算符的重载技术。

    c++ 学习经典教材 ————易学C++

    - **描述**:通过重复“非常好的书籍”,该描述突出了这本书在C++学习中的价值,暗示它能够提供易于理解和实践的C++知识。 ### 2. 版权声明与使用规定 - **部分内容**:书籍的版权属于作者和TomatoStudio,明确了...

    C++程序设计教程——设计思想与实现习题代码答案

    《C++程序设计教程——设计...总之,《C++程序设计教程——设计思想与实现习题代码答案》是一份宝贵的教育资源,它将理论知识与实际操作相结合,全面覆盖了C++的关键概念和技术,为学习者提供了一个扎实的C++编程基础。

Global site tag (gtag.js) - Google Analytics