logo头像

不忘初心,奋力前行

C++面向对象程序设计课程笔记(第十周)

本文于597天之前发表,文中内容可能已经过时,如有问题,请联系我。

第十周 C++11新特性和C++高级主题

第一节 C++新特性(1)

1.**统一的初始化方法 (1)int arr[3]{1, 2, 3}; (2)vector 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 > mp; for(auto i =mp.begin(); i!=mp.end(); ++i) cout<first<<”,”<<second; //i的类型是: map<string,int,greater >::iterator class A { }; A operator + ( int n,const A & a) { return a; } template 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 头文件: 。通过shared_ptr的构造函数,可以让shared_ptr对象托管一个new运算符返回的指针,写法如下: shared_ptr ptr(new T); // T 可以是 int ,char, 类名等各种类型 此后ptr就可以像 T 类型的指针一样来使用,即 ptr 就是用new动态分配的那个对象,而且不必操心释放内存的事。 多个shared_ptr对象可以同时托管一个指针,系统会维护一个托管计数。当无shared_ptr托管该指针时,delete该指针。 shared_ptr**对象不能托管指向动态分配的数组的指针,否则程序运行会出错。 例程1: #include #include using namespace std; struct A { int n; A(int v = 0):n(v){ } ~A() { cout << n << “ destructor” << endl; } }; int main(){ shared_ptr sp1(new A(2)); //sp1托管A(2) share_ptr sp2(sp1);//sp2也托管A(2) cout<<”1)”<n<<”,”<n<<endl; //output: 1)2,2可以像一个指针一样使用 shared_ptr sp3; A*p=sp1.get();///p指向A(2),把所托管的对象提取出来 cout<<”2)”<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 sp4(sp1); //sp4托管A(3) shared_ptr 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 #include using namespace std; struct A { ~A() { cout << “~A” << endl; } }; int main() { A p = new A(); shared_ptr ptr(p); shared_ptr 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 #include using namespace std; int main() { int p1 = NULL; int p2 = nullptr; shared_ptr 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 #include 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 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 #include #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 #include //使用正则表达式须包含此文件 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 nn; }; 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 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(pa);//pa逐个比特拷贝给u cout << hex << u<<endl;//输出5678abcd typedef void (\PF1)(int); typedef int (*PF2)(int,char ); PF1 pf1; PF2 PF2; pf2 = reinterpret_cast(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 #include 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 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 #include 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 #include //要使用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; }

支付宝打赏 微信打赏 QQ钱包打赏

感觉不错?欢迎给我 打个赏~我将不胜感激!