一.
该函数模板中,调用的实参不能为两个不同的类型
/* 重要.cpp */
// Eg9-1.cpp
#include <iostream>
//注意一点,max与min使用的时候,容易引起冲突,如果写了下面这一行代码,则要改变函数模板名字,否则直接使用std::cout与std::endl
using namespace std;
/*
不要把这里的class与类的声明关键字class混淆在一起,虽然它们由相同的字母组成,但含义是不同的。
这里的class表示T是一个类型参数,可以是任何数据类型,如int、float、char等,或者用户定义的struct、enum或class等自定义数据类型。
*/
template <class T> T Min(T a, T b) { return (a < b) ? a : b; }
/*
为了区别类与模板参数中的类型关键字class,标准C++提出了用typename作为模板参数的类型关键字,同时也支持使用class。
比如,把min定义的template <class T>写成下面的形式是完全等价的:
*/
template <typename T> T myMin(T a, T b) { return (a < b) ? a : b; }
/*
模板实例化发生在调用模板函数时。当编译器遇到程序中对函数模板的调用时,
它才会根据调用语句中实参的具体类型,确定模板参数的数据类型,
并用此类型替换函数模板中的模板参数,生成能够处理该类型的函数代码,即模板函数。
当多次发生类型相同的参数调用时,只在第1次进行实例化。编译器只在第1次调用时生成模板函数,
当之后遇到相同类型的参数调用时,不再生成其他模板函数,它将调用第1次实例化生成的模板函数。
*/
int main() {
double a = 2, b = 3.4;
float c = 2.3, d = 3.2;
cout << "2,3 的最小值是:" << Min<int>(2, 3) << endl; //显式调用
cout << "2,3.4 的最小值是:" << Min(a, b) << endl; //隐式调用
cout << "'a','b' 的最小值是:" << Min('a', 'b') << endl;
cout << "2.3,3.2的最小值是:" << Min(c, d) << endl;
cout << "2.3,3.2的最大值是:" << std::min(c, d)
<< endl; //引用命名空间内部的最小值函数
cout << "2.3,3.2的最小值是:" << myMin(c, d) << endl; //更换class为typename
// cout<<"2,'a' 的最小值是:"<<Min(2,'a')<<endl;
// //报错,不同类型无法处理,请看9-3-1.cpp
return 0;
}
cout<<"2,'a' 的最小值是:"<<Min((int)a,2)<<endl;改成这样即可
二.map容器
"Map" 是一种常用的数据结构,它允许你存储键值对(key-value pairs)。在许多编程语言中,你都可以找到类似的实现。我将为你列举一些常见的编程语言及其对应的 map 容器库,并简要介绍如何使用它们。
- C++ -
std::map
C++ 的标准库提供了一个 std::map
容器,你可以使用它来存储键值对。
#include <iostream>
#include <map>
int main() {
std::map<std::string, int> my_map;
my_map["apple"] = 1;
my_map["banana"] = 2;
my_map["cherry"] = 3;
std::cout << "Banana: " << my_map["banana"] << std::endl;
return 0;
}
// 引入需要的头文件
#include <iostream> // 引入输入输出流库
#include <map> // 引入map容器库
#include <string> // 引入字符串库
using namespace std; // 使用标准命名空间,这样我们就可以直接使用如cout, cin等而不需要std::cout, std::cin等
// 程序主函数
int main(int argc, char const *argv[]) {
// 定义两个数组,分别存储员工姓名和工资
string name[] = {"张三", "李四", "王麻子"};
double salary[] = {1200, 2000, 1450};
// 定义一个map容器来存储员工的姓名和工资,其中key为员工姓名,value为员工工资
map<string, double> sal;
// 定义一个迭代器来遍历map容器
map<string, double>::iterator p;
// 使用循环将前面定义的员工姓名和工资插入到map中
for (int i = 0; i < 3; i++) {
sal.insert(make_pair(name[i], salary[i]));
}
// 插入额外的员工信息
sal["tom"] = 6156;
sal["bob"] = 5999;
// 输出所有的员工姓名和工资信息
for (p = sal.begin(); p != sal.end(); p++) {
cout << p->first << "\t" << p->second << endl;
}
// 提示用户输入要查找的员工姓名
string person;
cout << "输入查找人员的姓名:";
cin >> person;
int flag = 1; // 定义一个标记,用于判断查找是否成功
// 在map中查找用户输入的员工姓名,并输出其工资信息
for (p = sal.begin(); p != sal.end(); p++)
if (p->first == person) {
cout << p->second << endl;
flag = 0; // 查找成功,将标记设为0
}
// 如果查找失败,则输出提示信息
if (flag)
cout << "没查找到对应的结果!" << endl;
// 提示用户输入要删除的员工姓名
cout << "输入待删除的人员的姓名:";
cin >> person;
map<string, double>::iterator it;
it = sal.find(person); // 在map中查找用户输入的员工姓名
// 如果查找成功,则删除该员工信息,并输出删除成功的信息
if (it != sal.end()) {
cout << "查找成功:" << (*it).first << ":" << (*it).second << endl;
sal.erase(it); // 删除找到的员工信息
cout << "删除成功" << endl;
}
// 输出删除后的所有员工姓名和工资信息
cout << "删除后的结果为" << endl;
for (p = sal.begin(); p != sal.end(); p++) {
cout << p->first << p->second << endl;
}
return 0; // 程序正常结束,返回0
}
上面这个代码可能更易于理解
三.try示例
这段代码演示了C++中的异常处理机制,具体解释如下:
// Eg10-1.cpp
#include <iostream> // 引入输入/输出流库
using namespace std; // 使用标准命名空间,使得可以不必前缀std::来调用标准库的成员
int main() { // 主函数
cout << "1--before try block..." << endl; // 输出“1--before try block...”并换行
try { // 尝试执行代码块,如果发生异常,则会跳转到匹配的catch块处理
cout << "2--Inside try block..." << endl; // 输出“2--Inside try block...”并换行
throw 10; // 抛出一个整型异常,值为10
cout << "3--After throw ...." << endl; // 这行代码不会被执行,因为throw会立即跳出try块
} catch (int i) { // 捕获整型异常
cout << "4--In catch block1 ... exception..errcode is.." << i << endl; // 输出“4--In catch block1 ... exception..errcode is..”和异常值i,并换行
} catch (char *s) { // 捕获字符指针异常(这个catch块在这里不会被触发,因为没有抛出字符指针类型的异常)
cout << "5--In catch block2 ... exception..errcode is.." << s << endl; // 输出“5--In catch block2 ... exception..errcode is..”和异常值s,并换行
}
cout << "6--After Catch..."; // 输出“6--After Catch...”
}
注意:在try
块中,一旦throw
语句被执行,try
块中的剩余代码将不会被执行。程序会直接跳到与抛出异常类型相匹配的catch
块中。如果没有匹配的catch
块,程序会终止。
// 引入输入/输出流库,使程序可以使用cin,cout等I/O功能
#include <iostream>
// 使用标准命名空间,使得可以不必前缀std::来调用标准库的成员
using namespace std;
// 定义一个名为temperature的函数,接受一个整数参数t
void temperature(int t) {
// 如果t等于100
if (t == 100)
// 抛出一个字符串类型的异常,异常信息为"沸点!"
throw "沸点!";
// 否则如果t等于0
else if (t == 0)
// 抛出一个字符串类型的异常,异常信息为"冰点!"
throw "冰点!";
// 否则
else {
// 输出t的值,提示当前温度
cout << "temperatore=" << t << endl;
}
}
// 主函数,程序的入口点
int main() {
try {
// 尝试调用temperature函数,传入参数0
// 如果发生异常,会进入catch块处理
temperature(0); // L1
// 尝试调用temperature函数,传入参数10
temperature(10); // L2
// 尝试调用temperature函数,传入参数100
temperature(100); // L3
} catch (char const *s) {
// 如果捕获到异常,输出异常信息
cout << s << endl;
}
// 主函数返回0,表示程序正常结束
return 0;
}
#include <iostream>
using namespace std;
void temperature(int t) {
try {
if (t == 100)
throw "It's at the boiling point.";
else if (t == 0)
throw "It reached the freezing point";
else
cout << "the temperature is OK..." << endl;
throw t;
}
catch (int x) {
cout << "temperature=" << x << endl;
}
catch (char const* s) {
cout << s << endl;
}
}
int main() {
temperature(0); // L1
temperature(10); // L2
temperature(100); // L3
return 0;
}
这两个程序就因为try的位置不同,而使得输出结果大不相同。
看下面的例子
// 引入iostream库,用于输入输出功能
#include <iostream>
// 使用std命名空间,使得我们可以直接使用iostream库中的成员,如cout,endl等
using namespace std;
// 定义一个名为handler的函数,它接受一个整数参数n,并可能抛出三种类型的异常:int,char和double
void handler(int n) throw(int, char, double) {
// 如果n等于1,抛出一个整数类型的异常,值为1
if (n == 1)
throw n;
// 如果n等于2,抛出一个字符类型的异常,值为字符'x'
if (n == 2)
throw 'x';
// 如果n等于3,抛出一个双精度浮点数类型的异常,值为1.1
if (n == 3)
throw 1.1;
}
// 定义主函数main
int main() {
// 输出"Before handler..."
cout << "Before handler..." << endl;
// 使用try块来包围可能抛出异常的代码
try {
// 调用handler函数并传入参数1,可能抛出异常
handler(1);
} catch (int i) {
// 如果抛出的是整数类型的异常,捕获并处理,输出"catch an integer..."
cout << "catch an integer..." << endl;
} catch (char c) {
// 如果抛出的是字符类型的异常,捕获并处理,输出"catch an char..."
cout << "catch an char..." << endl;
} catch (double d) {
// 如果抛出的是双精度浮点数类型的异常,捕获并处理,输出"catch an double..."
cout << "catch an double..." << endl;
}
// 主函数返回0,表示程序正常结束
return 0;
}
如果你从函数handler
中移除了throw(int, char, double)
,程序将无法编译并运行。这是因为在C++中,throw
关键字用于声明函数可能会抛出的异常类型。如果你移除了这个声明,编译器将无法知道函数可能会抛出哪些类型的异常,因此会报错。
如果你希望函数不抛出任何异常,可以将throw()
留空,这样编译器将认为该函数不会抛出任何异常。
void handler(int n) throw() {
// 函数实现
}
如果你去掉了throw(int, char, double)
这部分代码,程序仍然能够成功运行,那么可能是因为你没有在函数handler
中实际抛出任何异常。在这种情况下,移除throw
声明不会影响到程序的运行。
然而,如果你希望函数能够抛出异常并在调用该函数的地方捕获这些异常,那么你需要保留throw
声明,并确保函数内部有相应的throw
语句来抛出异常。
总之,如果你去掉了throw(int, char, double)
这部分代码并且程序能够成功运行,可能是因为你在函数中没有实际抛出任何异常,或者你没有在程序中捕获这些异常。建议你仔细检查代码,确保异常处理机制的正确性和健壮性。
在C++中,如果子类和父类有同名的成员函数,程序将使用子类的成员函数。这被称为函数重写(function overriding),它是面向对象编程的一个重要特性。
如果在子类中需要调用父类的同名函数,可以使用作用域解析运算符 ::
来显式地指定使用父类的成员函数。
// Eg10-12.cpp
#include <iostream>
using namespace std;
class BasicException {
public:
char *Where() { return "BasicException..."; }
};
class FileSysException : public BasicException {
public:
char *Where() { return "FileSysException..."; }
};
class FileNotFound : public FileSysException {
public:
char *Where() { return "FileNotFound..."; }
};
class DiskNotFound : public FileSysException {
public:
char *Where() { return "DiskNotFound..."; }
};
int main() {
try {
// ..... //程序代码
throw FileSysException();
} catch (DiskNotFound p) {
cout << p.Where() << endl;
} catch (FileNotFound p) {
cout << p.Where() << endl;
} catch (FileSysException p) {
cout << p.Where() << endl;
} catch (BasicException p) {
cout << p.Where() << endl;
}
try {
// ..... //程序代码
throw DiskNotFound();
} catch (BasicException p) {
cout << p.Where() << endl;
} catch (FileSysException p) {
cout << p.Where() << endl;
} catch (DiskNotFound p) {
cout << p.Where() << endl;
} catch (FileNotFound p) {
cout << p.Where() << endl;
}
}