C++标准中定义,long long是一个至少为64位的整数类型。请注意这里的用词“至少”,也就说long long的实际长度可能大于64位。
在C++11标准中添加两种新的字符类型char16_t和char32_t,它们分别用来对应Unicode字符集的UTF-16和UTF-32两种编码方法。过去也没有一个针对UTF-16和UTF-32的字符类型。到了C++11,char16_t和char32_t的出现打破了这个尴尬的局面。除此之外,C++11标准还为3种编码提供了新前缀用于声明3种编码字符和字符串的字面量,它们分别是UTF-8的前缀u8、UTF-16的前缀u和UTF-32的前缀U
如果两个字符串字面量具有相同的前缀,则生成的连接字符串字面量也具有该前缀;
随着新字符类型加入C++11标准,相应的库函数也加入进来。C11在中增加了4个字符的转换函数
C++11标准增强了命名空间的特性,提出了内联命名空间的概念。内联命名空间能够把空间内函数和类型导出到父命名空间中,这样即使不指定子命名空间也可以使用其空间内的函数和类型
C++11标准赋予了auto新的含义:声明变量时根据初始化表达式自动推断该变量的类型、声明函数时函数返回值的占位符 1.auto声明多个变量时,编译器从左向右推导,最左边为auto的具体类型 2.当使用条件表达式初始化auto声明的变量时,编译器总是使用表达能力更强的类型 3.auto声明非静态成员变量,C++11静态成员变量是可以用auto声明并且初始化的,不过前提是auto必须使用const限定符;C++17中不再需要const限定符 4.按照C++20之前的标准,无法在函数形参列表中使用auto声明形参(注意,在C++14中,auto可以为lambda表达式声明形参)
1.按值初始化,忽略cv限定
2.目标对象如果是引用,则引用属性会被忽略
3.使用auto和万能引用声明变量时,对于左值会将auto推导为引用类型
4.目标对象是一个数组或者函数,则auto会被推导为对应的指针类型
5.当auto关键字与列表初始化组合时,这里的规则有新老两个版本(C++17标准)
5.1)直接使用列表初始化,列表中必须为单元素
5.2)用等号加列表初始化,列表中可以包含单个或者多个元素,auto类型被推导为std::initializer_list<T>
1.当一眼就能看出声明变量的初始化类型的时候可以使用auto 2.对于复杂的类型,例如lambda表达式、bind等直接使用auto
多重返回值,那么需要保证返回值类型是相同
C++14标准中我们还可以把auto写到lambda表达式的形参中
C++17标准对auto关键字又一次进行了扩展,使它可以作为非类型模板形参的占位符。当然,我们必须保证推导出来的类型是可以用作模板形参的,否则无法通过编译
- C++11之前,GCC的扩展支持typeof的运算符,通过该运算符可以操作数的具体类型
- C++标准也提供了typeid运算符获取与目标操作数类型相关的信息,获取的类型信息会包含在一个类型为std::type_info的对象里,可以通过成员函数name获取类型名
- typeid的返回值是一个左值,且其生命周期一直被扩展到程序生命周期结束
- typeid返回的std::type_info删除了复制构造函数,若想保存std::type_info,只能获取其引用或者指针
- typeid的返回值总是忽略类型的 cv 限定符,也就是typeid(const T)== typeid(T))
C++11 引入decltype说明符,使用decltype说明符可以获取对象或者表达式的类型 C++11 可以利用decltype解决auto无法推断返回值类型的问题(利用decltype和尾置返回类型),但是C++14中auto已经支持auto推导返回值类型了,但是C++14中,auto又会自动忽略引用属性,这时,利用decltype和尾置返回类型可以保留引用属性
decltype(e)
- e是未加括号的标识符表达式或者未加括号的类成员访问,则推导出的类型是e的类型T,但是如果不存在该类型或者是重载函数,则无法推导
- e是函数调用或者仿函数调用,则为其返回值类型
- e是T类型的左值,推导为T&
- e是T类型的将亡值,推导为T&&
- 除了以上情况,都是T
通常情况下,decltype(e)所推导的类型会同步e的cv限定符,但是还有其他情况,当e是未加括号的成员变量时,父对象表达式的cv限定符会被忽略,不能同步到推导结果
用decltype的推导表达式规则来推导auto,另外需要注意的是,decltype(auto)必须单 独声明,也就是它不能结合指针、引用以及cv限定符
与auto一样,在C++17标准中decltype(auto)也能作为非类型模板形参的占位符
很大程度上加强了C++的泛型能力
C++11标准中的函数返回类型后置语法,通过这种方法可以让返回复杂类型的函数声明更加清晰易读。在无法使用C++14以及更新标准的情况下,通过返回类型后置语法来推导函数模板的返回类型无疑是最便捷的方法
用等号左右来区别左右值简单,但是有时无法正确断定 在C++中所谓的左值一般是指一个指向特定内存的具有名称的值(具名对象),有一个相对稳定的内存地址,并且有一段较长的生命周期 右值则是不指向稳定内存地址的匿名值(不具名对象),它的生命周期很短,通常是暂时性的 基于这一特征,我们可以用取地址符&来判断左值和右值,能取到内存地址的值为左值,否则为右值 但是有些情况,仍然要具体讨论,补充通常字面量是右值,但是字符串字面量除外
常量左值引用可以绑定右值
右值引用的特点之一是可以延长右值的生命周期
将亡值是指表达式的值即将被移动或者转移所有权,这意味着该值不再有用,并且可以被销毁或重用。将亡值通常出现在右值引用类型的函数参数、返回语句和std::move()等函数中。 产生将亡值得两种情况: 第一种是使用类型转换将泛左值转换为该类型的右值引用 第二种在C++17标准中引入,我们称它为临时量实质化,指的是纯右值转换到临时对象的过程。每当纯右值出现在一个需要泛左值的地方时,临时量实质化都会发生,也就是说都会创建一个临时对象并且使用纯右值对其进行初始化
- std::move()
- static_cast<Typeinfo&&>
只要有左值引用参与进来,最后推导的结果就是一个左值引用 只有实际类型是一个非引用类型或者右值引用类型时,最后推导出来的才是一个右值引用 利用这样得特点,可以使用万能引用:T&& / auto&&
万能引用最典型的用途被称为完美转发
[capture](params)specifiers exception -> ret {body}
最简lambda表达式:[]{}
捕获列表中的变量存在于两个作用域
- lambda表达式定义的函数作用域
- lambda表达式函数体的作用域。 前者是为了捕获变量,后者是为了使用变量。 另外,标准还规定能捕获的变量必须是一个自动存储类型。简单来说就是非静态的局部变量
Attention:如果我们将一个lambda表达式定义在全局作用域,那么lambda表达式的捕获列表必须为空
- 捕获值是将函数作用域的变量的值复制到lambda表达式对象的内部
- 捕获引用的语法与捕获值只有一个&的区别,要表达捕获引用我们只需要在捕获变量之前加上&,类似于取变量指针。
- 捕获的变量默认为常量,或者说lambda是一个常量函数(类似于常量成员函数),使用mutable说明符可以移除lambda表达式的常量性
- 对于捕获值的lambda表达式还有一点需要注意,捕获值的变量在lambda表达式定义的时候已经固定下来了,无论函数在lambda表达式定义后如何修改外部变量的值,lambda表达式捕获的值都不会变化。而且如果捕获值的每一次改变都将影响下一次的调用。
1. [this] —— 捕获this指针,捕获this指针可以让我们使用this类型的成员变量和函数。 2. [=] —— 捕获lambda表达式定义作用域的全部变量的值,包括this。 3. [&] —— 捕获lambda表达式定义作用域的全部变量的引用,包括this。
lambda表达式在编译期会由编译器自动生成一个闭包类,在运行时由这个闭包类产生一个对象,我们称它为闭包。 在C++中,所谓的闭包可以简单地理解为一个匿名且可以包含定义时作用域上下文的函数对象。
C++中的lambda表达式可以分为有状态和无状态两种。无状态lambda表达式指的是不需要捕获外部变量的lambda表达式,也就是没有状态的lambda表达式。
std::find_if(x.cbegin(),c.cend(),[](int i){return (i%3) ==0;})
所谓广义捕获实际上是两种捕获方式,第一种称为简单捕获,这种捕获就是我们在前文中提到的捕获方法,即 [identifier]、[&identifier]以及[this]等。第二种叫作初始化捕获,这种捕获方式是在C++14标准中引入的,它解决了简单捕获的一个重要问题,即只能捕获lambda表达式定义上下文的变量,而无法捕获表达式结果以及自定义捕获变量名
C++14标准让lambda表达式具备了模版函数的能力,我们称它为泛型lambda表达式
在捕获列表中直接添加[*this],然后在lambda表达式函数体内直接使用this指向对象的成员
在C++20标准中引入了[=, this]捕获this指针的语法,它实际上表达的意思和[=]相同,目的是让程序员们区分它与[=,*this]的不同.在C++20标准中还特别强调了要用[=, this]代替[=](C++17这样用是语法错误的)
C++11标准提出了新的初始化方法,即在声明非静态数据成员的同时直接对其使用=或者{}初始化 在此之前只有类型为整型或者枚举类型的常量静态数据成员才有这种声明默认初始化的待遇
C++20标准又对该特性做了进一步扩充。在C++20中我们可以对数据成员的位域进行默认初始化了
使用括号初始化的方式叫作直接初始化,还有,比如new运算符和类构造函数的初始化列表也属于直接初始化 使用等号初始化的方式叫作拷贝初始化(复制初始化),还有,函数传参和return返回则是拷贝初始化
C++11标准引入了列表初始化,它使用大括号{}对变量进行初始化,和传统变量初始化的规则一样,它也区分为直接初始化和拷贝初始化
标准容器之所以能够支持列表初始化,离不开编译器支持的同时,它们自己也必须满足一个条件:支持std::initializer_list为形参的构造函数。
列表初始化不支持隐式缩窄转换 1.从浮点类型转换整数类型。 2.从long double转换到double或float,或从double转换到float,除非转换源是常量表达式以及转换后的实际值在目标可以表示的值范围内。 3.从整数类型或非强枚举类型转换到浮点类型,除非转换源是常量表达式,转换后的实际值适合目标类型并且能够将生成目标类型的目标值转换回原始类型的原始值。 4.从整数类型或非强枚举类型转换到不能代表所有原始类型值的整数类型,除非源是一个常量表达式,其值在转换之后能够适合目标类型。
如果有一个类同时拥有满足列表初始化的构造函数,且其中一个是以std::initializer_list为参数,那么编译器将优先以std::initializer_ list为参数构造函数
可以使用指定初始化的情况 1.它要求对象必须是一个聚合类型 2.指定的数据成员必须是非静态数据成员 3.每个非静态数据成员最多只能初始化一次 4.非静态数据成员的初始化必须按照声明的顺序进行 5.针对联合体中的数据成员只能初始化一次,不能同时指定 6.不能嵌套指定初始化数据成员 7.在C++20中,一旦使用指定初始化,就不能混用其他方法对数据成员初始化了
有特殊待遇的成员函数 1.默认构造函数 2.析构函数 3.拷贝构造函数 4.拷贝赋值运算符函数 5.移动构造函数(C++11新增) 6.移动赋值运算符函数(C++11新增) C++11标准提供了一种方法能够简单有效又精确地控制默认特殊成员函数的添加和删除 =default和=delete
显式删除不仅适用于类的成员函数,对于普通函数同样有效,用于类成员函数时必须类内声明
用于类成员函数时必须类内或者类外声明
在C++11标准中解除了联合体的大部分限制,联合类型的成员可以是除了引用类型外的所有类型,在C++11中如果有联合类型中存在非平凡类型,那么这个联合类型的特殊成员函数将被隐式删除,也就是说我们必须自己至少提供联合类型的构造和析构函数。 补充:联合类型的静态成员不属于联合类型的任何对象,所以并不是对象构造时被定义的,不能在联合类型内部初始化
C++11引入了委托构造函数的概念,它允许一个构造函数调用同一个类中的另一个构造函数,从而避免了重复的代码。 使用委托构造函数注意以下几点: 1.构造函数,它可以既是委托构造函数也是代理构造函数 2.不要递归循环委托 3.如果一个构造函数为委托构造函数,那么其初始化列表里就不能对数据成员和基类进行初始化 4.委托构造函数的执行顺序是先执行代理构造函数的初始化列表,然后执行代理构造函数的主体,最后执行委托构造函数的主体 5.如果在代理构造函数执行完成后,委托构造函数主体抛出了异常,则自动调用该类型的析构函数
代理构造函数是一个函数模板,即称为委托模板构造函数。这样做的意义在于泛化了构造函数,减少冗余的代码的产生。将代理构造函数编写成函数模板往往会获得很好的效果
当使用Function-try-block去捕获委托构造函数异常时,其过程和捕获初始化列表异常如出一辙。如果一个异常在代理构造函数的初始化列表或者主体中被抛出,那么委托构造函数的主体将不再被执行,与之相对的,控制权会交到异常捕获的catch代码块中
派生类Derived使用using Base::Base
让编译器为自己生成转发到基类的构造函数
使用继承构造函数注意以下几点:
1.派生类是隐式继承基类的构造函数,所以只有在程序中使用了这些构造函数,编译器才会为派生类生成继承构造函数的代码
2.派生类不会继承基类的默认构造函数和复制构造函数
3.继承构造函数不会影响派生类默认构造函数的隐式声明,也就是说对于继承基类构造函数的派生类,编译器依然会为其自动生成默认构造函数的代码
4.在派生类中声明签名相同的构造函数会禁止继承相应的构造函数
5.派生类继承多个签名相同的构造函数会导致编译失败
6.继承构造函数的基类构造函数不能为私有
C++11标准引入了基于范围的for循环特性,该特性隐藏了迭代器的初始化和更新过程
for ( range_declaration : range_expression ) loop_statement
范围表达式可以是数组或对象,对象必须满足以下2个条件中的任意一个
- 对象类型定义了begin和end成员函数
- 定义了以对象类型为参数的begin和end普通函数
在C++标准中有一条特殊的规则,即0既是一个整型常量,又是一个空指针常量。0作为空指针常量还能隐式地转换为各种指针类型。 NULL是一个宏,在C++11标准之前其本质就是0 C++将NULL定义为0,而C语言将NULL定义为(void *)0
鉴于0作为空指针常量的种种劣势,C++标准委员会在C++11中添加关键字nullptr表示空指针的字面量,它是一个std::nullptr_t类型的纯右值。nullptr的用途非常单纯,就是用来指示空指针,它不允许运用在算术表达式中或者与非指针类型进行比较(除了空指针常量0)。它还可以隐式转换为各种指针类型,但是无法隐式转换到非指针类型。注意,0依然保留着可以代表整数和空指针常量的特殊能力,保留这一点是为了让C++11标准兼容以前的C++代码。