c++11的一些特性和语法

        1、final和override:

        final修饰类,类就变成了最终类,不能被继承,final修饰虚函数,这个虚函数就不能被重写,override是子类重写虚函数,检查是否完成重写,如果没有完成正确的重写,就报错。

        2、新容器

        (1)array(定长数组),实际使用的很少,缺点:定长 + 存储数据的空间再栈上,栈的空间本来就不大。(2)forward_list(单链表),实际使用的很少,缺点:不支持尾插尾删 + insert数据是在当前位置后面。(3)unordered_map/unordered_set,推荐使用,它们的效率高于map/set,因为map/set的底层是红黑树,unordered_map/unordered_set底层是哈希表。

        3、类的默认成员函数控制

        在类中如果用户定义了拷贝构造函数,而没有定义默认构造函数,这时候编译器不会自动生成类的默认构造函数了,这时可以使用=default来显式地区生成默认构造函数。

class A
{
public:
	A() = default;
	A(const int& a)
		:_a(a)
	{}
private:
	int _a = 10;
};

        如果此时要求A的对象不能拷贝和赋值(防拷贝),在c++98的做法是,将拷贝构造函数和赋值运算符重载函数在类中只声明不实现,这样在调用拷贝构造和赋值时会产生链接不上,就不能拷贝了,但是缺陷是,一旦构造函数和operator=函数在类的外部实现了,就有能拷贝了,这里可以将类的构造函数和operator=函数的访问限定符设置为private,私有成员函数在外部不能调用,就实现了防拷贝。在c++11中就可以使用delete关键字来删除类的拷贝构造和赋值函数,更加方便。

class A
{
public:
	A() = default;
	A(const int& a) = delete;
	A& operator=(const A& aa) = delete;
private:
	int _a = 10;
};

        4、右值引用

        结合左值引用来一起学习,左值引用主要用于给左值取别名,右值引用主要用于给右值取别名。这里牵扯两个概念,什么是左值和右值,举一个例子int x1 = 10;int x2 = x1;这里x1是左值,10是右值,x2是左值,一般认为可以修改的就是左值,左值通常是变量,右值通常是常量、表达式或者函数返回值(临时对象)。

int main()
{
	int x = 1, y = 2;
	// 左值引用的定义
	int a = 0;
	int& b = a;

	//左值引用不能引用右值,const左值引用可以(这两个写法是不对的)
	//int& e = 10;
	//int& f = x + y;
	const int& e = 10;
	const int& f = x + y;

	//右值引用的定义
	int&& c = 10;
	int&& d = x + y;

	//右值引用不能引用左值,但是可以引用move后的左值
	//int&& m = a;
	int&& m = move(a);
	return 0;
}

         c++11又将右值区分为纯右值将亡值。纯右值是基本类型的常量或者临时对象,将亡值是自定义类型的临时对象。内置类型在拷贝时一般不涉及深浅拷贝问题,所以内置类型的右值引用意义不大,自定义类型的深拷贝,是右值引用的主要用途。看下面这段代码,一般的拷贝构造,是先开辟空间再将参数上的值拷贝到空间内,如果传入的参数是一个将亡值(比如一个函数的返回值),那么在调用完拷贝构造函数后,将亡值的空间也就被释放掉了,这多进行了一次空间开辟的工作,如果采用传右值引用的方式,我们可以将将亡值的空间直接用作构造对象的空间,避免开辟空间及拷贝。就像下面这段代码,String有两个拷贝构造函数,如果传入的参数是左值(调用完还要接着用),就调用上面的构造函数,如果传入的是右值(调用完直接析构)就可以先将构造对象的地址赋值为空,在与将亡值交换,直接交换空间,效率更高。右值引用的应用场景是,对将亡值进行深拷贝的时候,本质是利用了移动赋值。可以说,左值引用和右值引用的目的都是减少拷贝。

class String
{
	String(const String& s)
	{
		_str = new char[strlen(s._str) + 1];
		strcpy(_str, s._str);
	}
	//右值(将亡值)
	String(String&& s)
		:_str(nullptr)
	{
		swap(_str, s._str);
	}
private:
	char* _str;
};

        operator=函数也可以多实现一个右值引用传参的版本,右值引用在所有深拷贝传参的场景都是能大大提高效率的。右值引用的逻辑就是,传入将亡值,反正这个参数也要马上被析构了,不如直接用这个参数的空间进行构造,省去了在函数内开辟空间的工作。这里注意,调用右值引用传参的函数时,如果传入move()后的变量,那么变量的值会被转移走。

        5、完美转发

        右值引用的变量在下一层次的传参中会发生右值属性的丢失。看一段代码和运行结果体会一下。在PerfectForward()函数中,参数是一个右值,由于是函数模板,因此传参也可以传左值,在函数体内部调用Fun()函数,根据参数t的类型不同,调用重载的不同的Fun,可以看到不论传入的t是左值还是右值,调用的Fun都是左值的版本,这证明了t在下一层次的传参中发生了右值属性的丢失。

void Fun(int& x) { cout << "lvalue ret" << endl; }
void Fun(int&& x) { cout << "rvalue ret" << endl; }
void Fun(const int& x) { cout << "const lvalue ret" << endl; }
void Fun(const int&& x) { cout << "const rvalue ret" << endl; }

template<typename T>
void PerfectForward(T&& t) { Fun(t); }

int main()
{
	PerfectForward(10);//rvalue ref
	int a;
	PerfectForward(a);//lvalue ref
	PerfectForward(std::move(a));//rvalue ref
	const int b = 8;
	PerfectForward(b);//const lvalue ref
	PerfectForward(std::move(b));//const rvalue ref
	return 0;
}

        使用forward解决上述问题,实现完美转发。

void Fun(int& x) { cout << "lvalue ret" << endl; }
void Fun(int&& x) { cout << "rvalue ret" << endl; }
void Fun(const int& x) { cout << "const lvalue ret" << endl; }
void Fun(const int&& x) { cout << "const rvalue ret" << endl; }

template<typename T>
void PerfectForward(T&& t) { Fun(std::forward<T>(t)); }

int main()
{
	PerfectForward(10);//rvalue ref
	int a;
	PerfectForward(a);//lvalue ref
	PerfectForward(std::move(a));//rvalue ref
	const int b = 8;
	PerfectForward(b);//const lvalue ref
	PerfectForward(std::move(b));//const rvalue ref
	return 0;
}

        6、lambda表达式

        lambda表达式的作用是类似于定义一个函数,从使用场景来看,与仿函数和函数指针相似。它的格式是[捕捉列表](参数)->返回值类型{函数体}, 其中“->返回值类型”这部分不写的话它也可以自己推返回值。写一段代码感受一下。下面是实现一个加法功能的lambda表达式。

int main()
{
	int a = 0, b = 1;
	auto add1 = [](int x1, int x2)->int {return x1 + x2; };
	cout << add1(a, b) << endl;
}

        捕捉列表的作用就是捕捉跟lambda表达式同一作用域的对象,[=]可以将作用域下的所有对象都捕捉,可以选择传值捕捉也可以选择传引用捕捉,如果希望对同一作用域的对象的值做修改的话往往选择传引用捕捉。传值捕捉的对象具有const属性,如果试图在lambda表达式的函数体内对捕捉对象进行修改,编译器会报错。如果非要在传值捕捉的条件下对对象进行修改,那么需要加上mutable关键字,但是此时a和b是拷贝了一份的对象,不是外部的对象了,此时在外部也无法实现正常的交换作用。

        lambda表达式不是仿函数,但是它的底层实现是仿函数,也就是说用户定义了一个lambda表达式,实际上编译器会全局域生成一个叫lambda_uuid类,仿函数的operator()的参数和实现就是我们写的lambda表达式的参数和实现,uuid是通过算法生成的不会轻易重复的号码。所以上面的代码中“auto add = ...”实际上就是实例化出一个仿函数的对象,可以用于一些函数传参。所以调用lambda表达式,需要先实例化对象,是依赖于对象来调用的。

        7、线程库

        我们知道windows下和Linux下实现多线程的方式和规则是不一样的,windows自己的一套API,如CreateThread,Linux中使用posix的pthread,如pthread_create。在c++98的版本里,如果用户想要写一个既可以在Windows下跑,也可以在Linux下跑的多线程程序,是要用到条件编译的。像下面这样。

        在c++11的版本下,有了线程库,特点是跨平台、面向对象(每个线程是一个类对象),在任何系统下都可以直接使用,更加方便,其实它的底层还是封装库时使用了条件编译,调用了不同平台的线程API。看一段代码和运行结果感受一下。

int x = 0;
//两个线程一起对x加n次

void Add(int n)
{
	for (int i = 0; i < n; ++i)
	{
		++x;
	}
}

int main()
{
	thread t1(Add, 100);
	thread t2(Add, 100);
	t1.join();
	t2.join();
	cout << x << endl;

}

  • 20
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值