类和对象之模板
Posted 可乐不解渴
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了类和对象之模板相关的知识,希望对你有一定的参考价值。
文章目录
前言
在我们现代的制造业当中,人们广泛地使用各种模具批量生产各种外形相同的产品,比如说手机的外壳,这些模具就可以看作是各种产品的模板(template)。
1.函数模板
像我们平时写的程序中,如果有定义众多的
swap()
函数,这个函数只是参数类型不同,但是功能完全相同。类似这种情况,如果能将逻辑功能相同而函数参数和函数值类型不同的多个重载函数用一个函数来描述,将会使代码的可重用性大大提高,从而提高软件的开发效率,此时C++提供的函数模板就是为了解决这个问题。函数模板的定义形式如下:
template<class 类型名 1,class 类型名 2,...>
返回类型 函数名(形参表)
{
函数体;
}
template
关键字表示声明的是模板。< >
中是模板的参数表,可以有一项或者多项,其中的类型名称为参数化类型,是一种抽象类型或者可变类型。class
是类型关键字,也可以使用typename
作为关键字。- 函数返回值类型可以是普通的内置类型,也可以是自定义类型,还可以是模板参数表中的指定的类型。
- 模板参数表中的参数类型可以是基本数据类型。
例如,下面将swap函数定义成了一个函数模板:
template<class T>
void Swap(T &val1, T &val2)
{
T temp = val1;
val1 = val2;
val2 = temp;
}
下面的一个函数模板的参数表中带有基本数据类型的形式参数:
template<class T,int size>
T sum()
{
...
}
1.1 函数模板的实例化
函数模板实例化分为显式实例化和隐式实例化。
1.1.1 显式实例化的格式如下:
函数名 <具体的类型名 1,具体的类型名 2, ... ,常量表达式> (实参表)
- 根据
< >
中给出的具体类型,用类似于函数调用实参于形参结合的方式,将模板参数表中的参数化类型一一实例化成具体的类型,将函数中的参数化类型也一一实例化。- 如果模板参数表中有形式参数,还需要用常量表达式初始化。
- 例1,使用
Swap< double >(8.1,9.2)
将void Swap(T &val1, T &val2)
示例化成
void Swap(double &val1,double &val2);
- 例2,使用sum<int,100>将T sum()实例化成int sum(),其中size获得初值100。
1.1.2 隐式实例化的格式如下:
- 隐式实例化的格式像函数调用一样,实例化过程是在实参与形参结合时用实参的类型实例化形参对应的参数化类型。
- 例3,使用
Swap('A','B')
将void Swap(T &val1, T &val2)
实例化成:
void Swap(char &val1, char &val2);
★注意
使用隐式实例化无法初始化模板参数表中的普通类型的形参,如果模板参数表中使用普通类型参数,必须使用显示初始化。
函数模板示例化后,函数调用执行的实际上是由函数模板生成的模板函数。
1.2 普通函数与函数模板调用规则
- 当我们的代码中出现了普通函数并且函数模板同名且同时也满足该函数的调用。此时普通函数与函数模板的调用规则如下:
1.如果函数模板和普通函数都可以调用,优先调用普通函数。
2.可以通过空模板参数列表 强制调用 函数模板。
3.函数模板也可以发生函数重载。
4.如果函数模板可以产生更好的匹配,优先调用函数模板。
具体问题具体分析,请看下面的例4:
void myPrint(int x, int y)
{
cout << "调用普通函数" << endl;
}
template<class T>
void myPrint(T x, T y)
{
cout << "调用模板函数" << endl;
}
template<class T>
void myPrint(T x, T y,T z)
{
cout << "调用重载的模板函数" << endl;
}
void test01()
{
int a = 10;
int b = 20;
//优先调用普通函数
myPrint(a, b);
//通过空模板参数列表,强制调用函数模板
myPrint<>(a, b);
//函数模板也可以发生重载
myPrint(a, b, 100);
//如果函数模板产生更好的匹配,优先调用函数模板
char c1 = 'a';
char c2 = 'b';
myPrint(c1,c2);
}
//总结:既让提供了函数模板,最好就不要提供普通函数,否则容易出现二义性
int main()
{
test01();
system("pause");
return 0;
}
结果如下:
1.2.1 普通函数与函数模板区别
普通函数调用可以发生隐式类型转换。
函数模板用自动类型推导,不可以发生隐式类型转换。
函数模板用显示指定类型,可以发生隐式类型转换。
2.类模板
和函数模板一样,用户也可以抽象出类的若干共同特性定义为类模板。
类模板
就是带有类型参数的类,是能根据不同参数建立不同类型成员的具体类,称之为类的实例化。类模板中的数据成员、成员函数的参数、成员函数的返回值都可以取不同的类型,在实例化成具体对象时根据传入的实际参数类型实例化成具体类型的对象。
- 类模板定义的语法如下:
template< 模板参数表 >
class 类名
{
成员名;
};
- 模板类的成员函数还可以在类外定义,其语法如下:
template< 模板参数表 >
类型 类名 < 模板参数名表 >::函数名(参数表)
{
函数体;
}
- 其中:
1.模板参数表与类模板的模板参数表相同。
2.模板参数名表列出的是模板参数表中的参数名,顺序与模板参数表中的顺序一致。
2.1 类模板的实例化
- 一个类模板是具体类的抽象,在使用类模板建立对象时才根据给定的模板参数值实例化成具体的类,然后由类建立对象。与函数模板不同,类模板实例化只能采用显式方式。
- 类模板示例化、建立对象的语法具体如下:
类模板名 <模板参数值表> 对象名称;
其中:
- 模板参数值表的值为类型名,类型名可以是基本数据类型名,也可以是构造数据类型名,还可以是类类型名。
- 模板参数值表的值还可以是常数表达式,以初始化模板参数表中的普通参数。
- 模板参数值表的值按一一对应的顺序实例化类模板的模板参数表。
2.2类模板成员函数和普通类中成员函数区别
类模板中成员函数和普通类中成员函数创建时机是有区别的:
普通类中的成员函数一开始就可以创建
。类模板中的成员函数在调用才创建
。
总结:类模板中的成员函数并不是一开始就创建的,是在调用时才去创建。
3.模板编程
3.1 动态数组模板
#define _CRT_SECURE_NO_WARNINGS 1
#pragma once
#include<iostream>
#include<assert.h>
using namespace std;
template <class T>
class MyArray
{
public:
MyArray(int capacity)
{
this->m_Capacity = capacity;
this->m_Size = 0;
this->pAddress = new T[this->m_Capacity];
}
//拷贝构造
MyArray(const MyArray& arr)
{
this->m_Capacity = arr.m_Capacity;
this->m_Size = arr.m_Size;
//深拷贝
this->pAddress = new T[arr.m_Capacity];
//将 arr中的数据都拷贝过来
for (int i = 0; i < this->m_Size; i++)
{
this->pAddress[i] = arr.pAddress[i];
}
}
//重载operator =防止浅拷贝问题
MyArray& operator =(const MyArray&arr)
{
//先判断原来堆区是否有数据,如果有先释放干净
if (this->pAddress != NULL)
{
delete[]this->pAddress;
this->pAddress = NULL;
this->m_Size = 0;
this->m_Capacity = 0;
}
//深拷贝
this->m_Capacity = arr.m_Capacity;
this->m_Size = arr.m_Size;
//深拷贝
this->pAddress = new T[arr.m_Capacity];
//将 arr中的数据都拷贝过来
for (int i = 0; i < this->m_Size; i++)
{
this->pAddress[i] = arr.pAddress[i];
}
return *this;
}
//尾插法
void Push_Back(const T& val)
{
//判断容量是否已满
if (this->m_Capacity == this->m_Size)
{
cout << "空间已满" << endl;
return;
}
this->pAddress[this->m_Size] = val; //在数组末尾 插入数据
this->m_Size++; //更新数组大小
}
//尾删法
void Pop_Back()
{
//让用户访问不到最后一个元素,即为尾删,逻辑删除
if (this->m_Size == 0)
{
cout << "没有元素,删除失败" << endl;
return;
}
this->m_Size--;
}
//通过下标方式访问数组中的元素
T& operator[](int index)
{
//考虑越界
assert(index < this->m_Size&& index>= 0);
return this->pAddress[index];
}
//返回数组容量
int getCapacity()
{
return this->m_Capacity;
}
//返回数组大小
int getSize()
{
return this->m_Size;
}
~MyArray()
{
if (this->pAddress != NULL)
{
delete[]this->pAddress;
this->pAddress = NULL;
}
}
private:
T* pAddress; //指针指向堆区开辟的真实数组
int m_Capacity; //数组容量
int m_Size; //元素个数
};
3.2 栈类模板
栈是一种先进后出(First In Last Out,FILO)的结构,在程序设计中被广泛的使用。栈的基本操作由压栈、出栈,其他的操作有判空、判满、读栈顶元素等。如若不懂,请点击查看我的往期栈的博客。
下面将栈设计成一个类模板,在栈中存放任意类型的数据。
#include<iostream>
template<class T>
class Stack
{
public:
Stack(int size= 10);
//析构函数
~Stack()
{
delete[]this->m_space;
}
int ma(int);
bool push(const T& element);
T pop();
//判空
bool IsEmpty() const
{
return this->m_top == this->m_size;
}
//判满
bool IsFull() const
{
return this->m_top == 0;
}
private:
int m_size; //元素个数
int m_top; //栈顶
T* m_space; //栈的空间
};
//构造函数
template<class T>
Stack<T>::Stack(int size)
{
this->m_size = size;
this->m_space = new T[size];
this->m_top = size;
}
//入栈
template<class T>
bool Stack<T>::push(const T& element)
{
if (!IsFull())
{
this->m_space[--this->m_top] = element;
return true;
}
else
{
return false;
}
}
//出栈
template<class T>
T Stack<T>::pop()
{
return this->m_space[this->m_top++];
}
4.模板总结:
【优点】
1.模板复用了代码,节省资源,更快的迭代开发,C++的标准模板库(STL)因此而产生。
2.增强了代码的灵活性。
【缺陷】
1.模板会导致代码膨胀问题,也会导致编译时间变长。
2.出现模板编译错误时,错误信息非常凌乱,不易定位错误。
3.模板不支持分离编译。
END...
以上是关于类和对象之模板的主要内容,如果未能解决你的问题,请参考以下文章