主要包括:
异常处理
强制转换
智能指针
auto,decltype
lambda表达式
具体异常情况:
异常处理机制:
函数 A 在执行过程中发现异常时可以不加处理,而只是“拋出一个异常”给 A 的调用者,假定为函数 B。
拋出异常而不加处理会导致函数 A 立即中止,在这种情况下,函数 B 可以选择捕获 A 拋出的异常进行处理,也可以选择置之不理。
如果置之不理,这个异常就会被拋给 B 的调用者,以此类推。如果一层层的函数都不处理异常,异常最终会被拋给最外层的 main 函数。main 函数应该处理异常。如果main函数也不处理异常,那么程序就会立即异常地中止。
具体实现方法:
通过 throw 语句和 try...catch 语句实现对异常的处理。
throw 表达式;
该语句拋出一个异常。
异常是一个表达式,其值的类型可以是基本类型,也可以是类。
try {
语句组
}
catch(异常类型) {
异常处理代码
}
...
catch(异常类型) {
异常处理代码
}
catch 可以有多个,但至少要有一个。
应用举例展示:
#include <iostream>
using namespace std;
int main()
{
double m ,n;
cin >> m >> n;
try {
cout << "before dividing." << endl;
if( n == 0)
throw -1; //抛出int类型异常
else
cout << m / n << endl;
cout << "after dividing." << endl;
}
catch(double d) {
cout << "catch(double) " << d << endl;
}
catch(int e) {
cout << "catch(int) " << e << endl;
}
cout << "finished" << endl;
return 0;
}
结果显示:
如果希望不论拋出哪种类型的异常都能捕获,可以编写如下 catch 块:
由于catch(...)
能匹配任何类型的异常,它后面的 catch 块实际上就不起作用,因此不要将它写在其他 catch 块前面。
catch(...) {
...
}
#include <iostream>
using namespace std;
int main()
{
double m, n;
cin >> m >> n;
try {
cout << "before dividing." << endl;
if (n == 0)
throw - 1; //抛出整型异常
else if (m == 0)
throw - 1.0; //拋出 double 型异常
else
cout << m / n << endl;
cout << "after dividing." << endl;
}
catch (double d) {
cout << "catch (double)" << d << endl;
}
catch (...) {
cout << "catch (...)" << endl;
}
cout << "finished" << endl;
return 0;
}
结果显示:
特殊处理:
如果一个函数在执行过程中拋出的异常在本函数内就被 catch 块捕获并处理,那么该异常就不会拋给这个函数的调用者(也称为“上一层的函数”);
如果异常在本函数中没有被处理,则它就会被拋给上一层的函数。
#include <iostream>
#include <string>
using namespace std;
class CException
{
public:
string msg;
CException(string s) : msg(s) {}
};
double Devide(double x, double y)
{
if (y == 0)
throw CException("devided by zero");
cout << "in Devide" << endl;
return x / y;
}
int CountTax(int salary)
{
try {
if (salary < 0)
throw - 1;
cout << "counting tax" << endl;
}
catch (int) {
cout << "salary < 0" << endl;
}
cout << "tax counted" << endl;
return salary * 0.15;
}
int main()
{
double f = 1.2;
try {
CountTax(-1);
f = Devide(3, 0);
cout << "end of try block" << endl;
}
catch (CException e) {
cout << e.msg << endl;
}
cout << "f = " << f << endl;
cout << "finished" << endl;
return 0;
}
结果显示:
虽然在函数中对异常进行了处理,但是还是希望能够通知调用者,以便让调用者知道发生了异常,从而可以作进一步的处理。
#include <iostream>
#include <string>
using namespace std;
int CountTax(int salary)
{
try {
if( salary < 0 )
throw string("zero salary");
cout << "counting tax" << endl;
}
catch (string s ) {
cout << "CountTax error : " << s << endl;
throw; //继续抛出捕获的异常
}
cout << "tax counted" << endl;
return salary * 0.15;
}
int main()
{
double f = 1.2;
try {
CountTax(-1);
cout << "end of try block" << endl;
}
catch(string s) {
cout << s << endl;
}
cout << "finished" << endl;
return 0;
}
结果显示:
C++标准异常类
1) bad_typeid
使用 typeid 运算符时,如果其操作数是一个多态类的指针
2) bad_cast
在用 dynamic_cast 进行从多态基类对象(或引用)到派生类的引用的强制类型转换时,如果转换是不安全的,则会拋出此异常。
#include <iostream>
#include <stdexcept>
#include <typeinfo> // std::bad_cast
using namespace std;
class Base
{
virtual void func() {}
};
class Derived : public Base
{
public:
void Print() {}
};
void PrintObj(Base & b)
{
try {
Derived & rd = dynamic_cast <Derived &>(b);
//此转换若不安全,会拋出 bad_cast 异常
rd.Print();
}
catch (bad_cast& e) {
cerr << e.what() << endl;
}
}
int main()
{
Base b;
PrintObj(b);
return 0;
}
结果显示:
3) bad_alloc
在用 new 运算符进行动态内存分配时,如果没有足够的内存,则会引发此异常。
#include <iostream>
#include <stdexcept>
using namespace std;
int main()
{
try {
char * p = new char[0x7fffffffffffff]; //无法分配这么多空间,会抛出异常
}
catch (bad_alloc & e) {
cerr << e.what() << endl;
}
return 0;
}
结果显示:
4) out_of_range
用 vector 或 string 的 at 成员函数根据下标访问元素时,如果下标越界。
#include <iostream>
#include <stdexcept>
#include <vector>
#include <string>
using namespace std;
int main()
{
vector<int> v(10);
try {
v.at(100) = 100; //拋出 out_of_range 异常
}
catch (out_of_range & e) {
cerr << e.what() << endl;
}
string s = "hello";
try {
char c = s.at(100); //拋出 out_of_range 异常
}
catch (out_of_range & e) {
cerr << e.what() << endl;
}
return 0;
}
结果显示:
强制类型风险:
如把整型数值转换成指针,把基类指针转换成派生类指针,把一种函数指针转换成另一种函数指针,把常量指针转换成非常量指针等。
强制转换用法:
强制类型转换运算符 <要转换到的类型> (待转换的表达式)
double d = static_cast <double> (3*5); //将 3*5 的值转换成实数
1)static_cast
static_cast 用于: 风险低的转换,如整型和浮点型、字符型之间的互相转换。
如果对象所属的类重载了强制类型转换运算符 T(如 T 是 int、int* 或其他类型名),则 static_cast 也能用来进行对象到 T 类型的转换。
#include <iostream>
using namespace std;
class A
{
public:
operator int() { return 1; }
operator char*() { return NULL; }
};
int main()
{
A a;
int n;
char* p = "New Dragon Inn";
n = static_cast <int> (3.14); // n 的值变为 3
n = static_cast <int> (a); //调用 a.operator int,n 的值变为 1
p = static_cast <char*> (a); //调用 a.operator char*,p 的值变为 NULL
n = static_cast <int> (p); //编译错误,static_cast不能将指针转换成整型
p = static_cast <char*> (n); //编译错误,static_cast 不能将整型转换成指针
return 0;
}
结果显示:
2)reinterpret_cast
reinterpret_cast 用于: 各种不同类型的指针之间、不同类型的引用之间以及指针和能容纳指针的整数类型之间的转换。转换时,执行的是逐个比特复制的操作。
#include <iostream>
using namespace std;
class A
{
public:
int i;
int j;
A(int n):i(n),j(n) { }
};
int main()
{
A a(100);
int &r = reinterpret_cast<int&>(a); //强行让 r 引用 a
r = 200; //把 a.i 变成了 200
cout << a.i << "," << a.j << endl; // 输出 200,100
int n = 300;
A *pa = reinterpret_cast<A*> ( & n); //强行让 pa 指向 n
pa->i = 400; // n 变成 400
pa->j = 500; //此条语句不安全,很可能导致程序崩溃
cout << n << endl; // 输出 400
long long la = 0x12345678abcdLL;
// pa = reinterpret_cast<A*>(la); //la太长,只取低32位0x5678abcd拷贝给pa
// unsigned int u = reinterpret_cast<unsigned int>(pa);//pa逐个比特拷贝到u
// cout << hex << u << endl; //输出 5678abcd
typedef void (* PF1) (int);
typedef int (* PF2) (int,char *);
PF1 pf1; PF2 pf2;
pf2 = reinterpret_cast<PF2>(pf1); //两个不同类型的函数指针之间可以互相转换
}
结果显示:
3) const_cast
const_cast 运算符仅用于:进行去除 const 属性的转换,它也是四个强制类型转换运算符中唯一能够去除 const 属性的运算符。
const string s = "Inception";
string& p = const_cast <string&> (s);
string* ps = const_cast <string*> (&s); // &s 的类型是 const string*
4) dynamic_cast
用 reinterpret_cast 可以将多态基类(包含虚函数的基类)的指针强制转换为派生类的指针,但不检查转换后的指针是否确实指向一个派生类对象。
dynamic_cast专门用于将多态基类的指针或引用强制转换为派生类的指针或引用,而且能够检查转换的安全性。对于不安全的指针转换,结果返回 NULL 指针。
#include <iostream>
#include <string>
using namespace std;
class Base
{ //有虚函数,因此是多态基类
public:
virtual ~Base() {}
};
class Derived : public Base { };
int main()
{
Base b;
Derived d;
Derived* pd;
pd = reinterpret_cast <Derived*> (&b);
if (pd == NULL)
//此处pd不会为 NULL。reinterpret_cast不检查安全性,总是进行转换
cout << "unsafe reinterpret_cast" << endl; //不会执行
pd = dynamic_cast <Derived*> (&b);
if (pd == NULL) //结果会是NULL,因为 &b 不指向派生类对象,此转换不安全
cout << "unsafe dynamic_cast1" << endl; //会执行
pd = dynamic_cast <Derived*> (&d); //安全的转换
if (pd == NULL) //此处 pd 不会为 NULL
cout << "unsafe dynamic_cast2" << endl; //不会执行
return 0;
}
结果显示:
那该如何判断该转换是否安全呢?不存在空引用,因此不能通过返回值来判断转换是否安全。
C++ 的解决办法是:dynamic_cast 在进行引用的强制转换时,如果发现转换不安全,就会拋出一个异常,通过处理异常,就能发现不安全的转换
实现原理:
只要将 new 运算符返回的指针 p 交给一个 shared_ptr 对象“托管”,托管 p 的 shared_ptr 对象在消亡时会自动执行。
该 shared_ptr 对象能像指针 p —样使用,即假设托管 p 的 shared_ptr 对象叫作 ptr,那么 *ptr 就是 p 指向的对象。
shared_ptr<T> ptr(new T); // T 可以是 int、char、类等各种类型
#include <iostream>
#include <memory>
using namespace std;
class A
{
public:
int i;
A(int n):i(n) { };
~A() { cout << i << " " << "destructed" << endl; }
};
int main()
{
shared_ptr<A> sp1(new A(2)); //A(2)由sp1托管,
shared_ptr<A> sp2(sp1); //A(2)同时交由sp2托管
shared_ptr<A> sp3;
sp3 = sp2; //A(2)同时交由sp3托管
cout << sp1->i << "," << sp2->i <<"," << sp3->i << endl;
A * p = sp3.get(); // get返回托管的指针,p 指向 A(2)
cout << p->i << endl; //输出 2
sp1.reset(new A(3)); // reset导致托管新的指针, 此时sp1托管A(3)
sp2.reset(new A(4)); // sp2托管A(4)
cout << sp1->i << endl; //输出 3
sp3.reset(new A(5)); // sp3托管A(5),A(2)无人托管,被delete
cout << "end" << endl;
return 0;
}
结果显示:
应用范围: 只有指向动态分配的对象的指针才能交给 shared_ptr 对象托管。将指向普通局部变量、全局变量的指针交给 shared_ptr 托管,编译时不会有问题,但程序运行时会出错,因为不能析构一个并没有指向动态分配的内存空间的指针。
A* p = new A(10);shared_ptr <A> sp1(p), sp2(p);
问题所在: sp1 和 sp2 并不会共享同一个对 p 的托管计数,而是各自将对 p 的托管计数都记为 1(sp2 无法知道 p 已经被 sp1 托管过)。这样,当 sp1 消亡时要析构 p,sp2 消亡时要再次析构 p,这会导致程序崩溃。
思考:对于只使用一次的函数对象类,能否直接在使用它的地方定义呢?L
优势:Lambda 表达式可以减少程序中函数对象类的数量,使得程序更加优雅。
[外部变量访问方式说明符] (参数表) -> 返回值类型
{
语句块
}
外部变量访问方式说明符:
”可以是=
或&
,表示{}
中用到的、定义在{}
外面的变量在{}
中是否允许被改变。=
表示不允许,&
表示允许。当然,在{}
中也可以不使用定义在外面的变量。
“-> 返回值类型”可以省略。
[=] (int x, int y) -> bool {return x%10 < y%10; }
int a[4] = {11, 2, 33, 4};
sort(a, a+4, [=](int x, int y) -> bool { return x%10 < y%10; } );
for_each(a, a+4, [=](int x) { cout << x << " ";} );
输出结果:11 2 33 4
#include <iostream>
#include <algorithm>
using namespace std;
int main()
{
int a[4] = { 1, 2, 3, 4 };
int total = 0;
for_each(a, a + 4, [&](int & x) { total += x; x *= 2; });
cout << total << endl; //输出 10
for_each(a, a + 4, [=](int x) { cout << x << " "; });
return 0;
}
结果显示:
“外部变量访问方式说明符”有更加复杂和灵活的用法:
[=, &x, &y]
表示外部变量 x、y 的值可以被修改,其余外部变量不能被修改;[&, x, y]
表示除 x、y 以外的外部变量,值都可以被修改。 #include <iostream>
using namespace std;
int main()
{
int x = 100,y=200,z=300;
auto ff = [=,&y,&z](int n) {
cout <<x << endl;
y++; z++;
return n*n;
};
cout << ff(15) << endl;
cout << y << "," << z << endl;
}
结果显示:
定义:用 auto 关键字定义变量,编译器会自动判断变量的类型:
auto i =100; // i 是 int
auto p = new A(); // p 是 A*
auto k = 34343LL; // k 是 long long
变量的类型名特别长:
map <string, int, greater <string> >mp;
for( auto i = mp.begin(); i != mp.end(); ++i)
cout << i -> first << ", " << i -> second;
编译器会自动判断出 i 的类型是 map<string, int, greater<string> >::iterator。
纯文本复制
int i;double t;
struct A { double x; };
const A* a = new A();
decltype(a) x1; //x1 是 A*
decltype(i) x2; //x2 是 int
decltype(a -> x) x3; // x3 是 double
特别注意:函数返回值若为 auto,则需要和 decltype 配合使用
#include <iostream>
using namespace std;
struct A {
int i;
A(int ii) : i(ii) {}
};
A operator + (int n, const A & a)
{
return A(a.i + n);
}
template <class T1, class T2>
auto add(T1 x, T2 y) -> decltype(x + y) {
return x + y;
}
int main() {
auto d = add(100, 1.5); // d 是 double 类型,d = 101.5
auto k = add(100, A(1)); // k 是 A 类型,因为表达式“100+A(1)”是A类型的
cout << d << endl;
cout << k.i << endl;
return 0;
}
结果显示: