带你了解并掌握一些C++关键字的使用

简介: 带你了解并掌握一些C++关键字的使用

前言

关键字在编程语言中具有特定的含义和用途,它们被用来表示语言的语法结构、控制流程、数据类型、访问权限等重要概念。


关键字有助于编程语言的解析和编译,使得编程语言的语法规则能够被准确地解释和执行。通过使用关键字,编程语言可以提供一致性的语法规范和编程范式,使得程序员可以按照特定的语义规则编写代码。


例如,在C++中,关键字"if"和"else"用于控制程序的条件执行,关键字"for"和"while"用于循环结构,关键字"class"用于定义类,关键字"int"和"double"用于声明整数和浮点数数据类型等等。


关键字是编程语言的基础构成要素之一,它们定义了语言的语法规则和功能特性。了解和正确使用关键字是编写有效、正确的程序的基础。同时,编程语言的关键字是预留的,不能将其用作标识符或变量名。

C++11关键字图表:

1700457031463.png

根据当前的 C++ 标准(C++17),以及截至到目前为止的最新 C++ 标准(C++20),export 关键字已经被移除,并不再是 C++ 的一个关键字。在 C++11 标准中,export 关键字被引入,但在后续的标准中,并没有被广泛采纳并实现,最终在 C++17 中被移除。

一、alignas?

在C++中,alignas关键字用于指定变量或类型的对齐方式。对齐方式是指将数据在内存中按照一定规则进行排列的方式,以提高访问效率和内存使用效率。

alignas关键字可以在类型或变量声明时使用,用于指定所需的对齐方式。它接受一个对齐值作为参数,该对齐值必须是2的幂。

示例代码:

#include <iostream>
struct alignas(16) MyStruct // 指定该结构体按16字节对齐
{
    int x;
    char c;
    double d;
};
int main()
{
    alignas(16) int alignedInt; // 指定该变量按16字节对齐
    std::cout << "Size of MyStruct: " << sizeof(MyStruct) << std::endl;
    std::cout << "Alignment of MyStruct: " << alignof(MyStruct) << std::endl;
    // 输出的结果可能因不同编译器而有所不同
    std::cout << "Alignment of alignedInt: " << alignof(decltype(alignedInt)) << std::endl;
    return 0;
}


二、alignof?

在C++中,alignof关键字用于获取类型或变量的对齐方式。对齐方式是指数据在内存中按照一定规则进行排列的方式,以提高访问效率和内存使用效率。

alignof关键字可以在类型或变量后面使用,用于获取所需的对齐方式。它返回一个整数值,表示类型或变量的对齐要求。

示例代码:

#include <iostream>
struct MyStruct
{
    int x;
    char c;
    double d;
};
int main()
{
    std::cout << "Alignment of MyStruct: " << alignof(MyStruct) << std::endl;
    double arr[5];
    std::cout << "Alignment of arr: " << alignof(decltype(arr)) << std::endl;
    return 0;
}

运行结果:

1. Alignment of MyStruct: 8
2. Alignment of arr: 8

三、asm?

在C++中,asm关键字用于嵌入汇编语言代码到C++程序中。它允许程序员直接在C++代码中插入特定的汇编指令,以便实现对底层硬件和操作系统特性的直接访问。


使用asm关键字时,要根据所使用的编译器和目标平台的不同来编写针对性的汇编代码。因此,示例代码可能因环境而异。在现代的C++标准中,推荐使用__asm__关键字,而不是简单的asm。

示例代码:

#include <iostream>
int main()
{
    int x = 10;
    int y = 20;
    int result;
    __asm__
    (
        "mov %1, %%eax;"  // 将y赋值给EAX寄存器
        "add %2, %%eax;"  // 将x加到EAX寄存器上
        "mov %%eax, %0;"  // 将EAX寄存器的值存回result变量
        : "=r" (result)   // 输出操作数(result)
        : "r" (y), "r" (x) // 输入操作数(y和x)
        : "%eax"         // 受影响的寄存器
    );
    std::cout << "Result: " << result << std::endl;
    return 0;
}

需要注意的是,嵌入汇编是一种非标准的C++语法扩展,它依赖于特定的编译器和平台。因此,在使用嵌入汇编时要特别小心,确保对应的汇编代码是正确的,并且在目标平台上能够正常工作。

1.使用asm关键字的用途及优势

1. 直接访问底层硬件和操作系统特性:通过嵌入汇编代码,可以直接使用底层硬件和操作系统提供的特性,如处理器指令集、寄存器等。这样可以更灵活地对底层资源进行操作,访问和控制硬件的细节。


2. 提高程序性能:嵌入汇编代码可以精确地控制指令执行的顺序和方式。通过编写高效的汇编代码,可以针对特定的场景和需求进行优化,从而提高程序的性能。


3. 实现特定的算法和功能:有些特定的算法和功能可能需要使用汇编代码来实现,如加密算法、视频编解码器等。通过嵌入汇编代码,可以直接使用汇编级的指令和操作来实现这些功能,而不受高级语言的限制。


4. 调试和研究底层问题:嵌入汇编代码可以用于调试和研究底层问题。通过直接观察和修改底层的寄存器和内存状态,可以更深入地了解程序运行时的细节,帮助排查和解决一些底层问题。


需要注意的是,嵌入汇编是一项高级技巧,需要对汇编语言和底层硬件有一定的了解。同时,使用嵌入汇编代码时要小心处理和维护代码的可移植性和可读性,避免破坏程序的可维护性。在大多数情况下,应当优先使用高级语言提供的特性和库函数来实现功能,只在必要的情况下才考虑使用嵌入汇编。

四、constexpr?

在C++constexpr关键字,用于在编译期计算值或表达式,从而提高程序的性能和可读性。


使用constexpr定义的变量或函数,在编译时就能够计算出值来,并且可以用于初始化其他变量。这个特性可以消除一些不必要的运行时计算,提高程序的效率。


示例代码:

#include <iostream>
constexpr int factorial(int n)
{
    return n <= 0 ? 1 : n * factorial(n - 1);
}
int main()
{
    constexpr int x = 5;
    std::cout << "Factorial of " << x << " is " << factorial(x) << std::endl;
    int y;
    std::cin >> y;
    constexpr int z = y * 10;  // 非法,y是运行时变量,不可以在编译期使用
    return 0;
}

在上述代码中,定义了一个constexpr函数factorial,用于计算输入参数的阶乘。在main函数中,我们使用constexpr关键字定义了一个常量x,并且使用factorial函数计算了x的阶乘作为输出。同时,还演示了constexpr在编译期进行计算的特性。

运行结果:

Factorial of 5 is 120

需要注意的是,constexpr变量或函数的定义必须满足一定的条件,只有在满足条件的情况下才能进行编译期计算。例如,constexpr函数必须满足输入参数和返回值都是字面量类型(literal type),函数体中只能包含需要在编译期获取的代码等。同时,对于constexpr变量的初始化表达式也必须满足特定的要求。

五、decltype?

decltype 关键字是 C++11 引入的,用于查询表达式的类型,可以用于获取变量、表达式或函数返回值的类型。并用获取到的类型声明变量。

示例代码:

#include <iostream>
int main() {
    int x = 5;
    const double pi = 3.14159;
    decltype(x) a = x;  // a的类型为int,与x的类型相同
    decltype(pi) b = pi;  // b的类型为const double,与pi的类型相同
    decltype(x + pi) c = x + pi;  // c的类型为double,根据表达式x + pi的结果推导
    std::cout << "a: " << a << std::endl;
    std::cout << "b: " << b << std::endl;
    std::cout << "c: " << c << std::endl;
    return 0;
}

其中,decltype里面的内容可以 是一个变量、表达式或函数调用。


在上述示例中,decltype 关键字用于声明了三个变量 a、b 和 c,分别初始化为 x、pi 和 x + pi 的类型。根据推导规则,a 的类型为 int,与 x 的类型相同;b 的类型为 double,与 pi 的类型相同;c 的类型为 double,根据表达式 x + pi 的结果推导。最后将结果打印输出。


需要注意的是,decltype 推导出的类型通常会保留 const 和引用特性。对于函数调用,decltype 将返回函数的返回值类型。


通过使用 decltype,我们可以在编译时获取表达式的类型,使得代码更加灵活和类型安全。


六、explicit?

explicit 是 C++ 关键字,用于修饰单参数构造函数,它的作用是禁止隐式类型转换。

使用 explicit 关键字可以防止编译器在某些情况下进行隐式的自动类型转换,保证只能使用显式的方式进行类型转换。

示例代码:

#include <iostream>
class MyClass {
public:
    explicit MyClass(int x) : value(x) {}
    int getValue() const {
        return value;
    }
private:
    int value;
};
void printValue(const MyClass& obj) {
    std::cout << "Value: " << obj.getValue() << std::endl;
}
int main() {
    MyClass obj1(10);
    printValue(obj1);
    // MyClass obj2 = 20;  // 错误,禁止隐式类型转换
    MyClass obj2 = MyClass(20); // 正确,通过显式构造函数进行赋值
    printValue(obj2);
    // MyClass obj3 = obj1;  // 错误,禁止隐式类型转换
    MyClass obj3(obj1); // 正确,通过显式构造函数进行赋值
    printValue(obj3);
    return 0;
}

在上述示例中,MyClass 类定义了一个使用 explicit 关键字修饰的单参数构造函数。该构造函数用于将整数值赋给 value 成员变量。


在 main 函数中,我们创建了 obj1 对象,并通过 printValue 函数打印出它的值。然后尝试使用隐式类型转换进行构造赋值,如 MyClass obj2 = 20; 和 MyClass obj3 = obj1;,这两行代码会在编译时产生错误。


为了通过编译,我们需要使用显式构造函数进行赋值,如 MyClass obj2 = MyClass(20); 和 MyClass obj3(obj1);。这样就确保只能通过显式的方式进行类型转换,增加了代码的可读性和类型安全性。


使用 explicit 关键字能够避免不必要的隐式类型转换,减少代码行为的模糊性。它通常用于构造函数,确保只有在明确的意图下进行类型转换。

七、mutable?

mutable 是 C++ 中的一个关键字,它的作用是修改类中被声明为 mutable 的成员变量,即使在 const 成员函数中,这些变量也可以被修改。


使用 mutable 关键字,可以在 const 成员函数中修改被声明为 mutable 的成员变量的值,即使按理说在 const 函数中是不允许修改成员变量的。

示例代码:

#include <iostream>
class Counter {
public:
    int getCount() const;
    void increment() const;
private:
    mutable int count = 0;
};
int Counter::getCount() const {
    // count 可以在 const 函数中被访问,但不可以直接修改
    // 但是因为被声明为 mutable,仍然可以修改它的值
    count += 1;
    return count;
}
void Counter::increment() const {
    count += 1;
}
int main() {
    Counter c;
    std::cout << "Count: " << c.getCount() << std::endl;  // 输出 1
    std::cout << "Count: " << c.getCount() << std::endl;  // 输出 2
    c.increment();
    std::cout << "Count: " << c.getCount() << std::endl;  // 输出 3
    return 0;
}

在上述示例中,我们有一个名为 Counter 的类。它有一个私有的 count 成员变量,被声明为 mutable,初始化为 0。


类中的 getCount() 和 increment() 函数都被声明为 const 成员函数。然而,getCount() 函数尝试通过 count += 1 来修改成员变量的值。


因为 count 被声明为 mutable,即使在 const 函数中,也可以修改它的值。


在 main() 函数中,我们使用 Counter 类的实例 c 来测试。我们首先通过 getCount() 函数获取初始的计数值,并对计数值递增操作进行多次操作,以验证 count 变量在const成员函数中的修改。


通过使用 mutable 关键字,我们可以在 const 函数中修改特定的成员变量,提供更多的灵活性和功能,并且不违反 const 成员函数的约定。

八、noexcept?

noexcept 是 C++ 中的一个关键字,它用于指示函数是否可能引发异常。使用 noexcept 关键字可以明确指定一个函数不会引发异常,或是可能引发异常。

作为一种函数修饰符,noexcept 在函数声明或定义时使用,并紧跟在函数参数列表和函数的返回类型之后。

示例代码:

#include <iostream>
void func1() noexcept {
    std::cout << "This function will not throw any exceptions." << std::endl;
}
void func2() {
    throw std::runtime_error("Exception thrown.");
}
int main() {
    try {
        func1();
        func2();
    } catch (const std::exception& e) {
        std::cout << "Exception caught: " << e.what() << std::endl;
    }
    return 0;
}

在 main() 函数中,我们尝试调用 func1() 和 func2()。由于 func1() 是 noexcept 的,不会引发异常,所以它可以正常执行。而 func2() 引发了异常,但没有被 try-catch 块捕获,所以程序会终止执行,并输出异常信息。


九、thread_local?

thread_local 是 C++ 中的一个关键字,它用于声明线程局部存储的变量。使用 thread_local 关键字声明的变量,在不同的线程中拥有各自独立的副本,每个线程都可以独立地访问和修改自己的副本。


示例代码:

#include <iostream>
#include <thread>
// 全局变量
thread_local int threadId;
void threadFunc(int id) {
    threadId = id;
    std::cout << "Thread " << id << " | threadId: " << threadId << std::endl;
}
int main() {
    std::thread t1(threadFunc, 1);
    std::thread t2(threadFunc, 2);
    t1.join();
    t2.join();
    std::cout << "Main thread | threadId: " << threadId << std::endl;
    return 0;
}

示例结果:

Thread 1 | threadId: 1
Thread 2 | threadId: 2
Main thread | threadId: 0


目录
相关文章
|
6月前
|
存储 安全 编译器
【C++专栏】C++入门 | auto关键字、范围for、指针空值nullptr
【C++专栏】C++入门 | auto关键字、范围for、指针空值nullptr
70 0
|
4月前
|
存储 安全 编译器
【C++入门 四】学习C++内联函数 | auto关键字 | 基于范围的for循环(C++11) | 指针空值nullptr(C++11)
【C++入门 四】学习C++内联函数 | auto关键字 | 基于范围的for循环(C++11) | 指针空值nullptr(C++11)
|
5月前
|
存储 安全 编译器
【C++航海王:追寻罗杰的编程之路】引用、内联、auto关键字、基于范围的for、指针空值nullptr
【C++航海王:追寻罗杰的编程之路】引用、内联、auto关键字、基于范围的for、指针空值nullptr
66 5
|
4月前
|
存储 编译器 C++
C++从遗忘到入门问题之float、double 和 long double 之间的主要区别是什么
C++从遗忘到入门问题之float、double 和 long double 之间的主要区别是什么
|
5月前
|
存储 编译器 程序员
C++一分钟之-auto关键字与类型推导
【6月更文挑战第21天】`auto`在C++11中重生,简化了类型声明,尤其在处理复杂类型时。它让编译器根据初始化值推导变量类型,减少了冗余和错误。使用`auto`简化了迭代器声明和函数返回类型推导,但也带来挑战:类型推导可能不直观,未初始化的`auto`是错误的,且过度使用影响可读性。使用`auto&`和`auto*`明确引用和指针,`decltype`辅助复杂类型推导,保持适度使用以维持代码清晰。
57 1
|
5月前
|
存储 安全 编译器
C++进阶之路:何为引用、内联函数、auto与指针空值nullptr关键字
C++进阶之路:何为引用、内联函数、auto与指针空值nullptr关键字
42 2
|
6月前
|
C++
C++中使用namespace关键字定义和访问命名空间的技术性探讨
C++中使用namespace关键字定义和访问命名空间的技术性探讨
46 3
|
5月前
|
Unix 编译器 C语言
【C++航海王:追寻罗杰的编程之路】关键字、命名空间、输入输出、缺省、重载汇总
【C++航海王:追寻罗杰的编程之路】关键字、命名空间、输入输出、缺省、重载汇总
27 0
|
5月前
|
存储 安全 编译器
【C++】:函数重载,引用,内联函数,auto关键字,基于范围的for循环,nullptr关键字
【C++】:函数重载,引用,内联函数,auto关键字,基于范围的for循环,nullptr关键字
36 0
|
5月前
|
编译器 C语言 C++
【C++】:C++关键字,命名空间,输入&输出,缺省参数
【C++】:C++关键字,命名空间,输入&输出,缺省参数
42 0