全文检索工具——grep
可变参数模板函数:
可变模板参数定义如下:参数包的展开:
1、递归方式展开 通过递归函数展开参数包,需要提供【一个参数包展开的函数】和【一个递归终止函数】。 #include <iostream> using namespace std; //递归终止函数 void debug() { cout << "empty\n"; } //展开函数 template <class T, class ... Args> void debug(T first, Args ... last) { cout << "parameter " << first << endl; debug(last...); } int main() { debug(1, 2, 3, 4); return 0; }递归调用过程如下:
debug(1, 2, 3, 4); debug(2, 3, 4); debug(3, 4); debug(4); debug();可变参数模板:
模板递归和特化方式展开参数包:
#include <iostream> using namespace std; template <long... nums> struct Multiply;// 变长模板的声明 template <long first, long... last> struct Multiply<first, last...> // 变长模板类 { static const long val = first * Multiply<last...>::val; }; template<> struct Multiply<> // 边界条件 { static const long val = 1; }; int main() { cout << Multiply<2, 3, 4, 5>::val << endl; // 120 return 0; } 这个print()接受一个参数和一包参数(……表示的就是不知道多少个参数)【这里不仅个数随意,参数类型也随意】 template<typename T,typename… Types>【表示的1+其余】 当没有参数的时候会调用①的print()函数 可以帮助我们做递归,这递归的目的就是为了将参数个数一一分解 怎样分解,就是把n变成1+其他 sizeof…(arg)可以获取参数的个数下面这两块没看懂????
重点看下面的注释部分
#include <iostream> #include <vector> #include <string> using namespace std; double foo() {} void func(vector<string> & tmp) { for (auto i = tmp.begin(); i < tmp.end(); i++) { // 一些代码 } } int main() { auto x = 1; // x的类型为int auto y = foo(); // y的类型为double struct m { int i; }str; auto str1 = str; // str1的类型是struct m auto z; // err, 无法推导,无法通过编译 z = x; return 0; }注意点(这几种无法编译的情况为啥呢????)
1: auto函数参数,有些编译器无法通过编译 2: auto非静态成员变量,无法通过编译 3: auto数组,无法通过编译 4: auto模板参数(实例化时),无法通过编译 void fun(auto x =1) {} // 1: auto函数参数,有些编译器无法通过编译 struct str { auto var = 10; // 2: auto非静态成员变量,无法通过编译 }; int main() { char x[3]; auto y = x; auto z[3] = x; // 3: auto数组,无法通过编译 // 4: auto模板参数(实例化时),无法通过编译 vector<auto> x = {1}; return 0; } 标准库本身也有使用新的关键字初始化 类内成员初始化
class Mem { public: Mem(int i): m(i){} //初始化列表给m初始化 int m; }; class Group { public: Group(){} private: int data = 1; // 使用"="初始化非静态普通成员,也可以 int data{1}; Mem mem{2}; // 对象成员,创建对象时,可以使用{}来调用构造函数 string name{"mike"}; };初始化列表
初始化方式如下 int a[]{1, 3, 5}; int i = {1}; int j{3}; 下面是用于结构体类型的情况 struct Person { std::string name; int age; }; int main() { Person p = {"Frank", 25}; std::cout << p.name << " : " << p.age << std::endl; } 其他一些不方便初始化的地方使用, 比如std的初始化,如果不使用这种方式,只能用构造函数来初始化,难以达到效果: std::vector<int> ivec1(3, 5); std::vector<int> ivec2 = {5, 5, 5}; std::vector<int> ivec3 = {1,2,3,4,5}; //不使用列表初始化用构造函数难以实现防止类型收窄
【类型收窄】指的是导致**数据内容发生变化**或者**精度丢失**的隐式类型转换。 使用列表初始化可以防止类型收窄。关于类型收窄
为什么要防止类型收窄
struct Foo { Foo(int i) { std::cout << i << std::endl; } }; Foo foo(1.2); 【以上代码在C++中能够正常通过编译】,但是传递之后的i却不能完整地保存一个浮点型的数据。 上面的示例让我们对类型收窄有了一个大概的了解。具体来说,类型收窄包括以下几种情况: 1)从一个浮点数隐式转换为一个整型数,如int i=2.2。 2)从高精度浮点数隐式转换为低精度浮点数,如从long double隐式转换为double或float。 3)从一个整型数隐式转换为一个浮点数,并且超出了浮点数的表示范围,如float x=(unsigned long long)-1。 4)从一个整型数隐式转换为一个长度较短的整型数,并且超出了长度较短的整型数的表示范围,如char x=65536。 在C++98/03中,像上面所示类型收窄的情况,编译器并不会报错(或报一个警告,如Microsoft Visual C++)。 这往往会导致一些隐藏的错误。【在C++11中,可以通过列表初始化来检查及防止类型收窄】 列表初始化防止类型收窄示例请看上图的报错
int a = 1.1; // OK int b = { 1.1 }; // error float fa = 1e40; // OK float fb = { 1e40 }; // error float fc = (unsigned long long)-1; // OK float fd = { (unsigned long long)-1 }; // error float fe = (unsigned long long)1; // OK float ff = { (unsigned long long)1 }; // OK const int x = 1024, y = 1; char c = x; // OK char d = { x }; // error char e = y; // OK char f = { y }; // OK 上面的各种隐式类型转换中,只要遇到了类型收窄的情况,初始化列表就不会允许这种转换发生。 其中需要注意的是x、y被定义成了const int。如果去掉const限定符,那么***一个变量f也会因为类型收窄而报错由源码可知,Initializer Lists的背后就是array
clion编译器中的源码和老师的一样
Initializer Lists的使用就可以使得max和min接受任意多的参数【这就是Initializer Lists的妙用】
下面是C++ 2.0【之后】的用法
基于范围的for循环
int a[] = { 1, 2, 3, 4, 5 }; int n = sizeof(a) / sizeof(*a); //元素个数 for (int i = 0; i < n; ++i) { int tmp = a[i]; cout << tmp << ", "; } cout << endl; for (int tmp : a) { cout << tmp << ", "; } cout << endl; for (int i = 0; i < n; ++i) { int &tmp = a[i]; tmp = 2 * tmp; cout << tmp << ", "; } cout << endl; for (int &tmp : a) { tmp = 2 * tmp; cout << tmp << ", "; } cout << endl;使用基于范围的for循环,其【for循环迭代的范围必须是可确定的】:
int func(int a[])//形参中数组是指针变量,无法确定元素个数 { for(auto e: a) // err, 编译失败 { cout << e; } } int main() { int a[] = {1, 2, 3, 4, 5}; func(a); return 0; }静态断言
C/C++提供了调试工具assert,这是一个宏,用于在【运行阶段】对断言进行检查, 如果条件为真,执行程序,否则调用abort()。 int main() { bool flag = false; //如果条件为真,程序正常执行,如果为假,终止程序,提示错误 assert(flag == true); //#include <cassert>或#include <assert.h> cout << "Hello World!" << endl; return 0; }【C++ 11新增了关键字static_assert】,可用于在【编译阶段】对断言进行测试。
语法: static_assert(常量表达式,提示字符串) 【注意】:只能是【常量表达式】,不能是变量 示例: int main() { //该static_assert用来确保编译仅在32位的平台上进行,不支持64位的平台 static_assert( sizeof(void *)== 4, "64-bit code generation is not supported."); cout << "Hello World!" << endl; return 0; } 静态断言的好处: 更早的报告错误,我们知道构建是早于运行的,更早的错误报告意味着【开发成本的降低】 减少运行时开销,静态断言是【编译期】检测的,【减少了运行时开销】
优秀博客
如果程序员【没有显式地为一个类定义】某个特殊成员函数,【而又需要用到】该特殊成员函数时, 则编译器会隐式的为这个类生成一个默认的特殊成员函数。 default针对的是【默认构造函数】、【析构函数】、【拷贝构造函数】和【拷贝赋值函数】 defaulted 函数特性仅适用于【类的特殊成员函数】(上面四种),且该【特殊成员函数没有默认参数】。好处: 1. 减轻程序员的编程工作量; 2. 获得编译器自动生成的默认特殊成员函数的高的代码执行效率。
default详解
delete 为了能够让程序员【显式的禁用某个函数】, C++11 标准引入了一个新特性:deleted 函数。程序员只需在函数声明后上“=delete;”,就可将该函数禁用。 用法: 1、用于禁用类的某些转换构造函数,从而避免不期望的类型转换。 2、用来禁用某些用户自定义的类的 new 操作符,从而避免在自由存储区创建类的对象 3、必须在函数第一次声明的时候将其声明为 deleted 函数,否则编译器会报错。 (即对于类的成员函数而言,deleted 函数必须在类体里(inline)定义,而不能在类体外(out-of-line)定义。) 4、虽然 defaulted 函数特性规定了只有类的特殊成员函数才能被声明为 defaulted 函数, 【但是】 deleted 函数特性并没有此限制。非类的成员函数,即普通函数也可以被声明为 deleted 函数。 5、虽然delete让函数不能被调用。但是【函数标示符】仍是有效的,在名字查找和函数重载解析时仍会查找到该函数标示符。 如果编译器在解析重载函数时,解析结果为 deleted 函数,则会出现编译错误。 构造函数可以重载,但是拷贝构造函数不能见下面的示例:
#include <iostream> #include <type_traits> //std::is_same using namespace std; using uint = unsigned int; typedef unsigned int UINT; using sint = int; int main() { //std::is_same 判断类型是否一致 //这个结构体作用很简单,就是两个一样的类型会返回true cout << is_same<int, sint>::value << endl; // 1 cout << is_same<uint, UINT>::value << endl; // 1 return 0; }参考这篇博客
下面是std::initializer_list的默认构造函数,其中使用了noexcept。 constexpr initializer_list() noexcept : _M_array(0), _M_len(0) { } 该关键字告诉编译器,函数中不会发生异常,这有利于编译器对程序做更多的优化。 如果在运行时,noexecpt函数向外抛出了异常(如果函数内部捕捉了异常并完成处理,这种情况不算抛出异常), 程序会直接终止,调用std::terminate()函数,该函数内部会调用std::abort()终止程序。C++的异常处理
C++中的异常处理是在运行时而不是编译时检测的。为了实现运行时检测,编译器创建额外的代码,然而这会妨碍程序优化。 在实践中,一般两种异常抛出方式是常用的: 一个操作或者函数可能会抛出一个异常; 一个操作或者函数不可能抛出任何异常。 后面这一种方式中在以往的C++版本中常用throw()表示,在C++ 11中已经被noexcept代替。 void swap(Type& x, Type& y) throw() //C++11之前 { x.swap(y); } //下面单独使用noexcept,表示其所限定的swap函数绝对不发生异常 void swap(Type& x, Type& y) noexcept //C++11 { x.swap(y); }有条件的noexcept
void swap(Type& x, Type& y) noexcept(noexcept(x.swap(y))) //C++11 { x.swap(y); } 它表示,如果操作x.swap(y)不发生异常,那么函数swap(Type& x, Type& y)一定不发生异常。什么时候该使用noexcept
使用noexcept表明函数或操作不会发生异常,会给编译器更大的优化空间。 然而,【并不是加上noexcept就能提高效率】。 以下情形鼓励使用noexcept: 1、移动构造函数(move constructor) 2、移动分配函数(move assignment) 3、析构函数(destructor)。 在新版本的编译器中,析构函数是默认加上关键字noexcept的。C++ 枚举类型详解
C++ 11引入了一种新的枚举类型,即“枚举类”,又称“强类型枚举”。 声明请类型枚举非常简单,只需要【在enum后加上使用class或struct】(声明实现)。如: enum Old{Yes, No}; // old style enum class New{Yes, No}; // new style enum struct New2{Yes, No}; // new style “传统”的C++枚举类型有一些【缺点】: 它会在一个代码区间中抛出枚举类型成员(如果在相同的代码域中的两个枚举类型具有相同名字的枚举成员,这会导致【命名冲突】), 【它们会被隐式转换为整型,并且不可以指定枚举的底层数据类型】。 int main() { enum Status{Ok, Error}; //enum Status2{Ok, Error};//err, 导致命名冲突, Status已经有成员叫Ok, Error return 0; }上诉的缺点在C++ 11中都获得了解决√
int main() { enum class Status {Ok, Error}; enum struct Status2{Ok, Error}; //Status flag1 = 10; // err,无法隐式转换为int类型 //Status flag2 = Ok; // err,必须使用强类型名称 Status flag3 = Status::Ok; enum class C : char { C1 = 1, C2 = 2};//指定枚举的底层数据类型 enum class D : unsigned int { D1 = 1, D2 = 2, Dbig = 0xFFFFFFF0U }; cout << sizeof(C::C1) << endl; // 1 cout << (unsigned int)D::Dbig << endl; // 编译器输出一致,4294967280 cout << sizeof(D::D1) << endl; // 4 cout << sizeof(D::Dbig) << endl; // 4 return 0; }参考博客
上诉代码若是去掉 using A::A; 这一行,看报错信息
注意: 1、继承的构造函数【只能初始化基类中的成员变量】,不能初始化派生类的成员变量 2、如果1、基类的构造函数被声明为私有,或者2、派生类是从基类中虚继承,那么不能继承构造函数 3、一旦使用继承构造函数,编译器不会再为派生类生成默认构造函数参考
参考博客
C++11之前,类模板是支持默认的模板参数,却不支持函数模板的默认模板参数:
//1、普通函数带默认参数,c++98 编译通过,c++11 编译通过 void DefParm(int m = 3) {} //2、类模板是支持默认的模板参数,c++98 编译通过,c++11 编译通过 template <typename T = int> class DefClass {}; //3、函数模板的默认模板参数, c++98 - 编译失败,c++11 - 编译通过 template <typename T = int> void DefTempParm() {}【类模板的默认模板参数必须【从右往左】定义】,函数模板的默认模板参数则没这个限定:
template<class T1, class T2 = int> class DefClass1; template<class T1 = int, class T2> class DefClass2; // 无法通过编译 template<class T, int i = 0> class DefClass3; template<int i = 0, class T> class DefClass4; // 无法通过编译 template<class T1 = int, class T2> void DefFunc1(T1 a, T2 b); template<int i = 0, class T> void DefFunc2(T a);override确保在派生类中声明的函数跟基类的虚函数有相同的签名:
class A1 { public: //这是第一个虚函数,没有重写,不能用override修饰 virtual int func(int a) { } }; class A2:public A1 { public: //在重写虚函数地方,加上override, 要求重写的虚函数和基类一模一样 virtual int func(int b) override { } }; 上诉代码如果将派生类中的函数改为 virtual int func(float b) overridefinal阻止类的进一步派生和虚函数的进一步重写:
//final【阻止类】的进一步【派生】,【虚函数】的进一步【重写】 #if 0 class A1 final //加上final,指定A1不能派生 { int a; }; class A2: public A1 //err, 基类不能再派生了 { }; #endif //基类 class B1 { public: virtual void func() final {} //这是最终版本的虚函数,不能再重写 }; //派生类重写基类的虚函数 class B2: public B1 { public: //virtual void func() {} //err, 基类中的虚函数是最终版本,不能再重写 };追踪返回类型
返回类型后置:在函数名和参数列表后面指定返回类型。基本用法
int getSize(); int main(void) { int tempA = 2; /*1.dclTempA为int.*/ decltype(tempA) dclTempA; /*2.dclTempB为int,对于getSize根本没有定义,但是程序依旧正常,因为decltype只做分析,并不调用getSize().*/ decltype(getSize()) dclTempB; return 0; }与const结合
double tempA = 3.0; const double ctempA = 5.0; const double ctempB = 6.0; const double *const cptrTempA = &ctempA; /*1.dclTempA推断为const double(保留顶层const,此处与auto不同)*/ decltype(ctempA) dclTempA = 4.1; /*2.dclTempA为const double,不能对其赋值,编译不过*/ dclTempA = 5; /*3.dclTempB推断为const double * const*/ decltype(cptrTempA) dclTempB = &ctempA; /*4.输出为4(32位计算机)和5*/ cout<<sizeof(dclTempB)<<" "<<*dclTempB<<endl; /*5.保留顶层const,不能修改指针指向的对象,编译不过*/ dclTempB = &ctempB; /*6.保留底层const,不能修改指针指向的对象的值,编译不过*/ *dclTempB = 7.0;与引用结合
int tempA = 0, &refTempA = tempA; /*1.dclTempA为引用,绑定到tempA*/ decltype(refTempA) dclTempA = tempA; /*2.dclTempB为引用,必须绑定到变量,编译不过*/ decltype(refTempA) dclTempB = 0; /*3.dclTempC为引用,必须初始化,编译不过*/ decltype(refTempA) dclTempC; /*4.双层括号表示引用,dclTempD为引用,绑定到tempA*/ decltype((tempA)) dclTempD = tempA; const int ctempA = 1, &crefTempA = ctempA; /*5.dclTempE为常量引用,可以绑定到普通变量tempA*/ decltype(crefTempA) dclTempE = tempA; /*6.dclTempF为常量引用,可以绑定到常量ctempA*/ decltype(crefTempA) dclTempF = ctempA; /*7.dclTempG为常量引用,绑定到一个临时变量*/ decltype(crefTempA) dclTempG = 0; /*8.dclTempH为常量引用,必须初始化,编译不过*/ decltype(crefTempA) dclTempH; /*9.双层括号表示引用,dclTempI为常量引用,可以绑定到普通变量tempA*/ decltype((ctempA)) dclTempI = ctempA;与指针结合
int tempA = 2; int *ptrTempA = &tempA; /*1.常规使用dclTempA为一个int *的指针*/ decltype(ptrTempA) dclTempA; /*2.需要特别注意,表达式内容为解引用操作,dclTempB为一个引用,引用必须初始化,故编译不过*/ decltype(*ptrTempA) dclTempB;
参考
C++ 11新特性