群体类和群体数据
基本数据类型是 c++ 编译系统预定义的,而自定义类型的数据是由多个基本类型或自定义类型的元素组成,称之为群体数据。
对于群体数据,仅有系统预定义的操作是不够的,在很多情况下,还需要设计与某些具体问题相关的特殊操作,并按照面向对象的方法将数据与操作封装起来,这就是群体类。
群体可分为两种:线性群体和非线性群体。线性群体中的元素按位置排到有序, 如数组。非线性群体不用位置顺序来标识元素, 如拓扑图.
函数模板和类模板
模板是 C++ 支持参数化程序设计的工具,通过它可以实现参数化多态性。所谓参数化多态性,就是将程序所处理的对象的类型参数化,使得一段程序可以用于处理多种不同类型的对象。
类模板的多文件结构中必须声明与实现都在头文件中. 因为如果分文件编写, 在编译的时候类模板没有实例化就不会产生目标代码. 当其他源文件引用该模板头文件时, 虽然能实例化各个类模板的声明, 但无法将函数的实现实例化, 因为编译器对每个源文件的编译时分别进行的. 编译时无法相互访问. 得不到模板的具体实现, 连接时就会发生错误.
如果将函数模板、类模板的成员函数和类模板的静态数据成员的定义都放在头文件中,就可以避免这一问题,因为这些定义在编译任何一个源文件时都是可见的,编译器可以按需对它们进行实例化。
由于函数模板和类模板的成员函数一般放在头文件中,这与内联函数相似,因此容易被人误以为函数模板和类模板的成员函数都会被自动当作内联函数处理。其实函数模板和类模板的成员函数既可以是内联函数,也可以是非内联函数,将其声明为内联函数的方法与非模板的函数一样,都要使用 inline 关键字,或将成员函数写在类定义内。
函数模板
(1)函数模板本身在编译时不会生成任何目标代码,只有由模板生成的实例会生成目标代码。
(2) 被多个源文件引用的函数模板,应当连同函数体一同放在头文件中,而不能像普通函数那样只将声明放在头文件中。
(3) 函数指针也只能指向模板的实例,而不能指向模板本身。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| #include <iostream> using namespace std;
template<typename T> void showT(T t) { cout << typeid(t).name() << endl; }
int main() { showT<int>(3); showT<float>(4.0); showT<char>('A'); showT<>("test"); return 0; }
|

类模板
使用类模板使用户可以为类定义一种模式,使得类中的某些数据成员、某些成员函数的参数、返回值或局部变量能取任意类型(包括系统预定义的和用户自定义的)。
类是对一组对象的公共性质的抽象,而类模板则是对不同类的公共性质的抽象,因此类模板是属于更高层次的抽象。由于类模板需要一种或多种类型参数,所以类模板也常常称为参数化类。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| #include <iostream> using namespace std;
class Intel_CPU {}; class AMD_CPU {}; class NVIDIA_GPU {}; class AMD_GPU {};
template <class C, class G> class Computer { public: Computer() {} Computer(C c, G g) : c(c), g(g) {} void getInfo(); private: C c; G g; };
template <class C, class G> void Computer<C, G>::getInfo() { cout << typeid(c).name() << " " << typeid(g).name() << endl; }
int main() { Intel_CPU IC; AMD_CPU AC; Computer<Intel_CPU, AMD_GPU> c_1(IC, AMD_GPU()); Computer<AMD_CPU, NVIDIA_GPU> c_2(AC, NVIDIA_GPU()); c_1.getInfo(); c_2.getInfo(); return 0; }
|

线性群体
线性群体中的元素次序与其位置关系是对应的。在线性群体中,又可按照访问元素的不同方法分为直接访问、顺序访问和索引访问。
两种特殊的线性群体 栈和队列。
直接访问群体一一数组类
实现一个简单的动态数组类模板 Array 。它由任意多个位置连续的、类型相同的元素组成,其元素个数可在程序运行时改变。它虽然比 vector 简单,但与 vector 的工作原理没有本质差别。
C++ 中,如果想将自定义类型T的对象隐含或显式地转换为S类型,可以将operator S定义为T的成员函数。这样,在把T类型对象显式隐含转换为S类型,或用static_cast 显式转换为S类型时,该成员函数会被调用。转换操作符的重载函数不用指定返回值的类型,这是由于这种情况下重载函数的返回类型与操作符名称一致,因此c++ 标准规定不能为这类函数指定返回值类型(也不要写 void)
而当对象本身为常数时,为了避免通过指针对数组内容进行修改,只能将对象转换为常指针。
实现动态数组类模板
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115
| #ifndef ARRAY_H #define ARRAY_H #include <cassert>
template<class T> class Array { private: T* list; int size; public: Array(int sz = 50); Array(const Array<T>& arr); ~Array(); Array<T>& operator= (const Array<T>& rhs); T& operator[] (int i); const T& operator[] (int i) const; operator T* (); operator 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]; } }
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]; }
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; }
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 < size; i++) { newList[i] = list[i]; } delete[] list; list = newList; size = sz; }
# endif
|
测试
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| #include <iostream> using namespace std; #include "Array.h"
int main() { Array<float> Arr_f(20); for (int i = 0; i < Arr_f.getSize(); i++) { Arr_f[i] = (float)i; } for (int i = 0; i < Arr_f.getSize(); i++) { cout << fixed << Arr_f[i] << " "; } cout << endl; Arr_f.resize(100); cout << Arr_f.getSize() << endl; cout << Arr_f[20] << endl;
const float* f = (const float*)Arr_f; cout << f[0] << endl;
float* ff = const_cast<float*>(f); cout << ff[0] << endl; return 0; }
|
顺序访问群体一一链表类
链表是一种动态数据结构,可以用来表示顺序访问的线性群体。
实现链表类模板
节点类模板
结点类的数据成员中应该包括数据域和指针域的内容,函数成员中应该含有对数据和指针进行初始化的方法(函数) ,以及在本结点之后插入新结点和删除后继结点的方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225
| #pragma once #include "Node.h"
template<class T> class LinkedList { private: Node<T>* front, * rear; Node<T>* prevPtr, * currPtr; int size; int position;
Node<T>* newNode(const T& item, Node<T>* ptrNext = NULL); void freeNode(Node<T>* p); 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 currentPostion(void) 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; void clear(); };
template<class T> Node<T>* LinkedList<T>::newNode(const T& item, Node<T>* ptrNext ) { Node<T>* p_node = new Node<T>(item, ptrNext); size++; return p_node; }
template<class T> void LinkedList<T>::freeNode(Node<T>* p) { delete p; size--; }
template<class T> void LinkedList<T>::copy(const LinkedList& L) { if (isEmpty(L)) return; LinkedList newList; L.reset(1); for (int i = 1; i < L.getSize(); i++) { insertAfter(L.data()); L.next(); } }
template<class T> LinkedList<T>::LinkedList() { size = 0; position = 0; Node<T>* p_node = newNode(0, NULL); front = rear = prevPtr = currPtr = p_node; }
template<class T> LinkedList<T>::LinkedList(const LinkedList<T>& L) { copy(L); }
template<class T> LinkedList<T>::~LinkedList() { clear(); }
template<class T> LinkedList<T>& LinkedList<T>::operator=(const LinkedList<T>& L) { copy(L); clear(L); }
template<class T> int LinkedList<T>::getSize() const { return size ; }
template<class T> bool LinkedList<T>::isEmpty() const { if (front == rear) return true; return false; }
template<class T> void LinkedList<T>::reset(int pos ) { if (pos == 0) return; currPtr = prevPtr = front; position = pos; for (int i = 0; i < position; i++) { next(); } }
template<class T> void LinkedList<T>::next() { prevPtr = currPtr; currPtr = currPtr->nextNode(); position++; }
template<class T> bool LinkedList<T>::endOfList() const { if (position == size) return true; return false; }
template<class T> int LinkedList<T>::currentPostion() const { return position; }
template<class T> void LinkedList<T>::insertFront(const T& item) { Node* p_node = newNode(item, front); front = p_node;
}
template<class T> void LinkedList<T>::insertRear(const T& item){ Node* p_node = newNode(item, NULL); rear->insertAfter(p_node); rear = p_node; }
template<class T> void LinkedList<T>::insertAt(const T& item) { Node<T>* p_node = newNode(item, currPtr); prevPtr->insertAfter(p_node); currPtr = p_node; position--; }
template<class T> void LinkedList<T>::insertAfter(const T& item) { Node<T>* p_node = newNode(item, currPtr->nextNode()); currPtr->insertAfter(p_node); currPtr = p_node; position++; }
template<class T> T LinkedList<T>::deleteFront() { Node<T>* temp = front; T data = front->data; front = front->nextNode(); freeNode(temp); return data; }
template<class T> void LinkedList<T>::deleteCurrent() { prevPtr->insertAfter(currPtr->nextNode()); Node<T>* temp_curr = currPtr; currPtr = currPtr->nextNode(); freeNode(temp_curr); }
template<class T> T& LinkedList<T>::data(){ return currPtr->data; }
template<class T> const T& LinkedList<T>::data() const { return currPtr->data; }
template<class T> void LinkedList<T>::clear() { while (front != rear) { Node<T>* temp = front->nextNode(); freeNode(front); front = temp; } freeNode(front); }
|
测试
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| #include "LinkedList.h" #include <iostream> using namespace std;
int main() { LinkedList<char> L; L.insertAfter('G'); L.insertAfter('O'); L.insertAfter('O'); L.insertAfter('D'); L.reset(1); for (int i = 0; i < 4; i++) { cout << L.data(); L.next(); } cout << endl; L.insertAt('4'); L.insertAt('3'); L.insertAt('2'); L.insertAt('1'); L.reset(1); for (int i = 0; i < 8; i++) { cout << L.data(); L.next(); } return 0; }
|

感觉实现有点问题, position 还没用. 书上给的是游标来控制插入删除取数的位置, 既有头插也有尾插, 用双向链表的节点好实现些.
栈类
栈的基本状态有:一般状态、栈空、栈满。当栈中没有元素时称为栈空;当栈中元素个数达到上限时,称为栈满;栈中有元素、但未达到栈满状态时,即处于一般状态。如果用静态数组存储存元素,则元素个数达到数组声明的元素个数时即为栈满。如果使用动态数组或链表,则可以根据需要设置或不设置元素的最大个数。
无论采用哪种数据结构,栈类中都应该包括下列基本操作:初始化、入栈、出栈、栈清空、访问栈顶元素、检测栈的状态(满或空)。
实现栈类模板
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60
| #pragma once #include <cassert>
template<class T, int SIZE = 50> class Stack { private: T list[SIZE]; int top; public: Stack(); void push(const T& item); T pop(); void clear(); const T& peek() const; bool isEmpty() const; bool isFull() const; };
template<class T, int SIZE> Stack<T, SIZE>::Stack() : top(-1) {}
template<class T, int SIZE> void Stack<T, SIZE>::push(const T& item) { assert(!isFull()); list[++top] = item; }
template<class T, int SIZE> T Stack<T, SIZE>::pop() { assert(!isEmpty()); return list[top--]; }
template<class T, int SIZE> const T& Stack<T, SIZE>::peek() const { assert(!isEmpty()); return list[top]; }
template<class T, int SIZE> bool Stack<T, SIZE>::isEmpty() const { return top == -1; }
template<class T, int SIZE> bool Stack<T, SIZE>::isFull() const { return top == SIZE - 1; }
template<class T, int SIZE> void Stack<T, SIZE>::clear() { top = -1; }
|
测试
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| #include <iostream> using namespace std; #include "Stack.h"
int main() { Stack<char, 50> s; s.push('L'); s.push('I'); s.push('V'); s.push('E'); while (!s.isEmpty()) { cout << s.peek(); s.pop(); } return 0; }
|
队列类
队列也有三种基本状态:一般状态、队空、队满。当队中没有元素时称为队空;当队中元素个数达到上限时,称为队满;队中有元素,但未达到队满状态时,即处于一般状态。
如果用静态数组存储队列元素,则元素个数达到数组声明的元素个数时即为队满。如果使用动态数组或链表,则可以根据需要设置或不设置元素的最大个数。
无论采用哪种数据结构,队列类的数据成员都应包括:队列元素、队头指针和队尾指针。队列类中函数成员应该能够实现下列基本操作:初始化、入队、出队、清空队列、访问队首元素、检测队列的状态(满、空、队列长度)。
如果用静态数组存储队列元素,每当有元素出队时,队中元素都要向队头方向移动。因此数据移动量大,效率不高。
可以将队列设计为循环队列。也就是在想象中将数组弯曲成环形,元素出队时,后继元素不移动,每当队尾达到数组最后一个元素时,便再回到数组开头。为了使队尾达到数组最后一个元素时,再回到数组开头,需要运用取余运算(%)。另外,为了判断队列的状态,需要对元素个数进行记录。当元素个数为0时,队空。当元素个数等于最大值时,队满。
实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81
| #ifndef QUEUE_H #define QUEUE_H #include <cassert>
template<class T, int SIZE = 50> class Queue { private: int front, rear, count; T list[SIZE]; public: Queue(); void insert(const T& item); T remove(); void clear(); const T& getFront() const;
int getLength() const; bool isEmpty() const; bool isFull() const;
};
template<class T, int SIZE> Queue<T, SIZE>::Queue() : front(0), rear(0), count(0) {}
template<class T, int SIZE> void Queue<T, SIZE>::insert(const T& item) { assert(count != SIZE); count++; list[rear] = item; rear = (rear + 1) % SIZE; }
template<class T, int SIZE> T Queue<T, SIZE>::remove() { assert(count != 0); count--; int temp = front; front = (front + 1) % SIZE; return list[temp]; }
template<class T, int SIZE> const T& Queue<T, SIZE>::getFront() const { return list[front]; }
template<class T, int SIZE> int Queue<T, SIZE>::getLength() const { return count; }
template<class T, int SIZE> bool Queue<T, SIZE>::isEmpty() const { return count == 0; }
template<class T, int SIZE> bool Queue<T, SIZE>::isFull() const { return count == SIZE; }
template<class T, int SIZE> void Queue<T, SIZE>::clear() { count = front = rear = 0; }
#endif
|
测试
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| #include<iostream> using namespace std; #include "Queue.h"
int main() { Queue<int, 10> q; for (int i = 0; !q.isFull() ; i++) { q.insert(i); } for (int i = 0; !q.isEmpty(); i++) { cout << q.getFront(); q.remove(); } return 0; }
|
参考
- 《C++语言程序设计(第4版)》 IBSN 9787302227984
- C++参考手册
- Microsoft C++文档