c++11特性
c++11特性
1. auto类型说明符
-
和只对应一种类型说明符(int,double)不同。auto用于初始化表达式中推断出变量的数据类型。auto定义的变量必须要有初始值
auto i=1;//自动推断为int型 auto d=1.0;//自动推断为double auto str="hello world";//字符串类型 auto a;//错误,没有初始化表达式就无法确定a 的类型 auto it=v.begin();//推断it是一个迭代器
auto_ptr智能指针
指向对象的原始指针,在c++中一般在构造函数中进行资源申请,在析构函数中进行资源释放,在c语言中有可能在某函数内部进行资源申请,在函数调用处进行资源释放,资源的申请和释放不再同一处非常容易产生内存泄漏问题。
c++中的动态内存管理是通过new和delete两个操作符来完成的。new操作符,为对象分配内存并调用对象所属类的构造函数,返回一个指向该对象的指针,delete调用时销毁对象,并释放对象所在的内存。
智能指针采用引用计数是完成资源管理的常用手法,当引用计数为0时,对象即被销毁
在STL库中对应的实现主要有shared_ptr、weak_ptr两种,二者都是类模板(class template),定义在< memory >头文件里。
二者的“计数”在主流平台上是原子操作,无锁,性能不俗;线程安全性和STL中的string一样。
不同点:
- shared_ptr控制对象的生命周期。是强引用(想象成铁丝绑住堆上的对象),只要有一个执行x对象的shared_ptr存在,x对象就不会析构。当指向对象x的最后一个shared_ptr析构或reset()的时候,x保证会被销毁。
#include <memory>
#include <iostream>
using namespace std;
int main()
{
//用make_shared来获得shared_ptr
shared_ptr<string> p1 = make_shared<string>("");
//share_ptr可以使用一个new表达式返回的指针进行初始化
shared_ptr<int> p4(new int(1024));//true
shared_ptr<int> p5 = new int(1024);//false
//不能将一个new表达式返回的指针赋值给share_ptr
不要混用new和share_ptr,一旦将一个new表达式返回的指针交给share_ptr管理智慧,就不要再通过普通指针访问这块内存
share_ptr可以通过reset方法重置指向另一个对象,此时原对象的引用计数减一。
//创建一个智能指针
shared_ptr<int> p1 = make_shared<int>(42);
cout << *p1 << " "<<endl;//访问
auto p2 = p1;//p1和p2指向同一片内存,且都是智能指针
cout<<"p1 cnt:"<<p1.use_count()<<"\tp2 cnt:"<<p2.use_count()<<endl;
p1.reset(new int(404));//通过reset指向另一个对象,此时p2引用次数减一
cout<<"p1 cnt:"<<p1.use_count()<<"\tp2 cnt:"<<p2.use_count()<<endl;
- unique_ptr对于所指向的对象,正如其名字所示,是独占的。所以,不可以对unique_ptr进行拷贝、赋值等操作,但是可以通过release函数和reset函数在unique_ptr之间转移控制权。
cout<<"test unique_ptr base usage:"<<endl;
unique_ptr<int> up1(new int(1024));
cout<<"up1: "<<*up1<<endl;
unique_ptr<int> up2(up1.release());
//release()返回的指针通常用来初始化另一个智能指针,或给另一个智能指针赋值
cout<<"up2: "<<*up2<<endl;
//unique_ptr<int> up3(up1); // wrong, unique_ptr 不支持拷贝
//up2 = up1; // wrong, unique_ptr 不支持赋值
unique_ptr<int> up4(new int(1025));
up4.reset(up2.release());
cout<<"up4: "<<*up4<<endl;
- weak_ptr不控制对象的生命周期,是弱引用(想象棉线轻轻拴住堆上的对象),但可以知道对象是否还活着。如果对象还活着,那么它可以提升为有效的shared_ptr,如果对象已经死了,返回一个空的shared_ptr。
- weak_ptr可以指向shared_ptr所指向的对象,但是却不增加对象的引用计数。这样就有可能出现weak_ptr所指向的对象实际上已经被释放了的情况。因此,weak_ptr有一个lock函数,尝试取回一个指向对象的shared_ptr。
2. decltype类型说明符
-
decltype的作用是选择并返回操作数的数据类型。编译器会分析表达式并得到它的类型,但是不会去计算表达式的值
-
注意:如果decltype使用的是一个不加括号的变量。得到的就是改变量的类型,如果给变量加上了一层括号,编译器会把它当作一个表达式,得到的则是引用类型。
int i=10; decltype(i) a;//a的类型是int decltype((i)) b=i;//b的类型是int&。必须要为其初始化,否则编译错误
3. 空指针nullptr
-
nullptr是为了解决原来c++标准中的NULL的二义性问题而引进的一种新的类型,因为NULL实际上代表的是0。但是实际上,用NULL代替0表示空指针在函数重载时会出现问题,程序执行的结果会与我们的想法不同。
#include <iostream> using namespace std; void func(void* t) { cout << "func( void* )" << endl; } void func(int i) { cout << "func( int ) " << endl; } int main() { func(NULL);//func(int) func(nullptr);//func(void*) system("pause"); return 0; }//在这个函数重载中,我们本来用NULL想要调用void*,但实际上确调用了int,所以引入了nullptr来表示空指针 #define NULL((void*)0)//在c语言中把空指针赋给指针的时候,发生了隐式类型转换,把void指针转换成了相应类型的指针 #define NULL 0//c++是强类型语言,void*不能隐式转换为其他类型指针,所以在这里会有歧义
4. 基于范围的for循环
-
基于范围的for循环:简化了常见的循环,对数组或容器类的每个元素执行相同的操作
int age[10]={1,2,3,4,5,6,7,8}; for(int y:age)//第一部分是范围内用于迭代的变量,第二部分表示被迭代的范围 {//迭代器可以指向容器中的某个元素,通过迭代器就可以读写它指向的元素。从这一点上看,迭代器和指针类似 cout<<y<<endl; }
-
for循环迭代的范围必须是确定的
//当数组作为函数参数时,数组名退化为指针,不能使用范围for void func(int arr[],int n) { for(const auto& e:arr) { cout<<e<<" "; } cout<<endl; }
5. 扩展的sizeof
- 在c++98中,只有静态成员,或者对象的实例才能对其成员进行sizeof操作,但是在c++11中可以。在c++11中,对非静态成员变量使用sizeof操作时合法的。
- sizeof是一个操作符,其作用是返回一个对象或类型所占内存字节数。其返回值类型为size_t。(size_t在头文件stddef.h中定义,它依赖于编译系统的值,一般定义为 typedef unsigned int size_t;)
size() sizeof() strlen() str.length()的区别
数组或字符串的长度:sizeof()、strlen()
- sizeof():返回所占总空间的字节数
- strlen():返回字符数组或字符串所占的字节数参数必须是字符型指针(char*)
sizeof(…)是运算符,其值在编译时即计算好了,参数可以是数组、指针、类型、对象、函数等。它的功能是:获得保证能容纳实现所建立的最大对象的字节大小。由于在编译时计算,因此sizeof不能用来返回动态分配的内存空间的大小。
strlen(…)是函数,要在运行时才能计算。参数必须是字符型指针(char*)。当数组名作为参数传入时,实际上数组就退化成指针了。它的功能是:返回字符串的长度。该字符串可能是自己定义的,也可能是内存中随机的,该函数实际完成的功能是从代表该字符串的第一个地址开始遍历,直到遇到结束符’\0’。返回的长度大小不包括’\0’。
c/c++ strlen(str)和str.length()和str.size()都可以求字符串长度。
其中str.length()和str.size()是用于求string类对象的成员函数
strlen(str)是用于求字符数组的长度,其参数是char*。
6. 虚函数的override和final指示符
- override,表示应当重写基类中的虚函数
- final,表似乎派生类不应当重写这个虚函数
7. 限定作用域的枚举
enum class,在枚举类型的作用域外是不可访问的,相反,在不限定作用域的枚举类型中,枚举成员的作用域与枚举类型本身的作用域相同。
枚举作用域(enumeration scope)是指枚举类型的成员的名字的作用域,起自其声明之处,终止枚举定义结束之处
-
枚举元素是常量,不能对他们赋值
例如:enum Weekday{SUN,MON,TUE,SAT}; SUN=0;//错误,不能写赋值表达式:
-
枚举元素有默认值,依次为:0,1,2,…
-
也可以在声明时另行指定枚举元素的值
如:enum Weekday{SUN=7,MON=1,TUE,SAT};
-
枚举值可以进行关系运算
不能直接用一个整数给枚举值赋值,需要进行强制类型转化。
不限定作用域的枚举类型可能导致枚举量泄漏到所在的空间作用域
//在同一个作用域中,定义了不限定作用域的枚举类Color,然后定义了red变量。由于没限定作用域,所以外部也可以使用red。auto red重新定义了red所以报错。
namespace TestSpace {
enum Color {
red = 0,
green,
blue,
};
auto red = true; // 错误
}; // namespace TestSpace
限定作用域的枚举类型
namespace TestSpace {
enum class Color {
red = 0,
green,
blue,
};
auto red = true; // 没问题
}; // namespace TestSpace
8. constexpr变量
- constexpr表达式是指值不会改变,并且在编译过程中就能得到计算结果的表达式。声明为constexpr变量一定是一个const变量,而且必须用常量表达式初始化
constexpr int mf = 20; //20是常量表达式
constexpr int limit = mf + 1; // mf + 1是常量表达式
constexpr int sz = size(); //之后当size是一个constexpr函数时才是一条正确的声明语句
-
在constexpr声明中如果定义了一个指针,限定符constexpr仅对指针有效,与指针所指的对象无关
-
const只能保证在运行时是常量,即具有运行时数据的不可更改性
9. noexcept
- 在c++11中,声明一个函数不可以抛出任何异常使用关键字noexcept
- 编译器不会在编译时检查noexcept的说明,如果一个函数在说明了noexcept的同时又含有throw语句,编译器将顺利编译通过,并不会因为这种违反异常说明的情况而报错。
void fun1(int) noexcept;//不会抛出异常
void fun2(int);//可能会抛出异常
void fun3(int) noexcept(true);//不会抛出异常
void fun4(int) noexcept(false);//可能会抛出异常
10. 匿名函数lambda表达式
lambda表达式具体形式如下:
[capture](parameters)mutable exception->return-type{body};
/*1)capture list:捕获外部变量列表
2)params list:形参列表
3)mutable:标明是否可以修改捕获的变量,默认情况下,Lambda函数总是一个const函数
mutable可以取消其常量性。在使用该修饰符时,参数列表不可省略(参数为空)
4)exception: 异常设定
5)return type:返回类型
6)function body:函数体*/
[](int x,int y){return x+y;}//隐式返回类型
[](int &x){++x}//没有return语句,lambda函数的返回类型是void
[](){++global_x;}//没有参数,仅仅访问某一个全局变量
[]{++global_x;}//与上一个相同,省略了()
[](int x,int y)->int{int z=x+y;return z;}//显示指定返回类型
正常使用中,我们很少会用到return type,因为Lambda表达式可以根据function body中的返回值来判断返回类型。省略params list时,也就相当于一个普通的无参函数。
符号 | 含义 |
---|---|
[] | 不截取任何变量 |
[&] | 截取外部作用域中所有变量,并作为引用在函数体中使用 |
[=] | 截取外部作用域中所有变量,并拷贝一份在函数体中使用 |
[=,&foo] | 截取外部作用域中所有变量,并拷贝一份在函数体中使用,但是对foo变量使用引用 |
[bar] | 截取bar变量并且拷贝一份在函数体重使用,同时不截取其他变量 |
[x,&y] | x按值传递,y按引用传递 |
[this] | 截取当前类中的this指针。如果已经使用了&或者=就默认添加此选项。 |
类型转换static_cast/dynamic_cast/const_cast/reinterpret_cast
参考文章:类型转换
- 在C语言中强制类型转换写成(new_type_name) expression的形式,new_type_name 是要转换的目标类型,expression 是待转换的表达式
- 在C++中强制类型转换通过更明显的关键字来完成,分别是
static_cast、 dynamic_cast, const_cast、 reinterpret_cast
static_cast 是静态转换,在编译期完成完成转换,与C语言中的强制类型转换重合度最高
它不能转换掉表达式的 const、volitale 或者 __unaligned 属性。
int val = 110119;
char c = static_cast<char>(val);
double d = static_cast<double>(val);
dynamic_cast 是动态转换,在运行时转换会进行检查,必须用在有继承关系的多态结构中
只能对指针和引用的进行转换,并且只用于类继承结构中基类和派生类之间指针或引用的转换,可以进行向上、向下,或者横向的转换。
必须有继承关系的类之间才能转换,并且在基类中有虚函数才可以
struct B { virtual void test() {} };
struct D1 : virtual B { };
struct D2 : virtual B { };
struct MD : D1, D2 { };
D1* pd1 = new MD();
std::cout << pd1 << std::endl;
// 向上转型
B* pb = dynamic_cast<B*>(pd1);
std::cout << pb << std::endl;
// 向下转型
MD* pmd = dynamic_cast<MD*>(pd1);
std::cout << pmd << std::endl;
// 横向转型
D2* pd2 = dynamic_cast<D2*>(pd1);
std::cout << pd2 << std::endl;
const_cast 是常量转换,用于取出指针或引用的常量属性,但是尽量通过设计杜绝它的使用场景
但需要特别注意的是 const_cast 不能去除变量的常量性,只能用来去除指向常数对象的指针或引用的常量性,且去除常量性的对象必须为指针或引用。
常量指针被转化成非常量指针,并且仍然指向原来的对象,常量引用被转换成非常量引用,并且仍然指向原来的对象;常量对象可能被转换成非常量对象
reinterpret_cast 是一种内存数据的重新解释,比较原始,开发者使用它的时候应该明确的知道自己在做什么