第一章1.6 作业1.6.1 测试
解释器和编译器有什么不同?
答:解释器是一种对代码(或字节码)进行解释并执行相应的工具;编译器将代码作为输入,并生成目标文件。就C++而言,编译和链接后,将得到一个可执行文件,处理器可直接执行它,而无需做进一步解释。
链接器的作用是什么?
答:链接器的作用是将多个目标文件(.o 或 .obj)和库文件组合在一起,生成一个可执行文件或库。
正常的开发周期包括哪些步骤?
答:
需求分析:明确项目目标、用户需求和功能要求。系统设计:设计架构、模块和数据库结构。开发实现:编写代码,完成各功能模块。测试:进行单元测试、集成测试、功能测试等,确保质量。部署上线:将产品部署到目标环境并上线运行。维护与迭代:修复问题、优化功能、发布新版本。
1.6.2 练习
阅读下面的程序,在不运行它的情况下猜测其功能
12345678910#includeint main(){ int x = 8; int y = 6; std::cout << std::endl; std::cout << x - y << " " << x * y << " " << x + y << std::endl; std::cout << std::endl; return 0;}
**答:**输出x与y加、乘和减三个计算结果
输入练习1中的程序,然后编译并链接它。它做什么?与您的猜测相符吗?
下面的程序存在什么样的错误?
123456includeint main(){ std::cout << "Hello Buggy World \n"; return 0;}
答:没有在include前加上#
4.修复练习3中的程序的错误,重新编译、链接并运行它。它做什么?
123456#includeint main(){ std::cout << "Hello Buggy World \n"; return 0;}
答:输出Hello Buggy World\n
第二章2.4 C++函数123456789101112131415161718192021222324//程序清单2.4 声明、定义和调用函数,该函数演示了std::cout的功能#includeusing namespace std;int DemoConsoleOutput();int main(){ DemoConsoleOutput(); return 0;}int DemoConsoleOutput(){ cout << "This is a simple string literal" << endl; cout << "Writing number five:" << 5 << endl; cout << "Performing division 10 / 5= " << 10/5 << endl; cout << "Pi when approximated is 22 / 7 = " << 22 / 7 << endl; cout << "Pi is 22 / 7 = " << 22.0 / 7 << endl; return 0;}
123456789101112131415161718192021//程序清单2.5 使用函数的返回值#includeusing namespace std;int DemoConsoleOutput();int main(){ return DemoConsoleOutput();}int DemoConsoleOutput(){ cout << "This is a simple string literal" << endl; cout << "Writing number five:" << 5 << endl; cout << "Performing division 10 / 5= " << 10/5 << endl; cout << "Pi when approximated is 22 / 7 = " << 22 / 7 << endl; cout << "Pi is 22 / 7 = " << 22.0 / 7 << endl; return 0;}
2.5 使用std::cin和std::cout执行基本输入输出操作123456789101112131415161718192021//程序清单2.6 使用cin和cout显示用户的数字输入和文本输出#include#includeusing namespace std;int main(){ int inputNumber; cout << "Enter an integer:"; cin >> inputNumber; cout << "Enter your name:"; string inputName; cin >> inputName; cout << inputName << " entered " << inputNumber << endl; return 0;}
2.8作业2.8.1 测验
声明Int main()有何问题?
答:1.Int不是数据类型关键字不能用作返回值
2.main函数只能有一个,不能重载
注释可以超过一行吗?
答:可以。使用/**/多行注释或使用预编译指令
2.8.2 练习
查错:输入下面的程序并编译它。为什么不能通过编译?如何修复?
12345#includevoid main(){ std::Cout << Is there a bug here";}
答:1.输出对象书写错误,应改为cout
2.字符串字面值常量的双引号不全,应改为“Is there a bug here”
修复练习1中的错误,然后重新编译、链接并运行它。
12345#includevoid main(){ std::cout << "Is there a bug here";}
修改程序清单2.4,以演示减法和乘法
1234567891011121314151617181920212223#includeusing namespace std;int DemoConsoleOutput();int main(){ DemoConsoleOutput(); return 0;}int DemoConsoleOutput(){ cout << "This is a simple string literal" << endl; cout << "Writing number five:" << 5 << endl; cout << "Performing subtraction 10 - 5= " << 10 - 5 << endl; cout << "Pi when approximated is 22 * 7 = " << 22 * 7 << endl; cout << "Pi is 22 * 7 = " << 22.0 * 7 << endl; return 0;}
第三章3.1 什么是变量3.1.2声明和访问变量
变量的定义
VariableType VariableName;
VariableType VariableName = InitialValue;
12345678910111213141516171819202122//3.1 程序清单使用变量存储数字及其相乘结果#includeusing namespace std;int main(){ cout << "This program will help you multiply tow numbers" << endl; cout << "Enter the first number:"; int firstNumber; cin >> firstNumber; cout << "Enter the second number:"; int secondNumber; cin >> secondNumber; int multiplicationResult = firstNumber * secondNumber; cout << firstNumber << " x " << secondNumber << " = " << multiplicationResult << endl; return 0;}
3.1.5 全局变量
123456789101112131415161718192021222324252627282930313233//程序清单3.3 使用全局变量#includeusing namespace std;int firstNumber = 0;int secondNumber = 0;int multiplicationResult = 0;void MultiplyNumbers(){ cout << "Enter the first number:"; cin >> firstNumber; cout << "Enter the second number:"; cin >> secondNumber; cout << firstNumber << " x " << secondNumber << " = " << multiplicationResult << endl;}int main(){ cout << "This program will help you multiply tow numbers" << endl; MultiplyNumbers(); cout << "DisplayNumbers()"; cout << firstNumber << " x " << secondNumber << " = " << multiplicationResult << endl; return 0;}
3.1.6 命名约定
Pascal拼写法:每个单词首字母大写
驼峰拼写法:第一个单词的首字母小写,后面单词首字母大写
匈牙利表示法:在变量名开头包含指出变量类型的字符
下划线拼写法:每一个单词用下划线隔开
3.2 编译器支持的常见C++变量类型3.2.6 选择正确的数据类型以避免发生溢出错误
12345678910111213141516//程序清单3.4 演示有符号整型变量和无符号中心变量溢出的负面影响#includeusing namespace std;int main(){ unsigned short uShortValue = 65535; cout << "Incrememting unsigned short " << uShortValue << "gives: "; cout << ++uShortValue << endl; short signedShort = 32767; cout << "Incrememting signed short " << signedShort << "gives:"; cout << ++signedShort << endl; return 0;}
3.3 使用sizeof确定变量长度3.3.1 使用列表初始化避免缩窄转换错误
12345int largeNum = 5000000;short anotherNum{ largeNum }; //error! Amend typesint anoterNUm{ largeNum }; //OKfloat someFloat{ largeNum }; //error! An int may be narrowedfloat someFloat{ 5000000 }; //OK! 5000000 can be accomodated
3.4使用auto自动推导类型1234567891011121314151617//程序清单3.6 使用关键字auto依靠编译器的类型推导功能#includeusing namespace std;int main(){ auto coinFlippedHeads = true; auto largeNumber = 2500000000000; cout << "coinFlippedHeads = " << coinFlippedHeads; cout << " , sizeof(coinFlippedHeads) = " << sizeof(coinFlippedHeads) << " " << typeid(coinFlippedHeads).name() << endl; cout << "largeNumber = " << largeNumber; cout << " , sizeof(largeNumber) = " << sizeof(largeNumber) << " " << typeid(largeNumber).name() << endl; return 0;}
3.6 什么是常量c++中的常量
字面常量;
使用关键字const声明的常量;
使用关键字constexpr声明的常量表达式(C++11 新增的);
使用关键字enum (class)声明的枚举常量;
使用#define定义的常量(不推荐);
3.6.2 使用const将变量声明为常量
通用的声明方法:const typeName constantName = value;
1234567891011//程序清单3.7 声明一个名为pi的变量#includeusing namespace std;int main(){ const double pi = 22.0 / 7; cout << "The value of constant pi is: " << pi << endl; return 0;}
3.6.3 使用constexpr 定义常量表达式
1234567891011121314151617//程序清单3.8 使用常量表达式来计算pi的值#includeusing namespace std;constexpr double GetPi() { return 22.0 / 7; }constexpr double TwicePi() { return 2 * GetPi(); }int main(){ const double pi = 22.0 / 7; cout << "constant pi contains value " << pi << endl; cout << "constexpr GetPi() returns value " << GetPi() << endl; cout << "constexpr TwicePi() return value " << TwicePi() << endl; return 0;}
3.6.4 枚举
123456789101112131415161718192021222324//程序清单3.9 使用枚举量指示基本方位#includeusing namespace std;enum CardinalDirections{ North = 25, South, West, East,};int main(){ cout << " Displaying directions and their symbolic values" << endl; cout << "North: " << North << endl; cout << "South: " << South << endl; cout << "West: " << West << endl; cout << "East: " << East << endl; CardinalDirections windDirection = South; cout << "Variable windDirection = " << windDirection << endl; return 0;}
作业3.10.1 测验
有符号整型和无符号整型有何不同?
答:它们在内存中的最高位表示不同,无符号的最高位表示数值,有符号的最高位表示符号位
为何不应使用#define来声明常量?
答:#define定义的常量只是进行文本替换,不进行安全检查,这是不安全的。
为何要对变量进行初始化?
答:初始化变量是一个良好的编程习惯,可以增强代码的可读性、可靠性和安全性,避免意外行为的发生。
给定如下枚举类型,Quee的值是多少?
1enum YourCards {Ace, Jack, Queen, King};
答:2
下述变量名有何问题?
1int Interger = 0;
答:编译没有问题,但是变量名不能够正确表达意思。
3.10.2 练习
修改测试题4中的枚举类型,让Queen的值为45
12enum YourCards {Ace, Jack, Queen = 45, King}; //方法一 0,1,45,46enum YourCards {Ace = 43, Jack, Queen = 45, King}; //方法二 43,44,45,36
编写一个程序,证明unsigned int 和 int 变量的长度,且它们对比long变量短。
1234567891011#includeusing namespace std;int main(){ cout << "unsigned int " << sizeof(unsigned int) << endl; cout << "int " << sizeof(int) << endl; cout << "long " << sizeof(long) << endl; return 0;}
编写一个程序,让用户输入圆的半径,并计算其面积和周长?
12345678910111213141516171819#includeusing namespace std;const double pi = 3.14;int main(){ cout << "Enter your radius:"; double radius = 0.0; cin >> radius; double circumference = 2 * radius * pi; double area = radius * radius * pi; cout << "circumference = " << circumference << endl; cout << "area = " << area << endl; return 0;}
在练习3中,如果将面积和周长存储在int中,输出将有何不同?
答:会损失小数的精度,输出一个整型
查错:下面的语句有何错误?
auto Integer
答:Integer没有初值,auto无法进行推导
第四章4.1 什么是数组数组具有一下特点
数组是一系列元素
数组中所有元素都是相同类型
这组元素形成一个完整的集合
4.1.2 声明和初始化静态数组
数组声明如下
ElementType ArrayName [constant_number of elements] = {optional initial values};
4.1.4 访问存储在数组中的数据
12345678910111213141516//程序清单 4.1 声明一个int数组并访问其元素#includeusing namespace std;int main(){ int myNumbers[5] = { 34,56,-21,5002,365 }; cout << "First element at index 0:" << myNumbers[0] << endl; cout << "Second element at index 1:" << myNumbers[1] << endl; cout << "Third element at index 2:" << myNumbers[2] << endl; cout << "Fourth element at index 3:" << myNumbers[3] << endl; cout << "Fifth element at index 4:" << myNumbers[4] << endl; return 0;}
4.1.5 修改存储在数组中的数据
12345678910111213141516171819202122232425262728//程序清单 4.2 给元素赋值#includeusing namespace std;constexpr int Square(int number) { return number * number; }int main(){ const int ARRAY_LENGTH = 5; int myNumbers[ARRAY_LENGTH] = { 5,10,0,-101,20}; int moreNumbers[Square(ARRAY_LENGTH)]; cout << "Enter index of the element to be change:"; int elementIndex = 0; cin >> elementIndex; cout << "Enter new value"; int newValue; cin >> newValue; myNumbers[elementIndex] = newValue; moreNumbers[elementIndex] = newValue; cout << "Element " << elementIndex << "in array myNumbers is: " << myNumbers[elementIndex] << endl; cout << "Element " << elementIndex << "in array moreNumbers is: " << moreNumbers[elementIndex] << endl; return 0;}
4.2 多维数组4.2.1 声明和初始化多维数组
在C++中,要声明多维数组,可指定每维包含的元素数
1int solarPanels[2][3] = {{0,1,2},{3,4,5}};
4.2.2 访问多维数组中的元素
12345678910111213141516171819202122//程序清单 4.3 访问多维数组中的元素#includeusing namespace std;int main(){ int threeRowsThreeColumns[3][3] = \ {{-501, 205, 2016}, { 989,101,206 }, {303,456,596}}; cout << "Row 0:" << threeRowsThreeColumns[0][0] << " "\ << threeRowsThreeColumns[0][1] << " "\ << threeRowsThreeColumns[0][2] << endl; cout << "Row 1:" << threeRowsThreeColumns[1][0] << " "\ << threeRowsThreeColumns[1][1] << " "\ << threeRowsThreeColumns[1][2] << endl; cout << "Row 2:" << threeRowsThreeColumns[2][0] << " "\ << threeRowsThreeColumns[2][1] << " "\ << threeRowsThreeColumns[2][2] << endl; return 0;}
4.3 动态数组1234567891011121314151617181920212223242526//程序清单 4.4 创建int动态数组并动态插入#include#includeusing namespace std;int main(){ vector dynArray(3); dynArray[0] = 365; dynArray[1] = -421; dynArray[2] = 789; cout << "Number of integers in array: " << dynArray.size() << endl; cout << "Enter another element to insert" << endl; int newValue = 0; cin >> newValue; dynArray.push_back(newValue); cout << "Number of integers in arry: " << dynArray.size() << endl; cout << "Last element in array: " << dynArray[dynArray.size() - 1] << endl; return 0;}
4.4 C风格字符串1234567891011121314151617//程序清单 4.5 分析C风格字符串中的终止空字符#includeusing namespace std;int main(){ char sayHello[] = {'H','e','l','l','o',' ','w','o','r','l','d','\0'}; cout << sayHello << endl; cout << "Size of array: " << sizeof(sayHello) << endl; cout << "Replaceing space with null" << endl; sayHello[5] = '\0'; cout << "Size of array: " << sizeof(sayHello) << endl; return 0;}
4.5 C++字符串:使用std::string1234567891011121314151617181920212223242526272829303132//程序清单 4.7 使用std::string初始化字符串、存储用户输入、复制和拼接字符串以及字符串的长度#include#includeusing namespace std;int main(){ string greetString("Hello std::string!"); cout << greetString << endl; cout << "Enter a line of text: " << endl; string firstLine; getline(cin, firstLine); cout << " Enter another: " << endl; string secondLine; getline(cin, secondLine); cout << "Result of concatenation: " << endl; string concatString = firstLine + " " + secondLine; cout << concatString; cout << "Copy of concatentated string: " << endl; string aCopy; aCopy = concatString; cout << aCopy << endl; cout << "Length of concatString: " << concatString.size() << endl; return 0;}
4.8 作业4.8.1 测验
d对于程序清单4.1中的数组myNumbers,第一个和最后一个的索引元素分别是多少?
答:0和4
如果需要让用户输入字符串,该使用C风格的字符串吗?
答:不推荐使用,应该使用std::string,以防止出现越界的情况
在编译器看来,’\0’表示多少个字符?
答:1个
如果忘记在C风格字符串末尾添加空字符,使用它的结果将会如何?
答:会出现字符串乱码
根据程序清单4.4中的矢量的声明,尝试声明一个包含char元素的动态数组?
答:vector charArray;
4.8.2 练习
声明一个国际象棋棋盘的数组;该数组的类型为枚举,该枚举定义了可能出现在棋盘方格中的棋子。
1234567891011121314151617181920212223#include#includeusing namespace std;enum Square{ empty = 0, Pawn, Rook, Knight, Bishop, King, Queen};int main(){ Square chessBoard[8][8]; chessBoard[0][0] = chessBoard[0][7] = Rook; chessBoard[7][7] = chessBoard[7][0] = Rook; return 0;}
查错:下面的代码段有什么错误?
12int myNumbers[5] = {0};myNumbers[5] = 450;
答:越界操作
查错:下面的代码段有什么错误?
12int myNumbers[5];cout << myNumbers[3];
答:访问了未初始化的内存
第五章5.3 使用运算符 5.3.2 理解左值和右值
左值:通常是内存单元
右值:是内存单元的内容
所有左值可以用作右值,但并非所有右值都可以用作左值
5.3.3 加法运算符、除法运算符、减法运算符、乘法运算符和取模运算符
12345678910111213141516171819//程序清单5.1 演示如何对用户输入的整数执行算术运算#includeusing namespace std;int main(){ cout << "Enter two integers: " << endl; int num1 = 0, num2 = 0; cin >> num1; cin >> num2; cout << num1 << " + " << num2 << " = " << num1 + num2 << endl; cout << num1 << " - " << num2 << " = " << num1 - num2 << endl; cout << num1 << " * " << num2 << " = " << num1 * num2 << endl; cout << num1 << " / " << num2 << " = " << num1 / num2 << endl; cout << num1 << " % " << num2 << " = " << num1 % num2 << endl; return 0;}
5.3.5 前缀还是后缀
前缀:先将右值递增或递减,再将结果赋给左值。
后缀:先将右值赋给左值,再将右值递减或递增
123456789101112131415161718192021222324252627282930//程序清单5.2 前缀运算符和后缀运算符之间的差别#includeusing namespace std;int main(){ int startValue = 101; cout << "Sart value of integer being operated: " << startValue << endl; int postfixIncrement = startValue++; cout << "Result of Postfix Increment = " << postfixIncrement << endl; cout << "After Postfix Increment, startValue = " << startValue << endl; startValue = 101; int prefixIncrement = ++startValue; cout << "Result of prefix Increment = " << prefixIncrement << endl; cout << "After prefix Increment, startValue = " << startValue << endl; startValue = 101; int postfixDecrement = startValue--; cout << "Result of Postfix Decrement = " << postfixDecrement << endl; cout << "After Postfix Decrement, startValue = " << startValue << endl; startValue = 101; int prefixDecrement = --startValue; cout << "Result of Postfix Decrement = " << prefixDecrement << endl; cout << "After prefix Decrement, startValue = " << startValue << endl; return 0;}
作业5.6.1 测验
编写将两个数相除的应用程序时,将变量声明为哪种数据类型更合适,int还是float?
答:float,防止精度丢失。
32/7的结果是多少?
答:4
32.0/7的结果是多少?
答:4.57143
sizeof()是函数吗?
答:是关键字
我需要将一个数翻倍,再加上5,然后在翻倍,下面的代码是否正确?
int result = number << 1 + 5 << 1
答:不正确
如果两个操作数的值都为true,对其执行XOR的结果是什么?
答:false
5.6.2 练习
使用括号改善测试题5中的代码,使其清晰
答:int result = ((number << 1) + 5) << 1;
下述代码导致result的值为多少?
答:result << 7
编写一个程序,让用户输入两个布尔值,并显示其执行各种按位运算的结果。
1234567891011121314151617#includeusing namespace std;int main(){ cout << "Enter your two bool value:" << endl; bool bit1 = false; bool bit2 = false; cin >> bit1; cin >> bit2; cout << "or: " << (bit1 | bit2) << endl; cout << "and: " << (bit1 & bit2) << endl; cout << "xor: " << (bit1 ^ bit2) << endl; return 0;}
第六章6.2 在循环中执行代码6.2.5 基于范围的for循环
for(VarType varName : sequence){
//Use varName that contains an element from sequence
}
1234567891011121314151617181920212223242526272829303132333435363738//程序清单6.12 使用基于范围的for循环来处理数组和std::string#include#includeusing namespace std;int main(){ int sumoNums[] = { 1,101,-1,40,2040 }; for (const int& aNum : sumoNums) { cout << aNum << ' '; } cout << endl; for (auto anElement : { 5,222,110,-45,2017 }) { cout << anElement << ' '; } cout << endl; char charArray[] = { 'h','e','l','l','o' }; for (auto anElement : charArray) { cout << anElement << ' '; } cout << endl; double moreNums[] = { 3.14,-1.3,22,10101 }; for (auto anElement : moreNums) { cout << anElement << ' '; } cout << endl; string sayHello{ "Hello world!" }; for (auto anElement : sayHello) { cout << anElement << ' '; } cout << endl; return 0;}
6.7 作业
6.7.1 测验
既然不缩进也能通过编译,为什么要缩进语句块、嵌套if语句和嵌套循环?
答:缩进让代码结构更加清晰,便于阅读和理解
使用goto可以快速解决问题,为何还要避免使用它?
答:goto会破坏程序的结构,容易引发逻辑错误
可以编写计算器递减的for循环吗?这样的for循环是什么样的?
123for(int i = value; i > 0; i--){}
下面的循环有何问题?
12for(int counter = 0; counter == 10; ++counter) cout << counter << " ";
答:判断条件存在问题,应该改为counter != 10
6.7.2 练习
编写一个for循环,以倒叙的方式访问数组
12345678910111213141516#includeusing namespace std;int main(){ const int ARRAY_LENGTH = 5; int arr[ARRAY_LENGTH] = { 0 ,1, 2, 3, 4}; for (int i = ARRAY_LENGTH - 1; i >= 0; i--) { cout << arr[i] << " "; } cout << endl; return 0;}
编写一个类似于程序清单6.14的嵌套for循环,但以倒序方式将一个数组的每个元素都与另一个数组的每个元素相加。
1234567891011121314151617181920212223#includeusing namespace std;int main(){ const int ARRAY_LENGTH = 5; int arr1[ARRAY_LENGTH] = { 0 ,1, 2, 3, 4}; int arr2[ARRAY_LENGTH] = { 10,20,30,40,50 }; for (int i = ARRAY_LENGTH - 1; i >= 0; i--) { for (int j = 0; j < ARRAY_LENGTH; j++) { arr1[i] += arr2[j]; } } for (int i = 0; i < ARRAY_LENGTH; i++) { cout << arr1[i] << " "; } cout << endl; return 0;}
编写一个程序,象清单6.16那样显示斐波那契数列,但让用户指定每次显示多少个
12345678910111213141516171819202122232425262728#includeusing namespace std;int main(){ cout << "Please enter the number of items to display per line." << endl; int num = 0; cin >> num; int n1 = 0, n2 = 1; char wantMore = '\0'; cout << n1 << " " << n2 << " " << endl; do{ for (int i = 0; i < num; i++) { cout << n1 + n2 << " "; int num2Temp = n2; n2 = n1 + n2; n1 = num2Temp; } cout << endl << "Do you want more numbers(y/n)"; cin >> wantMore; } while (wantMore == 'y'); cout << "Goodbye" << endl; return 0;}
编写一个switch-case结构,指出用户选择的颜色是否出现在彩虹中。请使用枚举常量
123456789101112131415161718192021222324252627282930313233343536#includeusing namespace std;enum Colors{ Violet,Indigo,Blue,Green,Yellow,Orange,Red,Crimson,Beige,Brown,Peach,Pink,White};int main(){ cout << "Here are the available colors: " << endl; cout << "Violet,Indigo,Blue,Green,Yellow,Orange,Red,Crimson,Beige,Brown,Peach,Pink,White" << endl; cout << "Corresponding to the numbers 0 to 12." << endl; cout << "Choose one by entering code: "; int yourChoice = Blue; cin >> yourChoice; switch (yourChoice) { case Violet: case Indigo: case Blue: case Green: case Yellow: case Orange: case Red: cout << "Bingo,your choice is a Rainbow color!" << endl; break; default: cout << "The color you chose is not in the rainbow" << endl; break; } return 0;}
查错:下面的代码有何错误?
12for(int counter = 0; counter = 10; ++counter) cout << counter << " ";
答:counter=10,这条语句可能会导致死循环
查错:下面的代码有何错误?
123456int loopCounter = 0;while(loopCounter < 5);{ cout << loopCounter << " "; loopCounter++;}
答:while(loopCounter < 5);的分号会导致死循环,不会执行后面的操作。
查错:下面的代码有何错误?
123456789101112131415cout << "Enter a number between 0 and 4" << endl;int input = 0;cin >> input;switch (input){case 0:case 1:case 2:case 3:case 4: cout << "Valid input" << endl;default: cout << "Invalid input" << endl;}
答:没有在cout << “Valid input” << endl;后添加break.
第七章7.1 为何需要函数123456789101112131415161718192021222324252627282930//程序清单7.1 两个根据半径分别计算圆的面积和周长的函数#includeusing namespace std;const double Pi = 3.14159265;double Area(double radius);double Circumference(double radius);int main(){ cout << "Enter radius: "; double radius = 0; cin >> radius; cout << "Area is: " << Area(radius) << endl; cout << "Circumference is: " << Circumference(radius) << endl; return 0;}double Area(double radius){ return Pi * radius * radius;}double Circumference(double radius){ return Pi * 2 * radius;}
7.1.1 函数原型是什么
double Area(double radius);
double: 返回值类型
Area:函数名
(double radius):函数参数(可选),参数列表由参数类型和参数名(可选)组成,当有多个参数时,用逗号隔开。
函数原型:指出了函数的名称、函数接受的参数列表以及返回值类型
7.1.2 函数定义是什么
函数定义:总是由一个语句块组成。除非返回类型被声明为void,否则必须包含一条return语句。
7.1.3 函数调用和实参是什么
**形参(parameter)**:函数声明中包含的参数。
**实参(argument)**:调用函数提供的参数
7.1.4 编写接收多个参数的函数
123456789101112131415161718192021222324252627282930//程序清单7.2 接受两个参数以计算圆柱表面积的函数#includeusing namespace std;const double Pi = 3.14159265;double SurfaceArea(double radius, double height);int main(){ cout << "Enter the radius of the cyliner:"; double radius = 0; cin >> radius; cout << "Enter the height of the cyliner:"; double height = 0; cin >> height; cout << "SurfaceArea is : " << SurfaceArea(radius, height) << endl; return 0;}double SurfaceArea(double radius, double height){ double area = 2 * Pi * radius * radius + 2 * Pi * radius * height; return area;}
7.1.5 编写没有参数和返回值的函数
1234567891011121314151617//程序清单7.3 没有参数和返回值的函数#includeusing namespace std;void SayHello();int main(){ SayHello(); return 0;}void SayHello(){ cout << "heloo world" << endl;}
7.1.6 带默认值的函数参数
1234567891011121314151617181920212223242526272829303132333435363738//程序清单7.4 计算圆面积的函数,其第二个参数为pi,该参数的默认值为3.14#includeusing namespace std;double Area(double radius, double pi = 3.14);int main(){ cout << "Enter radius: "; double radius = 0; cin >> radius; cout << "pi is 3.14, do you wish to change this(y/n)?"; char changePi = 'n'; cin >> changePi; double circleArea = 0; if (changePi == 'y') { cout << "Enter new Pi: "; double newPi = 3.14; cin >> newPi; circleArea = Area(radius, newPi); } else { circleArea = Area(radius); } cout << "Area is: " << circleArea << endl; return 0;}double Area(double radius, double pi){ return radius * radius * pi;}
7.1.7 递归函数——调用自己的函数
1234567891011121314151617181920//程序清单7.5 使用递归函数计算斐波那契数列中的数字#includeusing namespace std;int GetFibNumber(int fibIndex){ if (fibIndex < 2) return fibIndex; else return GetFibNumber(fibIndex - 1) + GetFibNumber(fibIndex - 2);}int main(){ cout << "Enter 0-Based index of desired Fibonacci Number: "; int index = 0; cin >> index; cout << "Fibonacci number is: " << GetFibNumber(index) << endl; return 0;}
7.2 使用函数处理不同的数据7.2.1 函数重载
名称和返回值相同,但参数不同的函数称为函数重载。
1234567891011121314151617181920212223242526272829303132333435363738394041//程序清单7.7 在同一个函数中使用多条语句#includeusing namespace std;const double Pi = 3.14159265;double Area(double radius);double Area(double radius, double height);int main(){ cout << "Enter z fro cyliner, c for Circle: "; char userSelection = 'z'; cin >> userSelection; cout << "Enter radius: "; double radius = 0; cin >> radius; if (userSelection == 'z'){ cout << "Enter height:"; double height = 0; cin >> height; cout << "Area of cyliner is: " << Area(radius, height) << endl; } else { cout << "Area of cyliner is: " << Area(radius) << endl; } return 0;}double Area(double radius){ return radius * radius * Pi;}double Area(double radius, double height){ return 2 * Area(radius) + 2 * Pi * radius * height;}
7.2.2 将数组传递给函数
示例:void DisplayIntegers(int numbers[], int Length);
第一参数是一个整型数组
第二个参数是数组的长度
12345678910111213141516171819202122232425262728293031323334//程序清单7.8 接受数组作为参数的函数#includeusing namespace std;void DisplayArray(int numbers[], int length);void DisplayArray(char charaters[], int length);int main(){ int myNums[4] = { 24,58,-1,245 }; DisplayArray(myNums, 4); char myStatement[7] = { 'h','e','l','l','o','!','\0' }; DisplayArray(myStatement, 7); return 0;}void DisplayArray(int numbers[], int length){ for (int i = 0; i < length; i++){ cout << numbers[i] << " "; } cout << endl;}void DisplayArray(char charaters[], int length){ for(int i = 0; i < length; i++){ cout << charaters[i] << " "; } cout << endl;}
7.2.3 按引用传递参数
1234567891011121314151617181920212223242526//程序清单7.9 以引用参数(而不是返回值)的方式提供圆的面积#includeusing namespace std;const double Pi = 3.1416;void Area(double radius, double& result);int main(){ cout << "Enter radius: "; double radius = 0; cin >> radius; double areaFetched = 0; Area(radius, areaFetched); cout << "This area is: " << areaFetched << endl; return 0;}void Area(double radius, double& result){ result = radius * radius * Pi;}
7.3 微处理器如何处理函数调用7.3.1 内联函数
123456789101112131415161718//程序清单7.10 将把整数翻倍的函数声明为内联#includeusing namespace std;inline long DoubleNum(int inputNum) { return inputNum * 2;}int main(){ cout << "Enter an integer: "; int input = 0; cin >> input; cout << "Double is:" << DoubleNum(input) << endl; return 0;}
7.3.2 自动推断返回类型
12345678910111213141516171819202122//程序清单7.11 将函数Area()的返回类型指定为auto#includeusing namespace std;const double Pi = 3.14159265;auto Area(double radius){ return Pi * radius * radius;}int main(){ cout << "Enter radius: "; double radius = 0; cin >> radius; cout << "Area is: " << Area(radius) << endl; return 0;}
7.3.3 lambda函数
1234567891011121314151617181920212223242526272829303132//程序清单 7.12 使用lambda函数对数组中的元素进行排序并显示它们#include#include#includeusing namespace std;void DisplayNums(vector& dynArray){ for_each(dynArray.begin(), dynArray.end(), \ [](int Element) {cout << Element << " "; }); cout << endl;}int main(){ vector myNums; myNums.push_back(501); myNums.push_back(-1); myNums.push_back(25); myNums.push_back(-35); DisplayNums(myNums); cout << "Sorting them in descending order" << endl; sort(myNums.begin(), myNums.end(), \ [](int Num1, int Num2) {return (Num1 > Num1); }); DisplayNums(myNums); return 0;}
7.6 作业7.6.1 测验
在函数原型中声明的变量的作用域是什么?
答:作用域仅在函数本身
传递给下述函数的值有何特征?
int Func(int &someNumber);
答:可通过someNumber修改传进来的实参
调用自己的函数叫什么?
答:递归
我声明了两个函数,它们的名称和返回值类型相同,但参数列表不同,这被称为什么?
答:函数重载
栈指针指向栈的顶部、中间还是底部?
答:栈的顶部
7.6.2 练习
编写两个重载函数,它们分别使用下述公式计算球和圆柱体的体积:
答:double Area(double radius);
double Area(double radius,double height);
编写一个函数,它将一个double数组作为参数。
答:void DisplayNumbers(double doubleArray,int length);
查错:下述代码有什么错误?
123456789101112131415161718192021222324#includeusing namespace std;const double Pi = 3.1416;void Area(double radius, double result){ result = Pi * radius * radius;}int main(){ cout << "Enter radius: "; double radius = 0; cin >> radius; double areaFetched = 0; Area(radius, areaFetched); cout << "The Area is: " << areaFetched << endl; return 0;}
答:通过使用值传递的方式,用形参修改实参
查错:下述函数声明有什么错误?
double Area(double Pi = 3.14, double radius);
答:函数的默认参数应该从右往左声明
编写一个返回类型为void的函数,在提供了半径的情况下,他能帮助调用者计算圆的周长和面积
答:
123456const double Pi = 3.14;void Calc(double radius, double &area, double &circumference){ area = Pi * radius * radius; circumference = 2 * radius * Pi;}
第八章8.1 什么是指针 指针是存储内存地址的变量
8.1.1 声明指针
PointedType * PointerVariableName;
PointedType * PointerVariableName = NULL;
8.1.2 使用引用运算符(&)获取变量地址
如果varName是一个变量,&varName将是存储该变量的地址
1234567891011121314//程序清单8.1 获取int变量和double变量的地址#includeusing namespace std;int main(){ int age = 30; const double Pi = 3.1416; cout << "Integer age is localted at: 0x" << &age << endl; cout << "double Pi is localted at: 0x" << &Pi << endl; return 0;}
8.1.3 使用指针存储地址
要将变量的地址存储到一个指针,需要声明一个同样的指针,并使用引用符号(&)将其初始化为该变量的地址
Type* Pointer = &Variable;
12345678910111213//程序清单8.2 声明指针并初始化指针#includeusing namespace std;int main(){ int age = 30; int* pointsToInt = &age; cout << "Integer age is localted at: 0x" << hex << pointsToInt << endl; return 0;}
123456789101112131415161718//程序清单8.3 给指针重新赋值,使其指向另一个变量#includeusing namespace std;int main(){ int age = 30; int* pointsToInt = &age; cout << "pointsToInt points to age now" << endl; cout << "pointaToInt = 0x" << hex << pointsToInt << endl; int dogsAge = 9; pointsToInt = &dogsAge; cout << "pointsToInt points to dogsAge now" << endl; cout << "pointaToInt = 0x" << hex << pointsToInt << endl; return 0;}
8.1.4 使用解除引用运算符(*)访问指向的数据
12345678910111213141516171819202122232425//程序清单8.4 使用解除引用运算符(*)来访问整数值#includeusing namespace std;int main(){ int age = 30; int dogsAge = 9; cout << "Interger age = " << age << endl; cout << "Interger dogsAge = " << hex << dogsAge << endl; int* pointsToInt = &age; cout << "pintersToint points to age" << endl; cout << "pointsToInt = 0x" << hex << pointsToInt << endl; cout << "*pointsToInt = " << dec << *pointsToInt << endl; pointsToInt = &dogsAge; cout << "pintersToint points to dogsAge" << endl; cout << "pointsToInt = 0x" << hex << pointsToInt << endl; cout << "*pointsToInt = " << dec << *pointsToInt << endl; return 0;}
123456789101112131415161718192021//程序清单8.5 使用指针和解除引用运算符操作数据#includeusing namespace std;int main(){ int dogsAge = 30; cout << "Initialized dogsAge = " << dogsAge << endl; int* pointesToAnAGe = &dogsAge; cout << "pointsToAnAge points to dogsAge" << endl; cout << "Enter an age for your dog: "; cin >> *pointesToAnAGe; cout << "Input stored at 0x" << hex << pointesToAnAGe << endl; cout << "Integer dogsAge = " << dec << dogsAge << endl; return 0;}
8.1.5 将sizeof()用于指针的结果
32位:sizeof结果为4
64位:sizeof结果为8
8.2 动态分配内存8.2.1 使用new和delete动态分配内存和释放内存
分配和释放一个元素
12Type* Pointer = new Type;delete Pointer;
分配和释放多个元素
12Type* Pointer = new Tyoe[numElements];delete[] Pointer;
8.2.2 将递增和递减运算符(++和–)用于指针的结果
1234567891011121314151617181920212223242526272829//程序清单8.9 使用偏移量和运算符来递增和递减指针#includeusing namespace std;int main(){ cout << "How many integers you wish to enter?"; int numEntries = 0; cin >> numEntries; int* pointsToInts = new int[numEntries]; cout << "Allocated for " << numEntries << "integers" << endl; for (int counter = 0; counter < numEntries; ++counter) { cout << "Enter number" << counter << ": "; cin >> *(pointsToInts + counter); } cout << "Displaying all numbers entered: " << endl; for (int counter = 0; counter < numEntries; ++counter) { cout << *(pointsToInts++) << " "; } cout << endl; pointsToInts -= numEntries; delete[] pointsToInts; return 0;}
8.2.3 将关键字用于const用于指针
指针包含的地址是常量,不能修改指针指向的数据:
12345int daysInMonrh = 30;int* const pDaysInMonth = &daysInMonrh;*pDaysInMonth = 31;int daysInLunarMonth = 28;pDaysInMonth = &daysInLunarMonth; //error,Cannot change address
指针指向的数据为常量,不能修改,但可以修改指针包含的地址,即指针可以指向其地方
123456int hoursInDay = 24;const int* pointsToInt = &hoursInDay;int monthsInYear = 12;pointsToInt = &monthsInYear;*pointsToInt = 13; //error ,Cannot change data being pointed toint* newPointer = pointsToInt; // error, Cannot assign const to non-const
指针包含的地址以及它指向的值都是常量,不能修改(这种最严格)
12345int hoursInDay = 24;const int* const pHourInDay = &hoursInDay;*pHourInDay = 25; //error, Cannot change data being pointed toint daysInMonth = 30;pHourInDay = &daysInMonth; //error, Cannot change address
8.2.4 将指针传递给函数
12345678910111213141516171819202122232425262728//程序清单8.10 在计算圆面积的函数使用关键字const#includeusing namespace std;void CalcArea(const double* const ptrPi, const double* const ptrRadius, double* const ptrArea) { if (ptrPi && ptrRadius && ptrArea) { *ptrArea = (*ptrPi) * (*ptrRadius) * (*ptrRadius); }}int main(){ const double Pi = 3.1416; cout << "Enter radius of circle: "; double radius = 0; cin >> radius; double area = 0; CalcArea(&Pi, &radius, &area); cout << "Area is = " << area << endl; return 0;}
8.2.5 数组和指针的类似之处
123456789101112131415//程序清单 8.11 数组变量是指向第一个元素的指针#includeusing namespace std;int main(){ int myNumbers[5]; int* pointToNums = myNumbers; cout << "pointToNums = 0x" << hex << pointToNums << endl; cout << "&myNumbers[0] = 0x" << hex << &myNumbers[0] << endl; return 0;}
1234567891011121314151617181920212223242526//程序清单8.12 使用解除引用运算符(*)访问数组中的元素以及将数组运算符([])用于指针#includeusing namespace std;int main(){ const int ARRAY_LEN = 5; int myNumbers[ARRAY_LEN] = { 24,-1,365,-999,2011 }; int* pointToNums = myNumbers; cout << "pointToNums = 0x" << hex << pointToNums << endl; cout << "&myNumbers[0] = 0x" << hex << &myNumbers[0] << endl; cout << "Display array using pointer syntax, operator*" << endl; for (int index = 0; index < ARRAY_LEN; index++) { cout << "Element " << index << " = " << *(myNumbers + index) << endl; } cout << "Display array using ptr with array syntax, operator[]" << endl; for (int index = 0; index < ARRAY_LEN; index++) { cout << "Element " << index << " = " << pointToNums[index] << endl; } return 0;}
8.5 引用是什么 引用是变量的别名。声明时,需要将初始化为一个变量
12VarType original = value;VarType ReferenceVarialbe = original;
12345678910111213141516171819//程序清单 8.17 引用是相应变量的别名#includeusing namespace std;int main(){ int original = 30; cout << "original = " << original << endl; cout << "original = " << hex << &original << endl; int& ref1 = original; cout << "ref1 is at address: " << hex << &ref1 << endl; int& ref2 = ref1; cout << "ref2 is at address: " << hex << &ref2 << endl; cout << "Therefore, ref2 = " << dec << ref2 << endl; return 0;}
8.5.1 是什么让引用很有用
可以在函数调用实参时避免复制这个步骤
12345//可避免复制步骤的函数版本类似下面这样:ReturnType DoSomething(Type& parameter);//调用如下ReturnType Result = Dosomething(argument);
1234567891011121314151617181920//程序清单8.18 一个计算平方值通过引用参数返回结果的函数#includeusing namespace std;void GetSquare(int& number){ number *= number;}int main(){ cout << "Enter a number you wish to square: "; int number = 0; cin >> number; GetSquare(number); cout << "Square is: " << number << endl; return 0;}
8.5.2 将关键字const用于引用
须要禁止通过引用修改它的指向变量的值,为此可在声明引用时使用关键字const:
12345int original = 30;const int& constRef = original;constRef = 40; //error: constRef can't change value in originalint& ref = constRef; //error: ref2 is not constconst int& constRef2 = constRef;
按引用向函数传递参数
123456789101112131415161718192021//程序清单8.19 使用const引用确保被调用的函数#includeusing namespace std;void GetSquare(const int& number, int &result){ result = number * number;}int main(){ cout << "Enter a number you wish to square: "; int number = 0; cin >> number; int square = 0; GetSquare(number, square); cout << number << "^2 = " << square << endl; return 0;}
8.5 作业8.8.1 测验
为何不能将const引用赋给非const引用
答:防止非const引用修改const引用的值
new和delete是函数吗?
答:是关键字
指针变量包含的值有何特征?
答:变量地址
要访问指针指向的数据,应使用哪种运算符?
答:解引用运算符(*)
8.8.2 练习
下面的语句显示什么
123456int number = 3;int *pNum1 = &number;*pNum1 = 20;int *PNum2 = pNum1;number *= 2;cout << *pNum2;
答:40
**下面三个重载的函数有何相同和不同之处? **
123int DoSomething(int num1, int num2);int DoSomething(int& num1, int& num2);int DoSomething(int* num1, int* num2);
答:
相同之处
函数名相同
功能相似
返回值相同
不同之处
参数不同
调用方式不同
对参数的影响
使用场景不同
要让练习1中第3行的赋值非法,应如何修改第1行中pNum1的声明(提示:让pNum1不能 修改它指向的数据)?
答:添加const关键字
查错:下面的代码有何错误?
1234567891011#includeusing namespace std;int main(){ int* pointToAnInt = new int; pointToAnInt = 9; cout << "The value at pointToAnInt: " << *pointToAnInt << endl; delete pointToAnInt; return 0;}
答:没有使用解引用运算符(*)就直接对pointToAnInt操作
查错:下面的代码有何错误?
12345678910111213#includeusing namespace std;int main(){ int* pointToAnInt = new int; int * pNumberCopy = pointToAnInt; *pNumberCopy = 30; cout << *pointToAnInt; delete pNumberCopy; delete pointToAnInt; return 0;}
答:对同一块内存重复释放;
修正练习5的代码后,其输出是什么?
答:30
第九章9.1 类和对象9.14 使用指针运算符(->)访问成员
1234567891011121314151617181920212223242526272829303132//程序清单 一个值得编译的Human类#include#includeusing namespace std;class Human {public: string name; int age; void IntroduceSelf() { cout << "I am" + name << " and am " << age << " years old" << endl; }};int main(){ Human firstMan; firstMan.name = "Adam"; firstMan.age = 30; Human firstWoman; firstWoman.name = "Eve"; firstWoman.age = 28; firstMan.IntroduceSelf(); firstWoman.IntroduceSelf(); return 0;}
9.2 关键字public和private9.2.1 使用关键字private实现数据抽象
123456789101112131415161718192021222324252627282930313233343536//程序清单9.2 一个对外隐藏真实年龄并将自己说的更年轻的Human类#include#includeusing namespace std;class Human {public: void SetAge(int inputAge) { age = inputAge; } int GetAge() { if (age > 30) return (age - 2); else return age; }private: int age;};int main(){ Human firstMan; firstMan.SetAge(35); Human firstWoman; firstWoman.SetAge(22); cout << "Age of firstMan " << firstMan.GetAge() << endl; cout << "Age of firstWoman " << firstWoman.GetAge() << endl; return 0;}
9.3 构造函数9.3.1 声明和实现构造函数
构造函数是一种特殊的函数,他与类同名且不返回任何值,Human类的构造函数的声明类似于下面这样
12345class Human{public: Human(); //constuctor declaration};
构造函数可在类声明中实现
12345678class Human{public: Human() { //constructor code here }};
在类声明外定义构造函数
12345678910class Human{public: Human(); //constuctor declaration};Human::Human(){ //constructor code here}
9.3.2 何时及如何使用构造函数
构造函数总是在创建对象时被调用,这让构造函数成为将类成员变量初始化为选定值的理想场所
1234567891011121314151617181920212223242526272829303132333435363738394041424344//9.3 使用构造函数初始化类成员变量#include#includeusing namespace std;class Human {public: Human() { age = 1; cout << "Constructed an instance of class Human" << endl; } void SetName(string humansNmae) { name = humansNmae; } void SetAge(int humansAge) { age = humansAge; } void IntroduceSelf() { cout << "I am " + name << " and am " << age << " years old" << endl; }private: string name; int age;};int main(){ Human firstWoman; firstWoman.SetName("Eve"); firstWoman.SetAge(28); firstWoman.IntroduceSelf(); return 0;}
9.3.3 重载构造函数
与函数一样,构造函数也可以重载
12345678910111213141516171819202122232425262728293031323334//9.4 包含多个构造函数的Human类#include#includeusing namespace std;class Human {public: Human() { age = 1; cout << "Default Constructor: name and age not set" << endl; } Human(string humansName, int humansAge) { name = humansName; age = humansAge; cout << "Overloaded constructor creates "; cout << name << " of " << age << " years " << endl; }private: string name; int age;};int main(){ Human firstMan; Human firstWoman("Eve", 20); return 0;}
9.3.4 没有默认构造函数的类
123456789101112131415161718192021222324252627282930313233343536//9.5 一个有重载的构造函数,但没有默认的构造函数的类#include#includeusing namespace std;class Human {public: Human(string humansName, int humansAge) { name = humansName; age = humansAge; cout << "Overloaded constructor creates "; cout << name << " of " << age << " years " << endl; } void IntroduceSelf() { cout << "I am " + name << " and am" << age << " years old" << endl; }private: string name; int age;};int main(){ Human firstMan("Adam", 25); Human firstWoman("Eve", 20); firstMan.IntroduceSelf(); firstWoman.IntroduceSelf(); return 0;}
9.3.5 带默认值的构造函数参数
就像函数可以带有默认值的参数一样,构造函数也可以
1234567891011121314151617181920class Human {public: Human(string humansName, int humansAge = 25) { name = humansName; age = humansAge; cout << "Overloaded constructor creates "; cout << name << " of " << age << " years " << endl; } void IntroduceSelf() { cout << "I am " + name << " and am" << age << " years old" << endl; }private: string name; int age;};
9.3.6 包含初始化列表的构造函数
123456789101112131415161718192021222324252627//程序清单9.6 接受带默认值的参数的默认构造函数,并使用初始化列表来设置成员#include#includeusing namespace std;class Human {public: Human(string humansName = "Adam", int humansAge = 25) : name(humansName),age(humansAge) { cout << "Overloaded constructor creates "; cout << name << " of " << age << " years " << endl; }private: string name; int age;};int main(){ Human firstMan; Human firstWoman("Eve", 20); return 0;}
也可以使用constexpr关键字将构造函数定义为常量表达式
123456789class Sample{ const char* someString;public: constexpr Sample(const char* input) : someString(input) { //constructor code }};
9.4 析构函数 析构函数看起来像一个类同名的函数,但前面有一个腭化符号(~)。因此Human类的析构函数声明如下
12345class Human{public: ~Human(); //declaration of a destructor};
类中声明实现
12345678class Human{public: ~Human() { //destructor code here }};
在类声明外定义
12345678910class Human{public: ~Human(); //declaration of a destructor};Human::~Human(){ //destructor code here}
9.4.2 何时及如何使用析构函数
每当对象不在作用域内或通过delete被删除进而被销毁时,都讲调用析构函数。这使得析构函数称为重置变量以及释放动态分配的内存和其他资源的理想场所
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647//程序清单9.7 一个简单的类,它封装了字符缓冲区并通过析构函数释放它#include#includeusing namespace std;class MyString {public: MyString(const char* initString) { if (initString != NULL) { buffer = new char[strlen(initString) + 1]; strcpy(buffer, initString); } else { buffer = NULL; } } ~MyString() { cout << "Invoking destructor, clearing up" << endl; } int GetLength() { return strlen(buffer); } const char* GetString() { return buffer; }private: char* buffer;};int main(){ MyString sayHello("Hello from String Class"); cout << "String buffer in sayHello is " << sayHello.GetLength() << " characters long" << endl; cout << "Buffer contains: " << sayHello.GetString() << endl; return 0;}
9.5 复制构造函数9.5.1 浅复制及其存在的问题
复制类的对象时,将复制其指针成员,但不复制指针指向的缓冲区,其结果是两个对象指向同一个动态分配的内存。销毁其中的一个对象时,导致另一个对象存储的指针拷贝无效。这种复制被称为浅复制
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950//程序清单9.7 按值传递类的对象带来的问题#include#includeusing namespace std;class MyString {public: MyString(const char* initString) { buffer = NULL; if (initString != NULL) { buffer = new char[strlen(initString) + 1]; strcpy(buffer, initString); } } ~MyString() { cout << "Invoking destructor, clearing up" << endl; delete[] buffer; } int GetLength() { return strlen(buffer); } const char* GetString() { return buffer; }private: char* buffer;};void UseMyString(MyString str){ cout << "String buffer in MyString is " << str.GetLength() << " characters long" << endl; cout << "buffer contains: " << str.GetLength() << endl;}int main(){ MyString sayHello("Hello from String Class"); UseMyString(sayHello); return 0;}
9.5.2 使用复制构造函数确保深复制
复制构造函数是一个重载的构造函数,由编写类的程序员提供。每当对象被复制时,编译器都将调用复制构造函数
123456789class MyString{ MyString(const MyString& copySource);};MyString::MyString(const MyString& copySource){ //Copy constructor implementation code}
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465//程序清单9.9 定义一个复制构造函数,确保对动态分配的缓冲区进行深复制#include#includeusing namespace std;class MyString {public: MyString(const char* initString) { buffer = NULL; cout << "Default constructor: creating new MyString" << endl; if (initString != NULL) { buffer = new char[strlen(initString) + 1]; strcpy(buffer, initString); cout << "buffer points to: 0x" << (unsigned int*)buffer << endl; } } MyString(const MyString& copySource) { buffer = NULL; cout << "Copy constructor: copying from MyString" << endl; if (copySource.buffer != NULL){ buffer = new char[strlen(copySource.buffer) + 1]; strcpy(buffer, copySource.buffer); cout << "buffer points to:0x" << hex << (unsigned int*)buffer << endl; } } ~MyString() { cout << "Invoking destructor, clearing up" << endl; delete[] buffer; } int GetLength() { return strlen(buffer); } const char* GetString() { return buffer; }private: char* buffer;};void UseMyString(MyString str){ cout << "String buffer in MyString is " << str.GetLength() << " characters long" << endl; cout << "buffer contains: " << str.GetLength() << endl;}int main(){ MyString sayHello("Hello from String Class"); UseMyString(sayHello); return 0;}
9.5.3 有助于改善性能的移动构造函数
移动构造函数的语法
12345678MyString(MyString&& moveSource){ if(moveSource.buffer != NULL) { buffer = moveSource.buffer; moveSource.buffer = NULL }}
9.6 构造函数和析构函数的其他用途9.6.1 不允许复制的类
要禁止类对象被复制,可声明一个私有的复制构造函数。确保函数调用无法通过编译。为禁止复制,可声明一个私有的赋值运算符
1234567class President{private: President(const President&); President& operator(const President&); //... other attributes}
9.6.2 只能有一个实列的单例类
要创建单例类,关键字static必不可少
static用于类的数据成员时,该数据成员将在所有实例之间共享
static用于函数中声明的局部变量时,该变量的值将在两次调用之间保持不变
static用于成员函数时,该方法将在所有成员之间共享
123456789101112131415161718192021222324252627282930313233343536373839404142//程序清单9.10 单例类President, 它禁止复制、赋值及创建多个实例#include#includeusing namespace std;class President{private: President() {}; //private default constructor President(const President&); //private copy constructor const President& operator=(const President&);//assignment operator string name;public: static President& GetInstance() { //static objects are constructor only once static President onlyInstance; return onlyInstance; } string GetName() { return name; } void SetName(string InputName) { name = InputName; }};int main(){ President& onlyPresident = President::GetInstance(); onlyPresident.SetName("Abraham Lincoln"); cout << "The name of the President is: " << President::GetInstance().GetName() << endl; return 0;}
9.6.3 禁止在栈中实例化的类
可声明一个私有的析构函数
1234567891011121314151617181920212223242526//程序清单 9.11 数据库类monsterDB,只能使用new在自由存储区创建其对象#includeusing namespace std;class MonsterDB{private: ~MonsterDB() {};public: static void DestroyInstance(MonsterDB* pInstance) { delete pInstance; } void Dosomething(){}};int main(){ MonsterDB* myDB = new MonsterDB(); myDB->DestroyInstance; myDB->DestroyInstance(myDB); return 0;}
9.6.4 使用构造函数进行类型转换
123456789101112131415161718192021222324252627//程序清单 使用explicit避免无意间的隐式转换#includeusing namespace std;class Human{ int age;public: explicit Human(int humansAge) : age(humansAge){}};void DoSomething(Human person){ cout << "Human sent did something" << endl;}int main(){ Human kid(10); Human anotherKid = Human(11); DoSomething(kid); //Human anotherKid2 = 11; //failure: implicit conversion not OK //DoSomething(10); //implicit conversion return 0;}
9.10 声明友元 不能从外部访问类的私有数据成员和方法,但这条规则不适用于友元类和友元函数。要声明友元函数或友元类,可使用friend关键字
1234567891011121314151617181920212223242526//程序清单 9.14 使用friend关键字让外部函数DisplayAge()能够访问私有数据成员#include#includeusing namespace std;class Human{ friend void DisplayAge(const Human& person); string name; int age;public: explicit Human(string HumansName,int humansAge) :name(HumansName), age(humansAge){}};void DisplayAge(const Human& person){ cout << person.age << endl;}int main(){ Human firstMan("Adam", 25); cout << "Accessing private member age via friend function: "; DisplayAge(firstMan); return 0;}
12345678910111213141516171819202122232425262728293031//程序清单 9.14 使用friend关键字让外部l类Utility能够访问私有数据成员#include#includeusing namespace std;class Human{ friend class Utility; string name; int age;public: explicit Human(string HumansName,int humansAge) :name(HumansName), age(humansAge){}};class Utility{public: static void DisplayAge(const Human& person) { cout << person.age << endl; }};int main(){ Human firstMan("Adam", 25); cout << "Accessing private member age via friend class: "; Utility::DisplayAge(firstMan); return 0;}
9.15 作业9.15.1 测验
使用new创建类实例时,将在什么地方创建它?
答:堆区
我的类包含一个原始指针int*,它指向一个动态分配的int 数组。请问将sizeof 用于这个类的 对象时,结果是否取决于该动态数组包含的元素数?
答:否
假设有一个类,其所有成员都是私有的,且没有友元类和友元函数。请问谁能访问这些成员?
答:设置成员函数get和set方法
可以在一个类成员方法中调用另一个成员方法吗?
答:可以
构造函数适合做什么?
答:初始化成员变量
构造函数适合做什么?
答:重置变量以及释放动态分配的内存
9.15.2 练习
查错:下面的类声明有什么错误?
1234567Class Human{ int age; string name;public: Human(){}}
答:类声明关键字错误且没有声明完成后加上分号
练习1所示类的用户如何访问成员Human::age?
答:1.使用友元函数进行访问
2.设置成员函数get和set方法
**对练习1中的类进行修改,在构造函数中使用初始化列表对所有参数进行初始化。 **
1234567class Human{ int age; string name;public: Human(string humansName,int humansAge):age{humansAge},name{humansName}{}};
编写一个 Circle 类,它根据实例化时提供的半径计算面积和周长。将 Pi 包含在一个私有成员 常量中,该常量不能在类外访问。
12345678910111213141516171819class Circle{public: Circle(double r = 0) : radius{r}{} double GetArea() { return radius * radius * Pi; } double GetCircumference() { return 2 * radius * Pi; }private: double radius; const double Pi = 3.14;};
第十章10.1 继承基础10.1.2 C++派生语法
c++派生语法如下
123456789class Base{ };class Derived : access-specifier Base{ };
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051//程序清单10.1 鱼类世界呈现的一种简单的继承层次结构#includeusing namespace std;class Fish{public: bool isFreshWaterFish; void swim() { if (isFreshWaterFish) { cout << "Swims in lake" << endl; } else { cout << "Swims in sea" << endl; } }};class Tuna : public Fish{public: Tuna() { isFreshWaterFish = false; }};class Carp : public Fish{public: Carp() { isFreshWaterFish = true; }};int main(){ Carp myLunch; Tuna myDinner; cout << "About my food: " << endl; myLunch.swim(); cout << "Dinner" << endl; myDinner.swim(); return 0;}
10.1.3 访问限定符protected
需要让基类的某些属性能在派生类中的访问,但不能在继承层次外部访问,可使用protected关键字
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051//程序清单10.2 一种更好的Fish类设计,可使用关键字protected将成员属性暴露给派生类#includeusing namespace std;class Fish{protected: bool isFreshWaterFish;public: void swim() { if (isFreshWaterFish) { cout << "Swims in lake" << endl; } else { cout << "Swims in sea" << endl; } }};class Tuna : public Fish{public: Tuna() { isFreshWaterFish = false; }};class Carp : public Fish{public: Carp() { isFreshWaterFish = true; }};int main(){ Carp myLunch; Tuna myDinner; cout << "About my food: " << endl; myLunch.swim(); cout << "Dinner" << endl; myDinner.swim(); return 0;}
10.1.4 基类初始化——向基类传递参数
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647//程序清单10.3 包含初始化列表的派生类构造函数#includeusing namespace std;class Fish{protected: bool isFreshWaterFish;public: Fish(bool isFreshWater) : isFreshWaterFish(isFreshWater){} void swim() { if (isFreshWaterFish) { cout << "Swims in lake" << endl; } else { cout << "Swims in sea" << endl; } }};class Tuna : public Fish{public: Tuna() : Fish(false){}};class Carp : public Fish{public: Carp() : Fish(true){}};int main(){ Carp myLunch; Tuna myDinner; cout << "About my food: " << endl; myLunch.swim(); cout << "Dinner" << endl; myDinner.swim(); return 0;}
10.1.5 在派生类中覆盖基类的方法
如果派生类实现了基类的函数,且返回值和特征标相同,相当于覆盖了基类的这个方法,如果使用派生类去调用这个方法,将不在调用基类的方法,而是自己的方法
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657//程序清单10.4 派生类Tuna和Carp覆盖了基类fish的方法Swim#includeusing namespace std;class Fish{private: bool isFreshWaterFish;public: Fish(bool isFreshWater) : isFreshWaterFish(isFreshWater){} void swim() { if (isFreshWaterFish) { cout << "Swims in lake" << endl; } else { cout << "Swims in sea" << endl; } }};class Tuna : public Fish{public: Tuna() : Fish(false){} void Swim() { cout << "Tuna swims real fast" << endl; }};class Carp : public Fish{public: Carp() : Fish(true){} void Swim() { cout << "Carp siwms real slow" << endl; }};int main(){ Carp myLunch; Tuna myDinner; cout << "About my food: " << endl; myLunch.swim(); cout << "Dinner" << endl; myDinner.swim(); return 0;}
10.1.6 调用基类中被覆盖的方法和方法
使用作用域解析运算符(::)
myDinner.Fish::Swim();
10.2 私有继承 私有继承意味着派生类的实例中,基类的所有公有成员和方法都是私有的——不能从外部访问,即便是Base类的公有成员和方法,也只能被Derived类使用,而无法通过Derived实例来使用它们
1234567891011121314151617181920212223242526272829303132333435363738394041//程序清单10.8 Car类以私有方式继承Motor类#includeusing namespace std;class Motor{public: void SwithcIgnition() { cout << "Ignition ON" << endl; } void PumpFuel() { cout << "Fuel in cylinders" << endl; } void FireCylinders() { cout << "Vroooom" << endl; }};class Car : private Motor{public: void Move() { SwithcIgnition(); PumpFuel(); FireCylinders(); }};int main(){ Car myDreamCar; myDreamCar.Move(); return 0;}
10.3 保护继承 保护继承和私有继承的类似之处
它也表示has-a关系;
它也让派生类能够访问基类的所用公有和保护对象
在继承层次结构外面,也不能通过派生类实例访问基类的公有成员
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354//情绪清单10.9 RaceCar类以保护方式继承了Car类,而Car以保护方式继承了Motor类#includeusing namespace std;class Motor{public: void SwithcIgnition() { cout << "Ignition ON" << endl; } void PumpFuel() { cout << "Fuel in cylinders" << endl; } void FireCylinders() { cout << "Vroooom" << endl; }};class Car : protected Motor{public: void Move() { SwithcIgnition(); PumpFuel(); FireCylinders(); }};class RaceCar : protected Car{public: void Move() { SwithcIgnition(); //RaceCar Has access to members of PumpFuel(); //base Motor due to "protected" inheritance FireCylinders(); //between RaceCar & Car, Car & Motor FireCylinders(); FireCylinders(); }};int main(){ RaceCar myDreamCar; myDreamCar.Move(); return 0;}
10.9 作业10.9.1 测试
**我希望基类的某些成员可在派生类中访问,但不能在继承层次结构外访问,该使用哪种访问限定符? **
答:protected
如果一个函数接受一个基类对象作为参数,而我将一个派生类对象作为实参按值传递给它,结果将如何?
答: 会导致切除问题
**该使用私有继承还是组合? **
答:使用组合,这样可以提高设计的灵活性
**在继承层次结构中,关键字using有何用途? **
答:避免隐藏基类方法
Derived 类以私有方式继承了 Base 类,而 SubDerived 类以公有方式继承了 Derived 类。请问 SubDerived 类能访问Base 类的公有成员吗?
答:不能
10.9.2 实践
**创建程序清单10.10所示的Platypus对象时,将以什么样的顺序调用构造函数? **
答:
构造函数:Mammal、Bird、Reptile、Platypus
析构函数:Platypus、Reptile、Bird、Mammal
**使用代码说明Polygon、Triangle和Shape类之间的关系。 **
答:
123class Shape {};class Polygon : public Shape {};class Triangle : public Polygon {};
**D2 类继承了D1类,而D1类继承了Base类。要禁止D2访问Base的公有方法,应使用哪种 访问限定符?在什么地方使用? **
答:应该在D1类继承Base类时使用private关键字
**下面的代码表示哪种继承关系? **
1234class Derived: Base { // ... Derived members };
答:私有继承
**查错:下述代码有何问题? **
12345678class Derived: public Base { // ... Derived members }; void SomeFunc (Base value) { // }
答: 会导致切除问题
第十一章11.1 多态基础11.1.1 为何需要多态行为
12345678910111213141516171819202122232425262728293031323334//程序清单11.1 将Tuna实例传递给Fish参数,并通过参数调用方法#includeusing namespace std;class Fish{public: void Swim() { cout << "Fish swims!" << endl; }};class Tuna : public Fish{public: void Swim() { cout << "Tuna swims" << endl; }};void MakeFishSwim(Fish& inputFish){ inputFish.Swim();}int main(){ Tuna myDinner; myDinner.Swim(); MakeFishSwim(myDinner); return 0;}
11.1.2 使用虚函数实现多态行为
通过使用virtual关键字,可确保编译器调用覆盖版本
123456789class Base{ virtual ReturnType FunctionName(Parameter List);};class Derived{ TeturnType FunctionName(Parameter List);};
123456789101112131415161718192021222324252627282930313233343536373839404142434445//程序清单 11.2 将Fish::Swim()声明为虚函数带来的影响#includeusing namespace std;class Fish{public: virtual void Swim() { cout << "Fish swims!" << endl; }};class Tuna : public Fish{public: void Swim() { cout << "Tuna swims" << endl; }};class Carp : public Fish{public: void Siwm() { cout << "Carp swims!" << endl; }};void MakeFishSwim(Fish& inputFish){ inputFish.Swim();}int main(){ Tuna myDinner; Carp myLunch; MakeFishSwim(myDinner); MakeFishSwim(myLunch); return 0;}
11.1.3 为何需要虚析构函数
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849//程序清单 11.3 在函数中通过基类指针调用运算符delete#includeusing namespace std;class Fish{public: Fish() { cout << "Consturcted Fish" << endl; } ~Fish() { cout << "Destroyed Fish" << endl; }};class Tuna : public Fish{public: Tuna() { cout << "Consturcted Tuna" << endl; } ~Tuna() { cout << "Destroyed Tuna" << endl; }};void DeleteFishMemory(Fish *pFish){ delete pFish;}int main(){ cout << "Allocating a Tuna on The Free store:" << endl; Tuna* pTuna = new Tuna; cout << "Deleteing the Tuna: " << endl; DeleteFishMemory(pTuna); cout << "Instantiating a Tuna on the stack:" << endl; Tuna myDinner; cout << "Automatic destruction as it goes out of scope: " << endl; return 0;}
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950//程序清单11.4 将析构函数声明为虚函数,确保通过基类指针调用delete时,将调用派生类的析构函数//程序清单 11.3 在函数中通过基类指针调用运算符delete#includeusing namespace std;class Fish{public: Fish() { cout << "Consturcted Fish" << endl; } virtual ~Fish() { cout << "Destroyed Fish" << endl; }};class Tuna : public Fish{public: Tuna() { cout << "Consturcted Tuna" << endl; } ~Tuna() { cout << "Destroyed Tuna" << endl; }};void DeleteFishMemory(Fish *pFish){ delete pFish;}int main(){ cout << "Allocating a Tuna on The Free store:" << endl; Tuna* pTuna = new Tuna; cout << "Deleteing the Tuna: " << endl; DeleteFishMemory(pTuna); cout << "Instantiating a Tuna on the stack:" << endl; Tuna myDinner; cout << "Automatic destruction as it goes out of scope: " << endl; return 0;}
11.1.4 虚函数的工作原理——理解虚函数表
1234567891011121314151617181920212223242526//程序清单11.5 对两个相同的类(一个包含虚函数,另一个不包含)进行比较,证明确实存在隐藏的虚函数指针表#includeusing namespace std;class SimpleClass{ int a, b;public: void DoSomething(){}};class Base{ int a, b;public: virtual void DoSomething() {}};int main(){ cout << "sizeof(simpleClass) = " << sizeof(SimpleClass) << endl; cout << "sizeof(Base) = " << sizeof(Base) << endl; return 0;}
11.1.5 抽象基类和纯虚函数
不能实例化的基类被称为抽象基类,这样的基类只有一个用途,那就是从它派生出其他类,要创建抽象基类,可声明纯虚函数
1234567891011121314151617class AbstractBase{public: virtual void DoSomething() = 0;};//该声明告诉编译器,AbstactBase的派生类必须实现方法DoSomething()class Derived : public AbstractBase{public: void DoSomething() { cout << "Implemented virtual function" << endl; }};
12345678910111213141516171819202122232425262728293031323334353637383940414243//程序清单11.6 Fish和Tuan和Crap的抽象基类#includeusing namespace std;class Fish{public: virtual void Swim() = 0;};class Tuna : public Fish{public: void Swim() { cout << "Tuna swims fast in the sea! " << endl; }};class Carp : public Fish{public: void Swim() { cout << "Carp swims slow in the lake!" << endl; }};void MakeFishSwim(Fish& inputFish){ inputFish.Swim();}int main(){ Carp myLunch; Tuna myDinner; MakeFishSwim(myLunch); MakeFishSwim(myDinner); return 0;}
11.2 使用虚继承解决菱形问题1234567891011121314151617181920212223242526272829303132333435//程序清单11.8 在继承层次结构中使用virtual关键字,将基类Animal的实例个数限定为1#includeusing namespace std;class Animal{public: Animal() { cout << "Animal constructor" << endl; } int age;};class Mammal : public virtual Animal {};class Bird : public virtual Animal {};class Reptile : public virtual Animal{};class Platypus final : public Mammal, public Bird, public Reptile{public: Platypus() { cout << "Platpus constructor" << endl; }};int main(){ Platypus duckBilledP; duckBilledP.age = 25; return 0;}
11.3 表面覆盖意图的限定符override override会让编译器做如下检查
基类函数是否是虚函数
基类中相应虚函数的特征标是否于派生类中被声明为override的函数完全相同
11.4 使用final来禁止覆盖函数 被声明为final的类不能用作基类,同样,对于被声明为final的虚函数,不能在派生类中进行覆盖
11.5 可将复制构造函数声明为虚函数吗 不能,因为在基类方法声明中使用了virtual,表示它将被派生的类实现覆盖,这种多态的行为是在运行阶段实现的。而构造函数只能创建固定类型的对象,不具备多态性
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677//程序清单11.9 Tuna和Carp包含Clone函数,它们模拟了虚复制构造函数#includeusing namespace std;class Fish{public: virtual Fish* Clone() = 0; virtual void Swim() = 0; virtual ~Fish() {};};class Tuna : public Fish{public: Fish* Clone() override { return new Tuna (*this); } void Swim() override final { cout << "Tuan swims fast in the sea" << endl; }};class BluefinTuna final : public Tuna{public: Fish* Clone() { return new BluefinTuna(*this); } //Cannot override Tuna::Swim as it is "final" in Tuna};class Carp final : public Fish{ Fish* Clone() override { return new Carp(*this); } void Swim() override final { cout << "Carp swims slow in the lake" << endl; }};int main(){ const int ARRAY_SIZE = 4; Fish* myFishes[ARRAY_SIZE] = { NULL }; myFishes[0] = new Tuna(); myFishes[1] = new Carp(); myFishes[2] = new BluefinTuna(); myFishes[3] = new Carp(); Fish* myNewFishers[ARRAY_SIZE]; for (int i = 0; i < ARRAY_SIZE; i++) { myNewFishers[i] = myFishes[i]->Clone(); } for (int i = 0; i < ARRAY_SIZE; i++) { myNewFishers[i]->Swim(); } for (int i = 0; i < ARRAY_SIZE; i++) { delete myFishes[i]; delete myNewFishers[i]; } return 0;}
11.8 作业11.8 测验
**假设您要模拟形状—圆和三角形,并要求每个形成都必须实现函数Area( )和Print( )。您该怎么办? **
答:把Area和Print声明成纯虚函数
编译器为每个类都创建虚函数表吗?
答:只有在使用了virtual关键字后才会创建
我编写了一个Fish类,它有两个公有方法、一个纯虚函数和几个成员属性。这个类是抽象基类吗?
答:是
11.8.2 练习
**创建一个继承层次结构,实现测验1中的Circle和Triangle类。 **
12345678910111213141516171819202122class Shape{public: virtual double Area() = 0; virtual void Print() = 0; virtual ~Shape() {};};class Circle : public Shape{public: double Area() override{} void Print() override{}};class Triangle : public Shape{public: double Area() override {} void Print() override {}};
**差错:下面的代码有何问题? **
12345678910111213class Vehicle{public: Vehicle(){} ~Vehicle(){}};class Car : public Vehicle{public: Car(){} Car(){} };
答:基类 Vehicle 的析构函数不是虚函数。这可能导致内存泄漏或资源未正确释放的风险。
**给定练习2所示的(错误)代码,像下面这样创建并销毁Car实例时,将按什么样的顺序执行 构造函数和析构函数? **
Vehicle* pMyRacer = new Car;
delete pMyRacer;
答:先调用Vehicle的构造函数,在调用Car的构造函数,delete时在调用Vehicle的析构函数
第十二章12.2 单目运算符 12.2.2 单目递增与单目递减运算符
在类中声明单目前缀递增运算符语法
12345Date& operator++(){ //operator implementation code return *this;}
在类中声明单目后缀递增运算符语法
123456789Date& operator++(int){ //store a copy of the current state of the object, before incrementing day Date copy(*this); //increment implementation code //return state before increment(because,postfix) return *this;}
前缀和后缀的语法相似,只是将声明中的++替换成了–
123456789101112131415161718192021222324252627282930313233343536//程序清单12.1 一个处理日、月、年的日历类,可对日期执行递增和递减操作#includeusing namespace std;class Date{public: Date(int inMonth,int inDay,int inYear) : month{inMonth},day{inDay},year{inYear}{} Date& operator++() { ++day; return *this; } Date& operator--() { --day; return *this; } void DisplayDate() { cout << month << " / " << day << " / " << year << endl; }private: int day, month, year;};int main(){ Date holiday(12, 25, 2016); return 0;}
12.2.3 转换运算符
123456789101112131415161718192021222324252627282930313233//程序清单12.2 在Date类中实现转换运算符的简单实现#include#include#includeusing namespace std;class Date{public: Date(int inMonth,int inDay,int inYear) : month{inMonth},day{inDay},year{inYear}{} explicit operator const char* () { ostringstream formattedDate; formattedDate << month << " / " << day << " / " << year; dateInString = formattedDate.str(); return dateInString.c_str(); }private: int day, month, year; string dateInString;};int main(){ Date holiday(12, 25, 2016); cout << "Holiday is on:" << holiday << endl; return 0;}
12.2.4 解除引用运算符(*)和成员选择运算符(->)
123456789101112131415161718192021222324252627282930313233343536#include#include#includeusing namespace std;class Date{public: Date(int inMonth,int inDay,int inYear) : month{inMonth},day{inDay},year{inYear}{} void DisplayDate() { cout << month << "/ " << day << " / " << year << endl; }private: int day, month, year; string dateInString;};int main(){ unique_ptr smartIntPtr(new int); *smartIntPtr = 42; cout << "Integer value is: " << *smartIntPtr << endl; unique_ptr smartHoliday(new Date(12, 25, 2016)); cout << "The new instance of date contains: " << endl; smartHoliday->DisplayDate(); return 0;}
12.3 双目运算符 以全局函数或静态成员的方式实现的双目运算符
return_type operator_type(parameter1,parameter2);
以类成员的方式实现的双目运算符
return_type operator_type(parameter);
12.3.2 双目加法和减法运算符
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849//程序清单12.4 实现了双目加减法日历类#includeusing namespace std;class Date{public: Date(int inMonth,int inDay,int inYear) : month{inMonth},day{inDay},year{inYear}{} void DisplayDate() { cout << month << "/ " << day << " / " << year << endl; } Date operator+(int daysToAdd) { Date newDate(month, day + daysToAdd, year); return newDate; } Date operator-(int daysToAdd) { Date newDate(month, day - daysToAdd, year); return newDate; }private: int day, month, year; string dateInString;};int main(){ Date Holiday(12, 25, 2016); cout << "Holiday on: "; Holiday.DisplayDate(); Date PreviousHoliday(Holiday - 19); cout << "Previous holiday on: "; PreviousHoliday.DisplayDate(); Date NextHoliday(Holiday + 6); cout << "Next Holiday on: "; NextHoliday.DisplayDate(); return 0;}
13.3.3 实现运算符+=与-=
12345678910111213141516171819202122232425262728293031323334353637383940414243444546//程序清单12.5 定义运算符+=和-=,以便将日历向前或向后翻整型输入参数指定天数#includeusing namespace std;class Date{public: Date(int inMonth,int inDay,int inYear) : month{inMonth},day{inDay},year{inYear}{} void DisplayDate() { cout << month << "/ " << day << " / " << year << endl; } void operator+=(int daysToAdd) { day += daysToAdd; } void operator-=(int daysToAdd) { day -= daysToAdd; }private: int day, month, year; string dateInString;};int main(){ Date Holiday(12, 25, 2016); cout << "Holiday is on: "; Holiday.DisplayDate(); cout << "holiday -= 9 gives: "; Holiday -= 9; Holiday.DisplayDate(); cout << "holiday += 25 gives: "; Holiday += 25; Holiday.DisplayDate(); return 0;}
12.3.4 重载等于运算符(==)和不等于运算符(!=)
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758//程序清单12.6 运算符==和!=#includeusing namespace std;class Date{public: Date(int inMonth,int inDay,int inYear) : month{inMonth},day{inDay},year{inYear}{} void DisplayDate() { cout << month << "/ " << day << " / " << year << endl; } bool operator ==(const Date& compareTo) { return ( day == compareTo.day && month == compareTo.month && year == compareTo.year ); } bool operator !=(const Date& compareTo) { return !(*this == compareTo); }private: int day, month, year; string dateInString;};int main(){ Date Holiday1(12, 25, 2016); Date Holiday2(12, 31, 2016); cout << "holiday 1 is: "; Holiday1.DisplayDate(); cout << "holiday 2 is: "; Holiday2.DisplayDate(); if (Holiday1 == Holiday2) { cout << "Equality operator: The tow are on the same day" << endl; } else { cout << "Equality operator: The tow are on the different day" << endl; } if (Holiday1 != Holiday2) { cout << "Equality operator: The tow are on the different day" << endl; } else { cout << "Equality operator: The tow are on the same day" << endl; } return 0;}
12.3.6 重载复制赋值运算符(=)
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960//程序清单12.8 对程序清单9.9的MyString类进行改进,添加了赋值运算符#include#includeusing namespace std;class MyString{public: MyString(const char* initialInput) { if (initialInput != NULL) { buffer = new char[strlen(initialInput) + 1]; strcpy(buffer, initialInput); } else { buffer = NULL; } } MyString& operator=(const MyString& copySource) { if ((this != ©Source) && (copySource.buffer != NULL)) { if (buffer) { delete buffer; } buffer = new char[strlen(copySource.buffer) + 1]; strcpy(buffer, copySource.buffer); } return *this; } operator const char* () { return buffer; } ~MyString() { delete[] buffer; }private: char* buffer;};int main(){ MyString string1("hello"); MyString string2(" World"); cout << "Before assignment: " << endl; cout << string1 << string2 << endl; string2 = string1; cout << "After assignment string2 = string1" << endl; cout << string1 << string2 << endl; return 0;}
12.3.7 下标运算符
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697//程序清单12.9 在MyString类中实现下标运算符,以便随机访问MyString::buffer包含的字符#include#include#includeusing namespace std;class MyString{public: MyString(const char* initialInput) { if (initialInput != NULL) { buffer = new char[strlen(initialInput) + 1]; strcpy(buffer, initialInput); } else { buffer = NULL; } } MyString(const MyString& copySource) { if (copySource.buffer) { buffer = new char[strlen(copySource.buffer) + 1]; strcpy(buffer, copySource.buffer); } else { buffer = NULL; } } MyString& operator=(const MyString& copySource) { if ((this != ©Source) && (copySource.buffer != NULL)) { if (buffer) { delete buffer; } buffer = new char[strlen(copySource.buffer) + 1]; strcpy(buffer, copySource.buffer); } return *this; } const char& operator[](int index) const { if (index < GetLength()) { return buffer[index]; } } int GetLength() const { return strlen(buffer); } operator const char* () { return buffer; } ~MyString() { if(buffer != NULL) delete[] buffer; }private: char* buffer; MyString() {};};int main(){ cout << "Tupe a statement: "; string strInput; getline(cin, strInput); MyString youSaid(strInput.c_str()); cout << "Using operator[] for displaying your input: " << endl; for (int i = 0; i < youSaid.GetLength(); i++) { cout << youSaid[i] << " "; } cout << endl; cout << "Enter index 0 - " << youSaid.GetLength() - 1 << ": "; int index = 0; cin >> index; cout << "Input character at zero-base position: " << index; cout << " is:" << youSaid[index] << endl; return 0;}
12.4 函数运算符operator() operator()让对象像函数,被称为函数运算符。函数运算符用于标准模板库(stl),通常stl算法中,其用途包括决策。根据使用的操作数数量,像这样的函数被称为单目谓词和双目谓词
123456789101112131415161718192021//程序清单12.10 一个使用operator()实现的函数对象#include#includeusing namespace std;class Display{public: void operator()(string input) const { cout << input << endl; }};int main(){ Display displayFuncObj; displayFuncObj("Display this string!"); return 0;}
12.5 用于高性能编程的移动构造函数和移动赋值运算符 移动构造函数和移动赋值函数运算符乃性能优化功能,属于C++11标准的一部分,皆在避免不必要的临时值(当前语句执行完毕后就不再存在的右值)。对于那些管理动态分配的类,如动态数组类和字符串类
12.5.2 声明移动构造函数和移动赋值运算符
移动构造函数的声明语法如下
12345678910111213141516171819202122232425class Sample{public: Sample(Sample&& moveSource) // Move constructor, note && { ptrResource = moveSource.ptrResource; moveSource.ptrResource = NULL; } Sample& operator=(Sample&& moveSource) //move assignment operator, note && { if (this != &moveSource) { delete[] ptrResource; ptrResource = moveSource.ptrResource; moveSource.ptrResource = NULL; } } Sample(); // default constructor Sample(const Sample& copySource); //copy constructor Sample& operator&(const Sample& copySource); //copy assignmentprivate: Type* ptrResource;};
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125//程序清单 12.11 除复制构造函数和复制赋值运算符除外,还包含移动构造函数和移动赋值运算符的MyString类#include#include#includeusing namespace std;class MyString{public: MyString(const char* initialInput) { if (initialInput != NULL) { buffer = new char[strlen(initialInput) + 1]; strcpy(buffer, initialInput); } else { buffer = NULL; } } MyString(const MyString& copySource) { if (copySource.buffer) { buffer = new char[strlen(copySource.buffer) + 1]; strcpy(buffer, copySource.buffer); } else { buffer = NULL; } } MyString(MyString&& moveSrc) { cout << "Move constructor moves: " << moveSrc.buffer << endl; if (moveSrc.buffer != NULL) { buffer = moveSrc.buffer; moveSrc.buffer = NULL; } } MyString& operator=(const MyString& copySource) { if ((this != ©Source) && (copySource.buffer != NULL)) { if (buffer) { delete buffer; } buffer = new char[strlen(copySource.buffer) + 1]; strcpy(buffer, copySource.buffer); } return *this; } MyString& operator=(MyString&& moveSrc) { cout << "Move assignment op. moves: " << moveSrc.buffer << endl; if (moveSrc.buffer != NULL) { delete buffer; buffer = moveSrc.buffer; moveSrc.buffer = NULL; } return *this; } const char& operator[](int index) const { if (index < GetLength()) { return buffer[index]; } } int GetLength() const { return strlen(buffer); } operator const char* () { return buffer; } MyString operator+(const MyString& addThis) { cout << "operator + called: " << endl; MyString newStr; if (addThis.buffer != NULL) { newStr.buffer = new char[GetLength() + addThis.GetLength() + 1]; strcpy(newStr.buffer, buffer); strcpy(newStr.buffer, addThis.buffer); } return newStr; } ~MyString() { if (buffer != NULL) delete[] buffer; }private: char* buffer; MyString() : buffer{NULL} { cout << "Default constructor called" << endl; }};int main(){ MyString Hello("Hello"); MyString World("World"); MyString CPP(" of C++"); MyString sayHelloAgain("overwrite this"); sayHelloAgain = Hello + World + CPP; return 0;}
12.6 用户自定义的字面值 要自定义字面量,可像下面这样定义operator””
1234ReturnType opeator "" YourLiteral(ValueType value){ //conversion code here}
123456789101112131415161718192021222324252627282930//程序清单12.12 将华氏温度和摄氏温度转换为开尔文温度#includeusing namespace std;struct Temperature{ double Kelvin; Temperature(long double kelvin) : Kelvin(kelvin){}};Temperature operator"" _C(long double celcius){ return Temperature(celcius + 273);}Temperature operator"" _F(long double fahrenheit){ return Temperature((fahrenheit + 459.67) * 5 / 9);}int main(){ Temperature k1 = 31.73_F; Temperature k2 = 0.0_C; cout << "k1 is " << k1.Kelvin << " Kelvin" << endl; cout << "k2 is " << k1.Kelvin << " Kelvin" << endl; return 0;}
12.7 作业12.10.1 测验
可以像下面这样,编写两个版本的下标运算符,一个的返回类型为const,另一个为非const吗?
12const Type& operator[](int index); Type& operator[](int index); // is this OK?
答:不可以,C++不允许两个函数的名称相同,返回类型值不同。
**可以将复制构造函数或复制赋值运算符声明为私有的吗? **
答:可以
给Date类实现移动构造函数和移动赋值运算符有意义吗?
答:没有意义,只有在动态分配资源时才会导致复制构造函数和复制赋值函数进行不必要的内存分配和释放
12.10.2 练习
为Date类编写一个转换运算符,将其存储的日期转换为整数。
1234explicit operator int(){ return year*10000 + month * 100 + day;}
*DynIntegers 类以 int私有成员的方式封装了一个动态分配的数组,请给它编写移动构造函数和 移动赋值运算符。 **
12345678910111213141516171819202122232425262728class DynIntegers{private: int* array;public: DynIntegers(DynIntegers&& moveArray) { array = moveArray.array; moveArray.array = NULL; } DynIntegers& operator=(DynIntegers&& moveArray) { if (moveArray.array) { delete[] array; array = moveArray.array; moveArray.array = NULL; } return (*this); } ~DynIntegers() { if (array) { delete[] array; } }};
第十三章 类型转换运算符13.3 C++类型转换运算符 4个C++类型转换运算符如下
static_cast
dynamic_cast
reinterpret_cast
const_cast
它们的使用语法相同
1destination_type result = cast_operator(object_to_cast);
13.3.1 使用static_cast
static_cast用于相关类型的指针之间进行转换,还可以显示的执行标准数据类型的类型转换——这种转换原本将自动或隐式地进行。用于指针时,static_cast实现了基本地编译阶段检查,确保指针转换为相关类型。使用static_cast可将指针向上转换为基类类型,也可以向下转换为派生类型。
12Base* objBase = new Derived;Derived objDer = static_cast(objBase);
13.3.2 使用dynamic_cast和运行阶段类型识别
顾名思义,与静态类型转换相反,动态类型转换在运行阶段(即应用程序运行时)执行类型转换。可检查dynamic_cast操作的结果,以判断类型转换是否成功
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273//程序清单13.1 使用动态转换判断Fish指针指向的是否时Tuna对象或Carp对象#includeusing namespace std;class Fish{public: virtual void Swim() { cout << "Fish 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 became 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 DetectFishType(Fish* objFish){ Tuna* objTuna = dynamic_cast(objFish); if (objTuna) { cout << "Detected Tuna. Making Tuna dinner: " << endl; objTuna->BecomeDinner(); } Carp* objCarp = dynamic_cast(objFish); if (objCarp) { cout << "Detected Carp.Making carp talk: " << endl; objCarp->Talk(); } cout << "Verifying type using virtual Fish::Swim: " << endl; objFish->Swim();}int main(){ Carp myLunch; Tuna myDinner; DetectFishType(&myDinner); cout << endl; DetectFishType(&myLunch); return 0;}
13.3.3 使用reinterpret_cast
reinterpret_cast时C++中与C风格类型转换最接近的类型转换运算符,它让程序员能够将一种对象类型转换为另一种,不管它们是否相关。
12Base* objBase = new Base();Unrelated* notRelated = reinterpret_cast(objBase);
13.3.4 使用const_cast
const_cast让程序员能够关闭对象的访问修饰符const
13.7 作业13.7.1 测验
您有一个基类对象指针objBase,要确定它指向的是否是Derived1或Derived2对象,应使用哪 种类型转换?
答:dynamic_cast
**假设您有一个指向对象的const引用,并试图通过它调用一个您编写的公有成员函数,但编译 器不允许您这样做,因为该函数不是const成员。您将修改这个函数还是使用const_cast? **
答:修改函数,除非万不得已,否则要使用const_cast
判断对错:仅在不能使用static_cast时才应使用reinterpret_cast,这种类型转换是必须和安全的。
答:对
判断对错:优秀的编译器将自动执行很多基于static_cast的类型转换,尤其是简单数据类型之 间的转换。
答:对
13.7.2 练习
查错:下述代码有何问题?
1234void DoSomething(Base* objBase) { Derived* objDer = dynamic_cast (objBase); objDer->DerivedClassMethod(); }
答:没有判断是否转换成功,就对对象进行了使用
假设有一个Fish指针(objFish),它指向一个Tuna对象,要让一个Tuna指针指向该指针指向的Tuna对象,应使用哪种类型转换?请使用代码证明您的 看法。
12Fish* objFish = new Tuna;Tuna* pTuna = objFish;
123//使用static_castFish* objFish = new Tuna;Tuna* pTuna = static_cast(objFish);
第十四章13.4 模板简介14.4.1 模板声明语法
模板声明以关键字template打头,接下来是类型参数列表。
12template template function / class declaration
关键字template标志着模板声明的开始,接下来是模板参数列表。该参数列表包含关键字typename,它定义了模板参数objType,objType是一个占位符。针对对象实例化模板时,将使用对象的类型替换它。
14.4.2 各种类型的模板声明
模板声明可以是:
函数的声明或实现
类的定义或声明
类模板的成员函数或成员类的声明或定义
类模板的静态数据成员的定义
嵌套在类模板中的类的静态数据成员的定义
类或模板类的成员模板的定义
14.4.3 模板函数
12345678910111213141516171819202122232425262728293031//程序清单14.3 模板函数GetMax,它返回两个参数中较大的一个#include#includeusing namespace std;templateconst Type& GetMax(const Type& value1, const Type& value2){ return value1 > value2 ? value1 : value2;}templatevoid DisplayComparison(const Type& value1, const Type& value2){ cout << "GetMax(" << value1 << ", " << value2 << ") = "; cout << GetMax(value1, value2) << endl;}int main(){ int num1 = -101, num2 = 2011; DisplayComparison(num1, num2); double d1 = 3.14, d2 = 3.1416; DisplayComparison(d1, d2); string name1("Jack"), name2("John"); DisplayComparison(name1, name2); return 0;}
14.4.8 一个模板示例
1234567891011121314151617181920212223242526272829303132333435363738394041//程序清单14.4 包含两个成员属性的模板类#includeusing namespace std;templateclass HoldPair{public: HoldPair(const T1& val1, const T2& val2) : value1(val1), value2(val2) {} const T1& GetFirstValue() const { return value1; } const T2& GetSecondValue() const { return value2; }private: T1 value1; T2 value2;};int main(){ HoldPair<> pairIntDbl(300,10.09); HoldPair pairShortStr(25, "learn templates, love c++"); cout << "The first object contains -" << endl; cout << "Value 1:" << pairIntDbl.GetFirstValue() << endl; cout << "Value 2:" << pairIntDbl.GetSecondValue() << endl; cout << "The second object contains -" << endl; cout << "Value 1:" << pairShortStr.GetFirstValue() << endl; cout << "Value 2:" << pairShortStr.GetSecondValue() << endl; return 0;}
14.4.9 模板的实例化和具体化
实例化:使用一个或多个模板参数来创建特定的类型
具体化:为特定的类型指定行为
123456789101112131415161718192021222324252627282930313233343536373839//程序清单14.5 模板具体化#includeusing namespace std;templateclass HoldPair{public: HoldPair(const T1& val1, const T2& val2) : value1(val1), value2(val2) {} const T1& GetFirstValue() const; const T2& GetSecondValue() const;private: T1 value1; T2 value2;};template<> class HoldPair{public: HoldPair(const int& val1, const int& val2) : value1(val1), value2(val2) {} const int& GetFirstValue() const { cout << "Returning integer " << value1 << endl; return value1; }private: int value1; int value2; string strFun;};int main(){ HoldPair pairInInt(223, 333); pairInInt.GetFirstValue(); return 0;}
14.4.10
模板类的静态成员,由特定的具体化的所用实例共享
12345678910111213141516171819202122232425262728//程序清单14.6 静态成员对模板类和实例的影响#includeusing namespace std;templateclass TestStatic{public: static int staticVal;};template int TestStatic::staticVal;int main(){ TestStatic intInstance; cout << "setting staticVzl for intInstance to 2011" << endl; intInstance.staticVal = 2011; TestStatic dblInstance; cout << "setting staticVal for dblInstance to 1011" << endl; dblInstance.staticVal = 1011; cout << "intInstance.staticVal = " << intInstance.staticVal << endl; cout << "dblInstance.staticVal = " << dblInstance.staticVal << endl; return 0;}
对于模板类的静态成员,通用初始化语法如下:
12template StaticTypeClassName::StaticVarName;
14.4.11 参数数量可变的模板
123456789101112131415161718192021222324252627282930//程序清单14.7 使用参数数量可变的模板的函数#include#includeusing namespace std;templatevoid Sum(Res& result, ValType& val){ result = result + val;}templatevoid Sum(Res& result, First val1, Rest... valN){ result = result + val1; return Sum(result, valN...);}int main(){ double dResult = 0; Sum(dResult, 3.14, 4.56, 1.1111); cout << "dResult = " << dResult << endl; string strResult; Sum(strResult, "Hello ", "World"); cout << "strResult = " << strResult << endl; return 0;}
1234567891011121314151617181920212223242526272829303132//程序请单14.8 实例化并使用std::tuple#include#include#includeusing namespace std;templatevoid DisplayTupleInfo(tupleType& tup){ const int numMembers = tuple_size::value; cout << "Num element in tuple: " << numMembers << endl; cout << "Last element value: " << get(tup) << endl;}int main(){ tuple tup1(make_tuple(101, 's', "Hello Tuple")); DisplayTupleInfo(tup1); auto tup2(make_tuple(3.14, false)); DisplayTupleInfo(tup2); auto concatTup(tuple_cat(tup2, tup1)); DisplayTupleInfo(concatTup); double pi; string sentence; tie(pi, ignore, ignore, ignore, sentence) = concatTup; cout << "Unpacked! Pi " << pi << " and \"" << sentence << "\"" << endl; return 0;}
14.4.12 使用static_assert执行编译阶段检查
static_assert是C++11新增的一项功能,让您能够在不满足指定条件时禁止编译
1static_assert(expression being validated,"Error message when check fails");
123456789101112131415161718192021//程序清单14.9 一个挑剔的模板类,它使用static_assert在您针对int类型实例化时发出抗议#include#include#includeusing namespace std;templateclass EverythingButInt{public: EverythingButInt() { static_assert(sizeof(int) != sizeof(T), "No int please!"); }};int main(){ EverythingButInt test; return 0;}
14.7 作业14.7.1 测验
什么是多次包含防范(inclusion guard)?
答:这是一个预编译结构,防止重复包含同文件
如果使用参数20调用下面的宏,结果将是多少?
#define SPLIT(x) x / 5
答:4
**如果用10+10调用测验2中的SPLIT宏,结果将是多少? **
答:12
**如何修改SPLIT宏以免得到错误的结果? **
答:#define SPLIT(x) ((x) / 5)
14.7.2 练习
**编写一个将两个数相乘的宏。 **
#define MULI(x,y) ((x) * (y))
**编写一个模板,实现测验4中宏的功能。 **
123456templateT Split(const T& input){ return (input / 5);}
**实现模板函数swap,它交换两个变量的值。 **
1234567templateT Swap(T& input1, T& input2){ T tmp = input1; input1 = input2; input2 = tmp;}
**查错:您将如何改进下面的宏使其计算输入值的1/4? **
#define QUARTER(x) (x / 4)
#define QUARTER(x) ((x) / 4)
编写一个简单的模板类,它存储两个数组,数组的类型是通过模板参数列表指定的。数组包含 10 个元素,模板类应包含存取器函数,可用于操作数组元素。
12345678910templateclass Arrays{public: arr1Type& GetElem1(int index) { return arr1[index]; } arr1Type& GetElem2(int index) { return arr2[index]; }private: arr1Type arr1[10]; arr2Type arr2[10];};
编写模板函数Display(),它可使用不同数量和类型的参数调用,并将所有的参数都显示出来
1234567891011void Display(){}templatevoid Display(T input,args... begin){ cout << input << endl; Display(begin...);}
第十五章15.5选择正确的容器
15.9 作业测验
**要包含一个对象数组,并允许在开头和末尾插入对象,应使用哪种容器? **
答:deque
**要存储元素以进行快速查找,应选择哪种容器? **
答:应该选择关联式容器
**要使用std:set 存储元素,并根据除元素值外的其他条件进行存储和查找,可能吗? **
答:可能
**哪项STL特性将算法和容器联系起来? **
答:迭代器
**如果应用程序要移植到不同的平台,并使用不同的C++编译器进行编译,是否可选择使用容器 hash_set? **
答:hash_set并非C++标准容器,因此不应在有移植性需求的程序上使用它,应该使用map
第十六章16.1 为何需要字符串操作类
减少了程序员创建和操作字符串方面需要做的工作
在内部管理内存分配细节,从而提高了应用程序的稳定性
提供了复制构造函数和赋值运算符,可确保成员字符串得以正确复制
提供了帮助执行截短、查找和删除等操作的实用函数
提供了用于比较的运算符
让程序能够将精力放在应用的主要需求上而不是字符串的操作上
16.2 使用STL string类12345678910111213141516171819202122232425//16.1程序清单 实例化和复制STL string的方法#include#includeusing namespace std;int main(){ const char* constCStyleString = "Hello String!"; cout << "Constant string is: " << constCStyleString << endl; string strFromConst(constCStyleString); cout << "strFromConst is: " << strFromConst << endl; string str2("Hello String!"); string str2Copy(str2); cout << "str2Copy is: " << str2Copy << endl; string strPartialCopy(constCStyleString, 5); cout << "strPartialCopy is: " << strPartialCopy << endl; string strRepeatChars(10, 'a'); cout << "strRepeatChars is: " << strRepeatChars << endl; return 0;}
16.2.2 访问std::string的字符内容
要访问STL string的字符串内容,可使用迭代器,也可采用类似数组的语法并使用下标运算符([])提供偏移量。要获得string对象的C风格字符串可使用成员函数c_str()
1234567891011121314151617181920212223242526//程序清单 16.2 两种访问STL string字符元素的方式:运算符[]和迭代器#include#includeusing namespace std;int main(){ string stlString("Hello string"); cout << "Display elements in string using array-syntax: " << endl; for (size_t i = 0; i < stlString.length(); i++) { cout << "Character [" << i << "] is:" << stlString[i] << endl; } cout << endl; cout << "Display elements in string using iterators: " << endl; int charOffset = 0; for (auto i = stlString.begin(); i != stlString.end(); i++) { cout << "Character [" << charOffset++ << "] is:" << *i << endl; } cout << endl; cout << "The char* representation of the string is: " << stlString.c_str() << endl; return 0;}
16.2.3 拼接字符串
要拼接字符串,可使用运算符+=,也可使用成员函数append()
1234567891011121314151617181920212223//程序清单16.3 使用加法赋值运算符(+=)或 apped() 拼接字符串#include#includeusing namespace std;int main(){ string sampleStr1("Hello"); string sampleStr2(" String"); sampleStr1 += sampleStr2; cout << sampleStr1 << endl; string sampleStr3(" Fun is not needing to use pointers!"); sampleStr3.append(sampleStr3); cout << "sampleStr1" << endl; const char* constCStyleString = " you however still can!"; sampleStr1.append(constCStyleString); cout << sampleStr1 << endl; return 0;}
16.2.4 在string中查找字符或子字符串
123456789101112131415161718192021222324252627//程序清单16.4 使用string::find()查找子字符串或字符#include#includeusing namespace std;int main(){ string sampleStr("Good day String! Today is beautiful!"); cout << "Sample string is:" << endl << sampleStr << endl << endl; size_t charPos = sampleStr.find("day", 0); if (charPos != string::npos) { cout << "First instance \"day\" at pos. " << charPos << endl; } else { cout << "Substring not found." << endl; } cout << "Locating all instance 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); } return 0;}
16.2.5 截断STL string
**STL string类提供了erase函数**
在给定偏移位置和字符串时删除指定数目的字符
12string sampleStr("Hello String! Wake up to a beautiful day!");sampleStr.erase(13,28);
在给定指向字符的迭代器时删除该字符
1sampleStr.erase(iChars);
在给定由两个迭代器指定的范围时删除该范围内的字符
1sampleStr.erase(iChars);
1//程序清单16.5 使用string::erase从指定偏移位置或迭代器指定的位置开始截断字符串
16.2.6 字符串反转
1234567891011121314151617//程序清单16.6 使用std::reverse反转STL string#include#include#includeusing namespace std;int main(){ string sampleStr("Hello String! We will reverse!"); cout << "The original sample string is: " << sampleStr << endl; reverse(sampleStr.begin(), sampleStr.end()); cout << "After applying the std::reverse algorithm: " << endl; cout << sampleStr << endl; return 0;}
16.2.7 字符串的大小写转换
12345678910111213141516171819202122232425//程序清单16.7 使用是std::transform()将STL string转换为大写#include#include#includeusing namespace std;int main(){ cout << "Please enter a string for case-convertion:" << endl; cout << "> "; string inStr; getline(cin, inStr); cout << endl; 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; return 0;}
16.6 作业16.6.1 测验
std::string 具体化了哪个 STL 模板类?
答:std::basic_string
**如果要对两个字符串进行区分大小写的比较,该如何做? **
答:将两个字符串赋值到两个副本对象中,再将每个复制的字符串都转为小写或大写,然后对转换后的字符串进行比较,返回结果
STL string 与 C 风格字符串是否类似?
答:不类似
16.6.2 练习
**编写一个程序检查用户输入的单词是否为回文。例如,ATOYOTA 是回文,因为该单词反转后 与原来相同 **
1234567891011121314151617181920212223#include#include#includeusing namespace std;int main(){ cout << "Enter string: "; string str; cin >> str; string tmp = str; reverse(tmp.begin(), tmp.end()); cout << "Whether it is a palindrome or not" << endl; if (tmp == str) { cout << "Yes" << endl; } else { cout << "No" << endl; } return 0;}
编写一个程序,告诉用户输入的句子包含多少个元音字母
123456789101112131415161718192021222324#include#include#includeusing namespace std;int main(){ cout << "Please enter string: "; string str; cin >> str; int count = 0; for (auto ch : str) { if (ch == 'a' || ch == 'e' || ch == 'i' || ch == 'o' || ch == 'u') { count++; } } cout << "The number of vowels in that sentence is:" << count << endl; return 0;}
将字符串的字符交替地转换为大写
12345678910111213141516171819#include#include#includeusing namespace std;int main(){ cout << "Please enter string: "; string str; cin >> str; for (int i = 0; i < str.size(); i += 2) { str[i] = toupper(str[i]); } cout << "The string converted to upper case is: " << str << endl; return 0;}
编写一个程序,将4个string对象分别初始化为I、Love、STL和String,然后在这些字符串之 间添加空格,再显示整个句子
1234567891011121314151617#include#include#includeusing namespace std;int main(){ string str1 = "I"; string str2 = "Love"; string str3 = "STL"; string str4 = "String"; string result = str1 + " " + str2 + " " + str3 + " " + str4; cout << "The Sentence reads: " << result << endl; return 0;}
编写一个程序,显示字符串Good day String! Today is beautiful!中每个 a 所在的位置
12345678910111213141516171819#include#include#includeusing namespace std;int main(){ string str = "Good day String! Today is beautiful!"; int count = 0; for (int i = 0;;) { i = str.find('a', i); if (i == string::npos) break; cout << "a found at position: " << i << endl; i++; } return 0;}
第十七章17.1 std::vector的特点 vector是一个模板类,提供了动态数组的通用功能,具有如下特点
在数组末尾添加元素所需要的时间是固定的,即在末尾插入元素的所需时间不随数组大小而异,在末尾删除元素也是如此
在数组中间添加或删除元素所需要的时间与该元素后面的元素个数成正比
存储的元素数是动态的,而vector类负责管理内存
17.2 典型的vector操作17.2.1 实例化vector
12345678910111213141516//程序清单17.1 各种实例化std::vector的方式:指定长度和初始值以及复制另一个vector的值#include#includeusing namespace std;int main(){ vector integers; vector initVector{ 202,2017,-1 }; vector tenElements(10); vector tneElemInit(10, 90); vector copyVector(tneElemInit); vector partialCopy(tenElements.begin(), tenElements.begin() + 5); return 0;}
17.2.2 使用push_back()在末尾插入元素
123456789101112131415161718//使用push_back()在vector中插入元素#include#includeusing namespace std;int main(){ vector integers; integers.push_back(50); integers.push_back(1); integers.push_back(987); integers.push_back(1001); cout << "The vector contains " << integers.size() << "Elements" << endl; return 0;}
17.2.4 使用insert()在指定位置插入元素
让您能够指定位置插入位置
1integers.insert(integers.begin(),25);
让您能够指定位置插入位置、要插入的元素以及这些元素的值(都相同)
1234567 integers.insert(integers.end(),2,45);* 还可以将另一个vector的内容插入到指定位置 ```C++ vector anoter(2,30); integers.insert(integers.begin() + 1,another.begin(),another.end());
1234567891011121314151617181920212223242526272829303132333435//程序清单17.3 使用函数vector::insert在指定位置插入元素#include#includeusing namespace std;void DisplayVector(const vector& inVec){ for (auto element = inVec.begin(); element != inVec.end(); element++) { cout << *element << " "; } cout << endl;}int main(){ vector integers(4,90); cout << "The initial contents of the Vector: "; DisplayVector(integers); integers.insert(integers.begin(), 25); integers.insert(integers.end(), 2, 45); cout << "Vector after inserting elements at beginning and end: "; DisplayVector(integers); vector another(2, 30); integers.insert(integers.begin() + 1, another.begin(), another.end()); cout << "Vector after inserting contents from another vector: "; cout << "in the middle:" << endl; DisplayVector(integers); return 0;}
17.2.5 使用数组语法访问vector中的元素
1234567891011121314151617181920//程序清单17.4 使用数组语法访问vector中的元素#include#includeusing namespace std;int main(){ vector integers{ 50,1,987,1001 }; for (int i = 0; i < integers.size(); i++) { cout << "Element [" << i << "] = " << integers[i] << endl; } integers[2] = 2011; cout << "After replacement: " << endl; cout << "Element[2] = " << integers[2] << endl; return 0;}
17.2.6 使用指针语法访问vector中的元素
12345678910111213141516171819//程序清单17.5 使用指针语法访问vector中的元素#include#includeusing namespace std;int main(){ vector integers{ 50,1,987,1001 }; vector::const_iterator element = integers.cbegin(); while (element != integers.end()) { size_t index = distance(integers.cbegin(), element);//计算偏移量 cout << "Element at position " << index << " is: " << *element << endl; ++element; } return 0;}
17.2.7 删除vector中元素
12345678910111213141516171819202122232425262728//程序清单17.6 使用pop_back()删除最后一个元素#include#includeusing namespace std;void DisplayVector(const vector& inVec){ for (auto element = inVec.begin(); element != inVec.end(); element++) { cout << *element << " "; } cout << endl;}int main(){ vector integers{ 50,1,987,1001 }; cout << "Vector contains " << integers.size() << " elements: "; DisplayVector(integers); integers.pop_back(); cout << "After a call pop_back()" << endl; cout << "Vector contains " << integers.size() << " elements: "; DisplayVector(integers); return 0;}
17.4 STL deque类 deque是一个STL动态数组类,与下面非常类似,但支持在数组开头和末尾插入或删除元素
1234567891011121314151617181920212223242526272829303132333435363738//程序清单17.8 实例化一个STL deque,并使用函数push_front()和pop_front()在开头插入和删除元素#include#include#include#includeusing namespace std;int main(){ deque inDeque; inDeque.push_back(3); inDeque.push_back(4); inDeque.push_back(5); inDeque.push_front(2); inDeque.push_front(1); inDeque.push_front(0); cout << "The contents of the deque after inserting elements "; cout << "at the top and bottom are: " << endl; for (size_t count = 0; count < inDeque.size(); count++) { cout << "Element [" << count << "] = " << inDeque[count] << endl; } cout << endl; inDeque.pop_front(); inDeque.pop_back(); for (auto element = inDeque.begin(); element != inDeque.end(); element++) { size_t Offset = distance(inDeque.begin(), element); cout << "Element [" << Offset << "] = " << *element << endl; } return 0;}
17.7 作业17.7.1 测验
在vector 的开头或中间插入元素时,所需的时间是否是固定的?
答:否
有一个vector,对其调用函数size( )和capacity( )时分别返回10和20。还可再插入多少个元素 而不会导致vector重新分配其缓冲区?
答:10个元素
pop_back()函数有何功能?
答:删除最后一个元素
如果vector 是一个整型动态数组,那vector 是什么类型的动态数组?
答:Mammal动态数组
能否随机访问vector中的元素?如果是,如何访问?
答:能,使用数组下标,迭代器或使用成员函数at()进行访问
哪种迭代器可用于随机访问vector中的元素?
答:随机访问迭代器
17.7.2 练习
编写一个交互式程序,它接受用户输入的整数并将其存储到vector中。用户应能够随时使用索 引查询vector中存储的值。
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263#include#include#includeusing namespace std;char DisplayOptions(){ cout << "What would you like to do?" << endl; cout << "Select 1 To enter an integer" << endl; cout << "Select 2 Query a value given an index" << endl; cout << "Select 3 To display the vector" << endl; cout << "Select 4 To quit" << endl; char ch = 0; cin >> ch; return ch;}int main(){ vector vecData; char chUserChoice = '\0'; while ((chUserChoice = DisplayOptions()) != '4') { switch (chUserChoice) { case '1': { cout << "Please enter an integer to be inserted: "; int val = 0; cin >> val; vecData.push_back(val); break; } case '2': { cout << "Please enter an index between 0 and " << vecData.size() - 1 << " : "; size_t index = 0; cin >> index; if (index < vecData.size()) { cout << "Element [" << index << "] = " << vecData[index] << endl; } break; } case '3': { for (int i = 0; i < vecData.size(); i++) { cout << vecData[i] << " "; } cout << endl; break; } case '4':return 0; default: break; } } return 0;}
对练习1中的程序进行扩展,使其能够告诉用户他查询的值是否在vector中。
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576#include#include#includeusing namespace std;char DisplayOptions(){ cout << "What would you like to do?" << endl; cout << "Select 1 To enter an integer" << endl; cout << "Select 2 Query a value given an index" << endl; cout << "Select 3 To display the vector" << endl; cout << "Select 4 To quit" << endl; cout << "Select 5 TO Query whether a value exists" << endl; char ch = 0; cin >> ch; return ch;}int main(){ vector vecData; char chUserChoice = '\0'; while ((chUserChoice = DisplayOptions()) != '4') { switch (chUserChoice) { case '1': { cout << "Please enter an integer to be inserted: "; int val = 0; cin >> val; vecData.push_back(val); break; } case '2': { cout << "Please enter an index between 0 and " << vecData.size() - 1 << " : "; size_t index = 0; cin >> index; if (index < vecData.size()) { cout << "Element [" << index << "] = " << vecData[index] << endl; } break; } case '3': { for (int i = 0; i < vecData.size(); i++) { cout << vecData[i] << " "; } cout << endl; break; } case '4':return 0; case '5': { cout << "Please enter an value: "; int val = 0; cin >> val; if (find(vecData.begin(), vecData.end(), val) != vecData.end()) { cout << "Found" << endl; } else { cout << "Not found" << endl; } } default: break; } } return 0;}
Jack 在 eBay 销售广口瓶。为了帮助他打包和发货,请编写一个程序,让他能够输入每件商品 的尺寸,将其存储在vector中再显示到屏幕上。
编写一个应用程序,将一个队列初始化为包含以下3个字符串:Hello、Containers are cool!和 C++ is evolving!,并使用适用于各种队列的泛型函数来显示这些元素。另外,在这个应用程序 中,使用C++11引入的列表初始化和C++14引入的operator “”s。
12345678910111213141516171819#include#include#includeusing namespace std;templatevoid Display(const deque& deq){ for (auto elem : deq) { cout << elem << endl; }}int main(){ deque strDuque{ "Hello"s,"Containers are cool"s,"C++ is evolving!"s }; Display(strDuque); return 0;}
第十八章18.2 基本的list操作18.2.1 实例化一个对象
123456789101112131415161718192021//程序清单18.1 各种实例化std::list的方式:指定元素数和初始值#include#includeusing namespace std;int main(){ list listInt; list listWith10Integers(10); list listWith4IntegerEach99(10, 99); list listCopyAnother(listWith4IntegerEach99); vector vecIntegers(10, 2017); list listContainsCopyOfAnother(vecIntegers.cbegin(), vecIntegers.cend()); return 0;}
18.2.2 在list 开头或末尾插入元素
与deque 类似,要在list 开头插入元素,可使用其成员方法push_front()。要在末尾插入,可使用 成员方法push_back()。
12345678910111213141516171819202122232425262728//程序清单18.2 使用push_front()和push_back()在 list 中插入元素#include#includeusing namespace std;templatevoid Display(const T& container){ for (auto elem = container.begin(); elem != container.end(); elem++) { cout << *elem << ' '; } cout << endl;}int main(){ list linkInts{ -101,42 }; linkInts.push_front(10); linkInts.push_front(2011); linkInts.push_back(-1); linkInts.push_back(9999); Display(linkInts); return 0;}
18.2.3 在list 中间插入元素
成员函数list::insert()有 3 种版本
第1种版本:
12iterator insert(iterator pos, const T& x);//insert 函数接受的第 1 个参数是插入位置,第2个参数是要插入的值。该函数返回一个迭代器,它指向刚插入到list中的元素。
第2种版本:
12void insert(iterator pos, size_type n, const T& x);//该函数的第1个参数是插入位置,最后一个参数是要插入的值,而第2个参数是要插入的元素个数。
第3种版本:
123template void insert(iterator pos, InputIterator f, InputIterator l);//该重载版本是一个模板函数,除一个位置参数外,它还接受两个输入迭代器,指定要将集合中相应范围内的元素插入到list 中。注意,输入类型 InputIterator 是一种模板参数化类型,因此可指定任何集合(数组、vector或另一个list)的边界。
12345678910111213141516171819202122232425262728293031323334353637383940414243//程序清单18.3 在list中插入元素的各种方法#include#includeusing namespace std;templatevoid Display(const T& container){ for (auto elem = container.begin(); elem != container.end(); elem++) { cout << *elem << ' '; } cout << endl;}int main(){ list linkInts1; linkInts1.insert(linkInts1.begin(), 2); linkInts1.insert(linkInts1.begin(), 1); linkInts1.insert(linkInts1.end(), 3); Display(linkInts1); list linkInts2; linkInts2.insert(linkInts2.begin(), 4,0); cout << "The contents of list 2 after inserting "; cout << linkInts2.size() << " elements of a value:" << endl; Display(linkInts2); list linkInts3; linkInts3.insert(linkInts3.end(), linkInts1.begin(), linkInts1.end()); cout << "The contents of list 3 after inserting the contents of list 1 at the beginning:" << endl; Display(linkInts3); cout << "The contents of list 3 after inserting the contents of list 2 at the end:" << endl; linkInts3.insert(linkInts3.end(), linkInts2.begin(), linkInts2.end()); Display(linkInts3); return 0;}
**18.2.4 删除list 中的元素 **
list 的成员函数erase()有两种重载版本:一个接受一个迭代器参数并删除迭代器指向的元素;另一 个接受两个迭代器参数并删除指定范围内的所有元素。
1234567891011121314151617181920212223242526272829303132//程序清单18.4 删除list中的元素#include#includeusing namespace std;templatevoid Display(const T& container){ for (auto elem = container.begin(); elem != container.end(); elem++) { cout << *elem << ' '; } cout << endl;}int main(){ list linkInts{4,3,5,-1,2017}; auto val2 = linkInts.insert(linkInts.begin(), 2); cout << "Initial contents of the list:" << endl; Display(linkInts); cout << "After erasing element " << *val2 << ":" << endl; linkInts.erase(val2); linkInts.erase(linkInts.begin(), linkInts.end()); cout << "Number of elements after erasing range: " << linkInts.size() << endl; return 0;}
18.3 对list 中的元素进行反转和排序18.3.1 使用list::reverse( )反转元素的排列顺序
list 提供了成员函数reverse(),该函数没有参数,它反转list中元素的排列顺序
linkInts.reverse();
1234567891011121314151617181920212223242526272829//程序清单18.5 反转list中元素的排列顺序#include#includeusing namespace std;templatevoid Display(const T& container){ for (auto elem = container.begin(); elem != container.end(); elem++) { cout << *elem << ' '; } cout << endl;}int main(){ list linkInts{0,1,2,3,4,5}; cout << "Initial contents of list:" << endl; Display(linkInts); linkInts.reverse(); cout << "Contents of list after using reverse():" << endl; Display(linkInts); return 0;}
18.3.2 对元素进行排序
list 的成员函数sort()有两个版本,其中一个没有参数:
linkInts.sort();
另一个接受一个二元谓词函数作为参数,让您能够指定排序标准:
12345678bool SortPredicate_Descending (const int& lhs, const int& rhs) { // define criteria for list::sort: return true for desired order return (lhs > rhs); } // Use predicate to sort a list: linkInts.sort (SortPredicate_Descending);
123456789101112131415161718192021222324252627282930313233343536373839//Use predicate to sort a list: linkInts.sort (SortPredicate_Descending);#include#includeusing namespace std;bool SortPredicate_Descending(const int& lhs, const int& rhs){ return (lhs > rhs);}templatevoid Display(const T& container){ for (auto elem = container.begin(); elem != container.end(); elem++) { cout << *elem << ' '; } cout << endl;}int main(){ list linkInts{0,-1,2011,444,-5}; cout << "Initial contents of list are - :" << endl; Display(linkInts); linkInts.sort(); cout << "Order after sort(): " << endl; Display(linkInts); linkInts.sort(SortPredicate_Descending); cout << "Order after sort() with a predicate:" << endl; Display(linkInts); return 0;}
18.3.3 对包含对象的list进行排序以及删除其中的元素
采取下面两种方式之一
在list包含的对象所属的类中,实现运算符<
提供一个排序二元谓词—一个这样的函数,即接受两个输入值,并返回一个布尔值,指出第 一个值是否比第二个值小。
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677//程序清单18.7 存储对象的list:创建一个联系人列表#include#include#includeusing namespace std;templatevoid Display(const T& container){ for (auto elem = container.begin(); elem != container.end(); elem++) { cout << *elem << endl; } cout << endl;}struct ContactItem{ string name; string phone; string displayAs; ContactItem(const string& conName, const string& conNum) { name = conName; phone = conNum; displayAs = name + " " + phone; } bool operator == (const ContactItem& itemToCompare) const { return (itemToCompare.name == name); } bool operator < (const ContactItem& itemToCompare) const { return (itemToCompare.name < name); } operator const char* () const { return displayAs.c_str(); }};bool SortPredicate_Descending(const ContactItem& lhs, const ContactItem& rhs){ return (lhs.phone < rhs.phone);}int main(){ list contacts; contacts.push_back(ContactItem("Jack Welsch", "+1 7889879879")); contacts.push_back(ContactItem("Bill Gates", "+1 97789787998")); contacts.push_back(ContactItem("Angi Merkel", "+49 234565466")); contacts.push_back(ContactItem("Vlad Putin", "+7 66454564797")); contacts.push_back(ContactItem("Ben Affleck", "+1 745641314")); contacts.push_back(ContactItem("Dan Craig", "+44 123641976")); cout << "List in initial order: " << endl; Display(contacts); contacts.sort(); cout << "Sorting in alphabetical order via operator<:" << endl; Display(contacts); contacts.sort(SortPredicate_Descending); cout << "Sorting in order of phone numbers via predicate" << endl; Display(contacts); cout << "Erasing Putin from list: " << endl; contacts.remove(ContactItem("Vlad Putin", "")); Display(contacts); return 0;}
18.3.4 C++11 引入的 std::forward_list
forward_list 的用法与 list 很像,但只能沿一个方向移动迭代器,且插入元素时只能使用函数 push_front( ),而不能使用 push_back( )。当然,总是可以使用insert( )及其重载版本在指定位置插入 元素。
1234567891011121314151617181920212223242526272829303132//程序清单18.8 forward_list 的基本插入和删除操作 #include#includeusing namespace std;templatevoid Display(const T& container){ for (auto elem = container.begin(); elem != container.end(); elem++) { cout << *elem << ' '; } cout << endl;}int main(){ forward_list flistIntegers{ 3,4,2,2,0 }; flistIntegers.push_front(1); cout << "Contents of forward_list: " << endl; Display(flistIntegers); flistIntegers.remove(2); flistIntegers.sort(); cout << "Contents after removing 2 and sorting: " << endl; Display(flistIntegers); return 0;}
**18.6 作业 **
18.6.1 测验
与在开头或末尾插入元素相比,在STL list中间插入元素是否会降低性能?
答:不会
假设有两个迭代器分别指向STL list对象中的两个元素,然后在这两个元素之间插入了一个元 素。请问这种插入是否会导致这两个迭代器无效?
答:不会
如何清空std::list 的内容?
答:调用成员函数clear()或erase(list.begin(),list.end());
能否在list中插入多个元素?
答:可以
18.6.2 练习
编写一个程序,它接受用户输入的数字并将它们插入到list开头。
123456789101112131415161718192021222324252627#include#includeusing namespace std;templatevoid Display(const T& container){ for (auto elem = container.begin(); elem != container.end(); elem++) { cout << *elem << ' '; } cout << endl;}int main(){ list listInts; cout << "Please enter a number: "; int val = 0; while (cin >> val) { listInts.push_back(val); cout << "Please enter a number: "; } Display(listInts); return 0;}
使用一个简短的程序来演示这样一点:在list中插入一个新元素,导致迭代器指向的元素的相 对位置发生变化后,该迭代器仍有效。
编写一个程序,使用list的insert()函数将一个vector的内容插入到一个STL list中。
1234list listInts;vector vecInts{ 1,2,3,4,5 };listInts.insert(listInts.begin(), vecInts.begin(), vecInts.end());
编写一个程序,对字符串list进行排序以及反转排列顺序。
1234567list listStr;vector vecStr{ "1,2,3,4,5","hfsjfkalfj","ddd","wwafa"};listStr.insert(listStr.begin(), vecStr.begin(), vecStr.end());listStr.sort();listStr.reverse();
第十九章19.1 简介 容器set和multiset让程序员能够在容器中快速查找键,键是存储在一维容器中的值。set和multiset 之间的区别在于,后者可存储重复的值,而前者只能存储唯一的值。为了实现快速搜索,STL set和multiset的内部结构像二叉树,这意味着将元素插入到set或multiset 时将对其进行排序,以提高查找速度。
19.2 STL set 和 multiset 的基本操作19.2.1 实例化std::set 对象
要实例化一个特定类型的set或multiset,必须针对该类型具体化模板类std::set或std::multiset
12std::set setInts; std::multiset msetInts;
要声明一个包含Tuna对象的set或multiset,应这样编写代码
12std::set tunaSet; std::multiset tunaMSet;
要声明一个指向set或multiset中元素的迭代器,应这样做
12std::set::const_iterator element; std::multiset::const_iterator element;
创建二元排序谓词,指定初始化序列
123456789101112template struct SortDescending { bool operator()(const T& lhs, const T& rhs) const { return (lhs > rhs); } }; // a set and multiset of integers (using sort predicate) set > setInts; multiset > msetInts;
1234567891011121314151617181920212223242526//程序清单19.1 各种实例化set和multiset的方式#includeusing namespace std;templatestruct SortDescending{ bool operator()(const T& lhs, const T& rhs)const { return (lhs > rhs); }};int main(){ set setInts1; multiset msetInts1; set> setInts2; multiset> msetInts2; set setInts3(setInts1); set msetInts3(setInts1.cbegin(),setInts1.end()); return 0;}
19.2.2 在set 或multiset 中插入元素
set 和 multiset 的大多数函数的用法类似,它们接受类似的参数,返回类型也类似。例如,要在这 两种容器中插入元素,都可使用成员函数insert(),这个函数接受要插入的值或容器的指定范围
12345678910111213141516171819202122232425262728293031323334//程序清单19.2 在STL set或multiset 中插入元素 #include#includeusing namespace std;templatevoid Display(const T& container){ for (auto elem = container.begin(); elem != container.end(); elem++) { cout << *elem << ' '; } cout << endl;}int main(){ set setInts{ 202,151,-999,-1 }; setInts.insert(-1); cout << "Contents of the set: " << endl; Display(setInts); multiset msetInts; msetInts.insert(setInts.begin(), setInts.end()); msetInts.insert(-1); cout << "Contents of the multiset: " << endl; Display(msetInts); cout << "Number of instances of -1 in the multiset are: " << msetInts.count(-1) << endl; return 0;}
19.2.3 在STL set 或 multiset 中查找元素
诸如set、multiset、map 和 multimap 等关联容器都提供了成员函数find( ),它让您能够根据给定的 键来查找值。multiset可包含多个值相同的元素,因此对于multiset,这个 函数查找第一个与给定键匹配的元素。
1234567891011121314151617181920212223242526272829303132333435363738//程序清单19.3 使用成员函数find#include#includeusing namespace std;templatevoid Display(const T& container){ for (auto elem = container.begin(); elem != container.end(); elem++) { cout << *elem << ' '; } cout << endl;}int main(){ set setInts{ 43,78,-1,124 }; Display(setInts); auto element = setInts.find(-1); if (element != setInts.end()) { cout << "Element " << *element << " found!" << endl; } else { cout << "Element not found in set!" << endl; } auto anotherFind = setInts.find(12345); if (anotherFind != setInts.end()) { cout << "Element " << *anotherFind << " found!" << endl; } else { cout << "Element not found in set!" << endl; } return 0;}
19.2.4 删除STL set 或multiset 中的元素
诸如set、multiset、map 和 multimap 等关联容器都提供了成员函数erase( ),它让您能够根据键删除值
1setObject.erase (key);
erase()函数的另一个版本接受一个迭代器作为参数,并删除该迭代器指向的元素
1setObject.erase (element);
通过使用迭代器指定的边界,可将指定范围内的所有元素都从set或multiset中删除
1setObject.erase (iLowerBound, iUpperBound);
1234567891011121314151617181920212223242526272829303132333435363738//程序清单19.4 使用multiset的成员函数erase#include#includeusing namespace std;templatevoid Display(const T& container){ for (auto elem = container.begin(); elem != container.end(); elem++) { cout << *elem << ' '; } cout << endl;}typedef multiset MSETINT;int main(){ MSETINT msetInts{ 43,78,78,-1,124 }; cout << "multiset contains " << msetInts.size() << "elements: "; Display(msetInts); cout << "Enter a number to erase from the set: "; int input = 0; cin >> input; cout << "Erasing " << msetInts.count(input); cout << " instances of value " << input << endl; msetInts.erase(input); cout << "multiset now contains " << msetInts.size() << "elements: "; Display(msetInts); return 0;}
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576//程序清单19.5 一个使用STL set及其成员函数find和erase的电话簿#include#include#includeusing namespace std;templatevoid Display(const T& container){ for (auto elem = container.begin(); elem != container.end(); elem++) { cout << *elem << endl; } cout << endl;}struct ContactItem{ string name; string phone; string displayAs; ContactItem(const string& conName, const string& conNum) { name = conName; phone = conNum; displayAs = name + " " + phone; } bool operator == (const ContactItem& itemToCompare) const { return (itemToCompare.name == name); } bool operator < (const ContactItem& itemToCompare) const { return (itemToCompare.name < name); } operator const char* () const { return displayAs.c_str(); }};bool SortPredicate_Descending(const ContactItem& lhs, const ContactItem& rhs){ return (lhs.phone < rhs.phone);}int main(){ set contacts; contacts.insert(ContactItem("Jack Welsch", "+1 7889879879")); contacts.insert(ContactItem("Bill Gates", "+1 97789787998")); contacts.insert(ContactItem("Angi Merkel", "+49 234565466")); contacts.insert(ContactItem("Vlad Putin", "+7 66454564797")); contacts.insert(ContactItem("Ben Affleck", "+1 745641314")); contacts.insert(ContactItem("Dan Craig", "+44 123641976")); cout << "Enter a name you wish to delete: "; string inputName; getline(cin, inputName); auto contactFound = contacts.find(ContactItem(inputName, "")); if (contactFound != contacts.end()) { contacts.erase(contactFound); cout << "Display contents after erasing " << inputName << endl; Display(contacts); } else { cout << "Contact not found" << endl; } return 0;}
19.6 作业19.6.1 测验
使用set 声明整型set 时,排序标准将由哪个函数提供?
答:less<>
在multiset 中,重复的值以什么方式出现?
答:在插入时排序,因此值相同的元素将在一起
set 和multiset 的哪个成员函数指出容器包含多少个元素?
答:size(0)
19.6.2 练习
在不修改 ContactItem 的情况下,扩展本章的电话簿应用程序,使其能够根据电话号码查找人 名(提示:调整运算符<和==,确保根据电话号码对元素进行比较和排序)。
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273#include#include#includeusing namespace std;templatevoid Display(const T& container){ for (auto elem = container.begin(); elem != container.end(); elem++) { cout << *elem << endl; } cout << endl;}struct ContactItem{ string name; string phone; string displayAs; ContactItem(const string& conName, const string& conNum) { name = conName; phone = conNum; displayAs = name + " " + phone; } bool operator == (const ContactItem& itemToCompare) const { return (itemToCompare.phone == phone); } bool operator < (const ContactItem& itemToCompare) const { return (itemToCompare.phone > phone); } operator const char* () const { return displayAs.c_str(); }};bool SortPredicate_Descending(const ContactItem& lhs, const ContactItem& rhs){ return (lhs.phone < rhs.phone);}int main(){ set contacts; contacts.insert(ContactItem("Jack Welsch", "+1 7889879879")); contacts.insert(ContactItem("Bill Gates", "+1 97789787998")); contacts.insert(ContactItem("Angi Merkel", "+49 234565466")); contacts.insert(ContactItem("Vlad Putin", "+7 66454564797")); contacts.insert(ContactItem("Ben Affleck", "+1 745641314")); contacts.insert(ContactItem("Dan Craig", "+44 123641976")); cout << "Enter a name you wish to delete: "; string inputName; getline(cin, inputName); auto contactFound = contacts.find(ContactItem("", inputName)); if (contactFound != contacts.end()) { cout << "The number belongs to " << contactFound->name << endl; } else { cout << "Contact not found" << endl; } return 0;}
定义一个multiset 来存储单词及其含义,即将multiset用作词典(提示:multiset存储的对象应 是一个包含两个字符串的结构,其中一个字符串为单词,另一个字符串是单词的含义)。
12345678910111213141516171819202122232425262728293031323334353637383940414243#include#include#includeusing namespace std;struct PAIR_WORD_MEANING{ string word; string meaning; PAIR_WORD_MEANING(const string& sWord, const string& sMeaning) :word(sWord),meaning(sMeaning){} bool operator<(const PAIR_WORD_MEANING& pairAnotherWord) const { return (word < pairAnotherWord.word); } bool operator==(const string& key) { return key == this->word; }};int main(){ multiset< PAIR_WORD_MEANING> msetDictionary; PAIR_WORD_MEANING word1("C++", "A programming language"); PAIR_WORD_MEANING word2("Programmer", "A geek!"); msetDictionary.insert(word1); msetDictionary.insert(word2); cout << "Enter a word you wish to find the meaning off" << endl; string input; getline(cin, input); auto element = msetDictionary.find(PAIR_WORD_MEANING(input, "")); if (element != msetDictionary.end()) { cout << "Meaning is: " << element->meaning << endl; } return 0;}
通过一个简单程序演示set不接受重复的元素,而multiset接受。
1234567891011121314151617181920212223242526272829303132333435#include#include#includeusing namespace std;templatevoid Display(const T& container){ for (auto elem = container.begin(); elem != container.end(); elem++) { cout << *elem << endl; } cout << endl;}int main(){ set setInts; setInts.insert(1); setInts.insert(1); setInts.insert(1); setInts.insert(1); setInts.insert(1); Display(setInts); multiset msetInts; msetInts.insert(1); msetInts.insert(1); msetInts.insert(1); msetInts.insert(1); msetInts.insert(1); Display(msetInts); msetInts.insert(1); return 0;}
第二十章20.2 STL map 和multimap 的基本操作 要实例化将整数用作键、将字符串用作值的 map 或 multimap,必须具体化模板类 std::map 或 std::multimap。实例化模板类 map 时,需要指定键和值的类型以及可选的谓词(它帮助map类对插入 的元素进行排序)
12map > mapObj; multimap > mmapObj;
123456789101112131415161718192021222324252627282930//程序清单20.1 实例化STL map和multimap(键类型为int,值类型为string)#include
20.2.2 在STL map 或multimap 中插入元素
map 和multimap 的大多数函数的用法类似,它们接受类似的参数,返回类型也类似。例如,要在 这两种容器中插入元素,都可使用成员函数insert
123std::map mapIntToStr1; // insert pair of key and value using make_pair function mapIntToStr.insert (make_pair (-1, "Minus One"));
鉴于这两种容器包含的元素都是键-值对,因此也可直接使用std::pair来指定要插入的键和值
1mapIntToStr.insert (pair (1000, "One Thousand"));
另外,还可使用类似于数组的语法进行插入。这种方式对用户不太友好,是由下标运算符([])支持
1mapIntToStr [1000000] = "One Million";
12345678910111213141516171819202122232425262728293031323334353637383940414243444546//程序清单20.2 使用insert( )以及数组语法(运算符[])在STL map或multimap中插入元素#include
20.2.3 在STL map 或multimap 中查找元素
诸如map和multimap 等关联容器都提供了成员函数find(),它让您能够根据给定的键查找值。 find( )总是返回一个迭代器
1234567891011121314151617181920212223242526272829303132333435363738394041//程序清单20.3 使用成员函数find()在map中查找键-值对#include
20.2.4 在STL multimap 中查找元素
multimap,容器可能包含多个键相同的键-值对,因此需要找到与指定 键对应的所有值。为此,可使用multimap::count( )确定有多少个值与指定的键对应,再对迭代器递增, 以访问这些相邻的值
123456789101112131415161718auto pairFound = mmapIntToStr.find(key); // Check if find() succeeded if(pairFound != mmapIntToStr.end()) { // Find the number of pairs that have the same supplied key size_t numPairsInMap = mmapIntToStr.count(1000); for(size_t counter = 0; counter < numPairsInMap; // stay within bounds ++ counter ) { } cout << "Key: " << pairFound->first; // key cout << ", Value [" << counter << "] = "; cout << pairFound->second << endl; // value ++ pairFound; } else cout << "Element not found in the multimap";
**20.2.5 删除STL map或multimap 中的元素 **
调用erase 函数时将键作为 参数,这将删除包含指定键的所有键-值对
mapObject.erase (key);
接受迭代器作为参数,并删除迭代器指向的元素
mapObject.erase(element);
可使用迭代器指定边界,从而将指定范围内的所有元素都从map或multimap中删除
`mapObject.erase (lowerBound, upperBound);
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051//程序清单20.4 删除multimap中的元素#include
20.3 提供自定义的排序谓词 map 和multimap 的模板定义包含第3个参数,该参数是确保map能够正常工作的排序谓词。如果 没有指定这个参数,将使用std::less <>提供的默认排序标准,该谓词使用<运算 符来比较两个对象。要提供不同的排序标准,可编写一个二元谓词—实现了operator( )的类或结构
12345678template struct Predicate { bool operator()(const keyType& key1, const keyType& key2) { // your sort priority logic here } };
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475//程序清单20.5 提供自定义排序谓词—电话簿应用程序#include
20.4 基于散列表的STL键-值对容器#include
**unordered_map 的平均插入和删除时间是固定的,查找元素的时间也是固定的。 **
**20.4.2 使用unordered_map 和 unordered_multimap **
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253//程序清单 20.6 实例化 STL 散列表实现 unordered_map,并使用 insert( )、find( )、size( )、max_bucket_count( )、load_factor( )和 max_load_factor( )#include#include#include#includeusing namespace std;templatevoid Display(unordered_map& cont){ cout << "Unordered Map contains: " << endl; for (auto elem = cont.begin(); elem != cont.end(); elem++) { cout << "elem->first = " << elem->first << " elem->second = " << elem->second << endl; } cout << "Number of pairs, size(): " << cont.size() << endl; cout << "Bucket count = " << cont.bucket_count() << endl; cout << "Current load factor: " << cont.load_factor() << endl; cout << "Max load factor: " << cont.max_load_factor() << endl;}int main(){ unordered_map umapIntToStr; umapIntToStr.insert(make_pair(1, "One")); umapIntToStr.insert(make_pair(45, "Forty Five")); umapIntToStr.insert(make_pair(1001, "Thousand One")); umapIntToStr.insert(make_pair(-2, "Minus Two")); umapIntToStr.insert(make_pair(-1000, "Minus One Thousand")); umapIntToStr.insert(make_pair(100, "One Hundred")); umapIntToStr.insert(make_pair(12, "Twelve")); umapIntToStr.insert(make_pair(-100, "Minus One Hundred")); Display(umapIntToStr); cout << "Inserting one more element" << endl; umapIntToStr.insert(make_pair(300, "Three Hundred")); Display(umapIntToStr); cout << "Enter key to find for: "; int key = 0; cin >> key; auto element = umapIntToStr.find(key); if (element != umapIntToStr.end()){ cout << "Found Key pairs with value " << element->second << endl; } else { cout << "key has no corresponding pair value!" << endl; } return 0;}
20.7 作业**20.7.1 测验 **
使用map 声明整型map时,排序标准将由哪个函数提供?
答:less<>
在multimap 中,重复的值以什么方式出现?
答:彼此相邻
map 和multimap 的哪个成员函数指出容器包含多少个元素?
答:size();
在map中的什么地方可以找到重复的值?
答:map中找不到重复元素
20.7.2 练习
编写一个应用程序来实现电话簿,它不要求人名是唯一的。应选择哪种容器?写出容器的定义。
multimap
1mulitmap telephoneDirectory;
下面是电话簿应用程序中一个map的定义。请定义二元谓词fPredicate,用于帮助该 map 根据WordProperty 键包含的 string 属性对元素 进行排序。
1234567map mapWordDefinition;struct WordProperty { string word; bool isLatinBase; };
1234567struct fPredicate{ bool operator()(const wordProperty& key1,const wordProperty& ket2) { return key1.word < key2.word; }}
map 不接受重复元素,而multimap接受;请编写一个简单程序来演示这一点。
1234567891011121314151617#include
第20章21.1 函数对象与谓词的概念
函数对象:是用作函数的对象;但从实现上说,函数对象是实现了operator()的类的对 象。虽然函数和函数指针也可归为函数对象,但实现了operator()的类的对象才能保存状态(即类的成 员属性的值),才能用于标准模板库(STL)算法。C++程序员常用于STL算法的函数对象可分为下列两种类型
一元函数:接受一个参数的函数,如f(x)。如果一元函数返回一个布尔值,则该函数称为谓词
二元函数:接受两个参数的函数,如f(x, y)。如果二元函数返回一个布尔值,则该函数称为二元 谓词
谓词:返回布尔类型的函数对象通常用于需要进行判断的算法
21.2 函数对象的典型用途21.2.1 一元函数
只对一个参数进行操作的函数称为一元函数。一元函数的功能可能很简单,如在屏幕上显示元素
1234567891011121314151617181920212223242526272829//程序清单21.1 使用一元函数将集合的内容显示在屏幕上#include#include#include#includeusing namespace std;templatestruct Display{ void operator() (const T& element) const { cout << element << ' '; }};int main(){ vector numsInVec{ 0,1,2,3,-1,-9,0,-999 }; cout << "Vector of integers contains: " << endl; for_each(numsInVec.begin(), numsInVec.end(), Display()); list charsInList{ 'a','z','k','d' }; cout << endl << "List of characters contains: " << endl; for_each(charsInList.begin(), charsInList.end(), Display()); return 0;}
12345678910111213141516171819202122232425262728293031//程序清单21.2 存储状态的函数对象#include#include#include#includeusing namespace std;templatestruct Display{ int count; Display() : count{ 0 } {} void operator() (const T& element) { ++count; cout << element << ' '; }};int main(){ vector numsInVec{ 22,2017,-1,999,43,901}; cout << "Vector of integers contains: " << endl; Display result; result = for_each(numsInVec.begin(), numsInVec.end(), Display()); cout << endl << "Functor invoked " << result.count << "times"; return 0;}
21.2.2 一元谓词
返回布尔值的一元函数是谓词
123456789101112131415//程序清单21.3 一个一元谓词,它判断一个数字是否为另一个数字的整数倍templatestruct IsMultiple{ T Divisor; IsMultiple(const T& divisor) { Divisor = divisor; } bool operator()(const T& element) const { return (elemen % Divisor == 0); }};
12345678910111213141516171819202122232425262728293031323334353637//程序清单21.4 在std::find_if( )中使用一元谓词IsMutilple,在 vector 中查找一个能被用户提供的除数整除的元素 #include#include#includeusing namespace std;templatestruct IsMultiple{ T Divisor; IsMultiple(const T& divisor) { Divisor = divisor; } bool operator()(const T& element) const { return (element % Divisor == 0); }};int main(){ vector numsInVec{ 25,26,27,28,29,30,31 }; cout << "Enter divisor (>0): "; int divisor = 2; cin >> divisor; auto element = find_if(numsInVec.begin(), numsInVec.end(), IsMultiple(divisor)); if (element != numsInVec.end()) { cout << "First element in vector divisible by " << divisor << ": " << *element << endl; } return 0;}
21.2.3 二元函数
如果函数 f(x, y)根据输入参数返回一个值,它将很有用。这种二元函数可用于对两个操作数执行 运算
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253//程序清单21.5 使用二元函数将两个范围相乘#include#include#includeusing namespace std;templateclass Multiply{public: const T& operator()(const T& elem1, const T& elem2) { return (elem1 * elem2); }};int main(){ vector multiplicands{ 0,1,2,3,4 }; vector multipliers{ 100,101,102,103,104 }; vector vecResult; vecResult.resize(multiplicands.size()); transform(multiplicands.begin(), multiplicands.end(), multipliers.begin(), vecResult.begin(), Multiply()); cout << "The contents of the first vector are: " << endl; for (int i = 0; i < multiplicands.size(); i++) { cout << multiplicands[i] << ' '; } cout << endl; cout << "The contents of the second vector are: " << endl; for (int i = 0; i < multipliers.size(); i++) { cout << multipliers[i] << ' '; } cout << endl; cout << "The result of the multiplication is: " << endl; for (int i = 0; i < vecResult.size(); i++) { cout << vecResult[i] << ' '; } cout << endl; return 0;}
21.2.4 二元谓词
接受两个参数并返回一个布尔值的函数是二元谓词。
1234567891011121314//程序清单21.6 对字符串进行不区分大小写排序的二元谓词 class CompareStringNoCase{public: bool operator()(const string& str1, const string& str2) { string str1LowerCase{str1}; string str2LowerCase{ str2 }; transform(str1.begin(), str1.end(),str1LowerCase.begin(), ::tolower); transform(str2.begin(), str2.end(), str2LowerCase.begin(), ::tolower); return (str1LowerCase < str2LowerCase); }};
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152//程序清单21.7 使用函数对象CompareStringNoCase对字符串vector进行不区分大小写的排序#include#include#include#includeusing namespace std;class CompareStringNoCase{public: bool operator()(const string& str1, const string& str2) { string str1LowerCase{str1}; string str2LowerCase{ str2 }; transform(str1.begin(), str1.end(),str1LowerCase.begin(), ::tolower); transform(str2.begin(), str2.end(), str2LowerCase.begin(), ::tolower); return (str1LowerCase < str2LowerCase); }};templatevoid Display(const T& container){ for (auto iter = container.begin(); iter != container.end(); iter++) { cout << *iter << endl; }}int main(){ vector names; names.push_back("jim"); names.push_back("jack"); names.push_back("Sam"); names.push_back("Anna"); cout << "The names in vector in order of insertion: " << endl; Display(names); cout << "Names after sorting using default std::less<>: " << endl; sort(names.begin(), names.end()); Display(names); cout << "Sorting using predicate that ignores case:" << endl; sort(names.begin(), names.end(), CompareStringNoCase()); Display(names); return 0;}
21.5 作业21.5.1 测验
返回布尔值的一元函数称为什么?
答:一元谓词
不修改数据也不返回布尔值的函数对象有什么用?请通过示例阐述您的观点。
答:可以用来显示数据和计算
函数对象这一术语的定义是什么?
答:在应用程序运行阶段的所有实体都是对象,因此结构体和类也可用作函数,这称为函数对象。函数对象也可通过函数指针来调用,它们也是函数对象
21.5.2 练习
编写一个一元函数,它可供std::for_each()用来显示输入参数的两倍。
123456789templatestruct Display{ void operator()(const T& elem) { cout << (elem * 2) << ' '; }};
进一步扩展上述谓词,使其能够记录它被调用的次数。
12345678910111213templatestruct Display{ int count; Display() : count{0}{} void operator()(const T& elem) { count++; cout << (elem * 2) << ' '; }};
编写一个用于降序排序的二元谓词。
12345678templatestruct Ascending{ bool operator()(const T& key1, const T& key2) const { return (ke1 < key2); }};
第二十二章22.1 lambda 表达式是什么 可将lambda表达式视为包含公有operator( )的匿名结构(或类),从这种意义上说,lambda表达式 属于函数对象
22.2 如何定义lambda表达式 lambda 表达式的定义必须以方括号([])打头。这些括号告诉编译器,接下来是一个lambda表达 式。方括号的后面是一个参数列表,该参数列表与不使用lambda表达式时提供给operator( )的参数列 表相同
22.3 一元函数对应的lambda表达式
与一元operator(Type)对应的 lambda 表达式接受一个参数,其定义如下
[](Type paramName) { // lambda expression code here; }
如果您愿意,也可按引用传递参数
[](Type& paramName) { // lambda expression code here; }
1234567891011121314151617181920//程序清单22.1 在算法for_each( )中使用lambda表达式而不是函数对象来显示容器中的元素 #include#include#include#includeusing namespace std;int main(){ vector numsInVec{ 104,-4,500,21,42,-1 }; list charsInList{ 'a','h','z','k','l' }; cout << "Display elements in a vector using a lambda: " << endl; for_each(numsInVec.begin(), numsInVec.end(), [](auto& elem) {cout << elem << ' '; }); cout << endl << "Display elements in a list using a lambda: " << endl; for_each(charsInList.begin(), charsInList.end(), [](auto& elem) {cout << elem << ' '; }); return 0;}
22.4 一元谓词对应的lambda表达式 谓词可帮助您做出决策。一元谓词是返回bool类型(true或false)的一元表达式。lambda表达式也可返回值,例如,下面的lambda表达式在num为偶数时返回true
[](int& num) {return ((num % 2) == 0); }
123456789101112131415161718//程序清单22.2 在算法std::find_if( )中,将 lambda 表达式用作一元谓词,以查找集合中的偶数#include#include#includeusing namespace std;int main(){ vector numsInVec{ 25,101,2017,-50 }; auto evenNum = find_if(numsInVec.begin(), numsInVec.end(), [](auto& elem) {return !(elem % 2); }); if (evenNum != numsInVec.end()) { cout << "Even number in collection is:" << *evenNum << endl; } return 0;}
22.5 通过捕获列表接受状态变量的lambda表达式12345678910111213141516171819202122232425//程序清单22.3 使用存储状态的lambda表达式来判断一个数字能否被另一个数字整除#include#include#includeusing namespace std;int main(){ vector numsInVec{ 25,26,27,28,29,30,31 }; cout << "The vector contains: "; cout << endl << "Enter divisor (> 0): "; int divisor = 2; cin >> divisor; vector::iterator elem; elem = find_if(numsInVec.begin(), numsInVec.end(), [divisor](auto& element) {return !(element % divisor); }); if (elem != numsInVec.end()) { cout << "First element in vector idvisible by " << divisor << ": " << *elem << endl; } return 0;}
22.6 lambda 表达式的通用语法
lambda 表达式总是以方括号打头,并可接受多个状态变量,为此可在捕获列表([…])中指定这 些状态变量,并用逗号分隔
[stateVar1, stateVar2](Type& param) { // lambda code here; }
如果要在lambda表达式中修改这些状态变量,可添加关键字multable
[stateVar1, stateVar2](Type& param) mutable { // lambda code here; }
这样,便可在lambda表达式中修改捕获列表([])中指定的变量,但离开lambda表达式后,这些 修改将无效。要确保在lambda表达式内部对状态变量的修改在其外部也有效,应按引用传递它们
[&stateVar1, &stateVar2](Type& param) { // lambda code here; }
如果要向编译器明确地指定返回类型,可使用->
12[stateVar1, stateVar2](Type1 var1, Type2 var2) -> ReturnType { return (value or expression ); }
22.7 二元函数对应的lambda表达式123456789101112131415161718192021222324252627282930313233//程序清单22.4 将lambda 表达式用作二元函数,以便将两个容器中的元素相乘,并将结果存储到第三个容器中#include#include#includeusing namespace std;int main(){ vector vecMultiplicand{ 0,1,2,3,4 }; vector vecMultiplier{ 100,101,102,103,104 }; vector result; result.resize(vecMultiplicand.size()); transform(vecMultiplicand.begin(), vecMultiplicand.end(), vecMultiplier.begin(), result.begin(), [](int a, int b) {return a * b; }); cout << "The contents of the first vector are: " << endl; for (int i = 0; i < vecMultiplicand.size(); i++) cout << vecMultiplicand[i] << ' '; cout << endl; cout << "The contents of the second vector are: " << endl; for (int i = 0; i < vecMultiplier.size(); i++) cout << vecMultiplier[i] << ' '; cout << endl; cout << "The result of the multiplication is: " << endl; for (int i = 0; i < result.size(); i++) cout << result[i] << ' '; cout << endl; return 0;}
22.8 二元谓词对应的lambda表达式1234567891011121314151617181920212223242526272829303132333435363738394041424344454647//程序清单22.5 在std::sort( )中,将 lambda 表达式用作二元谓词,以便进行区分大小写的排序#include#include#include#includeusing namespace std;templatevoid Display(const T& input){ for (auto elem : input) { cout << elem << ' '; } cout << endl;}int main(){ vector namesInVec{ "jim","Jack","Sam","Anna" }; cout << "The names in vector in order of insertion: " << endl; Display(namesInVec); cout << "Order after case sensitive sort: " << endl; sort(namesInVec.begin(), namesInVec.end()); Display(namesInVec); cout << "Order after sort ignoring case: " << endl; sort(namesInVec.begin(), namesInVec.end(), [](const string& str1, const string& str2) { string str1LC; string str2LC; str1LC.resize(str1.size()); str2LC.resize(str2.size()); transform(str1.begin(), str1.end(), str1LC.begin(), ::tolower); transform(str2.begin(), str2.end(), str2LC.begin(), ::tolower); return (str1LC < str2LC); }); Display(namesInVec); return 0;}
22.11 作业22.11.1 测验
编译器如何确定lambda表达式的起始位置?
答:总是以[]开头
如何将状态变量传递给lambda表达式?
答:使用[]捕获列表
如何指定lambda表达式的返回类型?
答:[var1,var2,...](Type& param)->ReturnType{...;}
22.11.2 练习
编写一个可用作二元谓词的lambda表达式,帮助将元素按降序排列。
1234[](auto& key1, auto& key2){ return key1 > key2;}
编写一个这样的lambda表达式,即用于for_each()时,给vector等容器中的元素加上用户指定 的值。
1for_each(vec.begin(),vec.end(),[value](auto& val){return (value + val);});
第二十三章23.2 STL算法的分类23.2.1 非变序算法
不改变容器中元素的顺序和内容的算法称为非变序算法
23.2.2 变序算法
变序算法改变其操作的序列的元素顺序或内容
23.3 使用STL算法23.3.1 根据值或条件查找元素
STL 算法find( )和 find_if( )用于在 vector 等容器中查找与值匹配或满足条件的元素。find_if( )需要通过第三个参数提供一个一元谓词(返回true或false的一元 函数).这两个函数都返回一个迭代器
12345678910111213141516171819202122232425262728293031//程序清单23.1 使用find( )在vector中查找一个整数,并使用find_if以及一个用lambda表达式表示的一元谓词查找第一个偶数#include#include#includeusing namespace std;int main(){ vector numsInVec{ 2017,0,-1,42,10101,25 }; cout << "Enter number to find in collection: "; int numToFind = 0; cin >> numToFind; auto elem = find(numsInVec.begin(), numsInVec.end(), numToFind); if (elem != numsInVec.end()) { cout << "Value " << *elem << " found!" << endl; } else { cout << "No element contains value " << numToFind << endl; } auto evenNum = find_if(numsInVec.begin(), numsInVec.end(), [](auto& val) {return !(val % 2); }); if (evenNum != numsInVec.end()) { cout << "Number " << *evenNum << " Found at position ["; cout << distance(numsInVec.begin(), evenNum) << "]" << endl;; } return 0;}
23.3.2 计算包含给定值或满足给定条件的元素数
算法std::count( )和 count_if( )计算给定范围内的元素数。std:: count( )计算包含给定值(使用相等运 算符==进行测试)的元素数。std::count_if( )计算这样的元素数,即满足通过参数传递的一元谓词(可以是函数对象,也可以是 lambda 表达式)
12345678910111213141516171819202122232425//程序清单23.2 使用std::count( )和count_if( )分别计算有多少个元素包含指定值和满足指定条件#include#include#includeusing namespace std;templatebool isEven(const T& val){ return !(val % 2);}int main(){ vector numsInVec{ 2017,0,-1,42,10101,25 }; size_t numZeros = count(numsInVec.begin(), numsInVec.end(), 0); cout << "Number of instance of '0': " << numZeros << endl << endl; size_t numEvenNums = count_if(numsInVec.begin(), numsInVec.end(), isEven); cout << "Number of even elements: " << numEvenNums << endl; cout << "Number of odd elements:" << numsInVec.size() - numEvenNums << endl; return 0;}
23.3.3 在集合中搜索元素或序列
应使 用search( )或 search_n( )。search( )用于在一个序列中查找另一个序列。search_n( )用于在容器中查找n个相邻的指定值。这两个函数都返回一个迭代器
123456789101112131415161718192021222324252627282930313233343536373839404142434445//程序清单23.3 使用search和search_n在集合中查找序列#include#include#include#includeusing namespace std;templatevoid Display(const T& input){ for (auto& elem : input) { cout << elem << ' '; } cout << endl;}int main(){ vector numsInVec{ 2017,0,-1,42,10101,25,9,9,9 }; list numsInList{ -1,42,10101 }; cout << "The contents of the sample vector are: " << endl; Display(numsInVec); cout << "The contents of the sample list are: " << endl; Display(numsInList); cout << "search() for the contents of list in vector:" << endl; auto range = search(numsInVec.cbegin(), numsInVec.cend(), numsInList.begin(),numsInList.end()); if (range != numsInVec.end()) { cout << "Sequence in list found in vector at position: "; cout << distance(numsInVec.cbegin(), range) << endl; } cout << "Searching{9,9,9} in vector using search_n(): " << endl; auto partialRange = search_n(numsInVec.begin(), numsInVec.end(), 3, 9); if (partialRange != numsInVec.end()) { cout << "Sequence{9,9,9} found in vector at position: "; cout << distance(numsInVec.begin(), partialRange) << endl; } return 0;}
23.3.4 将容器中的元素初始化为指定值
fill( )将指定范围内的元素设置为 指定值
fill (numsInVec.begin (), numsInVec.end (), 9);
fill_n( )将 n 个元素设置为指定的值,接受的参数包括起始位置、元素数以及要设置 的值
fill_n (numsInVec.begin () + 3, /*count*/ 3, /*fill value*/ -9);
12345678910111213141516171819202122//程序清单23.4 使用fill( )和 fill_n( )设置容器中元素的初始值#include#include#include#includeusing namespace std;int main(){ vector numsInVec(3); fill(numsInVec.begin(), numsInVec.end(), 9); numsInVec.resize(6); fill_n(numsInVec.begin() + 3, 3, -9); cout << "Contents of the vector are: " << endl; for (int i = 0; i < numsInVec.size(); i++) { cout << "Element [" << i << "] = " << numsInVec[i] << endl; } return 0;}
23.3.5 使用std::generate( )将元素设置为运行阶段生成的值
可使用generate( )将指定范围内的元素设置为生成器函数返回的值
12generate (numsInVec.begin (), numsInVec.end (), // range rand); // generator function
generate_n( )与 generate( )类似,但您指定的是要设置的元素数,而不是闭区间
1generate_n (numsInList.begin (), 5, rand);
1234567891011121314151617181920212223242526272829303132333435//程序清单23.5 使用generate( )和generate_n( )将集合设置为随机值#include#include#include#include#includeusing namespace std;templatevoid Display(const T& input){ for (auto& elem : input) { cout << elem << ' '; } cout << endl;}int main(){ srand(time(NULL)); vector numsInVec(5); generate(numsInVec.begin(), numsInVec.end(), rand); cout << "Elements in the vector are: "; Display(numsInVec); list numsInList(5); generate_n(numsInList.begin(), 5, rand); cout << "Elements int the list are: "; Display(numsInList); return 0;}
23.3.6 使用for_each( )处理指定范围内的元素
算法for_each( )对指定范围内的每个元素执行指定的一元函数对象
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849//程序清单23.6 使用for_each( )显示集合的内容#include #include #include #include using namespace std;template struct DisplayElementKeepcount{ int count; DisplayElementKeepcount() : count(0) {} void operator()(const elementType& element) { ++count; cout << element << ' '; }};int main(){ vector numsInVec{ 2017, 0, -1, 42, 10101, 25 }; cout << "Elements in vector are: " << endl; DisplayElementKeepcount functor = for_each(numsInVec.cbegin(), // Start of range numsInVec.cend(), // End of range DisplayElementKeepcount()); // Functor cout << endl; // Use the state stored in the return value of for_each! cout << "'" << functor.count << "' elements displayed" << endl; string str("for_each and strings!"); cout << "Sample string: " << str << endl; cout << "Characters displayed using lambda:" << endl; int numChars = 0; for_each(str.cbegin(), str.cend(), [&numChars](char c) { cout << c << ' '; ++numChars; }); cout << endl; cout << "'" << numChars << "' characters displayed" << endl; return 0;}
23.3.7 使用std::transform( )对范围进行变换
第一个版本一个接受一元函数,常用于将字符串转换为大写或小写(使用 的一元函数分别是toupper( )和tolower( )
第二个版本接受一个二元函数,让transform( )能够处理一对来自两个不同范围的元素
1234567891011121314151617181920212223242526272829303132333435363738394041//程序清单23.7 使用一元函数和二元函数的std::transform( ) #include #include #include #include #include #include using namespace std;int main(){ string str("THIS ais a TEst string!"); cout << "The sample string is: " << str << endl; string strLowerCaseCopy; strLowerCaseCopy.resize(str.size()); transform(str.cbegin(), str.cend(), strLowerCaseCopy.begin(), ::tolower); cout << "Result of transform on the string with tolower: " << endl; cout << strLowerCaseCopy << endl; vector numsInVec1{ 2017, 0, -1, 42, 10101, 25 }; vector numsInVec2(numsInVec1.size(), -1); deque sumInDeque(numsInVec1.size()); transform(numsInVec1.begin(), numsInVec1.end(), numsInVec2.begin(), sumInDeque.begin(), plus()); cout << "Result of transform using binary function plus: " << endl; cout << "Index Vector1 + Vector2 = Result(in Deque)" << endl; for (size_t i = 0; i < numsInVec1.size(); i++) { cout << i << " \t " << numsInVec1[i] << "\t+"; cout << numsInVec2[i] << " \t= "; cout << sumInDeque[i] << endl; } return 0;}
23.3.8 复制和删除操作
copy 沿向前的方向将源范围的内容赋给目标范围
123auto lastElement = copy (numsInList.cbegin(), // start source range numsInList.cend(), // end source range numsInVec.begin()); // start dest range
copy_if( )是 C++11 新增的,仅在指定的一元谓词返回true时才复制元素
123copy_if (numsInList.cbegin(), numsInList.cend(), lastElement, // copy position in dest range [](int element){return ((element % 2) == 1);});
copy_backward( )沿向后的方向将源范围的内容赋给目标范围
123copy_backward (numsInList.cbegin (), numsInList.cend (), numsInVec.end ());
remove( )将容器中与指定值匹配的元素删除
12auto newEnd = remove (numsInVec.begin (), numsInVec.end (), 0); numsInVec.erase (newEnd, numsInVec.end ());
remove_if( )使用一个一元谓词,并将容器中满足该谓词的元素删除
123newEnd = remove_if (numsInVec.begin (), numsInVec.end (), [](int num) {return ((num % 2) == 1);} ); //predicate numsInVec.erase (newEnd, numsInVec.end ()); // resizing
1234567891011121314151617181920212223242526272829303132333435363738394041//程序清单23.8 一个演示copy( )、copy_if( )、copy_backward( )、remove( )和 remove_if( )的示例,它将list的内容复制到vector中,并删除包含零或偶数的元素#include #include #include #include using namespace std;templatevoid Display(const T& input){ for (auto& elem : input) { cout << elem << ' '; } cout << "| Number of elements :" << input.size() << endl;}int main(){ list numsInList{ 2017,0,-1,42,10101,25 }; cout << "Source(list) contains: " << endl; Display(numsInList); vector numsInVec(numsInList.size() * 2); auto lastElement = copy(numsInList.begin(), numsInList.end(),numsInVec.begin()); copy_if(numsInList.begin(), numsInList.end(), lastElement, [](auto& elem) {return (elem % 2); }); cout << "Destination(vector) after copy and copy_if:" << endl; Display(numsInVec); auto newEnd = remove(numsInVec.begin(), numsInVec.end(), 0); numsInVec.erase(newEnd, numsInVec.end()); newEnd = remove_if(numsInVec.begin(), numsInVec.end(), [](auto& elem) {return (elem % 2); }); numsInVec.erase(newEnd, numsInVec.end()); cout << "Destination(vector) after remove, remove_if,erase:" << endl; Display(numsInVec); return 0;}
23.3.9 替换值以及替换满足给定条件的元素
12345678910111213141516171819202122232425262728293031323334353637//程序清单23.9 使用replace( )和replace_if( )在指定范围内替换值#include #include #include #include using namespace std;templatevoid Display(const T& input){ for (auto& elem : input) { cout << elem << ' '; } cout << "| Number of elements :" << input.size() << endl;}int main(){ vector numsInVec(6); fill(numsInVec.begin(), numsInVec.begin() + 3, 8); fill_n(numsInVec.begin() + 3, 3, 5); random_shuffle(numsInVec.begin(), numsInVec.end()); cout << "The initial contents of vector: " << endl; Display(numsInVec); cout << endl << "std::replace value 5 by 8" << endl; replace(numsInVec.begin(), numsInVec.end(), 5, 8); replace_if(numsInVec.begin(), numsInVec.end(), [](int& elem) {return !(elem % 2); },-1); cout << endl << "Vector after replacements: " << endl; Display(numsInVec); return 0;}
23.3.10 排序、在有序集合中搜索以及删除重复元素
12345678910111213141516171819202122232425262728293031323334353637383940414243444546//程序清单23.10 使用sort( )、binary_search( )和 unique( )#include #include #include #include using namespace std;templatevoid Display(const T& input){ for (auto& elem : input) { cout << elem << endl; } cout << "| Number of elements :" << input.size() << endl;}int main(){ vector vecNames{ "John", "jack","sean", "Anna" }; vecNames.push_back("jack"); cout << "The initial contents of the vector are: " << endl; Display(vecNames); cout << "The sorted vector contains names in the order:" << endl; sort(vecNames.begin(), vecNames.end()); Display(vecNames); cout << "Searching for John using binary_search:" << endl; bool element = binary_search(vecNames.begin(), vecNames.end(), "John"); if (element) { cout << "Result:John was found in the vector!" << endl; } else { cout << "Element not found" << endl; } auto newEnd = unique(vecNames.begin(), vecNames.end()); vecNames.erase(newEnd, vecNames.end()); cout << "The contents of the vector after using unique:" << endl; Display(vecNames); return 0;}
23.3.11 将范围分区
std::partition( )将输入范围分为两部分:一部分满足一元谓词;另一部分不满足。然而,std::partition( )不保证每个分区中元素的相对顺序不变。在相对顺序很重要,需要保持不变 时,应使用std::stable_partition( )
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647//程序清单23.11 使用partition( )和stable_partition( )将整型范围分为偶数值和奇数值 #include #include #include #include using namespace std;templatevoid Display(const T& input){ for (auto& elem : input) { cout << elem << endl; } cout << "| Number of elements :" << input.size() << endl;}int main(){ vector vecNames{ "John", "jack","sean", "Anna" }; vecNames.push_back("jack"); cout << "The initial contents of the vector are: " << endl; Display(vecNames); cout << "The sorted vector contains names in the order:" << endl; sort(vecNames.begin(), vecNames.end()); Display(vecNames); cout << "Searching for John using binary_search:" << endl; bool element = binary_search(vecNames.begin(), vecNames.end(), "John"); if (element) { cout << "Result:John was found in the vector!" << endl; } else { cout << "Element not found" << endl; } auto newEnd = unique(vecNames.begin(), vecNames.end()); vecNames.erase(newEnd, vecNames.end()); cout << "The contents of the vector after using unique:" << endl; Display(vecNames); return 0;}
23.3.12 在有序集合中插入元素
12345678910111213141516171819202122232425262728293031323334353637383940//程序清单23.12 使用lower_bound( )和 upper_bound( )在有序范围中插入元素#include #include #include #include #include using namespace std;templatevoid Display(const T& input){ for (auto& elem : input) { cout << elem << endl; } cout << "| Number of elements :" << input.size() << endl;}int main(){ list names{ "John","Brad","jack","sean","Anna" }; cout << "Sorted contents of the list are: " << endl; names.sort(); Display(names); cout << "Teh Lowest index where Brad can be inserted is: "; auto minPos = lower_bound(names.begin(), names.end(), "Brad"); cout << distance(names.begin(), minPos); cout << endl; cout << "Teh highest index where Brad can be inserted is: "; auto maxPos = upper_bound(names.begin(), names.end(), "Brad"); cout << distance(names.begin(), maxPos); cout << "List after inserting Brad in sorted order: " << endl; names.insert(minPos, "Brad"); Display(names); return 0;}
23.6 作业23.6.1 测验
要将list 中满足特定条件的元素删除,应使用std::remove_if( )还是list::remove_if( )?
答:list::remove_if()
假设有一个包含ContactItem对象的list,在没有显式指定二元谓词时,函数list::sort( )将如何 对这些元素进行排序?
答:将调用less<>进行排序
STL算法generate( )将调用函数generator( )多少次?
答:对每个元素调用一次
std::transform( )与 std::for_each( )之间的区别何在?
答:for_each只能接受一元谓词,transform接受一元和二元谓词
23.6.2 练习
编写一个二元谓词,它接受字符串作为输入参数,并根据不区分大小写的比较结果返回一个值。
12345678910111213141516struct Compare{ bool operator()(const string& str1, const string& str2) { string str1L; string str2L; str1L.resize(str1.size()); str2L.resize(str2.size()); transform(str1.begin(), str1.end(), str1L.begin(), ::tolower); transform(str2.begin(), str2.end(), str2L.begin(), ::tolower); return str1L < str2L; }};
演示 STL算法(如copy())如何使用迭代器实现其功能—复制两个类型不同的容器存储的序 列,而无需知道目标集合的特征。
123list names{ "John","Brad","jack","sean","Anna" };vector strs(names.size());copy(names.begin(), names.end(), strs.begin());
您正在编写一个应用程序,它按星星在地平线上升起的顺序记录它们的特点。在天文学中,星 球的大小很重要,其升起和落下的相对顺序亦如此。如果要根据星星的大小对这个集合进行排 序,应使用std::sort( )还是 std::stable_sort( )?
1std::stable_sort();
第二十四章24.1 栈和队列的行为特征**24.1.1 栈 **
栈是LIFO(后进先出)系统,只能从栈顶插入或删除元素。
**24.1.2 队列 **
队列是FIFO(先进先出)系统,元素被插入到队尾,最先插入的元素最先删除。
24.2 使用STL stack 类24.2.1 实例化stack
std::stack的定义如下
1234template < class elementType, class Container=deque > class stack;
参数elementType 是 stack 存储的对象类型。第二个模板参数Container是stack使用的默认底层容 器实现类。
12345678910111213//程序清单24.1 实例化STL stack#include #include using namespace std;int main(){ stack numsInStack; stack dblsInStack; stack> doublesStackedInVec; stack numsInStackCopy(numsInStack); return 0;}
**24.2.3 使用push( )和 pop( )在栈顶插入和删除元素 **
1234567891011121314151617181920212223242526272829//程序清单24.2 使用整型stack#include #include using namespace std;int main(){ stack numsInStack; cout << "Pushing{25,10,-1,5} on stack in that order:" << endl; numsInStack.push(25); numsInStack.push(10); numsInStack.push(-1); numsInStack.push(5); cout << "Stack contains " << numsInStack.size() << " elements" << endl; while (numsInStack.size() != 0) { cout << "Popping topmost element: " << numsInStack.top() << endl; numsInStack.pop(); } if (numsInStack.empty()) { cout << "Popping all elements empties stack!" << endl; } return 0;}
24.3 使用STL queue类**24.3.1 实例化queue **
std::queue 的定义如下
1234template < class elementType, class Container = deque > class queue;
其中elementType 是 queue 对象包含的元素的类型。Container是std::queue用于存储其数据的集合 类型
1234567891011121314//程序清单24.3 实例化STL queue#include #include using namespace std;int main(){ queue numsInQ; queue dblsInQ; queue> dbsInQInList; queue copyQ(numsInQ); return 0;}
24.3.3 使用push( )在队尾插入以及使用pop( )从队首删除
12345678910111213141516171819202122232425262728//程序清单24.4 在整型queue中插入、删除和查看元素#include #include using namespace std;int main(){ queue numsInQ; cout << "Inserting{10,5,-1,20} into queue" << endl; numsInQ.push(10); numsInQ.push(5); numsInQ.push(-1); numsInQ.push(20); cout << "Queue contains " << numsInQ.size() << " elements" << endl; cout << "Element at front: " << numsInQ.front() << endl; cout << "Element at back: " << numsInQ.back() << endl; while (numsInQ.size() != 0) { cout << "Deleting element: " << numsInQ.front() << endl; numsInQ.pop(); } if (numsInQ.empty()) { cout << "The queue is now empty!" << endl; } return 0;}
24.4 使用STL优先级队列 priority_queue与queue 的不同之处在于,包含最大值(或二元谓词认为是最大值)的元素位于队首,且只能在队首执行操作。
24.4.1 实例化priority_queue 类
std::priority_queue 类的定义如下
123456template < class elementType, class Container=vector, class Compare=less > class priority_queue
elementType 是一个模板参数,指定了优先级队列将包含的元素的类型。第二个模板参数指定 priority_queue 在内部将使用哪个集合类来存储数据,第三个参数让程序员能够指定一个二元谓词,以 帮助队列判断哪个元素应位于队首。如果没有指定二元谓词,priority_queue类将默认使用std::less,它 使用运算符<比较对象。
12345678910111213//程序清单24.5 实例化STL priority_queue#include #include using namespace std;int main(){ priority_queue numsInPrioQ; priority_queue dblsInPrioQ; priority_queue, greater> numsInDescendingQ; priority_queue copyQ(numsInPrioQ); return 0;}
24.4.3 使用push( )在 priority_queue 末尾插入以及使用 pop( )在 priority_queue 开头删除
12345678910111213141516171819202122//程序清单24.6 使用priority_queue 的成员函数push( )、top( )和 pop( )#include #include using namespace std;int main(){ priority_queue numsInPrioQ; cout << "Inserting{10,5,-1,20} into the priority_queue" << endl; numsInPrioQ.push(10); numsInPrioQ.push(5); numsInPrioQ.push(-1); numsInPrioQ.push(20); cout << "Deleting the " << numsInPrioQ.size() << " elements" << endl; while (!numsInPrioQ.empty()) { cout << "Deleting topmost element: " << numsInPrioQ.top() << endl; numsInPrioQ.pop(); } return 0;}
123456789101112131415161718192021222324//程序清单24.7 通过使用谓词将值最小的元素放在priority_queue开头#include #include #include #include using namespace std;int main(){ priority_queue, greater> numsInPrioQ; cout << "Inserting{10,5,-1,20} into the priority_queue" << endl; numsInPrioQ.push(10); numsInPrioQ.push(5); numsInPrioQ.push(-1); numsInPrioQ.push(20); cout << "Deleting the " << numsInPrioQ.size() << " elements" << endl; while (!numsInPrioQ.empty()) { cout << "Deleting topmost element: " << numsInPrioQ.top() << endl; numsInPrioQ.pop(); } return 0;}
24.7 作业24.7.1 测验
**能否修改priority_queue 的行为,使得值最大的元素最后弹出? **
答:可以
假设有一个包含Coin对象的priority_queue,要让priority_queue 将币值最大的硬币放在队首, 需要为Coin定义哪种成员运算符?
答:运算符<
假设有一个包含6个Coin对象的stack,能否访问或删除第一个插入的Coin对象?
答:不能
24.7.2 练习
邮局有一个包含人(Person类)的队列。Person包含两个成员属性,分别用于存储年龄和性别, 其定义如下,请改进这个类,使得包含其对象的priority_queue优先向老人和妇女提供服务。
123456class Person { public: int age; bool isFemale; };
12345678910111213141516class Person{public: int age; bool isFemale; bool operator<(const Person& psn) { if (age > psn.age) { return true; } else if(isFemale && psn.isFemale){ return true; } return false; }};
编写一个程序,使用stack类反转用户输入的字符串的排列顺序
123456789101112131415161718192021222324#include #include #include using namespace std;int main(){ string str("hello world"); stack charInS; cout << str << endl; for (auto ch : str) { charInS.push(ch); } while (!charInS.empty()) { cout << charInS.top(); charInS.pop(); } cout << endl; return 0;}
第二十五章25.1 bitset类 std::bitset是一个STL类,用于处理以位和位标志表示的信息
实例化std::bitset
实例化这个模板类时,必须通过一个模板参数指定实例需要管理的位数
1bitset <4> fourBits; // 4 bits initialized to 0000
还可将bitset初始化为一个用字符串字面量(char*)表示的位序列
1bitset <5> fiveBits("10101"); // 5 bits 10101
使用一个bitset来实例化另一个bitset非常简单
1bitset <8> fiveBitsCopy(fiveBits);
123456789101112131415161718192021222324//程序清单25.1 实例化std::bitset#include #include #include using namespace std;int main(){ bitset<4> fourBits; cout << "Initial contents of fourBits: " << fourBits << endl; bitset<5> fiveBits("10101"); cout << "Initial contents of fiveBits: " << fiveBits << endl; bitset<6> sixBits(0b100001); cout << "Initial contents of sixBits: " << sixBits << endl; bitset<8> eightBits(255); cout << "Initial contents of eightBits: " << eightBits << endl; bitset<8> eightBitsCopy(eightBits); return 0;}
25.2 使用std::bitset 及其成员25.2.1 std:bitset 的运算符
25.2.2 std::bitset 的成员方法
12345678910111213141516171819202122232425//程序清单25.2 使用bitset执行逻辑运算#include #include #include using namespace std;int main(){ bitset<8> inputBits; cout << "Enter a 8-bit sequence: "; cin >> inputBits; cout << "Num 1s you supplied: " << inputBits.count() << endl; cout << "Num 0s you supplied: " << inputBits.size() - inputBits.count() << endl; bitset<8> inputFlipped(inputBits); inputFlipped.flip(); cout << "Flipped version is: " << inputFlipped << endl; cout << "Result of AND, OR and XOR between the two:" << endl; cout << inputBits << " & " << inputFlipped << " = " << (inputBits & inputFlipped) << endl; cout << inputBits << " | " << inputFlipped << " = " << (inputBits | inputFlipped) << endl; cout << inputBits << " ^ " << inputFlipped << " = " << (inputBits ^ inputFlipped) << endl; return 0;}
25.3 vector**25.3.1 实例化vector **
1234567891011//程序清单25.3 实例化vector#includeusing namespace std;int main(){ vector boolFlags1; vector boolFlags2(10,true); vector boolFlags2Copy(boolFlags2); return 0;}
25.3.2 vector的成员函数和运算符
vector提供了函数 flip( ),用于将序列中的布尔值取反,这与函数bitset<>::flip( )很像
12345678910111213141516171819202122232425262728//程序清单25.4 使用vector#include#includeusing namespace std;int main(){ vector boolFlags(3); boolFlags[0] = true; boolFlags[1] = true; boolFlags[2] = !true; boolFlags.push_back(true); cout << "The contents of the vector are: " << endl; for (int i = 0; i < boolFlags.size(); i++) { cout << boolFlags[i] << ' '; } cout << endl; boolFlags.flip(); cout << "The contents of the vector are: " << endl; for (int i = 0; i < boolFlags.size(); i++) { cout << boolFlags[i] << ' '; } return 0;}
25.6 作业25.6.1 测验
bitset 能否扩展其内部缓冲区以存储可变的元素数?
答:不能
为什么bitset 不属于STL容器类?
答:不能动态调整长度,不支持迭代器
您会使用std::vector 来存储位数在编译阶段就知道的固定位数吗?
答:不会
25.6.2 练习
创建一个长4位的bitset对象,并使用一个数字来初始化它,然后显示结果并将其与另一个bitset 对象相加(注意:bitsets不支持语法bitsetA = bitsetX + bitsetY)。
123456789101112131415#include#includeusing namespace std;int main(){ bitset<4> bit1(3); bitset<4> bit2(4); bitset<4> bit3(bit1.to_ullong() + bit2.to_ullong()); cout << "bit1 = " << bit1 << endl; cout << "bit2 = " << bit2 << endl; cout << "bit3 = " << bit3 << endl;18214341174 return 0;}
请演示如何将bitset对象中的位取反。
bit.flip();
第二十六章26.1 什么是智能指针 智能指针是包含重载运算符的类,其行为像常规指针,但智能指针能够及时、妥 善地销毁动态分配的数据,并实现了明确的对象生命周期,因此更有价值。
26.2 智能指针是如何实现的 这个问题暂时可以简化为:“智能指针 spData 是如何做到像常规指针那样的?”答案如下:智能 指针类重载了解除引用运算符(*)和成员选择运算符(->),让程序员可以像使用常规指针那样使用 它们。
1234567891011121314151617181920212223//程序清单26.1 智能指针类最基本的组成部分templateclass SmartPointer{public: SmartPointer(T* pData) : rawPtr{ pData } {} ~SmartPointer() { delete pDada; } SmartPointer(const SmartPointer& anotherSP); SmartPointer& operator=(const SmartPointer& anotherSP); T& operator*() const { return *(rawPtr); } T* operator->() const { return rawPtr; }private: T* rawPtr;};
26.3 智能指针类型 内存资源管理(即实现的内存所有权模型)是智能指针类与众不同的地方。智能指针决定在复制 和赋值时如何处理内存资源。智能指针的分类实际上就是内存资源管理策略的分类,可分为如下几类
深复制
写时复制
引用计数
引用链接
破坏性复制
26.3.1 深复制
在实现深复制的智能指针中,每个智能指针实例都保存一个它管理的对象的完整副本。每当智能 指针被复制时,将复制它指向的对象(因此称为深复制)
12345678910111213141516171819//程序清单26.2 使用基于深复制的智能指针将多态对象作为基类对象进行传递templateclass DeepcopySmartPtr{public: DeepcopySmartPtr(const DeepcopySmartPtr& source) { object = source->Clone(); } DeepcopySmartPtr& operator=(const DeepcopySmartPtr& source) { if (object) delete object; object = source->Clone(); }private: T* object;};
26.3.2 写时复制机制
写时复制机制(Copy on Write,COW)试图对深复制智能指针的性能进行优化,它共享指针,直 到首次写入对象。首次调用非const函数时,COW指针通常为该非const函数操作的对象创建一个副 本,而其他指针实例仍共享源对象
26.3.3 引用计数智能指针
引用计数是一种记录对象的用户数量的机制。当计数降低到零后,便将对象释放。因此,引用计 数提供了一种优良的机制,使得可共享对象而无法对其进行复制。这种智能指针被复制时,需要将对象的引用计数加1。至少有两种常用的方法来跟踪计数
在对象中维护引用计数
引用计数由共享对象中的指针类维护
26.3.4 引用链接智能指针
引用链接智能指针不主动维护对象的引用计数,而只需知道计数什么时候变为零,以便能够释放 对象
26.3.5 破坏性复制
破坏性复制是这样一种机制,即在智能指针被复制时,将对象的所有权转交给目标指针并重置原 来的指针
123456789101112131415161718192021222324252627282930313233343536//程序清单26.3 一个破坏性复制智能指针#include#includeusing namespace std;templateclass DestructivecopyPtr{public: DestructivecopyPtr(T* input) : object(input) {} ~DestructivecopyPtr() { delete object; } DestructivecopyPtr(DestructivecopyPtr& source) { object = source.object; source.object = 0; } DestructivecopyPtr& operator=(DestructivecopyPtr& source) { if (object != source.object) { if (object) delete object; object = source.object; source.object = 0; } }private: T* object;};int main(){ DestructivecopyPtr num(new int); DestructivecopyPtr copy = num; return 0;}
26.3.6 使用std::unique_ptr
unique_ptr 是一种简单的智能指针,其复制构造函数和 赋值运算符被声明为私有的,因此不能复制它,即不能将其按值传递给函数,也不能将其赋给其他指 针
1234567891011121314151617181920212223242526272829//程序清单26.4 使用std::unique_ptr#include#includeusing namespace std;class Fish{public: Fish() { cout << "Fish: Constructed!" << endl; } ~Fish() { cout << "Fish: Destructed!" << endl; } void Swim() const { cout << "Fish swims in water" << endl; }};void MakeFishSwim(const unique_ptr& inFish){ inFish->Swim();}int main(){ unique_ptr smartFish(new Fish); smartFish->Swim(); MakeFishSwim(smartFish); unique_ptr copy; //copy = smartFish; //error: operator= is private return 0;}
26.7 作业26.7.1 测试
为应用程序编写自己的智能指针前应查看什么地方?
答:www.boost.org
智能指针是否会严重降低应用程序的性能?
答:不会
引用计数智能指针在什么地方存储引用计数?
答:
在对象中维护引用计数
引用计数由共享对象中的指针类维护
引用链接指针使用的链表机制是单向链表还是双向链表?
答:双向链表
26.7.2 练习
查错:指出下述代码中的错误
1234std::auto_ptr object (new SampleClass ()); std::auto_ptr anotherObject (object); object->DoSomething (); anotherObject->DoSomething();
答:auto_ptr会在复制时使其失去所有权,object->DoSomething (); 无效
使用 unique_ptr 类实例化一个Carp对象,而Carp类继承了Fish类。将该对象作为Fish指针传 递时是否会出现切除问题
12345678910111213141516171819202122232425262728#include#includeusing namespace std;class Fish{public: Fish() { cout << "Fish: Constructed!" << endl; } ~Fish() { cout << "Fish: Destructed!" << endl; } void Swim() const { cout << "Fish swims in water" << endl; }};class Carp : public Fish{};void MakeFishSwim(const unique_ptr& inFish){ inFish->Swim();}int main(){ unique_ptr smartFish(new Carp); smartFish->Swim(); MakeFishSwim(smartFish); return 0;}
答:使用引用传递,不是值传递,不会切除问题
查错:指出下述代码中的错误
123std::unique_ptr myTuna (new Tuna); unique_ptr copyTuna; copyTuna = myTuna;
答:unique_ptr不允许复制
第二十七章27.2 重要的C++流类和流对象
27.3 使用std::cout 将指定格式的数据写入控制台27.3.1 使用std::cout 修改数字的显示格式
123456789101112131415161718192021222324//程序清单27.1 使用cout和控制符以十进制、十六进制和八进制格式显示整数#include#includeusing namespace std;int main(){ cout << "Enter an integer: "; int input = 0; cin >> input; cout << "Integer in octal: " << oct << input << endl; cout << "Integer in hexadecimal: " << hex << input << endl; cout << "Integer in hex using base notations: "; cout << setiosflags(ios_base::hex | ios_base::showbase | ios_base::uppercase); cout << input << endl; cout << "Integer after resetting I/O flags: " << endl; cout << resetiosflags(ios_base::hex | ios_base::showbase | ios_base::uppercase); cout << input << endl; return 0;}
1234567891011121314151617181920212223242526272829//程序清单27.2 使用cout以定点表示法和科学表示法显示Pi和圆面积#include#includeusing namespace std;int main(){ const double Pi = (double)22.0 / 7; cout << "Pi = " << Pi << endl; cout << endl << "Setting precision to: 7" << endl; cout << setprecision(7); cout << "Pi = " << Pi << endl; cout << fixed << "Fixed Pi = " << Pi << endl; cout << scientific << "Scientific Pi = " << Pi << endl; cout << endl << "Setting precision to 10: " << endl; cout << setprecision(10); cout << "Pi = " << Pi << endl; cout << fixed << "Fixed Pi = " << Pi << endl; cout << scientific << "Scientific Pi = " << Pi << endl; cout << endl << "Enter a radius: "; double radius = 0.0; cin >> radius; cout << "Area of circle: " << 2 * Pi * radius << endl; return 0;}
27.3.2 使用std::cout 对齐文本和设置字段宽度
可使用setw( )控制符来设置字段宽度,插入到流中的内容将在指定宽度内右对齐。在这种情况下, 还可使用setfill( )指定使用什么字符来填充空白区域
123456789101112131415161718//程序清单27.3 使用控制符setw( )设置字段宽度,并使用setfill( )指定填充字符#include#includeusing namespace std;int main(){ cout << "Hey - default!" << endl; cout << setw(35); cout << "Hey - right aligned!" << endl; cout << setw(35) << setfill('*') << endl; cout << "Hey - right aligned!" << endl; cout << "Hey - back to default!" << endl; return 0;}
27.4 使用std::cin 进行输入27.4.1 使用std::cin 将输入读取到基本类型变量中
123456789101112131415161718192021222324//程序清单27.4 使用cin将输入读取到int变量中,将使用科学表示法的浮点数读取到double变量中,将三个字符分别读取到char变量中 #includeusing namespace std;int main(){ cout << "Enter an integer: "; int inputNum = 0; cin >> inputNum; cout << "Enter the value of Pi: "; double Pi = 0.0; cin >> Pi; cout << "Enter three characters separated by space: " << endl; char char1, char2, char3; cin >> char1 >> char2 >> char3; cout << "The recorded variable values are: " << endl; cout << "inputNum: " << inputNum << endl; cout << "Pi: " << Pi << endl; cout << "The three characters: " << char1 << char2 << char3 << endl; return 0;}
27.4.2 使用std::cin:get 将输入读取到char*缓冲区中
123456789101112//程序清单27.5 插入到char缓冲区中时不超越其边界#includeusing namespace std;int main(){ cout << "Enter a line: " << endl; char charBuf[10] = { 0 }; cin.get(charBuf, 9); cout << "charBuf: " << charBuf << endl; return 0;}
27.4.3 使用std::cin 将输入读取到std::string 中
12345678910111213//程序清单27.6 使用cin将文本插入到std::string中#include#includeusing namespace std;int main(){ cout << "Enter your name: " << endl; string name; cin >> name; cout << "Hi " << name << endl; return 0;}
12345678910111213//程序清单27.7 使用getline( )和 cin 读取整行用户输入#include#includeusing namespace std;int main(){ cout << "Enter your name: " << endl; string name; getline(cin,name); cout << "Hi " << name << endl; return 0;}
27.5 使用std::fstream 处理文件 C++提供了std::fstream,旨在以独立于平台的方式访问文件。std::fstream从std::ofstream那里继承 了写入文件的功能,并从std::ifstream那里继承了读取文件的功能。
27.5.1 使用open( )和 close( )打开和关闭文件
要使用fstream、ofstream 或 ifstream 类,需要使用方法open( )打开文件。open( )接受两个参数:第一个是要打开的文件的路径和名称(如果没有提供路径,将假定为应用 程序的当前目录设置);第二个是文件的打开模式
1234567fstream myFile; myFile.open("HelloFile.txt",ios_base::in|ios_base::out|ios_base::trunc); if (myFile.is_open()) // check if open() succeeded { // do reading or writing here myFile.close(); }
还有另一种打开文件流的方式,那就是使用构造函数
fstream myFile("HelloFile.txt",ios_base::in|ios_base::out|ios_base::trunc);
如果只想打开文件进行写入,可使用如下代码
ofstream myFile("HelloFile.txt", ios_base::out);
如果只想打开文件进行读取,可使用如下代码
ifstream myFile("HelloFile.txt", ios_base::in);
可在下述各种模式下打开文件流
ios_base::app:附加到现有文件末尾,而不是覆盖它
ios_base::ate:切换到文件末尾,但可在文件的任何地方写入数据
ios_base::trunc:导致现有文件被覆盖,这是默认设置
ios_base::binary:创建二进制文件(默认为文本文件)
ios_base::in:以只读方式打开文件
ios_base::out:以只写方式打开文件
27.5.2 使用open( )创建文本文件并使用运算符<<写入文本
123456789101112131415161718192021//程序清单27.8 使用ofstream新建一个文本文件并向其中写入文本#include#includeusing namespace std;int main(){ ofstream myFile; myFile.open("./test.txt", ios_base::out); if (myFile.is_open()) { cout << "File open successful" << endl; myFile << "My first text file!" << endl; myFile << "Hello file"; cout << "Finished writing to file, will close now" << endl; myFile.close(); } return 0;}
27.5.3 使用open( )和运算符>>读取文本文件
要读取文件,可使用fstream或ifstream,并使用标志ios_base::in打开它
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556//程序清单27.9 从程序清单27.8创建的文件HelloFile.txt中读取文本#include#include#includeusing namespace std;int main(){ ifstream myFile; myFile.open("./test.txt", ios_base::in); if (myFile.is_open()) { cout << "File open successful. It contains: " << endl; string fileContents; while (myFile.good()) { getline(myFile, fileContents); cout << fileContents << endl; } cout << "Finished reading file, will close now" << endl; myFile.close(); } else { cout << "open() failed: check if file is in right folder" << endl; } return 0;}#include#include#includeusing namespace std;int main(){ ifstream myFile; myFile.open("./test.txt", ios_base::in); if (myFile.is_open()) { cout << "File open successful. It contains: " << endl; string fileContents; while (myFile.good()) { getline(myFile, fileContents); cout << fileContents << endl; } cout << "Finished reading file, will close now" << endl; myFile.close(); } else { cout << "open() failed: check if file is in right folder" << endl; } return 0;}
27.5.4 读写二进制文件 写入二进制文件的流程与前面介绍的流程差别不大,重要的是在打开文件时使用 ios_base::binary 标志。通常使用ofstream::write 和ifstream::read 来读写二进制文件
12345678910111213141516171819202122232425262728293031323334353637383940414243444546//程序清单27.10 将一个结构写入二进制文件并使用该文件的内容创建一个结构#include#include#includeusing namespace std;struct Human{ char name[30]; int age; char DOB[20]; Human() {}; Human(const char* inName, int inAge, const char* inDOB) : age(inAge) { strcpy(name, inName); strcpy(DOB, inDOB); }};int main(){ Human Input("Siddhartha Rao", 101, "May 1916"); ofstream fsOut("./MyBinary.bin", ios_base::out | ios_base::binary); if (fsOut.is_open()) { cout << "Writing one object of Human to a binary file" << endl; fsOut.write(reinterpret_cast(&Input), sizeof(Input)); fsOut.close(); } ifstream fsIn("./MyBinary.bin", ios_base::in | ios_base::binary); if (fsIn.is_open()) { Human somePerson; fsIn.read((char*)&somePerson, sizeof(somePerson)); cout << "Reading information from binary file: " << endl; cout << "Name = " << somePerson.name << endl; cout << "Age = " << somePerson.age << endl; cout << "Date of Birth = " << somePerson.DOB << endl; } return 0;}
27.6 使用std::stringstream 对字符串进行转换12345678910111213141516171819202122232425262728//程序清单27.11 使用std::stringstream 在整型和字符串之间进行转换 #include#include#includeusing namespace std;int main(){ cout << "Enter an integer: " << endl; int input = 0; cin >> input; stringstream converterStream; converterStream << input; string inputAsStr; converterStream >> inputAsStr; cout << "Integer Input = " << input << endl; cout << "String gained from integer = " << inputAsStr << endl; stringstream anotherStream; anotherStream << inputAsStr; int Copy = 0; anotherStream >> Copy; cout << "Integer gained from string, Copy = " << Copy << endl; return 0;}
27.9 作业27.9.1 测验
在只需写入文件的情况下,应使用哪种流?
答:ofstream
如何使用cin从输入流中获取一整行?
答:使用cin.getline;
在需要将std::string 对象写入文件时,应使用ios_base::binary 模式吗?
答: 不应该,string包含文本信息,
使用open( )打开流后,为何还要使用is_open( )进行检查?
答:检测文件是否正确打开
27.9.2 练习
查错:找出下述代码中的错误
1234fstream myFile; myFile.open("HelloFile.txt", ios_base::out); myFile << "Hello file!"; myFile.close();
答:没有检测文件是否正确打开
查错:找出下述代码中的错误
123456ifstream myFile("SomeFile.txt"); if(myFile.is_open()) { myFile << "This is some text" << endl; myFile.close(); }
答:写入应该使用ofstream
第二十八章28.2 导致异常的原因 异常可能是外部因素导致的,如系统没有足够的内存;也可能是应用程序内部因素导致的,如使 用的指针包含无效值或除数为零。为了向调用者指出错误,有些模块引发异常。
28.3 使用try和catch捕获异常28.3.1 使用catch(…)处理所有异常
1234567891011121314151617181920//程序清单28.1 使用try和catch捕获并处理内存分配异常#includeusing namespace std;int main(){ cout << "Enter number of integers you wish to reserve: "; try { int input = 0; cin >> input; int* numArray = new int[input]; delete[] numArray; } catch (...) { cout << "Exception occurred. Got to end, sorry!" << endl; } return 0;}
28.3.2 捕获特定类型的异常
12345678910111213141516171819202122//程序清单28.2 使用try和catch捕获并处理内存分配异常 #include#includeusing namespace std;int main(){ cout << "Enter number of integers you wish to reserve: "; try { int input = 0; cin >> input; int* numArray = new int[input]; delete[] numArray; } catch (bad_alloc& exp) { cout << "Exception occurred.: " << exp.what() << endl; cout << "Got to end, sorry!" << endl; } return 0;}
28.3.3 使用throw引发特定类型的异常
程序清单28.2捕获std::bad_alloc 时,实际上是捕获new引发的std::bad_alloc类对象。您可以引发 自己选择的异常,为此只需使用关键字throw
12345678910111213141516171819202122232425262728293031//程序清单28.3 在试图除以零时引发一种自定义异常#include#includeusing namespace std;double Divide(double dividend, double divisor){ if (divisor == 0) throw "Dividing by 0 is a cirme"; return (dividend / divisor);}int main(){ cout << "Enter dividend: "; double dividend = 0; cin >> dividend; cout << "Enter divisor: "; double divisor = 0; cin >> divisor; try { cout << "Result is: " << Divide(dividend, divisor) << endl; } catch (const char* exp) { cout << "Exception: " << exp << endl; cout << "Sorry, can't continue!" << endl; } return 0;}
28.4 异常处理的工作原理 理程序catch(char)捕获它。 每当您使用throw 引发异常时,编译器都将查找能够处理该异常的 catch(Type)。异常处理逻辑首 先检查引发异常的代码是否包含在try块中,如果是,则查找可处理这种异常的catch(Type)。如果throw 语句不在try块内,或者没有与引发的异常兼容的catch( ),异常处理逻辑将继续在调用函数中寻找。 因此,异常处理逻辑沿调用栈向上逐个地在调用函数中寻找,直到找到可处理异常的 catch(Type)。在 退栈过程的每一步中,都将销毁当前函数的局部变量,因此这些局部变量的销毁顺序与创建顺序相反*
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455#include#includeusing namespace std;struct StructA{ StructA() { cout << "StructA constructor" << endl; } ~StructA() { cout << "StructA destructor" << endl; }};struct StructB{ StructB() { cout << "StructB constructor" << endl; } ~StructB() { cout << "StructB destructor" << endl; }};void FuncB(){ cout << "In Func B" << endl; StructA objA; StructB objB; cout << "FuncA: returning to caller" << endl; throw "Throwing for the heck of it";}void FuncA(){ try { cout << "In Func A" << endl; StructA objA; StructB objB; FuncB(); cout << "FuncA: returning to caller" << endl; } catch (const char* exp) { cout << "FuncA: Caught exception: " << exp << endl; cout << "Handled it, will not throw to caller" << endl; }}int main(){ cout << "main() : Started execution " << endl; try { FuncA(); } catch (const char* exp) { cout << "Exception: " << exp << endl; } cout << "main(): exiting gracefully" << endl; return 0;}
28.4.1 std::exception 类
下述重要异常类都是从std::exception派生而来的
bad_alloc:使用 new 请求内存失败时引发
bad_cast:试图使用 dynamic_cast 转换错误类型(没有继承关系的类型)时引发
ios_base::failure:由 iostream 库中的函数和方法引发
std::exception 类是异常基类,它定义了虚方法what( );这个方法很有用且非常重要,详细地描述 了导致异常的原因
28.4.2 从std::exception 派生出自定义异常类
12345678910111213141516171819202122232425262728293031323334353637383940414243444546//程序清单28.5 继承std::exception 的 CustomException 类#include#include#includeusing namespace std;class CustomException : public exception{public: CustomException(const char* why) :reason{ why } {} virtual const char* what() const { return reason.c_str(); }private: string reason;};double Divide(double dividend, double divisor){ if (divisor == 0) throw CustomException("CustomException: Dividing by 0 is a crime"); return (dividend / divisor);}int main(){ cout << "Enter dividend: "; double dividend = 0; cin >> dividend; cout << "Enter divisor: "; double divisor = 0; cin >> divisor; try { cout << "Result is: " << Divide(dividend, divisor) << endl; } catch (exception& exp) { cout << exp.what() << endl; cout << "Sorry, can't continue!" << endl; } return 0;}
28.7 作业**28.7.2 练习 **
std::exception 是什么?
答:是所有异常类的基类
使用new分配内存失败时,将引发哪种异常?
答:bad_alloc
在异常处理程序(catch块)中,为大量int变量分配内存以便备份数据合适吗?
答:不合适,避免资源消耗或复杂操作
假设有一个异常类MyException,它继承了std::exception,您将如何捕获这种异常对象?
答:使用catch(exception &exp)进行捕获
**28.7.2 练习 **
查错:下述代码有何错误
12345678910class SomeIntelligentStuff { bool isStuffGoneBad; public: ~SomeIntelligentStuff() { if(isStuffGoneBad) throw "Big problem in this class, just FYI"; } };
答:不要在析构函数中引发异常
查错:下述代码有何错误
123456int main() { int* millionNums = new int [1000000]; // do something with the million integers delete []millionNums; }
答:没有处理可能引发异常的代码
查错:下述代码有何错误
1234567891011121314int main() { try { int* millionNums = new int [1000000]; // do something with the million integers delete []millionNums; } catch(exception& exp) { int* anotherMillion = new int [1000000]; // take back up of millionNums and save it to disk } }
答:不要在catch中分配内存,如果try块内的代码分配内存失败将导致恶性循环