-
函数模板
2017-11-22 17:21:37函数模板环境:win10 vs2013
1.什么是函数模板?
2.函数模板怎么写?
3.函数模板的实例化?
4.参数如何推演?
5.函数模板如何编译?
6.函数模板的模板参数列表?
7.函数模板重载?
8.函数模板的特化?那就跟着我来看看函数模板吧
1.什么是函数模板?函数模板:代表了一个函数家族,该函数和类型无关,在使用时被参数化,根据实参类型产生函数的特定类型版本 模板就像是一个模具,我们在生活中可以见到很多模具,我们可以根据模具做出不同的东西
2.函数模板怎么写?
函数模板的格式 template <typename T1,typename T2,...,typename Tn> 返回值类型 函数名(参数列表) { ...... } 例如: template <typename T>//T是模板形参名字,可以是任意起名 T Add(T left, T right) { return left+right; } typename是用来定义模板参数的关键字,也可以使用class,但是建议使用typename(typename有些编译器不支持) 注意:不能使用struct代替class
3.函数模板的实例化?
模板是一个设计图,它本身不是类或者函数,编译器用模板产生指定的类或者函数的特定类型版本,产生模板特定类型得过程称为函数模板实例化。
4.参数如何推演?
实参推演发生在隐式实例化中#include<iostream> #include<stdlib.h> using namespace std; template<class T> T Add(T left, T right) { cout << "left类型:"<<typeid(left).name() << endl; //可以输出left的类型 (函数name()是对象typeid(left)的成员函数) return left + right; } int main() { cout<<Add(10, 20)<<endl; cout<<Add('1', '2')<<endl; system("pause"); return 0; } Add(10,20)//在底层调用Add函数时,使用的函数名是Add<int> Add('1', '2')//在底层调用Add函数时,使用的函数名是Add<char> 编译器实参推演: 编译器在编译时,会根据传的实参的类型,通过函数模板生成不同的实例,在此代码中会生成一个int型的Add()函数和一个char型的Add()函数 注意: Add(1,'1') //在底层时是Add<int ,char>,这种不可以运行,因为"T"只代表一种类型(如果想传不同类型的数据,可以定义为T1,T2,......,Tn,T1和T2是不同的类型,可是T1只能代表一种类型),此加法函数中有int型数据和char型数据,所以编译器在实参推演时,不知道应该推演成int类型的数据还是char类型的数据 解决办法: (1)显式实例化 Add<int>(1,'1')//在实例化阶段,会进行隐式类型转换,把char型的转换成int型的 (2)强转 Add(1,(int('1'))//进行强转,把char型转换成int型的
运行结果:
类型形参转换 一般不会转换实参来匹配已有的实例化,相反会产生新的实例 编译器会执行两种转换: 1.const转换:接收const引用或const指针的函数可以分别用非const对象的引用或指针来调用(即就是说传的普通数据的引用,执行此函数时函数模板会生成参数类型为const的函数 ) 2.数组或函数的转换: (1)数组实参将转换为指向其第一个元素的的指针; (2)函数实参将转换为当做指向函数类型的指针;
5.函数模板如何编译?
函数模板被编译了两次分为两个阶段 (1)实例化之前,对函数模板进行语法检测,查看是否出现语法错误,如:遗漏分号(此种错误在此阶段检查不出来,即就是如果遗漏分号,编译器也不会报错) (2)在实例化期间,检查模板代码,查看是否所有的调用都有效,生成不同类型函数(即生成合适的、满足要求的函数)的代码,再进行编译这些代码(此阶段会进行代码各种错误检测,如果有错编译器就会报错,直到代码没有任何错误)
6.函数模板的模板参数列表?
函数模板有两种类型参数:模板参数和调用参数1.模板参数 模板参数有两种类型:类型形参和非类型形参 (1)模板形参名字只能在模板形参之后到模板声明或未定义的末尾之间使用,遵循名字屏蔽规则 #include<iostream> #include<stdlib.h> using namespace std; typedef int T;//把int重命名为T template<class T> //模板参数名字也是T(模板形参名字只能在模板形参之后到模板声明或定义的末尾之间使用,在此程序中就是说T只能在模板函数体中、返回值中、参数类型中使用也可在强制类型转换中使用) T Add(T left, T right) { return left + right; } T a; int main() { cout<<Add(10, 20)<<endl; cout<<Add('1', '2')<<endl; system("pause"); return 0; } 名字屏蔽规则就是说: 在此代码中有一个typedef int T,在函数模板中有一个template<class T>,但是在函数模板中的T代表的是模板形参T,不是int型的,只是一个类型,会根据实参类型的不同而变化,模板形参中的T暂时会把typedef定义的T屏蔽(这样的话就可以生成根据传的实参的类型,生成不同的函数),出了函数模板的作用域,T就是int型的 (2)模板形参的名字在同一个模板中不允许出现多次,只能使用一次 例如: template<class T, class T> void Test(T a, T b); 编译器就会报错:错误 重定义 模板 参数“T” (3)模板形参的前面必须加上关键字:class 或者 template 注意:在函数模板的内部不能指定缺省的模板形参
注意:定义模板函数时,模板形参不能为空
2.非模板类型参数 非模板类型参数:是在模板内部定义的常量,在需要常量表达式的时候,可以使用非模板类型参数 例如:求数组的长度(求数组的长度时,不仅需要数组的类型,还需要数组元素的个数) template<class T,int N>//T是数组的类型,N是数组元素的总个数
7.函数模板重载?
#include<iostream> #include<stdlib.h> using namespace std; template<class T> T Max(const T& left, const T& right) { return left > right ? left : right; } template<class T> T Max(const T&a, const T&b, const T&c) { T max = Max(a, b); return max > c ? max : c; } int main() { cout << Max(10, 200) << endl; cout << Max(1, 4, 8) << endl; cout << Max(1.1, 2.2) << endl; cout << Max(1.1, 2.2, 3.3) << endl; system("pause"); return 0; }
运行结果:
注意:函数模板的所有重载版本的声明到必须放在该函数被调用之前
即就是函数模板的所有形式的声明都必须放在该函数被调用之前,因为编译器编译代码时是在main函数之前或main函数之中找需要调用的函数的定义或声明的,如果放在之后,编译器就会找不到8.函数模板的特化?
函数模板可以生成所有满足实参类型的函数,但是有时实例化出的函数是错误的,或者是不能编译的。 特化:就是特别写出函数模板不能解决的某个类型的函数 特化形式: 1.定义模板关键字:template< > 2.函数名后跟一对尖括号(<>),尖括号中指定这个特化定义的模板形参 3.函数形参表:一定要与函数模板的基础参数类型完全相同(如果不相同,会产生无法想象的后果) 4.函数体
函数模板的特化很复杂,一不小心就会写出有很多bug的代码,自己写一个解决此问题的函数也可以解决函数模板解决不了的问题,而且还比较方便,所以建议遇到此问题时,尽量自己写一个函数。
函数模板注意的问题
1.对于非模板函数和同名函数模板,都可以完成程序的要求,那编译器会优先调用非函数模板,而不会从该模板生成一个函数,但是如果函数模板生成的函数更匹配要求,那就选择该模板; 2.模板函数不允许自动类型转换,但是普通函数可以进行自动类型转换; 3.一个非模板函数可以和一个同名的函数模板同时存在,而且该函数模板还可以被实例化为这个非模板函数。
-
C++函数模板(模板函数)详解
2019-07-04 16:03:01C++函数模板(模板函数)详解定义用法:函数模板的原理延申用法2.1为什么需要类模板2.2单个类模板语法2.3继承中的类模板语法案例1:案例2:2.4类模板的基础语法2.5类模板语法知识体系梳理1.所有的类模板函数写在类的...C++函数模板(模板函数)详解
定义
函数模板不是一个实在的函数,编译器不能为其生成可执行代码。定义函数模板后只是一个对函数功能框架的描述,当它具体执行时,将根据传递的实际参数决定其功能。(好吧,咱也听不懂,直接上用法吧?)
用法:
面向对象的继承和多态机制有效提高了程序的可重用性和可扩充性。在程序的可重用性方面,程序员还希望得到更多支持。举一个最简单的例子,为了交换两个整型变量的值,需要写下面的 Swap 函数:
void Swap(int & x, int & y) { int tmp = x; x = y; y = tmp; }
为了交换两个 double 型变量的值,还需要编写下面的 Swap 函数:
void Swap (double & xr double & y) { double tmp = x; x = y; y = tmp; }
如果还要交换两个 char 型变量的值,交换两个 CStudent 类对象的值……都需要再编写 Swap 函数。而这些 Swap 函数除了处理的数据类型不同外,形式上都是一样的。能否只写一遍 Swap 函数,就能用来交换各种类型的变量的值呢?继承和多态显然无法解决这个问题。因此,“模板”的概念就应运而生了。
众所周知,有了“模子”后,用“模子”来批量制造陶瓷、塑料、金属制品等就变得容易了。程序设计语言中的模板就是用来批量生成功能和形式都几乎相同的代码的。有了模板,编译器就能在需要的时候,根据模板自动生成程序的代码。从同一个模板自动生成的代码,形式几乎是一样的。
函数模板的原理
C++ 语言支持模板。有了模板,可以只写一个 Swap 模板,编译器会根据 Swap 模板自动生成多个 Sawp 函数,用以交换不同类型变量的值。
在 C++ 中,模板分为函数模板和类模板两种。
- 函数模板是用于生成函数;
- 类模板则是用于生成类的。
函数模板的写法如下:
template <class 类型参数1, class类型参数2, ...> 返回值类型 模板名(形参表) { 函数体 }
其中的 class 关键字也可以用 typename 关键字替换,例如:
template <typename 类型参数1, typename 类型参数2, ...>
函数模板看上去就像一个函数。前面提到的 Swap 模板的写法如下:
template <class T> void Swap(T & x, T & y) { T tmp = x; x = y; y = tmp; }
T 是类型参数,代表类型。编译器由模板自动生成函数时,会用具体的类型名对模板中所有的类型参数进行替换,其他部分则原封不动地保留。同一个类型参数只能替换为同一种类型。编译器在编译到调用函数模板的语句时,会根据实参的类型判断该如何替换模板中的类型参数。
例如下面的程序:
#include <iostream> using namespace std; template<class T> void Swap(T & x, T & y) { T tmp = x; x = y; y = tmp; } int main() { int n = 1, m = 2; Swap(n, m); //编译器自动生成 void Swap (int &, int &)函数 double f = 1.2, g = 2.3; Swap(f, g); //编译器自动生成 void Swap (double &, double &)函数 return 0; }
编译器在编译到Swap(n, m);时找不到函数 Swap 的定义,但是发现实参 n、m 都是 int 类型的,用 int 类型替换 Swap 模板中的 T 能得到下面的函数:
void Swap (int & x, int & y) { int tmp = x; x = y; y = tmp; }
该函数可以匹配Swap(n, m);这条语句。于是编译器就自动用 int 替换 Swap 模板中的 T,生成上面的 Swap 函数,将该 Swap 函数的源代码加入程序中一起编译,并且将Swap(n, m);编译成对自动生成的 Swap 函数的调用。
同理,编译器在编译到Swap(f, g);时会用 double 替换 Swap 模板中的 T,自动生成以下 Swap 函数:
void Swap(double & x, double & y) { double tmp = x; x = y; y = tmp; }
然后再将Swap(f, g);编译成对该 Swap 函数的调用。
编译器由模板自动生成函数的过程叫模板的实例化。由模板实例化而得到的函数称为模板函数。在某些编译器中,模板只有在被实例化时,编译器才会检查其语法正确性。如果程序中写了一个模板却没有用到,那么编译器不会报告这个模板中的语法错误。
编译器对模板进行实例化时,并非只能通过模板调用语句的实参来实例化模板中的类型参数,模板调用语句可以明确指明要把类型参数实例化为哪种类型。可以用:
模板名<实际类型参数1, 实际类型参数2, ...>
的方式告诉编译器应该如何实例化模板函数。例如下面的程序:
#include <iostream> using namespace std; template <class T> T Inc(int n) { return 1 + n; } int main() { cout << Inc<double>(4) / 2; return 0; }
Inc(4)指明了此处实例化的模板函数原型应为:
double Inc(double);
编译器不会因为实参 4 是 int 类型,就生成原型为 int Inc(int) 的函数。因此,上面程序输出的结果是 2.5 而非 2。
函数模板中可以有不止一个类型参数。例如,下面这个函数模板的写法是合法的:
template <class Tl, class T2> T2 print(T1 argl, T2 arg2) { cout << arg1 << " " << arg2 << endl; return arg2; }
【实例】一个求数组中最大元素的函数模板
例题:设计一个分数类 CFraction,再设计一个名为 MaxElement 的函数模板,能够求数组中最大的元素,并用该模板求一个 CFmction 数组中的最大元素。示例程序如下:
#include <iostream> using namespace std; template <class T> T MaxElement(T a[], int size) //size是数组元素个数 { T tmpMax = a[0]; for (int i = 1; i < size; ++i) if (tmpMax < a[i]) tmpMax = a[i]; return tmpMax; } class CFraction //分数类 { int numerator; //分子 int denominator; //分母 public: CFraction(int n, int d) :numerator(n), denominator(d) { }; bool operator <(const CFraction & f) const {//为避免除法产生的浮点误差,用乘法判断两个分数的大小关系 if (denominator * f.denominator > 0) return numerator * f.denominator < denominator * f.numerator; else return numerator * f.denominator > denominator * f.numerator; } bool operator == (const CFraction & f) const {//为避免除法产生的浮点误差,用乘法判断两个分数是否相等 return numerator * f.denominator == denominator * f.numerator; } friend ostream & operator <<(ostream & o, const CFraction & f); }; ostream & operator <<(ostream & o, const CFraction & f) {//重载 << 使得分数对象可以通过cout输出 o << f.numerator << "/" << f.denominator; //输出"分子/分母" 形式 return o; } int main() { int a[5] = { 1,5,2,3,4 }; CFraction f[4] = { CFraction(8,6),CFraction(-8,4), CFraction(3,2), CFraction(5,6) }; cout << MaxElement(a, 5) << endl; cout << MaxElement(f, 4) << endl; return 0; }
编译到第 41 行时,根据实参 a 的类型,编译器通过 MaxElement 模板自动生成了一个 MaxElement 函数,原型为:
int MaxElement(int a[], int size);
编译到第 42 行时,根据 f 的类型,编译器又生成一个 MaxElement 函数,原型为:
CFraction MaxElement(CFraction a[], int size);
在该函数中,用到了<比较两个 CFraction 对象的大小。如果没有对<进行适当的重载,编译时就会出错。
从 MaxElement 模板的写法可以看出,在函数模板中,类型参数不但可以用来定义参数的类型,还能用于定义局部变量和函数模板的返回值。
延申用法
2.1为什么需要类模板
-
类模板与函数模板的定义和使用类似,我们已经进行了介绍。 有时,有两个或多个类,其功能是相同的,仅仅是数据类型不同,如下面语句声明了一个类:
-
类模板用于实现类所需数据的类型参数化
-
类模板在表示如数组、表、图等数据结构显得特别重要,
-
这些数据结构的表示和算法不受所包含的元素类型的影响
2.2单个类模板语法
1 //类的类型参数化 抽象的类 2 //单个类模板 3 template<typename T> 4 class A 5 { 6 public: 7 A(T t) 8 { 9 this->t = t; 10 } 11 12 T &getT() 13 { 14 return t; 15 } 16 protected: 17 public: 18 T t; 19 }; 20 void main() 21 { 22 //模板了中如果使用了构造函数,则遵守以前的类的构造函数的调用规则 23 A<int> a(100); 24 a.getT(); 25 printAA(a); 26 return ; 27 }
2.3继承中的类模板语法
案例1:
1 //结论: 子类从模板类继承的时候,需要让编译器知道 父类的数据类型具体是什么(数据类型的本质:固定大小内存块的别名)A<int> 2 // 3 class B : public A<int> 4 { 5 public: 6 B(int i) : A<int>(i) 7 { 8 9 } 10 void printB() 11 { 12 cout<<"A:"<<t<<endl; 13 } 14 protected: 15 private: 16 }; 17 18 //模板与上继承 19 //怎么样从基类继承 20 //若基类只有一个带参数的构造函数,子类是如何启动父类的构造函数 21 void pintBB(B &b) 22 { 23 b.printB(); 24 } 25 void printAA(A<int> &a) //类模板做函数参数 26 { 27 // 28 a.getT(); 29 } 30 31 void main() 32 { 33 A<int> a(100); //模板了中如果使用了构造函数,则遵守以前的类的构造函数的调用规则 34 a.getT(); 35 printAA(a); 36 37 B b(10); 38 b.printB(); 39 40 41 cout<<"hello..."<<endl; 42 system("pause"); 43 return ; 44 }
案例2:
1 #include<iostream> 2 using namespace std; 3 //A编程模板类--类型参数化 4 /* 5 类模板的定义 类模板的使用 类模板做函数参数 6 */ 7 template <typename T> 8 class A 9 { 10 public: 11 A(T a = 0) 12 { 13 this->a = a; 14 } 15 public: 16 void printA() 17 { 18 cout << "a:" << a << endl; 19 } 20 protected: 21 T a; 22 private: 23 24 }; 25 //从模板类派生时,需要具体化模板类,C++编译器需要知道父类的数据类型是什么样子的 26 //要知道父类所占的内存多少 27 class B :public A<int> 28 { 29 public: 30 B(int a =10, int b =20):A<int>(a) 31 { 32 this->b = b; 33 } 34 void printB() 35 { 36 cout << "a:" << a << "b:" << b << endl; 37 } 38 protected: 39 private: 40 int b; 41 42 }; 43 //从模板类派生模板类 44 template <typename T> 45 class C :public A<T> 46 { 47 48 public: 49 C(T c,T a) : A<T>(a) 50 { 51 this->c = c; 52 } 53 void printC() 54 { 55 cout << "c:" << c << endl; 56 } 57 protected: 58 T c; 59 private: 60 61 }; 62 63 void main() 64 { 65 //B b1(1, 2); 66 //b1.printB(); 67 C<int> c1(1,2); 68 c1.printC(); 69 }
2.4类模板的基础语法
1 #include<iostream> 2 using namespace std; 3 //A编程模板类--类型参数化 4 /* 5 类模板的定义 类模板的使用 类模板做函数参数 6 */ 7 template <typename T> 8 class A 9 { 10 public: 11 A(T a = 0) 12 { 13 this->a = a; 14 } 15 public: 16 void printA() 17 { 18 cout << "a:" << a << endl; 19 } 20 protected: 21 private: 22 T a; 23 }; 24 //参数 C++编译器具体的类 25 void UseA(A<int> &a) 26 { 27 a.printA(); 28 } 29 void main() 30 { 31 //模板类本身就是抽象的,具体的类,具体的变量 32 A<int> a1(11),a2(22),a3(33);//模板类是抽象的, 需要类型具体化 33 //a1.printA(); 34 35 UseA(a1); 36 UseA(a2); 37 UseA(a3); 38 }
2.5类模板语法知识体系梳理
1.所有的类模板函数写在类的内部
代码:
复数类:
1 #include<iostream> 2 using namespace std; 3 template <typename T> 4 class Complex 5 { 6 public: 7 friend Complex MySub(Complex &c1, Complex &c2) 8 { 9 Complex tmp(c1.a-c2.a, c1.b-c2.b); 10 return tmp; 11 } 12 13 friend ostream & operator<< (ostream &out, Complex &c3) 14 { 15 out << c3.a << "+" << c3.b <<"i"<< endl; 16 return out; 17 } 18 Complex(T a, T b) 19 { 20 this->a = a; 21 this->b = b; 22 } 23 Complex operator+(Complex &c2) 24 { 25 Complex tmp(a + c2.a, b + c2.b); 26 return tmp; 27 } 28 void printCom() 29 { 30 cout << "a:" << a << " b:" << b << endl; 31 } 32 protected: 33 private: 34 T a; 35 T b; 36 }; 37 38 /* 39 重载运算符的正规写法: 40 重载左移<< 右移>> 只能用友元函数,其他的运算符重载都要用成员函数,不要滥用友元函数 41 */ 42 //ostream & operator<< (ostream &out, Complex &c3) 43 //{ 44 // out<< "a:" << c3.a << " b:" << c3.b << endl; 45 // return out; 46 //} 47 void main() 48 { 49 Complex<int> c1(1,2); 50 Complex<int> c2(3, 4); 51 52 Complex<int> c3 = c1 + c2;//重载加号运算符 53 54 c3.printCom(); 55 56 //重载左移运算符 57 cout << c3 << endl; 58 59 { 60 Complex<int> c4 = MySub(c1 , c2); 61 62 cout << c4 << endl; 63 } 64 system("pause"); 65 }
2.所有的类模板函数写在类的外部,在一个cpp中
注意:
复制代码
//构造函数 没有问题-
普通函数 没有问题
-
友元函数:用友元函数重载 << >>
friend ostream& operator<< (ostream &out, Complex &c3) ;
-
友元函数:友元函数不是实现函数重载(非 << >>)
1 需要在类前增加 类的前置声明 函数的前置声明
template<typename T> class Complex; template<typename T> Complex<T> mySub(Complex<T> &c1, Complex<T> &c2);
2 类的内部声明 必须写成:
friend Complex<T> mySub <T> (Complex<T> &c1, Complex<T> &c2);
3 友元函数实现 必须写成:
template<typename T> Complex<T> mySub(Complex<T> &c1, Complex<T> &c2) { Complex<T> tmp(c1.a - c2.a, c1.b-c2.b); return tmp; }
4 友元函数调用 必须写成
Complex<int> c4 = mySub<int>(c1, c2); cout<<c4;
结论:友元函数只用来进行 左移 友移操作符重载。
复数类:
代码:
1 #include<iostream> 2 using namespace std; 3 4 template<typename T> 5 class Complex; 6 template<typename T> 7 Complex<T> mySub(Complex<T> &c1, Complex<T> &c2); 8 9 template <typename T> 10 class Complex 11 { 12 public: 13 friend Complex<T> mySub <T>(Complex<T> &c1, Complex<T> &c2); 14 15 friend ostream & operator<< <T>(ostream &out, Complex &c3); 16 Complex(T a, T b); 17 void printCom(); 18 Complex operator+(Complex &c2); 19 Complex operator-(Complex &c2); 20 21 protected: 22 private: 23 T a; 24 T b; 25 }; 26 27 //构造函数的实现,写在了外部 28 template <typename T> 29 Complex<T>::Complex(T a, T b) 30 { 31 this->a = a; 32 this->b = b; 33 } 34 35 template <typename T> 36 void Complex<T>::printCom() 37 { 38 cout << "a:" << a << " b:" << b << endl; 39 } 40 //成员函数实现加号运算符重载 41 template <typename T> 42 Complex<T> Complex<T>::operator+(Complex<T> &c2) 43 { 44 Complex tmp(a + c2.a, b + c2.b); 45 return tmp; 46 } 47 template <typename T> 48 Complex<T> Complex<T>::operator-(Complex<T> &c2) 49 { 50 Complex(a-c2.a,a-c2.b); 51 return tmp; 52 } 53 //友元函数实现<<左移运算符重载 54 55 /* 56 严重性 代码 说明 项目 文件 行 禁止显示状态 57 错误 C2768 “operator <<”: 非法使用显式模板参数 泛型编程课堂操练 58 59 错误的本质:两次编译的函数头,第一次编译的函数头,和第二次编译的函数有不一样 60 */ 61 template <typename T> 62 ostream & operator<< (ostream &out, Complex<T> &c3)//不加T 63 { 64 out << c3.a << "+" << c3.b << "i" << endl; 65 return out; 66 } 67 68 69 // 70 template <typename T> 71 Complex<T> mySub(Complex<T> &c1, Complex<T> &c2) 72 { 73 Complex<T> tmp(c1.a - c2.a, c1.b - c2.b); 74 return tmp; 75 } 76 77 void main() 78 { 79 Complex<int> c1(1, 2); 80 Complex<int> c2(3, 4); 81 82 Complex<int> c3 = c1 + c2;//重载加号运算符 83 84 c3.printCom(); 85 86 //重载左移运算符 87 cout << c3 << endl; 88 89 { 90 Complex<int> c4 = mySub<int>(c1, c2); 91 92 cout << c4 << endl; 93 } 94 system("pause"); 95 }
所有的类模板函数写在类的外部,在不同的.h和.cpp中
也就是类模板函数说明和类模板实现分开//类模板函数
构造函数
普通成员函数
友元函数
用友元函数重载<<>>;
用友元函数重载非<< >>
demo_complex.cpp
1 #include"demo_09complex.h" 2 #include<iostream> 3 using namespace std; 4 5 template <typename T> 6 Complex<T>::Complex(T a, T b) 7 { 8 this->a = a; 9 this->b = b; 10 } 11 12 template <typename T> 13 void Complex<T>::printCom() 14 { 15 cout << "a:" << a << " b:" << b << endl; 16 } 17 //成员函数实现加号运算符重载 18 template <typename T> 19 Complex<T> Complex<T>::operator+(Complex<T> &c2) 20 { 21 Complex tmp(a + c2.a, b + c2.b); 22 return tmp; 23 } 24 //template <typename T> 25 //Complex<T> Complex<T>::operator-(Complex<T> &c2) 26 //{ 27 // Complex(a - c2.a, a - c2.b); 28 // return tmp; 29 //} 30 template <typename T> 31 ostream & operator<< (ostream &out, Complex<T> &c3)//不加T 32 { 33 out << c3.a << "+" << c3.b << "i" << endl; 34 return out; 35 } 36 37 38 // 39 //template <typename T> 40 //Complex<T> mySub(Complex<T> &c1, Complex<T> &c2) 41 //{ 42 // Complex<T> tmp(c1.a - c2.a, c1.b - c2.b); 43 // return tmp; 44 //}
demo_09complex.h
1 #pragma once 2 #include<iostream> 3 using namespace std; 4 template <typename T> 5 class Complex 6 { 7 public: 8 //friend Complex<T> mySub <T>(Complex<T> &c1, Complex<T> &c2); 9 10 friend ostream & operator<< <T>(ostream &out, Complex &c3); 11 Complex(T a, T b); 12 void printCom(); 13 Complex operator+(Complex &c2); 14 //Complex operator-(Complex &c2); 15 16 protected: 17 private: 18 T a; 19 T b; 20 };
demo_09complex_text.cpp
1 #include"demo_09complex.h" 2 #include"demo_09complex.cpp" 3 4 #include<iostream> 5 using namespace std; 6 7 void main() 8 { 9 Complex<int> c1(1, 2); 10 Complex<int> c2(3, 4); 11 12 Complex<int> c3 = c1 + c2;//重载加号运算符 13 14 c3.printCom(); 15 16 //重载左移运算符 17 cout << c3 << endl; 18 19 /*{ 20 Complex<int> c4 = mySub<int>(c1, c2); 21 22 cout << c4 << endl; 23 }*/ 24 system("pause"); 25 }
2.5总结
归纳以上的介绍,可以这样声明和使用类模板:
- 先写出一个实际的类。由于其语义明确,含义清楚,一般不会出错。
- 将此类中准备改变的类型名(如int要改变为float或char)改用一个自己指定的虚拟类型名(如上例中的numtype)。
- 在类声明前面加入一行,格式为:
template <class 虚拟类型参数> 如: template <class numtype> //注意本行末尾无分号 class Compare {…}; //类体
- 用类模板定义对象时用以下形式:
类模板名<实际类型名> 对象名; 类模板名<实际类型名> 对象名(实参表列); 如: Compare<int> cmp; Compare<int> cmp(3,7);
- 如果在类模板外定义成员函数,应写成类模板形式:
template <class 虚拟类型参数> 函数类型 类模板名<虚拟类型参数>::成员函数名(函数形参表列) {…}
关于类模板的几点说明:
-
类模板的类型参数可以有一个或多个,每个类型前面都必须加class,如:
template <class T1,class T2> class someclass {…};
在定义对象时分别代入实际的类型名,如:
someclass<int,double> obj;
-
和使用类一样,使用类模板时要注意其作用域,只能在其有效作用域内用它定义对象。
-
模板可以有层次,一个类模板可以作为基类,派生出派生模板类。
2.6类模板中的static关键字
从类模板实例化的每个模板类有自己的类模板数据成员,该模板类的所有对象共享一个static数据成员和非模板类的static数据成员一样,模板类的static数据成员也应该在文件范围定义和初始化
每个模板类有自己的类模板的static数据成员副本1 #include<iostream> 2 using namespace std; 3 template <typename T> 4 class AA 5 { 6 public: 7 static T m_a; 8 protected: 9 private: 10 }; 11 template <typename T> 12 T AA<T>::m_a =0; 13 void main() 14 { 15 AA<int> a1, a2, a3; 16 a1.m_a = 10; 17 a2.m_a++; 18 a3.m_a++; 19 cout << AA<int>::m_a << endl; 20 21 AA<char> b1, b2, b3; 22 b1.m_a = 'a'; 23 b2.m_a++; 24 b3.m_a++; 25 cout << AA<char>::m_a << endl; 26 27 //m_a是每个类型的类,去使用,手工写两个类 int char 28 system("pause"); 29 }
案例2:以下来自:C++类模板遇上static关键字
1 #include <iostream> 2 using namespace std; 3 4 template<typename T> 5 class Obj{ 6 public: 7 static T m_t; 8 }; 9 10 template<typename T> 11 T Obj<T>::m_t = 0; 12 13 int main04(){ 14 Obj<int> i1,i2,i3; 15 i1.m_t = 10; 16 i2.m_t++; 17 i3.m_t++; 18 cout << Obj<int>::m_t<<endl; 19 20 Obj<float> f1,f2,f3; 21 f1.m_t = 10; 22 f2.m_t++; 23 f3.m_t++; 24 cout << Obj<float>::m_t<<endl; 25 26 Obj<char> c1,c2,c3; 27 c1.m_t = 'a'; 28 c2.m_t++; 29 c3.m_t++; 30 cout << Obj<char>::m_t<<endl; 31 }
当类模板中出现static修饰的静态类成员的时候,我们只要按照正常理解就可以了。static的作用是将类的成员修饰成静态的,所谓的静态类成员就是指类的成员为类级别的,不需要实例化对象就可以使用,而且类的所有对象都共享同一个静态类成员,因为类静态成员是属于类而不是对象。那么,类模板的实现机制是通过二次编译原理实现的。c++编译器并不是在第一个编译类模板的时候就把所有可能出现的类型都分别编译出对应的类(太多组合了),而是在第一个编译的时候编译一部分,遇到泛型不会替换成具体的类型(这个时候编译器还不知道具体的类型),而是在第二次编译的时候再将泛型替换成具体的类型(这个时候编译器知道了具体的类型了)。由于类模板的二次编译原理再加上static关键字修饰的成员,当它们在一起的时候实际上一个类模板会被编译成多个具体类型的类,所以,不同类型的类模板对应的static成员也是不同的(不同的类),但相同类型的类模板的static成员是共享的(同一个类)。
2.7类模板在项目开发中的应用
小结
-
模板是C++类型参数化的多态工具。C++提供函数模板和类模板。
-
模板定义以模板说明开始。类属参数必须在模板定义中至少出现一次。
-
同一个类属参数可以用于多个模板。
-
类属参数可用于函数的参数类型、返回类型和声明函数中的变量。
-
模板由编译器根据实际数据类型实例化,生成可执行代码。实例化的函数。
-
模板称为模板函数;实例化的类模板称为模板类。
-
函数模板可以用多种方式重载。
-
类模板可以在类层次中使用 。
训练题
1) 请设计一个数组模板类( MyVector ),完成对int、char、Teacher类型元素的管理。
设计:
-
类模板 构造函数 拷贝构造函数 << [] 重载=操作符
-
a2=a1
-
实现
2) 请仔细思考:
a) 如果数组模板类中的元素是Teacher元素时,需要Teacher类做什么工作
b) 如果数组模板类中的元素是Teacher元素时,Teacher类含有指针属性哪?
1 class Teacher 2 { 3 friend ostream & operator<<(ostream &out, const Teacher &obj); 4 public: 5 Teacher(char *name, int age) 6 { 7 this->age = age; 8 strcpy(this->name, name); 9 } 10 11 Teacher() 12 { 13 this->age = 0; 14 strcpy(this->name, ""); 15 } 16 17 private: 18 int age; 19 char name[32]; 20 }; 21 22 23 class Teacher 24 { 25 friend ostream & operator<<(ostream &out, const Teacher &obj); 26 public: 27 Teacher(char *name, int age) 28 { 29 this->age = age; 30 strcpy(this->name, name); 31 } 32 33 Teacher() 34 { 35 this->age = 0; 36 strcpy(this->name, ""); 37 } 38 39 private: 40 int age; 41 char *pname; 42 };
结论1: 如果把Teacher放入到MyVector数组中,并且Teacher类的属性含有指针,就是出现深拷贝和浅拷贝的问题。
结论2:需要Teacher封装的函数有:
-
重写拷贝构造函数
-
重载等号操作符
-
重载左移操作符。
理论提高:
所有容器提供的都是值(value)语意,而非引用(reference)语意。容器执行插入元素的操作时,内部实施拷贝动作。所以STL容器内存储的元素必须能够被拷贝(必须提供拷贝构造函数)。
3) 请从数组模板中进行派生
1 //演示从模板类 派生 一般类 2 #include "MyVector.cpp" 3 4 class MyArray01 : public MyVector<double> 5 { 6 public: 7 MyArray01(int len) : MyVector<double>(len) 8 { 9 ; 10 } 11 protected: 12 private: 13 }; 14 15 16 //演示从模板类 派生 模板类 //BoundArray 17 template <typename T> 18 class MyArray02 : public MyVector<T> 19 { 20 public: 21 MyArray02(int len) : MyVector<double>(len) 22 { 23 ; 24 } 25 protected: 26 private: 27 }; 28 测试案例: 29 30 //演示 从模板类 继承 模板类 31 void main() 32 { 33 MyArray02<double> dArray2(10); 34 dArray2[1] = 3.15; 35 36 } 37 38 39 //演示 从模板类 继承 一般类 40 void main11() 41 { 42 MyArray01 d_array(10); 43 44 for (int i=0; i<d_array.getLen(); i++) 45 { 46 d_array[i] = 3.15; 47 } 48 49 for (int i=0; i<d_array.getLen(); i++) 50 { 51 cout << d_array[i] << " "; 52 } 53 54 cout<<"hello..."<<endl; 55 system("pause"); 56 return ; 57 }
作业:
封装你自己的数组类;设计被存储的元素为类对象;
思考:类对象的类,应该实现的功能。
-
优化Teacher类, 属性变成 char *panme, 构造函数里面 分配内存
-
优化Teacher类,析构函数 释放panme指向的内存空间
-
优化Teacher类,避免浅拷贝 重载= 重写拷贝构造函数
-
优化Teacher类,在Teacher增加 <<
-
在模板数组类中,存int char Teacher Teacher*(指针类型)
zuoye.h
1 #pragma once 2 3 template <typename T> 4 class MyVector 5 { 6 7 //friend ostream & operator<< <T>(ostream &out, const MyVector &obj); 8 public: 9 MyVector(int size = 0);//构造函数 10 MyVector(const MyVector &obj);//copy构造函数 11 ~MyVector(); 12 public: 13 T& operator [](int index); 14 MyVector &operator=(const MyVector &obj); 15 16 17 int getLen() 18 { 19 return m_len; 20 } 21 protected: 22 private: 23 T *m_space; 24 int m_len; 25 26 };
zuoye_test12.cpp
1 #include"zuoye.h" 2 #include"zuoye12.cpp" 3 #include<iostream> 4 using namespace std; 5 class Teacher 6 { 7 public: 8 Teacher() 9 { 10 age = 33; 11 m_p = new char[1]; 12 strcpy(m_p, " "); 13 } 14 15 Teacher(char *name, int age) 16 { 17 this->age = age; 18 m_p = new char[strlen(name)+1]; 19 strcpy(this->m_p, name); 20 21 } 22 Teacher(const Teacher &obj) 23 { 24 m_p = new char[strlen(obj.m_p) + 1]; 25 strcpy(this->m_p, obj.m_p); 26 age = obj.age; 27 } 28 ~Teacher() 29 { 30 if (m_p!=NULL) 31 { 32 delete[] m_p; 33 m_p = NULL; 34 } 35 } 36 void printT() 37 { 38 cout << m_p << ", " << age; 39 } 40 public: 41 //重载<< == 42 friend ostream & operator<<(ostream &out,Teacher &t); 43 Teacher & operator=(const Teacher &obj) 44 { 45 //1 46 if (m_p!=NULL) 47 { 48 delete[] m_p; 49 m_p = NULL; 50 age = 33; 51 } 52 //2 53 m_p = new char[strlen(obj.m_p) + 1]; 54 age = obj.age; 55 //3 56 strcpy(this->m_p, obj.m_p); 57 return *this; 58 } 59 protected: 60 private: 61 int age; 62 //char name[32]; 63 char *m_p; 64 }; 65 ostream & operator<<(ostream &out, Teacher &t) 66 { 67 out << t.m_p << ", " << t.age << endl; 68 return out; 69 } 70 71 void main() 72 { 73 Teacher t1("t1", 31), t2("t2", 32); 74 75 MyVector<Teacher *> Tarray(2); 76 77 Tarray[0] = &t1; 78 Tarray[1] = &t2; 79 for (int i = 0; i < 2; i++) 80 { 81 Teacher *tmp = Tarray[i]; 82 tmp->printT(); 83 } 84 system("pause"); 85 } 86 void main123() 87 { 88 Teacher t1("t1", 31), t2("t2", 32); 89 MyVector<Teacher> Tarray(2); 90 Tarray[0] = t1; 91 Tarray[1] = t2; 92 for (int i = 0; i < 2; i++) 93 { 94 Teacher tmp = Tarray[i]; 95 tmp.printT(); 96 } 97 system("pause"); 98 } 99 void main112() 100 { 101 MyVector<int> myv1(10); 102 myv1[0] = 'a'; 103 myv1[1] = 'b'; 104 myv1[2] = 'c'; 105 myv1[3] = 'd'; 106 myv1[4] = 'e'; 107 //cout << myv1; 108 MyVector<int> myv2 = myv1; 109 } 110 111 112 void main111() 113 { 114 MyVector<int> myv1(10); 115 for (int i = 0; i < myv1.getLen(); i++) 116 { 117 myv1[i] = i + 1; 118 cout << myv1[i] << " "; 119 } 120 121 MyVector<int> myv2 = myv1; 122 for (int i = 0; i < myv2.getLen(); i++) 123 { 124 myv2[i] = i + 1; 125 cout << myv2[i] << " "; 126 } 127 128 //cout << myv2 << endl;//重载左移运算符 129 130 system("pause"); 131 }
zuoye12.cpp
1 #include"zuoye.h" 2 #include<iostream> 3 using namespace std; 4 5 template <typename T> 6 ostream & operator<<(ostream &out, const MyVector<T> &obj) 7 { 8 for (int i = 0; i<obj.m_len; i++) 9 { 10 out << obj.m_space[i] << " "; 11 } 12 out << endl; 13 return out; 14 } 15 //构造函数 16 template <typename T> 17 MyVector<T>::MyVector(int size = 0) 18 { 19 m_space = new T[size]; 20 m_len = size; 21 } 22 //MyVector<int> myv2 = myv1; 23 template <typename T> 24 MyVector<T>::MyVector(const MyVector &obj) 25 { 26 //根据大小分配内存 27 m_len = obj.m_len; 28 m_space = new T[m_len]; 29 //copy数据 30 for (int i = 0; i<m_len; i++) 31 { 32 m_space[i] = obj.m_space[i]; 33 } 34 } 35 template <typename T> 36 MyVector<T>::~MyVector() 37 { 38 if (m_space != NULL) 39 { 40 delete[] m_space; 41 m_space = NULL; 42 m_len = 0; 43 44 } 45 } 46 template <typename T> 47 T& MyVector<T>::operator [](int index) 48 { 49 return m_space[index]; 50 } 51 template <typename T> 52 MyVector<T> & MyVector<T>::operator=(const MyVector<T> &obj) 53 { 54 //先把a2的内存释放掉 55 if (m_space != NULL) 56 { 57 delete[] m_space; 58 m_space = NULL; 59 m_len = 0; 60 61 } 62 63 //根据a1分配内存 64 m_len = obj.m_len; 65 m_space = new T[m_len]; 66 67 //copy数据 68 for (i = 0; i<m_len; i += ) 69 { 70 m_space[i] = obj.m_space[i]; 71 } 72 return *this;//a2= a1 返回a2的自身 73 }
参考一些资料,加上一些见解,如果有雷同,纯属巧合。
更多C++相关知识体系,请移步C++知识目录。 -
函数模板与模板函数
2019-05-31 21:20:21函数模板与模板函数 函数模板是一组函数的抽象描述,它不是一个实实在在的函数,函数模板不会编译成任何目标代码。函数模板必须先实例化成模板函数,这些模板函数再程序运行时会进行编译和链接,然后产生相应的...函数模板与模板函数
- 函数模板是一组函数的抽象描述,它不是一个实实在在的函数,函数模板不会编译成任何目标代码。函数模板必须先实例化成模板函数,这些模板函数再程序运行时会进行编译和链接,然后产生相应的目标代码。
函数模板的异常处理
- 函数模板中的模板形参可实例化为各种类型,但当实例化模板形参的各模板实参之间不完全相同,就可能发生错误,如:
template < class T> void min(T& x,T& y) //函数模板 { return (x<y)?x:y; } void func(int i,char j) { min(i,i); //实例化的模板函数,正确调用 min(i,j); //实例化的模板函数,但错误调用 }
-
上述代码中,min(i,i)的调用是正确的,而min(i,j)的调用是错误的。因为在函数模板中声明了min的两个参数必须是同一个类型,在调用时,编译器会对所有模板函数进行一致性检查。例如min(i,j)在编译时,编译器先将模板形参T解释为int类型,此后出现的模板形参j却为字符型,所以编译产生错误,此时没有隐含的类型转换功能。
-
解决此种异常的方法有两种:
1.采用强制类型转换,将min(i,j)改写为min(i,(int)j)。
2.用非模板函数重载函数模板,方法有两种:
①.借用函数模板的函数体。此时只声明非模板函数的原型,它的函数体借用函数模板的函数体,如:
template < class T> void min(T& x,T& y) //函数模板 { return (x<y)?x:y; } int min(int,int); //非模板函数重载函数模板解决异常问题 void func(int i,char j) { min(i,i); min(i,j); } //这时执行不会出错,因为重载函数支持数据间的隐式类型转换。
②.重新定义一个完整的非模板函数。
-
函数指针 指针函数 模板类 类模板 函数模板 模板函数
2016-08-31 15:02:20指针函数:本质是一个函数,函数的返回值是某一类型的指针。形式一般如下: 类型标识符 * 函数名(参数列表) 如 int * f(x,y). 函数指针:本质上是指针,它指向的是一个函数。...函数模板:对一批模样相同的函数指针函数:本质是一个函数,函数的返回值是某一类型的指针。形式一般如下: 类型标识符 * 函数名(参数列表) 如 int * f(x,y).
函数指针:本质上是指针,它指向的是一个函数。形式一般如下:类型标识符 (*函数名)(参数) 如 int (*pf)(int x)。
数组指针: int (*p)[ 4 ].
指针数组: int * p [ 4 ] .
函数模板:对一批模样相同的函数的说明描述,他不是某一个具体的函数,不能具体执行,需要实例化为模板函数后才能执行,而一旦数据类型形参实例化以后,就会产生一个实实在在的模板函数;减少了程序员输入代码的工作量
模板函数:则是将函数模板内的‘数据类型参数’具体化后得到的重载函数,就是由模板而来的函数。
简单的说,模板函数是具体的,而函数模板是抽象的。
类模板:描述了代码类似的部分类的集合,具体为模板类后,才能生成具体的对象。
类模板不是一个具体的类,是对类的一种具体描述,必须用类型参数将其实例化为模板类后,才能用来生成具体的对象。简而言之,类是对象的抽象,类模板是类的抽象。
C++ 中引入模板类主要有以下几个方面的内容:
1) 可用来创建动态增长或减小的数据结构。
2)它是与类型无关的,因此具有很高的可复用性。
3)它是与平台无关的,可移植性强。
4)在编译时而不是运行时检查数据类型,保证了类型的安全。
5)可用于基本的数据类型。
指针常量是指定义的指针只能在定义的时候初始化,之后不能改变其值。
例如:char * const p1 int * const p2.... 常指针的值不能改变,但是其所指向的内容却可以改变。
常量指针是指向常量的指针,因为常量指针指向的对象是常量,因此这个对象的值是不能改变的。
如:int const *p const int *p
指针常量强调的是指针的不可改变性,指针指向的内容具有可变性。而常量指针强调的是指针所指向对象的不可改变性,它所指向的对象是不能通过常量指针来改变的。
-
函数模板和模板函数
2017-07-18 09:38:261.函数模板的声明和模板函数的生成 1.1函数模板的声明 函数模板可以用来创建一个通用的函数,以支持多种不同的形参,避免重载函数的函数体重复设计。它的最大特点是把函数使用的数据类型作为参数。 函数模板的... -
C++ 函数模板和排序的函数模板
2020-02-27 15:26:281.函数模板的声明 函数模板可以用来创建一个通用的函数,以支持多种不同的形参,避免重载函数的函数体重复设计。它的最大特点是把函数使用的数据类型作为参数。 函数模板的声明形式为: template<typename 数据... -
C++模板:函数模板
2018-09-10 23:40:52函数模板: 函数模板: 我们看这段代码,compare是一个函数名还是一个模板名?其实他是一个模板名。如果要把他看成一函数来使用,就要用一个类型来实例化这个模板,在使用时可以给其后尖括号中加上要使用的... -
C++函数模板和模板函数、类模板和模板类
2019-05-08 10:03:04这期间有涉及到函数模板与模板函数,类模板与模板类的概念 (类似于类与类对象的区别) 注意:模板类的函数声明和实现必须都在头文件中完成,不能像普通类那样声明在.h文件中,实现在.cpp文件中。 1、函数模板和... -
C++函数模板使用心得(函数模板,函数模板的显示具体化,函数模板的显示实例化的声明顺序)
2018-08-31 13:30:051.在编写C++程序时,很多情况下会同时使用函数模板和函数模板的显示具体化: void Swap(T &a, T &b) { T temp; temp = a; a = b; b = temp; } template <> void Swap<... -
C++函数模板和模板函数的区别
2020-09-02 09:44:52函数模板:函数的重载能够实现一个函数名多用,将实现相同或者相似功能的函数用同一个函数名来定义,但是在程序中仍然要分别定义每一个函数。为进一步简化,C++提供了函数模板,实际上是建立一个通用函数,其函数... -
函数模板,函数模板重载,可变参数模板,函数模板覆盖,通过引用交换数据
2014-08-17 00:28:591.函数模板初级,如果想使用模板,需要实例化,实例化的方式是加上数据类型> #include //函数模板可以对类型进行优化重载,根据类型会覆盖 //如果仍然要使用模板函数,需要实例化 ... -
模板类与类模板、函数模板与模板函数等的区别
2019-05-08 10:12:04模板类与类模板、函数模板与模板函数等的区别 函数指针 = 指向函数的指针 指针函数=返回指针的函数 数组指针=指向数组的指针 指针数组=内容是指针的数组 类模板=用来产生类的模板 模板类=使用类模板产生的类... -
C++函数模板和模板函数
2015-12-25 18:10:091.函数模板的声明和模板函数的生成 1.1函数模板的声明 函数模板可以用来创建一个通用的函数,以支持多种不同的形参,避免了重载函数的多个函数体。它的最大特点是把函数使用的数据类型作为参数。 函数模板的... -
VS C++ 函数模板、模板函数
2018-11-01 19:42:06函数模板是不能直接使用的,必须进行实例化后才能使用 一个函数模板能实例化出无数个模板函数 // 模板函数 T fun<int> (T a); T fun<float> (T a); T fun<... -
函数模板、函数模板实例化、函数模板重载
2018-10-14 19:39:34模板分为模板函数和模板类。 如果是交换两个数据,我们会定义对应类型的函数,比如要交换int类型数据,我们会定义int类型swap函数,如果是交换double类型数据,会再定义double类型交换函数。 void Swap(int&... -
从零开始学C++之模板(一):函数模板、函数模板特化、重载函数模板、非模板函数重载
2013-07-20 20:09:09一、引子 考虑求两数较大值函数max(a,b) 对于a,b的不同类型,都有相同的处理形式: return a 用已有方法解决: ...(3)使用函数模板 二、模板 模板是一种参数化的多态工具 所谓参数化的多
-
win远程多用户rdpwrap配置文件(10.0.10240.18485)
-
归并两个链表
-
Vue之第三方通用接口页面
-
课程作业-C#学生成绩管理系统
-
vue如何监控属性值变化?
-
小乌龟一键清除SVN,断开项目SVN
-
uni-app实战专题
-
【大前端】说说vue的侦听器
-
Anaconda安装pytorch0.4.1和对应的torchvision
-
win远程多用户rdpwrap配置文件(6.3.9600.19318)
-
AQUAS 2020 1.1.1.zip
-
JS数组
-
【数据分析-随到随学】数据分析基础及方法论
-
课程设计-C#酒店管理系统
-
彻底学会正则表达式
-
hadoop自动化运维工具Ambari应用实践
-
Bootstrap开发实战撩课学院界面实现
-
朱有鹏老师嵌入式linux核心课程2期介绍
-
C#文件传输、Socket通信、大文件断点续传
-
win远程多用户rdpwrap配置文件(10.0.10240.18186)