第十周 C++11新特性和C++高级主题
第一节 C++新特性(1)
1.统一的初始化方法
(1)int arr[3]{1, 2, 3};
(2)vector<int> iv{1, 2, 3};
(3)map<int, string> mp{{1,”a”},{2,”b”}};
(4)string str{“Hello World”};
(5)int * p = new int[20]{1,2,3};
(6)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;
return 0;
}
3.auto关键字
用于定义变量,编译器可以自动判断变量的类型。
auto i =100;//i是int
auto p = new A();//p是*A
map<string,int,greater<string> > mp;
for(auto i =mp.begin(); i!=mp.end(); ++i)
cout<<i->first<<“,”<<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.智能指针share_ptr
头文件: <memory>。通过shared_ptr的构造函数,可以让shared_ptr对象托管一个new运算符返回的指针,写法如下:
shared_ptr<T> ptr(new T); // T 可以是 int ,char, 类名等各种类型
此后ptr就可以像 T* 类型的指针一样来使用,即 *ptr 就是用new动态分配的那个对象,而且不必操心释放内存的事。
多个shared_ptr对象可以同时托管一个指针,系统会维护一个托管计数。当无shared_ptr托管该指针时,delete该指针。
shared_ptr对象不能托管指向动态分配的数组的指针,否则程序运行会出错。
例程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)
share_ptr<A> sp2(sp1);//sp2也托管A(2)
cout<<“1)”<<sp1->n<<“,”<<sp2->n<<endl;
//output: 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 )
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)
}
输出结果:
1)2,2
2)2
3)2
4)sp1 is null
5)3 before end main
3 destruct11or
例程2:
#include <memory>
#include <iostream>
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;
}
在ptr中托管了p,但是我在ptr2中也托管p的时候,并不增加ptr中对p的托管计数。因为ptr和ptr2认为他们所托管的p不是一个p(虽然实际上是一个p)。在输出end之后主程序结束,ptr和ptr2都要进行执行析构函数,p被执行了两次析构函数,系统会崩溃。所以输出结果如下:
end
~A
~A
(之后程序崩溃)
6.空指针nullptr
nullptr在有些地方类似于空指针NULL。例程如下:
#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,p3和p2类型不同
if(p3 == NULL)
cout<<“equal4″<<endl;
bool b = nullptr;//b=false
int i = nullptr;//error,nullptr无法自动转换成整型
return 0;
}
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> str(ary,ary+5);
for(auto &it:st)
it.n*=10;
for(A it: st)
cout<<it.n<<“,”;
return 0;
}
类似于Java中的使用。
8.右值引用和move语义
右值:一般来说,不能取地址的表达式,就是右值,能取地址的,就是左值。例如:
class A { };
A & r = A(); // error , A()是无名变量,是右值
A && r = A(); //ok, r 是右值引用
主要目的是提高程序运行的效率,减少需要进行深拷贝的对象进行深拷贝的次数。我们前面学习的都是左值引用!
第二节 C++新特性(2)
1.可移动但不可复制的对象
struct A{
A(const A & a) = delete;
A(const A && a) { cout << “move” << endl; }
A() { };
};
A b;
A func(){
A a;
return a;
}
void func2(A a){}
int main(){
A a1;
A a2(a1);//compile error
func2(a1);//compile error
func();
return 0;
}
2.无序容器(哈希表)
#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;
}
哈希表插入和查询的时间复杂度几乎是常数。
3.正则表达式
#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, 失败
return 0;
}
4.Lambda表达式
形式:
[外部变量访问方式说明符](参数表) ->返回值类型{
语句组
}
[]:不使用任何外部变量;
[=]:以传值的形式使用所有外部变量;
[&]:以引用形式使用所有外部变量;
[x, &y]:x以传值形式使用,y以引用形式使用;
[=,&x,&y]:x和y以引用形式使用,其余变量以传值形式使用;
[&,x,y]:x和y以传值的形式使用,其余变量以引用形式使用。
如果以传值方式进行传递对象,就不能修改该对象的值。“->返回值类型”也可以没有, 没有则编译器自动判断返回值类型。
例程:
int main() {
int x = 100,y=200,z=300;
cout << [ ](double a,double b) { return a + b; }(1.2,2.5) <<endl;
auto ff = [=,&y,&z](int n){
cout<<x<<endl;
y++,z++;
return n*n;
};
cout << ff(15)<<endl;
cout<y<<“,”<<z<<endl;
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<<” “;});//输出11 2 33 4
return 0;
} 要注意,[ ](double a,double b) { return a + b; }(1.2,2.5)中的(1.2,2.5)并不是lambda表达式一部分,而只是调用lambda表达式函数的赋值语句。
例程:实现递归求菲波那切数列第n项:
function<int(int)> fib = [&fib](int n)
{return n<=2?1:fib(n-1)+fib(n-2);};
function<int(int)>表示返回值为int,有一个int参数的函数。
第三节 强制类型转换
共有四种类型:static_cast、reinterpret_cast、const_cast和dynamic_cast。
1.static_cast
static_cast用来进用行比较“自然”和低风险的转换,比如整型和实数型、字符型之间互相转换。
static_cast不能来在不同类型的指针之间互相转换,也不能用于整型和指针之间的互相转换,也不能用于不同类型的引用之间的转换。
2.reinterpret_cast
reinterpret_cast用来进行各种不同类型的指针之间的转换、不同类型的引用之间转换、以及指针和能容纳得下指针的整数类型之间的转换。转换的时候,执行的是逐个比特拷贝的操作。
例程:
#include <iostream>
using namespace std;
class A
{
public:
int i ,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,因为r是4个字节,所以上面的引用只引用了a的前面4个字节,也就是a.i
cout<<a.i<<“,”a.j<<endl;
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);
//两个不同类型的函数指针之间可以互相转换
return 0;
}
输出结果:200,100 400 5678abcd
3.const_cast
用来进行去除const属性的转换。将const引用转换成同类型的非const引用,将const指针转换为同类型的非const指针时用它。例如
const string s = “Inception”;
string & p = const_cast<string&>(s);
string * ps = const_cast<string*>(&s);
// &s的类型是const string *
4.dynamic_cast
dynamic_cast专门用于将多态基类的指针或引用,强制转换为派生类的指针或引用,而且能够检查转换的安全性。对于不安全的指针转换,转换结果返回NULL指针。
dynamic_cast不能用于将非多态基类的指针或引用,强制转换为派生类的指针或引用。
例程如下:
#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)
cout<< “unsafe dynamic_cast2” << endl; //不会执行
return 0;
}
如下所示:Derived & r = dynamic_cast<Derived&>(b); 那该如何判断该转换是否安全呢?
答案:不安全则抛出异常。
第四节 异常处理
程序运行中总难免发生错误,我们希望不只是简单地终止程序运行,还能够反馈异常情况的信息:哪一段代码发生的、什么异常,还能够对程序运行中已发生的事情做些处理:取消对输入文件的改动、释放已经申请的系统资源。
1.异常处理
一个函数运行期间可能产生异常。在函数内部对异常进行处理未必合适。因为函数设计者无法知道函数调用者希望如何处理异常。我们需要告知函数调用者发生了异常,让函数调用者处理比较好,但是用函数返回值告知异常不方便。
(1)用try,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 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;
}
表4.1 trycatch例程输入输出结果
程序输入 | 9 0 | 0 6 |
输出结果 | before dividing catch(…) finished
| before dividing catch(double) -1 finished
|
注意:try块中定义的局部对象,发生异常时会析构!
(2)异常的再抛出
如果一个函数在执行的过程中,抛出的异常在本函数内就被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);//在这个函数自己处理完了,try里面就感知不到这个错误了
f = Devide(3,0);//Devide本身没有处理异常,所以抛给了这个try里面了
cout << “end of try block” << endl;
}
catch(CException e) {
cout << e.msg << endl; }
cout << “f=” << f << endl;
cout << “finished” << endl;
return 0;
}
2.C++标准异常类
C++标准库中有一些类代表异常,这些类都是从exception类派生而来的。
图4.1 exception派生出的异常类
(1)bad_cast
在用 dynamic_cast进行从多态基类对象(或引用),到派生类的引用的强制类型转换时,如果转换是不安全的,则会抛出此异常。
(2)bad_alloc
在用new运算符进行动态内存分配时,如果没有足够的内存,则会引发此异常。
(3)out_of_range
用vector或string的at成员函数根据下标访问元素时,如果下标越界,就会抛出此异常。
3.运行时类型检查
C++运算符typeid是单目运算符,可以在程序运行过程中获取一个表达式的值的类型。typeid运算的返回值是一个type_info类的对象,里面包含了类型的信息。
例程如下:
#include <iostream>
#include <typeinfo> //要使用typeinfo,需要此头文件
using namespace std;
struct Base { }; //非多态基类
struct Derived : Base { };
struct Poly_Base {virtual void Func(){ } }; //多态基类struct Poly_Derived: Poly_Base { };
int main()
{
//基本类型
long i; int * p = NULL;
cout << “1) int is: ” << typeid(int).name() << endl;
//输出 1) int is: int
cout << “2) i is: ” << typeid(i).name() << endl;
//输出 2) i is: long
cout << “3) p is: ” << typeid(p).name() << endl;
//输出 3) p is: int *
cout << “4) *p is: ” << typeid(*p).name() << endl ;
//输出 4) *p is: int
//非多态类型
Derived derived;
Base* pbase = &derived;
cout << “5) derived is: ” << typeid(derived).name() << endl;
//输出 5) derived is: struct Derived
cout << “6) *pbase is: ” << typeid(*pbase).name() << endl;
//输出 6) *pbase is: struct Base
cout << “7) ” << (typeid(derived)==typeid(*pbase) ) << endl;
//输出 7) 0
//多态类型
Poly_Derived polyderived;
Poly_Base* ppolybase = &polyderived;
cout << “8) polyderived is: ” << typeid(polyderived).name() << endl;
//输出 8) polyderived is: struct Poly_Derived
cout << “9) *ppolybase is: ” << typeid(*ppolybase).name() << endl;
//输出 9) *ppolybase is: struct Poly_Derived
cout << “10) ” << (typeid(polyderived)!=typeid(*ppolybase) ) << endl;
//输出 10) 0
return 0;
}