IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> C++11新语法特性(列表初始化/变量类型推导/范围for/final&override/默认成员函数控制) -> 正文阅读

[C++知识库]C++11新语法特性(列表初始化/变量类型推导/范围for/final&override/默认成员函数控制)

零、前言

本章将开始学习C++11的新语法特性,主要是一些比较常用的语法

一、C++11简介

  • 发展历程:
  1. 在2003年C++标准委员会曾经提交了一份技术勘误表(简称TC1),使得C++03这个名字已经取代了C++98称为C++11之前的最新C++标准名

  2. 不过由于TC1主要是对C++98标准中的漏洞进行修复,语言的核心部分则没有改动,因此人们习惯性的把两个标准合并称为C++98/03标准

  3. 从C++0x到C++11,C++标准10年磨一剑,第二个真正意义上的标准珊珊来迟。相比于C++98/03,C++11则带来了数量可观的变化,其中包含了约140个新特性,以及对C++03标准中约600个缺陷的修正,这使得C++11更像是从C++98/03中孕育出的一种新语言。

  4. 相比较而言,C++11能更好地用于系统开发和库开发、语法更加泛华和简单化、更加稳定和安全,不仅功能更强大,而且能提升程序员的开发效率

二、列表初始化

  • 背景引入:

在C++98中,标准允许使用花括号{}对数组元素进行统一的列表初始值设定

  • 示例:
int array1[] = {1,2,3,4,5};
int array2[5] = {0};

注:对于一些自定义的类型,却无法使用这样的初始化

1、内置类型列表初始化

C++11扩大了用大括号括起的列表(初始化列表)的使用范围,使其可用于所有的内置类型和用户自定义的类型,使用初始化列表时,可添加等号(=),也可不添加

  • 示例:
int main()
{
    // 内置类型变量
    int x1 = {10};
    int x2{10};
    int x3 = 1+2;
    int x4 = {1+2};
    int x5{1+2};
    // 数组
    int arr1[5] {1,2,3,4,5};
    int arr2[]{1,2,3,4,5};
    // 动态数组,在C++98中不支持
    int* arr3 = new int[5]{1,2,3,4,5};
    // 标准容器
    vector<int> v{1,2,3,4,5};
    map<int, int> m{{1,1}, {2,2,},{3,3},{4,4}};
    return 0;
}
  • 效果:
image-20220505203928311

注:列表初始化可以在{}之前使用等号,其效果与不使用=没有什么区别

2、自定义类型列表初始化

  1. 标准库支持单个对象的列表初始化
class Pointer
{
public:
     Pointer(int x = 0, int y = 0) : _x(x), _y(y)
    {}
private:
    int _x;
    int _y;
};
int main()
{
    Pointer p{ 1, 2 };
    //等同于与调用构造函数 
    //Pointer p( 1, 2 );
    return 0;
}
  1. 多个对象的列表初始化

多个对象想要支持列表初始化,需要实现initializer_list类型参数的构造函数

  • 示例:
#include <initializer_list>
template<class T>
class Vector {
public:
	// ...
	Vector() : _capacity(0), _size(0){}
	Vector(initializer_list<T> l) : _capacity(l.size()), _size(0)
	{
		_array = new T[_capacity];
		for (auto e : l)
			_array[_size++] = e;
	}
	Vector<T>& operator=(initializer_list<T> l) {
		delete[] _array;
		size_t i = 0;
		for (auto e : l)
			_array[i++] = e;
		return *this;
	}
	// ...
private:
	T* _array;
	size_t _capacity;
	size_t _size;
};
int main()
{
	Vector<int> v1{ 1,2,3,4 };
	Vector<int> v2;
	v2 = { 1,2,3,4,5 };
	return 0;
}
  • 效果:
image-20220505205611142

注:initializer_list是系统自定义的类模板,该类模板中主要有三个方法:begin()**、**end()迭代器以及获取区间中元素个数的方法size()

三、变量类型推导

1、auto类型推导

在定义变量时,必须先给出变量的实际类型,编译器才允许定义,但有些情况下可能不知道需要实际类型怎么给,或者类型写起来特别复杂

  • 示例:
void test1()
{
    short a = 32670;
    short b = 32670;
    // c如果给成short,会造成数据丢失,如果能够让编译器根据a+b的结果推导c的实际类型,就不会存在问题
    short c = a + b;
    cout<<c<<endl;
}
void test2()
{
    std::map<std::string, std::string> m{
        {"apple", "苹果"}, {"banana","香蕉"}
    };
    // 使用迭代器遍历容器, 迭代器类型太繁琐
    std::map<std::string, std::string>::iterator it = m.begin();
    while(it != m.end())
    {
        cout<<it->first<<" "<<it->second<<endl;
        ++it;
    }
}

C++11中,可以使用auto来根据变量初始化表达式类型推导变量的实际类型,可以给程序的书写提供许多方便。将程序中c与it的类型换成auto,程序可以通过编译,而且更加简洁

  • 示例:
void test3()
{
    short a = 32670;
    short b = 32670;
    // c如果给成short,会造成数据丢失,如果能够让编译器根据a+b的结果推导c的实际类型,就不会存在问题
    auto c = a + b;
    cout<<c<<endl;
    std::map<std::string, std::string> m{
        {"apple", "苹果"}, {"banana","香蕉"}
    };
    // 使用迭代器遍历容器, 迭代器类型太繁琐
    auto it = m.begin();
    while(it != m.end())
    {
        cout<<it->first<<" "<<it->second<<endl;
        ++it;
    }
}
  • 效果:
image-20220505210715464

2、decltype类型推导

  • 为什么需要decltype:
  1. auto使用的前提是:必须要对auto声明的类型进行初始化,否则编译器无法推导出auto的实际类型
  2. 但有时候可能需要根据表达式运行完成之后结果的类型进行推导,因为编译期间,代码不会运行,此时auto也就无能为力
  • 示例:
template<class T1, class T2>
T1 Add(const T1& left, const T2& right)
{
	return left + right;
}

注:如果能用加完之后结果的实际类型作为函数的返回值类型就不会出错,但这需要程序运行完才能知道结果的实际类型,即RTTI(Run-Time Type Identification 运行时类型识别)

  • C++98中确实已经支持RTTI:
  1. typeid只能查看类型不能用其结果类定义类型
  2. dynamic_cast只能应用于含有虚函数的继承体系中

注:运行时类型识别的缺陷是降低程序运行的效率

  • decltype的使用:

decltype是根据表达式的实际类型推演出定义变量时所用的类型

  1. 推演表达式类型作为变量的定义类型
  • 示例:
int main()
{
	int a = 10000000000000;
	int b = 10000000000000;
	// 用decltype推演a+b的实际类型,作为定义c的类型
	decltype(a + b) c;
    cout<<typeid(c).name()<<endl;
	return 0;
}
  1. 推演函数返回值的类型
  • 示例:
void* GetMemory(size_t size)
{
	return malloc(size);
}
int main()
{
	// 如果没有带参数,推导函数的类型
	cout << typeid(decltype(GetMemory)).name() << endl;
	// 如果带参数列表,推导的是函数返回值的类型,注意:此处只是推演,不会执行函数
	cout << typeid(decltype(GetMemory(0))).name() << endl;
	return 0;
}
  • 效果:
image-20220505212540036

四、范围for循环

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

  • 示例:
int main(void)
{
    vector<int> v = { 1, 2, 3, 4, 5, 6 };
    for (auto it = v.begin(); it != v.end(); ++it)
    {
        cout << *it << " ";
    }
    cout << endl;
    for (auto& value : v)
    {
        cout << value << " ";
    }
    cout << endl;
    return 0;
}
  • 效果:

image-20220505213557529

  • C++11基于范围的for循环语法格式:
for (declaration : expression)
{
    // 循环体
}
  • 解释:

declaration 表示遍历声明,在遍历过程中,当前被遍历到的元素会被存储到声明的变量中。expression 是要遍历的对象,它可以是 表达式容器数组初始化列表

五、final和override

1、final

C++ 中增加了 final 关键字来限制某个类不能被继承,或者某个虚函数不能被重写。如果使用 final 修饰函数,只能修饰虚函数,并且要把final关键字放到类或者函数的后面

  1. 修饰函数:

如果使用 final 修饰函数,只能修饰虚函数,这样就能阻止子类重写父类的这个函数了:

class Base
{
public:
    virtual void test() final
    {
        cout << "Base class..."<<'\n';
    }
    virtual void test2()
    {
        cout << "Base class test2..."<<'\n';
    }
};
class Child : public Base
{
public:
    //带有final, 无法重写
    void test() 
    {
        cout << "Child class..." << '\n';
    }
    void test2()
    {
        cout << "Child class test2..." << '\n';
    }
};
  • 效果:
image-20220505222246375
  1. 修饰类

使用 final 关键字修饰过的类是不允许被继承的,也就是说这个类不能有派生类

  • 示例:
class Base final
{
public:
    virtual void test()
    {
        cout << "Base class..." << '\n';
    }
    virtual void test2()
    {
        cout << "Base class test2..." << '\n';
    }
};

class Child : public Base 
{
public:
    void test() 
    {
        cout << "Child class..." << '\n';
    }
    void test2()
    {
        cout << "Child class test2..." << '\n';
    }
};
  • 效果:
image-20220505222604629

2、override

override 关键字确保在派生类中声明的重写函数与基类的虚函数有相同的签名,同时也明确表明将会重写基类的虚函数。这样就可以保证重写的虚函数的正确性,也提高了代码的可读性,和 final 一样这个关键字要写到方法的后面

  • 示例:
class Base
{
public:
    void test()
    {
        cout << "Base class...";
    }
};
class Child : public Base
{
public:
    void test() override
    {
        cout << "Child class...";
    }
};
  • 效果:
image-20220505223101527

注:使用了 override 关键字之后,假设在重写过程中因为误操作,写错了函数名或者函数参数或者返回值编译器都会提示语法错误

六、默认成员函数控制

  • 引入背景:
  1. 在C++中对于空类编译器会生成一些默认的成员函数,如果在类中显式定义了,编译器将不会重新生成默认版本
  2. 有时候这样的规则可能被忘记,最常见的是声明了带参数的构造函数,必要时则需要定义不带参数的版本以实例化无参的对象。而且有时编译器会生成,有时又不生成,容易造成混乱,于是C++11让程序员可以控制是否需要编译器生成
  1. 显式缺省函数

在C++11中,可以在默认函数定义或者声明时加上=default,从而显式的指示编译器生成该函数的默认版本,用=default修饰的函数称为显式缺省函数

  • 示例:
class A
{
public:
	A(int a) : _a(a)
	{}
	// 显式缺省构造函数,由编译器生成
	A() = default;
	// 在类中声明,在类外定义时让编译器生成默认赋值运算符重载
	A& operator=(const A& a);
private:
	int _a;
};
A& A::operator=(const A& a) = default;
int main()
{
	A a1(10);
	A a2;
	a2 = a1;
	return 0;
}
  1. 删除默认函数
  1. 如果能想要限制某些默认函数的生成,在C++98中,是该函数设置成private,并且不给定义,这样只要其他人想要调用就会报错
  2. 在C++11中更简单,只需在该函数声明加上=delete即可,该语法指示编译器不生成对应函数的默认版本,称=delete修饰的函数为删除函数
  • 示例:
class A
{
public:
	A(int a) : _a(a)
	{}
	// 禁止编译器生成默认的拷贝构造函数以及赋值运算符重载
	A(const A&) = delete;
	A& operator=(const A&) = delete;
private:
	int _a;
};
int main()
{
	A a1(10);
	// 编译失败,因为该类没有拷贝构造函数
	A a2(a1);
	// 编译失败,因为该类没有赋值运算符重载
	A a3(20);
	a3 = a2;
	return 0;
}

注:避免删除函数和explicit一起使用

能想要限制某些默认函数的生成,在C++98中,是该函数设置成private,并且不给定义,这样只要其他人想要调用就会报错

  1. 在C++11中更简单,只需在该函数声明加上=delete即可,该语法指示编译器不生成对应函数的默认版本,称=delete修饰的函数为删除函数
  • 示例:
class A
{
public:
	A(int a) : _a(a)
	{}
	// 禁止编译器生成默认的拷贝构造函数以及赋值运算符重载
	A(const A&) = delete;
	A& operator=(const A&) = delete;
private:
	int _a;
};
int main()
{
	A a1(10);
	// 编译失败,因为该类没有拷贝构造函数
	A a2(a1);
	// 编译失败,因为该类没有赋值运算符重载
	A a3(20);
	a3 = a2;
	return 0;
}

注:避免删除函数和explicit一起使用

  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2022-05-11 16:14:52  更:2022-05-11 16:16:36 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/23 18:44:12-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码