此专栏为移动机器人知识体系下的编程语言中的 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; }