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


