C++11的新特性(部分)

目录

11.1类型推导

11.2auto的推导规则

11.3auto作为函数的形参类型

11.4auto的限制

11.5auto可以推导函数的返回值类型

11.6decltype关键字

11.7decltype的推导规则

11.8auto和decltype的使用

11.9基于范围的for循环

11.9.1一般遍历一个数组(容器)的方法

11.9.2C++11基于范围的for循环

11.9.3基于范围的for循环的优势:

11.10指针空值--nullptrptr

11.11typedef与using


11.1类型推导

C++11引入了 auto 和 decltype 关键字实现类型推导,通过这两个关键字不仅能方便地获取复杂的类型,而且还能简化书写,提高编码效率。

auto类型推导: auto 定义的变量,可以根据初始化的值,在编译时推导出变量名的类型。

int main()
{
    auto a;//error,没有初始化值 auto无法推导出s的类型
    auto int b;//error,C++11中auto不再表示存储类型指示符,C语言中auto为存储类型指示符
    auto dx = 12.23;//ok,double
    auto fx = 12.23f;//ok,float
    auto x = 5;//ok,x为int类型
    auto ip = new int(10);//ok,ip为int*类型
    const auto *xp = &x,u = 6;//ok,xp为const int*类型,u为const int类型,auto的推导类型为int
    auto xp1 = &x,u1 = 6;//error,因为这样auto即推导出xp1为整型指针类型,又推导出u1为整型,产生矛盾。auto推导的类型必须保持一致
    
    
    //练习
    auto x=5;
    const auto *xp = &x,u;//error,虽然经过前面const auto *xp = &x的推导,auto的类型可以确定为int了,但是u仍然必须要写 "= 6" 后面否则编译器不能通过
    const auto *ip = &x,u = 6.0;//error,前半句使auto推导为int,后半句使auto推导为double,推导类型不一致,产生矛盾
    return 0;
}

auto 并不能代表一个实际的类型声明 (如s的编译错误),只是一个类型声明的"占位符"。使用 auto 声明的变量必须要有初始化值,以让编译器推断出它的实际类型,并在编译时将auto 占位符替换为真正的数据类型。

11.2auto的推导规则

int main()
{
    int x = 10;
    auto *ip = &x;//ok,ip为int*类型,auto被推导为int类型
    auto xp = &x;//ok,xp为int*类型,auto被推导为int*类型
    
    auto& c = x;//ok,c是int &(整型引用)类型,auto是int类型
    auto d = c;//ok,d是int类型,auto也是int类型,无法推导出auto也是int&(整型引用)类型
    
    const auto e = x;//ok,e是const int类型,auto为int类型
    auto f = e;//ok,e为常变量,但f仍然为int类型,auto也是int类型,不具有常性。就等价于const f = 10;
    
    const auto &g = x;//ok,g为const int &类型,auto为int类型
    auto & h = g;//ok,h为const int &类型,auto为const int类型,因为如果h这个引用不具有常性,它就可以去改变g也是x的值,但g为常性引用不允许修改值,所以h必须为常性。与f不同的原因在于h是引用可以修改g即x的值,而f只是一个普通变量不会改变e的值。
}

ip 和 c的推导结果是很显然的,auto 在编译时被替换为 int,因此 a 和c分别被推导为int*和 int&。

xp的推导结果说明,其实 auto 不声明为指针,也可以推导出指针类型。

d 的推导结果说明当表达式是一个引用类型时,auto 会把引用类型抛弃,直接推导成原始类型 int。

e 的推导结果说明,const auto 会在编译时被替换为 const int。

f 的推导结果说明,当表达式带有const (实际上 volatile (异变属性)也会得到同样的结果) 属性时,auto 会把const属性抛弃掉,推导成没有 const 类型的 int。

g、h的推导说明,当 auto 和引用(换成指针在这里也将得到同样的结果) 结合时,auto 的推导将保留表达式的 const 属性。

通过上面的一系列示例,可以得到下面这两条规则:

1、当不声明为指针或引用时,auto 的推导结果和初始化表达式抛弃引用和cv 限定符后类型一致

2、当声明为指针或引用时,auto 的推导结果将保持初始化表达式的cv 属性

11.3auto作为函数的形参类型

早期(C++11)中,auto不能作为函数的参数,需要将编译器的标准改为C++20标准

#include<typeinfo>
void func(auto x)
{
    cout<<sizeof(x)<<endl;
    cout<<typeid(x).name<<endl;
}
void funr(auto &x)
{
    cout<<sizeof(x)<<endl;
    cout<<typeid(x).name<<endl;
}
int main()
{
    int a=10;
    int ar[]={12,23,34,45,56};
    func(a);//4  int
    func(ar);//4  int*
    
    funr(a);//4  int
    funr(ar);//20  int [5] //数组引用作为参数时不会退化为指针,它依然具有数组的两个属性
    return 0;
}

11.4auto的限制

struct Foo
{
    auto value=0;//error  auto不能用于非静态成员变量
    static const int num =10;//ok
}
int main()
{
    int ar[10]={0};
    auto br=ar;//ok,br ->int*
    auto dr[5]={1,2,3,4,5};//error,auto无法定义数组
    auto &er=ar;//ok,er为int(&)[10]类型的数组引用
    return 0;
}

C++11中auto成为类型指示符。

auto不能用于函数参数(对于早期的标准,对于C++20标准中可以)

auto不能用于非静态成员变量

auto无法定义数组,但能推导数组引用

实例化模板时不能使用auto作为模板参数

11.5auto可以推导函数的返回值类型

template<class T>
T my_max(T a,T b)
{
    return a>b?a:b;
}
int main()
{
    auto x=my_max(12,23);
    auto y=my_max('a','b');
    
    cout<< x << endl;
    cout<< y << endl;
    return 0;
}

11.6decltype关键字

auto用于通过一个表达式在编译时确定待定义的变量类型,auto 所修饰的变量必须被初始化,编译器需要通过初始化来确定 auto 所代表的类型,即必须要定义变量。如果只获取类型而不定义变量,就需要decltype关键字。

语法格式:decltype(exp)

从格式上来看,decltype 很像sizeof-一用来推导表达式类型大小的操作符。类似于sizeofdecltype 的推导过程是在编译期完成的,并且不会真正计算表达式的值。

对于sizeof():

int main()
{
    int a=10;
    doubel dx=12.23;
    cout<<sizeof(++a)<<endl;//4
    cout<<sizeof(dx+a)<<endl;//8
    cout<<"a="<<a<<endl;//10;
    return 0;
}

由此可见,sizeof()只是根据其类型计算其大小,并不执行括号内的表达式。这点和decltype()相同

int max_int(int a.int b)
{
    return a>b?a:b;
}
int main()
{
    int x=10;
    decltype(x) y=1;//y -> int
    decltype(x) k;//k -> int,可以不用初始化
    decltype((x)) a=x;//a -> int &,必须初始化,因为a为引用
    decltype((x+1)) b=x;//b -> int,并不计算x+1
    //想要得到某种类型的引用,需要给其变量加上括号,使得定义类型为引用
    decltype((x+y)) c=x;//c -> int,并不计算x+y
    decltype(x+y) z=0;//z -> int,并不计算x+y
    const int &i=x;
    decltype(i) j=y;//j -> const int &
    const decltype(z)*p=&z;//p -> const int*
    decltype(z) *ip=&z;//ip -> int*
    decltype(ip) *pp=&ip;//pp -> int**
    
    int a=10;
    int* p=&a;
    decltype(p) t = p;//t -> int*
    decltype(*p) rc = a;//rc -> int &
    decltype((p)) s = p;//s -> int* &    
        
    int a=10,b=20;
    decltype(max_int(a,b)) c = max_int(a,b);//decltype中的函数不会被调动,它是在编译期就完成类型的推导
}

auto和decltype的最大区别:auto在推导时会丢失CV属性,而decltype会保留原有表达式的类型,不会丢失CV属性

11.7decltype的推导规则

推导规则1:exp 是标识符、类访问表达式,decltype(exp)和exp 的类型一致。

推导规则2:exp 是函数调用,decltype(exp)和返回值的类型一致。

推导规则3:其他情况,若exp 是一个左值,则 decltype(exp)是exp 类型的左值引用,否则和 exp类型一致。

11.8auto和decltype的使用

使用一个函数比较不同类型或相同类型的变量大小

template<class T,class U>
T MAX1(T a,U b)
{
    return a>b?a:b;
}

template<class R,class T,class U>
R MAX2(T a,U b)
{
    return a>b?a:b;
}

template<class T,class U>
auto MAX3(T a,U b)//->decltype(a>b?a:b)//类型后置方式
{
    return a>b?a:b;
}
int main()
{
    auto x=MAX1(12,23.34);//会丢失精度,结果为23
    //早期的解决方法
    auto y=MAX2<double>(12,23.34);//结果为23.34,但是每次使用时必须明确函数的返回值类型,因为函数模板无法推演函数的返回值类型
    //最优的解决方法
    auto z=MAX3(12,23.34);//结果为23.34  z为double类型
    auto a=MAX3(45,23.34);//结果为45  a为double类型
    auto b=MAX3(12,'a');//结果为97  b为int类型
    auto c=MAX3('a',23.34);//结果为97 c为double类型
    return 0;
}

猜想:对于MAX3()函数的返回值类型的推导为参数列表类型中精度更高的类型。

11.9基于范围的for循环

在C98中,不同的容器和数组,遍历的方法不尽相同,写法不统一,也不够简洁,而C++11基于范围的 for循环以统一、简洁的方式来遍历容器和数组,用起来更方便了。

11.9.1一般遍历一个数组(容器)的方法

int main()
{
    int ar[]={1,2,3,4,5,6,7,8,9,10};
    int len=sizeof(ar)/sizeof(ar[0]);
    for(int i=0;i<len;++i)
    {
        cout<<ar[i]<<" ";
    }
    cout<<endl;
    return 0;
}

11.9.2C++11基于范围的for循环

int main()
{
    int ar[]={1,2,3,4,5,6,7,8,9,10};
    for(int x:ar)//这样使用的前提是ar必须为容器名(存在两个属性)
    {
        cout<<x<<" ";
    }
    cout<<endl;
    return 0;
}

基于范围的for循环的一般格式:

for(ElemType val:array)
{
    ...//statement 循环体
}

ElemType:是范围变量的数据类型。它必须与数组(容器)元素的数据类型一样,或者是数组元素可以自动转换过来的类型。

val:是范围变量的名称。该变量将在循环迭代期间依次接收数组中的元素值。在第一次循环迭代期间它接收的是第一个元素的值;在第二次循环迭代期间,它接收的是第二个元素的值,以此类推。

array:是要让该循环进行处理的数组(容器)的名称。该循环将对数组中的每个元素迭代一次。

statement:是在每次循环选代期间要执行的语句。要在循环中执行更多的语句,则可以使用一组大括号来包围多个语句。与其他循环体一样,可以用continue来结束本次循环,也可以用break来跳出整个循环。

11.9.3基于范围的for循环的优势:

void Print(auto & xr)//此处传参时不能使用指针,即auto xr。因为指针具有容器的大小,而引用具有容器的两个属性(类型和大小)
{
    for(auto x:xr)
    {
        cout<<x<<" ";
    }
    cout<<endl;
}
int main()
{
    int ar[]={12,23,34,45,56,67};
    double br[]={1.2,2.3,3.4,4.5,5.6,6.7,7.8};
    char ch[]={'a','b','c','d'};
    //使用一个函数打印三种不同类型的数组元素
    Print(ar);//12 23 34 45 56 67
    Print(br);//1.2 2.3 3.4 4.5 5.6 6.7 7.8
    Print(cr);//a b c d
    //使用基于范围的for对容器的元素进行更改
    int* p1=ar;
    int(&p2)[]=ar;//ok,但大小未知,不能用为基于范围的for
    auto &p3=ar;//ok,p3为int(&)[6]类型
    int(&p4)[6]=ar;//pk
    for(auto &x:ar)//需要加上引用,否则无法改变ar容器中的元素。
    //for(auto &x:p1)//error,这里不能将ar替换为p1,这是错误的,因为基于范围的for在使用时必须知道该容器的两个属性(类型和大小)
    //for(auto &x:p2)//error,p2是引用,类型为int(&)[],大小未知
    //for(auto &x:p3)//ok,p3类型为int(&)[6],具有类型和大小属性
    //for(auto &x:p4)//ok,p4为ar容器的别名具有类型和大小两个属性,所以可以使用基于范围的for
    {
        if(x%2==0)
        {
            x+=10;
        }
        cout<<x<<" ";
    }//22 23 44 45 66 67
    Print(ar);//22 23 44 45 66 67
    //当上面循环中没加引用,则ar容器中的值不会改变
    //Print(ar);//12 23 34 45 56 67
    return 0;
}

11.10指针空值--nullptrptr

//NULL是个宏定义,在C头文件(stddef.h)中有如下定义:
#undef NULL
#if defined (__cplusplus)//C++
#define NULL 0
#else
#define NULL((void*)0)//C
#endif

在C++中,NULL可能被定义为字面常量或者为无类型指针(void*)0常量,这样在使用时会出现问题,如下重载的例子:

void fun(char *p)
{
    cout<<"fun char *p"<<endl;
}
void fun(int i)
{
    cout<<"fun int i"<<endl;
}
int main()
{
    fun(0);//fun int i,0为字面常量会调用第二个函数
    fun(NULL);//fun int i,NULL在C++中会宏替换为0,这样依旧调用第二个函数。
    fun((void*)0);//fun char *p,表明参数为指针类型
}

fun(NULL);表达的意思是想要调用第一个函数,但实际调动第二个函数。引起该问题的原因就是字面常量0的二义性,在C98标准中,字面常量0的类型既是一个整型,又是一个无类型指针,如果想要调用第一个函数就必须对字面常量0进行强转为((char *)0)或((void *)0),否则编译器总是优先将0看作字面常量。

为了解决这种问题,C++11标准就给出了nullptr,nullptr是一个所谓”指针空值类型“的常量。指针空值类型被名为nullptr_t。在头文件cstddef中可以看到如下定义:

typedef decltype(nullptr) nullptr_t;

在使用nullptr_t时必须#include,而nullptr不用。因为nullptr是关键字,而nullptr_t是推导而来的。

所有定义为 nullptr_t类型的数据都是等价的,行为也是完全一致。nullptr _t类型数据可以隐式转换成任意一个指针类型。nullptr_t类型数据不能转换为非指针类型使用

int main()
{
    int a=nullptr;//error
    int *p=nullptr;//ok
    char* cp=nullptr;//ok
}

nullptr_类型数据不适用于算术运算表达式。

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

注意:

1、nullptr 是C++11新引入的关键字,是一个所谓"指针空值类型”的常量,在C++程序中直接使用。

2、在C++11中,sizeof(nullptr) 与 sizeof((void*)0) 所占的字节数相同都 (4或8)。

3、为了提高代码的健壮性,在后续表示指针空值时建议最好使用nullptr。

11.11typedef与using

一切合法的变量定义都可以通过typedef转成类型的声明

//二者等价
typedef unsigned int u_int;
using u_int=unsigned int;
//二者等价
typedef double array[10];
using array=double[10];
//二者等价
typedef void (*pfun)(int,int);
using pfun=void(*)(int,int);

void add(int a,int b)
{
    return a+b;
}

int main()
{
    u_int a=10;//a的类型为unsigned int
    array b,c;//b和c的类型都为有十个元素的数组->double [10]
    pfun d=add;//d的类型是参数为两个整型,返回值类型为void的函数指针->void(*)(int,int)
    return 0;
}

using可以和模板一起使用,而typedef不能,如下:

template<class T,size_t N>//class T叫模板参数类型,size_t N为非类型,代表一个常量值,为整型常量
using array=T[N];

int main()
{
    array<int,100> a;//a为int类型数组有100个元素
    array<double,10> b;//b为double类型数组有10个元素
    array<char,20> c;//c为char类型数组有20个元素
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值