群体数据:自定义类型的数据由多个基本类型或自定义类型的元素组成
群体类:对于群体数据,仅有系统预定义的操作是不够的,在很多情况下,还需要设计与某些具体问题相关的特殊操作,并按照面向对象的方法将数据与操作封装起来
群体
函数模板
template <class 类型参数1, class类型参数2, ...>
返回值类型 模板名(形参表)
{
函数体
}
代码中的class也可换成typename
template<参数表>class标识符,指明可以接受一个类模板名作为参数
实例化
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;
}
编译器由模板自动生成函数的过程叫模板的实例化。由模板实例化而得到的函数称为模板函数。在某些编译器中,模板只有在被实例化时,编译器才会检查其语法正确性。如果程序中写了一个模板却没有用到,那么编译器不会报告这个模板中的语法错误
编译器对模板进行实例化时,并非只能通过模板调用语句的实参来实例化模板中的类型参数,模板调用语句可以明确指明要把类型参数实例化为哪种类型。可以用:
模板名<实际类型参数1, 实际类型参数2, ...>
#include <iostream>
using namespace std;
template <class T>
T Inc(T n)
{
return 1 + n;
}
int main()
{
cout << Inc<double>(4) / 2; //好好看看这里是怎么用的
return 0;
}
此时,Inc返回的是2.5,不因为4是int而改变(因为指明了double),函数模板是不允许隐式类型转换的,调用时类型必须严格匹配
可以有不只一个类型参数
template <class Tl, class T2>
T2 print(T1 argl, T2 arg2)
{
cout << arg1 << " " << arg2 << endl;
return arg2;
}
注意
类模板
使用类模板使用户可以为类定义一种模式,使得类中的某些数据成员,某些成员函数的参数,返回值或局部变量能取任意类型
类是对一组对象的公共性质的抽象,而类模板则是对不同类的公共性质的抽象,因此类模板是属于更高层次的抽象。由于类模板需要一种或多种类型参数,所以类模板也常常称为参数化类
代码
template <typename T>
class Complex{
public:
//构造函数
Complex(T a, T b)
{
this->a = a;
this->b = b;
}
//运算符重载
Complex<T> operator+(Complex &c) \\Complex<T>是具化了返回类型
{
Complex<T> tmp(this->a+c.a, this->b+c.b);
return tmp;
}
private:
T a;
T b;
}
int main()
{
//对象的定义,必须声明模板类型,因为要分配内容
Complex<int> a(10,20);
Complex<int> b(20,30);
Complex<int> c = a + b;
return 0;
}
template <typename T>
class Blob
{
public:
Blob();
Blob(std::initializer_list<T> i);
T func(T const &str);//在类内声明
};
//类外定义
template <typename T>
T Blob<T>::func(T const &str) \\这里可以好好看看
{
}
建立对象:
有点像vector<类型名>
类模板还有很多知识,但是书上没有了,考试应该也不会涉及
线性群体
线性群体中的元素按位置排列有序
直接访问群体——数组类
#ifndef ARRAY_H
#define ARRAY_H
#include <cassert>
template<class T>
class Array {
private:
T* list; //T类型指针,用于存放动态分配的数组内存首地址
int size; //数组大小(元素个数)
public:
Array(int sz = 50); //构造函数
Array(const Array<T>& a); //复制构造函数
~Array(); //析构函数
Array<T>& operator=(const Array<T>& rhs); //重载"="使数组对象可以整体赋值
T& operator[](int i); //重载"[]",使Array对象可以起到C++普通数组的作用
const T& operator [] (int i) const; //"[]"运算符针对const的重载
operator T* (); //重载到T*类型的转换,使Array对象可以起到C++普通数组的作用
operator const T* () const; //到T*类型转换操作符针对const的重载
int getSize() const; //取数组的大小
void resize(int sz); //修改数组的大小
};
//构造函数
template<class T>
Array<T>::Array(int sz) {
assert(sz >= 0);
size = sz;
list = new T[size];
}
//析构函数
template<class T>
Array<T>::~Array() {
delete[] list;
}
//复制构造函数
template<class T>
Array<T>::Array(const Array<T>& a) {
size = a.size;
list = new T[size];
for (int i = 0; i < size; i++)
list[i] = a.list[i];
}
//重载"="运算符,将对象rhs赋值给本对象,实现对象之间的整体赋值
template<class T>
Array<T>& Array<T>::operator=(const Array<T>& rhs) {
if (&rhs != this) {
if (size != rhs.size) {
delete[] list;
size = rhs.size;
list = new T[size];
}
for (int i = 0; i < size; i++)
list[i] = rhs.list[i];
}
return *this; //因为返回的是引用类型,所以这里加了*,返回的引用会自动绑定
}
//重载下标运算符,实现与普通数组一样通过下标访问元素,并且具有越界检查功能
template<class T>
T& Array<T>::operator[] (int n) {
assert(n >= 0 && n < size);
return list[n];
}
template<class T>
const T& Array<T>::operator[] (int n) const {
assert(n >= 0 && n < size);
return list[n];
}
//重载指针转换运算符,将Array类的对象名转换为T类型的指针
template<class T>
Array<T>::operator T* () { //因为返回的是地址,所以没有返回类型
return list;
}
template<class T>
Array<T>::operator const T* () const {
return list;
}
//取当前数组的大小
template<class T>
int Array<T>::getSize() const {
return size;
}
//将数组大小修改为sz
template<class T>
void Array<T>::resize(int sz) {
assert(sz >= 0);
if (sz == size)
return;
T* newList = new T[sz];
int n = (sz < size) ? sz : size;
for (int i = 0; i < n; i++)
newList[i] = list[i];
delete[] list;
list = newList;
size = sz; //这里不用delete[] newList,因为这一段内存空间已经给予了list了
}
#endif
注意看一下 =,T* 的运算符重载,返回类型是引用(原因后面有说)
浅复制与深复制
如果类模板中涉及到地址(或是如果类中成员函数有指针)则需要设计复制构造函数实现深复制(浅复制在之前已经说过——共用一段内存空间,在执行析构函数时会将两个对象都删掉)
特殊的运算符重载
返回值类型为对象的引用
简而言之——当这个运算符在我们一般写法是放在左边的,那么他的重载函数返回类型就得是引用(指针符例外)
指针转换运算符的作用
在上面的那段长代码中表现的就是operator T*