C++11: 新手易学,老兵易用

1 C++11中针对vector<pair<int,string> >两个尖括号间的空格去除,也就是说C++11中可以写成:vector<pair<int,string>>,编译器这里不会将>>认为是右移符号。


2  auto: C++中每个变量使用前必须定义从而被视为静态语言,而不像一些脚本语言变量可以这样用x=1称为动态语言。 静态语言和动态语言主要区别:静态语言类型检查发生在编译阶段,动态语言类型检查发生在运行阶段。C++11中实现了auto和decltype从静态语言向动态语言拉近了一点。注意的是auto不再是存储类型的标识符,还记得曾几何C语言中大讲atuo、static等等变量的区别,现在auto在C++11中是个类型指示符,原来C++98的存储类型标识符语义被遗弃。auto告诉编译器所声明的变量类型在编译时期推导而得,相当于atuo是个占位符,等编译时确定变量的类型。如:atuo x=1; 编译时x的类型自动推断为int,其等价于int x=1;

       一个实用之处就是std::vector<int>::iterator it=vec.begin();现在可以写为: auto it=vec.begin()

        atuo增加了类型的灵活性,比如:

class PI {
public:
    double operator* (float v) {
        return (double)val * v;    // 这里精度被扩展了
    }
    const float val = 3.1415927f;
};

int main() {
    float radius = 1.7e10;
    PI pi;
    auto circumference = 2 * (pi * radius);//atuo的一个好处就是,如果将来PI中的operator* 返回值类型改变了,main中不需要跟着修改
}
         atuo不能解决溢出问题,如:

unsigned int a=4294967295;//最大无符号整数
unsigned int b=1;
auto c=a+b;//c=0
        atuo为跨平台带来了一定好处,如:

auto var=strlen("hello world")//strlen在32位下返回4字节,64位下返回8字节整型,实用auto完全屏蔽了该细节
        auto提高了模板的灵活性:

template<typename T1, typename T2>
double Sum(T1 & t1, T2 & t2) {//遗憾的是这里double不能用auto了,编译器报错
    auto s = t1 + t2;   // s的类型会在模板实例化时被推导出来,不过此处已经增加了灵活性
    return s; 
} 

int main() {
    int a = 3;
    long b = 5;
    float c = 1.0f, d = 2.3f;
    
    auto e = Sum(a, b); // s的类型被推导为long
    auto f = Sum(c, d); // s的类型被推导为float
}
       auto用于宏的一个惊艳例子:

#define Max1(a, b) ((a) > (b)) ? (a) : (b)//遇见一个宏替换a或b就要计算一次a或b
#define Max2(a, b) ({ \
        auto _a = (a); \  //这里将宏替换a的计算结果先保存至_a中,这样减少了计算开销
        auto _b = (b); \
        (_a > _b) ? _a: _b; })

int main() {
    int m1 = Max1(1*2*3*4, 5+6+7+8);
    int m2 = Max2(1*2*3*4, 5+6+7+8);//计算开销减小
}
        atuo与指针和引用的关系,如:

int x;
int * y = &x;
double foo();
int & bar();

auto * a = &x;      // int*,这里auto* 和auto一样的
auto & b = x;       // int&,这里必须是atuo&
auto c = y;         // int*
auto * d = y;       // int*
auto * e = &foo();  // 编译失败, 指针不能指向一个临时变量
auto & f = foo();   // 编译失败, nonconst的左值引用不能和一个临时变量绑定
auto g = bar();     // int
auto & h = bar();   // int&
      CV限制符:auto, volatile, const 分别表示类型标示符,易失的,常量的。

double foo();
float * bar();

const auto a = foo();       // a: const double
const auto & b = foo();     // b: const double&
volatile auto * c = bar();  // c: volatile float*

auto d = a;                 // d: double, 简单的auto并不能带走右边的cv特性
auto & e = a;               // e: const double &,但是auto&能带走右边的cv特性
auto f = c;                 // f: float *
volatile auto & g = c;      // g: volatile float * &
     auto可以和初始化列表和new一起使用:

#include <initializer_list>

auto x = 1;
auto x1(1);
auto y {1};     // 使用初始化列表的auto
auto z = new auto(1);   // 可以用于new

     auto在同一个语句中推断多个变量,第一个atuo的类型就是后面变量的了性,从左至右推导,如:

auto a=1,b=2;//等价于int a=1; int b=2;但是建议不要这样使用auto,还是每个auto推断单独占一行
      一些auto不适用的地方:

#include <vector>
using namespace std;

void fun(auto x =1){}  // 1: auto函数参数,无法通过编译

struct str{
    auto var = 10;   // 2: auto非静态成员变量,无法通过编译
};

int main() {
    char x[3];
    auto y = x;
    auto z[3] = x; // 3: auto数组,无法通过编译

    // 4: auto模板参数(实例化时),无法通过编译
    vector<auto> x = {1};
}


3 decltype:类型推导

       typeid(类名).name()可以返回类型的名字,typeid(类名).hash_code()返回类型对应的唯一hash值,该hash值可以比较,如:typeid(类1).hash_code()==typeid(类名2).hash_code()。is_same<类名1,类名2>::value也可以判断两个类是否相同。

    int i;
    decltype(i) j = 0; 
    cout << typeid(j).name() << endl;   // 打印出"i", g++表示integer

    float a;
    double b;
    decltype(a + b) c;
    cout << typeid(c).name() << endl;   // 打印出"d", g++表示double
        decltype不像auto那样从变量声明的初始化表达式获得变量的类型,decltype总是以一个普通的表达式为参数,返回该表达式的类型。decltype可以获得的类型来定义另外一个变量,与auto相同的是,decltype也是在编译是进行的。

        decltype和using结合的例子:

using size_t=decltype(sizeof(0));//size_t就可以跨平台使用了
        decltype可以像auto it=vec.begin()一样用于标准容器:

    vector<int> vec;
    typedef decltype(vec.begin()) vectype;

    vectype i;  // 这是auto无法做到的
    for (i = vec.begin(); i < vec.end(); i++) {
        // 做一些事情
    }

    for (decltype(vec)::iterator i = vec.begin(); i < vec.end(); i++) {
        // 做一些事情
    }
          decltype可以重用匿名类型:

enum class{K1, K2, K3}anon_e;   // 匿名的强类型枚举

union {
    decltype(anon_e) key;
    char* name;
}anon_u;    // 匿名的union

struct {
    int d;
    decltype(anon_u) id;
}anon_s[100];   // 匿名的struct数组
int main() {
    decltype(anon_s) as;//重用匿名类型的类型
    as[0].id.key = decltype(anon_e)::K1;   // 引用匿名强类型枚举中的值
}//通过匿名的变量名anon_e等加上decltype可以推断这些匿名类型的类型并进行重用,不过这个似乎违背了匿名类型的设计初衷
         decltype重塑前面“auto提高了模板的灵活性”的例子:

// s的类型被声明为decltype(t1 + t2)
template<typename T1, typename T2>
void Sum(T1 & t1, T2 & t2, decltype(t1 + t2) & s) {//可以通过参数引用s返回这个不确定类型的返回值
    s = t1 + t2;
}

int main() {
    int a = 3;
    long b = 5;
    float c = 1.0f, d = 2.3f;

    long e;//这里仍然需要定义e的类型,传给Sum,还不是完全智能的,后面的函数返回值推导可以解决这个问题
    float f; 
    Sum(a, b, e);   // s的类型被推导为long
    Sum(c, d, f);   // s的类型被推导为float
}
          decltype用于实例化模板:

int hash(char*);//一个hash函数

//map<char*, decltype(hash)> dict_key;    // 无法通过编译,hash是个函数名,hash(nullptr)才是表达式满足decltype
map<char*, decltype(hash(nullptr))> dict_key;
        模板类 result_of实现就是基于decltype的,用于推断函数的返回值类型,位于头文件<type_traits>:

typedef double (*func)();
result_of<func()>::type f;   // 由func()推导其结果类型,double
        decltype(e)推断的四个规则:

1) 当e是一个没有带括号的标记符表达式(除去关键字、字面量等编译器需要使用的标记之外的程序员自定义的标记)或者类成员访问表达式,那么decltype(e)就是e所命名的实体的类型。如果e是一个被重载的函数名,则会编译错误。

2) 否则,假设e类型是T,若e是一个将亡值(如函数非引用返回值),那么decltype(e)为T&&

3) 否则,若e是一个左值,decltype(e)为T&,这是一个左值规则

4) 否则,decltype(e)为T
  例子如下:

int i = 4;
int arr[5] = {0};
int *ptr = arr;

struct S { double d; } s;

void Overloaded(int);
void Overloaded(char);      // 重载的函数

int && RvalRef();

const bool Func(int);

// 规则1: 单个标记符表达式以及访问类成员,推导为本类型
decltype(arr) var1;            // int[5], 标记符表达式
decltype(ptr) var2;            // int*, 标记符表达式
decltype(s.d) var4;            // double, 成员访问表达式
decltype(Overloaded) var5;     // 无法通过编译,是个重载的函数

// 规则2: 将亡值,推导为类型的右值引用
decltype(RvalRef()) var6 = 1;  // int&&

// 规则3: 左值,推导为类型的引用
decltype(true ? i : i) var7 = i;    // int&, 三元运算符,这里返回一个i的左值
decltype((i)) var8 = i;             // int&, 带圆括号的左值 
decltype(++i) var9 = i;             // int&, ++i返回i的左值
decltype(arr[3]) var10 = i;         // int& []操作返回左值
decltype(*ptr)  var11 = i;          // int& *操作返回左值
decltype("lval") var12 = "lval";    // const char(&)[9], 字符串字面常量为左值

// 规则4:以上都不是,推导为本类型
decltype(1) var13;              // int, 除字符串外字面常量为右值
decltype(i++) var14;            // int, i++返回右值
decltype((Func(1))) var15;      // const bool, 圆括号可以忽略
       规则3左值规则,确实比较难以判断,可以通过一定技术检查,先声明decltype定义的变量,再在其它语句对其初始化,若是左值必须声明即初始化,那么编译器会报错。也可以借助is_lvalue_reference等模板类判断变量引用类型:

#include <type_traits>
#include <iostream>
using namespace std;

int i = 4;
int arr[5] = {0};
int *ptr = arr;

int && RvalRef();

int main(){
    cout << is_rvalue_reference<decltype(RvalRef())>::value << endl;    // 1

    cout << is_lvalue_reference<decltype(true ? i : i)>::value << endl; // 1
    cout << is_lvalue_reference<decltype((i))>::value << endl;          // 1
    cout << is_lvalue_reference<decltype(++i)>::value << endl;          // 1
    cout << is_lvalue_reference<decltype(arr[3])>::value << endl;       // 1
    cout << is_lvalue_reference<decltype(*ptr)>::value << endl;         // 1
    cout << is_lvalue_reference<decltype("lval")>::value << endl;       // 1

    cout << is_lvalue_reference<decltype(i++)>::value << endl;          // 0
    cout << is_rvalue_reference<decltype(i++)>::value << endl;          // 0
}
        decltype可以带来冗余的符号,如decltype(T) & a中decltype本身可能就推断出是个引用,后面由出现一个&则出现冗余的&。auto不能带走初始化表达式的cv限制符,但是decltype却能带走其表达式的cv限制符。

#include <type_traits>
#include <iostream>
using namespace std;

const int ic = 0;
volatile int iv;

struct S { int i; };

const S a = {0};
volatile S b;
volatile S* p = &b;

int main() {
    cout << is_const<decltype(ic)>::value << endl;      // 1
    cout << is_volatile<decltype(iv)>::value << endl;   // 1

    cout << is_const<decltype(a)>::value << endl;       // 1
    cout << is_volatile<decltype(b)>::value << endl;    // 1

    cout << is_const<decltype(a.i)>::value << endl;     // 0, 成员不是const //成员变量失去了其所属类型的cv属性
    cout << is_volatile<decltype(p->i)>::value << endl; // 0, 成员不是volatile
}
decltype最后的推断结果包含了一些冗余符号cv限制符和引用&则可以忽略多余的符号

#include <type_traits>
#include <iostream>
using namespace std;

int i = 1;
int & j = i;
int * p = &i;
const int k = 1;

int main() {
    decltype(i) & var1 = i; 
    decltype(j) & var2 = i;     // 冗余的&, 被忽略 
        
    cout << is_lvalue_reference<decltype(var1)>::value << endl;     // 1, 是左值引用

    cout << is_rvalue_reference<decltype(var2)>::value << endl;     // 0, 不是右值引用
    cout << is_lvalue_reference<decltype(var2)>::value << endl;     // 1, 只是左值引用

    //decltype(p)* var3 = &i;     // 无法通过编译  //auto*和auto相同,但是decltype的*不能省略,这里推断结果int** var3和右边的&i不符
    decltype(p)* var3 = &p;     // var3的类型是int**

    auto* v3 = p;               // v3的类型是int*
    v3 = &i;

    const decltype(k) var4 = 1; // 冗余的const,被忽略 
}


4 追踪返回类型

先看一个实例:

#include <iostream>
using namespace std;

template<typename T1, typename T2>
auto Sum(const T1 & t1, const T2 & t2) -> decltype(t1 + t2)
{//当模板的返回值类型是随着模板实例化才能确定时,通过auto和decltype追踪返回值类型,auto相当于占位符,最后模板实例化从左至右,最后decltype推导出返回值类型填写回auto处即可
    return t1 + t2;
} 
template <typename T1, typename T2>
auto Mul(const T1 & t1, const T2 & t2) -> decltype(t1 * t2)
{ 
     return t1 * t2;
}
int main() 
{ 
     auto a = 3; 
     auto b = 4L;
     auto pi = 3.14; 
     auto c = Mul(Sum(a, b), pi);//完全根据模板实例化自动推导参数 
     cout << c << endl; // 21.98
}

       追踪返回类型用于简化函数定义: 

#include <type_traits>
#include <iostream>
using namespace std;

// 有的时候,你会发现这是面试题
int (*(*pf())())() {//这个看起来真的比较蛋疼
    return nullptr;
}

// auto (*)() -> int(*) () 一个返回函数指针的函数(假设为a函数)
// auto pf1() -> auto (*)() -> int (*)() 一个返回a函数的指针的函数
auto pf1() -> auto (*)() -> int (*)() {//从右向左解析
    return nullptr;
}

int main() {
    cout << is_same<decltype(pf), decltype(pf1)>::value << endl;    // 1
}

       追踪返回值类型用于转发函数中:

#include <iostream>
using namespace std;

double foo(int a) {
    return (double)a + 0.1;
}

int foo(double b) {
    return (int)b;
}

template <class T> 
auto Forward(T t) -> decltype(foo(t)){//转发函数
    return foo(t);
}

int main(){
    cout << Forward(2) << endl;     // 2.1
    cout << Forward(0.5) << endl;   // 0
}


5 for循环

    标准库已经有了for_each

for_each( InputIt first, InputIt last, UnaryFunction f );
     c++11有了新的for循环方式:

#include <vector>
#include <iostream>
using namespace std;

int main() {
    vector<int> v = {1, 2, 3, 4, 5};
    for (auto i = v.begin(); i != v.end(); ++i)
        cout << *i << endl;     // i是迭代器对象

    for (auto e: v)//冒号分开,前面部分是范围内用于迭代的变量,后半部分是迭代的范围,只能用于明确已知迭代范围
        cout << e << endl;      // e是解引用后的对象
}

      对于迭代范围不确定不能使用新的for形式,如下:

#include <iostream>
using namespace std;

int func(int a[]) {

    for (auto e: a) // 编译失败,迭代范围不确定
        cout << e;
}

int main() {
    int arr[] = {1, 2, 3, 4, 5};
    func(arr);
}








 



        


   




   

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值