携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第6天,点击查看活动详情
C++参考手册
c++11新特性
一、新类型
C++11新增了类型long long和unsigned long long,以支持64位(或更宽)的整型,新增了类型char16_t和char32_t,以支持16位和32位的字符表示;还新增了"原始"字符串。
二、统一的初始化
C++11扩大了用大括号括起的列表(初始化列表)的适用范围,使其可用于所有内置类型和用户定义的类型(即类对象)。使用初始化列表时,可添加等号(=),也可不添加:
三、声明
auto
实现自动类型推断,要求进行显示初始化,让编译器能够将变量的类型设置为初始值的类型:
decItype
decltype将变量的类型声明为表达式指定的类型。
decltype(expression) var;
decltype(x) y; 让y的类型与x相同,x是一个表达式
四、返回类型后置
C++11新增了一种函数声明语法:在函数名和参数列表后面(而不是前面)指定返回类型;
五、模板别名
对于冗长或复杂的标识符,如果能够创建其别名将很方便。之前,C++提供了typedef:
typedef std : : vector<std: : string> : :iterator itType;
C++11提供了另一种创建别名的语法:
using itType = std::vector<std: : string> : :iterator;
差别在于,这种方式也可以用于模板部分具体化,但typedef不能:
六、智能指针
1.用来干嘛的
2.核心思想
如果在程序中使用new从堆(自由存储区)分配内存,等到不再需要时,应使用delete将其释放。C++引入了智能指针auto_ptr (C++98),以帮助自动完成这个过程。智能指针是行为类似于指针的类对象。
在使用时(尤其是使用STL),需要更精致的机制,C++11摒弃了auto_ptr,并新增了三种智能指针:unique_ptr、shared_ptr和weak_ptr。
智能指针和普通指针的区别
智能指针和普通指针的区别在于智能指针实际上是对普通指针加了一层封装机制,区别是它负责自动释放所指的对象,这样的一层封装机制的目的是为了使得智能指针可以方便的管理一个对象的生命期。
使用
auto_ptr、unique_ptr和shared_ptr这三个智能指针模板都定义了类似指针的对象,可以将new获得(直接或间接)的地址赋给这种对象。当智能指针过期时,其析构函数将使用delete来释放内存。
如果ps和vo是常规指针,则两个指针指向同一个string对象。这样会ps和vo过期时,会释放一块内存两次,避免这种问题,有如下几种方式:
- 重载赋值运算符,使之执行深复制。这样两个指针指向不同的对象,其中的一个对象是另一个对象的副本;
- 建立所有权(ownership)概念,对于特定的对象,只能有一个智能指针可拥有它,这样只有拥有对象的智能指针的析构函数会删除该对象。然后,让赋值操作转让所有权。这就是用于
auto_ptr和unique_ptr的策略,但unique_ptr的策略更严格 - 创建智能更高的指针,跟踪引用特定对象的智能指针计数。这称为引用计数(reference counting)。例如,赋值时,计数将加1,而指针过期时,计数将减1。仅当最后一个指针过期时,才调用delete。这是
shared_ptr采用的策略。 - 同样的策略也适用于复制构造函数。
unique_ptr相比auto_ptr还有一个优点,它有一个可用于数组的变体。模板auto_ptr使用delete而不是delete[],因此只能与new一起使用,而不能与new一起使用。但unique_ptr有使用new[]和delete[][的版本:
各自特性/智能指针的选择
- 如果程序要使用多个指向同一个对象的指针,应该选择shared_ptr; 引用计数的模式
- 如果程序不需要多个指向同一个对象的指针,则可以使用unique_ptr;
- 如果使用new[]分配内存,应该选择unique_ptr;
- 如果函数使用new分配内存,并返回指向该内存的指针,将其返回类型声明为unique_ptr是不错的选择。
七、异常规范方面的修改
以前,C++提供了一种语法,可用于指出函数可能引发哪些异常:
void test(throw(int);
void test(throw());
C++11摒弃了异常规范,标准委员会认为,指出函数不会引发异常有一定的价值,为此添加了关键字noexcept
八、作用域内枚举
传统的枚举存在一些问题,其中之一是两个枚举定义中的枚举量可能发生冲突。枚举名的作用域为枚举定义所属的作用域,这意味着如果在同一个作用域内定义两个枚举,它们的枚举成员不能同名。为避免这种问题,C++11提供了一种新枚举,其枚举量的作用域为类:
enum class egg { sma1l,Medium,Large,Jumbo};
enum class t_shirt {sma11,Medium,Large,x1arge};
九、对类的修改
显示转换运算符
C++很早就支持对象自动转换,但是自动类型转换可能导致意外转换的问题,为了解决这个问题,C++引入了关键字explicit,以禁止单参数构造函数导致的自动转换:
C++11拓展了explicit的这种用法,使得可以对转换函数做类似的处理
类内成员初始化
可以使用等号或大括号版本的初始化,但不能使用圆括号版本的初始化。如果构造函数在成员初始化列表中提供了相应的值,这些默认值将被覆盖。
class Person {
string name = "zs";
int age {22};
pub1ic:
Person(){}
Person(string s, int a) : name(s), age(a) {};
}
十、右值引用
传统的C++引用(现在称为左值引用)使得标识符关联到左值。左值是一个表示数据的表达式(如变量名或解除引用的指针),程序可获取其地址。最初,左值可出现在赋值语句的左边,但修饰符const的出现使得可以声明这样的标识符,即不能给它赋值,但可获取其地址
int & rn = n;
int & rt = *pt;
const int & rb = b;
const int & rb = 10; // 非法 c++11之后允许
C++11新增了右值引用(rvalue reference),这种引用可指向右值(即可出现在赋值表达式右边的值),但不能对其应用地址运算符。右值包括字面常量(C-风格字符串除外,它表示地址)、诸如× +y等表达式以及返回值的函数〔条件是该函数返回的不是引用),右值引用使用&&声明:
int x = 10;
int y = 23;
int && r1 = 13;
int && r2 = x + y;
double && r3 = std: : sqrt(2.0);
右值引用的作用:
- 避免拷贝,提高性能,实现move()
- 避免重载参数的复杂性,实现forward()
十一、移动语义
所谓移动语义,指的就是以移动而非深拷贝的方式初始化含有指针成员的类对象。简单的理解,移动语义指的就是将其他对象(通常是临时对象)拥有的内存资源"移为已用”。
vector<string a11caps(const vector<string> & vs) {
vector<string> temp;
//在temp中存储vs的全大写版本return temp;
}
vector<string> vstr;
//建立一个vector,20000个 string,每个 string 1000个字符
vector<strings vstr_copy1(vstr);//#1
vector<string> vstr_copy2(a11caps(vstr));// #2
allcaps()创建了对象temp,该对象管理着20000000个字符;vector和string的复制构造函数创建这20000000个字符的副本,然后程序删除allcaps()返回的临时对象。这里做了大量的无用功。考虑到临时对象被删除了,如果编译器将对数据的所有权直接转让给vstr_copy2,不是更好么?也就是说,不将20000000个字符复制到新地方,再删除原来的字符,而将字符留在原来的地方,并将vstr_copy2与之相关联。这类似于在计算机中移动文件的情形:实际文件还留在原来的地方,而只修改记录。这种方法被称为移动语义(move semantics) 。
要实现移动语义,需要采取某种方式,让编译器知道什么时候需要复制,什么时候不需要。这就是右值引用发挥作用的地方。可定义两个构造函数,其中一个是常规复制构造函数,它使用const左值引用作为参数,这个引用关联到左值实参,如语句#1中的vstr;另一个是移动构造函数,它使用 右值引用作为参数,该引用关联到右值实参,如语句#2中allcaps(vstr)的返回值。复制构造函数可执行深复制,而移动构造函数只调整记录。在将所有权转移给新对象的过程中,移动构造函数可能修改其实参,这意味着右值引用参数不应是const。
当类中同时包含拷贝构造函数和移动构造函数时,如果使用临时对象初始化当前类的对象,编译器会优先调用移动构造函数来完成此操作。只有当类中没有合适的移动构造函数时,编译器才会退而求其次,调用拷贝构造函数。默认情况下,左值初始化同类对象只能通过拷贝构造函数完成,如果想调用移动构造函数,则必须使用右值进行初始化。C++11标准中为了满足用户使用左值初始化同类对象时也通过移动构造函数完成的需求,新引入了std:move()函数,它可以将左值强制转换成对应的右值,由此便可以使用移动构造函数。
十二、Lambda表达式
count13 = count_if(numbers.begin(),numbers.end(),[](int x) {return x % 13 == 0;});cout <<"能被13整除的个数为:" << count13 << end1;
count3 = count13 = 0 ;
for_each(numbers.begin(),numbers.end(),[&](int x) {count3+= x % 3 m= 0; count13 +=×%13 == 0; });
&的作用:以引用的方式传递参数
问题:Lambda表达式,仿函数,和普通函数的区别
- 距离:Lambda
- 简洁:Lambda
- 效率:基本相同
- 功能:Lambda和仿函数更多
十三、类型转换运算符
使用C风格的类型转换可以把想要的任何东西转换成我们需要的类型,但是这种类型转换太过松散。对于这种松散的情况,C++提供了更严格的类型转换,可以提供更好的控制转换过程,并添加4个类型转换运算符,使转换讨程更规范:
- static_cast
- dynamic_cast
- const_cast
- reinterpret_cast
静态转换 static_cast
用于类层次结构中基类(父类)和派生类(子类)之间指针或引用的转换
- 进行上行转换(把派生类(子类)的指针或引用转换成基类(父类)表示)是安全的
- 进行下行转换(把基类(父类)指针或引用转换成派生类(子类)表示)时,由于没有动态类型检查,所以是不安全的
用于基本数据类型之间的转换,如把 int转换成char,把 char转换成 int。这种转换的安全性也要开发人员来保证,可能会损失精度
动态转换 dynamic_cast
主要用于类层次间的上行转换和下行转换
- 子类转化为父类 -- 安全
- 产生多态时,父类可以转化为子类
在类层次间进行上行转换(子类 -> 父类)时,dynamic_cast和static_cast的效果是一样的
在进行下行转换时,dynamic_cast具有类型检查(产生多态)的功能,比static_cast更安全
常量转换 const_cast
只能用在指针和引用
- 常量指针 <--> 非常量指针
- 常量引用 <--> 非常量引用
重新解释转换 reinterpret_cast
这是最不安全的一种转换机制,最有可能出问题
- 主要用于将一种数据类型从转换为另一种类型,它可以将一个指针转换成一个整数,也可以将一个整数转换成一个指针