此专栏为移动机器人知识体系下的编程语言中的 C {\rm C} C++从入门到深入的专栏,参考书籍:《深入浅出 C {\rm C} C++》(马晓锐)和《从 C {\rm C} C到 C {\rm C} C++精通面向对象编程》(曾凡锋等)。
10.模板与命名空间
10.1 模板简述
-
模板使函数和类的处理对象参数化,使代码具有通用性;
-
C {\rm C} C++程序的组成单位是函数和类,因此,模板分为函数模板 ( f u n c t i o n t e m p l a t e ) ({\rm function\ template}) (function template)和类模板 ( c l a s s t e m p l a t e ) ({\rm class\ template}) (class template);定义模板后,可以处理不同的数据类型,不必显式定义针对不同数据类型的函数或类;
-
模板、函数模板、类模板与对象间的关系如下:
-
模板可以最大限度地实现代码重用,使代码精简;
10.2 函数模板
-
函数模板是一类可以被实例化的特殊函数,通过模板可以操作通用类型的数据,函数模板处理的数据类型是通过参数来体现,在函数模板实例化的过程中,才将这些参数具体化为一种特定的数据类型,因此,在定义函数时不用为每种数据类型都编写重复的相似代码;模板中表示数据类型的参数称为模板参数,这是一种特殊的参数,能传递一种数据类型;
-
声明函数模板参数类型的语法格式:
// 声明格式1: template <class 类型标识符> 返回类型 函数名(函数形参表);// 声明格式2: template <typename 类型标识符> 返回类型 函数名(函数形参表);
- t e m p l a t e {\rm template} template是声明模板的关键字,表示声明一个模板;
- t e m p l a t e {\rm template} template关键字后是用尖括号’<>'括起来的类型参数表,类型参数表中包含一个或多个由逗号分隔的类型参数项,每一项由关键字 c l a s s {\rm class} class和用户命名的标识符组成,此标识符为类型参数,不是一种数据类型,可以同一般数据类型一样使用在函数的任何地方;
-
调用函数模板的语法格式:
函数名<具体类型>(参数表);
-
在调用函数模板时,<具体类型>可以省略,由系统自动判定;当<具体类型>不省略时,为显式实例化,当<具体类型>省略时,为隐式实例化;
-
函数模板接收参数类型问题:
// 定义一个返回两个对象中较大对象的函数模板; // 下面的函数模板只能接收相同参数,不能接收两个不同的参数; // 因为只包含了一种类型的模板参数typename TheType; template <typename TheType> TheType GetMax(TheType a,TheType b) {return (a>b?a:b); }
// 下面的函数模板可以接收两个不同类型的参数; template <typename TheType1,typename TheType2> TheType1 GetMax(TheType1 a,TheType2 b) {return (a>b?a:b); }
-
函数模板实例:定义一个操作数组的函数模板,完成遍历数组输出元素的功能 ( e x a m p l e 10 _ 1. c p p ) ({\rm example10\_1.cpp}) (example10_1.cpp):
/*** 作者:罗思维* 时间:2024/03/24* 描述:定义一个操作数组的函数模板,完成遍历数组输出元素的功能*/ #include <iostream> #include <string>using namespace std;// 定义函数模板; template <class T> void printArray(const T *array, const int count) {for (int i = 0; i < count; i++) {cout << array[i] << " ";}cout << endl; }int main() {int nArray[10] = {10, 20, 30, 40, 50, 60, 70, 80, 90, 100};char cArray[] = {'W', 'E', 'L', 'C', 'O', 'M', 'E'};// 调用函数模板输出整型数组和字符数组中元素;printArray(nArray, sizeof(nArray) / sizeof(int));printArray(cArray, sizeof(cArray) / sizeof(char));return 0; }
-
模板函数可以像普通函数一样被重载,实例如下 ( e x a m p l e 10 _ 2. c p p ) ({\rm example10\_2.cpp}) (example10_2.cpp):
/*** 作者:罗思维* 时间:2024/03/24* 描述:模板函数重载。*/ #include <iostream> #include <string.h>using namespace std;// 定义函数模板; template <typename TheType> TheType GetMax(TheType a, TheType b) {return (a > b ? a : b); }// 重载函数模板,使函数模板支持字符串的处理; char* GetMax(char* a, char* b) {return (strcmp(a, b) > 0 ? a : b); }int main() {int nNumber1 = 10, nNumber2 = 20;float fNumber1 = 13.14, fNumber2 = 5.20;char a = 'L', b = 'C';char *p1 = (char*)"C++";char *p2 = (char*)"Python";cout << "GetMax(10,20):" << GetMax(nNumber1, nNumber2) << endl;cout << "GetMax(13.14,5.20):" << GetMax(fNumber1, fNumber2) << endl;cout << "GetMax('L','C'):" << GetMax(a, b) << endl;cout << "GetMax('C++','Python'):" << GetMax(p1, p2) << endl;return 0; }
10.3 类模板
-
类模板的作用:将类所处理的对象类型参数化,它使得类中的某些数据成员的参数和返回值能取任意数据类型;
-
类模板定义的语法格式:
template <类型参数表> class 类名 {// 类体 };
- t e m p l a t e {\rm template} template:声明模板的关键字,表示声明一个模板类;
- <类型参数表>中包含一个或多个类型参数项,每一项由关键字 c l a s s {\rm class} class和一个用户自定义的标识符组成,标识符为类型参数;
- 使用类模板时,先将其实例化,即用实际的数据类型代替类型参数;
- 当类模板中的成员函数在类定义体外定义时,必须被定义为一个函数模板的形式;
-
类模板实战项目 ( c l a s s T e m p l a t e ) ({\rm classTemplate}) (classTemplate):
-
项目需求:定义一个简单通用数组类模板,实现对一般数据类型数组的操作;
-
C A r r a y {\rm CArray} CArray类定义头文件 ( C A r r a y . h ) ({\rm CArray.h}) (CArray.h):
/*** 作者:罗思维* 时间:2024/03/25* 描述:CArray类定义头文件。 */ #pragma once #include <iostream> #include <iomanip> #include <string.h>using namespace std;const int MIN_SIZE = 30;// 定义模板类 template <class T> class CArray { // 数组类; protected:T* m_pArray; // 数组指针; int m_nSize; // 数组元素个数; public:CArray(int nSize, T Initial); // 构造函数,初始化数组; ~CArray() { // 析构函数,释放内存; delete[] m_pArray;};T& operator[] (int nIndex) { // 重载数组下标运算符; return m_pArray[nIndex];};void Show(const int nNumElems); // 输出前nNumElems个元素; void Sort(int nNumElems); // 将前nNumElems个元素进行排序; };template <class T> CArray<T>::CArray(int nSize, T InitVal) {m_nSize = (nSize > 1) ? nSize : 1; // 保证nSize不小于1; m_pArray = new T[m_nSize];for (int i = 0; i < m_nSize; i++) {m_pArray[i] = InitVal; // 将元素全部初始化为InitVal; } }template <class T> void CArray<T>::Show(const int nNumElems) {for (int i = 0; i < nNumElems; i++) {cout << m_pArray[i] << ' ';} }template <class T> void CArray<T>::Sort(int nNumElems) { // 对元素进行排序; int nOffset = nNumElems;bool bSorted;if (nNumElems < 2) {return;}do {nOffset = (nOffset * 8) / 11;nOffset = (nOffset < 1) ? 1 : nOffset;bSorted = true;for (int i = 0, j = nOffset; i < (nNumElems - nOffset); i++, j++) {if (m_pArray[i] > m_pArray[j]) {T nSwap = m_pArray[i];m_pArray[i] = m_pArray[j];m_pArray[j] = nSwap;bSorted = false;}}} while (!bSorted || nOffset != 1); }
-
C M y S t r i n g {\rm CMyString} CMyString类定义头文件 ( C M y S t r i n g . h ) ({\rm CMyString.h}) (CMyString.h):
/*** 作者:罗思维* 时间:2024/03/25* 描述:CMyString类定义头文件。 */ #pragma once #include "CArray.h"// 定义字符串类; class CMyString {protected:char* m_pszString; // 字符串指针; int m_nSize; // 字符串中的字符个数; public:CMyString(int nSize = MIN_SIZE) {m_pszString = new char[m_nSize = nSize];};CMyString(const CMyString& CString);CMyString(const char* pszString);CMyString(const char cChar);~CMyString() {delete[] m_pszString;};int getLen() {return strlen(m_pszString);};int getMaxLen() {return m_nSize;};// 重载运算符= CMyString& operator=(const CMyString& aString);CMyString& operator=(const char* pszString);CMyString& operator=(const char cChar);// 重载运算符> friend operator > (CMyString& aString1, CMyString& aString2) {return (strcmp(aString1.m_pszString, aString2.m_pszString) > 0) ? 1 : 0;}friend ostream& operator << (ostream& os, CMyString& aString); };
-
C M y S t r i n g {\rm CMyString} CMyString类实现文件 ( C M y S t r i n g . c p p ) ({\rm CMyString.cpp}) (CMyString.cpp):
/*** 作者:罗思维* 时间:2024/03/25* 描述:CArray类实现文件。 */ #include "CMyString.h"CMyString::CMyString(const CMyString &aString) {m_pszString = new char[m_nSize = aString.m_nSize];strcpy(m_pszString, aString.m_pszString); }CMyString::CMyString(const char *pszString) {m_pszString = new char[m_nSize = strlen(pszString) + 1];strcpy(m_pszString, pszString); }CMyString::CMyString(const char cChar) {m_pszString = new char[m_nSize = MIN_SIZE];m_pszString[0] = cChar;m_pszString[1] = '\0'; }CMyString &CMyString::operator=(const CMyString &aString) {// 检查是否有足够的空间进行字符串的复制; if (strlen(aString.m_pszString) < unsigned(m_nSize)) {strcpy(m_pszString, aString.m_pszString);} else {strncpy(m_pszString, aString.m_pszString, m_nSize - 1);}return *this; }CMyString &CMyString::operator=(const char *pszString) {if (strlen(pszString) < unsigned(m_nSize)) {strcpy(m_pszString, pszString);} else {strncpy(m_pszString, pszString, m_nSize - 1);}return *this; }CMyString &CMyString::operator=(const char cChar) {if (m_nSize > 1) {m_pszString[0] = cChar;m_pszString[1] = '\0';}return *this; }// 输出对象重载; ostream &operator << (ostream &os, CMyString &aString) {os << aString.m_pszString;return os; }
-
程序主文件 ( m a i n . c p p ) ({\rm main.cpp}) (main.cpp):
/*** 作者:罗思维* 时间:2024/03/25* 描述:程序主文件。 */ #include <iostream> #include "CMyString.h"using namespace std;int main() {const int MAX_ELEMS = 10;int nArr[MAX_ELEMS] = {10, 20, 40, 50, 60, 90, 80, 70, 30, 100};char cArr[MAX_ELEMS] = {'C', 'W', 'r', 'Y', 'k', 'J', 'X', 'Z', 'y', 's'};CArray<int> IntegerArray(MAX_ELEMS, 0); // 用int类型实例化通用数组类模板;CArray<char> CharArray(MAX_ELEMS, ' '); // 用char类型实例化通用数组类模板;CArray<CMyString> StringArray(MAX_ELEMS, " "); // 用自定义类型CMyString实例化通用数组类模板;for (int i = 0; i < MAX_ELEMS; i++) {IntegerArray[i] = nArr[i];}for (int i = 0; i < MAX_ELEMS; i++) {CharArray[i] = cArr[i];}StringArray[0] = "GuangDong";StringArray[1] = "BeiJing";StringArray[2] = "HuBei";StringArray[3] = "GuiZhou";StringArray[4] = "GuangXi";StringArray[5] = "HuNan";StringArray[6] = "ShanDong";StringArray[7] = "ShanXi";StringArray[8] = "JiangSu";StringArray[9] = "ZheJiang";// 输出IntegerArray排序前后的内容;cout << "Unsorted array is:" << endl;IntegerArray.Show(MAX_ELEMS);IntegerArray.Sort(MAX_ELEMS);cout << "\nSorted array is:" << endl;IntegerArray.Show(MAX_ELEMS);cout << endl;// 输出CharArray排序前后的内容;cout << "Unsorted array is:" << endl;CharArray.Show(MAX_ELEMS);CharArray.Sort(MAX_ELEMS);cout << "\nSorted array is:" << endl;CharArray.Show(MAX_ELEMS);cout << endl;// 输出StringArray排序前后的内容;cout << "\nUnsorted array is:" << endl;StringArray.Show(MAX_ELEMS);StringArray.Sort(MAX_ELEMS);cout << "\nSorted array is:" << endl;StringArray.Show(MAX_ELEMS);;return 0; }
-
10.4 命名空间
-
命名空间是 A N S I C {\rm ANSI\ C} ANSI C++引入的可以由用户命名的作用域,用来处理程序中常见的同名冲突;
-
C {\rm C} C++中的作用域有文件作用域、函数作用域、复合语句作用域和类作用域等,在不同的作用域中,定义具有相同名称的变量是合法的;
-
在文件中可以定义全局变量,作用域是整个程序,在同一个作用域中不应该出现两个或多个同名的实体;
-
在大型软件开发中,一般程序分模块完成,在各模块中可能会产生同名的实体,从而产生命名冲突;
-
如果引用标准库、第三方库、自定义库中包含与程序中定义的全局实体同名的实体,或不同库之间有同名的实体,则编译时出现命名冲突,称为全局命名空间污染 ( g l o b a l n a m e s p a c e p o l l u t i o n ) ({\rm global\ namespace\ pollution}) (global namespace pollution);
-
命名空间是由开发者命名的一个作用域区域,这些区域称为空间域,开发者可以根据需要指定一些有名称的空间域,把自定义的实体放在这个空间域中,保证使其与外界分离,这样可以使空间域内部实体不会与外界产生冲突;
-
命名空间定义的语法格式:
namespace <命名空间名> {...; // 命名空间实体; }
- n a m e s p a c e {\rm namespace} namespace:定义命名空间的关键字;
- <命名空间名>:用户指定的命名空间的名称;
- 大括号内是声明块,在其中声明的实体称为命名空间成员 ( n a m e s p a c e m e m b e r ) ({\rm namespace\ member}) (namespace member),命名空间成员可以包含变量、常量、结构体、类、模板、命名空间等;
-
命名空间举例:
namespace myns {int a;char c; }
- 在程序中使用变量 a 、 c {\rm a、c} a、c,需要加上命名空间名和作用域限定符" : : :: ::",如: m y n s : : a 、 m y n s : : c {\rm myns::a、myns::c} myns::a、myns::c,此用法称为命名空间限定 ( q u a l i f i e d ) ({\rm qualified}) (qualified), m y n s : : a {\rm myns::a} myns::a称为被限定名 ( q u a l i f i e d n a m e ) ({\rm qualified\ name}) (qualified name);
-
程序开发过程中,可以根据实际情况定义多个命名空间,把不同的库中的实体放到不同的命名空间中,即用不同的命名空间把不同的实体隐藏起来;
-
对命名空间成员引用的语法格式:
命名空间::命名空间成员名
-
命名空间的几种使用方法:
-
定义命名空间后,可以为其起一个别名:
// 声明命名空间,名为:NameSpaceGraduateStudent; namespace NameSpaceGraduateStudent {...; }// 给命名空间起别名; namespace NSGS=NameSpaceGraduateStudent;
-
使用 u s i n g {\rm using} using引入命名空间中的成员, u s i n g {\rm using} using的作用是引入命名空间或命名空间中的成员,其后面必须是由命名空间限定的名称;
// 用using引入命名空间中的成员; // 引入后可以直接引用CStudent即可; using Stu::CStudent;// 等价关系; Stu::CStudent student ("Willard") 等价于 CStudent student ("Willard")
-
使用 u s i n g n a m e s p a c e {\rm using\ namespace} using namespace引入命名空间,可以一次性引入命名空间的全部成员,语法格式:
using namespace 命名空间名;
-
无名的命名空间,在其他文件中无法使用,只能在本文件的作用域有效,语法格式:
namespace {// 定义命名空间名;void func(){...;} }
-
-
标准命名空间 s t d {\rm std} std,在程序中没有引入标准命名空间时,要使用其中的成员,则使用 s t d {\rm std} std来进行限定;
-
C {\rm C} C++头文件的作用:为用户提供调用其实现的外部接口;
10.5 实战
项目需求:
约瑟夫 ( J o s e p h u s ) ({\rm Josephus}) (Josephus)问题:假设有 n n n个小孩做成一个环,从第一个小孩开始数数,如果数到第 m m m个小孩,则该小孩离开,问最后留下的小孩是第几个小孩?
问题分析:
如果总共有 6 6 6个小孩,围成一圈,从第一个小孩开始,每次数 2 2 2个小孩,则游戏情况过程:
小孩序号: 1 、 2 、 3 、 4 、 5 、 6 1、2、3、4、5、6 1、2、3、4、5、6;
离开小孩序号: 2 、 4 、 6 、 3 、 1 2、4、6、3、1 2、4、6、3、1;
则获胜小孩序号为: 5 5 5;
代码实现 ( J o s e p h u s ) ({\rm Josephus}) (Josephus):
-
J o s e p h u s R i n g . h {\rm JosephusRing.h} JosephusRing.h代码:
/*** 作者:罗思维* 时间:2024/03/26* 描述:JosephusRing类定义头文件; */ #pragma once #include <iostream> #include <iterator> // iterator:迭代器 #include <list> // list是一个容器,其结构为双向链表;using namespace std;template <class Type> class JosephusRing {list <Type> lst;public:class iterator;friend class iterator;class iterator: public std::iterator<std::bidirectional_iterator_tag, Type, ptrdiff_t> {typename list<Type>::iterator it;list<Type>* r;public:iterator(list<Type>& lst, const typename list<Type>::iterator& i): it(i), r(&lst) {};bool operator==(const iterator& x) const {return it == x.it;};bool operator!=(const iterator& x) const {return !(*this == x);};typename list<Type>::reference operator*() const {return *it;};iterator& operator++() {++it;if (it == r->end()) {it = r->begin();}return *this;};iterator operator++(int) {iterator tmp = *this;++* this;return tmp;};iterator& operator--() {if (it == r->begin()) {it = r->end();}--it;return *this;};iterator operator--(int) {iterator tmp = *this;--*this;return tmp;};iterator insert(const Type& x) {return iterator(*r, r->insert(it, x));};iterator erase() {return iterator(*r, r->erase(it));};};void push_back(const Type& x) {lst.push_back(x);};iterator begin() {return iterator(lst, lst.begin());};int size() {return lst.size();} };
-
程序主文件 ( m a i n . c p p ) ({\rm main.cpp}) (main.cpp):
/*** 作者:罗思维* 时间:2024/03/26* 描述:程序主文件。 */ #include "JosephusRing.h"int main() {int n, m;cout << "请输入小孩总数:";cin >> n;cout << "每次数的孩子数:";cin >> m;JosephusRing<int> Josephus;for (int i = 1; i <= n; i++) {Josephus.push_back(i);}JosephusRing<int>::iterator tmp = Josephus.begin();JosephusRing<int>::iterator it = tmp;for (int index = 0; index < n - 1; index++) {it = tmp;for (int j = 0; j < m - 1; j++) {it++;tmp++;}tmp++;cout << "离开的孩子:" << *it << endl;it.erase();}it = Josephus.begin();cout << "最后剩下的孩子:" << *it << endl;return 0; }