More Exceptional C++中文版试读(泛型程序设计与C++标准库)

 [Herb Sutter 的名作More Exceptional C++中文版即将出版。作为本书译者,我很高兴将本书推荐给大家。征得华中科技大学出版社同意,我将公开部分译稿,敬请大家批评指正。




  


泛型程序设计与C++标准库


 


 


 


C++威力强大的特性之一是对泛型程序设计(generic programming)的支持。这种威力直接反映在C++标准库的灵活性上,特别是它的容器、迭代器和算法部分,这一部分一直以来被称作标准模板库(STL)。


 


本书的开篇章节集中讨论如何最有效地使用C++标准库,尤其是STL。什么时候使用std::vectorstd::deque会最有效?如何使用?在使用std::mapstd::set的时候可能会碰到哪些陷阱?如何安全地避免这些陷阱?std::remove()为什么不能真正删除任何东西?


 


本章还特别介绍了一些有用的技巧和易犯的错误,在撰写自己的泛型程序代码的时候,包括撰写那些“用以和STL一起工作”或“用以扩充STL”的代码的时候,你会经常碰到它们。什么样的predicates才能安全地和STL一起使用?什么样的不行?为什么?要想让模板自身的行为可以改变,而且这种行为的改变是基于“与之协同工作的类型(type)”的能力,有什么现有的技术可以写出这种功能强大的泛型模板代码吗?如何在不同种类的输入输出流之间自如地切换?模板特殊化和重载是怎么一回事?“古怪”的typename关键字究竟有何过人之处?


 


随着对泛型程序设计和C++标准库有关话题的深入研究,我们还会碰到更多的问题。


 


 










条款1:流


难度:2


在动态地使用不同的输入输出流——包括标准控制台流(console stream)和文件时,最佳使用方式是什么?


 


1. std::cinstd::cout的类型是什么?


2. 写一个ECHO程序,让它简单地响应输入,并能通过以下两种方式等效地调用:


 



 


    ECHO <infile> outfile


    ECHO infile outfile


 


在大多数流行的命令行环境下,第一个命令假定程序从cin获得输入,并将输出发送到cout。第二个命令告诉程序从一个名为infile的文件中获得输入,并在名为outfile的文件中产生输出。这个程序应该能够支持以上所有的输入/输出选项。


 







解答


 


1. std::cinstd::cout的类型是什么?


 


简短地回答,cin实际上是:


std::basic_istream<char, std::char_traits<char> >


 


cout实际上是:


std::basic_ostream<char, std::char_traits<char> >


 


下面是较详细的回答,它通过一些标准的typedef和模板向你展示答案的来龙去脉。首先,cincout具有的类型分别是std::istreamstd::ostream。接着,这些类型是std::basic_istream<char>std::basic_ostream<char>typedef。最后,考虑到模板参数的默认值,我们得到上面的答案。


 


注意:如果你使用的iostream子系统是C++标准制定之前的实现版本,你可能还会看到一些中间类(intermediate class),例如istream_with_assign。但这些类在标准中是不存在的。


 


2. 写一个ECHO程序,让它简单地响应输入,并能通过以下两种方式等效地调用:


  ECHO <infile> outfile


    ECHO infile outfile


 


最精简的方案


 


对于追求精简代码的人来说,最精简的方案莫过于下面这个程序,它仅包含一条语句:



 


// 1-1:惊讶吗?只使用了一条语句


    //


    #include <fstream>


    #include <iostream>


 


    int main( int argc, char* argv[] )


    {


      using namespace std;


 


      (argc > 2


         ? ofstream(argv[2], ios::out | ios::binary)


         : cout)


   <<


      (argc > 1


         ? ifstream(argv[1], ios::in | ios::binary)


         : cin)


      .rdbuf();


    }


 


这个方案之所以可行,得益于两个相辅相成的条件:第一,basic_ios提供了一个方便的rdbuf()成员函数,它返回某个流对象所使用的streambuf,在本例中,这个流对象也就是cin或临时ifstream对象,二者都派生于basic_ios。第二,basic_ostream提供了一个operator<<(),它正好接受这样的basic_streambuf对象,将其作为输入,然后将输入完全读取。正如法国人会说的那样,“C'est ca”(“就是这样!”)。


 


逐步趋向更灵活的方案


1-1中的方案有两个主要缺点:首先,精简会带来晦涩,而且过度的精简不适合应用到产品代码中。


 









设计准则


尽量提高可读性。避免撰写精简代码(即,简洁但难以理解和维护)。避免晦涩。


 


第二,虽然例1-1回答了前面的问题,但只是在对输入进行逐字拷贝的情况下,这种方法才可行。这种功能在目前可能已经够用,但如果将来你需要对输入进行其它处理,例如将字符转换成大写,或是计算字符总数,或删除第三个字符,那该怎么办?这种需要在将来是很合理的;所以,最好我们现在就立即动手,将这些处理工作封装在一个单独的函数中,使这个函数可以多态地(polymorphically)使用正确类型的输入或输出对象:



 


    #include <fstream>


    #include <iostream>


 


    int main( int argc, char* argv[] )


    {


      using namespace std;


 


      fstream in, out;


      if( argc > 1 ) in.open ( argv[1], ios::in  | ios::binary );


      if( argc > 2 ) out.open( argv[2], ios::out | ios::binary );


 


      Process( in.is_open()  ? in  : cin,


               out.is_open() ? out : cout );


    }   


 


但如何实现Process()?在C++中,主要有四种方法获得多态行为:虚函数、模板、重载和转换。其中,前两种方法可以直接用在这里,用以表达我们所需要的那种多态。


 


方法A:模板(编译时多态)


第一种方法使用的是编译时多态,这需要借助于模板;它只需要被传递的对象有一个合适的接口(例如一个名为rdbuf()的成员函数):


 


    // 1-2(a):模板化的Process()


    //


    template<typename In, typename Out>


    void Process( In& in, Out& out ) {


      // ... 执行某种更复杂的操作,


      //     或只是简单的“out << in.rdbuf();...


    }


 


方法B:虚函数(运行时多态)


第二种方法使用的是运行时多态,它需要一个条件,即,存在一个具有合适接口的公共基类:


 


    // 1-2(b):第一次尝试,一定程度上可行


    //


    void Process( basic_istream<char>& in,


                  basic_ostream<char>& out )


    {


      // ... 执行某种更复杂的操作,


      //     或只是简单的“out << in.rdbuf();...


    }



 


注意,在例1-2(b)中,Process()的参数类型不是basic_ios<char>&,因为那将不允许使用operator<<()


 


毫无疑问,例1-2(b)中的方法具有依赖性,它要求输入和输出流必须分别从basic_istream<char>basic_ostream<char>派生。这一点对我们的例子来说还不错,但要知道,并非所有的流都基于简单的char或者char_traits<char>。例如,宽字符流基于wchar_tExceptional C++ [Sutter00]的条款23也演示了一些具有不同行为特征的用户自定义traits(在那些例子中,ci_char_traits提供了大小写不分的行为特征),并展示了其潜在的用途。


 


因而,即使是采用方法B,我们也应该使用模板,让编译器去推导出合适的参数:


 


  // 1-2(c):更好的方案


    //


template<typename C, typename T>


  void Process( basic_istream<C,T>& in,


                   basic_ostream<C,T>& out )


  {


      // ... 执行某种更复杂的操作,


      //     或只是简单的“out << in.rdbuf();...


   }


 


有效的工程设计原则


就其本身而言,以上所有答案都是“正确”的;但在目前场合下,我个人倾向于选择方法A。其原因归结于两条很有价值的设计准则。第一条是:


 









设计准则


尽量提高可扩充性。


 


避免写出的代码只能解决当前问题。几乎任何时候,若能写出可扩充的方案,那将是更佳选择——当然,只要我们不太过分。


 


均衡的判断力是有经验的程序员所具有的一个特征。尤其是,在“编写专用代码,只解决当前问题”(短视,难以扩充)和“编写一个宏大的通用框架去解决本来应该很简单的问题”(追求过度设计)之间,有经验的程序员懂得如何去获取最佳的平衡。


 


较之例1-1中的方案,方法A具有大致相同的整体复杂度,但除此之外,后者还更易于理解、更具可扩充性。较之方法B,方法A既简单又更具灵活性;它更能适应新的要求,因为它没有了束缚,不只是能和iostream体系打交道。



 


所以,如果存在两个选择,它们在设计和实现中需要的工作量相同,而且具有大致相当的清晰度和可维护性,那么,请尽量考虑可扩充性。这条建议并不是在教唆你,让你去对一个本来很简单的问题大动干戈——这方面大家以前已经做得够多了。相反,这条建议是一条鼓励:如果稍微思考一下就可以发现,自己正在解决的问题实际上是某个更通用的问题的特例,你就应该多做一些工作,而不要仅仅满足于解决当前问题。这条建议十分正确,因为,在设计中提高了可扩充性,往往意味着同时提高了封装性。


 


 









设计准则


尽量提高封装性。将关系分离。


 


只要有可能,一段代码——函数或类——应该只知道并且只负责一件事。


 


可以证明,方法A最出色的地方在于:它展示了关系之间有效的分离。它包括两部分代码,一部分代码知道输入/输出源(source)和目标(sink)中可能的区别,另一部分代码知道如何真正执行处理,这两部分代码被分离开来。这种分离也使得代码的用途更清晰,更易于他人阅读和理解。将关系进行有效的分离是好的工程设计的另一个特征,在本书条款中,我们将不断地看到这一点。


 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
### 回答1: exceptional c指的是超乎寻常的C语言,即在使用C语言进行编程时,达到了非凡的水平和能力。这包括但不限于熟练掌握C语言的语法和特性,理解C语言的底层机制和内存管理,编写高效且可靠的代码,解决复杂而困难的问题。拥有exceptional c能力的程序员通常能够编写高性能的程序,优化代码,改善资源利用效率,提高软件的质量和可维护性。 而more exceptional c则进一步指的是在exceptional c的基础上,更进一步的提升和发展。它可能包括对C语言更深层次的理解,对C语言编程范式、设计模式和最佳实践的掌握,能够设计和实现复杂的数据结构和算法,以及利用C语言的扩展功能和库实现创新的解决方案。 与exceptional c相比,more exceptional c更注重在C语言的高级特性上的应用,如面向对象的编程,泛型编程,元编程等。通过更深入的学习和研究,更出色的C程序员可以利用这些高级技术,进一步提高代码的可读性,可维护性和重用性。 总而言之,exceptional c是对熟练掌握C语言编程的基本要求,而more exceptional c则代表在此基础上进一步提升和发展,拥有更高级的技能和能力。 ### 回答2: exceptional c是指非常优秀的C语言代码,具有出色的设计和实现,能够在效率、可靠性和可读性等方面表现出色。这种代码通常具有良好的算法和数据结构选择,以及正确的错误处理机制。 而more exceptional c是指比exceptional c更加出色的C语言代码,更具有创新和突破性。它可能包含了前沿的编程技术、高级的数据结构和算法,以及更为复杂和深入的问题解决方案。这种代码可能会利用C语言的底层优势,充分发挥计算机性能并提供更高的效率。 在开发过程中,编写exceptional c代码需要遵循良好的编程习惯和规范,保持代码的可维护性和可扩展性。这种代码通常具有清晰的结构和注释,函数和变量命名规范恰当。同时,编写exceptional c代码也要注意错误处理和异常处理,确保代码的健壮性和可靠性。 而more exceptional c则需要更深入的编程知识和技巧。它可能涉及更多的底层编程,例如操作系统级别的代码或者底层硬件的访问。同时,more exceptional c还可能包含更复杂的算法和数据结构,以及高级的设计模式和架构思想。编写more exceptional c代码需要对C语言的特性和底层工作原理有深刻的理解和掌握。 总之,exceptional c和more exceptional c都是指在C语言中表现出色的代码,但more exceptional c更进一步,采用更高级的技术和解决方案,展现出更出色的能力和创新精神。 ### 回答3: exceptional c是指非常出色的c,可能是指一个人的能力或者品质在某个方面超越了常人,表现出非凡的才华或者特点。它强调了c的某种特别之处,使得它在众多同类中脱颖而出。 而more exceptional c则意味着更加出色的c,即在exceptional c的基础上进一步提升,突破了先前的界限,表现出更加突出的表现和才能。它可以指一个人在原有的基础上通过努力和不断学习进步,取得了更加卓越的成就,或者是指一个事物在原有的基础上通过创新和改进,达到了更高的水平。 总之,exceptional c和more exceptional c都表达了某种非凡和出色的意思,但more exceptional c更加强调在原有的基础上取得更高的成就和突破。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

lostmouse

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值