C++ 宏和模板简介
参考《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++ 宏和模板简介的更多相关文章
- WPF三大模板简介(Z)
WPF三大模板简介 WPF支持以下类型的模板: (1) 控件模板.控件模板可以将自定义模板应用到某一特定类型的所有控件,或是控件的某一实例.决定控件外观的是ControlTemplate,它决定了 ...
- Windows Phone 8初学者开发—第10部分:数据绑定应用程序和透视应用程序项目模板简介
原文 Windows Phone 8初学者开发—第10部分:数据绑定应用程序和透视应用程序项目模板简介 原文地址: http://channel9.msdn.com/Series/Windows-Ph ...
- 分布式监控系统之Zabbix宏、模板和自定义item
前文我们聊了下zabbix的基础使用,包括主机的添加.监控项.触发器.action以及告警通知的配置,回顾请参考https://www.cnblogs.com/qiuhom-1874/p/140073 ...
- MVC 之 T4模板简介
个人网站地址:nee32.com 一.T4模板内容简介 为了更好地学习T4模板,我们安装一个插件tangible T4 Editor 在使用了EF生成实体类后,我们会发现一个.tt后缀的文件,它就是T ...
- Visual Studio宏注释模板
前言 有时写代码需要写注释的时候 甚是苦恼 写吧 怕麻烦 不写吧 似乎这代码估计自己都看不懂 权衡之下 似乎找一个自动写注释的方法最靠谱 一直在VS下开发 偶尔听人说过有一个宏工具可以帮助开发者快速注 ...
- flask模板结构组织(局部模板、宏、模板继承)
模板结构组织 除了使用函数.过滤器等工具控制模板的输出外,jinja2还提供了一些工具来在宏观上组织模板内容. 局部模板 在Web程序中,我们通常会为每一类页面编写一个独立的模板.比如主页模板.用户资 ...
- WPF三大模板简介
WPF支持以下类型的模板: (1) 控件模板.控件模板可以将自定义模板应用到某一特定类型的所有控件,或是控件的某一实例.决定控件外观的是ControlTemplate,它决定了控件“长成什么样子”,因 ...
- Django模板简介
在settings.py中有个TEMPLATES的设置,其中BACKEND用来配置Django模板引擎, DIRS 定义了一个目录列表,模板引擎按列表顺序搜索这些目录以查找模板源文件 一般我们都会把模 ...
- Vue开发模板简介
1. 传统发开模式的问题 用传统模式引用vue.js以及其他的js文件的开发方式,会产生一些问题. 基于页面的开发模式:传统的引用vue.js以及其他的js文件的开发方式,限定了我们的开发模式是 ...
随机推荐
- SDOI2015做题记录
由于我懒,并且这里面除了D2T3恶心以外都不难写,所以很多代码都没写-- 排序 对于某一个合法的操作序列(操作序列定义为每次交换的两组数),可以随意交换顺序,仍然合法.所以对于一个操作集合,答案就加\ ...
- flutter 监听返回
在项目中遇到了一个场景,A页面必须返回某个tab页,但是A页面可能会调到B,再跳到C,最后回到A.这个时候A的返回肯定是C. 想了一些解决方案,都不如监听A页面的实体键返回或者虚拟键返回来的快速便捷. ...
- js将数组分割成等长数组
方法一: function group(array, subGroupLength) { let index = 0; let newArray = []; while(index < arra ...
- redis 业务锁 not exist 模式
背景: 业务核心模块只能提交一次,原实现方案 前端加提交限制.后端加数据库业务逻辑判定,结果失效,api站点部署多台负载,切方法需要强求第三方接口 响应时间较慢 ,故放弃lock. 解决方案:redi ...
- if ( ! defined('BASEPATH')) exit('No direct script access allowed')的作用
在看源代码时,发现codeigniter框架的控制器中,总是加上这样一段话: if(!defined('BASEPATH'))exit('No direct script access allowed ...
- 【转】iPhone手机获取uuid 安装测试app
iPhone手机获取uuid 安装测试app UDID是一种iOS设备的特殊识别码.除序号之外,每台ios装置都另有一组独一无二的号码,我们就称之为识别码( Unique Device Identif ...
- Nutch、Scrapy、Lucene、Heritrix、Solr、Sphinx
Nutch.Scrapy.Lucene.Heritrix.Solr.Sphinx
- Spring Junit4 接口测试
Junit实现接口类测试 - dfine.sqa - 博客园http://www.cnblogs.com/Automation_software/archive/2011/01/24/1943054. ...
- rapidjson的简单使用(转)
rapidjson的简单使用 C++ rapidjson 基础入门 rapidjson图文讲解
- JSOUP 爬虫
作者QQ:1095737364 QQ群:123300273 欢迎加入! 1.mavne 依赖: <!--html 解析 : jsoup HTML parser library @ ...