一、decltype(RTTI)
RTTI 即 run time type identification(程序运行对对象的类型识别) auto和decltype
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 int a{ 10 }, b{ 20 };decltype (a + b) c = 30 ; cout << typeid (c).name () << endl; cout << c << endl; auto d = a+b;cout << typeid (d).name () << endl; cout << d << endl;
虽然auto不能作为函数的形参和返回值 但是在C++14的标准中就有了lambda表达式内可以有auto
二、C++11中的新容器
c++98容器: string / vector / list / deque / map / set / bitset + stack / queue / priority_queue c++11新容器: array(定长数组) / forward_list(单链表) array: #include< array> 缺点:定长+存储数据的空间在 栈上,栈的空间本来就不大 forward_list: #include< forward_list > 缺点:不支持尾插尾删+insert数据也是在当前位置的后面 unordered_map/unordered_set : 效率高于map / set,推荐
三、默认成员函数的控制
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 class B { public : B () = default ; B (const int & b) :_b(b) {} B (const B& b); B& operator =(const B& b); B (const B& b) = delete ; B& operator =(const B& b) = delete ; private : int _b = 10 ; }; B::B (const B& b) {} void test4 () { B b1; B b2 (b1) ; b1 = b2; }
四、右值引用
1.右值引用的基本使用
左右不是方向,和左移右移一样,是C留下的坑
左值通常是变量
右值通常是常量,表达式或者函数返回值等临时常量
c++11将右值分为:纯右值 , 将亡值
纯右值:基本类型的常量或者临时对象
将亡值:自定义类型的临时对象
总结 右值引用做参数和做返回值减少拷贝的本质是利用了移动构造和移动赋值 左值引用和右值引用本质的作用都是减少拷贝,右值引用本质可以认为是弥补左值引用不足的地方,他们相辅相成
左值引用: 解决的是传参过程中和返回值过程中的拷贝 做参数 : void push(T x) -> void push(T& x) 解决的是传参过程中减少拷贝 做返回值 : T f2() -> T& f2() 解决的是返回值过程中的拷贝 注意: 这里有限制,如果返回对象出了作用域不在了就不能传引用,这个左值引用无法解决,等待C++11右值引用解决
右值引用 : 解决的是传参后,push/insert 函数内部将对象移动到容器空间上的问题 + 传值返回接受返回值的拷贝 做参数 : void push(T&& x) 解决的push内部不再使用拷贝构造x到容器空间上,而是移动构造过去 做返回值: T&& f2() 解决的外面调用接收f2() 返回对象的拷贝,T ret = f2() , 这里就是右值引用的移动给构造,减少了拷贝
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 template <class T>void fun (const T& a) { cout << "void fun(const T& a)" << endl; } template <class T>void fun (const T&& a) { cout << "void fun(const T&& a)" << endl; } void test5 () { int x = 1 , y = 2 , a = 10 ; int & b = a; int & e = 10 ; int & f = x + y; const int & e = 10 ; const int & f = x + y; int && c = 10 ; int && d = x + y; int && m = a; int && m = move (a); fun (x); fun (10 ); }
2.右值引用的作用 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 class String { public : String (const char * str = "" ) { _str = new char [strlen (str) + 1 ]; strcpy (_str, str); } String (const String& s) { cout << "String(const String& s) - 深拷贝" << endl; _str = new char [strlen (s._str) + 1 ]; strcpy (_str, s._str); } String (String&& s) :_str(nullptr ) { cout << "String(const String&& s) - 移动拷贝" << endl; swap (_str, s._str); } String& operator =(const String& s) { if (this != &s) { char * newstr = new char [strlen (s._str) + 1 ]; strcpy (newstr, s._str); delete [] _str; _str = newstr; } return *this ; } String& operator =(String&& s) { cout << "String& operator=(String&& s) - 移动赋值 - 高效" << endl; swap (_str, s._str); return *this ; } ~String () { delete [] _str; } String& operator +=(const String& s) { return *this ; } String operator +(const String& s) { String ret (*this ) ; return ret; } private : char * _str; }; String f (const char * str) { String tmp (str) ; return tmp; } int test1 () { String s1 ("左值" ) ; String s2 (s1) ; String s3 (move(s2)) ; String s4 (f("右值,将亡值" )) ; String s5 (s1) ; s5 = s1; s5 = f ("rightval-dyingv" ); String s6 = s1 += s3; String s7 = s1 + s2; return 0 ; }
3.emplace_back
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 int test2 () { vector<string> v; string s1 ("left val" ) ; int val = 123 ; v.push_back (s1); v.push_back ("right val" ); v.push_back (to_string (val)); v.emplace_back (s1); v.emplace_back (move (s1)); v.emplace_back ("right val" ); vector<pair<string, string>> vp; pair<string, string> kv ("left v" , "left v" ) ; vp.push_back (make_pair ("right v" , "right v" )); vp.emplace_back (make_pair ("right v" , "right v" )); vp.push_back (kv); vp.emplace_back (kv); vp.emplace_back ("right v" , "right v" ); return 0 ; }
4.完美转发 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 void fun (int & x) { cout << "leftv ref" << endl; }void fun (int && x) { cout << "rightv ref" << endl; }void fun (const int & x) { cout << "const leftv ref" << endl; }void fun (const int && x) { cout << "const rightv ref" << endl; }template <typename T>void PerfectFoward (T&& t) { fun (std::forward<T>(t)); } int test3 () { PerfectFoward (10 ); int a; PerfectFoward (a); PerfectFoward (std::move (a)); const int b = 1 ; PerfectFoward (b); PerfectFoward (std::move (b)); return 0 ; }
五、lambda表达式
lambda表达式的轮廓为
1 2 [capture - list] (parameters) mutable -> return -type {statement} 捕捉列表 参数列表 取消常性 返回类型 函数体
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 int add1 (int a, int b) { return a + b; } int main () { int a = 1 , b = 2 ; auto add1 = [](int x, int y)->int {return x + y; }; add1 (a, b); ::add1 (a, b); auto add2 = [a, b]() {return a + b; }; add2 (); auto add3 = [=]() {return add1 (a, b); }; auto add4 = [=]() {return ::add1 (a, b); }; auto swap1 = [&]() {int x = a; a = b; b = x; }; swap1 (); cout << a << b << endl; auto swap2 = [&a,&b]() {int x = a; a = b; b = x; }; swap2 (); cout << a << b << endl; auto swap3 = [](int & x,int & y) {int z = x; x = y; y = z; }; swap3 (a,b); cout << a << b << endl; auto test = [a, b]()mutable {int c = a, a = 20 , b = c; }; test (); int arr[] = {1 , 5 , 4 , 23 , 5 , 76 , 5 , 342 , 4 }; sort (arr, arr + sizeof (arr) / sizeof (arr[0 ]), [](auto a, auto b){return a > b; }); sort (arr, arr + sizeof (arr) / sizeof (arr[0 ]), std::greater <int >()); for (auto & e : arr) cout << e << " " ; cout << endl; return 0 ; }
六、异常
1.异常简介
传统处理错误的方式有: 1.返回错误码 2.终止程序:eg:exit() 其缺点是: 1.拿到错误码,需要查找错误码表才知道具体错误 2.如果一个函数是通过返回值拿数据,发生错误很难处理(让传一个自定义类型,却传了个-1之类的) 3.如果调用的函数栈很深,一层层返回错误码,处理很复杂
异常的优缺点: 优点: 1.清晰的包含错误信息 2.面对 T operator[](int i)这类函数越界错误,异常可以很好的解决 3.多层调用时,里面发生错误,不再需要层层处理,最外层直接捕获即可 4.很多第三方库都是用异常,使用异常可以更好的使用他们.eg:boost,gtest.gmock
缺点: 1.异常会导致执行流乱飞,会使得调试分析程序bug带来一些困难 2.异常可能导致资源泄露等异常安全问题,要学会RAII来解决 3.C++库里的异常体系不好用,常自己定义 4.C++异常语言可以抛任意类型异常,项目若没有规范管理,会非常混乱
总的来说,异常利大于弊,实际小项目不使用
2.异常的基本使用 STL库中有的自带抛异常
try和catch必须带有花括号,否则无法识别 如果try了没有catch到,程序会直接终止
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 int main () { try { vector<int > v{ 1 ,2 ,3 ,4 }; for (size_t i = 0 ; i <= v.size (); ++i) { cout << v.at (i) << " " ; } cout << endl; } catch (exception& e) { cout << e.what () << endl; } return 0 ; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 int Div (int m, int n) { if (n == 0 ) { throw string ("/0 error" ); } return m / n; } int Fun1 (int m, int n) { try { return Div (m, n); } catch (const string& err) { cout << __LINE__ << err << endl; } } int main () { int m, n; cin >> m>> n; try { cout << Fun1 (m, n) << endl; } catch (const string& err) { cout << __LINE__ << err<< endl; } catch (...) { cout << "none matched catch" << endl; } return 0 ; }
异常的底层逻辑
首先检查throw本身是否在try内部,如果是再查找匹配的catch. 没有则退出当前函数栈,继续在调用函数的栈中寻找匹配的catch 如果main函数的栈中还没有,则终止程序
3.异常的重新抛出,自定义异常类 异常是很危险的,非常容易导致内存泄漏
,throw后直接跑到catch那里不再回来了,导致throw后可能有的释放内存没有进行
解决的方法就是让异常重新抛出 一个实例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 class Exception { public : Exception (const char * errmsg,int errid) :_errid(errid) ,_errmsg(errmsg) {} virtual string what () = 0 ; protected : int _errid; string _errmsg; }; class SqlException : public Exception{ public : SqlException (const char * errmsg, int errid) :Exception (errmsg, errid) {} virtual string what () { return "sql error : " + _errmsg; } }; class NetworkException : public Exception{ public : NetworkException (const char * errmsg, int errid) :Exception (errmsg,errid) {} virtual string what () { return "internet error : " + _errmsg; } }; void ServeStart () { int * arr = new int [10 ]; try { if (rand () % 6 == 0 ) throw SqlException ("sql open error" , 1 ); if (rand () % 7 == 0 ) throw NetworkException ("internet connect error" , 3 ); cout << "sucessful" << endl; } catch (const Exception& err) { delete [] arr; throw ; } delete [] arr; } int main () { for (size_t i = 0 ; i < 10 ; ++i) { try { ServeStart (); } catch (Exception& err) { cout << err.what () << endl; } catch (...) { cout << "catch error" << endl; } } return 0 ; }
4.异常规范
throw(A,B) 函数会抛出A,B类型某个类的异常 throw(A) 函数只会抛出A类型异常 throw() 函数不会抛出异常 noexcept 函数不会抛出异常
void* operator new(std::size_t size) throw(std::bad_alloc); //表示这个函数只会抛出bad_alloc异常 void* operator delete(std::size_t size, void* ptr) throw(); //表示这个函数不会抛出异常 void* operator delete(std::size_t size, void* ptr) noexcept; //表示这个函数不会抛出异常
七、类型转换
1.隐式类型转换和显示类型转换
隐式类型转换:相近类型,也就是意义相似的类别之间的转换 显示类型转换:不相近类型,意义差别很大的类型的转换
1 2 3 4 5 6 7 8 int i = 1 ;double d = 3.33 ;i = d; cout << i << endl; int * p = nullptr ;p = (int *)i; cout << p << endl;
2.C++11中的类型转换操作符 1 2 3 4 static_cast reinterpret_cast const_cast dynamic_cast
①static_cast,reinterpret_cast 1 2 3 4 5 6 7 8 9 10 11 12 13 int main () { int i = 1 ; double d = 3.33 ; int * p = nullptr ; d = static_cast <double >(i); p = reinterpret_cast <int *>(i); }
②const_cast 1 2 3 4 5 6 7 8 9 10 11 12 13 14 int main () { volatile const int ci = 10 ; int * pi = const_cast <int *>(&ci); *pi = 20 ; cout << *pi << endl; cout << ci << endl; }
③dynamic_cast 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 class A { public : virtual void Fun () {} int _a; }; class B : public A{ public : int _b; }; void Fun_cast (A* pa) { B* pb = dynamic_cast <B*>(pa); if (pb != nullptr ) { cout << "translated pa->parent" << endl; pb->_a = 10 ; pb->_b = 20 ; } else { cout << "translat error" << endl; } } void main () { A a; B b; A* pa = &a; Fun_cast (pa); pa = &b; Fun_cast (pa); }
④explicit 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 class AA { public : explicit AA (int a) { cout << "AA(int a)" << endl; } AA (const AA& a) { cout << "AA(const AA& a)" << endl; } private : int _a; }; void test5 () { AA a1 (1 ) ; }
八、IO流
1.IO流 简单的使用
文件打开方式
作用
ios::in
为读文件打开文件
ios::out
为写文件打开文件
ios::ate
初始位置:文件尾
ios::app
追加方式写文件
ios::trunc
如果文件存在先删除,再创建
ios::binary
二进制方法
ofstream 写操作 ifstream 读操作 fstream 读写操作
文本文件写文件
1 2 3 4 5 6 7 8 9 #include <fstream> int main () { ofstream ofs; ofs.open ("文件.txt" ,ios::out); ofs<<"内容" <<endl; ofs.close (); return 0 ; }
文本文件读文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 #include <fstream> int main () { ifstream ifs ("文件.txt" ,ios::in) ; if (!ifs.is_open ()) { cout<<"文件打开失败" <<endl; exit (-1 ); } char buf[1024 ] = {0 }; while (ifs>>buf) cout<<buf<<endl; char buf[1024 ] = {0 }; while (ifs.getline (buf,sizeof (buf))) cout<<buf<<endl; string buf; while (getline (ifs,buf)) cout<<buf<<endl; char c; while ((c=ifs.get ()) != EOF) cout<<c; ifs.close (); return 0 ; }
二进制写文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 #include <fstream> class Person { public : char name[64 ]; int age; }; int main () { ofstream ofs; ofs.open ("文件.txt" ,ios::out | ios::binary); Person p = {"name" ,18 }; ofs.write ((const char *)&p,sizeof (Person)); ofs.close (); return 0 ; }
二进制读文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 #include <fstream> class Person { public : char name[64 ]; int age; }; int main () { ifstream ifs ("文件.txt" ,ios::in | ios::binary) ; if (!ifs.is_open ()) { cout<<"文件打开失败" <<endl; exit (-1 ); } Person p; ifs.read ((char *) &p,sizeof (Person)); cout<<"name:" <<p.name<<"age" << p.age<<endl; ifs.close (); return 0 ; }
其他写法读写文件
1 2 3 4 5 6 7 8 9 10 11 12 13 #include <fstream> int main () { ofstream ofs ("text.txt" ) ; ofs.put ('x' ); ofs.write ("jack" , 5 ); ofs.close (); ifstream ifs ("text.txt" ) ; cout<<ifs.rdbuf (); }
2.读写结构体 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 #include <fstream> struct Msg { string name; int age; }; int main () { Msg per1{ "jack" ,18 }; ofstream ofs ("text.txt" ) ; ofs << per1.name << endl; ofs << per1.age << endl; ofs.close (); Msg per2; ifstream ifs ("text.txt" ) ; cout << ifs.rdbuf (); }
3.序列化 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 #include <sstream> struct Msg { string name; int age; }; int main () { Msg per1{ "jack" ,18 }; ostringstream ost; ost << per1.name << endl; ost << per1.age << endl; string str1 = ost.str (); cout << str1 << endl; istringstream ist; Msg per2; ist >> per2.name; ist >> per2.age; }