记录篇-c++11特性学习-关键字1

前言

本文是记录个人对c++11新特性的学习,文中所用可能会采用其他大神的图、思路、例子等,请大神们见谅。本博客文章是在学习过程的一些总结,整理出来,分享给大家,希望对各位读者有帮助,文章中的总结可能存在很多不完整或有错误的地方,也希望读者指出。

1、auto关键字

本章节为auto关键字的c++11的新语法特点;在C++98中auto关键字用于声明变量为自动变量,这是多余的,因为就算不使用auto声明,变量依旧拥有自动的生命期。

1.1、auto关键字概述

c++11中auto关键字作用:
1)声明变量时根据初始化表达式自动推断该变量的类型
2)声明函数时函数返回值的占位符,需要与关键字 decltype 一起使用

类型推到规则:
1)auto变量(不是auto&),忽视掉初始化表达式的顶层const
2)auto&变量,保持初始化表达式的顶层const或volatile 属性
3)若希望auto推导的是顶层const,加上const,即const auto
举例1:

int i = 0, &ri = i;
auto a = i; //a为int型变量
auto a1 = ri; //a1为int型变量
auto p = &i;// &i 是一个普通int指针,p是一个整型指针int *
auto p1 = &ri; //同上

举例2:

const int ci = 2, &rci = ci , ci2 = 9;
auto b = ci;//b为int型变量,因为规则1,b`并不是一个const int型的常量`
auto b1 = rci;//同上

b = 4;b1 = 5;//b和b1的值可以改变
auto cp = &ci;//cp是一个指向常量的指针const int* ,因为&ci对常量对象取地址是底层const,无顶层const属性
cp = &ci2;//cp的指向可以改变

举例3:

int z = 9,z1 = 10;
int* const pz= &z;
auto apz = pz; //因为规则1,忽视pz的顶层const属性,所以apz为int*
apz = &z1;

pz为常量指针,声明apz,用pz初始化,pz可以改变指向;但是如果pz的声明是const int* const,这apz为const int* 类型
注意:对常量对象取地址总是看作为一种底层的const,即不是对指针(也就是指向)的常量,而是对指针指向内存中的值是常量const,如举例2中的&ci是const int* 类型

举例4:const auto

int i = 0, &ri = i;
const int ci = 2, &rci = ci ;

const auto cb = i; //cb为const int型。因为规则3,cb被提升为const
const auto cb1 = ci; //同上

const auto ca1 = &i;//cal为常量指针。&i本是int*,因为规则2,强行将cal提升为常量指针int *const
const auto ccp = &ci;//本来&ci为const int *,因为规则2,加了const后,提示为const int * const

举例5:auto &

int i = 0, &ri = i;
const int ci = 2, &rci = ci ;
	
auto & j = i; //j为int &
auto & k = ci; // k为const int &
auto & h = 42; //错误,不能将非常量引用绑定字面值,这是引用&规则决定的

const auto &j2 = i; //j2为const int &,因为规则3,j2被提升为顶层const
const auto &k2 = ci; //k2为const int &
const auto &h2 = 42; //正确,可以为常量绑定字面值 

auto& m =  &i;//Error,无法从“int *”转换为“int *&” ,这是引用&规则决定的
auto& m1 = &ci;// Error,无法从“const int *”转换为“const int *&” ,这是引用&规则决定的
const auto &m2 = &i;//m2为int * const &
const auto &m3 = &ci;//m3为const int * const &

上述错误原因:引用不能绑定表达式的计算结果,除非使用const。并且对于指针而言,它提升到顶层const,只能为常量指针
小结
:使用auto &的时候不光需要知道auto&的推到规则,还要明白引用(&)的使用限制。我们首先看的就是&的使用限制

1.2、运用场景

1)用于代替冗长复杂、变量使用范围专一的变量声明
举例:

#include<string>
#include<vector>
int main()
{
    std::vector<std::string> vs;
    for (std::vector<std::string>::iterator i = vs.begin(); i != vs.end(); i++)
    {
        //...
    }

    for (auto i = vs.begin(); i != vs.end(); i++)
    {
        //..
    }
}

2)在定义模板函数时,用于声明依赖模板参数的变量类型

template <typename _Tx,typename _Ty>
void Multiply(_Tx x, _Ty y)
{
    auto v = x*y;
    std::cout << v;
}

3)模板函数依赖于模板参数的返回值

template <typename _Tx, typename _Ty>
auto multiply(_Tx x, _Ty y)->decltype(_Tx*_Ty)
{
    return x*y;
}

1.3、注意事项

1)auto 变量必须在定义时初始化,这类似于const关键字
2)定义在一个auto序列的变量必须始终推导成同一类型
3)auto关键字做类型自动推导规则简述
a.如果初始化表达式是引用,则去除引用语义

    int a = 10;
    int &b = a;
 
    auto c = b;//c的类型为int而非int&(去除引用)
    auto &d = b;//此时c的类型才为int&
 
    c = 100;//a =10;
    d = 100;//a =100;

b.如果初始化表达式为const或volatile(或者两者兼有),则除去const/volatile语义

    const int a1 = 10;
    auto  b1= a1; //b1的类型为int而非const int(去除const)
    const auto c1 = a1;//此时c1的类型为const int
    b1 = 100;//合法
    c1 = 100;//非法

c.如果auto关键字带上&号,则不去除const语意

    const int a2 = 10;
    auto &b2 = a2;//因为auto带上&,故不去除const,b2类型为const int
    b2 = 10; //非法

d.初始化表达式为数组时,auto关键字推导类型为指针, 除非为数组且auto带上&,则推导类型为数组类型

    int a3[3] = { 1, 2, 3 };
    auto b3 = a3;
    cout << typeid(b3).name() << endl;  /* 程序输出是int* */

    auto & b7 = a7;
    cout << typeid(b7).name() << endl;  /* 程序输出是int[3] */    

e.函数或者模板参数不能被声明为auto

f.时刻要注意auto并不是一个真正的类型。仅仅是一个占位符,它并不是一个真正的类型,不能使用一些以类型为操作数的操作符,如sizeof或者typeid

2、nullptr关键字

1)null和nullptr的对比:
C++11标准之前,编译器进行解释程序时,NULL会被直接解释成0,而C语言中将null作为(void *)类型
C++11的出现后为了规避这个问题,nullptr在C++11中就是代表空指针

2)nullptr和void * 对比:
nullptr习惯被称作指针空 ;void*习惯被称作无类型指针

3)nullptr和nullptr_t 对比:
nullptr习惯被称作指针空 ;
nullptr_t习惯被称作指针空值类型 ,即表示指针空值类型并非仅有nullptr一个实例,可以通过nullptr_t来声明一个指针空值类型的变量

总结:
std::nullptr_t,空指针常数可以转换为任意类型的指针类型,但(void )不能转化为任意类型的指针,即 int *p=(void)是错误的,但int *p=nullptr是正确的。

拓展-C++11标准严格规定了数据间的关系规则
1)所有定义为nullprt_t类型的数据都是等价的,行为也完全一致

2)nullprt_t类型数据可以隐式转换成任意一个指针类型,不能转换为非指针类型, 使用reinterpret_cast<nullptr_t>也是不可以

3)nullptr_t类型数据不适用算术运算表达式,nullptr_t类型数据可以用于关系运算表达式,但仅能与nullptr_t类型数据或者指针类型数据进行比较,当且仅当关系运算符为==,<=,>=等时返回true

3、using关键字

1)using 在 C++11之前主要用于名字空间、类型、函数与对象的引入,实际上是去除作用域的限制

   //引入名字空间
  	using namespace std;
  	//引入类型
  	using std::iostream;
  	//引入函数
  	using std::to_string;
  	//引入对象
  	using std::cout;

2)通过using引入函数可以解除函数隐藏
派生类的函数屏蔽了与其同名的基类函数,规则如下:
1)如果派生类的函数与基类的函数同名,但是参数不同。此时,不论有无virtual关键字,基类的函数将被隐藏(注意别与重载混淆)
2)如果派生类的函数与基类的函数同名,并且参数也相同,但是基类函数没有virtual关键字。此时,基类的函数被隐藏(注意别与覆盖混淆)

  class Base{
  public:
  	void func()	{ cout << "in Base::func()" << endl; }
  	void func(int n) { cout << "in Base::func(int)" << endl;}
  };
  
  class Sub : public Base {
  public:
  	using Base::func;	//引入父类所有同名函数func,解除函数隐藏
  	void func()	{ cout<<"in Sub::func()"<<endl;}
  };

3)使用 using 代替 typedef,给类型命名

using uint8 = unsigned char; //等价于typedef unsigned char uint8;

using FunctionPtr = void (*)(); //等价于typedef void (FunctionPtr)();

template using MapString = std::map<T, char>; //定义模板别名,注意typedef无法定义模板别名,因为typedef只能作用于具体类型而非模板

4、decltype关键字

decltype与auto关键字一样,用于进行编译时类型推导,decltype则可以从一个变量或表达式中得到类型
注意:decltype 仅仅“查询”表达式的类型,并不会对表达式进行“求值”

map<string, float> cell;
decltype(cell)::value_type elem;//推断容器获得类型

4.1 decltype推导原则

如果e是一个没有带括号的标记符表达式或者类成员访问表达式,那么的decltype(e)就是e所命名的实体的类型。此外,如果e是一个被重载的函数,则会导致编译错误
否则 ,假设e的类型是T,如果e是一个将亡值,那么decltype(e)为 T&&
否则,假设e的类型是T,如果e是一个左值,那么decltype(e)为 T&
否则,假设e的类型是T,则 decltype(e)为 T

1)推导出表达式类型

// 1-基本用法
double a = 2;
decltype(a) a1; cout << typeid(a1).name() << endl;

// 2-只推断类型,不调用函数
decltype(foo()) b; cout << typeid(b).name() << endl;

// 3-与 const 结合
double c = 3.0;
decltype(c) c1; cout << typeid(c1).name() << endl;
decltype(c) c2 = 3.2;

const double d = 5.0;
// decltype(d) d1; // err, 推断出师 const double,所以必须初始化赋值
decltype(d) d2 = 6.3; cout << typeid(d2).name() << endl;
decltype(d) d3 = 5.2; // err, 不允许赋值

const double * const e = &c;
// decltype(e) e2 = 1.2; // err, 不允许赋值
decltype(e) e3 = &a;
// *e3 = 3.5; // err, 不允许改变值

// 4-与 reference 结合
int f = 0, &rf = f;
decltype(rf) rf1 = f; // 推断出为 reference, 正确
// decltype(rf) rf2 = 0; // 必须引用变量,不能使常量
// decltype(rf) rf3; // 必须初始化
decltype((f)) rf4 = f; // f 为左值,(f)为左值表达式即为引用,所以引用要初始化

const int g = 1, &rg = g;
decltype(rg) rg1 = g; // 引用 const 变量
decltype(rg) rg2 = 1; // 引动常量
// decltype(rg) rg3;  // err, 必须初始化

// 5-与指针结合
int h = 2;
int *ptrH= &h;
decltype(ptrH) ptrH2;
// decltype(*ptrH) ptrH3; // err, 表达式内容为解指针操作,ptrH3 为一个引用,引用必须初始化,故编译不过
struct A { double x; };
const A* a = new A{0};

//第6种情况
decltype(a->x) y;       // type of y is double
decltype((a->x)) z = y; // type of z is const double&,因为a一个常量对象指针

//第7种情况
int* aa=new int;
decltype(*aa) y=*aa;    //type of y is int&,解引用操作

//第8种情况
decltype(5) y;          //type of y is int
decltype((5)) y;        //type of y is int
const int&& RvalRef() { return 1; }
decltype ((RvalRef())) var = 1;  //type of var is const int&&

2)与using/typedef合用,用于定义类型

using size_t = decltype(sizeof(0));//sizeof(a)的返回值为size_t类型
using ptrdiff_t = decltype((int*)0 - (int*)0);
using nullptr_t = decltype(nullptr);

vector<int>vec;
typedef decltype(vec.begin()) vectype;
for (vectype i = vec.begin; i != vec.end(); i++){...}

3)重用匿名类型

  struct {
    int d ;
    doubel b;
  }anon_s;
  
decltype(anon_s) as ;	//定义了一个上面匿名的结构体

注意:匿名类型有其匿名的原因,一般情况下,匿名类型不应该被重用,应尽量避免这种用法
4)泛型编程中结合 auto,用于追踪函数的返回值类型,这是 decltype的最大用途

template <typename _Tx, typename _Ty> auto multiply(_Tx x, _Ty y)->decltype(x*y) { return x*y; }

5.constexpr 关键字

constexpr 在 C++11 中用于申明常量表达式 (const expression),可作用于函数返回值、函数参数、数据申明以及类的构造函数等

constexpr的好处:
1)是一种很强的约束,更好地保证程序的正确语义不被破坏。
2)编译器可以在编译期对constexpr的代码进行非常大的优化,比如将用到的constexpr表达式都直接替换成最终结果等。
3)相比宏来说,没有额外的开销,但更安全可靠。

5.1 constexpr应用

1)常量表达式函数
如果函数返回值在编译时期可以确定,那么可以使用constexpr修饰函数返回值,使函数成为常量表达式函数

constexpr int f(){return 1;}

注意:constexpr修饰函数返回值需要满足如下条件:
(a)函数必须有返回值;
(b)函数体只有单一的return语句;
(c)return语句中的表达式也必须是一个常量表达式;
(d)函数在使用前必须已有定义。

2)常量表达式值
如果认定变量是一个常量表达式,那就把它声明为constexpr类型

在constexpr声明中,如果定义了一个指针,constexpr仅对指针有效,与指针所指对象无关

const int *p=nullptr;  		//p是一个指向整型常量的指针(pointer to const)
constexpr int *p1=nullptr;	//p1是一个常量指针(const pointer)

自定义类型对象为常量表达式,那么在定义自定义类型时,需要将constexpr作用于自定义类型的构造函数

  struct MyType {
	int i;
	constexpr MyType(int x):i(x){}
  };
  constexpr MyType myType(1);

constexpr作用于自定义类型的构造函数需要满足如下条件:
(a)构造函数体必须为空;
(b)初始化列表只能使用常量表达式

3)常量表达式作用于函数模板
可以作用于函数模板,但是由于函数模板参数的不确定性,实例化后的模板函数可能不满足常量表达式的条件,此时,C++11标准规定,自动忽略constexpr

  struct NotConstType {
	int i;
	NotConstType(int x) :i(x) {}
  };
  NotConstType myType;

  //constexpr作用于函数模板
  template <typename T> constexpr T ConstExpFunc(T t) {
	return t;
  }

  int main(){
	NotConstType objTmp = ConstExpFunc(myType);	//编译通过,ConstExpFunc实例化为普通函数,constexpr被忽略
	constexpr NotConstType objTmp1 = ConstExpFunc(myType);	//编译**失败**
	constexpr int a = ConstExpFunc(1);	//编译通过,ConstExpFunc实例化为常量表达式函数
  }

4)constexpr元编程
constexpr可以作用于递归函数来实现编译时期的数值计算,即constexpr元编程。C++11标准规定,常量表达式应至少支持512层递归

  constexpr int Fibonacci(int n){
	return (n == 1) ? 1 : (n == 2 ? 1 : Fibonacci(n - 1) + Fibonacci(n - 2));
  }

  int main(){
	constexpr int fib8 = Fibonacci(8);	//编译期常量等于21
  }

5.2 constexpr 与 const 的区别

1)const 可以修饰函数参数、函数返回值、函数本身、类等,在不同的使用场景下,const具有不同的意义,不过大多数情况下,const描述的是“运行时常量性”,即在运行时数据具有不可更改性

2)constexpr可以修饰函数参数、函数返回值、变量、类的构造函数、函数模板等,是一种比const更加严格的约束,它修饰的表达式除了具有“运行时常量性”,也具有“编译时常量性”,即constexpr修饰的表达式的值在编译期间可知

  const int getConst(){ return 1; }
  enum{ e1=getConst(),e2};				//编译出错

  //换成constexpr即可在编译期确定函数返回值用于初始化enum常量
  constexpr int getConst(){ return 1; }
  enum{ e1=getConst(),e2};				//编译OK
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值