21天学通C++第8版(三)
(直接上传md文件好像有点问题…,可以直接到我的github
看)
第十三章 类型转换运算符
使用static_cast
可以将指针向上转换位基类类型,也可以向下转换为派生类型
Base* objBase = new Derived(); Derived* objDer = static_cast<Derived*>(objBase);
将 Derived*转换为 Base*被称为向上转换,无需使用任何显式类型转换运算符就能进行这种转换:
Derived objDerived; Base* objBase = &objDerived; // ok!
将 Base*转换为 Derived*被称为向下转换,如果不使用显式类型转换运算符,就无法进行这种转换:
Derived objDerived; Base* objBase = &objDerived; // Upcast -> ok! Derived* objDer = objBase; // Error: Downcast needs explicit cast
使用dynamic_cast
先看个例子
class Fish { public: virtual void Swim() { cout << "Fist swims in water" << endl; } virtual ~Fish() {}; }; class Tuna : public Fish { public: void Swim() { cout << "Tuna swims real fast in the sea" << endl; } void BecomeDinner() { cout << "Tuna become dinner in Sushi" << endl; } }; class Carp : public Fish { public: void Swim() { cout << "Carp swims real slow in the lake" << endl; } void Talk() { cout << "Carp talked Carp!" << endl; } }; void DectectFishType(Fish* objFish) { Tuna* objTuna = dynamic_cast<Tuna*>(objFish); if (objTuna) { cout << "Dectected Tuna. Making Tuna dinner" << endl; objTuna->BecomeDinner(); } Carp* objCarp = dynamic_cast<Carp*>(objFish); if (objCarp) { cout << "Dectected Carp. Making Carp dinner" << endl; objCarp->Talk(); } cout << "Verifying type using virtual Fish: Swim: " << endl; objFish->Swim(); } int main() { Carp myLunch; Tuna myDinner; DectectFishType(&myLunch); cout << endl; DectectFishType(&myDinner); system("pause"); return 0; }
输出结果:
Dectected Carp. Making Carp dinner Carp talked Carp! Verifying type using virtual Fish: Swim: Carp swims real slow in the lake Dectected Tuna. Making Tuna dinner Tuna become dinner in Sushi Verifying type using virtual Fish: Swim: Tuna swims real fast in the sea
这个示例的独特之处在于,给定一个基类指针(Fish),您可动态地检测它指向的是否是 Tuna 或 Carp。这种动态检测(运行阶段类型识别)是在函数DetectFishType( )中进行的。使用 dynamic_cast 传入的基类指针(Fish)参数指向的是否是 Tuna 对象。如果该 Fish指向的是 Tuna 对象,该运算符将返回一个有效的地址,否则将返回 NULL。因此,总是需要检查 dynamic_cast 的结果是否有效。如果通过了if语句的检查,您便知道指针 objTuna 指向的是一个有效的 Tuna 对象,因此可以使用它来调用函数 Tuna::BecomeDinner( )。如果传入的 Fish参数指向的是 Carp 对象,则使用它来调用函数 Carp::Talk( )。返回之前,DetectFishType( )调用了 Swim( ),以验证对象类型; Swim( )是一个虚函数,这行代码将根据指针指向的对象类型,调用相应类(Tuna 或 Carp)中实现的方法 Swim( )。
第十四章 宏和模板
宏
预处理器与编译器
预处理器编译指令都以#打头,比如#define
#define ARRAY_LENGTH 25
使用宏避免多次包含
C++程序员通常在.H 文件(头文件)中声明类和函数, 并在.CPP 文件中定义函数, 因此需要在.CPP文件中使用预处理器编译指令#include \
来包含头文件。如果在头文件 class1.h 中声明了一个类,而这个类将 class2.h 中声明的类作为其成员,则需要在 class1.h 中包含 class2.h。如果设计非常复杂,即第二个类需要第一个类,则在 class2.h 中也需要包含 class1.h! 然而,在预处理器看来,两个头文件彼此包含对方会导致递归问题。为了避免这种问题,可结合使用宏以及预处理器编译指令#ifndef 和#endif。
包含
模板
模板声明语法
模板声明以关键字 template 打头,接下来是类型参数列表。这种声明的格式如下:
template <parameter list> template function / class declaration..
关键字 template 标志着模板声明的开始,接下来是模板参数列表。该参数列表包含关键字 typename,它定义了模板参数 objType, objType 是一个占位符,针对对象实例化模板时,将使用对象的类型替换它。
template <typename T1, typename T2 = T1> bool TemplateFunction(const T1& param1, const T2& param2); //A template class template <typename T1, typename T2 = T1> class MyTemplate{ private: T1 member1; T2 member2; public: T1 GetObj1() { return member1;} //...other members };
模板函数
下面模板声明返回两个参数中较大的一个:
template <typename objType> const objType GetMax(const objType& value1, const objType& value2){ if(value1 > value2) return value1; else return value2; }
下面是一个使用该模板的实例:
int num1 = 24; int num2 = 25; int maxVal = GetMax<int>(num1, num2); //注意到调用 GetMax 时使用了<int>,这将模板参数 objType 指定为 int。
然而,实际上调用模板函数时并非一定要指定类型,因此下面的函数调用没有任何问题:
int maxVal = GetMax(num1,num2);
在这种情况下,编译器很聪明,知道这是针对整型调用模板函数。然而,对于模板类,必须显式地指定类型。
模板类
- 声明包含多个参数的模板
template <typename T1, typename T2> class HoldPair { private: T1 value1; T2 value2; public: HoldPair(const T1& val1, const T2& val2) { value1 = val1; value2 = val2; } }; //使用 //模板实例化为一个参数int和另一个参数double HoldPair<int, double> pairIntDouble(1, 1.99); //模板两个参数都是int HoldPair<int, int> pairIntInt(1, 2);
- 声明包含默认参数的模板
template <typename T1=int, typename T2=int> class HoldsPair { // ... method declarations }; //使用 // Pair an int with an int (default type) HoldsPair <> pairInts (6, 500);
- 模板的实例化和具体化
模板类是创建类的蓝图,因此在编译器看来,仅当模板类以某种方式被使用后,其代码才存在。换言之,对于您定义了但未使用的模板类,编译器将忽略它。然而,当您像下面这样通过提供模板参数来实例化模板类(如HoldsPair)时:
HoldPair<int,double> pairIntDbl;
就相当于命令编译器使用模板来创建一个类,即使用模板参数指定的类型(这里是 int 和 double)实例化模板。因此,对模板来说,实例化指的是使用一个或多个模板参数来创建特定的类型。
另一方面,在有些情况下,使用特定的类型实例化模板时,需要显式地指定不同的行为。这就是具体化模板,即为特定的类型指定行为。下面是模板类HoldsPair 的一个具体化,其中两个模板参数的类型都为 int:
template<> class HoldPair<int,int>{ //implementation code here } //注意具体化模板的语法形式
下面给个实例:
template <typename T1, typename T2> class HoldPair { private: T1 value1; T2 value2; public: HoldPair(const T1& val1, const T2& val2) { value1 = val1; value2 = val2; } const T1& GetFirstValue() const; }; //具体化模板 类型是int,int //specialization of HoldsPair for types int & int here template<> class HoldPair<int, int> { private: int value1; int value2; string strFun; public: HoldPair(const int& val1, const int& val2) : value1(val1), value2(val2) {} const int& GetFirstValue() const { cout << "Returning integer " << value1 << endl; return value1; } }; int main() { HoldPair<int, int> pairIntInt(1, 2); pairIntInt.GetFirstValue(); system("pause"); return 0; }
输出:
Returning integer 1
事实上,在模板具体化 HoldsPair
第15章 标准库模板简介
没啥好说的…
第16章 STL string类
使用STL string类
实例化和复制STL string
const char* constCStylyString = "Hello String"; //可以用括号 std::string strFromConst(constCStylyString); //也可以用等号 std::string strFromConst = constCStylyString; //可以直接用常量字符串初始化 string str2("Hello String"); //也可以用一个string对象来初始化 string str2Copy(str2); //可让string的构造函数只接受输入字符串的前n个字符 string strPartialCopy(constCStylyString, 5); //还可这样初始化 string 对象,即使其包含指定数量的特定字符: string strRepeatChars(10, 'a');
访问std::string的字符内容
两种访问 STL string 字符元素的方式:运算符[]和迭代器
int main() { string stlString("Hello String"); //使用运算符[]访问string字符 cout << "Display elements in string using array-ayntax: " << endl; for (size_t charCounter = 0; charCounter < stlString.length(); ++charCounter) { cout << "Character [" << charCounter << "] is: "; cout << stlString[charCounter] << endl; } cout << endl; //使用迭代器访问string字符 cout << "Display elements in string using iterators: " << endl; int charOffset = 0; for (auto charLocator = stlString.cbegin(); charLocator != stlString.cend(); ++charLocator) { cout << "Character [" << charOffset++ << "] is: "; cout << *charLocator << endl; } cout << endl; //Access content as a const char* //获取string对象的c风格表示,用成员函数c_str() cout << "The char* representation of the string is: "; cout << stlString.c_str() << endl; system("pause"); return 0; }
输出结果:
Display elements in string using array-ayntax: Character [0] is: H Character [1] is: e Character [2] is: l Character [3] is: l Character [4] is: o Character [5] is: Character [6] is: S Character [7] is: t Character [8] is: r Character [9] is: i Character [10] is: n Character [11] is: g Display elements in string using iterators: Character [0] is: H Character [1] is: e Character [2] is: l Character [3] is: l Character [4] is: o Character [5] is: Character [6] is: S Character [7] is: t Character [8] is: r Character [9] is: i Character [10] is: n Character [11] is: g The char* representation of the string is: Hello String
拼接字符串
要拼接字符串,可使用运算符+=,也可以使用成员函数append():
string sampleStr1("Hello"); string sampleStr2(" String"); sampleStr1 += sampleStr2;//use std::string::operator+= //alternatively use std::string::append() sampleStr1.append(sampleStr2);// overloaded for char* too
在string中查找字符或字符串
主要是成员函数find()
int main() { string sampleStr("Good day String! Today is beautiful!"); cout << "Sample string is: " << endl << sampleStr << endl << endl; //Find substring "day" -- find returns position size_t charPos = sampleStr.find("day", 0);//0 表示从哪个位置开始搜索 //Check if the substring was found... if (charPos != string::npos) //string::npos实际为-1 cout << "First instance \"day\" at pos: " << charPos << endl; else cout << "Substring not found" << endl; cout << "Locating all instances of substring \"day\" " << endl; size_t subStrPos = sampleStr.find("day", 0); while (subStrPos != string::npos) { cout << "\"day\" found at position: " << subStrPos << endl; size_t searchOffset = subStrPos + 1; subStrPos = sampleStr.find("day", searchOffset); } system("pause"); return 0; }
输出结果:
Sample string is: Good day String! Today is beautiful! First instance "day" at pos: 5 Locating all instances of substring "day" "day" found at position: 5 "day" found at position: 19
截短STL string
STL string 类提供了 erase()函数,具有以下用途 :
- 在给定偏移位置和字符数时删除指定数目的字符。
string sampleStr ("Hello String! Wake up to a beautiful day!"); sampleStr.erase (13, 28); // Hello String!
- 在给定指向字符的迭代器时删除该字符。
sampleStr.erase (iCharS); // iterator points to a specific character //只删除迭代器指向的这个字符
- 在给定由两个迭代器指定的范围时删除该范围内的字符。
sampleStr.erase (sampleStr.begin (), sampleStr.end ()); // erase from begin to end
实例:
int main() { string sampleStr("Hello String! Wake up to a beautiful day!"); cout << "The original sample string is: " << endl; cout << sampleStr << endl << endl; // Delete characters from the string given position and count cout << "Truncating the second sentence: " << endl; sampleStr.erase(13, 28); //这里用sampleStr.erase(13)也是一样的 cout << sampleStr << endl << endl; //输出:Hello String! // Find a character 'S' in the string using STL find algorithm auto iCharS = find(sampleStr.begin(), sampleStr.end(), 'S'); //找到处于'S'位置的迭代器 // If character found, 'erase' to deletes a character cout << "Erasing character 'S' from the sample string:" << endl; if (iCharS != sampleStr.end()) sampleStr.erase(iCharS); cout << sampleStr << endl << endl; //输出Hello tring! // Erase a range of characters using an overloaded version of erase() cout << "Erasing a range between begin() and end(): " << endl; sampleStr.erase(sampleStr.begin(), sampleStr.end()); // Verify the length after the erase() operation above if (sampleStr.length() == 0) cout << "The string is empty" << endl; //输出:The string is empty system("pause"); return 0; }
输出结果:
The original sample string is: Hello String! Wake up to a beautiful day! Truncating the second sentence: Hello String! Erasing character 'S' from the sample string: Hello tring! Erasing a range between begin() and end(): The string is empty
字符串反转
有时需要反转字符串的内容。假设要判断用户输入的字符串是否为回文,方法之一是将其反转, 再与原来的字符串进行比较。反转 STL string 很容易,只需使用泛型算法 std::reverse():
string sampleStr ("Hello String! We will reverse you!"); reverse(sampleStr.begin(), sampleStr.end()); cout << sampleStr << endl; //output:!uoy esrever lliw eW !gnirtS olleH
字符串的大小写转换
int main() { cout << "Please enter a string for case-convertion: " << endl; cout << "> "; string inStr; getline(cin, inStr); cout << endl; //注意transform的用法,第三个参数应该是谓词! transform(inStr.begin(), inStr.end(), inStr.begin(), toupper); cout << "The string converted to upper case is: " << endl; cout << inStr << endl << endl; transform(inStr.begin(), inStr.end(), inStr.begin(), tolower); cout << "The string converted to lower case is: " << endl; cout << inStr << endl << endl; system("pause"); return 0; }
输出:
Please enter a string for case-convertion: > Convert to this string The string converted to upper case is: CONVERT TO THIS STRING The string converted to lower case is: convert to this string
基于模板的STL string实现
基于模板的STL string实现
前面说过, std::string 类实际上是 STL 模板类 std::basic_string 的具体化。容器类 basic_string 的模板声明如下:
template<class _Elem, class _Traits,//trait 特质 class _Ax> class basic_string
在该模板定义中,最重要的参数是第一个: Elem,它指定了 basic_string 对象将存储的数据类型。 因此, std::string 使用Elem=char 具体化模板basic_string 的结果,而 wstring 使用_Elem= wchar 具体化 模板basic_string 的结果。
换句话说, STL string 类的定义如下 :
typedef basic_string<char, char_traits<char>, allocator<char>> string;
而 STL wstring 类的定义如下:
typedef basic_string<wchar_t, char_traits<wchar_t>,allocator<wchar_t> > wstring;
课后习题
使用 tolower( )函数将字符串转换为小写时, std::transform()的作用是什么?
答:std::transform()对 string 对象中指定边界内的每个字符调用 tolower ()函数。
编写一个程序,告诉用户输入的句子包含多少个元音字母。
int GetNumCharacters(string& inputStr, char findChar) { int countFindChar = 0; size_t findOffset = inputStr.find(findChar,0); while (findOffset != string::npos) { ++countFindChar; findOffset = inputStr.find(findChar, findOffset + 1); } return countFindChar; } int main() { cout << "Please enter a string: " << endl << "> "; string inputStr; getline(cin, inputStr); int nNumberVowels = GetNumCharacters(inputStr, 'a'); nNumberVowels += GetNumCharacters(inputStr, 'e'); nNumberVowels += GetNumCharacters(inputStr, 'i'); nNumberVowels += GetNumCharacters(inputStr, 'o'); nNumberVowels += GetNumCharacters(inputStr, 'u'); //这里大写就不写了 cout << "The number of vowels in that sentence is: " << nNumberVowels; system("pause"); return 0; }
将字符串的字符交替地转换为大写。
int main() { cout << "Please enter a string: " << endl << "> "; string inputStr; getline(cin, inputStr); for (size_t nCharIndex = 0; nCharIndex < inputStr.length(); nCharIndex += 2) { inputStr[nCharIndex] = toupper(inputStr[nCharIndex]); } cout << inputStr << endl; system("pause"); return 0; }
第17章 STL动态数组类
典型的vector操作
vector是一种动态数组
实例化vector
std::vector<int> dynIntArray; // vector containing integers std::vector<float> dynFloatArray; // vector containing floats std::vector<Tuna> dynTunaArray; // vector containing Tunas
要声明指向list中元素的迭代器,可以这样做:
std::vector<int>::const_iterator elementInVec;
下面给出各种实例化vector的方式:
int main () { // vector of integers std::vector<int> integers; // vector with 3 elements initialized using C++11 list initialization std::vector<int> initVector{ 202, 2017, -1 }; // Instantiate a vector with 10 elements (it can still grow) std::vector<int> tenElements (10); // Instantiate a vector with 10 elements, each initialized to 90 std::vector<int> tenElemInit (10, 90); //注意,第一个参数是元素个数,第二个参数是初始化的值 // Instantiate one vector and initialize it to the contents of another std::vector<int> copyVector (tenElemInit); // Vector initialized to 5 elements from another using iterators std::vector<int> partialCopy (tenElements.cbegin(), tenElements.cbegin() + 5); return 0; }
使用push_back()在末尾插入元素
vector <int> integers; // declare a vector of type int // Insert sample integers into the vector: integers.push_back (50); integers.push_back (1);
列表初始化
C++11 通过 std::initialize_list<>支持列表初始化,让您能够像处理静态数组那样,在实例化 vector 的同时初始化其元素。与大多数容器一样, std::vector 也支持列表初始化,让您能够在实例化 vector 的 同时指定其元素:
vector<int> integers = {50, 1, 987, 1001};//用等号,列表初始化用的是中括号{}! // alternatively: vector<int> vecMoreIntegers {50, 1, 987, 1001}; //不用等号,直接加!
使用insert()在指定位置插入元素
- 指定插入位置
//insert an element at the beginning integers.insert(integers.begin(),25);
- 指定插入位置、要插入的元素数以及这些元素的值(都相同)
//insert 2 elements of value 45 at the end //就是插入两个45 integers.insert(integers.end(), 2, 45);
- 将另一个 vector 的内容插入到指定位置
// Another vector containing 2 elements of value 30 vector <int> another (2, 30); // Insert two elements from another container in position [1] integers.insert (integers.begin () + 1, another.begin (), another.end ()); //第一个参数给出要插入的迭代器的位置 //第二、第三个则把另一个vector中要插入的元素的迭代器范围传进来
展示例子:
int main() { vector<int> integers(4, 90); cout << "The initial contents of the vector: "; DisplayVector(integers); //在开头插入25 integers.insert(integers.begin(), 25); DisplayVector(integers); cout << endl; //在末尾插入2个45 integers.insert(integers.end(), 45); DisplayVector(integers); cout << endl; //创建另一个vector vector<int> another(2, 30); //把another插入到integers[1] integers.insert(integers.begin() + 1, another.begin(), another.end()); DisplayVector(integers); cout << endl; system("pause"); return 0; }
输出结果:
The initial contents of the vector: 90 90 90 90 25 90 90 90 90 25 90 90 90 90 45 25 30 30 90 90 90 90 45
使用数组语法访问 vector 中的元素
可使用下列方法访问 vector 的元素:
- 使用下标运算符([])以数组语法方式访问;
可使用类似于数组的语法访问并设置各个元素:
std::vector <int> tenElements (10); tenElements[3] = 2011; // set 4th element
- 使用成员函数 at( );
使用[]访问 vector 的元素时,面临的风险与访问数组元素相同,即不能超出容器的边界。 使用下标运算符([])访问 vector 的元素时,如果指定的位置超出了边界,结果将是不 确定的(什么情况都可能发生,很可能是访问违规)。 更安全的方法是使用成员函数 at( ):
//gets element at position 2 cout << integers.at(2)
at( )函数在运行阶段检查容器的大小,如果索引超出边界(无论如何都不能这样做),将引发异常。
下标运算符([])只有在保证边界完整性的情况下才是安全的。
- 使用迭代器 (使用指针语法访问 vector 中的元素)
迭代器(类似于指针的语法)访问 vector 中的元素
int main() { vector<int> integers{ 50,1,987,1001 }; auto element = integers.cbegin(); while (element != integers.cend()) { //计算迭代器的距离用distance函数!!! size_t index = distance(integers.cbegin(), element); cout << "Element at position "; cout << index << " is: " << *element << endl; ++element; } system("pause"); return 0; }
输出:
Element at position 0 is: 50 Element at position 1 is: 1 Element at position 2 is: 987 Element at position 3 is: 1001
删除vector中的元素
除支持使用 push_back()函数在末尾插入元素外, vector 还支持使用 pop_bac()k 函数将末尾的元素 删除。
vector<int> integers; // Insert sample integers into the vector: integers.push_back (50); integers.push_back (1); integers.push_back (987); integers.push_back (1001); //现在integers有四个元素: 50,1,987,1001 // Erase one element at the end integers.pop_back (); //pop_back()后只有三个: 50,1,987
理解大小和容量
vector 的大小指的是实际存储的元素数,而 vector 的容量指的是在重新分配内存以存储更多元素 前 vector 能够存储的元素数。
因此, vector 的大小小于或等于容量。
- 要查询 vector 当前存储的元素数,可调用 size( ):
cout << "Size: " << integers.size();
- 要查询 vector 的容量,可调用 capacity( ):
cout << "Capacity: " <, integers.capacity() << endl;
STL deque类
deque 是一个 STL 动态数组类,与 vector 非常类似,但支持在数组开头和末尾插入或删除元素。
要实例化一个整型 deque,可以像下面这样做:
std::deque<int> intDeque;
要使用 std::deque,需要包含头文件:
#include<deque>
deque 与 vector 极其相似,也支持使用函数 push_back( )和 pop_back( )在末尾插入和删除元素。
与 vector 一样, deque 也使用运算符[]以数组语法访问其元素。
deque 与 vector 的不同之处在于,它还允许 您使用 push_front 和 pop_front 在开头插入和删除元素。
-
要清空 vector 和 deque 等 STL 容器,即删除其包含的所有元素,可使用函数 clear()。
integers.clear();
请注意, vector 和 deque 还包含成员函数 empty(),这个函数在容器为空时返回 true,而 不像 clear()那样删除既有的元素。
intDeque.clear(); if(intDeque.empty()) cout << "The container is now empty" << endl;