C++——C++11的标准(上)

上篇主要介绍一些零散的改动、类、右值引用与构造函数

下篇主要介绍Lambda函数、可变模板参数和包装器

初始化列表

其实就是{ ... }这个东西。可作为函数参数。

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

double sum(initializer_list<double> il);

int main()
{
    double total = sum({3.14,2.22,96.3,45});
    cout << total;
    return 0;
}

double sum(initializer_list<double> il)
{
    double sum = 0;
    for(auto i=il.begin();i != il.end();i++)
    {
        sum += *i;
    }
    return sum;
}

返回后置类型

auto func (char a,double b) -> decltype(a+b)
{
    return a+b;
}

auto func (char a,double b) -> int
{
    return (int)(a+b);
}
template <typename T, typename U>
// 如果T*U定义的话
auto func(T &t, U &u) -> decltype(T * U)
{
    //do someting
}

using别名

主要用于模板类

与typedef的不同,可用于模板的部分具体化

uisng 新别名 = 原名称

int main()
{
    using Vec = std::vector<int>;
    Vec vec;
    //do something
    
    using My_Int = int;
    My_Int a = 10;
    //do smoething
    return 0;
}
template <class T>
  using My_arr = std::array<T,12>;
  //typedef std::array<T,12> My_arr; //error

空指针nullptr

使用nullptr可使程序更加清晰和安全

#include <iostream>

int main()
{
    using namespace std;
    int *p1 = 0; //可以。但是有时候可能会不方便阅读
    int a = NULL; //可以,但是没有必要
    int *p2 = nullptr;
    //int a = (int)nullptr; //error
    //int a =nullptr; //error
    if(a == NULL) cout << "确定这么写的人没被打死?" << endl;
    if(p2 == 0); //ok
    return 0;
}

nullptr实质上是一个指针类型。

智能指针,之前写过

异常规范

C++11不建议使用 异常规范

但新增了指出函数不会发成异常的关键字

int func(int a,int b) noexcept;

不是原本有异常的代码写这个东习就不会出现异常,而是程序员处理完异常再表明:这个函数被处理的不会发生异常

#include <iostream>
int func(int a,int b) noexcept;
int main()
{

    using namespace std;
    int sum;
    try
    {
        sum = func(3,-3);
    }
    catch(const char * &error_log)
    {
        cout << error_log;
    }
    cout << sum;
    return 0;
}
//声明与定义必须对应都有noexcept
int func(int a,int b) noexcept
{
    if(a == -b)
        throw "error";
    return 10/(a+b);
}

不会抛出异常,但是还会引发标准错误流向屏幕发送错误消息

 运行结果

terminate called after throwing an instance of 'char const*'

翻译过来:在抛出 'char const*' 的实例调用终止

作用域内枚举

使用 :: 访问

int main()
{
    using namespace std;
    enum A{a,b,c};
    //enum B{c,d,e}; error
    //or use struct
    enum class B {c,d,e}; //ok
    return 0;
}

显示转换

防止单参构造函数的类进行隐式转换再构造函数前用explicit修饰,不解释了。

类内初始化

#include <iostream>
class Data
{
public:
    int a = 10;
    int b = 20;
};
int main()
{
    Data data;
    using namespace std;
    cout << data.a << " " << data.b << endl;
    return 0;
}

运行结果

基于范围for循环

注意输入要使用引用,只要有迭代器并使用其迭代器历遍容器即可使用

这里用数组做演示

#include <iostream>
using namespace std;
int main()
{
    int arr[10] {1,2,3,4,5,6,7,8,9,0}; //initialized with C++ 11
    for(int x : arr)
    {
        cout << x << " ";
    }
    for(auto & x: arr)
    {
        cin >> x;
    }
    for(auto x : arr)
    {
        cout << x << " ";
    }
    return 0;
}

新的STL容器

之前的博客里有说过

forward_list

unordered_map

unordered_multimap

unordered_set

unordered_multiset

新增的模板

array

新的STL方法

cbeing()

cend()

与begin()和end()一样,只不过将容器内的元素视为常量

crbeing()

crend()

同上

valarry升级

添加了begin()和end()方法

摒弃了export

但保留其关键字,方便以后使用

模板类实例化的嵌套

尖括号可不用空格隔开

右值引用 &&

可对右值做引用

//同样传入3,4这两个右值,上面的可以,下面的不行
int func(int &&a, int &&b);
int func(int & a, int &b);

还可用于获取常量的地址

#include <iostream>
using namespace std;

int main()
{
    int && rr = 13;
    int * p = &rr;
    cout << p << endl;
    *p = 15;
    cout << rr;
    return 0;
}

运行结果

移动语义和右值引用 

假设我们要对一个特别长的字符串进行处理,怎么办,肯定不能用值传递对吧。这样即浪费时间又浪费空间。可以考虑传递引用或者指针。

再来说一说移动语义的概念

移动是 语言标准 提出的概念,通过编写遵守移动的 移动构造函数、右值限定成员函数,逻辑上 优化 对象内资源 的转移流程

某些智能指针也用到了这个概念

简单的来说,移动语义就是对对象的所有权按的转移

 理论存在,时实践开始

首先分两步

1.右值引用让编译器知道何时可以使用移动语义

2.编写移动构造函数(就是参数传递右值引用的构造函数),进行处理

为什么右值引用能让类进行移动构造?

其前面说过了,右值引用能获得右值的地址,进而用指针进行所有权的转让。

代码

#include <iostream>
using namespace std;
class Useless
{
private:
    int n;
    char * pc;
    static int ct;
    void ShowObject() const;
public:
    Useless();   //默认构造函数
    explicit Useless(int k);    //显示单参构造函数
    Useless(int k,char ch);     //构造函数
    Useless(const Useless & f); //拷贝函数
    //移动构造函数
    Useless( Useless && f);
    ~Useless(); //析构函数
    //运算符重载
    Useless operator+(const Useless & f)const;
    void ShowData() const;  //method
};
//初始化
int Useless::ct = 0;

Useless::Useless()
{
    ++ct;
    n = 0;
    pc = nullptr;
    cout << "default constructor called; number of objects: " << ct << endl;
    ShowObject();
}

Useless::Useless(int k) : n(k)
{
    ++ct;
    cout << "int constructor called; number of objects: " << ct << endl;
    pc = new char[n];   //new一个空间存放字符串
    ShowObject();
}
//将字符串全部初始化为ch
Useless::Useless(int k, char ch) : n(k)
{
    ++ct;
    cout << "int, char constructor called; number of objects: " << ct << endl;
    pc = new char[n];
    for(int i=0;i<n;i++)
        pc[i] = ch;
    ShowObject();
}

//深拷贝
Useless::Useless(const Useless &f) : n(f.n)
{
    ++ct;
    cout << "copy const called; number of objects: " << ct << endl;
    pc = new char[n];   //深拷贝
    for(int i=0;i<n;i++)
        pc[i] = f.pc[i];
    ShowObject();
}

//移动构造函数,指的是右值对象的所有权转移
Useless::Useless(Useless &&f) : n(f.n)
{
    ++ct;
    cout << "MOVE constructor called; number of objects: " << ct << endl;
    pc = f.pc;  //共享地址
    f.pc = nullptr;     //防止重复析构右值
    f.n = 0;
    ShowObject();
}

Useless::~Useless()
{
    cout << "destructor called; objects left: " << --ct << endl;
    cout << "deleted object:\n";
    ShowObject();
    delete []pc;
}

Useless Useless::operator+(const Useless &f) const
{
    cout << "Entering operator+()\n";
    Useless temp = Useless(n+f.n); //构造函数生成一个临时的变量赋值给temp
    for (int i = 0; i < n; i++)
        temp.pc[i] = pc[i];
    for (int i = n; i < temp.n; i++)
        temp.pc[i] = f.pc[i - n];
    cout << "temp object:\n";
    cout << "Leaving operator+()\n";
    return temp;
}
void Useless::ShowObject() const
{
    cout << "Number of elements: " << n;
    cout << " Data address: " << (void *) pc << endl;
}

void Useless::ShowData() const
{
    if (n == 0)
        cout << "(object empty)";
    else
        for (int i = 0; i < n; i++)
            cout << pc[i];
    cout << endl;
}

int main()
{
    Useless one(10,'x');
    cout << "-----------------"<< endl;
    Useless two = one;
    cout << "-----------------"<< endl;
    Useless three (20,'o');
    cout << "-----------------"<< endl;
    Useless four (one + three);     //根据上面的代码,重载+返回一个临时变量temp,也就是右值,所以one + three是一个右值,将调用移动构造函数
    //将返回值(右值)内容的的所有权交给four保管
    cout << "-----------------"<< endl;
    cout << "object one: ";
    one.ShowData();
    cout << "object two: ";
    two.ShowData();
    cout << "object three: ";
    three.ShowData();
    cout << "object four: ";
    four.ShowData();
    cin.get();
    return 0;
}

运行结果也是很有意思

笑死,根本没有调用构造函数

 原因是编译器进行了优化(编译器clang),用g++也是一样的效果

编译推断重载+函数的返回值要赋值给four,直接将数据转给了four的名下。

微软自家的编译器编译

可以看见在重载+号调用结束之后进行了移动构造 

注意:函数中的temp和函数的返回值的地址是不一样的

不得不说,编译器的行为还是试试比较好。

在为引入构造函数之前。用const 限定的引用接受右值

int func(const int &a, const int &b)
{
    return a+b;
}
int sum = func(3,4);

移动语义的意义

上面的程序,如果删除了移动构造函数,可看看其资源消耗

 又进行了一次拷贝构造,即花费了时间,又花费了空间,但是移动构造不会(因为参数是一个右值引用,只是用的对象的别名,虽然这个对象原来也没有名字)。

右值引用还可以进行重载=的操作

强制移动

如果程序要分析一个数组,并且选中其中的一个元素使用,并将数组丢弃掉。虽然数组中的某一个对象是左值,但也能使用一些方法让其作为右值进行移动构造。

可用static_cast<>将对象强制转换为 &&

或者

C++11提供了一种技术

要包含头文件<utility>

使用std::move();

#include <iostream>
#include <utility>

using namespace std;

class Useless
{
private:
    int n;          // number of elements
    char* pc;      // pointer to data
    static int ct;  // number of objects
    void ShowObject() const;
public:
    Useless();
    explicit Useless(int k);
    Useless(int k, char ch);
    Useless(const Useless& f); // regular copy constructor
    Useless(Useless&& f);      // move constructor
    ~Useless();
    Useless operator+(const Useless& f)const;
    Useless& operator=(const Useless& f); // copy assignment
    Useless& operator=(Useless&& f);      // move assignment
    void ShowData() const;
};

int Useless::ct = 0;

Useless::Useless()
{
    ++ct;
    n = 0;
    pc = nullptr;
}

Useless::Useless(int k) : n(k)
{
    ++ct;
    pc = new char[n];
}

Useless::Useless(int k, char ch) : n(k)
{
    ++ct;
    pc = new char[n];
    for (int i = 0; i < n; i++)
        pc[i] = ch;
}

Useless::Useless(const Useless& f) : n(f.n)
{
    ++ct;
    pc = new char[n];
    for (int i = 0; i < n; i++)
        pc[i] = f.pc[i];
}

Useless::Useless(Useless&& f) : n(f.n)
{
    ++ct;
    pc = f.pc;       // steal address
    f.pc = nullptr;  // give old object nothing in return
    f.n = 0;
}

Useless::~Useless()
{
    delete[] pc;
}

//左值引用赋值
Useless& Useless::operator=(const Useless& f)
{
    cout << "copy assignment operator called:\n";
    if (this == &f)
        return *this;
    delete[]pc;
    n = f.n;
    pc = new char[n];   //深拷贝
    for (int i = 0; i < n; i++)
        pc[i] = f.pc[i];
    return *this; //因该返回*this ,因为赋值表达式的值是左值的值
}

Useless& Useless::operator=(Useless&& f)
{
    cout << "move assignment operator called:\n";
    if (this == &f)
        return *this;
    delete[]pc;
    n = f.n;
    pc = new char[n];
    pc = f.pc;
    f.n = 0;
    f.pc = nullptr;
    return *this;
}

Useless Useless::operator+(const Useless& f)const
{
    Useless temp = Useless(n + f.n);
    for (int i = 0; i < n; i++)
        temp.pc[i] = pc[i];
    for (int i = n; i < temp.n; i++)
        temp.pc[i] = f.pc[i - n];
    return temp;
}

void Useless::ShowObject() const
{
    std::cout << "Number of elements: " << n;
    std::cout << " Data address: " << (void*)pc << std::endl;
}

void Useless::ShowData() const
{
    if (n == 0)
        std::cout << "(object empty)";
    else
        for (int i = 0; i < n; i++)
            std::cout << pc[i];
    std::cout << std::endl;
}

int main()
{
    using std::cout;
    {
        Useless one(10, 'x');
        Useless two = one + one;   // calls move constructor
        cout << "object one: ";
        one.ShowData();
        cout << "object two: ";
        two.ShowData();
        Useless three, four;
        cout << "three = one\n";
        three = one;              // automatic copy assignment
        cout << "now object three = ";
        three.ShowData();
        cout << "and object one = ";
        one.ShowData();
        cout << "four = one + two\n";
        four = one + two;         // automatic move assignment
        cout << "now object four = ";
        four.ShowData();
        cout << "four = move(one)\n";
        //
        four = std::move(one);    // forced move assignment
        //
        cout << "now object four = ";
        four.ShowData();
        cout << "and object one = ";
        one.ShowData();
    }
    std::cin.get();
}

运行结果

 可以看见有两个move assignment显示,分别在148和143这两条语句中调用。

特别的,如果没有定义移动赋值,那么move(...)会使用默认的赋值函数

如果没有提供移动构造,而又需要移动构造,那么编译器会自动提供一个,但是是逐一赋值,不是深拷贝

另外的,STL类现在都有移动赋值函数,移动构造函数。 

新的类功能

 特殊的成员函数

1.构造函数

2.析构函数

3.复制构造函数

4.赋值运算符

C++11新增的两个如上

5.移动构造函数

6.移动赋值函数

默认的方法和禁用的方法 

default显示的声明哪个构造函数是默认版本,不能做具体的实现,只是一个空实现

A() = default;

delete可禁用编译器使用特定的方法,虽然也可放在私有作用下,不过这样做更不容易犯错,更加清晰,更加使人理解

#include <iostream>

using namespace std;
class A
{
    int a;
public:
    A() = default;
    void show(double & p)
    {
        cout << "p(double) is " << p << endl;
    }
    void show(int) = delete; //禁止编译器用int的版本
};
int main()
{
    A _a;
    //_a.show(10); //error 10 is a int
    return 0;
}

委托构造函数

有些类的构造函数可能键入大量重复的内容,为了编码更简洁,可用委托构造函数解决。

将当前构造函数的任务委托给另外一个构造函数

#include <iostream>

using namespace std;
class Data
{
public:
    int a;
    int b;
    int c;
public:
    Data(int x, int y, int z) : a(x), b(y), c(z) {}
    Data() : Data(1,2,3) {
        //do other
    }
    Data(int y) : Data(0,y,0)
    {
        //do other thing
    }

};
int main()
{
    Data data(2);
    cout << data.a << data.b << data.c << endl;
    return 0;
}

运行结果

继承构造函数

 C++11提供了能派生类继承基类构造函数的方法

使用using声明可让派生类继承基类的所有构造函数,但不会使用与派生类构造函数冲突的的基类构造函数。

#include <iostream>

using namespace std;
class A
{
public:
    double a;
    A () : a(0) {}
    A (double x) : a(x) {}
};

class B : public A
{
public:
    int b;
    using A::A;
    //共用一个值初始化
    B (double x) : A(3.0*x), b((int)x) {}
};
int main()
{
    B b(3.2);
    cout << b.a <<" " << b.b;
    return 0;
}

运行结果

虚方法的管理

C++11中可使用虚说明符override指出要覆盖一个虚函数,将其放在参数列表后面。如果声明与基类的方法不匹配,将导致编译错误

这减少了程序员犯错误的概率,也许在重写虚方法时,可能由于马虎写成了同名函数,结果时隐藏了基类中的方法而不是重写。

final与override作用相反,它说明了派生类不允许重写基类中的哪些函数,在参数列表后面加上即可 

特别一说的是,override和final并非关键字,而是标识符,编译器根据上下文来判断他们是否有特殊含义。下面的代码完全可以。

    int final = 10;
    double override = 3.14;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值