杂记2

explicit

在 C++ 中,explicit 是一个关键字,用于修饰类的构造函数,其作用是禁止编译器将一个参数构造函数用于隐式类型转换。具体来说,当一个构造函数被 explicit 修饰时,只能通过显式调用来创建该类的对象,而不能通过隐式类型转换来创建对象。

下面通过一个例子来说明 explicit 关键字的作用:

class A {
public:
explicit A(int x) : m_x(x) {}
int getX() const {
return m_x;
} private:
int m_x;
}; void foo(A a) {
cout << a.getX() << endl;
} int main() {
A a1{1}; // 正确,使用显式调用构造函数创建对象
A a2 = 2; // 错误,隐式类型转换被禁止了
foo(3); // 错误,隐式类型转换被禁止了
foo(A{4}); // 正确,使用显式调用构造函数创建对象
return 0;
}

在上面的代码中,我们定义了一个类 A,其中构造函数被 explicit 修饰。我们在函数 foo 中使用了 A 类型的参数,然后在 main 函数中分别使用了显式调用构造函数和隐式类型转换来创建 A 类型的对象,并尝试将这些对象作为参数传递给函数 foo。

由于构造函数被 explicit 修饰,因此我们无法直接使用隐式类型转换来创建 A 类型的对象,也无法将一个整数隐式转换为 A 类型并作为参数传递给函数 foo。只有通过显式调用构造函数来创建对象,才能正常进行编译。

使用 explicit 关键字可以显式地指定哪些构造函数可以被用于隐式类型转换,从而避免了一些潜在的类型转换错误。使用 explicit 关键字还可以提高代码的可读性,使得代码更加易于理解和维护。

export

export关键字是C++20中引入的新特性,用于向编译器声明和导出模块接口。它用于定义模块的接口,可以将声明的标识符(函数、变量、类等)导出到模块外部,供其他模块使用。同时,它还可以限定一个模块内的标识符只能被本模块内的其他代码使用,而不能被导出到外部。

export关键字通常与import关键字一起使用,import用于导入其他模块的接口。

下面是一个使用export的示例:

// module1.cpp
export module module1; export int add(int a, int b) {
return a + b;
} export int value = 42; // main.cpp
import module1; int main() {
int result = add(2, 3);
std::cout << "result: " << result << std::endl;
std::cout << "value: " << value << std::endl;
return 0;
}

在上面的示例中,module1模块中导出了add函数和value变量,并在main.cpp中使用了这两个标识符。当编译main.cpp时,编译器会自动将module1模块编译成一个单独的文件,并将其链接到main程序中。

需要注意的是,export关键字目前还不是所有编译器都支持,具体情况需要查看编译器的文档。

typeid和decltype

C++中的typeid关键字用于获取一个表达式的类型信息,可以用来判断两个对象是否为同一种类型。具体来说,typeid可以返回一个type_info类型的对象,该对象包含了表达式的类型信息,可以通过type_info对象的name()方法获取类型名。typeid一般用于运行时类型识别(RTTI)。

RTTI运行时类型识别(Runtime Type Identification,RTTI)是一种在程序运行时确定对象类型的机制。RTTI通常用于C++中,可以使用typeid运算符来获得对象的类型信息。在程序中,通过将对象转换为其基类或接口类型来使用RTTI。RTTI可以用于在程序运行时进行类型安全的转换和异常处理,但过度使用RTTI可能会导致代码的性能下降。

与之相关的还有编译时类型识别(Compile-time type checking)

例如,下面的代码可以获取一个变量的类型信息,并输出其类型名:

#include <iostream>
#include <typeinfo> int main() {
int x = 42;
const std::type_info& type = typeid(x);
std::cout << type.name() << std::endl;
return 0;
}

输出结果为:

i

这里的i表示整数类型。另外,char型的类型值为c,long型的类型值为l,float型为f。简单类型是用的单个字符表示,类或函数用字符串表示,其中会包括返回值类型、参数类型等字符,如:

int fun(long x=0,char y='1'){
return 0;
}
int main(){
const std::type_info& type = typeid(fun);
std::cout << type.name() << std::endl;
}

第二个i是函数返回值类型,l是第一个参数类型值,c是第二个参数类型值。

另外,C++11中引入了decltype关键字,可以用来获取一个表达式的类型,也可以用于函数返回类型的推导等。

C++11中引入了decltype关键字,用于获取一个表达式的类型,也可以用于函数返回类型的推导等。decltype的基本语法如下:

decltype(expression)

其中,expression可以是任意一个表达式,decltype会返回该表达式的类型,包括const、引用等修饰符。

下面是几个使用decltype的例子:

int x = 42;
const int& y = x;
decltype(x) z1 = 0; // z1的类型为int
decltype(y) z2 = x; // z2的类型为const int&
decltype(x+y) z3 = 0; // z3的类型为int

在上面的代码中,z1的类型为int,因为decltype(x)的结果是intz2的类型为const int&,因为decltype(y)的结果是const int&z3的类型为int,因为decltype(x+y)的结果是int

另外,decltype还可以用于函数返回类型的推导。例如:

template <typename T, typename U>
auto add(T t, U u) -> decltype(t+u) {
return t + u;
}

在上面的代码中,decltype(t+u)用于推导函数返回类型,即tu相加的结果类型。

需要注意的是,decltype并不会对表达式进行求值,而是仅仅返回表达式的类型。因此,如果表达式中包含了函数调用或运算符重载等操作,decltype会返回对应的函数返回类型或运算符重载结果类型,而不是表达式的值。

typename

typename关键字则用于指明一个名称是类型名称。在C++中,有时需要使用嵌套的类型名称,如类模板中的类型别名或嵌套类的名称等。在这种情况下,需要使用typename关键字来指明名称是类型名称而非成员名称。例如:

template <typename T>
struct my_template {
typename T::value_type* ptr;
};

在上面的代码中,typename T::value_type表示T类型中的value_type类型,而不是T类型中的名为value_type的成员变量。如果省略了typename关键字,则编译器会将T::value_type解释为成员变量名,从而导致编译错误。

四种cast

在C++中,有四种类型转换的方式,它们分别是static_cast、dynamic_cast、const_cast和reinterpret_cast。

  1. static_cast:用于基本数据类型的转换,以及非多态类型的转换。例如,将int类型转换为double类型,或将指针类型转换为void*类型。

    如:

    int a = 10;
    double b = static_cast<double>(a); // 将int类型的a转换为double类型的b class Base {};
    class Derived : public Base {};
    Base* base = new Derived;
    Derived* derived = static_cast<Derived*>(base); // 将基类指针转换为派生类指针
  2. dynamic_cast:用于多态类型的转换,即具有虚函数的类型。它会在运行时检查是否能够安全地将一个指针或引用转换为目标类型,如果不能则返回NULL。例如,将基类指针转换为派生类指针,或将派生类指针转换为基类指针。

    class Base {
    public:
    virtual void print() { cout << "Base" << endl; }
    };
    class Derived : public Base {
    public:
    void print() { cout << "Derived" << endl; }
    }; Base* base = new Derived;
    Derived* derived = dynamic_cast<Derived*>(base); // 将基类指针转换为派生类指针
    if (derived) {
    cout<<"derived"<<endl; // 输出"Derived"
    }else{
    cout<<"error deriving";
    }
  3. const_cast:用于去除const属性。例如,将const int类型指针转换为int类型指针,或将const对象转换为非const对象。

    const int a = 10;
    int& b = const_cast<int&>(a); // 将const int类型的a转换为int类型的b的引用
    b = 20;
    cout << a << endl; // 输出10,因为a依然是const类型
  4. reinterpret_cast:用于进行二进制的低层次转换,不考虑类型之间的关系。例如,将一个指针转换为一个整数,或将一个整数转换为一个指针。

    int a = 10;
    int* p = &a;
    long long b = reinterpret_cast<long long>(p); // 将int类型的指针p转换为long long类型的b
    cout << b << endl; // 输出p的地址的十进制表示

需要注意的是,这些类型转换都具有一定的风险,需要谨慎使用。特别是reinterpret_cast,容易导致不可预测的错误,应该尽量避免使用。

memset

C++ 中的 memset 是一个用于填充内存块的函数,其定义在头文件 <cstring> 中。memset 可以将一段内存块的值都设置为指定的值,常用于清空数组或结构体等操作。

memset 函数的语法如下:

void* memset(void* ptr, int value, size_t num);

其中,第一个参数 ptr 是指向内存块的指针,第二个参数 value 是要设置的值(通常是 0 或 -1),第三个参数 num 是要设置的字节数。函数会将 ptr 指向的内存块中的前 num 个字节都设置为 value。

例如,下面的代码使用 memset 函数将一个数组清空:

int arr[10];
memset(arr, 0, sizeof(arr));

在上面的代码中,我们将 arr 数组中的所有元素都设置为 0。由于数组中有 10 个元素,因此我们传递给 memset 函数的第三个参数是 sizeof(arr),表示要设置的字节数。

需要注意的是,memset 函数并不会检查数组越界等错误,因此使用时需要确保不会访问到不属于自己的内存区域。此外,对于非 POD 类型(即含有构造函数、析构函数或虚函数的类型),使用 memset 函数可能会导致不可预期的行为,因此需要谨慎使用。

assert

assert断言,是C++<assert.h>库的函数,用来找出程序的错误的。格式:assert(exp);

assert的第一个参数是一个表达式,就是用来找错的表达式,如果为真则程序继续执行,若为假则引起abort中断信号,程序终止执行。

如:

#include<assert.h>
#include<iostream>
int main(){
int a=0;
assert(a); }

为什么不用if

assert是用来排除错误的,而if是用来找异常的,错误是可以通过修改去掉的,而异常是无法避免的。

为什么不直接cout

因为在一些大项目中,可以不止一个输出,所以如果找到错误,后续的程序便不需要继续执行。如:

#include<assert.h>
#include<iostream>
int main(){
int a=0;
int b=0;
int c=0;
//...
assert(a); std::cout<<a<<" ";
std::cout<<b<<" ";
std::cout<<c<<" ";
//...
}
使用规则
  • 根据上一条,所以assert一般用于程序输出的开始

  • 每个assert只检查一个条件,不然找到错误不知道是哪个

  • 不能改变环境的表达式

    如:assert(a++);这样会改变环境的表达式要用,只能用assert(a),assert(a<100)这样对原环境无影响的表达式

  • 一般assert()语句下一行空着,用来标注断言语句

C++温故补缺(二十一):杂项补充2的更多相关文章

  1. C温故补缺(二):volatile

    volatile 参考:CSDN volatile也是一个类型修饰符,被其修饰的变量意味着可以被某些编译器未知的因素修改,如操作系统,硬件,线程等. 当遇到volatile修饰的变量时,编译器对访问该 ...

  2. css杂项补充

    css杂项补充 一.块与内联 1.块 独行显示 支持宽高,宽度默认适应父级,高度默认由子级或内容撑开 设置宽高后,采用设置的宽高 2.内联 同行显示 不支持宽高 margin上下无效果,左右会起作用, ...

  3. 无废话ExtJs 入门教程二十一[继承:Extend]

    无废话ExtJs 入门教程二十一[继承:Extend] extjs技术交流,欢迎加群(201926085) 在开发中,我们在使用视图组件时,经常要设置宽度,高度,标题等属性.而这些属性可以通过“继承” ...

  4. Bootstrap <基础二十一>徽章(Badges)

    Bootstrap 徽章(Badges).徽章与标签相似,主要的区别在于徽章的边角更加圆滑. 徽章(Badges)主要用于突出显示新的或未读的项.如需使用徽章,只需要把 <span class= ...

  5. 【圣诞特献】Web 前端开发精华文章推荐【系列二十一】

    <Web 前端开发精华文章推荐>2013年第九期(总第二十一期)和大家见面了.梦想天空博客关注 前端开发 技术,分享各种增强网站用户体验的 jQuery 插件,展示前沿的 HTML5 和  ...

  6. Citrix 服务器虚拟化之二十一 桌面虚拟化之部署Provisioning Services

    Citrix 服务器虚拟化之二十一  桌面虚拟化之部署Provisioning Services Provisioning Services 是Citrix 出品的一系列虚拟化产品中最核心的一个组件, ...

  7. 二十一、contextMap中放的常用数据

    二十一.contextMap中放的常用数据 request:请求范围的数据.即ServletRequest中的那个Map parameters:请求参数的数据.即request.getParamete ...

  8. 牢记!SQL Server数据库开发的二十一条注意点

    如果你正在负责一个基于SQL Server的项目,或者你刚刚接触SQL  Server,你都有可能要面临一些数据库性能的问题,这篇文章会为你提供一些有用的指导(其中大多数也可以用于其它的DBMS). ...

  9. 转:二十一、详细解析Java中抽象类和接口的区别

    转:二十一.详细解析Java中抽象类和接口的区别 http://blog.csdn.net/liujun13579/article/details/7737670 在Java语言中, abstract ...

  10. COJ 0979 WZJ的数据结构(负二十一)

    WZJ的数据结构(负二十一) 难度级别:C: 运行时间限制:5000ms: 运行空间限制:262144KB: 代码长度限制:2000000B 试题描述 请你实现一个数据结构,完成这样的功能: 给你一个 ...

随机推荐

  1. 自己动手从零写桌面操作系统GrapeOS系列教程——18.外设和IO

    学习操作系统原理最好的方法是自己写一个简单的操作系统. 一.外设和I/O接口 前面我们介绍过冯·诺依曼结构包含5部分,其中输入设备和输出设备统称为外部设备,简称外设.常见的外设有鼠标.键盘.显示器.硬 ...

  2. java面向对象-基础入门

    java面向对象-基础入门 面向过程:线性思维 面向对象思维:物以类聚,分类的思维 对于描述复杂的事物,为了从宏观上把握,从整体上合理分析,我们需要使用面向对象的思路来分析整个系统,但是具体到某个微观 ...

  3. uni-app云开发入门

    云函数 首先创建一个uniapp项目,创建项目时选择启用uniCloud云开发. 创建项目成功后,按照下面的步骤进行开发.   创建云函数 1.关联云服务器 2.创建云函数 一个云函数可以看成是一个后 ...

  4. Go语言:利用 TDD 逐步为一个字典应用创建完整的 CRUD API

    前言 在数组这一章节中,我们学会了如何按顺序存储值.现在,我们再来看看如何通过键存储值,并快速查找它们. Maps 允许你以类似于字典的方式存储值.你可以将键视为单词,将值视为定义. 所以,难道还有比 ...

  5. MordernC++之 auto 和 decltype

    在C++11标准中,auto作为关键字被引入,可以用来自动推导变量类型,auto可以用于定义变量,函数返回值,lambda表达式等,在定义变量时可以使用auto来代替具体类型,编译器根据变量初始化表达 ...

  6. Vue3中无法为el-tree-select设置反选问题分析

    好久没有写博客了,刚好上周遇到一个难缠问题,这里记录一下. 环境:Vue3.2.Element Plus 问题:子组件 setting.vue => 弹窗组件 Dialog => 树选择组 ...

  7. Springboot一些常用注解

    Springboot启动注解 @SpringbootApplication 这个注解是Springboot最核心的注解,用在Springboot的主类上,标识这是一个Springboot应用,用来开启 ...

  8. 电脑上跨平台的电子书阅读器Koodo Reader

    https://wbsu2003.gitee.io/2021/04/30/%E7%94%B5%E8%84%91%E4%B8%8A%E8%B7%A8%E5%B9%B3%E5%8F%B0%E7%9A%84 ...

  9. 借助 APISIX Ingress,实现与注册中心的无缝集成

    作者张晋涛,API7.ai 云原生技术专家,Apache APISIX PMC 成员,Apache APISIX Ingress Controller 项目维护者. 原文链接 云原生场景下是否需要服务 ...

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

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