c++11新特性(代码片段)

mr.chenyuelin mr.chenyuelin     2023-02-04     349

关键词:

文章目录

NULL和nullptr

nullptr的出现以避免将空指针NULL转为整数0带来的错误.

auto和decltype

都是从表达式的类型推断出要定义的变量类型,但是decltype但是不想用该表达式的值初始化变量(初始化可以用auto)

花括号初始化

int values[]1, 2, 3;
vector<int> v2, 3, 5, 7, 11, 13, 17;
vector<string> cities"Berlin", "New York", "London", "Braunschweig" "Cairo", "Cologne";
complex<double> c4.0, 3.0; 	// 等价于 c(4.0, 3.0)

功能:
1.值初始化,未定义的基础数据类型的变量值设为0(或nullptr).

int i;		// i has undefined value
int j;	// j is initialized by 0
int* p;		// P has undefined value
int* q;	// q is initialized by nullptr

2.当自动类型转换可能使变量降低精度时报错.

int x1(5.3);		// OK, but OUCH: xl becomes 5
int x2 = 5.3;		// OK, but OUCH: x2 becomes 5
int x35.0;		// ERROR: narrowing
int x4 = 5.3;		// ERROR: narrowing
char c17;			// OK: even though 7 is an int, this is not narrowing
char c299999;		// ERROR: narrowing (if 9999 doesn 'fut into a char)
std::vector<int> v11, 2, 4, 5;		// OK
std::vector<int> v21, 2.3, 4, 5.6;	// ERROR: narrowing

底层依赖于模板类initializer_list,该类封装了一个array<T,n>.调用函数时该array内的元素可被编译器分解逐一传递给函数.若函数参数是initializer_list,则传入的数据不会被拆解.

void print(std::initializer_list<int> vals) 
    for (auto p = vals.begin(); p != vals.end(); ++p)  //a list of values
        std::cout << *p << endl;
    


print(12, 3, 5, 7, 11, 13, 17); 		// pass a list of values to print()

若函数同时有接收多个参数的重载版本和接收initializer list的重载版本,则优先调用接收initializer list的重载版本.

class P 
public:
    // 有两个重载版本的构造函数,uniform initialization时优先调用接收initializer list的重载版本
    P(int a, int b) 
        cout << "P(int, int), a=" << a << ", b=" << b << endl;
    

    P(initializer_list<int> initlist) 
        cout << "P(initializer list<int>), values= ";
        for (auto i : initlist)
            cout << i << ' ';
        cout << endl;
    
;

P p(77, 5);		// P(int, int), a=77, b=5
P q77, 5;		// P(initializer list<int>), values= 77 5
P r77, 5, 42; // P(initializer list<int>), values= 77 5 42
P s = 77, 5;	// P(initializer list<int>), values= 77 5

STL中的大部分容器和算法相关函数均有接收initializer list的重载版本

=default、=delete

使用=default使得编译给类加上默认的构造函数、析构函数、拷贝构造函数、拷贝赋值函数、移动构造函数等.
=delete(或简写为=0)表示删除该函数,使得该类不具有对应的构造、析构、拷贝构造、拷贝赋值、析构等功能.

noexcept

修饰函数代表不会抛出异常

 MyString &operator=(MyString &&str) noexcept 
        ++MAsgn;
        if (this != &str) 
            if (_data) delete _data;
            _len = str._len;
            _data = str._data; //MOVE!
            str._len = 0;
            str._data = nullptr; // 将传入对象的_data指针设为nullptr,防止析构函数多次delete同一根指针
        
        return *this;
    

override

重写的话,父类的成员函数为virtual修饰,且子类函数与父类函数完全一致

使用关键字override好处:当子类漏写了虚函数重写的某个苛刻条件,也可以通过编译器的报错,快速改正错误

class Base 
public:
    virtual void mf1() const;
    virtual void mf2(int x);
    virtual void mf3() &;
    void mf4() const;    // is not declared virtual in Base
;
class Derived: public Base 
public:
    virtual void mf1() override;
    virtual void mf2(unsigned int x) override;
    virtual void mf3() && override;
    virtual void mf4() const override;
;

所以override加不加不影响子类重写父类

final

修饰类,该类不会被继承,修饰函数,该函数不会被重写

右值引用

用途:加快容器操作

右值是临时对象,如数值1,函数返回值等

注意:右值不能放等号左边,只能放等号右边

当右值出现在赋值运算符=的右侧时,我们认为对其资源进行偷取/搬移(move)而非拷贝(copy)是合理的,依次:

1.必须有语法让我们在调用端告诉编译器这是一个右值.

2.必须有语法让我们在被调用端写出一个专门处理右值的移动赋值函数.

专门处理右值的函数使用value_type&&声明参数:

iterator insert(const_iterator __position, const value_type& __x);
iterator insert(const_iterator __position, value_type&& __x);

我们是insert是先去调用一下中介的一个函数,可能出现改变变量的可变性和左值右值等性质,

使用std::forward()函数可以完美转交变量,不改变其可变性和左值右值等性质.

// 函数process的两个重载版本,分别处理参数是左值和右值的情况
void process(int &i) 
    cout << "process(int&):" << i << endl;

void process(int &&i) 
    cout << "process(int&&):" << i << endl;


// 中间转交函数forward使用std::forward()转交变量
void forward(int &&i) 
    cout << "forward(int&&):" << i << ", ";
    process(std::forward<int>(i));


forward(2);           	// forward(int&&):2, process(int&&):2	(临时变量作左值传给forward函数,forward函数体内使用std::forward函数包装变量,保留其作为右值的性质)
forward(std::move(a));  // forward(int&&):0, process(int&&):0	(临时变量作左值传给forward函数,forward函数体内使用std::forward函数包装变量,保留其作为右值的性质)

std::move()函数可将左值转为右值来传递

接下来就是我们的移动构造函数移动赋值函数

#include <cstring>

class MyString 
public:
    static size_t DCtor;    // 累计默认构造函数调用次数
    static size_t Ctor;     // 累计构造函数调用次数
    static size_t CCtor;    // 累计拷贝构造函数调用次数
    static size_t CAsgn;    // 累计拷贝赋值函数调用次数
    static size_t MCtor;    // 累计移动构造函数调用次数
    static size_t MAsgn;    // 累计移动赋值函数调用次数
    static size_t Dtor;     // 累计析构函数调用次数
private:
    char *_data;
    size_t _len;

    void _init_data(const char *s) 
        _data = new char[_len + 1];
        memcpy(_data, s, _len);
        _data[_len] = '\\0';
    

public:
    // 默认构造函数
    MyString() : _data(nullptr), _len(0)  ++DCtor; 

	// 构造函数
    MyString(const char *p) : _len(strlen(p)) 
        ++Ctor;
        _init_data(p);
    

    // 拷贝构造函数
    MyString(const MyString &str) : _len(str._len) 
        ++CCtor;
        _init_data(str._data);
    

    // 拷贝赋值函数
    MyString &operator=(const MyString &str) 
        ++CAsgn;
        if (this != &str) 
            if (_data) delete _data;

            _len = str._len;
            _init_data(str._data); //COPY!
        
        return *this;

    

    // 移动构造函数
    MyString(MyString &&str) noexcept : _data(str._data), _len(str._len) 
        ++MCtor;
        str._len = 0;
        str._data = nullptr; 	// 将传入对象的_data指针设为nullptr,防止析构函数多次delete同一根指针
    

	// 移动赋值函数
    MyString &operator=(MyString &&str) noexcept 
        ++MAsgn;
        if (this != &str) 
            if (_data) delete _data;
            _len = str._len;
            _data = str._data; //MOVE!
            str._len = 0;
            str._data = nullptr; // 将传入对象的_data指针设为nullptr,防止析构函数多次delete同一根指针
        
        return *this;
    

    //dtor
    virtual ~MyString() 
        ++Dtor;
        if (_data)
            delete _data;
    
;

size_t MyString::DCtor = 0;
size_t MyString::Ctor = 0;
size_t MyString::CCtor = 0;
size_t MyString::CAsgn = 0;
size_t MyString::MCtor = 0;
size_t MyString::MAsgn = 0;
size_t MyString::Dtor = 0;

其实就是移动构造函数就是让当前指针指向临时指针的同一块空间,然后将临时指针置空,防止析构函数delete释放同一块内存空间,移动赋值函数就只是加了检测自我赋值的一个操作罢了

特别注意:移动构造函数和移动赋值函数参数列表之后必须加noexcept关键字,否则不会调用

容器测试:

1.在插入元素部分,只有vector容器的速度受元素是否movable影响大,这是因为只有容器vector在增长过程中会发生复制.

2.对于所有容器,其移动构造函数都远快于其拷贝构造函数,容器vector的移动复制函数仅仅发生了指针的交换,未发生元素的复制.

lambda表达式

写了一个函数但没有名字

[] 
    std::cout << "hello lambda" << std::endl;
;

// 用作变量
auto l = [] 
    std::cout << "hello lambda" << std::endl;
;
l();

// 直接执行
[] 
    std::cout << "hello lambda" << std::endl;
();

int id = 0;
auto f = [id]() mutable 
    std::cout << "id:" << id << std::endl;
    ++id;
;
id = 42;
f();							// id:0
f();							// id:1
f();							// id:2
std::cout << id << std::endl;	// 42

lambda函数使用时相当于仿函数(functor)[…]中传入的对象相当于为仿函数的成员变量.

class Functor 
    private:
    int id; // copy of outside id
    public:
    void operator()() 
        std::cout << "id: " << id << std::endl;
        ++id; // OK
    
;
Functor f;

一般用于当函数功能比较简单,比如修改sort函数排序规则及其它函数

// lambda函数充当predict谓词
vector<int> vi5, 28, 50, 83, 70, 590, 245, 59, 24;
int x = 30;
int y = 100;
remove_if(vi.begin(), vi.end(),
          	[x, y](int n)  return x < n && n < y; );

c++11新特性:19——c++11右值引用(代码片段)

...础上,C++11标准对C++语言增添了约140个新特性。本节要讲的右值引用就是众多新特性中的一个,同时也是最重要的特性之一。很多初学者都感 查看详情

c#11新特性整理(代码片段)

假期中有时间,整理了C#11的各个新特性,简单分享给大家。一、使用VSCode新建一个.NET7.0的Console工程<ProjectSdk="Microsoft.NET.Sdk"><PropertyGroup><OutputType>Exe</OutputType><TargetFrame 查看详情

c++11新特性详解(代码片段)

C++11新特性详解C++11简介列表初始化{}初始化std::initializer_list声明auto(在C++中不支持C语言中原来auto的用法)decltypenullptr范围for循环STL中一些变化右值引用和移动语义左值引用和右值引用左值引... 查看详情

c++11新特性详解(代码片段)

C++11新特性详解C++11简介列表初始化{}初始化std::initializer_list声明auto(在C++中不支持C语言中原来auto的用法)decltypenullptr范围for循环STL中一些变化右值引用和移动语义左值引用和右值引用左值引... 查看详情

c#11新特性:泛型attribute(代码片段)

之前使用JsonConverterAttribute,我们可以为任意类型自定义Json序列化。例如:[JsonConverter(typeof(UserJsonConverter))]public class User    public string Name  get; set;     public override string ToString()       查看详情

c++11新特性(代码片段)

文章目录NULL和nullptrauto和decltype花括号初始化=default、=deletenoexceptoverridefinal右值引用lambda表达式NULL和nullptrnullptr的出现以避免将空指针NULL转为整数0带来的错误.auto和decltype都是从表达式的类型推断出要定义的变量类型,... 查看详情

c++11新特性(代码片段)

文章目录NULL和nullptrauto和decltype花括号初始化=default、=deletenoexceptoverridefinal右值引用lambda表达式NULL和nullptrnullptr的出现以避免将空指针NULL转为整数0带来的错误.auto和decltype都是从表达式的类型推断出要定义的变量类型,... 查看详情

c++11新特性之autodecltype(代码片段)

C++11新特性之auto、decltype1.类型推导auto根据等号右边类型推导变量的类型。staticautob=1;会报错。因为类只能初始化静态常量(staticconst)auto的使用必须马上初始化,否则无法推导出类型auto在一行定义多个变量时,各... 查看详情

c/c++c++11新特性:=default和=delete(代码片段)

...档显式默认设置的函数和已删除的函数C++11标准新特性:Defaulted和Deleted函数C++11中值得关注的几大变化(详解)C++11以前,我们习惯将拷贝构造函数和拷贝赋值函数设置为private来禁止类的拷贝行... 查看详情

逐个使用c++11新特性(代码片段)

C++11auto&decltypeauto:根据变量初始值来推导变量类型,并用右值初始化变量。decltype:从表达式推导出类型,并将变量定义为该类型,但不用表达式的值初始化该变量。这里要注意下:decltype(i)--是i的类型,而decltype((i))就是引... 查看详情

c_cppc++11标准新特性:defaulted和deleted函数(代码片段)

查看详情

c++11类的新语法和特性(代码片段)

sizeof计算类成员大小sizeof可以直接用于获取Class::member的大小,而不用通过Class的实例获取。classApublic:longlongllint=0;;cout<<sizeof(A::llint)<<endl;上述测试输出类A的longlong类型的成员占用8个字节。default关键字扩展新标准... 查看详情

c++11新特性:函数对象包装器(代码片段)

函数对象包装器std::functionstd::bind和std::placeholder总结std::function​C++11std::function是一种通用、多态的函数封装,它的实例可以对任何可以调用的目标实体进行存储、复制和调用操作,它也是对C++中现有的可调用... 查看详情

c++11新特性:23——c++11完美转发及其实现(代码片段)

原文地址:http://c.biancheng.net/view/vip_8695.htmlC++11标准为C++引入右值引用语法的同时,还解决了一个C++98/03标准长期存在的短板,即使用简单的方式即可在函数模板中实现参数的完美转发。那么,什... 查看详情

c++11新特性:变长参数模板详解(代码片段)

这里写目录标题C++11变长参数模板变长函数参数包如何解参数包sizeof()获得函数参数个数递归模板函数变参模板展开结论C++11变长参数模板在C++11之前,无论是类模板还是函数模板,都只能按其指定的样子... 查看详情

c++11精要:部分语言特性(代码片段)

...仅是对并发的支持,还有许多新程序库和C++新特性。对于线程库和本书其他章节涉及的某些C++新特性,本附录给出了简要概览。虽然这些特性都与并发功能没有直接关系(thread_local除外,见A.8节)&... 查看详情

c++11常用新特性(代码片段)

神级编程网站,堪称程序员的充电站,我给你找好了不能错过_程序员编程指南的博客-CSDN博客nullptrnullptr出现的目的是为了替代NULL。在某种意义上来说,传统C++会把NULL、0视为同一种东西,这取决于编译器如... 查看详情

c++11常用新特性(代码片段)

神级编程网站,堪称程序员的充电站,我给你找好了不能错过_程序员编程指南的博客-CSDN博客nullptrnullptr出现的目的是为了替代NULL。在某种意义上来说,传统C++会把NULL、0视为同一种东西,这取决于编译器如... 查看详情