20.1 C++11新特性
1)统一的初始化方法:使用{ }初始化数组和容器
int arr[3]{1, 2, 3};
vector<int> iv{1, 2, 3};
map<int, string> mp{{1, "a"}, {2, "b"}};
string str{"Hello World"};
int * p = new int[20]{1,2,3};
struct A {
int i,j; A(int m,int n):i(m),j(n) { }
};
A func(int m,int n ) { return {m,n}; }
int main() { A * pa = new A {3,7}; }
2)成员变量默认初始值:在定义类的时候可以初始化变量
class B
{
public:
int m = 1234;
int n;
};
int main(){
B b;
cout << b.m << endl; //输出 1234
return 0;
}
3)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
class A { };
A operator + ( int n,const A & a){
return a;
}
template <class T1, class T2>
auto add(T1 x, T2 y) -> decltype(x + y) {
return x+y;
}
auto d = add(100,1.5); // d是double d=101.5
auto k = add(100,A()); // d是A类型
4)decltype 关键字:求表达式的类型
int i;
double t;
struct A { double x; };
const A* a = new A();
decltype(a) x1; // x1 is A *
decltype(i) x2; // x2 is int
decltype(a->x) x3; // x3 is double
decltype((a->x)) x4 = t; // x4 is double&
//在里面多用一个括号会被判定为引用
5)智能指针shared_ptr
(1)头文件: <memory>
(2)通过shared_ptr的构造函数,可以让shared_ptr对象托管一个new运算符返回的指针,写法如下:
shared_ptr<T> ptr(new T); // T 可以是 int ,char, 类名等各种类型
(3)此后ptr就可以像 T* 类型的指针一样来使用,即 *ptr 就是用new动态分配的那个对象,而且不必操心释放内存的事。
(4)多个shared_ptr对象可以同时托管一个指针,系统会维护一个托管计数。当无shared_ptr托管该指针时,delete该指针。
(5)shared_ptr对象不能托管指向动态分配的数组的指针,否则程序运行会出错
shared_ptr<T> ptr(new T[100]); //error
(6)示例1:
#include <memory>
#include <iostream>
using namespace std;
struct A {
int n;
A(int v = 0):n(v){ }
~A() { cout << n << " destructor" << endl; }
};
int main(){
shared_ptr<A> sp1(new A(2)); //sp1托管A(2)
shared_ptr<A> sp2(sp1); //sp2也托管 A(2)
cout << "1)" << sp1->n << "," << sp2->n << endl;//输出1)2,2
shared_ptr<A> sp3;
A * p = sp1.get(); //p 指向 A(2),返回指针
cout << "2)" << p->n << endl;
sp3 = sp1; //sp3也托管 A(2)
cout << "3)" << (*sp3).n << endl; //输出 2
sp1.reset(); //sp1放弃托管 A(2)
if( !sp1 ) //sp1没有托管任何东西
cout << "4)sp1 is null" << endl; //会输出
A * q = new A(3);
sp1.reset(q); // sp1托管q
cout << "5)" << sp1->n << endl; //输出 3
shared_ptr<A> sp4(sp1); //sp4托管A(3)
shared_ptr<A> sp5;
sp1.reset(); //sp1放弃托管 A(3)
cout << "before end main" <<endl;
sp4.reset(); //sp1放弃托管 A(3)
cout << "end main" << endl;
return 0; //程序结束,会delete 掉A(2)
}
(7)示例2:不增加托管计数情况:
#include <iostream>
#include <memory>
using namespace std;
struct A{
~A() { cout << "~A" << endl; }
};
int main(){
A * p = new A();
shared_ptr<A> ptr(p);
shared_ptr<A> ptr2;
ptr2.reset(p); //并不增加 ptr中对p的托管计数
cout << "end" << endl;
return 0;
}
(8)unique_ptr:独享指针
a)只能有一个unique_ptr托管某个对象
b)可以用move更换托管对象
c)使用方法:
unique_ptr<T> p1(new T);
unique_ptr<T> p2(p1); //error
unique_ptr<T> p3 = std::move(p1); //ok
d)unique_ptr和shared_ptr相比,可以指向数组:
unique_ptr<int> p(new int[10]); //ok
p[4] = 5; //ok
(9)weak_ptr:shared_ptr的助手:
a)weak_ptr是用来监视shared_ptr的,不会增加引用计数,没有*和->运算符,纯粹只是作为旁观者监视shared_ptr
b)基本用法:
shared_ptr<int> p1(new int(10));
weak_ptr<int> p2(p1);
cout << p2.use_count() << endl; //输出1
//使用expired()成员函数判断是否监视有效
if (p2.expired())
cout << "weak_ptr无效,所监视的智能指针已经失效"<< endl;
else
cout << "weak_ptr有效"<< endl;
//使用lock()方法获取所监视的shared_ptr
auto p3 = p2.lock();
cout << * p3 << endl; //输出10
6)空指针nullptr
#include <memory>
#include <iostream>
using namespace std;
int main() {
int* p1 = NULL;
int* p2 = nullptr;
shared_ptr<double> p3 = nullptr;
if(p1 == p2)
cout << "equal 1" <<endl;
if( p3 == nullptr)
cout << "equal 2" <<endl;
if( p3 == p2) ; // error
if( p3 == NULL)
cout << "equal 4" <<endl;
bool b = nullptr; // b = false
int i = nullptr; //error,nullptr不能自动转换成整型
return 0;
}
去掉出错的语句后输出:
equal 1
equal 2
equal 4
7)基于范围的for循环:
#include <iostream>
#include <vector>
using namespace std;
struct A { int n; A(int i):n(i) { } };
int main() {
int ary[] = {1,2,3,4,5};
for(int & e: ary)
e*= 10;
for(int e : ary)
cout << e << ",";
cout << endl;
vector<A> st(ary,ary+5);
for( auto & it: st)
it.n *= 10;
for( A it: st)
cout << it.n << ",";
return 0;
}
输出:
10,20,30,40,50,
100,200,300,400,500,
8)右值引用和move语义
(1)右值:一般来说,不能取地址的表达式,就是右值,能取地址的,就是左值
class A { };
A & r = A(); // error , A()是无名变量,是右值
A && r = A(); //ok, r 是右值引用
(2)主要目的是提高程序运行的效率,减少需要进行深拷贝的对象进行深拷贝的次数
(3)以前学的都是左值引用(引用变量),右值引用就是引用常量和临时对象,可以延长变量的生命周期,和右值引用变量一起消亡
(4)std::move(a)等价于 static_cast<T&&>(a),就是将变量a转化成右值引用
(5)示例:
#include <iostream>
#include <string>
#include <cstring>
using namespace std;
class String{
public:
char * str;
String():str(new char[1]) { str[0] = 0;}
String(const char * s) {
str = new char[strlen(s)+1];
strcpy(str,s);
}
String(const String & s) {
cout << "copy constructor called" << endl;
str = new char[strlen(s.str)+1];
strcpy(str,s.str);
}
String & operator=(const String & s) {
cout << "copy operator= called" << endl;
if( str != s.str) {
delete [] str;
str = new char[strlen(s.str)+1];
strcpy(str,s.str);
}
return * this;
}
String(String && s):str(s.str) {
cout << "move constructor called"<<endl;
s.str = new char[1];
s.str[0] = 0;
}
// move assigment
String & operator = (String &&s) {
cout << "move operator= called"<<endl;
if (str!= s.str) {
delete [] str;
str = s.str;
s.str = new char[1];
s.str[0] = 0;
}
return *this;
}
~String() { delete [] str; }
};
template <class T>
void MoveSwap(T& a, T& b) {
T tmp(move(a)); // std::move(a)为右值,这里会调用move constructor
a = move(b); // move(b)为右值,因此这里会调用move assigment
b = move(tmp); // move(tmp)为右值,因此这里会调用move assigment
}
int main()
{
//String & r = String("this"); // error
String s;
s = String("ok"); // String("ok")是右值
cout << "******" << endl;
String && r = String("this");
cout << r.str << endl;
String s1 = "hello",s2 = "world";
MoveSwap(s1,s2);
cout << s2.str << endl;
return 0;
}
9)无序容器(哈希表):
(1)哈希表插入和查询的时间复杂度几乎是常数
(2)用法和map相同
(3)示例:根据姓名查询获奖时间
#include <iostream>
#include <string>
#include <unordered_map>
using namespace std;
int main(){
unordered_map<string,int> turingWinner; //图灵奖获奖名单
turingWinner.insert(make_pair("Dijkstra",1972));
turingWinner.insert(make_pair("Scott",1976));
turingWinner.insert(make_pair("Wilkes",1967));
turingWinner.insert(make_pair("Hamming",1968));
turingWinner["Ritchie"] = 1983;
string name;
cin >> name; //输入姓名
unordered_map<string,int>::iterator p =
turingWinner.find(name);
//据姓名查获奖时间
if( p != turingWinner.end())
cout << p->second;
else
cout << "Not Found" << endl;
return 0;
}
10)正则表达式(就是某种模式的字符串)
#include <iostream>
#include <regex> //使用正则表达式须包含此文件
using namespace std;
int main(){
regex reg("b.?p.*k");
cout << regex_match("bopggk",reg) <<endl; //输出 1, 表示匹配成功
cout << regex_match("boopgggk",reg) <<endl; //输出 0, 匹配失败
cout << regex_match("b pk",reg) <<endl ; //输出 1, 表示匹配成功
regex reg2("\\d{3}([a-zA-Z]+).(\\d{2}|N/A)\\s\\1");
string correct="123Hello N/A Hello";
string incorrect="123Hello 12 hello";
cout << regex_match(correct,reg2) <<endl; //输出 1,匹配成功
cout << regex_match(incorrect,reg2) << endl; //输出 0, 失败
}
11)Lambda表达式
(1)只使用一次的函数对象,能否不要专门为其编写一个类?
(2)只调用一次的简单函数,能否在调用时才写出其函数体?
(3)形式:
[外部变量访问方式说明符](参数表) ->返回值类型{
语句组
}
a)[ ] 不使用任何外部变量
b)[=] 以传值的形式使用所有外部变量
c)[&] 以引用形式使用所有外部变量
d)[x, &y] x 以传值形式使用,y 以引用形式使用
e)[=,&x,&y] x,y 以引用形式使用,其余变量以传值形式使用
f)[&,x,y] x,y 以传值的形式使用,其余变量以引用形式使用
g)“->返回值类型”也可以没有, 没有则编译器自动判断返回值类型。
h)如果传值进去就不能改变该变量,否则会报错
(4)示例1:lambda表达式基本使用
int main(){
int x = 100,y=200,z=300;
cout << [ ](double a,double b) { return a + b; }(1.2,2.5)
<< endl;
//lambda表达式定义,y和z以引用传进去,x以值传进去
auto ff = [=,&y,&z](int n) {
cout <<x << endl;
y++; z++;
return n*n;
};
//lambda表达式使用
cout << ff(15) << endl;
cout << y << "," << z << endl;
}
输出:
3.7
100
225
201,301
(5)示例2:
int a[4] = { 4,2,11,33};
sort(a,a+4,[ ](int x,int y)->bool {return x%10 < y%10; });
for_each(a,a+4,[ ](int x) {cout << x << " " ;} ) ;
(6)示例3:
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
int main(){
vector<int> a { 1,2,3,4};
int total = 0;
//把total使用引用传进来
for_each(a.begin(),a.end(),[&](int & x){total += x; x*=2;});
cout << total << endl; //输出 10
for_each(a.begin(),a.end(),[ ](int x){ cout << x << " ";});
return 0;
}
12)chrono时间库
(1)chrono 库主要包含了三种类型:时间间隔 Duration、时钟 Clocks 和时间点 Time point
(2)标准库为了方便使用,就定义了一些常用的时间间隔,如时、分、秒、毫秒、微秒和纳秒,在chrono命名空间下,它们的定义如下:
typedef duration <Rep, ratio<3600,1>> hours;
typedef duration <Rep, ratio<60,1>> minutes;
typedef duration <Rep, ratio<1,1>> seconds;
typedef duration <Rep, ratio<1,1000>> milliseconds;
typedef duration <Rep, ratio<1,1000000>> microseconds;
typedef duration <Rep, ratio<1,1000000000>> nanoseconds;
(3)通过定义这些常用的时间间隔类型,我们能方便的使用它们,比如线程的休眠:
std::this_thread::sleep_for(std::chrono::seconds(3)); //休眠三秒
std::this_thread::sleep_for(std::chrono:: milliseconds (100)); //休眠100毫秒
(4)chrono 还提供了获取时间间隔的时钟周期个数的方法 count()
#include <iostream>
#include <chrono>
using namespace std;
int main(void) {
std::chrono::milliseconds ms{3};//3毫秒
std::chrono::microseconds us = 2 * ms;//6000微秒
std::chrono::duration<double, std::ratio<1, 30>> hz(3.5);
//hz为3.5个周期时间长度,每个周期为1/30s
cout << ms.count() << endl;//3 一个周期为1s
cout << us.count() << endl;//6000 一个周期为1us
cout << hz.count() << endl;//3.5
return 0;
}
(5)时间间隔之间可以做运算,比如下面的例子中计算两端时间间隔的差值:
#include <iostream>
#include <chrono>
using namespace std;
int main(void) {
std::chrono::minutes t1(10);//10分钟
std::chrono::seconds t2( 60 );//60秒
std::chrono::seconds t3 = t1 - t2;
//t3 的时间周期为 second
std::cout << t3.count() << " second" << std::endl;
//通过 duration_cast 将时间周期转化为 minutes
cout << chrono::duration_cast<chrono::minutes>(t3).count() << " minutes" << endl;
return 0;
}
// 输出:
// 540 second
// 9 minutes
(6)使用chrono中的steady_clock计算程序运行时间(相当于秒表)
chrono::steady_clock::time_point t1 = chrono::steady_clock::now();
//测试程序
chrono::steady_clock::time_point t2 = chrono::steady_clock::now();
chrono::duration<double> time_used = chrono::duration_cast<chrono::duration<double>>(t2 - t1);
cout << "time spend: " << time_used.count() << " seconds." << endl;
#include <iostream>
#include <chrono>
#include <thread>
using namespace std;
int main() {
chrono::steady_clock::time_point t1 = chrono::steady_clock::now();
//测试程序
chrono::seconds time(1);
this_thread::sleep_for(time);
chrono::steady_clock::time_point t2 = chrono::steady_clock::now();
chrono::duration<double> time_used = chrono::duration_cast<chrono::duration<double>>(t2 - t1);
cout << "time spend: " << time_used.count() << " seconds." << endl; //输出1.00004
}
20.2 强制类型转换
20.2.1 static_cast
1)用于基本数据类型之间的转换,比如整型和实数型、字符型之间互相转换,static_cast不能来在不同类型的指针之间互相转换,也不能用于整型和指针之间的互相转换,也不能用于不同类型的引用之间的转换。
2)static_cast示例:
#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 int *,p的值变为 NULL
n = static_cast<int> (p);//编译错误,static_cast不能将指针转换成整型
p = static_cast<char*>(n);//编译错误,static_cast不能将整型转换成指针
return 0;
}
20.2.2 reinterpret_cast
1)用来进行各种不同类型的指针之间的转换、不同类型的引用之间转换、以及指针和能容纳得下指针的整数类型之间的转换。转换的时候,执行的是逐个比特拷贝的操作,操作有很大风险
2)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,引用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);
//两个不同类型的函数指针之间可以互相转换
}
20.2.3 const_cast:
(1)用来进行去除const属性的转换。将const引用转换成同类型的非const引用,将const指针转换为同类型的非const指针时用它
(2)例如:
const string s = “Inception”;
string & p = const_cast<string&>(s);
string * ps = const_cast<string*>(&s);
// &s的类型是const string *
20.2.4 dynamic_cast
(1)专门用于将多态基类的指针或引用,强制转换为派生类的指针或引用,而且能够检查转换的安全性。对于不安全的指针转换,转换结果返回NULL指针。
(2)dynamic_cast不能用于将非多态基类的指针或引用,强制转换为派生类的指针或引用
(3)示例:
#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;
}
20.3 异常处理
1)程序运行中总难免发生错误
(1)数组元素的下标超界、访问NULL指针
(2)除数为0
(3)动态内存分配new需要的存储空间太大
2)C++ 通过 throw 语句和 try…catch 语句实现对异常的处理。throw 语句的语法如下:
throw 表达式;
(1)该语句拋出一个异常。异常是一个表达式,其值的类型可以是基本类型,也可以是类。
(2)try…catch 语句的语法如下:
try {
语句组
}
catch(异常类型) {
异常处理代码
}
...
catch(异常类型) {
异常处理代码
}
(3)catch 可以有多个,但至少要有一个
(4)不妨把 try 和其后{}中的内容称作“try块”,把 catch 和其后{}中的内容称作“catch块”
(5)try…catch 语句的执行过程是:执行 try 块中的语句,如果执行的过程中没有异常拋出,那么执行完后就执行最后一个 catch 块后面的语句,所有 catch 块中的语句都不会被执行;如果 try 块执行的过程中拋出了异常,那么拋出异常后立即跳转到第一个“异常类型”和拋出的异常类型匹配的 catch 块中执行(称作异常被该 catch 块“捕获”),执行完后再跳转到最后一个 catch 块后面继续执行
(6)示例:
#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;
}
程序的运行结果如下:
9 6↙
before dividing.
1.5
after dividing.
finished
9 0↙
before dividing.
catch(int) -1
finished
(7)当 n 为 0 时,try 块中会拋出一个整型异常。拋出异常后,try 块立即停止执行。该整型异常会被类型匹配的第一个 catch 块捕获,即进入catch(int e)块执行,该 catch 块执行完毕后,程序继续往后执行,直到正常结束
(8)如果拋出的异常没有被 catch 块捕获,例如,将catch(int e),改为catch(char e),当输入的 n 为 0 时,拋出的整型异常就没有 catch 块能捕获,这个异常也就得不到处理,那么程序就会立即中止,try…catch 后面的内容都不会被执行
(9)如果希望不论拋出哪种类型的异常都能捕获,可以编写如下 catch 块:
catch(...) {
...
}
(10)示例:
#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;
}
程序运行结果:
9 0↙
before dividing.
catch(...)
finished
0 6↙
before dividing.
catch(double) -1
finished
(11)如果有多个符合的catch块,执行前面的,也就是catch块从上往下执行
3)异常的再抛出:如果一个函数在执行的过程中,抛出的异常在本函数内就被catch块捕获并处理了,那么该异常就不会抛给这个函数的调用者(也称“上一层的函数”);如果异常在本函数中没被处理,就会被抛给上一层的函数。
(1)示例:
#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;
}
输出结果:
salary < 0
tax counted
devided by zero
f=1.2
finished
4)C++标准异常类: C++标准库中有一些类代表异常,这些类都是从exception类派生而来
(1)bad_cast:在用 dynamic_cast进行从多态基类对象(或引用),到派生类的引用的强制类型转换时,如果转换是不安全的,则会抛出此异常
#include <iostream>
#include <stdexcept>
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;
}
输出结果:
Bad dynamic_cast!
(2)bad_alloc:在用new运算符进行动态内存分配时,如果没有足够的内存,则会引发此异常。
#include <iostream>
#include <stdexcept>
using namespace std;
int main (){
try {
char * p = new char[0x7fffffff];
//无法分配这么多空间,会抛出异常
}
catch (bad_alloc & e) {
cerr << e.what() << endl;
}
return 0;
}
输出结果:
bad allocation
(3)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;
}
输出结果:
invalid vector<T> subscript
invalid string position