Unix/Linux C++应用开发-C++模板

12.1 C++高级编程之模板简介

C++模板机制的出现增强了软件可重用性,反应到具体的应用程序中最简单的表现是程序代码实现同一功能性操作只需要定义实现一次,就可以处理不同类型的数据C++中模板机制最直接表现形式为通用函数以及通用类的定义实现。在通用函数与通用类的定义中,可以通过将函数处理以及类操作处理的不同数据类型定义为一个模板参数。因此通过这样一种实现机制,通用函数与通用类将会用于实现不同数据类型的同一类功能,而不需要针对每种需要处理的数据类型定义实现具体处理代码。

本章讲述的模板机制将主要包括函数模板与类模板两种形式。由于C++是一门通用型语言,其编程方式主要包括面向过程以及面向对象两种。模板机制部分的讲述将主要包括模板的基本概念、基本定义实现形式、通用函数与通用类的实现与应用情况。

12.2  C++函数模板

C++过程化程序设计中,针对同一功能性函数处理不同类型的数据时,通常需要定义多个同样的功能性函数代码体。根据传入函数的参数类型的不同采用函数重载来解决该需求。而C++函数模板概念的出现很好的解决了针对不同数据类型实现同一函数功能的需求。通过函数模板的定义,实现了只需要通过一次函数代码体的定义实现,即可操作不同类型的数据的同一功能性操作。大大缩减了为解决该类需求而急剧膨胀的代码体。除了增强软件的可重用性同时,也利于编写出简洁可维护的应用程序。

12.2.1  函数模板基本概念

函数模板定义需要使用关键字template,紧随模板关键字之后的是尖括号“<>”,尖括号中为使用逗号分隔开的函数模板参数表。函数模板定义语法形式如下所示。

template<class Type,…>

return_type function_name(parameter list);

template<typename Type,…>

return_type function_name(parameter list);

函数模板定义中,关键字template之后是函数模板定义的参数表。其中通过关键字classtypename定义模板的参数,模板参数可以是任何内置数据类型、自定义类类型以及枚举类型等。模板参数表要求多个参数定义之间参数名称不能相同。而随后定义的函数声明与普通函数并无不同,只是函数的返回值类型、函数的头中参数表具体参数的类型都可以用上述模板参数来替换。下面通过一些模板函数定义实例,演示函数模板基本定义实现。

//相同参数类型数据求和函数定义

template<class Type1>

Type1 computeSum(Type1 a,Type1 b)

{

         return(a+b);

}

//不同参数类型数据求和函数定义

template<typename Type1, typename Type2>

Type1 computeSum(Type1 a,Type2 b)

{

         return(a+b);

}

上述简单定义函数模板实例主要用于计算两个类型数据相加之和。首先通过template关键字定义函数模板computeSum实现同类型数据之和计算功能。该模板参数通过class关键字定义为Type1,函数部分定义与普通函数相同,由返回值Type1类型以及两个同Type1类型的函数参数变量ab组成。函数体中执行代码为直接返回传入Type1类型参数变量值的相加结果。

第二个模板函数实例则使用typename关键字定义两个模板参数类型,分别为Type1Type2。该函数同样实现两数求和的功能。其中传入函数的两个参数数据类型不同,程序最终返回的将会根据函数返回类型作一定的转换。这里需要注意的是类型的转换可能会为计算的精度带来一定的影响。

模板定义中需要注意模板参数不能重复定义,尖括号中的模板参数可以定义多个之间采用逗号分隔符隔开。另外需要注意的是模板参数定义的参数名称在该函数定义中不允许有相同名称的变量或对象的定义。如下几种模板函数声明定义都是错误不允许的,在实际软件编程中需要细心的注意避免。

//模板参数类型重复定义错误

template<class Type,class Type>

Type computeSum(Type a,Type b);

//模板参数类型被当作变量名称定义错误

template<class Type>

Type computeSum(Type a,Type b)

{

         intType;

         …

}

//模板参数定义必须每个参数类型名称前都加上关键字class或者typename

Template<class Type1,Type2…>

Type1 computeSum(Type1 a,Type2 b);

模板函数同样可以使用inline关键字定义该函数为内联形式,该关键字直接放置在函数定义的返回值之前即可,其关键字位置与非模板函数相同。下例将计算两数之和模板函数定义为内联函数。

template<class Type>

inline Type computeSum(Type a,Type b)

{

         return(a+b);

}

从上述内容讲述来看,函数模板主要用于解决实现同一功能不同类型数据处理的需求。与重载函数定义不同的是,函数模板只需要定义一份功能函数代码,通过实际定义传入的实参类型自动替换函数中参与功能运算的数据的类型。另外宏的定义也可以实现类似函数模板的功能,但是函数模板由于比宏拥有类型的安全检查机制,相对来讲比起使用宏定义来实现类似的需求,函数模板显得更加的方便安全。

12.2.2  函数模板定义实现

上小节中已经讲述过基本函数模板的定义语法以及定义实现中需要注意的方面。本小节主要介绍函数模板的运用情况,即函数模板调用时的实例化概念。函数模板实例化是指应用程序调用过程中,根据实际传入的实参数据类型,来产生对应处理的功能函数的过程。实例化的过程是由编译器负责实现的,至于编译器怎么去实现函数模板的实例化,在此不作详细讲述。有兴趣的初学者可以深入去学习。

下面将会通过一个完整实例的两种实现定义,演示实际应用程序中函数模板实例化的处理和不同的编译方式,让初学者从中直观的理解函数模板的优势之处。该实例依然采用多文件方式编写程序。

1.包含编译模式

在函数模板定义实现方面,C++中主要支持包含模式与分离模式两种编译方式。在包含编译模式下往往将函数模板的声明与定义统统放置于头文件中,类似于内联函数定义实现方式。而在分离编译模式下则允许函数模板声明在头文件中,定义置于具体包含声明头文件的源文件中。需要注意的是在函数模板定义时,需要在template关键字前加上export关键字。编译器通过此关键字保证在生成函数模板实例时能够准确的链接到对应的函数定义上。

包含编译模式下的实例创建从代码编写开始,首先初学者通过打开UE工具,创建新的空文件并且另存为chapter1201_01.hchapter1201_01.cpp。该代码文件随后会同makefile文件一起通过FTP工具传输至Linux服务器端,客户端通过scrt工具访问操作。程序代码文件编辑如下所示。

//函数模板实例化程序头文件chapter1201_01.h

#ifndef TESTTEMPLATE_H

#define TESTTEMPLATE_H

#include <iostream>

using namespace std;

//定义实现计算两数据之和的函数模板

template<typename Type>

Type computeSum(Type a,Typeb)

{

         return (a+b);

}

//定义实现两数比较计算较小数的函数模板

template<typename Type>

Type computeMin(Type a,Typeb)

{

         return a<b?a:b;

}

//定义实现交换两数据位置的函数模板

template<typename Type>

Type computeswap(Type&a,Type &b)

{

         Type temp = b;

         b = a;

         a = temp;

}

#endif

//函数模板实例化源文件chapter1201_01.cpp

#include"chapter1201_01.h"

 

int main()

{

         int a,b;                                                                                                                       //定义整型变量ab

         a = 4,b = 5;                                                                                                               //分别为整型变量ab初始化赋值

         cout<<"Two int data'ssum:"<<computeSum(2,3)<<endl;                              //调用计算两数之和函数打印结果

         cout<<"Two double data's sum:"<<computeSum(2.5,3.7)<<endl;

         cout<<"Two int data'smin:"<<computeMin(3,9)<<endl;                                 //调用两数比较返回小的数据函数

         cout<<"Two double data'smin:"<<computeMin(3.1,9.8)<<endl;

         cout<<"Change two databefore:"<<"a = "<<a<<""<<"b = "<<b<<endl;//     //打印交换位置前的实参值

         computeswap(a,b);                                                                                                //调用交换实参位置函数

         cout<<"Change two dataafter:"<<"a = "<<a<<""<<"b = "<<b<<endl;          //打印交换位置后的实参值

         return 0;

}

本实例主要通过函数模板的方式演示应用程序中基本的计算功能。程序主要由主函数与3个计算模板函数组成。程序具体剖析见程序注释与后面讲解。

Linux平台下makefile文件编写与前面水果实例无异,可以将其作为模板修改并添加相关代码文件。

OBJECTS=chapter1201_01.o

CC=g++

 

chapter1201_01: $(OBJECTS)

         $(CC) $(OBJECTS) -o chapter1201_01

chapter1201_01.o:chapter1201_01.h

 

clean:

         rm -f chapter1201_01 core $(OBJECTS)

submit:

         cp -f -r chapter1201_01 ../bin

         cp -f -r *.h ../include

上述makefile文件套用前面的模板格式,主要替换了代码文件、程序编译中间文件、可执行程序等。在编译命令部分-g选项的加入,表明程序编译同时加入了可调式信息。下面将会开始运行实例程序。

当前shell下执行make命令,生成可执行程序文件。随后通过make submit命令提交程序文件至本实例bin目录,通过cd命令定位至实例bin目录,执行该程序文件运行结果如下所示。

[developer@localhost src]$make

g++    -c -o chapter1201_01.o chapter1201_01.cpp

g++chapter1201_01.o -o chapter1201_01

[developer @localhost src]$make submit

cp -f -r chapter1201_01../bin

cp -f-r *.h ../include

[developer@localhost src]$ cd ../bin

[developer @localhost bin]$./chapter1201_01

Two int data's sum:5

Two double data's sum:6.2

Two int data's min:3

Two double data's min:3.1

Change two data before:a = 4b = 5

Change two data after:a = 5 b= 4

本实例主要定义实现了三个函数模板。分别用来实现计算两数之和、计算两数中小的数据以及交换两数实参位置的功能。程序采用多文件的实现方式。头文件中通常为函数声明,而源文件中通常为函数的定义实现,只需要在源文件中包含该头文件即可。本实例由于模板机制特殊性,将函数模板声明与定义同放置于代码的头文件中。

2.分离编译模式

采用分离编译模式定义上述实例如下。

//函数模板实例化程序头文件testTemplate.h

#ifndef TESTTEMPLATE_H

#define TESTTEMPLATE_H

#include <iostream>

using namespace std;

//定义实现计算两数据之和的函数模板

template<typename Type>

Type computeSum(Type a,Type b);

//定义实现两数比较计算较小数的函数模板

template<typename Type>

Type computeMin(Type a,Type b);

//定义实现交换两数据位置的函数模板

template<typename Type>

Type computeswap(Type &a,Type &b);

#endif

//函数模板实例化源文件testTemplate.cpp

#include "testTemplate.h"

export template<typename Type>

Type computeSum(Type a,Type b)

{

         return(a+b);

}

 

export template<typename Type>

Type computeMin(Type a,Type b)

{

         returna<b?a:b;

}

 

export template<typename Type>

Type computeswap(Type &a,Type &b)

{

         Typetemp = b;

         b= a;

         a= temp;

}

 

int main()

{

         inta,b;

         a= 4,b = 5;

         cout<<"Twoint data's sum:"<<computeSum(2,3)<<endl;

         cout<<"Twodouble data's sum:"<<computeSum(2.5,3.7)<<endl;

         cout<<"Twoint data's min:"<<computeMin(3,9)<<endl;

         cout<<"Twodouble data's min:"<<computeMin(3.1,9.8)<<endl;

         cout<<"Changetwo data before:"<<"a = "<<a<<""<<"b = "<<b<<endl;

         computeswap(a,b);

         cout<<"Changetwo data after:"<<"a = "<<a<<""<<"b = "<<b<<endl;

         return0;

}

经过修改的实例如上所示。采用分离编译模式同样可以实现函数模板的实例化。但是模板的分离编译模式应用于多文件复杂情况下比较容易出错。应用程序采用分离编译模式必须很谨慎的组织程序文件。为了引起不必要的错误,本章所讲述的编译方式都会采用包含编译模式。

本实例主程序中首先定义两个整型变量ab,用于作为交换位置的实参。随后调用计算两数之和的功能函数computeSum。根据传入的实参类型实例化模板参数中的Type类型,函数体具体实现中通过传入的实参类型实现具体替换操作。最终实现传入的数据类型计算实现相同的功能。computeSum(2,3)函数调用并打印其返回结果,传入的实参分别为两个整型数据。调用函数模板实例化具体的操作函数,该函数实例主要用于处理整型数的计算和操作。

随后computeSum(2.5,3.7)函数调用,实参传入的则默认为double类型的数据。根据调用函数模板产生具体实例化的函数,此时模板函数即产生一份处理double类型数据的函数实例。依据传入的实参数据类型具体实例化函数模板,计算和的函数体中直接返回double型的计算结果并打印。

同样的操作方式,调用computeMin()函数模板产生具体实例,处理根据传入的不同类型数据打印并输出计算结果。最后首先打印初始化过的两个整型变量值,随后调用computeSwap()函数产生具体模板函数实例,实现交换实参对应值得功能,最后打印验证。

函数模板同样也可以类似普通函数定义,实现其重载定义。依据函数重载的基本规则,函数模板也根据传入函数参数的个数已经类型的不同实现其重载意义。函数模板的应用往往会带来编译器处理的二义性,函数模板的重载实现相对也比较复杂易出错,非特殊情况下不建议使用。

12.2.3  函数模板应用——通用函数

C++中模板机制应用场合很多,最主要的应用情形则为定义函数模板中实现应用不同数据类型的一般性操作,而函数操作的具体数据类型则依据传入的参数决定。通过函数模板定义实现很多应用中比较普及的操作,将这些大众普及的操作功能定义为可以称为通用函数,供用户根据不同的需要传入实际类型数据的相同功能操作。

C++程序提供的库中,很多情况下都封装实现了一般性算法的模板定义,供实际开发者使用过程中根据不同的需求传入不同类型数据完成既定功能。例如库中提供的排序算法功能函数,该库调用实现的排序算法对于处理不同数据类型的思路是相同的,差不多具体实现也基本一致。无论是应用于整型数据、浮点数据以及自定义类型数据处理。

C++应用程序中根据需求分析,通常比较普遍的实现操作功能一个是在C++提供的库中可以使用模板的概念定义实现,供开发者应用程序中调用使用。另一个则在用户应用程序中,当定义实现的操作属于作为C++组件或者作为编译为库供其它不同模块调用的情况下。同样也可以考虑采用模板机制,但是需要注意的是不要任何情况下都滥用模板机制,根据具体需求分析来设计。

通常一般性操作算法的功能实现,如果考虑到软件系统中可能需要应对不同种类型一般性功能处理,或者软件系统中实现的数据结构也需要根据传入的不同类型数据作相同的处理,以及该一般性的操作功能函数可能需要作为组件以及库中实现的功能供其它模块调用,则可以考虑将需要实现的函数通过模板机制定义为通用函数供使用。

12.2.4  实例12-1  通用函数——基本排序算法实现

前面已经描述过模板机制的最重要的应用之一即实现通用函数,外界根据传入的不同类型作出相同处理,而不需要根据每种数据类型的相同操作都需要定义同一份处理代码。下面通过实现几个排序算法功能函数通过模板机制的加入,定义为通用函数的实例理解函数模板的应用情况,代码编辑如下。

1.准备实例

打开UE工具,创建新的空文件并且另存为chapter1202.cpp。该代码文件随后会同makefile文件一起通过FTP工具传输至Linux服务器端,客户端通过scrt工具访问操作。程序代码文件编辑如下所示。

/**

* 实例chapter1202

* 源文件chapter1202.cpp

* 通用模板函数-基本排序算法实例

*/

#include <iostream>

using namespace std;

/*

* 函数功能:实现数组元素冒泡排序功能

* 函数参数:传入指定类型数组名及其数组长度

* 返回值:空

*/

template<class ARRAY_TYPE>

void bubbleCompositor(ARRAY_TYPE *array,intlen);

/*主程序入口*/

int main()

{

         constint size = 10;                                                   //定义整型常量size,表示数组长度

         intarray1[size] = {1,4,2,5,6,8,45};                         //定义包含size个元素的数组array同时初始化值

         bubbleCompositor(array1,size);                                    //调用冒泡排序实现函数,传入数组名与数组元素个数

         for(inti = 0;i < size;i++)                                           //循环打印数组排序后的元素

         {

                   cout<<array1[i]<<"";

         }

         cout<<endl;

         floatarray2[size] = {1.0,4.1,2.2,5.3,6.8,8.0,45.7};

         bubbleCompositor(array2,size);

         for(inti = 0;i < size;i++)

         {

                   cout<<array2[i]<<"";

         }

         cout<<endl;

         return0;

}

/*数组元素冒泡排序函数定义实现*/

template<class ARRAY_TYPE>

void bubbleCompositor(ARRAY_TYPE *array,intlen)

{

         inti,j;                                     //定义三个整型变量,ij用来作循环变量,temp用来作数组元素交换的临时变量

         ARRAY_TYPEtemp;

         for(i=0;i<len-1;i++)             //第一重for循环,从数组下标0开始到倒数第二个元素用来依次跟后续元素比较

         {

                for(j=i+1;j<len;j++)    //数组第二重for循环,从数组下标1开始直到最后一个元素用于比较

                 {

                       if(array [i]> array [j])                    //比较数组前后位置的数据,如果符合判断条件则执行如下的元素替换

                      {

                             temp= array [i];          //当元素需要比较时,先将i元素给临时变量

                             array[i]= array [j];      //j的元素值放到i元素位置中

                             array [j]=temp;           //将临时变量中值放到j位置中

                      }

                   }

         }

}

2.编辑makefile

Linux平台下需要编译源文件为chapter1202_01.cpp,相关makefile工程文件编译命令编辑如下所示。

OBJECTS=chapter1202_01.o

CC=g++

 

chapter1202_01: $(OBJECTS)

         $(CC)$(OBJECTS) -g -o chapter1202_01

clean:

         rm-f chapter1202_01 core $(OBJECTS)

submit:

         cp-f -r chapter1202_01 ../bin

上述makefile文件套用前面的模板格式,主要替换了代码文件、程序编译中间文件、可执行程序等。在编译命令部分-g选项的加入,表明程序编译同时加入了可调式信息。

3.编译运行程序

当前shell下执行make命令,生成可执行程序文件,随后通过make submit命令提交程序文件至本实例bin目录,通过cd命令定位至bin目录,执行该程序文件运行结果如下所示。

[develpoer@localhost src]$ make

g++    -c-o chapter1202.o chapter1202.cpp

g++chapter1202.o -g -o chapter1202

[develpoer @localhost src]$ make submit

cp -f -r chapter1202 ../bin

[develpoer @localhostsrc]$ cd ../bin

[develpoer @localhostbin]$ ./chapter1202

0 0 0 1 2 4 5 6 8 45

0 0 0 1 2.2 4.1 5.36.8 8 45.7

4.程序剖析

本实例复用第六章节中数组演示的例子,依然采用数组方式实现冒泡排序功能。其中冒泡排序函数采用了模板方式,定义成通用的冒泡排序函数。实际应用开发中,面对多种类型的数据通常可以采用重载函数的方式来实现相应排序功能。但是由于面对不同种类型的数据,排序方法实现的思路基本一致,仅仅数据类型的不同,完全没有必要重载成多个函数的实现方式。

本实例主要利用模板定义冒泡排序函数,在具体实例化时决定排序的数据基本类型。无论从代码整体风格等角度来讲通用函数模板相对于重载函数的定义使用,的确方便很多。实例中冒泡排序算法在模板函数bubbleCompositor中实现,该模板函数参数ARRAY_TYPE用于表示数组的具体类型,另外参数len用于表示数组的长度。冒泡模板函数实现内部根据传入的数组与数组长度,进行冒泡比较排序。函数内部定义两个整型变量ij,模板参数类型变量temp用于存放临时数据元素。

通过for循环方式从数组第0个元素到倒数第2个元素用来依次与各自后续元素比较,第一重for循环内部再次通过for循环方式来具体实现相邻位置的元素比较。直接比较前后数组元素大小,符合if结构中的判断条件,则进行元素的替换工作。排序中数组元素替换工作过程:如果前面的元素大于相邻后面的元素,则将前面元素array[i]赋值给临时变量temp,然后将后一个元素放入前面元素array[i]位置上。最后将临时变量存储的array[i]值放到后面元素中,实现两个值的交换过程。

经过最终循环,最后数组元素按照默认规则从小到大的原则实现排序。主函数中,定义整型常量size初始化值为10,用于表示排序数组的长度。定义整型数组array1,拥有size个元素,定义同时初始化值为{1,4,2,5,6,8,45},由于数组默认初始化规则,因此此时array1数组中存放着元素为{0,0,0,1,4,2,5,6,8,45}

主函数中调用冒泡排序函数bubbleCompositor,传入实例化的数组与数组长度参数。通过实例化数组的参数类型,bubbleCompositor函数内部实现了整型数组元素的冒泡排序处理过程,最后打印输出排序后的结果。随后同样的实现过程,实例化float类型数组元素实现冒泡排序。

12.3  C++类模板

C++模板不仅仅应用于一套相同处理逻辑但是内部可以支持处理不同数据类型的函数中,同样适用于多个类具有相同的方法或者数据成员,但需要处理不同的数据类型上。在这两种情况下,C++模板的概念都可以有很好的应用前景,本小节主要在前面讲述函数模板的基础上,讲述C++类模板的基本使用情况,带领初学者迈向C++面向对象的高级编程阶段。

12.3.1  类模板基本概念

Linux系统上C++应用开发中设想一个很常见的场景,假设公司的产品体系中有相当一部分的产品是属于C++应用开发的后台处理的系统,这些系统之间拥有共同的一些应用特点,例如对字符串操作的类似性,对Linux文件系统操作的共性。大部分产品组的做法是将这些不同系统之间存在共性的部分通过组件库的封装提供给具体应用使用,这样的好处一方面产品体系会显得很有条理,另一方面采用了共用的思路,降低开发的成本。

这种场景中C++语言开发的组件库大多数都是以类的方式封装提供的,如果不是考虑很大众化的应用库开发,其中C++类模板的使用会在某个组件是不同应用模块或者不同系统共用,但是涉及相同逻辑类中的处理数据类型或者数据结构类型的不同场景下。

举个最简单的应用开发的实例,例如一个C++应用处理系统需要从数据库定期的获取数据。那么与数据库打交道的应用组件,根据要求可能会从数据库不同的表中获取数据,实现逻辑都是相同的将数据从数据库获取到,然后放入到一个容器中,这一切在数据库接口类中实现的逻辑都相同。

但是对于不同的模块由于操作的数据库表不同,意味着存放相应数据的容器结构是不同的。一种情况是开发者应对不同模块要求,实现多份相同类的拷贝实现,或者通过类的多继承来实现。但是这种思路不仅仅会影响应用开发的性能,同时还会增加开发工作量。此时C++类模板的机制就可以得到很好的应用。

根据不同模块需要,只要在定义数据库接口类对象的时候,各自实例化自己所需的结构类型即可实现各取所需的需求。这方面的应用最多的当属非常著名的C++ STL库,该标准库中包含了类模板的大量运用,封装的大部分数据结构都是采用模板方式实现的。通过模板类的运用根据开发者使用时对象实例定义传入具体需要处理的数据类型,实例化处理具体数据类型的算法处理,不需要重复实现相同逻辑的不同类封装。

Linux系统上C++类模板与模板函数相同,采用关键字template表示模板定义,其基本语法定义操作如下所示。

template<class Type>                                            //模板参数类型定义

class class_name                                                   //定义类类型

{

         public:                                                               //公共成员方法声明

                   Typefunction();

                   …

         private:                                                             //私有保护成员定义

                   Typedata;

                   …

};

一个类模板一般定义如上所示,首先是关键字template表示本类为模板类,紧接着是模板参数类型定义放在尖括号中。下一行起则定义类类型名称,在类体内部声明其方法时可以使用模板类型,无论是方法成员还是数据成员。当然模板的参数类型不仅仅是某种具体的数据类型,前面也介绍过可以是具体的不同的数据结构。

如一个模板测试类TemplateTest,该类数据成员包含一个容器的定义,该容器中存放的是一个数据结构定义的记录。该类模板定义实例如下所示。

//样例1

template<calss Type>

class TemplateTest                                                 //模板类TemplateTest

{

         public:

                   TemplateTest();                                     //构造函数

                   virtual~ TemplateTest();                     //虚拟析构函数

         public:

                   Type          function();

                   //……

         private:

                   vector<Type>m_data;                         //不同类型的容器(可以是数据类型,可以是数据结构类型)

                   //……

};

//样例2

class TemplateTest                                                 //模板类TemplateTest

{

         public:

                   TemplateTest();                                     //构造函数

                   virtual~ TemplateTest();                     //虚拟析构函数

         public:

                   template<calssType>

                   Type           function(Type& data);

         private:

                   //……
};

上述实例代码片段样例1是一个模板类TemplateTest的简单声明,该类拥有构造函数与虚拟析构函数,同时拥有一个Type类型返回值的方法。该类模板参数主要应用在其私有的数据成员上,数据成员是一个vector容器,此时没有介绍到标准的STL模板库,初学者可以理解为一个数组。其中用于存放不同类型的数据的,根据需要应用在使用该类的时候根据传入的具体类型,实例化该类,同时该类的私有数据成员m_data就会是一个固定的类型容器,即容器中存放的数据的类型、大小基本能够确定。

需要注意说明的是,该容器不仅仅可以实例化具体的整型、bool型这些数据类型,还可以将一个数据结构的定义传入实例化,例如Type表示的类型可以是一个结构体,当然也可以是一个类类型,这就是C++面向对象加模板概念的威力之处。C++标准的STL模板库大量的运用了类似的模板技术在其中,尽可能的让STL库封装的操作、算法、数据结构变得通用。

上述实例代码中样例2演示了C++类中函数模板的使用方式。应用开发中可能并不需要某个类作为模板方式出现,类中需要模板方式实现类型泛化的只是某些类方法成员。代码片段2中表示了类中模板函数的声明方式,即将模板相应的关键字以及模板参数直接放在需要声明的方法上方即可。具体的模板类以及模板函数成员定义方式下一节会着重介绍。

12.3.2  类模板成员定义

C++应用中面向对象类类型定义实现主要包含两个方面。一方面是C++类的声明,包含类名、类体,其中类体中主要包含该类类型的方法成员和数据成员,它们可以是公开的、保护的或者私有类型的。另一个方面是具体的类中的这些方法成员的定义,通常是分代码文件定义的,类声明放在代码头文件,类的方法定义实现放代码源文件。同样当一个模板类声明完毕之后,就开始进行模板类中方法的定义实现,具体实现类体中的方法处理逻辑。

C++模板类定义同样如此,当前面小节实例中实现模板类TemplateTest声明之后,本小节主要着重于模板类中的方法成员的定义实现。同时将应用开发中模板类定义实现的常见遇到的问题作一个介绍。

模板类的定义实现与普通的类基本相同,只是在关键字使用上需要注意,下面给出模板类定义语法。

//单文件模板类定义实现方式

//样例1

template<calss Type>

class TemplateTest                                                 //模板类TemplateTest

{

         public:

                   TemplateTest();                                     //构造函数

                   virtual~ TemplateTest();                     //虚拟析构函数

         public:

                   bool function(const Type& data);

                   //……

         private:

                   vector<Type>m_data;                         //不同类型的容器(可以是数据类型,可以是数据结构类型)

                   //……
};

TemplateTest:: TemplateTest()

{
}

TemplateTest::~ TemplateTest()

{
}

bool TemplateTest ::function(const Type& data)

{

         m_data.push_back(data);

         //……
}

上述代码片段中主要列举了类模板、类方法成员具体的定义使用形式。对于C++中声明的模板类,给出单文件中定义实现的方式。代码中主要分为两个部分,模板类TemplateTest声明和模板类TemplateTest对应的成员方法定义方式。

C++模板类表明模板的定义形式是针对整个类本身的,凡是在该类有效的范围内,模板定义的类型参数都可以任意使用。上述代码中类TemplateTest的方法成员function()的参数类型就是用的模板参数类型Type,等待实例化时具体确定Type类型。同时私有数据成员m_datavector容器对象,也是采用Type作为其实例化类型。

模板类TemplateTest方法成员定义与普通类大致相同,上述代码主要描述了单文件中模板类和其方法成员定义使用方式。模板类TemplateTest的方法成员主要包括空定义的构造函数、析构函数和方法成员function()。在单文件中定义与普通的类方法成员定义相同,不需要特殊的定义方式。

//样例2

#include <iostream>

#include <string>

using namespace std;

 

class testTemplate

{

         public:

                   template<classType>

                   Typefunction(Type &data);

         private:

};

template<class Type>

Type testTemplate::function(Type &data)

{

           cout<<data<<endl;

           return data;

}

上述代码片段主要列举单个文件中类中模板方法成员使用方式。当应用需要一个类中的方法成员以模板的方式出现时,并不需要定义整个类为模板类,只需要将模板的关键形式放在类具体方法声明的上面即可。需要注意的是,模板方法成员定义默认情况下不支持分离定义,即不支持头文件中声明模板方法成员,源文件中定义具体的模板方法成员。

代码样例2中类testTemplate只包含一个方法成员的声明和定义。声明模板方法成员时只需要将template关键字和相应的模板参数在前面提供即可。另外在定义模板方法成员时,可以单个文件类声明体的外部定义,定义时同样需要模板关键字形式。同时也可以自己在类体内部声明方法的同时来实现定义,该种形式定义的模板方法成员可以视为内联定义。

//样例3

//多文件模板类定义实现方式

//头文件test.h

template<class Type>

class TemplateTest                                                 //模板类TemplateTest

{

         public:

                   TemplateTest();                                     //构造函数

                   virtual~ TemplateTest();                     //虚拟析构函数

         public:

                   bool function(const Type& data);

                   //……

         private:

                   vector<Type>m_data;                         //不同类型的容器(可以是数据类型,可以是数据结构类型)

                   //……

};

//源文件test.cpp

template<class Type>

TemplateTest<Type>::TemplateTest()

{

}

template<class Type>

TemplateTest<Type>::~ TemplateTest()

{

}

template<class Type>

bool TemplateTest<Type>::function(constType& data)

{

         m_data.push_back(data);

         //……

}

样例3的代码片段主要演示了多文件情况下模板类的定义使用方式。C++面向对象类设计开发代码时,通常会根据类声明和定义分开为头文件和源文件实现代码编写。目的是提供给其它应用使用时,封装好的C++类只需要提供头文件接口声明即可,不需要将具体定义代码实现暴露出来。

对于模板类的定义同样可以采用这种分离定义实现方式,根据需要上述代码片段中模板类TemplateTest声明放在头文件test.h中,其源文件test.cpp中主要存放类的成员具体定义。此时定义模板类的成员函数需要在每个方法前加上template关键字和参数类型声明,同时还需要注意因为类TemplateTest为模板类,在定义TemplateTest::域的定义时需要将模板参数加上,便于实例化。

本小节主要向初学者介绍了模板类以及模板函数定义实现方法成员的几种使用方式,通过简单的代码片段来演示模板类和类模板函数在实际应用开发中如何能够正确的定义使用。下面小节将会通过几个完整代码实例来演示类模板实例化应用过程。

12.3.3  类模板实例化

Linux平台上C++定义的实现的类模板,最终的目的是在应用中使用这些模板定义。这会涉及具体类模板实例化的定义过程,本小节主要介绍C++模板中类定义实例对象的几种方式。C++中模板类的实例化操作主要通过两个完整实例来介绍应用开发中具体的模板类使用方法,相关实例代码完整组织如下所示。

下面首先第一个模板类封装实例,本实例主要通过封装简单列表队列操作,通过模板化参数定义来支持不同种链表数据元素类型,来扩展支持应用多类型数据、相同处理流程思路的要求。

1.准备实例

打开UE工具,创建新的空文件并且另存为chapter1203.hchapter1203.cpp。该代码文件随后会同makefile文件一起通过FTP工具传输至Linux服务器端,客户端通过scrt工具访问操作。程序代码文件编辑如下所示。

/**

* 实例chapter1203

* 源文件chapter1203.hchapter1203.cpp

* 队列模板类封装实例

*/

//头文件chapter1203.h

#include <iostream>

using namespace std;

 

template<class T>

class TestQueue

{

         structQueueItem

         {

                   Titem;

                   QueueItem*next;

                   QueueItem()

                   {

                            next= NULL;

                   }

         };      

         public:

                   TestQueue();

               ~TestQueue(){}

               bool push(T &item);

               bool get(T &item);

               int size() {return m_size;}

               bool empty() { return first == NULL;}

       private:

                   QueueItem*first, *last;

                   int  m_size;

};

//源文件chapter1203.cpp

#include "chapter1203.h"

 

template<class T>

TestQueue<T>::TestQueue()

{

         m_size = 0;

         first = NULL;

         last= NULL;

}

 

template<class T>

bool TestQueue<T>::push(T &item)

{

         if(m_size >5000)

                   returnfalse;

         QueueItem*ps = new QueueItem;

         ps-> item = item;

         ps-> next = NULL;

         if(first == NULL)

         {

                   first=  ps;

                   ps-> next = NULL;

                   last= first;

         }

         else

         {

                   last-> next = ps;

                   last  = ps;

         }

         m_size++;

         returntrue;

}

 

template<class T>

bool TestQueue<T>::get(T &item)

{

         if(empty())

                   returnfalse;

         QueueItem*ps = first;

         first= first -> next;

         m_size--;

         item = ps -> item;

         delete ps;

         return true;

}

 

int main()

{

         stringdata,tempdata;

     TestQueue<string> queue;

         cout<<"Pleaseinput Queue data:"<<endl;

         cin>>data;

     while(data != "#")

     {

                   if(!queue.push(data))

                   {

                            cout<<"Queue istoo iteams!"<<endl;

                   }

                   cout<<"Pleaseinput Queue data:"<<endl;

                   cin>>data;

     }

     int size = queue.size();

     cout<<"size"<<size<<endl;

     cout<<"Queuedata:"<<endl;

     for(inti=0;i<size;i++)

         {

            if(!queue.get(tempdata))

            {

                            cout<<"Queue isempty!"<<endl;

                   }

                   cout<<tempdata<<" ";

     }

         cout<<endl;

         return 0;

}

2.编辑makefile

Linux平台下需要编译源文件为chapter1203.cpp,相关makefile工程文件编译命令编辑如下所示。

OBJECTS=chapter1203.o

CC=g++

 

chapter1203: $(OBJECTS)

         $(CC)$(OBJECTS) -g -o chapter1203

clean:

         rm-f chapter1203 core $(OBJECTS)

submit:

         cp-f -r chapter1203 ../bin

         cp-f -f *.h ../include

上述makefile文件套用前面的模板格式,主要替换了代码文件、程序编译中间文件、可执行程序等。在编译命令部分-g选项的加入,表明程序编译同时加入了可调式信息。

3.编译运行程序

当前shell下执行make命令,生成可执行程序文件,随后通过make submit命令提交程序文件至本实例bin目录,通过cd命令定位至实例bin目录,执行该程序文件运行结果如下所示。

[developer@localhost src]$ make

g++    -c -ochapter1203.o chapter1203.cpp

g++ chapter1203.o-g -o chapter1203

[developer @localhost src]$ make submit

cp -f -r chapter1203 ../bin

cp -f -f *.h ../include

[developer @localhost src]$ cd ../bin

[developer @localhost bin]$ ./chapter1203

Please input Queue data:

hello

Please input Queue data:

jack

Please input Queue data:

lily

Please input Queue data:

end

Please input Queue data:

#

size4

Queue data:

hello jack lily end

4.程序剖析

上述实例主要通过封装队列模板类的一系列操作,演示了模板在C++类类型中实际实例化应用的方法。队列模板类TestQueue主要采用简单的链表实现方式,通过将链表元素数据模板参数化,来支持不同种队列元素数据的操作。下面将会具体解释TestQueue类的方法实现情况。

本实例程序TestQueue类主要封装实现了简单的列表操作,通过模板参数化其中的链表数据元素的类型,从而在应用中根据需要实例化操作各种类型数据的链表队列。

模板类TestQueue封装主要包含两个部分,第一个部分为模板类TestQueue声明头文件,第二个部分为TestQueue类封装方法的具体定义源文件。

1)第一个部分为头文件中TestQueue类声明,该类主要包含TestQueue()(构造函数)、~TestQueue()(空的析构函数)、push(T &item)(添加元素操作)、get(T &item)(获取并删除元素操作)、size()(获取队列长度操作)以及empty()(判断队列是否为空操作)。另外还包括定义链表每个节点的具体结构QueueItem,其中结构体QueueItem作为模板类TestQueue内部成员出现。

头文件中QueueItem主要包含队列元素数据成员、队列中next指针以及相应的结构体QueueItem()的构造成员方法。其中构造成员方法QueueItem()内部主要工作是初始化next指针为NULL。模板类TestQueue私有数据成员中主要包含QueueItem结构的头尾指针声明,用于指向链表的头尾元素。另外整型私有成员m_size用于表示链表节点的个数。

2)第二个部分为源文件,主要负责定义实现模板类TestQueue中的相应方法成员,其中构造函数中主要工作是初始化链表队列中相关元素。初始化链表长度m_size0,初始化链表结构的头尾指针为NULL

模板类TestQueuepush方法成员,内部主要通过修改链表指针指向来添加相应的数据元素。该方法内部首先判断链表长度是否超过5000个节点数据的限制,如果超过则返回false。否则通过new关键字创建一个新的链表节点对象,将push方法传入的元素数据item放到新链表节点中相应元素数据的指向上。

push方法成员同时将链表节点结构中next指针设置为NULL,如果链表first头指针为空,则表明链表队列为空,此时将链表头指针first指向新创建的链表节点。同时该节点指向下一个元素的next指针初始化为NULL,同时将为节点last也指向first指针指向的位置。如果链表队列不为空,即first指针指向元素不为空。那么直接修改链表结构中next指针指向,指向新构造的链表节点上。同时将尾指针last也指向新添加的链表尾部元素上。最后将相应的链表长度m_size增加,成功的情况下返回true表明添加链表元素数据成功。

模板类TestQueueget方法主要用于从链表队列头部获取元素数据,获取到之后将该节点删除。方法get中首先通过empty方法来判断链表是否为空,如果为空则直接返回false。在链表不为空的情况下,定义元素结构的指针,将该指针指向链表的头部first指针指向的元素数据位置。下一步是修改链表头指针first指向,使得first指向了首节点中关联的下一个节点位置。

get方法中随后将链表的长度减掉一个元素,同时通过传入的链表元素item获取到要删除的首节点的相应数据元素ps -> item。最后通过delete清除定义的链表节点结构的指针ps指向的内容,返回true表明该方法执行成功。调用该方法者,通过传入链表节点中数据类型变量,获取了节点内容数据同时将该节点从链表头部删除之。

3)本实例程序通过上述封装实现的链表队列模板类,基本实现了链表的基本操作。源程序中main主函数内部通过实例化模板类进行具体应用开发,下面通过分析main函数中的实现过程来了解模板类实例化的具体操作方式。主程序中首先通过string类类型定义两个字符串对象datatempdata,用于存放具体链表节点中的数据。普通类方式使用TestQueue时,仅仅需要随后通过空格方式给出对象的名称,程序中会根据TestQueue类构造方法来构造其对象,从而来使用该类的一些方法定义。

由于TestQueue为模板类定义,所以在定义该模板类对象时需要给出类类型的模板实例化参数类型。因此此时类对象定义方式稍有变动,例如程序所示通过尖括号内部实例化模板参数为string,从而定义TestQueue模板类对象queue。此时的模板类queue对应的类实例中,凡事涉及模板参数template<classT>T定义处,都必须实例化为相应的参数类型string

通过cout输出流提示用户从键盘输入链表节点数据,cin输入流将用户输入的字符串数据存储到data中。通过while循环结构判断用户输入的数据是否为结束符号“#”,如果不是则通过if判断结构调用其push方法将data数据加入到该链表中去。同时判断调用push方法是否成功,不成功则输出相应的出错提示信息。同时提示用户继续输入相应的数据,来进行链表插入操作。

实例中输入4个元素数据后通过输入“#”结束了链表插入数据操作,此时定义链表长度整型变量size,该变量通过模板类对象queue调用其size方法来获取的链表长度赋值。通过cout输出流打印输出获取到的链表长度,随后提示程序下一步动作是循环取出并显示链表节点的数据。

主程序中通过for循环结构,以链表的长度为上限来循环从链表中获取数据节点。循环结构体内通过调用get方法,传入tempdata变量获取到要从头部删除节点的数据。最后在循环中一条条打印出删除节点的数据,中间通过空格符号来隔开显示。

12.3.4  类模板非参数实例化

C++语言中对于类模板定义机制,同样也支持模板定义的同时出现非类型的模板参数的情况。这种实现机制往往在应用中都会被用来限制类中某些容量或者界限的实现手段。同样通过采用上述实例代码修改来演示类模板非类型参数的使用情况,上述实例代码改造如下所示。

1.准备实例

打开UE工具,创建新的空文件并且另存为chapter1204.hchapter1204.cpp。该代码文件随后会同makefile文件一起通过FTP工具传输至Linux服务器端,客户端通过scrt工具访问操作。程序代码文件编辑如下所示。

/**

* 实例chapter1204

* 源文件chapter1204.hchapter1204.cpp

* 队列模板非参数类型类封装实例

*/

//头文件chapter1204.h

#include <iostream>

using namespace std;

 

template<class T,int size_node>

class TestQueue

{

         structQueueItem

         {

                   Titem;

                   QueueItem*next;

                   QueueItem()

                   {

                            next= NULL;

                   }

         };

         public:

                   TestQueue();

               ~TestQueue(){}

               bool push(T &item);

               bool get(T &item);

               int size() {return m_size;}

               bool empty() { return first == NULL;}

       private:

                   QueueItem*first, *last;

                   int  m_size;

};

//源文件chapter1204.cpp

#include "chapter1204.h"

 

template<class T,int size_node>

TestQueue<T,size_node>::TestQueue()

{

         m_size= 0;

         first= NULL;

         last= NULL;

}

 

template<class T,int size_node>

bool TestQueue<T,size_node>::push(T&item)

{

         if(m_size > size_node)

                   returnfalse;

         QueueItem*ps = new QueueItem;

         ps-> item = item;

         ps-> next = NULL;

         if(first == NULL)

         {

                   first=  ps;

                   ps-> next = NULL;

                   last= first;

         }

         else

         {

                   last-> next = ps;

                   last  = ps;

         }

         m_size++;

         returntrue;

}

 

template<class T,int size_node>

bool TestQueue<T,size_node>::get(T&item)

{

         if(empty())

                   returnfalse;

         QueueItem*ps = first;

         first= first -> next;

         m_size--;

         item = ps -> item;

         delete ps;

         return true;

}

 

int main()

{

         stringdata,tempdata;

         TestQueue<string,2000>queue;

         cout<<"Pleaseinput Queue data:"<<endl;

         cin>>data;

     while(data != "#")

         {

           if(!queue.push(data))

           {

                            cout<<"Queueis too iteams!"<<endl;

           }

           cout<<"Please input Queuedata:"<<endl;

                   cin>>data;

     }

     int size = queue.size();

     cout<<"size"<<size<<endl;

     cout<<"Queuedata:"<<endl;

     for(inti=0;i<size;i++)

         {

            if(!queue.get(tempdata))

            {

                            cout<<"Queue isempty!"<<endl;

            }

                   cout<<tempdata<<" ";

     }

     cout<<endl;

         return 0;

}

2.编辑makefile

Linux平台下需要编译源文件为chapter1204.cpp,相关makefile工程文件编译命令编辑如下所示。

OBJECTS=chapter1204.o

CC=g++

 

chapter1204: $(OBJECTS)

         $(CC)$(OBJECTS) -g -o chapter1204

clean:

         rm-f chapter1204 core $(OBJECTS)

submit:

         cp-f -r chapter1204 ../bin

上述makefile文件套用前面的模板格式,主要替换了代码文件、程序编译中间文件、可执行程序等。在编译命令部分-g选项的加入,表明程序编译同时加入了可调式信息。

3.编译运行程序

当前shell下执行make命令,生成可执行程序文件,随后通过make submit命令提交程序文件至本实例bin目录,通过cd命令定位至实例bin目录,执行该程序文件运行结果如下所示。

[developer@localhost src]$ make

g++    -c -ochapter1204.o chapter1204.cpp

g++ chapter1204.o-g -o chapter1204

[developer @localhost src]$ make submit

cp -f -r chapter1204 ../bin

[developer @localhost src]$ cd ../bin

[developer @localhost bin]$ ./chapter1204

Please input Queue data:

hello

Please input Queue data:

jim

Please input Queue data:

end

Please input Queue data:

#

size3

Queue data:

hello jim end

C++类模板中允许非模板参数出现,并且通常这类非模板参数都会用来作为类内部封装内部实现的限制手段。例如上述实例从实例chapter1203修改而来,该实例通过用户使用过程中实例化传入的参数来限定创建链表队列的最大长度。

类模板的非模板参数设置非常简单,实例中定义类为模板类时采用的方式定义如下。

template<class T,int size_node>

上述模板类设置模板参数时通过增加整型常量size_node来作为链表队列长度限定值,具体定义该方法的时候也需要按照这种方式在类每个方法定义时添加模板参数。具体的非模板参数作为定义实现限定在方法push中,该方法中将原来静态设置的2000元素大小通过非模板参数设定来作具体限定。

非模板参数需要注意,该参数只能是整型常量或者枚举类型值,另外就是指向外部链接对象的指针。如浮点型非模板参数和类对象是不支持的。因此如下定义是不被允许的。

//错误使用方式1

template<class T,double size_node>

class TestQueue

{

//……

};

//错误使用方式2

template<class T,string object_name>

class TestQueue

{

//……

};

从上述使用错误方式来看,类对象作为非模板参数类型和浮点类型常量值现有的C++标准是不支持,但是可以通过外部链接的对象指针申请作为模板的非模板参数,具体使用方式在此不作详细介绍。C++类模板非模板化参数只需要了解前面介绍的整型常量和枚举值相应的支持就可以了,实际应用开发中很少会使用模板的该类特性。

12.3.5  类模板函数实例化

前面小节主要介绍了模板类实例化使用的一般开发方法,通过封装并实例化链表队列类的方式,向初学者介绍模板类在应用中具体的操作情况,让初学者初步掌握如何定义使用模板类。本小节将会介绍模板概念在C++面向对象类类型中的另一种应用情况。

应用开发中同样存在这样一种情况,通过C++面向对象类封装的方法封装了某个数据结构算法实现,但是其中某个方法是具有通用性的。需要根据实际情况来实例化具体的类型来形成独特的数据结构类型处理逻辑。于是,类中方法成员模板化定义实例化将会通过如下实例来介绍。

1.准备实例

打开UE工具,创建新的空文件并且另存为chapter1205.hchapter1205.cpp。该代码文件随后会同makefile文件一起通过FTP工具传输至Linux服务器端,客户端通过scrt工具访问操作。程序代码文件编辑如下所示。

/**

* 实例chapter1205

* 源文件chapter1205.hchapter1205.cpp

* 字符串处理类模板方法实例

*/

//头文件chapter1205.h

#include <sstream>

#include <string>

#include <vector>

#include <iostream>

using namespace std;

 

class StringOperate

{

         public:

                   /*

                   *函数功能:去掉输入字符串的左边空白字符

                   *函数参数:输入字符串

                   *返回值:返回结果字符串

                   */

                   staticstring & ltrim(string & str) ;

                   /*

                   *函数功能:掉输入字符串的右边空白字符

                   *函数参数:输入字符串

                   *返回值:返回结果字符串

                   */

                   staticstring & rtrim(string & str) ;

                   /*

                   *函数功能:把输入字符串转换成大写

                   *函数参数:输入字符串

                   *返回值:返回结果字符串

                   */

                   staticstring & toUpperCase(string & str) ;

                   /*

                   *函数功能:把输入字符串转换成小写

                   *函数参数:输入字符串

                   *返回值:返回结果字符串

                   */

                   staticstring & toLowerCase(string & str) ;

                   /*

                   *函数功能:输入对象/数值,保证该对象/数值可以输出到字符串流中

                   *函数参数:输入对象/数值,转换时是否转换数值符号,缺省为false

                   *返回值:转换成功返回字符串对象

                   */

                   template<class T>

                   staticstring toString(T value, bool sign = false)

                   {

                            ostringstreambuffer;

                    if (sign) buffer<<showpos;

                    buffer<<fixed<<value;

                    return buffer.str();

                   }

};

//源文件chapter1205.cpp

#include "chapter1205.h"

 

//去字符串左边空白字符方法定义

string & StringOperate::ltrim(string &str)

{

   string::iterator pos = str.begin();

    for (; pos != str.end() && (* pos== ' ' || * pos == '\t'); pos++);

    if (pos !=str.begin()) str.erase(str.begin(), pos);

    returnstr;

};

 

//去字符串右边空白字符方法定义

string & StringOperate::rtrim(string &str)

{

   string::reverse_iterator pos = str.rbegin();

    for (;pos != str.rend() && (* pos == ' ' || * pos == '\t'); pos++);

    if (pos!= str.rbegin()) str.erase(pos.base(), str.end());

    returnstr;

};

 

//字符串转换成大写方法定义

string & StringOperate::toUpperCase(string& str)

{

    for(string::iterator pos=str.begin(); pos != str.end(); pos++)

        *pos= toupper(*pos);

    returnstr;

};

 

//字符串转换成小写方法定义

string & StringOperate::toLowerCase(string& str)

{

    for(string::iterator pos=str.begin(); pos != str.end(); pos++)

        *pos= tolower(*pos);

    returnstr;

};

 

int main()

{

         StringOperatestring_util;

         stringtestdata = "   hello!";

         cout<<"testdata:"<<testdata<<endl;

         string_util.ltrim(testdata);

         cout<<"testdata:"<<testdata<<endl;

         testdata= "hello!   ";

         cout<<"testdata:"<<testdata<<endl;

         string_util.rtrim(testdata);

         cout<<"testdata:"<<testdata<<endl;

         string_util.toUpperCase(testdata);

         cout<<"testdata:"<<testdata<<endl;

         string_util.toLowerCase(testdata);

         cout<<"testdata:"<<testdata<<endl;   

     long testInt;

     testInt = 10000;

     testdata= string_util.toString(testInt);

     cout<<"testdata:"<<testdata<<endl;

         return 0;

}

2.编辑makefile

Linux平台下需要编译源文件为chapter1205.cpp,相关makefile工程文件编译命令编辑如下所示。

OBJECTS=chapter1205.o

CC=g++

 

chapter1205: $(OBJECTS)

         $(CC)$(OBJECTS) -g -o chapter1205

clean:

         rm-f chapter1205 core $(OBJECTS)

submit:

         cp-f -r chapter1205 ../bin

上述makefile文件套用前面的模板格式,主要替换了代码文件、程序编译中间文件、可执行程序等。在编译命令部分-g选项的加入,表明程序编译同时加入了可调式信息。

3.编译运行程序

当前shell下执行make命令,生成可执行程序文件,随后通过make submit命令提交程序文件至本实例bin目录,通过cd命令定位至实例bin目录,执行该程序文件运行结果如下所示。

[developer@localhost src]$ make

g++    -c -ochapter1205.o chapter1205.cpp

g++ chapter1205.o-g -o chapter1205

[developer @localhost src]$ make submit

cp -f -r chapter1205 ../bin

[developer @localhost src]$ cd ../bin

[developer @localhost bin]$ ./chapter1205

testdata:  hello!

testdata:hello!

testdata:hello!  

testdata:hello!

testdata:HELLO!

testdata:hello!

testdata:10000

本实例主要演示类封装不需要定义成模板类,但其中某个方法需要实现通用类型的情况下的模板方法操作。本实例程序主要通过封装日常开发中字符串基本操作的组件类,将常见的字符串操作封装在该类中,供不同的应用程序调用。字符串封装操作类主要包含五个基本字符串操作的方法,分别为ltrim(去除字符串左边空格字符)、rtrim(去除字符串右边空白字符)、toUpperCase(将输入字符串转换成大写)、toLowerCase(将输入字符串转换成小写)和toString(将输入对象或者数值转换为字符串流)。

1)方法成员toString通过模板函数定义方式支持通用数据类型转换,本实例主要通过该方法的实际实例化使用来演示了类方法模板的操作使用方法。方法toString默认情况下定义实现只能是在同一个头文件,或者在内内部采用内联定义实现方式,这与模板分离机制有关,此处暂不深究如此实现的原理,仅关注操作使用性。下面简单剖析下字符串封装类StringOperate中具体操作方法定义实现过程。

字符串操作类StringOperate中所有方法成员定义为静态类型,编译程序时地址可以固定,方便类中多进程操作。

2)方法成员ltrim返回字符串类型值,拥有一个输入参数,主要输入字符串数据。该方法内部实现首先通过“string::iterator pos”定义字符串位置指针,通过字符串begin方法获取到字符串第一个位置字符。通过for循环判断指向的每个字符是否为空,或者是否为制表符“\t”,如果是上述两种情况中一种或者两种,在上述存在为空或者“\t”情况下,判断如果指向字符串位置pos不为字符串开始第一个,则删除从字符串开始第一个位置到pos位置之间的字符。通过循环操作,最终返回去除左边空白字符之后的字符串,并返回。

3)方法成员rtrim,该方法返回string类型字符串,该方法同时拥有输入参数字符串str。该方法内部,首先通过“string::reverse_iteratorpos”定义一个指向字符串反转的第一个位置,即字符串倒数第一个位置。随后通过for循环判断每个字符位置是否为空字符或者“\t”,如果为其中一种或者两种,则通过字符串erase方法删除倒数第一字符位置到指定的非倒数结束的位置之间的字符。最后返回最终去除字符串右边空白字符的字符串str

4)方法成员toUpperCase用于将输入的字符串转变成大写字符串。方法内部通过循环传入字符串str的每个字符,分别通过系统函数toupper将其转变为大写字母,最后返回转变后的字符串str

5)方法成员toLowerCase用于将输入的字符串转变为小写字符串。方法内部同样是通过for循环操作将字符串中每个字符依次转变为小写字符,最后返回转变之后的字符串。

6)字符串操作类StringOperate最后一个模板方法toString用于转换传入的各类类型数据值转换为可以输出到流中的字符串,该模板方法采用“template <class T>”单独在方法位置前标识此方法。该方法返回值为string字符串类型,拥有两个参数,一个模板参数为T根据输入值判断具体类型,另一个参数为bool型用于标识转换时标记是否需要将转换数值对应的正数符号加上的标记sign

该方法默认情况下signfalse,即转换时对于正的数值不会显示“+”添加在字符串流中,如果该标记设置为true,则转换数值时如果为正数会转换为字符串流时添上“+”符号,这点初学者可以自行在实例中修改观察转换后的结果。toString方法内部主要通过标准库的ostringstream来实现数值转换为字符串流操作,该类由标准C++库提供,使用的时候需要包含头文件<sstream>。在方法内部首先定义其对象buffer,随后判断是否需要将非负数插入流中时显示“+”符号。主要通过输出流中的showpos来进行设置。

如果不需要进行sign标记设置,则调用时使用默认值,直接通过输出流将传入变量转换为字符串流,同时通过fixed来指定转换时数值采用定点计数法,支持和保留原来小数部分按照固定的小数长度来显示,不足的部分补零。方法的最后通过标准库的ostringstream字符串流类方法成员str返回转换后的字符串对象。

7)主函数中首先定义封装字符串操作类StringOperate对象string_util,定义字符串变量testdata同时附一个字符串左边存在空白字符的值。通过cout对象输出操作该字符串前的字符串内容,随后对象string_util调用去掉输入字符串的左边空白字符方法ltrim,传入需要去除空白字符的字符串,最后打印去除验证结果。

同样的操作步骤实现去除字符串右边空白字符,重新修改testdata字符串中的内容,使得字符串右边存在空白字符,随后调用相应的对象string_utilrtrim方法,最后输出结果验证。通过对象string_util调用转换成大写字符串的方法toUpperCase,传入测试的字符串变量,输出并验证处理结果。同样操作步骤通过toLowerCase将其转换为小写字符串。

为了验证模板方法toString,在应用中并不主要在调用具体模板方法时传入参数,而是类似模板函数的使用方式,直接调用让模板方法内部自行执行类型转换实现实例化操作。定义长整型变量testInt赋值为10000,之后通过调用toString方法实现字符串流转换,随后将调用返回的字符串对象结果放入到字符串变量testData中,打印输出验证是否将输入数值转换成相应的字符串。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值