1、群体分为:线性群体(元素按位置排列有序)和非线形群体(不用位置顺序来标识元素)
2、C++最重要的特性之一是代码重用,为了实现代码重用,代码必须具有通用性。代码可以自动适应数据类型的变化的程序设计类型称为参数化程序设计。
参数化多态性:将程序所处理的对象的类型参数化,使得一段程序可以用于处理多种不同类型的对象。
3、函数模版的定义形式是:
template<模版参数表>
类型名 函数名(参数表)
{
函数体的定义
}
所有函数模版的定义都是用关键字template开始的。模板参数表由用逗号分隔的模板参数构成,可以包括以下内容:
(1)class(或typename)标识符,指明可以接受一个类型参数。这些类型参数代表的是类型,可以是与定义类型或自定义类型。
(2)类型说明符 标识符,指明可以接受一个由“类型说明符”所规定类型的常量作为参数;
(3)template<参数表>class 标识符,指明可以接收一个类模版名作为参数。
函数模版中函数体的定义方式与定义普通函数类似。下面是求绝对值的函数模版及其应用:
#include <iostream>
using namespace std;
template<typename T>
T abs(T x){
return x<0?-x:x;
}
int main(){
int n=-5;
double d=-5.5;
cout<<abs(n)<<endl;
cout<<abs(d)<<endl;
return 0;
}
上述程序中,由于实参n为int类型,所以推导出模版中类型参数T为int.
当类型参数含义的含义确定后,编译器将以函数模板为样板,生成一个函数,这一过程称为函数模板的实例化,该函数称为函数模板abs的一个实例。
4、函数模版的实例
#include <iostream>
using namespace std;
template <class T> //定义函数模版
void outputArray(const T *arrat,int count){ //实参类型如果是类,则必须为该类 重载过“<<"运算符
for(int I=0;i<count;i++)
cout<<array[i]<<" ";
cout<<endl;
}
int main(){
...
outputArray(a,A_COUNT); //调用函数模版
...
return 0;
}
虽然函数模版的使用形式与函数类似,但二者有本质的区别,这主要表现在以下3方面:
(1)函数模版本身在编译时不会生成任何目标代码,只有由模版生成的实例会生成目标代码。
(2)被多个源文件引用的函数模版,应当连通函数体一同放在头文件中,而不能像普通函数那样只将声明放在头文件中,而不能像普通函数那样只将声明放在头文件中。
(3)函数指针也只能指向模版的实例,而不能指向模版本身。
5、类模版(使用类模版使用户可以为类定义一种模式,使得类中的某些数据成员、某些成员函数的参数、返回值或局部变量能取不同类型)
类是对一组对象的公共性质的抽象,而类模版则是对不同类的公共性质的抽象。类模版是属于更高层次的抽象。
类模版声明的语法形式是:
template <模版参数表>
class 类名
{
类成员声明
}
如果需要在类模版以外定义其成员函数,则要采用以下形式:
template <模版参数表>
类型名 类名<模版参数标识符列表>::函数名(参数表)
使用一个模版类来建立对象时,应按照如下形式声明:
模版名<模版参数表> 对象名1,...,对象名 n;
模版参数表中的类型参数同样也可以声明为该模版类的友元类,声明方式如下:
template <typename T>
class DemoClass{
friend T; //将参数类型T声明为Democlass的友元类
...
}
6、typedef和using来为类型设定别名的主要区别是:当需要为模版而非模版实例化后的类定义别名时,只能通过C++11标准下的using关键字来实现,具体方式距离为:
template <typename T>using AliasDemo=DemoClass<T>;
7、类模版应用举例
#include <iostream>
#include <cstdlib>
using namespace std;
struct Student{
int id;
float gpa;
};
template <class T> //类模版
class Store{
private:
T item; //item用于存放任意类型的数据
bool haveValue; //haveValue标记item是否已被存入内容
public:
Store(); //缺省形式(无形参)的构造函数
T &getElem(); //提取数据函数
void putElem(const T &x);//存入数据函数
};
...
8、线形群体
元素次序与其位置关系是对应的。对可直接访问的线形群体,可以直接访问群体中的任何一个元素。对顺序访问的线形群体,只能按照元素的排列顺序从头开始依次访问各个元素。
还有两种特殊的线形群体--栈和队列
一般而言,当对象需要通过显式定义的复制构造函数执行深层复制时,也需要重载赋值运算符,执行类似的深层复制操作。
语法规定“=”“[]”“()”“->”只能被重载为成员函数,而且派生类中的“=”运算符总会隐藏基类运算符函数。
链表是一种动态数据结构,可以用来表示顺序访问的线形群体。链表是由一系列结点组成的,结点可以在运行时动态生成,每个结点包括数据域和指向链表中下一个结点的指针。结点类的数据成员包括数据域和指针域的内容,函数成员中应该含有对数据成员和指针进行初始化的方法(函数)。
9、结点类模版
//Node.h
#ifndef NODE_H
#define NODE_H
//类模版的定义
template <class T>
class Node{
private:
Node<T>*next; //指向后继结点的指针
public:
T data; //数据域
Node(const T &data,Node<T> *next=0); //构造函数
void insertAfter(Node<T> *p); //在本结点之后插入一个同类结点p
Node <T> *deleteAfter(); //删除本结点的后稷结点,并返回其地址
Node <T> *nextNode(); //获取后继结点的地址
const Node <T> *nextNode() const; //获取后继结点的地址
};
//类的实现部分
//构造函数,初始化数据和指针成员
template <class T>
Node <T>::Node(const T& date,Node<T> *next/*=0:data(data),next(next){}
//返回后继结点的指针
template <class T>
Node<T> *Node<T>::nextNode(){
return next;
}
//返回后继结点的指针
template<class T>
const Node<T> *Node<T>::nextNode() const{
return next;
}
//在当前结点之后插入一个结点p
template <class T>
void Node<T>::insertAfter(Node<T> *p){
p->next=next; //p结点指针域指向当前结点的后继指针
next=p; //当前结点的指针域指向p
}
//删除当前结点的后继结点,并返回其地址
template <class T>
Node <T>* Node<T>::deleteAfter(){
Node<T> *template=next; //将欲删除的结点地址存储到tempPtr中
if(next==0) //如果当前结点没有后继结点,则返回空指针
return 0;
next=tempPtr->next; //使当前结点的指针域指向tempPtr的后继指针
return tempPtr; //返回被删除的结点的指针
}
#endif //NODE_H
10、链表类的数据成员:链表类表示的是一个顺序访问的线形群体,由一组用指针域串联的Node模版对象构成,对链表的任何操作都要由头结点开始,因此对于所有应用链表的程序来说,链表的头指针是必然要使用的,尾指针对许多应用来说也是有用的信息。
由于链表是动态数据结构,其结点个数是动态变化的,因此需要封装在类中进行实时维护。
在链表类的数据成员中,需要保存表头指针、表尾指针、元素个数、当前的遍历位置等信息。
链表的基本操作应该包括:生成新结点、插入结点、删除结点、访问/修改结点数据、遍历链表等。
因此在链表中应该包括完成上述操作的成员函数,以及为了实现这些函数而添加的一些辅助函数,为了方便链表类对象间的赋值,还应重载“=”运算符,另外由于面向对象的封装特性,还要提供一些接口函数。
11、链表类模版声明
#ifndef LINKEDLIST_H
#define LINKEDLIST_H
#include "Node.h"
template <class T>
class LinkedList{
private:
//数据成员
Node<T>*front,*rear,*rear; //表头和表尾指针
Node<T>*precPtr,*currPtr; //记录表当前遍历位置的指针,由插入和删除操作更新
int size; //表中的元素个数
int position; //当前元素在表中的位置序号,由函数reset使用
//函数成员
//生成新结点,数据域为item,指针域为ptrNext
Node<T>*newNode(const T &item,Node<T> *ptrNext=NULL);
//释放结点
void freeNode(Node<T>*p);
//将链表L复制到当前表(假设当前表为空)
//被复制构造函数、operator=调用
void copy(const LinkedList<T>& L);
public:
LinkedList():
LinkedList(const LinkedList<T>&L);
~LinkedList();
LinkedList<T>&operator=(const LinkedList<T> &L; //重载赋值运算符
int getSize() const;
bool isEmpty() const;
void reset(int pos=0); //初始化游标的位置
void next(); //使游标移动到下一个结点
bool endOfList() const; //游标是否到了链尾
int currentPosition() const; //返回游标当前位置
void insertFront(const T &item); //在表头插入结点
void insertRear(const T &item); //在表尾添加结点
void insertAt(const T &item); //在当前结点之前插入结点
void insertAfter(const T &item); //在当前结点之后插入结点
T deleteFront(); //删除头结点
void deleteCurrent(); //删除当前结点
T& data(); //返回对当前结点成员数据的引用
const T& data() const; //返回对当前结点成员数据的常引用
//清空列表:释放所有结点的内存空间。被析构函数、operator=调用
void clear();
};
#endif
12、栈的基本状态有:一般状态、栈空、栈满。
栈类的基本操作:初始化、入栈、出栈、栈清空、访问栈顶元素、检测栈的状态。