概述

这一篇简单介绍一些其他的比较实用的特性,如果读者想了解现代C++的全部特性,参考:cpp reference

其他特性

预置和弃置函数default&delete

在 C++11 中引入了 default 和 delete 关键字,允许程序员更加明确地控制类的默认操作(如默认构造函数,拷贝构造函数,拷贝赋值运算符,析构函数等)。

default

default 关键字用于明确地要求编译器生成默认的实现。例如,如果你想要一个类拥有默认的构造函数,你可以这样做:

class Obj {
public:
Obj() = default; // 使用编译器生成的默认构造函数
};

delete

delete 关键字用于禁止编译器生成默认的实现。例如,如果你不希望你的类被拷贝,你可以这样做:

class Obj {
public:
Obj(const Obj&) = delete; // 禁止拷贝构造函数
Obj& operator=(const Obj&) = delete; // 禁止拷贝赋值运算符
};

在这个例子中,如果你尝试拷贝 Obj 的实例,编译器将会报错。

这两个关键字可以让你更明确地控制类的行为,防止编译器生成你不希望的默认操作。

继承和多态的控制final&override

override

override 关键字用于明确表示一个虚函数覆盖了它的基类中的版本。这可以帮助编译器检查你的代码,防止你因为拼写错误或参数不匹配而无意中没有覆盖基类的函数。例如:

class Base {
public:
virtual void foo(int) {}
}; class Derived : public Base {
public:
void foo(int) override; // 明确表示这个函数覆盖了基类的版本
};

final

final 关键字可以用于类或虚函数。当用于类时,它表示这个类不能被继承。例如:

class Base final {};  // 这个类不能被继承

class Derived : public Base {};  // 错误:Base 是 final 的

当 final 用于虚函数时,它表示这个函数在派生类中不能被覆盖。例如:

class Base {
public:
virtual void foo() final; // 这个函数不能被覆盖
}; class Derived : public Base {
public:
void foo(); // 错误:Base::foo 是 final 的
};

这两个关键字可以更明确地控制类的继承和多态行为,防止错误的覆盖或继承。

有作用域的枚举

在 C++11 之前,枚举类型的值可以隐式转换为整数,而且枚举类型的成员在枚举类型的作用域之外是可见的。这可能会导致命名冲突和类型安全问题。

作用域枚举通过使用 enum class 关键字来定义,如下所示:

enum class Color {
Red,
Green,
Blue
};

作用域枚举的成员只能通过作用域解析运算符 :: 来访问,这可以避免命名冲突。例如,不能直接写 Red,而应该写 Color::Red。

此外,作用域枚举的值不能隐式转换为整数,这有助于提高类型安全

列表初始化

C++11 引入了一种新的初始化语法,通常被称为列表初始化或统一初始化。这种语法使用花括号 {} 来初始化对象,可以用于几乎所有的初始化情况。

如:

int a = {5};  // 初始化基本类型
std::string s = {"hello"}; // 初始化类类型
int arr[] = {1, 2, 3, 4, 5}; // 初始化数组
std::vector<int> v = {1, 2, 3, 4, 5}; // 初始化容器
struct Point {int x, y;} p = {5, 10}; // 初始化聚合类型

列表初始化有几个优点:

  1. 统一的语法:列表初始化可以用于所有的初始化情况,包括基本类型、数组、容器、聚合类型等。

  2. 防止窄化转换:列表初始化不允许窄化转换,即从一个类型到另一个类型的转换可能丢失信息。如,试图用浮点数初始化一个整数,或者用一个大的整数初始化一个小的整数,这样的代码将无法通过编译。

int a = {3.14};  // 错误:窄化转换
char c = {300}; // 错误:窄化转换
  1. 初始化类的成员:在类的构造函数的初始化列表中,可以使用列表初始化来初始化类的成员。
class MyClass {
public:
std::vector<int> v;
MyClass() : v{1, 2, 3, 4, 5} {} // 使用列表初始化来初始化类的成员
};

列表初始化是一种非常有用的特性,可以帮助你编写更清晰、更安全的代码。

nullptr 空指针

在C++11之前,我们通常使用NULL来表示空指针。然而,NULL其实就是整数0,这可能会导致一些问题。例如,当函数重载中有一个接受int参数的版本和一个接受指针参数的版本时,如果你传递NULL给这个函数,编译器会选择接受int参数的版本,这可能不是你想要的结果。

为了解决这个问题,C++11引入了nullptr关键字来表示空指针。nullptr的类型是nullptr_t,它可以隐式转换为所有的指针类型,但不能转换为其他的类型。这使得nullptr在函数重载中的行为更符合预期。

下面是一个例子:

void f(int) {
std::cout << "f(int) called" << std::endl;
} void f(char*) {
std::cout << "f(char*) called" << std::endl;
} int main() {
f(NULL); // 输出 "f(int) called"
f(nullptr); // 输出 "f(char*) called"
return 0;
}

在这个例子中,当你传递NULL给f函数时,编译器选择了接受int参数的版本。但是当你传递nullptr给f函数时,编译器选择了接受指针参数的版本。这是因为nullptr的类型是nullptr_t,它可以隐式转换为char*,但不能转换为int。

因此,C++11推荐使用nullptr来表示空指针,而不是NULL或者0。

noexcept 不会抛出异常承诺

noexcept是C++11引入的一个新关键字,用于指定函数是否会抛出异常。如果一个函数被声明为noexcept,那么它保证不会抛出任何异常。如果在运行时该函数确实抛出了异常,那么程序将调用std::terminate来立即结束执行。

如:

void f() noexcept {
// 这个函数保证不会抛出任何异常
} void g() {
throw std::runtime_error("An error occurred"); // 这个函数可能会抛出异常
}

使用noexcept关键字有两个主要的好处:

  1. 性能优化:如果编译器知道一个函数不会抛出异常,那么它可能会生成更优化的代码。例如,一些需要异常安全保证的操作(如对象的移动)在知道不会抛出异常的情况下,可以被编译器优化。

  2. 提供更清晰的接口:通过在函数声明中使用noexcept关键字,你可以明确地告诉调用者该函数不会抛出任何异常。这可以帮助调用者更好地理解函数的行为,并据此来编写代码。

需要注意的是,noexcept是一个承诺,如果声明一个函数为noexcept,那么你需要确保它在任何情况下都不会抛出异常。如果不能保证这一点,最好不要使用noexcept关键字。

另外,你也可以使用noexcept运算符来检查一个表达式是否保证不抛出异常:

static_assert(noexcept(f()));  // 编译时检查f()是否不抛出异常

三路比较(c++ 20)

假设我们有一个 Person 类,它有一个名字和年龄属性。我们想要比较两个 Person 对象,首先比较他们的名字,如果名字相同,再比较他们的年龄。

在C++20中,我们可以使用三路比较运算符 <=> 来实现这个比较逻辑

#include <string>
#include <compare> struct Person {
std::string name;
int age; auto operator<=>(const Person& other) const {
if (auto cmp = name <=> other.name; cmp != 0) {
return cmp;
}
return age <=> other.age;
}
}; #include <iostream> int main() {
Person alice{"Alice", 20};
Person bob{"Bob", 20};
Person charlie{"Charlie", 25}; std::cout << ((alice <=> bob) < 0 ? "Alice < Bob" : "Alice >= Bob") << std::endl;
std::cout << ((alice <=> charlie) < 0 ? "Alice < Charlie" : "Alice >= Charlie") << std::endl;
std::cout << ((bob <=> charlie) < 0 ? "Bob < Charlie" : "Bob >= Charlie") << std::endl;
}

operator<=> 首先比较 name,如果 name 不相同,就返回 name 的比较结果;如果 name 相同,就比较 age,并返回 age 的比较结果。我们也可以使用auto operator<=>(const Person& other) const = default让编译器生成,它会按照成员的声明顺序比较每个成员。

值得一提的特性

  • alignof 与 alignas 内存对齐相关
  • static_assert 静态断言
  • c++17:if和switch语句中进行初始化,如if (int i = f(); i > 10) {}、switch (int i = f(); i) {}
  • 聚合初始化(花括号),c++20允许实用圆括号

C++20 一些新的、未实践但感觉有用的特性

  • 协程
  • 模块
  • 限定与概念
  • 缩略函数模板
  • DR :数组 new 可推导数组长度

现代C++(Modern C++)基本用法实践:N、其他零散的常用特性的更多相关文章

  1. Linux中sed的用法实践

    Linux中sed的用法实践 参考资料:https://www.cnblogs.com/emanlee/archive/2013/09/07/3307642.html http://www.fn139 ...

  2. Java map 详解 - 用法、遍历、排序、常用API等

    尊重原创: http://www.cnblogs.com/lzq198754/p/5780165.html 概要: java.util 中的集合类包含 Java 中某些最常用的类.最常用的集合类是 L ...

  3. Linux find命令的用法实践

    一.find命令简介 Linux下find命令在目录结构中搜索文件,并执行指定的操作.Linux下find命令提供了相当多的查找条件,功能很强大.由于find具有强大的功能,所以它的选项也很多,其中大 ...

  4. PHP命令空间namespace及use的用法实践总结

    以下皆为本人自我理解内容,如有失误之处,请多多包涵. 文章大纲: 使用namespace的目的 namespace的使用方法 使用use的目的 use的使用方法 使用namespace的目的: 团队合 ...

  5. CountDownLatch用法实践

    项目中写多线程时,需要判断所有线程是否执行完毕,所以想到了添加累加器来判断.这个累加器使用什么变量,找到了以下2种方式. 1. 类似AtomicInteger这种提供原子操作的类型AtomicInte ...

  6. java反射-- Field 用法实践

    java 反射是一种常用的技术手段, 通过加载类的字节码的方式来获取相关类的一些信息 比如成员变量, 成员方法等. Field 是什么呢? field 是一个类, 位于java.lang.reflec ...

  7. ASP.NET Web API实践系列04,通过Route等特性设置路由

    ASP.NET Web API路由,简单来说,就是把客户端请求映射到对应的Action上的过程.在"ASP.NET Web API实践系列03,路由模版, 路由惯例, 路由设置"一 ...

  8. C++11常用特性介绍——for循环新用法

    一.for循环新用法——基于范围的for循环 for(元素类型 元素对象 : 容器对象) { //遍历 } 1)遍历字符串 std::string str = "hello world&qu ...

  9. C++11常用特性介绍——nullptr关键字及用法

    一.nullptr关键字及用法 1)NULL的二义性 void func(int) {} void func(int*) {} 当函数调用func(NULL)时会执行哪个函数呢? 先看C++对NULL ...

  10. HTML meta标签的用法及head中的一些常用标签

    meta是用来在HTML文档中模拟HTTP协议的响应头报文.meta主要为分HTTP标头信息(HTTP-EQUIV)和页面描述信息(NAME)标头信息包括文档类型.字符集.语言等浏览器正确显示网页的信 ...

随机推荐

  1. python:冒泡排序(Bubble Sort)超详细教程!

    关于排序,真的非常的重要.数据可以从小到大排序,也可以从大到小排序.这样对于一个有序的数据,我们处理起来就很方便,这对于我们的工作帮助是很大的. 那么你拿到一组无序的数据,你将要如何去处理它呢? 冒泡 ...

  2. Simulation-计算统计——Monte Carlo

    Monte Carlo Integration 找到原函数,再计算 无法找到原函数,MC积分 Assume that we can generate \(U_1, . . . , U_n \sim U ...

  3. 在Winform分页控件中集成保存用户列表显示字段及宽度调整设置

    在Winform的分页控件里面,我们提供了很多丰富的功能,如常规分页,中文转义.导出Excel.导出PDF等,基于DevExpress的样式的分页控件,我们在其上面做了不少封装,以便更好的使用,其中就 ...

  4. 在docker容器里,ffmpeg给视频文件内嵌字幕文件,不生效,如何解决?

    用ffmpeg命令,发现执行成功,但视频文件就是没有字幕.看不出问题出现在什么地方.后来直接用ffmpeg添加水印命令测试,发现是缺少字体文件,如下图所示: 报Fontconfig error: Ca ...

  5. 2022-08-05:以下go语言代码输出什么?A:65, string;B:A, string;C:65, int;D:报错。

    2022-08-05:以下go语言代码输出什么?A:65, string:B:A, string:C:65, int:D:报错. package main import ( "fmt&quo ...

  6. 2022-03-16:给你一个整数 n ,表示有 n 个专家从 0 到 n - 1 编号。 另外给一个下标从 0 开始的二维整数数组 meetings , 其中 meetings[i] = [xi,

    2022-03-16:给你一个整数 n ,表示有 n 个专家从 0 到 n - 1 编号. 另外给一个下标从 0 开始的二维整数数组 meetings , 其中 meetings[i] = [xi, ...

  7. vue全家桶进阶之路23:Element UI

    Element UI 是一套基于 Vue.js 的组件库,它提供了一系列常用的 UI 组件,包括表单.弹窗.布局.导航等等.Element UI 的设计风格简洁.易用.美观,且易于定制. Elemen ...

  8. 【使用git之旅】

    前言 在学习各种语言的时候我总喜欢把例子改成有自己想法并且有趣的程序, 但是时间一长,我发现在本地管理很麻烦,于是乎想到了github和gitee, 然后昨晚一时兴起,就开始了学习,开个博客记录一下我 ...

  9. 2014年蓝桥杯C/C++大学B组省赛真题(奇怪的分式)

    题目描述: 上小学的时候,小明经常自己发明新算法.一次,老师出的题目是:1/4 乘以 8/5 小明居然把分子拼接在一起,分母拼接在一起,答案是:18/45 (参见图1.png)老师刚想批评他,转念一想 ...

  10. CSS文本过长时设置省略号

    写页面时很多时候会遇到一个容器中文本内容超出,使用overflow : hidden;,但是跟用户体验不太友好,设置overflow : scroll; 会出现滚动看着也不爽,所以我就想使用css 设 ...