zoukankan      html  css  js  c++  java
  • 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



  • 相关阅读:
    Java mysql数据库连接Demo1
    java JDBC编程流程步骤
    spring boot定时任务
    mysql分页
    mysql limit 偏移量过大效率解决方式 转贴
    svn both sides of the move must be committed together
    An invalid property 'jdbcType ' was found in mapping
    JSON高亮格式化页面显示
    nprogress 转
    org.apache.commons工具类方法解释 转
  • 原文地址:https://www.cnblogs.com/huty/p/8519288.html
Copyright © 2011-2022 走看看