21天学通C++(八)面向对象编程基础4

本文介绍了C++中的预处理器和宏,包括如何使用#define定义常量和宏函数,以及模板的声明、优点和缺点,展示了模板在C++11后的应用,如参数数量可变的模板和static_assert。最后提到了模板在标准模板库(STL)中的重要角色。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

宏和模板简介

1.1 预处理器和编译器

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

// instruct preprocessor to insert contents of iostream here 
#include <iostream> 
// define a macro constant 
#define ARRAY_LENGTH 25 
int numbers[ARRAY_LENGTH]; // array of 25 integers 
// define a macro function 
#define SQUARE(x) ((x) * (x)) 
int TwentyFive = SQUARE(5);

        本小节重点介绍两种预处理器编译指令,一是使用#define定义常量,二是使用#define定义宏函数。这两个编译指令都告诉编译器,将每个宏实例替换为其定义的值。

1.2 使用#define 定义常量

        使用#define定义常量的语法非常简单:

#define identifier value

        C++程序员通常在 .h 文件(头文件)中声明类和函数,并在 .cpp 文件中定义函数,因此需要在 .cpp 文件中使用预处理器编译指令 #include<header> 来包含头文件。如果在头文件 class1.h中声明了一个类,而这个类将 class2.h 中声明的类作为其成员,则需要在 class1.h 中包含 class2.h。如果设计非常复杂,即第二个类需要第一个类,则在 class2.h 中也需要包含 class1.h。

        然而,在预处理器看来,两个头文件彼此包含对方会导致递归问题。为了避免这种问题,可结合使用宏以及预处理器编译指令 #ifndef 和 #endif。

1.3 使用#define 编写宏函数

        预处理器对宏指定的文本进行简单替换,因此也可以使用宏来编写简单的函数。例如:

#define SQUARE(x) ((x) * (x))

这个宏计算平方值。

1.3.1 为什么要使用括号

        计算圆面积的宏定义如下:

#define AREA_CIRCLE(r) (PI*(r)*(r))

        上述代码使用了大量的括号。如果省略括号:

#define AREA_CIRCLE(r) (PI*r*r)

        如果用下面的语句调用这个宏:

cout << AREA_CIRCLE (4+6);

        编译器看到的语句将会如下:

cout << (PI*4+6*4+6); // not the same as PI*10*10

        所以通过使用括号,能让宏代码不受运算符优先级的影响。

1.3.2 使用assert宏验证表达式

        编写程序后,立即单步执行以测试每条代码路径很不错,但对大型应用程序来说可能不现实。比较现实的做法是,插入检查语句,对表达式或变量的值进行验证。

        assert宏能够完成这项任务,要使用asser宏,需要包含<assert.h>,其语法如下:

assert (expression that evaluates to true or false);
1.3.3 使用宏函数的优点和缺点
        使用宏函数减少了代码行,这是一种细微的优势,诱使有些程序员使用宏来定义简单函数。宏函数将在编译前就地展开,因此简单宏的性能优于常规函数调用。这是因为函数调用要求创建调用栈、传递参数等,这些开销占用的 CPU时间通常比 MIN 执行的计算还多。
        然而,宏不支持任何形式的类型安全,这是一个严重的缺点。另外,复杂的宏调试起来也不容易。
        如果需要编写独立于类型的泛型函数,又要确保类型安全,可使用模板函数,而不是宏函数。如果要改善性能,可将函数声明为内联的。
尽可能不要自己编写宏函数。
尽可能使用const常量,而不是宏常量。
宏并非类型安全的,预处理器不执行类型检查。
1.4 模板简介
1.4.1 模板声明语法

        模板声明以关键字template打头,接下来是类型参数列表。这种声明的格式如下:

template <parameter list> 
template function / class declaration..

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

template <typename T1, typename T2 = T1> 
bool TemplateFunction(const T1& param1, const T2& param2); 
// A template class 
template <typename T1, typename T2 = T1>
class MyTemplate 
{ 
private: 
 T1 member1; 
 T2 member2; 
public: 
 T1 GetObj1() {return member1; } 
 // ... other members 
};

        上述代码演示了一个模板函数和一个模板类,它们都接受两个模板参数:T1和T2,其中T2的类型默认为T1。

1.4.2 各种类型的模板声明

        模板声明可以是:

  • 函数的声明或定义;
  • 类的定义或声明;
  • 类模板的成员函数或成员类的声明或定义;
  • 类模板的静态数据成员的定义;
  • 嵌套在类模板中的类的静态数据成员的定义;
  • 类或类模板的成员模板的定义。
1.4.3 模板函数

        假设要编写一个函数,它适用于不同类型的参数,为此可使用模板语法!

template <typename objType> 
const objType& GetMax(const objType& value1, const objType& value2) 
{ 
 if (value1 > value2) 
 return value1; 
 else 
 return value2; 
}

下面是一个使用该模板的示例:

int num1 = 25; 
int num2 = 40; 
int maxVal = GetMax <int> (num1, num2); 
double double1 = 1.1; 
double double2 = 1.001; 
double maxVal = GetMax <double>(double1, double2);
1.4.4 模板类

        类是一种编程单元,封装类属性以及使用这些属性的方法。使用模板类,可指定要为哪种类型具体化类。下面是一个简单的模板类,它只有单个模板参数T,用于存储一个成员变量:

template <typename T> 
class HoldVarTypeT 
{ 
private: 
 T value; 
public: 
 void SetValue (const T& newValue) { value = newValue; } 
 T& GetValue() {return value;} 
};

        下面来看该模板类的一种用法:

HoldVarTypeT <int> holdInt; // template instantiation for int 
holdInt.SetValue(5); 
cout << "The value stored is: " << holdInt.GetValue() << endl;

        这里使用该模板类来存储和检索类型为int的对象,即使用int类型的模板参数实例化Template类。同样,这个类也可以用于处理字符串,其用法类似:

HoldVarTypeT <char*> holdStr; 
holdStr.SetValue("Sample string"); 
cout << "The value stored is: " << holdStr.GetValue() << endl;

        因此,这个模板类定义了一种模式,并可针对不同的数据类型实现这种模式。

1.4.5 声明包含多个参数的模板

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

template <typename T1, typename T2> 
class HoldsPair 
{ 
private: 
 T1 value1; 
 T2 value2; 
public: 
 // Constructor that initializes member variables 
 HoldsPair (const T1& val1, const T2& val2) 
 { 
 value1 = val1; 
 value2 = val2; 
 }; 
 // ... Other member functions 
};

在这里,类HoldPair接受两个模板参数,参数名分别为T1和T2。可使用这个类来存储两个类型相同或不同的对象,如下所示:

// A template instantiation that pairs an int with a double 
HoldsPair <int, double> pairIntDouble (6, 1.99); 
// A template instantiation that pairs an int with an int
HoldsPair <int, int> pairIntDouble (6, 500);
1.4.6 声明包含默认参数的模板

        可以修改前面的<HoldsPair<...>,将模板参数的默认类型指定为int:

template <typename T1=int, typename T2=int> 
class HoldsPair 
{ 
 // ... method declarations 
};

这与给函数指定默认参数值及其类似,只是这里指定的是默认类型。

1.4.7 模板的实例化和具体化

        模板类是创建类的蓝图,因此在编译器看来,仅当模板类以某种方式被使用后,其代码才存在。换言之,对于定义了但未使用的模板类,编译器将忽略它。

        当像下面这样通过提供模板参数来实例化模板类时:

HoldsPair<int, double> pairIntDbl;

就相当于命令编译器使用模板来创建一个类,即使用模板参数指定的类型实例化模板。因此,对模板来说,实例化指的是使用一个或多个模板参数来创建特定的类型。

        另一方面,在有些情况下,使用特定的类型实例化模板时,需要显式地指定不同的行为。这就是具体化模板,即为特定的类型指定行为。下面是模板类HoldsPair的一个具体化,其中两个模板参数的类型都为int:

template<> class HoldsPair<int, int> 
{ 
 // implementation code here 
};
1.4.8 模板类和静态成员

        如果将类成员声明为静态的,该成员将由类的所有实例共享。模板类的静态成员与此类似,由特定具体化的所有实例共享。也就是说,如果模板类包含静态成员,该成员将在针对int具体化的所有实例之间共享;同样,它还将在针对double具体化的所有实例之间共享,且与针对int具体化的实例无关。换句话说,可以认为编译器创建了两个版本的x: x_int用于针对int具体化的实例,而x_double针对double具体化的实例。

1.4.9 参数数量可变的模板

        假如要编写一个将两个值相加的通用函数,为此可编写下面这样的模板函数Sum():

template <typename T1, typename T2, typename T3> 
void Sum(T1& result, T2 num1, T3 num2) 
{ 
 result = num1 + num2; 
 return; 
}

这很简单。然而,如果需要编写一个函数,能够计算任意数量值的和,就需要使用参数数量可变的模板。参数数量可变的模板是2014年发布的C++14新增的。

1.4.10 使用static_assert执行编译阶段检查

        static_assert是C++11新增的一项功能,能够在不满足指定条件时禁止编译。例如,可能想禁止针对int实例化模板类,为此可使用static_assert,它是一种编译阶段断言,可用于在开发环境显示一条自定义消息:

static_assert(expression being validated, "Error message when check fails");

要禁止针对类型int实例化模板类,可使用static_assert(),并将sizeof(T)与sizeof(int)进行比较,如果它们相等,就显示一条错误消息:

static_assert(sizeof(T) != sizeof(int), "No int please!");
1.4.11 在实际C++编程中使用模板

        模板是一个重要而最强大的应用是在标准模板库(STL)中。STL由一系列模板类和函数组成,它们分别包含泛型实用类和算法。这些STL模板类能够实现动态数组、链表以及包含键-值对的容器,而sort等算法可用于这些容器,从而对容器包含的数据进行处理。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值