参考《21天学通C++》第14章节,对C++中的宏和模板进行了学习,总结起来其主要内容如下:

(1) 预处理器简介

(2) 关键字#define与宏

(3) 模板简介

(4) 如何编写函数模板和模板类

(5) 宏和模板之间的区别

(6) 使用static_assert进行编译阶段检查

************************************************************************************************************************************

1 预处理器与编译器

预处理器:在编译器之前运行,根据程序员的指示,决定实际要编译的内容,预编译指令都以#打头

典型的应用有如下几种:

(1) 使用#define定义常量

通常在定义数组的长度时用到。其定义的常量只是进行文本替换,并不进行类型检测,比如定义#define PI 3.1416来讲,这个宏PI的类型编译器并不检测,也不知道其到底是float还是double。

定义常量时,更好地选择是使用关键字const和数据类型,比如const double PI = 3.1416 就比使用宏进行定义要好得多。

(2) 使用宏避免多次包含

C++典型的做法是将类和函数的声明放在头文件(.h)中,而在源文件(.cpp)中定义函数,因此需要在.cpp文件中使用#include <header>来包含头文件。如果两个头文件需要相互包含时,在预处理器看来会导致递归问题,解决方案之一就是使用宏以及预处理器编译指令#ifndef和#endif。举例如下:

/*<header1.h>*/

#ifndef HEADER1_H_

#define HEADER1_H_

#include <header2.h>

....................................

....................................

....................................

#endif

header2.h与此类似,但宏定义不同,且包含 header1.h :

/*<header2.h>*/

#ifndef HEADER2_H_

#define HEADER2_H_

#include <header1.h>

....................................

....................................

....................................

#endif

解释说明:#ifndef是一个条件处理命令,让预处理器仅在标识符未定时才继续,#endif告诉预处理器,条件处理指令到此结束。因此,预处理器首次处理header1.h并遇到#ifndef后,发现HEADER1_H_还未定义,因此继续处理。#ifndef后面的第一行定义了宏HEADER1_H_,确保预处理器再次处理该文件时,将在遇到包含#ifndef的第一行时结束,因为其中的条件已变为false。header2.h与此类似。在C++编程领域,这种简单的机制是最常用的宏功能应用。

除此之外,还有另一种保证头文件只被编译一次的方法:#pragma once。只需要在开头写上这条预编译指令,就可以保证所有头文件只被包含编译一次。但是#pragma once是编译器相关的,有的编译器支持,有的编译器则不支持,不过大部分编译器都支持这个预编译指令了。而#ifndef,#define,#endif是C/C++语言中的宏定义,所有支持C/C++语言的编译器上都是有效的,如果编写跨平台程序,最好使用宏的方式来避免头文件的重复包含。

(3) 使用assert断言宏进行调试

编写程序后,立即单步执行测试每种路径似乎很不错,但可能不现实,比较好的做法是插入检查语句,对表达式或变量的值进行验证。assert宏就是用来完成这项任务,使用assert宏需包含<assert.h>,语法如下:

assert(expression that evaluates to true or false);举例:char * test = new char[25]; assert(test != NULL);

assert在指针无效时将指出这一点,在Microsoft Visual Studio 中,assert能够返回应用程序,而调用栈将指出哪行代码没有通过断言测试,这让assert成为一项方便的调试功能。

在大多数开发环境中,assert在发布模式下被禁用,因此它仅在调试模式下显示错误消息。另外在有些开发环境中assert被实现为函数,而不是宏。

(4) 使用#define定义宏函数

预处理器对宏指定的文本进行简单替换,因此可以使用编写简单的函数。宏函数通常用于执行非常简单的计算,相比于常规函数调用,宏函数的优点是它们将在编译前就地展开,有助于改善代码的性能(是不是有点内联函数的感觉?)。因为宏不考虑数据类型,因此使用宏就比较危险,这一点需要考虑到。另外,宏是简单的替换,对于宏函数一定要使用括号保证替换后的功能逻辑正确,这是使用宏函数中经常犯错误的点。

正是宏不进行类型检查,所以使用宏函数可以作用于不同的变量类型。宏函数不像常规函数那样在函数调用时需要创建调用栈、传递参数等,这些开销占用CPU的时间通常比常规函数的执行时间还多。所以,对于简单的函数,通常可以使用宏函数进行。但是宏不支持任何形式的类型安全,而且复杂的宏调试起来不方便。如果比那些独立于类型的泛型函数,又要保证类型安全,可使用模板函数,而不是宏函数,如要改善性能,可以定义为内联函数,使用关键字inline,这样就完全覆盖了宏函数的优势。

小结

尽可能不要自己编写宏函数;尽可能使用const变量,而不是宏常量;请牢记宏并非类型安全的,预处理器不进行类型检查;在宏函数定义中要使用括号将每个变量括起来;避免头文件重复包含编译,可使用宏来解决;别忘了在调试中可以大量使用assert断言,对提高代码质量很有帮助。

************************************************************************************************************************************

2. 模板

模板可能是C++语言中最强大却最少被使用(或被理解)的特性之一。

在C++中,模板允许程序员定义一种适用于不同类型的对象的行为,有一点类似宏,但宏不是类型安全的,而模板是类型安全的。

(1) 模板声明语法

template <parameter list>

template function / class declaration

关键字template标志模板声明的开始,接下来是模板参数列表。该参数列表包含关键字typename ,它定义了模板参数objectType,而objectType是一个占位符,针对对象实例化模板时,将使用对象类型替换它。

(2) 模板声明的类型

模板声明可以是:函数的声明或定义;类的声明或定义;类模板的成员函数或成员类的声明或定义;类模板的静态数据成员的定义;嵌套在类模板中的类的静态数据成员定义;类或类模板的成员模板的定义。

(3) 模板函数

调用模板函数时并非一定要指定类型。举例如下:

template <typename objectType>

const objectType& GetMax(const objectType& value1,const objectType& value2)

{

if(value1 > value2)

return value1;

else

return value2;

}

具体使用该模板的示例:

int Integer1 = 25;

int Integer2 = 50;

使用 int MaxValue  =  GetMax <int> (Integer1 , Integer2 );

与使用int MaxValue  =  GetMax (Integer1 , Integer2 );效果是一样的。这种情况下编译器会进行数据类型检查。但对于模板类则必须显式的指明类型。但是并不能进行像GetMax
(Integer1 , “some string” )这样的混杂类型。这种调用将导致编译器错误。

(4) 模板类

类是一种编程单元,封装属性以及使用这些属性的方法。属性通常是私有成员。当某一属性可以是int型,也可以是long型等时,模板类可派上用场。使用模板类时,可指定哪种类型的具体化。举例如下:

template <typename T>

class Test

{

public:

void SetValue(const T& newValue) { Value = newValue;}

const T& GetValue() const { return Value;}

private:

T Value;

};

模板类的使用方法:

Test <int> Test1;

Test1.SetValue(5);

这样就实现了模板类中同一个属性可以具有不同的数据类型实现,只要在实例化对象时指定实例化对象需要的属性的数据类型就好了。在术语上,使用模板时,实例化指的是根据模板声明以及一个或多个参数创建特定的类型,而实例化创建的特定类型称为具体化。

(5) 声明包含多个参数的模板

模板参数列表包含多个参数,参数之间使用逗号分割。因此,如果要声明一个泛型类用于存储两个类型可能不同的对象,可以使用如下代码:

template <typename T1, typename T2>

class Test

{

private:

T1 Value1;

T2 Value2;

public:

/*some methods*/

/*constructor*/

Test ( const T1& value1, const T2& value2)

{Value1 = value1; Value2 = value2;}

};

使用方法如下:

Test <int, int> Test1(5, 6);//通过构造函数来进行初始化。

(6) 声明包含默认参数的模板

举例如下:

template <typename T1 = int, typename T2 = int>

class Test

{

private:

T1 Value1;

T2 Value2;

public:

/*some methods*/

/*constructor*/

Test ( const T1& value1, const T2& value2)

{Value1 = value1; Value2 = value2;}

};

这与给函数指定默认参数值及其类似。所以,这种指定了默认类型的模板,可以简化实例化过程:

Test < > Test1(5, 6);//通过构造函数来进行初始化。

(7) 模板类和静态成员

如果将类成员声明为静态,该成员将由类的所有实例共享,模板类的静态成员也类似,由特定具体化的所有实例共享。如果模板类T包含静态成员X,该成员将针对int具体化的所有实例之间共享。同样,还将针对double具体化的所有实例之间共享,且针对int具体化的实例无关。编译器创建了两个版本的X,X_int用于针对int具体化的实例,而X_double则针对double具体化的实例。也就是说,对于针对每种类型具体化的类,编译器保证其静态变量不受其他类的影响。模板类的每个具体化都有自己的静态成员。举例如下:

template <typename T>

class TestStatic

{

private:

public:

static int StaticValue;

/*some methods*/

};

// static member initialization

template <typename T> int TestStatic<T>:: StaticValue;

(8) 使用static_assert执行编译阶段检查

可以用来进行设置挑剔的模板类,屏蔽针对某种类型的具体化,使用static_assert进行编译阶段的检查。举例如下:

template <typename T>

class Test

{

private:

public:

/*some methods*/

EverythingButInt()

{

static_assert(sizeof(T) != sizeof(int), " No int please!");

}

};

int main()

{

Test<int> test;

return 0;

}

编译结果输出:

error:No int please!

这个编译结果是由模板类中的static_assert指定的。

小结

务必使用模板来实现通用概念,而不是宏;编写模板函数和模板类时尽可能使用const;模板类的静态成员由特定具体化的所有实例共享。

************************************************************************************************************************************

总结

本文详细介绍了预处理器,在运行编译器时,预处理器都将首先运行,对#define等指令进行转换;预处理器执行文本替换,但在使用宏时替换将比较复杂。通过使用宏函数,可以在编译阶段传递给宏的参数进行复杂的文本替换。将宏中的每个参数放在括号内以确保进行正确的替换,这很重要。模板有助于编写可重用的代码,它向开发人员提供了一种可用于不同数据类型的模式。模板可以取代宏,而且是类型安全的。学习好模板,对于后续使用C++标准模板库STL有着极为重要的概念性基石意义。

************************************************************************************************************************************

2015-7-30

C++ 宏和模板简介的更多相关文章

  1. WPF三大模板简介(Z)

    WPF三大模板简介   WPF支持以下类型的模板: (1) 控件模板.控件模板可以将自定义模板应用到某一特定类型的所有控件,或是控件的某一实例.决定控件外观的是ControlTemplate,它决定了 ...

  2. Windows Phone 8初学者开发—第10部分:数据绑定应用程序和透视应用程序项目模板简介

    原文 Windows Phone 8初学者开发—第10部分:数据绑定应用程序和透视应用程序项目模板简介 原文地址: http://channel9.msdn.com/Series/Windows-Ph ...

  3. 分布式监控系统之Zabbix宏、模板和自定义item

    前文我们聊了下zabbix的基础使用,包括主机的添加.监控项.触发器.action以及告警通知的配置,回顾请参考https://www.cnblogs.com/qiuhom-1874/p/140073 ...

  4. MVC 之 T4模板简介

    个人网站地址:nee32.com 一.T4模板内容简介 为了更好地学习T4模板,我们安装一个插件tangible T4 Editor 在使用了EF生成实体类后,我们会发现一个.tt后缀的文件,它就是T ...

  5. Visual Studio宏注释模板

    前言 有时写代码需要写注释的时候 甚是苦恼 写吧 怕麻烦 不写吧 似乎这代码估计自己都看不懂 权衡之下 似乎找一个自动写注释的方法最靠谱 一直在VS下开发 偶尔听人说过有一个宏工具可以帮助开发者快速注 ...

  6. flask模板结构组织(局部模板、宏、模板继承)

    模板结构组织 除了使用函数.过滤器等工具控制模板的输出外,jinja2还提供了一些工具来在宏观上组织模板内容. 局部模板 在Web程序中,我们通常会为每一类页面编写一个独立的模板.比如主页模板.用户资 ...

  7. WPF三大模板简介

    WPF支持以下类型的模板: (1) 控件模板.控件模板可以将自定义模板应用到某一特定类型的所有控件,或是控件的某一实例.决定控件外观的是ControlTemplate,它决定了控件“长成什么样子”,因 ...

  8. Django模板简介

    在settings.py中有个TEMPLATES的设置,其中BACKEND用来配置Django模板引擎, DIRS 定义了一个目录列表,模板引擎按列表顺序搜索这些目录以查找模板源文件 一般我们都会把模 ...

  9. Vue开发模板简介

    1.    传统发开模式的问题 用传统模式引用vue.js以及其他的js文件的开发方式,会产生一些问题. 基于页面的开发模式:传统的引用vue.js以及其他的js文件的开发方式,限定了我们的开发模式是 ...

随机推荐

  1. SDOI2015做题记录

    由于我懒,并且这里面除了D2T3恶心以外都不难写,所以很多代码都没写-- 排序 对于某一个合法的操作序列(操作序列定义为每次交换的两组数),可以随意交换顺序,仍然合法.所以对于一个操作集合,答案就加\ ...

  2. flutter 监听返回

    在项目中遇到了一个场景,A页面必须返回某个tab页,但是A页面可能会调到B,再跳到C,最后回到A.这个时候A的返回肯定是C. 想了一些解决方案,都不如监听A页面的实体键返回或者虚拟键返回来的快速便捷. ...

  3. js将数组分割成等长数组

    方法一: function group(array, subGroupLength) { let index = 0; let newArray = []; while(index < arra ...

  4. redis 业务锁 not exist 模式

    背景: 业务核心模块只能提交一次,原实现方案 前端加提交限制.后端加数据库业务逻辑判定,结果失效,api站点部署多台负载,切方法需要强求第三方接口 响应时间较慢 ,故放弃lock. 解决方案:redi ...

  5. if ( ! defined('BASEPATH')) exit('No direct script access allowed')的作用

    在看源代码时,发现codeigniter框架的控制器中,总是加上这样一段话: if(!defined('BASEPATH'))exit('No direct script access allowed ...

  6. 【转】iPhone手机获取uuid 安装测试app

    iPhone手机获取uuid 安装测试app UDID是一种iOS设备的特殊识别码.除序号之外,每台ios装置都另有一组独一无二的号码,我们就称之为识别码( Unique Device Identif ...

  7. Nutch、Scrapy、Lucene、Heritrix、Solr、Sphinx

    Nutch.Scrapy.Lucene.Heritrix.Solr.Sphinx

  8. Spring Junit4 接口测试

    Junit实现接口类测试 - dfine.sqa - 博客园http://www.cnblogs.com/Automation_software/archive/2011/01/24/1943054. ...

  9. rapidjson的简单使用(转)

    rapidjson的简单使用 C++ rapidjson 基础入门 rapidjson图文讲解

  10. JSOUP 爬虫

    作者QQ:1095737364    QQ群:123300273     欢迎加入! 1.mavne 依赖: <!--html 解析 : jsoup HTML parser library @ ...