C++温故补缺(二十一):杂项补充2

这篇具有很好参考价值的文章主要介绍了C++温故补缺(二十一):杂项补充2。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

杂记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;
}

C++温故补缺(二十一):杂项补充2

在上面的代码中,我们定义了一个类 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;
}

C++温故补缺(二十一):杂项补充2

第二个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);

}

C++温故补缺(二十一):杂项补充2

为什么不用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()语句下一行空着,用来标注断言语句文章来源地址https://www.toymoban.com/news/detail-467205.html

到了这里,关于C++温故补缺(二十一):杂项补充2的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 温故知新(十一)——IIC

    IIC(I2C)是一种同步、多主、多从、分组交换、单端、串行计算机总线,由飞利浦半导体(现在的 NXP 半导体)在 1982 年发明。它广泛用于在短距离、板内通信中将低速外设集成电路附加到处理器和微控制器上。 I2C 总线有两根线 SDA/SCL 就可以连一堆芯片,实现很多的外设应用。

    2024年02月09日
    浏览(46)
  • C++:查漏补缺笔记

    第一种:数组定义空间大小但不手动赋值(默认初始化) 1.1 不初始化,只定义,后面不赋值( 没有意义 ) 这种直接开辟空间的是没有意义的,如果这样初始化后,必须记得后面要进行赋值操作,下面的代码打印出来就是一个没有意义的数值。 1.2 另一种是定义,并 默认初

    2024年02月13日
    浏览(30)
  • C++新经典 | C++ 查漏补缺(智能指针)

    目录 一、动态分配 1.初始化的三种方式 2. 释放内存 (1)悬空指针  3.创建新工程与观察内存泄漏 二、深入了解new/delete 1.new和delete 2.operator new()和operator delete() 3.申请和释放一个数组 三、智能指针  1.shared_ptr (1)常规初始化 (2)make_shared (3)引用计数 (3.1)引用计

    2024年02月07日
    浏览(41)
  • C++(20):explicit(true/false)

    explicit通常用于声明是否运行隐式转换: C++20扩展了explicit,可以通过explicit(false)来禁用,或通过explicit(true)来启用explicit

    2024年02月06日
    浏览(43)
  • C++——初始化列表 | explicit关键字 | static成员

    🌸作者简介: 花想云 ,在读本科生一枚,致力于 C/C++、Linux 学习。 🌸 本文收录于 C++系列 ,本专栏主要内容为 C++ 初阶、C++ 进阶、STL 详解等,专为大学生打造全套 C++ 学习教程,持续更新! 🌸 相关专栏推荐: C语言初阶系列 、 C语言进阶系列 、 数据结构与算法 本章我们

    2023年04月11日
    浏览(50)
  • 每天一点C++——杂记

    浅拷贝就是只拷贝指针,并不拷贝指针所指向的内容,深拷贝则会对指针的内容进行拷贝。浅拷贝会在一些场景下出现问题,看下面的例子: 如果我定义 一个对象s1,并且为name申请一块内存并赋值为“zhangsan”,然后定义对象s2=s1,然后修改s2的name值会发生什么情况? 重载可

    2023年04月17日
    浏览(36)
  • 设计模式(二十一)策略

    定义一系列算法类,将每一个算法封装起来,并让它们可以相互替换。策略模式让算法独立于使用它的客户而变化。策略模式是一种对象行为型模式,又称为政策(Policy)模式。 包含以下三个角色: 1、Context(环境类): 环境类是使用算法的角色,它在解决某个问题(即实

    2024年02月01日
    浏览(41)
  • 第二十一章 Classes

    类定义并不是 ObjectScript 的正式组成部分。相反,可以在类定义的特定部分中使用 ObjectScript (特别是在方法定义中,可以在其中使用其他实现语言)。 每个 IRIS 类都有一个名称,该名称在定义它的命名空间中必须是唯一的。完整的类名是由一个或多个句点分隔的字符串,如

    2024年02月09日
    浏览(37)
  • 第二十一章

    计算机应用实现了多台计算机间的互联,使得它们彼此之间能够进行数据交流。网络应用程序就是在已连接的不同计算机上运行的程序,这些程序借助于网络协议,相互之间可以交换数据。编写网络应用程序前,首先必须明确所要使用的网络协议。TCP/IP协议是网络应用程序的

    2024年02月04日
    浏览(51)
  • opencv_c++学习(二十一)

    轮廓检测函数: image:输入图像,数据类型为CV_8U的单通道灰度图像或者二值化图像。contours:检测到的轮廓,每个轮廓中存放着像素的坐标。 mode:轮廓检测模式标志。 method:轮廓逼近方法标志。 offset:每个轮廓点移动的可选偏移量。这个函数主要用在从ROI图像中找出的轮廓并基于

    2024年02月06日
    浏览(44)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包