C++11新特性

1.统一的列表初始化

{}列表初始化

C++11规定,所有的内置类型和自定义类型,可以使用列表初始化,例如:

struct point
{
	int x;
	int y;
};
int main()
{
	point a1 = {1,2 };
	int a2 = { 1 };
	int a3[] = { 1,2,3,4,5 };
	string s = { "lijinpeng" };
	return 0;

}

不过看起来没什么用,确实没什么大用

还可以把= 去掉,这样效果和上面是一样的

struct point
{
	int x;
	int y;
};
int main()
{
	point a1{ 1,2 };
	int a2 { 1 };
	int a3[] { 1,2,3,4,5 };
	string s{ "lijinpeng" };
	return 0;

}

std::initializer_list 初始化

initializer_list是一个类,一般是用来作为构造函数的参数,C++11对STL中的不少容器就增加了initializer_list作为参数的构造类型,这样初始化容器就方便很多了

int main()
{
	vector<int> v = { 1,2,3,4 };
	list<int> lt = { 1,2 };
	// 这里{"sort", "排序"}会先初始化构造一个pair对象,用pair对象作为一个元素,进行dict的构造
	map<string, string> dict = { {"sort", "排序"}, {"insert", "插入"} };
	// 使用大括号对容器赋值
	v = { 10, 20, 30 };

	return 0;
}

2.C++11新增关键字

1.auto

auto可以自动推断出数据的类型,非常方便

it1和it2的类型是一样的,这样auto用起来就很方便,但是如果不清楚数据的类型,还是尽量不这样写,容易看不懂自己写的代码;

2.decltype

decltype可以拿到数据的类型,还可以用来声明该数据,比如:

decltype拿到a.begin()的类型,并用来声明it3,

3.右值引用

1.左值引用和右值引用

什么叫左值?什么是左值引用?

左值是一个表示数据的表达式,通俗来说,可以被取地址的叫做左值,左值引用就是给左值取别名,

int main()
{
// 以下的p、b、c、*p都是左值
int* p = new int(0);
int b = 1;
const int c = 2;
// 以下几个是对上面左值的左值引用
int*& rp = p;
int& rb = b;
const int& rc = c;
int& pvalue = *p;
return 0;
}

什么叫右值?什么是右值引用?

右值也是一个表示数据的表达式,右值不能取地址,右值引用就是给右值取别名

int main()
{
double x = 1.1, y = 2.2;
// 以下几个都是常见的右值
10;
x + y;
fmin(x, y);
// 以下几个都是对右值的右值引用
int&& rr1 = 10;
double&& rr2 = x + y;
double&& rr3 = fmin(x, y);
// 这里编译会报错:error C2106: “=”: 左操作数必须为左值
10 = 1;
x + y = 1;
fmin(x, y) = 1;
return 0;
}

需要注意的是右值是不能取地址的,但是给右值取别名后,会导致右值被储存到特定位置,且可以取到该位置的地址,并修改该右值,

2.左值引用和右值引用的区别

左值可以引用左值,但是不能引用左值,const 左值既可以引用左值,也可以引用右值,

int main()
{
    // 左值引用只能引用左值,不能引用右值。
    int a = 10;
    int& ra1 = a;   // ra为a的别名
    //int& ra2 = 10;   // 编译失败,因为10是右值
    // const左值引用既可引用左值,也可引用右值。
    const int& ra3 = 10;
    const int& ra4 = a;
    return 0;
}

右值可以引用右值,但是不能引用左值,右值可以引用move后的左值

int main()
{
 // 右值引用只能右值,不能引用左值。
 int&& r1 = 10;
 
 // error C2440: “初始化”: 无法从“int”转换为“int &&”
 // message : 无法将左值绑定到右值引用
 int a = 10;
 int&& r2 = a;
 // 右值引用可以引用move以后的左值
 int&& r3 = std::move(a);
 return 0;
}

3.右值引用的场景和意义

引用的意义是减少拷贝,提高效率,但是左值引用返回值的问题没有彻底解决,如果返回函数中局部对象,不能引用返回,还是要拷贝一下,

这时候就要用右值引用返回了;

namespace ljp
{
    class string
    {
    public:
        typedef char* iterator;
        iterator begin()
        {
            return _str;
        }
        iterator end()
        {
            return _str + _size;
        }
        string(const char* str = "")
            :_size(strlen(str))
            , _capacity(_size)
        {
            //cout << "string(char* str)" << endl;
            _str = new char[_capacity + 1];
            strcpy(_str, str);
        }
        // s1.swap(s2)
        void swap(string& s)
        {
            ::swap(_str, s._str);
            ::swap(_size, s._size);
            ::swap(_capacity, s._capacity);
        }
        // 拷贝构造
        string(const string& s)
            :_str(nullptr)
        {
            cout << "string(const string& s) -- 深拷贝" << endl;
            string tmp(s._str);
            swap(tmp);
        }
        // 赋值重载
        string& operator=(const string& s)
        {
            cout << "string& operator=(string s) -- 深拷贝" << endl;
            string tmp(s);
            swap(tmp);
            return *this;
        }
        // 移动构造
        string(string&& s)
            :_str(nullptr)
            , _size(0)
            , _capacity(0)
        {
            cout << "string(string&& s) -- 移动构造" << endl;
            swap(s);
        }
        // 移动赋值
        string& operator=(string&& s)
        {
            cout << "string& operator=(string&& s) -- 移动赋值" << endl;
            swap(s);
            return *this;
        }
        ~string()
        {
            delete[] _str;
            _str = nullptr;
        }
        char& operator[](size_t pos)
        {
            assert(pos < _size);
            return _str[pos];
        }
        void reserve(size_t n)
        {
            if (n > _capacity)
            {
                char* tmp = new char[n + 1];
                strcpy(tmp, _str);
                delete[] _str;
                _str = tmp;
                _capacity = n;
            }
        }
        void push_back(char ch)
        {
            if (_size >= _capacity)
            {
                size_t newcapacity = _capacity == 0 ? 4 : _capacity * 2;
                reserve(newcapacity);
            }
            _str[_size] = ch;
            ++_size;
            _str[_size] = '\0';
        }
        //string operator+=(char ch)
        string& operator+=(char ch)
        {
            push_back(ch);
            return *this;
        }
        const char* c_str() const
        {
            return _str;
        }
    private:
        char* _str;
        size_t _size;
        size_t _capacity; // 不包含最后做标识的\0
    };
}

当函数返回对象是一个局部变量,出了函数作用域就不存在了,就不能使用左值引用返回,

只能传值返回。例如:bit::string to_string(int value)函数中可以看到,这里只能使用传值返回,

传值返回会导致至少1次拷贝构造(如果是一些旧一点的编译器可能是两次拷贝构造)。

对于这个问题,我们使用右值引用,进行移动构造,因为"-1234"传参可以识别成右值,然后走移动构造,这样就可以只走一次构造了减少了构造

// 移动赋值
string& operator=(string&& s)
{
cout << "string& operator=(string&& s) -- 移动语义" << endl;
swap(s);
return *this;
}
int main()
{
 ljp::string ret1;
 ret1 = ljp::to_string(1234);
 return 0;
}

在string类中增加移动赋值函数,再去调用to_string(1234) 走的就是移动赋值,

右值引用本身是左值

4.完美转发

模板中&& 表示万能引用

void Fun(int& x) { cout << "左值引用" << endl; }
void Fun(const int& x) { cout << "const 左值引用" << endl; }
void Fun(int&& x) { cout << "右值引用" << endl; }
void Fun(const int&& x) { cout << "const 右值引用" << endl; }

//函数模板里,这里可以叫做万能引用,其既能接受左值,又能接受右值
template<class T>
void PerfectForward(T&& t)
{
	//当t是左值时,就调用左值引用
	//但是t是右值时,由于右值引用本身是左值,所以Fun(t)中的t是左值,这样模板只能实例化出左值的引用
	Fun(t);
}

解决办法就是用完美转发

template<class T>
void PerfectForward(T&& t)
{
 //Fun((T&&)t);
 Fun(std::forward<T>(t));//std::forward<T>(t)在传参过程中保持了t的原生类型,传的是左值就是左值
                         //传的是右值就是右值
}

还有一种办法就是强制转换成T&&类型,效果和forward<T>(t)一样的

5.新增类功能

原来C++类中有六个默认成员函数

1.构造函数

2.析构函数

3.拷贝构造函数

4.赋值重载

5.取地址重载

6.const 取地址重载

C++新增了两个 默认成员函数:

移动构造函数和移动赋值函数

主要是针对右值传参

如果你没有自己实现移动构造函数,且没有实现拷贝构造 赋值重载 和析构函数,那么编译器会自己实现一个默认移动构造函数,默认的移动构造函数,对内置类型,会按字节拷贝,对自定义类型成员,要看这个成员是否实现移动构造,如果实现了移动构造就调用移动构造,如果没有实现就调用拷贝构造;

如果你没有实现移动赋值重载函数,且没有实现拷贝构造,赋值重载和析构函数,那么编译器会默认生成移动赋值,默认生成的移动赋值函数,对对内置类型,会按字节拷贝,对自定义类型成员,要看这个成员是否实现移动赋值,如果实现了移动赋值就调用移动赋值,如果没有实现就调用拷贝赋值

default:强制生成默认函数的关键字

C++11可以让你更好的控制要使用的默认函数,假如你要使用某个默认的函数,但是因为一些原因这个函数没有生成,就可以使用default关键字强制生成默认函数;

delete:禁止生成默认函数的关键字

在C++11中,只需要在该函数声明上加上=delete,那么编译器就不默认生成该函数,称=delete函数为删除函数

6.lambda表达式

lambda表达式实际上是一个匿名函数,

书写格式:

[capture-list] (parameters) mutable -> return-type { statement }

[capture-list]是捕捉列表,该列表出现在lambda函数的开始位置,捕捉列表能够捕捉上下文的变量供lambma函数使用

parameters 参数列表,和普通参数传参一样,如果不需要传参可以省略

mutable:默认情况下,lambma函数总是一个const函数,mutable可以取消其常量性 使用该修饰符时,参数列表不可省略(即使参数为空)

->returntype:返回值类型,函数可以自动推导,如果没有返回值,可以省略,如果有返回值,返回值明确的情况下,也可以省略,所以我们常见的写法都是省略了;

statement :函数体,在该函数体内,除了可以使用其参数外,还可以使用所有捕获到的变量

lambda表达式可以理解为无名函数该函数无法直接调用,如果想要调用,可以借助auto将他赋值给一个变量

注意:每个lambda的返回类型都是不一样的,所以我们借助auto直接推导lambda的类型,方便我们调用,

lambda的底层就是仿函数,类似于范围for的底层是迭代器一样,

传参时用引用传参,可以修改x和y的值

捕捉列表说明:

[var]: 表示捕捉变量var

[=]: 表示值传递方式捕获所有父作用域中的变量(包括this)

[&var]:表示引用捕捉变量var

[&]:表示引用传递捕捉所有父作用域的变量(包括this)

[this]: 表示值传递方式捕捉当前this指针

[=]捕捉所有的变量,但是默认是const属性的,mutable修饰后,可以取消其常量性,

还可以混合捕捉:

7.可变参数模板

//T是一个模板参数包,args是一个函数形参参数包
//声明一个参数包T...args,这个参数包中可以包含0到任意个模板参数	
template<class ...T>
void ShowList(T...args)
{

}

上面的参数args前面有省略号,表示他是一个可变模板参数,也叫做参数包,他里面包含了从0到N个模板参数,我们无法直接获取参数包中的参数,只能通过展开参数包的方式获取参数,比如

用递归方式展开参数包

void showlist()
{
	cout <<"\n";
}
template<class T,class  ...Args>
void showlist(T value, Args...args)
{
	cout << value << " ";
	showlist(args...);	
}

template<class ...Args>
void _showlist(Args...args)
{
	showlist(args...);
}
int main()
{
	showlist(1);
	showlist(1, 'A');
	showlist(1,'A','B',"ljp");
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值