C++11中的一些新特性以及代码详解

C++11新特性

  • auto
  • decltype
  • 追踪返回类型
  • 类内成员初始化
  • 列表初始化
  • 基于范围的for循环
  • 静态断言
  • noexcept修饰符
  • 强类型枚举
  • 常量表达式
  • 原生字符串字面值
  • 继承控制 final 和override
  • 类默认函数控制:default和delete函数
    • default函数
    • delete函数
  • 可变参数模板
    • 可变参数模板函数
    • 可变参数模板类
  • 闭包
    • 仿函数:重载operator()
    • std::bind绑定器实现
    • lamdba表达式实现

auto

  • 在c中,修饰局部变量,局部变量也叫auto变量,自动变量
auto int a = 3;
  • 在C++中,auto可以在声明变量的时候根据变量初始值的类型自动为此变量选择匹配的类型,类似的关键字还有decltype;
auto b = 1;

auto和其他变量类型有明显的区别:

  1. auto声明的变量必须要初始化,否则编译器不能判断变量的类型
  2. auto不能被声明为返回值,auto不能用于声明数组,auto不能作为形参,auto不能被修饰为模板参数

decltype

有点像auto的反函数,decltype可以从一个变量或者表达式中得到其类型

int i;
decltype(i) j = 0;

vector<int> tmp;
decltype(tmp.begin()) k;
for(k = tmp.begin;k!=tmp.end();++k){}

enum{OK, ERROR} flag;  //匿名类型枚举变量
decltype(flag) flag2;

追踪返回类型

返回类型后置:在函数名和参数列表后面指定返回类型

int func(int,double);		
auto func2(int,double)->int;   //结果会发生折断
auto func3(int a,double b)->decltype(a+b); 

template<typename T1, typename T2>//根据需要获得返回值类型,更加智能
auto mul(const T1 &t1, const T2 &t2) -> decltype(t1*t2) 
{
    return t1*t2;
}

类内成员初始化

class B{
public:
	int data{1};
    int data2 = 1;
    string name{"mike"};
};

列表初始化

struct Test{
    int a;
    int b;
    char name[50];
};

struct Test tmp = {1,2,"mike"}; //列表初始化
int b = {1};
//int arr[] = {1,2,3};   曾经
int arr[]{1,2,3};  

//防止类型收窄
int a = 1024;
char b = {a};  //会报错,int 转换到char会类型收窄

基于范围的for循环

vector<int> arr;
for(auto &tmp : arr);
for(auto tmp : arr);

静态断言

  • 之前的断言在运行时候检查条件,如果条件为真,往下执行。如果条件为假,中断,提示错误。
assert(flag == true)
  • 在编译阶段对断言进行检查测试
    • 更早的报告错误,知道构建是早于运行的,更早的错误报告意味着开发成本降低。
    • 减少运行时开销,静态断言是编译期间检测的,减少运行时开销。
//static_assert只能是常量表达式,不能是变量
//static_assert(条件,“提示字符串”);
//(如检测程序平台64位不允许执行)
static_assert(sizeof(void *)==8,"64位系统不支持");

noexcept修饰符

用于不抛出异常

void func(){throw 1;} //抛出异常
void func1() throw() {} //函数体内不允许抛出异常

void func2() noexcept {} //函数体内不允许抛出异常

强类型枚举

以前枚举的局限性

  • 不同枚举中相同枚举单元会被认为是重定义
  • 本质当成是一个int类型

强枚举类型

  • enum后面加上class或struct修饰
  • 可以指定成员变量的类型
int main() {
	enum stat1 {OK,ERROR};  
	enum stat2 {OK,ERROR};   // 错误	C2365“main::ERROR”: 重定义;
}

//强枚举类型
int main() {
	enum class stat1 { OK, ERROR };
	enum class stat2 { OK, ERROR };
	enum class stat3: char{ OK,ERROR };  // 指定成员变量的类型
	stat1 flag = stat1::OK;  			//支持,需要指定作用域
	cout << (flag == stat1::OK)<<" "<< sizeof(stat3::OK);  //1 1
}

常量表达式

常量表达式允许一些计算发生在编译时,即发生在代码编译而不是运行时。

这次优化可以避免每次程序允许时候都被计算,而只需编译时期计算一次即可。

限制

  • 函数中只能有一个return语句(极少特例)
  • 函数必须有返回值(不能是void函数)
  • 在使用前必须已有定义
  • return返回语句表达式中不能使用非常量表达式的函数、全局数据,且必须是一个常量表达式。
int getNum1() {return 3;}

constexpr int getNum2() {return 3;}

int main() {
	//enum MyEnum{a1 = getNum1(), a2};//编译失败,枚举类型初始化必须是整型常量
	enum MyEnum { a1 = getNum2(), a2 }; //ok,发生在编译期间

	constexpr int tmp = 3;
	enum MyEnum2 { a3 = tmp, a4 }; //ok,发生在编译期间
}

原生字符串字面值

输出原始字符串

cout<<R"(hello,\n      world)"<<endl;

继承控制 final 和override

C++11以前,一直没有继承控制的关键字,禁用一个类的进一步衍生比较麻烦。

C++11添加了两个继承控制关键字:final和override

  • final阻止类的进一步派生和虚函数的进一步重写

  • override确保派生类中声明的函数跟基类的虚函数有相同的签名

    //final阻止类的进一步派生,虚函数的进一步重写
    class A1 final{  //加上final,指定A1不能派生
    	int a;
    };
    
    class A2 {
    public:
    	virtual void func() final{} // 派生类无法对该虚函数进行重写
    };
    
    class A3 {
    public:
    	virtual void func(int a) {} //第一个虚函数不能使用override修饰
    };
    class B3:public A3{
    public:
    	void func(int a) override {}  
        //重写虚函数的地方,加上override,要求重写的虚函数和基类一模一样的,否则编译错误
    };
    
    

类默认函数控制:default和delete函数

default函数

C++的类有四种特殊成员函数:默认构造函数、析构函数、拷贝构造函数、拷贝复制运算符。这些类的特殊成员函数负责创建、初始化、销毁或者拷贝类的对象。

如果一个程序员没有显式的为一个类定义某个特殊成员,而又需要用到该特殊成员函数的时,则编译器会隐式的为这个类生成一个默认的特殊成员函数。

但是,如果程序员为类显式的定义了非默认构造函数,编译器将不会再为他隐式的生成默认无参构造函数。

注意

  • default只能修饰类中默认提供的四个成员函数;
class X {
public:
	X(int i) {i = i;}
	X() = default; //让编译器提供一个默认构造函数,效率比用户写的高	

	int i = 1;
};
int main() {
	X x;
	cout << x.i;
}

delete函数

为了能够让程序员显式的禁用某个函数,C++11标准引入了一个新特性:“=delete”函数。程序员只需要在声明后“=delete”,就可以将该函数禁用。

class X {
public:
	X() = default;
	X(const X &b) = delete; //拷贝构造 
	X& operator==(const X&) = delete; //赋值重载
	X(int i) = delete;
	int i = 1;
};
int main() {
	//X x(10);   //無法調用
	//X x1 = x; //拷贝构造  发生error
	//x1 = x; //赋值重载   禁用
}

可变参数模板

可变参数模板函数

  • 语法

    template<class ... T>  //T 模板参数包
    void func(T... args){
        cout<<sizeof...(args)<<endl; 
    } //args 函数参数包
    
    func<int>(10);
    func<int,char>(10,'a');
    func<int, const char*>(10,"123"); 
    
  • 参数包展开方式

    • 递归方式展开

      用递归函数展开参数包,需要提供一个参数包的展开函数和一个递归终止函数

      //void debug() {			//递归终止函数
      //	cout << "endl" << endl;
      //
      //}
      
      template<class T>     //递归终止函数   根据需要处理最后几个参数
      void debug(T t) {
      	cout <<t<< " endl" << endl;
      }
      
      //可变参数的模板类型
      template<class T1, class ... T2>  
      void debug(T1 first, T2... args) {
      		cout << first << " ";
      		debug(args...); //需要一个结束条件
      } 
      
      int main() {
      	debug<int>(10);
      	debug<int, char>(10, 'a');
      	debug<int, const char*>(10, "123");
      	debug(1,2,3,4,'a','b','c');
      }
      

      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-S0acuPxa-1659580060067)(C:\Users\11606\AppData\Roaming\Typora\typora-user-images\image-20220803182005288.png)]

    • 非递归方式展开

      template<class T>     //递归终止函数   根据需要处理最后几个参数
      void printf(T t) {
      	cout << t << " ";
      }
      
      //可变参数的模板类型
      template< class ... T>  
      void expand(T... args) {
          //借助逗号运算符
          //通过初始化列表方式实现,最终a数组全为0
      	int a[] = { (printf(args),0)... };
      	cout << endl;
      } 
      
      int main() {
      	expand<int>(10);
      	expand<int, char>(10, 'a');
      	expand<int, const char*>(10, "123");
      	expand(1,2,3,4);
      }
      

      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LqiJJEAA-1659580060069)(C:\Users\11606\AppData\Roaming\Typora\typora-user-images\image-20220803182624212.png)]

可变参数模板类

  • 继承方式展开参数包

    可变参数模板类的展开一般需要定义2-3个类,包含类声明和特化的模板类

    1、可变参数模板声明
    2、递归继承模板类
    3、边界条件

    //1、可变参数模板声明
    template<class ...Tail>class car{};
    
    //2、递归继承模板类
    template<class Head, class ...Tail>
    class car<Head,Tail...>:public car<Tail...> {//递归继承本身
    public:
    	car() {	cout << "type = " << typeid(Head).name() << endl;}
    };
    
    //3、边界条件
    template<>
    class car<>{};
    
    int main() {
    	car<int, char, char> c;
    }
    

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wLtw64JJ-1659580060070)(C:\Users\11606\AppData\Roaming\Typora\typora-user-images\image-20220803210329708.png)]

  • 模板递归和特例化方式展开参数包

    1、变长参数模板声明
    2、变长模板类定义
    3、边界条件

    //1、变长模板声明
    template<int ...Tail>class test{};
    
    //2、变长模板类定义
    template<int Head, int ...Tail>
    class test<Head,Tail...>{
    public:
    	static const int val = Head * test<Tail...>::val;
    };
    
    //3、边界条件
    template<>
    class test<>{
    public:
    	static const int val = 1;
    };
    
    int main() {
    	cout<< test<1, 2, 3>::val;
    }
    

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-M7yaGRqf-1659580060070)(C:\Users\11606\AppData\Roaming\Typora\typora-user-images\image-20220803211815155.png)]

闭包

概念:

一般来说,闭包就是带有上下文的函数。说白了就是有状态的函数。更直接点就是一个类。

一个函数带了状态,就是闭包了。带了状态就是说有属于自己的变量,这些变量的值是创建闭包的时候设置的,并在调用闭包的时候,可以访问这些变量。

函数是代码,状态是一组变量,将代码和一组变量捆绑(bind),就形成了闭包。

闭包的状态捆绑,必须发生在运行时

仿函数:重载operator()

//仿函数
class Test {
public:
	Test(int i ):r(i) {}

	//仿函数,重载()
	int operator()(int temp){
		return temp + r;
	}

private:
	int r;
};

int main() {
	Test t(10);
	cout << t(1);
}

std::bind绑定器实现

std::bind可以预先把可调用实体的某些参数绑定到已有变量,产生一个新的可调用实体,这种机制在回调函数中使用广泛。

#include 
#include 
using namespace std;

//普通函数
void test1() {
	cout << __func__ <<"(" << ")" << endl;
}

//类中静态函数
class test2 {
public:
	static int test_func(int a) {
		cout << __func__ << "(" << a << ")" << endl;
		return a;
	}
};

//类中仿函数
class test3 {
public:
	int operator()(int a) {
		cout << __func__ << "(" << a << ")" << endl;
		return a;
	}
};

int main() {
	//1.绑定普通函数
	function<void(void)> f1 = test1; 
	f1();  //等价于test1()
	bind(test1)();
	bind(f1)();
	cout << endl;

	//2.绑定类中静态函数
	function<int(int)> f2 = test2::test_func;
	f2(2); //等价于调用test2::test_func(2)
	bind(test2::test_func, 2)();
	bind(f2, 2)();
	cout << endl;
	//3.绑定类中的仿函数

	test3 obj;
	function<int(int)> f3 = obj;
	f3(3); //等价于调用obj(3)
	bind(obj, 3)();
	bind(f3, 3)();
	bind(f3, placeholders::_1)(3);  //_1占位符,运行时替换指定参数,且有顺序
	using namespace placeholders;
	bind(f3, _1)(3);
}

C++11中的一些新特性以及代码详解_第1张图片

lamdba表达式实现

基本构成:

C++11中的一些新特性以及代码详解_第2张图片

[ ] 表示一个lamdba开始,必须存在。参数只能使用那些lamdba可见范围内的局部变量,包括lamdba所在类的this,存在以下形式:

  • 空:不捕获任何变量
  • =:函数体内可以使用lamdba所在作用范围内所有可见局部变量,且是值传递方式(相当于编译器自动为给我们按值传递了所有局部变量)
  • &:可见局部变量的引用传递
  • this:函数体内可以使用lamdba所在类中的成员变量、全局变量(this可以使用的变量),不可以捕获局部变量;
  • a:将a按值传递,且默认情况时const,要修改需要指定mutable;
  • &a:按引用传递a
  • a,&b:a按值传递,b按引用传递
  • =,&a, &b :除了a,b引用传递,其余按值传递
  • &,a, b:除了a,b按值传递,其余按引用传递。
int a=0, b=0;
int c, d;
auto f = [&,a, b](int x, int y) { //a,b不可修改,可以使用mutable修改
    c = 1; d = 1;
    cout << c << " " << d << endl;
    cout << a << " " << b << endl;
    cout << x << " " << y << endl; };
f(3, 3);

在这里插入图片描述

与仿函数的区别

仿函数使用前需要实例化类,要调用构造函数,耗费时间。

lamdba不需要定义类,直接利用函数输出结果。

事实上,仿函数是编译器实现lamdba的一种方式,通常**编译器都是把lambda表达式转化为一个仿函数对象。**因此,在C++11中,lamdba可以视为仿函数的一种等价形式。

lambda返回类型

lamdba表达式类型在C++11中被称为闭包类型,每一个lambda表达式则会产生一个临时对象(右值),因此,严格的说,lambda函数并非函数指针。

不过C++标准却允许lambda表达式向函数指针转换,前提是lambda函数没有捕获任何变量([ ]),且函数指针所致的函数原型,必须和lambda函数有这相同的调用方式。

你可能感兴趣的:(C++学习,c++,算法)