c 代码示例大全(简单好玩的编程代码)

c  代码示例大全(简单好玩的编程代码)

泛型编程基本概念

C 支持多种程序设计方法(编程方法),比如面向过程编程,面向对象编程,模板与泛型编程(简称泛型编程),元编程等等,其实元编程也可以归属到泛型编程之中,只不过元编程用到的一些编程技巧相对特殊。

在日常编程中最常用的是面向对象编程,通常要实现许多业务逻辑时,采用面向对象编程基本就够了;次常用的是泛型编程,泛型编程具有独特的特点,其中最突出的特点就是在增加代码复用性和减少代码冗余方面,不可替代。虽然泛型编程的学习难度相对比较大,但是每一个希望成为C 开发高手的人,都应该努力学好泛型编程知识。

泛型编程的英文表达是“Generic Programming”,简称GP。例如,大家最熟悉的C 标准库、Boost库都广泛地使用泛型编程,甚至可以把C 标准库(容器、分配器、迭代器、算法、函数对象、适配器)、Boost库看成是用泛型编程实现的商业作品。也有人把泛型编程看成是由模板技术衍生出来的,这些观点都普遍存在。

(1)泛型编程的含义:以独立于任何特定类型的方式编写代码。

(2)泛型编程涉及的技术:模板技术是泛型编程的基础,或者说泛型编程就是运用模板进行编程。

很多人都知道面向对象编程有三大特性:封装、继承与多态。但是不要把这种编程特性和思维带到泛型编程中来,泛型编程有很多独特的编程方法,让人耳目一新。

书籍介绍

泛型编程方面的知识比较庞杂,C 新标准中也不断增加很多新的语法规则,目前在国内,支持C 11/14/17新标准的模板与泛型编程类书籍资料非常稀少,一些同类书籍资料的出版的时间大多都在十数年前,比较老旧,这给广大渴望学习模板与泛型编程开发知识的人造成了很大的不便。本书的出版很好的填补了这方面的空白。书中内容本着尽量把最常用的知识讲述出来的目标,对一些特别偏难怪且很少用到的知识,书中不会涉猎。

本书有如下比较明显的特点:

(1)注重细节,注重基础,把很多可能让人觉得比较陌生但确是基础性的知识以各种范例的形式展示出来,为读者打下良好的深入学习基础。

(2)讲解难度适宜,范例由浅入深,读者容易理解,容易消化,读起来顺畅没有障碍。

(3)内容比较全面:有着笔者前几本C 新经典类书籍作为基础,可以非常平滑的继续进行本书的阅读学习,不存在知识体系断裂导致学习者产生突兀、茫然之感。整个书籍无论是基础知识,还是高级知识以及演示范例,所涉及的知识点都非常到位非常全面。

(4)目前在国内,支持C 11/14/17新标准的模板与泛型编程类书籍还非常少,一些同类书籍出版的时间大多都在十数年前,比较老旧,这给广大渴望学习模板与泛型编程开发知识的读者造成了很大的不便。而本书的出版,很好的弥补了目前国内缺少C 11/14/17新标准发布后的“C 模板与泛型编程”类书籍的空白,是一本非常具有价值和意义的图书。

本书包含如下几部分内容:

一、模板基础知识:涉及函数模板、类模板、变量模板、别名模板、可变参模板等诸多的基础概念以及奇异的递归模板模式与混入模式两种常用的编程手法。

二、模板进阶知识:以万能引用概念作为讲解起点,进一步介绍函数模板类型推断和auto类型推断的概念、完美转发的概念和实现,通过对标准库中std::enable_if编译期的分支逻辑的讲解引入SFINAE概念。

三、标准库的典型内容:将介绍一些C 标准库中典型且常用的函数模板、类模板、别名模板等功能组件以及它们的实现细节,这些基础组件在模板与泛型编程中会被频繁使用。

四、萃取(trait)技术与策略(policy)技术:用萃取技术和策略技术生成的诸多模板广泛分布于C 标准库中,为程序人员的开发工作提供了极大的方便。通过诸多范例展示这两种技术在模板与泛型编程中的实际运用。

五、元编程:元编程的主要目的在于将各种计算从运行期提前至编译期进行以达到程序运行时性能提升的目的,是一种增加程序的编译时间从而减少程序运行时间的一种编程技术。会以两个经典的范例——typelist(类型列表)和tuple(元组)来展示元编程的代码编写手法,使大家对元编程能够达到的效果有比较深刻的认识。

六、STL标准模板库代码简析:通过模板与泛型编程技术编写了标准模板库STL中list容器以及配套迭代器的源码,让大家对这类核心的STL部件实现机制有更深刻的了解。还会对容器中迭代器分类目的进行剖析,最后,通过讲解一个算法find_if的实现源码让大家掌握如何将可调用对象当做算法中的参数来使用。

小源码大功能

《C 新经典:模板与泛型编程》一书中介绍了很多有趣的知识点,试举一例看看。

C 模板与泛型编程中有些实现源码虽然非常简单,却可以实现难以想象的功能。一起来看一看std::void_t。

C 17引入了std::void_t,这其实是一个别名模板,它的源码非常简单,大概如下所示:

template<typename…Args>

using void_t = void;表面看起来,这两行代码不起眼,无论给进去什么类型,也无论给进去多少个类型,都会变成void类型。但是它却具有比较神奇的功能,它能够检测到应用SFINAE(替换失败并不是一个错误)特性时出现的非法类型,换句话说,给进去的类型必须是有效的类型,不能是非法类型。3.1 利用std::void_t判断类中是否存在某个类型别名看一个演示范例,随便定义两个类,代码如下:structNoInnerType{int m_i;};structHaveInnerType{using type = int;void myfunc() {}};可以看到,HaveInnerType类中使用using给int类型定义了一个别名(类型别名)叫type而NoInnerType类则没有。现在,利用void_t来写一个类模板HasTypeMem,来判断某个类中是否定义了一个type别名(HaveInnerType类中定义了type别名,而NoInnerType类中没有定义type别名),看看代码怎样写://泛化版本template<typename T, typename U =std::void_t<>>structHasTypeMem: std::false_type//struct默认是public继承,class默认是private继承{};//特化版本template<typename T>structHasTypeMem<T,std::void_t<typename T::type>> : std::true_type{};可以看到,泛化版本的HasTypeMem的父类是std::false_type。该版本有两个模板参数,其中第二个模板参数有默认值std::void<>。特化版本的HasTypeMem的父类是std::true_type,注意HasTypeMem后面紧跟着的尖括号内的内容,是核心代码。在main主函数中,加入如下测试代码:cout << HasTypeMem<NoInnerType>::value << endl;cout << HasTypeMem<HaveInnerType>::value << endl;执行起来,看一看结果:01从结果可以看到,程序正确的判断出来NoInnerType类不带type别名而HaveInnerType带有type别名。所谓是否带type别名,实际上就是测试是否有“类名::type”这样一个成员存在(注意type不能使用protected或者private修饰——class内的默认修饰符是private,struct内的默认修饰符是public)。HasTypeMem代码的工作原理并不复杂,因为其泛化版本的第二个类型模板参数有默认值,所以使用HasTypeMem时后面的尖括号中可以只给一个模板参数,此处模板参数T代表的类是NoInnerType或者HaveInnerType,如果T代表的类中带有type别名,那么HasTypeMem的特化版本就会满足条件,被优先考虑采用,因为该特化版本的父类是std::true_type类型,std::true_type类型正好有一个类型为static constexpr(静态常量)的value成员,其值固定为1,所以输出结果为1。而如果T代表的类中没有type别名,那么根据SFINAE原则,HasTypeMem的特化版本会被无视,此时只有HasTypeMem的泛化版本可供匹配,因为该泛化版本的父类是std::false_type类型,std::false_type类型正好有一个类型为static constexpr的value成员,其值固定为0,所以输出结果为0。上面这个范例有一个局限性,就是HasTypeMem的特化版本中,::type这个别名是写死的,如果要判断的别名不叫type而是叫作sizetype,那么用HasTypeMem没法判断,所以这就大大限制了HasTypeMem的实用性和通用性,可以写一个宏来解决这个问题://带参数的宏定义,注意“反斜杠”表示下一行接着本行来,是本行的一部分#define_HAS_TYPE_MEM_(parMTpNm) \template<typename T, typename U = std::void_t<> > \struct HTM_##parMTpNm : std::false_type { }; \template<typename T> \struct HTM_##parMTpNm<T, std::void_t<typename T::parMTpNm>> : std::true_type { };上面代码是个带参数的宏定义_HAS_TYPE_MEM_,其中##的作用是用来连接两个字符串。如何使用这个宏定义?一般需要在main主函数之外(比如某个.cpp源文件的上面位置)来使用,为什么要这样使用呢?因为这个宏定义是在定义类模板(一个泛化的类模板以及一个特化的类模板),而类模板的定义显然不能放在main主函数中。在main主函数的上面位置,加入如下代码:_HAS_TYPE_MEM_(type)_HAS_TYPE_MEM_(sizetype)上面两行代码,是用于判断某个类中是否存在type别名以及是否存在sizetype别名。其中第一行宏展开后相当于定义了如下类模板(泛化版本 特化版本):template<typename T, typename U = std::void_t<> >structHTM_type: std::false_type { };template<typename T>structHTM_type<T, std::void_t<typename T::type>> : std::true_type { };第二行宏展开后相当于定义了如下类模板(泛化版本 特化版本):template<typename T, typename U = std::void_t<> >structHTM_sizetype: std::false_type { };template<typename T>structHTM_sizetype<T, std::void_t<typename T::sizetype>> : std::true_type { };所以,这两行对宏定义的使用,定义了两个不同名字的类模板,分别是HTM_type和HTM_sizetype,这一点必须要有清醒的认识。在main主函数中,加入如下代码就可以判断NoInnerType和HaveInnerType类中是否包含type以及sizetype别名:cout << HTM_type<NoInnerType>::value << endl;cout << HTM_type<HaveInnerType>::value << endl;cout << HTM_sizetype<NoInnerType>::value << endl;cout << HTM_sizetype<HaveInnerType>::value << endl;执行起来,看一看结果:01003.2 判断类中是否存在某个成员变量再举一例,与上面这个范例非常类似。范例是判断某个类中是否包含某个成员变量——判断某个类中是否包含一个名字叫作m_i的成员变量,看代码://泛化版本template<typename T, typename U = std::void_t<> >struct HasMember :std::false_type{};//特化版本template<typename T>struct HasMember<T, std::void_t<decltype(T::m_i)>> :std::true_type{};在main主函数中,继续加入如下测试代码:cout << HasMember<NoInnerType>::value << endl;cout << HasMember<HaveInnerType>::value << endl;执行起来,看一看新增代码的结果:10一切正常。当然,HasMember这个类模板当前只能判断m_i这个成员变量是否存在,同样可以用宏定义的手段来解决它的实用性和通用性问题,有兴趣可以自己尝试书写。HasMember 代码的工作原理其实与HasTypeMem非常类似,但在这里增补一些解释文字与上面对HasTypeMem工作原理的解释形成互补:当遇到代码行cout << HasMember<NoInnerType>::value << endl;时,编译器首先会去看HasMember的特化版本是否满足需求,因为NoInnerType类中有一个名字叫作m_i的成员,所以,特化版本中的std::void_t<decltype(T::m_i)>这种写法是合法的或者说是有效的(SFINAE特性),或者换句话说,从语法上来讲,HasMember的特化版本是没问题的,因此就选择此特化版本。此时,std::void_t<decltype(T::m_i)>这种写法会被推断为void,模板HasMember产生的实例化代码为HasMember<NoInnerType,void>。因为特化版本的HasMember继承自std::true_type,因此,该cout代码行输出的结果为1。当遇到代码行cout << HasMember<HaveInnerType>::value << endl;时,编译器首先会去看HasMember的特化版本是否满足需求,因为HaveInnerType类中并没有一个名字叫作m_i的成员,所以,特化版本中的std::void_t<decltype(T::m_i)>这种写法并不合法,根据SFINAE特性,编译器不会选择此特化版本(该特化版本会被无视/丢弃掉),此时考虑HasMember的泛化版本,该泛化版本没问题,因此就选择此泛化版本。此时,std::void_t<>这种写法会被推断为void,模板HasMember产生的实例化代码为HasMember<HaveInnerType,void>,因为泛化版本的HasMember继承自std::false_type,因此,该cout代码行输出的结果为0。3.3 判断类中是否存在某个成员函数其实判断类中是否存在某个成员函数的写法与判断类中是否存在某个成员变量的写法很类似,看代码://泛化版本template<typename T, typename U = std::void_t<> >struct HasMemFunc : std::false_type{};//特化版本template<typename T>struct HasMemFunc<T, std::void_t<decltype(std::declval<T>().myfunc())>> : std::true_type{};上面代码用来判断类中是否包含名字叫作myfunc的并且不带参数的成员函数。注意特化版本中decltype后面圆括号中的内容,是核心的判断代码。这里使用了std::declval,从语义上来讲,std::declval<T>()是要创建一个临时的T类对象(实际并没创建该临时对象),这样才能写出调用myfunc成员函数这种代码来。在main主函数中,继续加入如下测试代码:cout << HasMemFunc<NoInnerType>::value << endl;cout << HasMemFunc<HaveInnerType>::value << endl;执行起来,看一看新增代码的结果:01

总结作为C 新经典系列图书的第四本——《C 新经典:模板与泛型编程》,面对的是希望系统学习C 模板与泛型编程、元编程相关开发知识的中高级C 开发者。本书的编写初衷是帮助读者解决各种模板与泛型编程中的语法问题,以免读者在阅读他人所写的泛型编程代码(如C 标准库代码、Boost库代码)时磕磕绊绊甚至进行不下去。此外,书中也会对一些比较有商业价值的源码进行适当的分析和讲解,以进一步提高读者的实战能力,更好的让读者利用模板与泛型编程技术编写可复用的高质量代码以及供第三方开发者使用的接口库。这本书为在C 学习道路上有进取心、希望成长为高手的C 程序员所写。如果读者希望能够阅读诸如C 标准库、Boost库等的源码来极大的提高自己的开发实力,阅读一些大师的C 作品来促使自己技术上的提升,写出高可复用性的代码,甚至有志于写出很棒的程序库作品供第三方使用,那么毫无疑问,这本书将为读者在泛型编程方面打下良好的基础。

相关图书

发表评论

登录后才能评论