当前位置: 首页 > article >正文

Project #0 - C++ Primer前置知识学习

这次的Lab0主要以熟悉C++ 11和C++ 17的新特性。这里先把相关新特性分析一下。

目录

一、Doxygen注释

二、函数签名分析

三、构造函数与delete、explicit

四、static constexpr 解释

五、各种cast转换(重点)

5.1 const_cast与reinterpret_cast

5.2 static_cast与dynamic_cast

何时使用 static_cast 和 dynamic_cast?

总结

六、几种整形类总结


一、Doxygen注释

#pragma once

  • 作用:#pragma once 是一种常用的预处理指令,通常用在头文件中,表示该头文件在同一编译单元中只会被包含一次。
  • 解释:它告诉编译器,如果头文件已经被包含过,则不要再重复包含。这样可以避免重复定义和提高编译效率。它的功能和 #ifndef、#define 和 #endif 组合的作用相似,但语法更加简洁,且一般认为性能更好。

@brief

  • 作用:@brief 是 Doxygen(一个文档生成工具)用于文档注释的标签之一。
  • 解释:@brief 标签用于简要描述函数、类、变量等的功能。Doxygen 会根据这个注释生成文档并展示函数或类的简短描述。在本代码中,@brief 被用来为类和函数提供简要的描述,帮助开发者理解代码的功能。

@todo

  • 作用:@todo 也是 Doxygen 的标签之一,用于标记尚未完成或需要改进的代码部分。
  • 解释:当代码中有开发者希望未来进行改进或补充的地方时,可以使用 @todo 标注。Doxygen 会在生成的文档中列出所有的 @todo 标签,提醒开发者这些部分尚未完成。

@param[in]

  • @param 是 Doxygen 的注释标记,用来解释函数的参数。
  • [in] 表示这个参数是 输入参数,即在函数内部不会被修改或更新。它的值是由调用者传递的,函数可以使用该值,但不负责修改它。

二、函数签名分析

using hash_t = std::size_t;

  • 这个 using 语句并不是宏定义,而是 C++11 引入的 类型别名。它为 std::size_t 类型创建了一个新的别名 hash_t。这样,之后你可以在代码中使用 hash_t 来表示 std::size_t,提高代码的可读性和抽象层次。
auto ComputeBinary(const hash_t &hash) const -> std::bitset<BITSET_CAPACITY>;

·  const:表示这是一个常量成员函数,不能修改类的成员变量,只能读取它们。

·  const hash_t &hash:这是函数的参数,表示一个常量引用类型的 hash_t(即 std::size_t)对象。const 表示参数 hash 在函数内部不会被修改。

auto 关键字

  • 在函数声明中,auto 表示编译器会根据函数体的返回值自动推导出返回类型。
  • 在 C++14 及更高版本中,auto 可以用于函数的返回类型。通常这种做法可以简化代码,尤其是当返回类型比较复杂时。比如,在本例中,我们用 auto 表示返回类型,并在函数签名的末尾(-> std::bitset<BITSET_CAPACITY>)明确指定返回类型。

尾返回类型(Trailing Return Type

  • -> std::bitset<BITSET_CAPACITY> 指定了函数的返回类型。在这个例子中,返回类型是 std::bitset<BITSET_CAPACITY>,即一个包含 BITSET_CAPACITY 位的二进制位集。
  • std::bitset 是一个模板类,表示固定大小的二进制位集合。BITSET_CAPACITY 是一个常量,表示位集合的大小。

三、构造函数与delete、explicit

delete 关键字用于 禁用 默认构造函数。它的作用是禁止编译器生成一个默认的构造函数。

  • 默认构造函数是指没有任何参数的构造函数。例如,如果你没有显式提供任何构造函数,C++ 编译器会自动生成一个默认构造函数,它会将类的成员变量初始化为默认值(例如,对于基本数据类型会设置为零)。
  • 当你使用 delete 禁用默认构造函数时,编译器将无法自动生成该构造函数,从而防止你使用默认构造函数来创建对象。
  • HyperLogLog() = delete; HyperLogLog hll; // 这是无效的,因为默认构造函数被禁用了

explicit 关键字用来 防止隐式类型转换

  • 在 C++ 中,构造函数可以用于隐式类型转换。例如,如果类有一个单参数构造函数,并且你将一个合适类型的对象传递给类的构造函数,编译器可能会自动进行类型转换,调用该构造函数来创建对象。
  • explicit 关键字的作用是 防止这种隐式类型转换,强制要求在创建对象时显式地传递正确的参数。
  • HyperLogLog hll = 10; // 这是无效的,因为构造函数是 explicit 的,无法隐式传入一个 int 类型。

四、static constexpr 解释

static constexpr double CONSTANT = 0.79402;

在 C++ 中,static constexpr 是两个关键字组合使用的,分别有不同的含义。

1 static 关键字

  • static 关键字在类内定义成员变量时的作用是 使变量成为类的静态成员。静态成员属于类本身,而不是类的任何特定实例。
  • 静态成员变量在所有类的实例之间共享,而非每个实例有一个副本。

static 关键字的作用:

  • 使 CONSTANT 成为类的静态成员变量,它在所有 HyperLogLog 类的对象中只有一份副本。
  • 即使你没有创建任何 HyperLogLog 对象,静态成员变量也可以通过类名来访问。

2 constexpr 关键字

  • constexpr 表示常量表达式,用于定义编译时常量。被 constexpr 修饰的变量,必须是一个常量,且其值必须在编译时就已确定。

constexpr 关键字的作用:

  • 在编译时计算常量值,因此它具有常量的特点,确保其值不会在程序运行时改变。
  • 它还允许在编译时执行计算并用于数组大小、模板参数等场景。

3 static constexpr 的组合

当你将 static 和 constexpr 结合使用时,意味着:

  • CONSTANT 是一个类的静态常量成员变量。
  • CONSTANT 的值是编译时常量,它的值在类的所有实例中共享。
  • CONSTANT 的值在程序运行时不可改变,必须在编译时就知道其值。

五、各种cast转换(重点)

5.1 const_cast与reinterpret_cast

从一句代码入手

int32_t offset = *reinterpret_cast<int32_t *>(const_cast<char *>(data_ + col.GetOffset()));

这段代码涉及到类型转换,特别是通过 reinterpret_castconst_cast 进行的转换。我们逐一分析这段代码及其背后的类型转换原理。

1、data_ + col.GetOffset()

  • data_ 是一个指向 char 数组的指针,长度由模板参数 KeySize 决定。

  • col.GetOffset() 返回的是某个列的偏移量,它应该是一个整数(通常是 size_tint 类型),表示该列数据相对于 data_ 的偏移位置。

这行代码是将 col.GetOffset() 加到 data_ 上,得到一个新的指针,该指针指向 data_ 数组中某个列的起始位置。也就是说,data_ + col.GetOffset() 计算的是某个特定列在 data_ 数组中的位置。

 2、const_cast<char \*>(...)

  • const_cast 是一种类型转换运算符,它用于在指针或引用上移除 const 限定符,或者添加 const 限定符。

  • 在这里,data_char data_[KeySize] 类型的数组,它本身是 const 类型(假设 data_const 修饰的),因此 data_ + col.GetOffset() 的类型是 const char *。但在此处,需要一个 char * 类型的指针来进行后续的 reinterpret_cast 操作,因为 reinterpret_cast 要操作的是一个普通的指针类型。

const_cast<char *>(...) 用来去掉 data_ + col.GetOffset() 结果中的 const,将其转换为 char * 类型。这样做是为了让指针指向的内存可以进行修改或重新解释(比如 reinterpret_cast)。但值得注意的是,const_cast 只改变指针的 const 属性,并不会改变实际内存中的数据,因此这种转换可能会导致未定义行为,除非你确信不需要保证该数据是 const 的。

3、reinterpret_cast<int32_t \*>(...)

  • reinterpret_cast 是一种强制类型转换,用于将一个指针或引用转换成其他类型的指针或引用。

  • 它通常用来进行低级的、完全不安全的类型转换,允许你将一个类型的指针转换为与其完全不同类型的指针。

  • 在这个例子中,reinterpret_cast<int32_t *>(...)char * 类型的指针转换为 int32_t * 类型的指针。意味着你可以通过该指针访问一个 int32_t 类型的数据,而不考虑它实际是存储在 char 数组中的某一段字节。

通过这种转换,你实际上是将内存中的字节块视为一个 int32_t 类型的值,按 int32_t 类型来读取数据。这里的 int32_t 是 4 字节,而 char 是 1 字节,所以这个转换的效果是将内存中连续的 4 字节(从 data_ + col.GetOffset() 开始)解释为一个 int32_t 类型的整数。

4. 解引用操作 

reinterpret_cast<int32_t *>(...)
  • 解引用操作符 * 用于访问指针所指向的值。在这里,解引用的是 reinterpret_cast<int32_t *>(...) 返回的 int32_t * 类型的指针。

  • 也就是说,解引用后,我们将从 data_ + col.GetOffset() 开始的 4 个字节读取并解释为一个 int32_t 类型的值,这个值会赋给 offset 变量。

类型转换总结

这段代码使用了两种类型转换:

const_cast

  • 用于去掉 const 限定符,使得 data_ + col.GetOffset() 结果可以被修改或解释为其他类型。它确保 data_ + col.GetOffset()const char * 转换为 char *

  • const_cast 是最轻量级的类型转换方式,通常用于去掉或添加常量属性。

reinterpret_cast

  • 用于将 char * 指针转换为 int32_t * 指针。这种转换是低级别的,不会考虑源数据的实际布局,只是根据内存地址将其重新解释为不同类型的数据。在这里,它将从 data_ + col.GetOffset() 开始的 4 字节解释为一个 int32_t 类型的值。
  • reinterpret_cast 是一种非常危险的类型转换方式,因为它可以突破类型系统的限制,将不同类型的数据按字节重解释,因此在使用时需要非常小心,确保转换后的指针能够正确地访问数据。

5.2 static_cast与dynamic_cast

何时使用 static_castdynamic_cast

  • 使用 static_cast 当你知道类型之间的关系并且转换是安全的。它对编译器更高效,并且适用于已知类型之间的转换。

  • 使用 dynamic_cast 当你不确定基类指针或引用的实际类型时,尤其是在涉及多态的情况下。它会确保转换的类型正确性并避免潜在的错误。

在 C++ 中,类型转换(cast)有四种主要形式:

1、static_cast

  • 用于类型之间的转换,通常用于类型之间有明确定义的转换关系时(如整数类型与浮点类型之间、指针类型之间等)。

  • 它通常用于需要在编译时检查类型合法性的转换。

  • 示例:

float f = 3.14f;
int x = static_cast<int>(f);  // 将 float 转换为 int

2、dynamic_cast

  • 主要用于类之间的指针或引用转换,尤其是多态类型(有虚函数的类)之间的转换。它在运行时执行类型检查。

  • 用于安全地将基类指针或引用转换为派生类指针或引用。

  • 示例:

Base* base = new Derived();
Derived* derived = dynamic_cast<Derived*>(base);  // 运行时检查是否是 Derived 类型

3、const_cast

  • 用于添加或去除 const 限定符,改变对象的常量性。const_cast 不能用来改变对象的类型,它只改变常量性。

  • 示例:

const int x = 10;
int* y = const_cast<int*>(&x);  // 去掉 const,得到一个非 const 指针

reinterpret_cast

  • 用于执行低级的指针类型转换,可以将任何指针类型转换为另一个指针类型,甚至是完全不相关的类型。它仅仅根据内存地址进行转换,几乎不做任何类型检查,因此非常不安全。

  • 示例:

    int x = 42;
    char* p = reinterpret_cast<char*>(&x);  // 将 int* 转换为 char*,按字节访问

总结

  • const_cast 用来移除 const 限定符,使得可以修改指向的内容,或者用于访问原本被声明为 const 的数据。

  • reinterpret_cast 用于指针类型之间的强制转换,允许将指针转换为完全不同类型的指针,按新的类型去解释数据。这种转换是非常低级且不安全的,可能会引发未定义行为,必须小心使用

六、几种整形类总结

最后,把几个整数类型都理清楚一下。这里重点调int、int8、int8_t、size_t

int

大小:通常是 4 字节(32 位),但这取决于平台(例如,某些 64 位平台上的 int 可能为 8 字节)。

符号:有符号类型,可以表示负数、零和正数。通常是 -2,147,483,648 到 2,147,483,647(即 -2^31 到 2^31 - 1),但具体范围取决于平台和编译器。

int8

用途:int8 不是 C++ 标准库中的一种类型,它通常是一个非标准的、可能依赖于特定平台或库的 8 位有符号整数类型。在某些情况下,int8 可能作为某些编译器或库的自定义类型,用于表示 8 位的有符号整数。

int8_t

大小:1 字节(8 位)。

符号:有符号类型,能表示负数、零和正数。

范围:-128 到 127(即 -2^7 到 2^7 - 1)

用途:通常用于跨平台编程,确保类型的大小严格为 8 位。它不受平台架构(如 32 位或 64 位)影响,保证类型大小一致。

size_t

size_t(无符号整数,用于表示大小或索引)

大小:依赖于平台,通常在 32 位系统上为 4 字节,在 64 位系统上为 8 字节。

用途:用于表示对象的大小或数组索引,通常用于数组大小、容器大小等。

范围:取决于平台。32 位平台的 size_t 范围为 0 到 4,294,967,295,64 位平台为 0 到 18,446,744,073,709,551,615。


http://www.kler.cn/a/562996.html

相关文章:

  • ARM Coretex-M核心单片机(STM32)找到hardfault的原因,与hardfault解决方法
  • 算法题(79):两个数组的交集
  • seacmsv9注入管理员账号密码+order by+limit
  • MaxKB上架至阿里云轻量应用服务器镜像市场
  • 安科瑞为高速公路服务区充电桩建设运营提供解决方案
  • Canvas在视频应用中的技术解析
  • 国密算法Sm2工具类--golang实现版
  • SpringBoot项目连接Oracle视图报错整理
  • 上证50期权代码是什么?上证50股指期权数据从哪里可以找到?
  • 怎么获取免费的 GPU 资源完成大语言模型(LLM)实验
  • 在CentOS 7上安装RocketMQ 4.9.2
  • Vscode编辑器:解读文件结构、插件的导入导出、常用快捷键配置技巧及其常见问题的解决方案
  • 如何在Spring Boot中监控缓存的命中率?
  • 学习路之PHP --TP6异步执行功能 (无需安装任何框架)
  • HDFS扩缩容及数据迁移
  • 面试之《react hooks在源码中是怎么实现的?》
  • HIVE SQL函数之比较函数
  • Wpf 之Generic.xaml
  • vmware:新装ubuntu无法使用ssh连接或者复制粘连
  • Elasticsearch 相关面试题