文章目录
关键字
一、常用关键字
(一)数据类型相关关键字
bool
:
布尔类型,其取值仅有true
(真)和false
(假)两种情况,两者也皆是关键字。主要用于条件判断等逻辑场景中,例如:bool isTrue = true; if (isTrue) { std::cout << "条件为真" << std::endl; }
char
:
字符类型,用来表示单个字符,在内存中通常占用1个字节,可存储ASCII码表中的字符,像:char ch = 'A';
,常用于文本处理相关操作。short
:
短整型,一般情况下它所占用的字节数比int
类型少,通常为2个字节,能表示的整数范围相对较小,适用于对内存占用要求较为严格且数值范围不大的场景,比如:short num = 10;
。int
:
整数类型,是最常用的整型数据类型,在多数系统中占用4个字节,可表示一定范围内的整数,例如:int age = 25;
,广泛应用于各种数值计算及变量定义场景。long
:
长整型,可用于表示比int
类型更大范围的整数,其具体的字节数在不同系统环境下可能有所差异,在32位系统中常为4个字节,在64位系统中可能是8个字节,例如:long bigNum = 1234567890L;
(一般建议加上后缀L
来明确表示长整型字面量)。float
:
单精度浮点型,占用4个字节,能够表示带有小数部分的数值,但精度相对有限,常用于对精度要求不是特别高的浮点数场景,且在定义时需要添加后缀f
以区分double
类型,例如:float pi = 3.14f;
。double
:
双精度浮点型,通常占用8个字节,相较于float
能提供更高的精度,更适合用于科学计算、高精度数值处理等场景,例如:double result = 3.1415926;
。wchar_t
:
宽字符类型,主要用于处理扩展的字符集,像存储中文字符等非ASCII字符,它的大小根据系统实现而定,在使用时需要添加L
前缀来表示宽字符字面量,例如:wchar_t wideChar = L'好';
。char8_t
:
用于表示8位字符类型,通常与UTF - 8编码配合使用,方便处理基于UTF - 8编码的字符数据,例如在处理网络传输中的文本信息(常采用UTF - 8编码)时可能会用到,像char8_t utf8Char = u8'中';
(这里u8
前缀表示UTF - 8编码的字符字面量)。char16_t
:
用于表示16位字符类型,多与UTF - 16编码相关联,常用于存储和处理遵循UTF - 16编码规则的字符,例如:char16_t utf16Char = u'😀';
(u
前缀表示UTF - 16编码的字符字面量)。char32_t
:
用于表示32位字符类型,对应UTF - 32编码,适合在需要精确处理每个字符且按照UTF - 32编码方式的场景下使用,比如一些国际化文本处理库的内部实现中,例如:char32_t utf32Char = U'🌍';
(U
前缀表示UTF - 32编码的字符字面量)。
(二)类型修饰相关关键字
signed
:
与整型类型结合使用时,表示该整型是有符号的,意味着它能够表示负数、零和正数,例如signed int num = -10;
(实际上int
类型默认就是有符号的,这里明确写上是为了强调语义)。在内存中,最高位用于表示符号位(0表示正数,1表示负数)。unsigned
:
同样用于修饰整型类型,表明该类型是无符号的,此时它只能表示零和正数,所以能增大可表示的正数范围,常用于计数、表示内存地址等不可能出现负数的场景,例如:unsigned int count = 0;
。typedef
:
用于给已有的数据类型定义一个新的别名,以此增强代码的可读性和可移植性。比如:typedef int MyInt;
,定义之后就可以使用MyInt
来替代int
声明变量,像MyInt var = 5;
,在复杂的代码结构或者跨平台开发中,合理使用typedef
可以让代码更易于理解和维护。using
:
在C++11之后引入,功能与typedef
类似,也是用于类型别名的定义,但语法更加灵活。例如:using NewInt = int;
,同样可以用NewInt
来声明变量,而且在模板别名等复杂场景下使用起来更加方便,例如:using IntList = std::vector<int>;
(定义了一个std::vector<int>
的别名IntList
)。const
:
常量修饰符,用于声明常量,一旦被声明为const
的变量,在其作用域内禁止修改其值,常用于定义那些在程序运行过程中不应该被改变的数据,例如:const int MAX_VALUE = 100;
,这样MAX_VALUE
的值就不能再被重新赋值了,编译器会对试图修改它的操作报错,有助于提高代码的安全性和可维护性。register
:
表示请求编译器将变量存储在CPU寄存器中,目的是提高变量的访问速度,因为寄存器的读写速度比内存快很多。不过在现代编译器中,编译器通常会自动根据变量的使用频率等因素来决定是否将变量放入寄存器,所以这个关键字很多时候会被编译器忽略,例如:register int fastVar;
,但在一些对性能极致优化且开发者对硬件架构比较了解的特定场景下,仍可能会使用它来给编译器提供优化提示。auto
:
从C++11开始重新定义了其功能,用于让编译器自动推导变量的类型,能够极大地简化代码编写,尤其是在使用复杂的模板类型或者迭代器等场景下。例如:auto num = 10;
,这里编译器会自动推导出num
的类型为int
;再比如对于std::vector<int>::iterator it = vec.begin();
,可以写成auto it = vec.begin();
,让代码更加简洁明了。
(三)程序控制相关关键字
if
:
最基本的条件判断语句,根据给定的条件表达式的真假来决定是否执行相应的代码块。条件表达式通常是一个能够返回布尔值(true
或者false
)的表达式,例如:int num = 5; if (num > 3) { std::cout << "num大于3" << std::endl; }
else
:
常与if
语句配合使用,当if
语句中的条件不满足(即条件表达式的值为false
)时,就会执行else
后面的代码块,用于提供另一种执行路径,例如:int num = 2; if (num > 3) { std::cout << "num大于3" << std::endl; } else { std::cout << "num小于等于3" << std::endl; }
switch
:
用于多分支的条件选择场景,它会根据一个表达式的值来跳转到相应的case
分支去执行代码。表达式的值必须是整型、枚举类型或者是能隐式转换为整型的类型,例如:int day = 2; switch (day) { case 1: std::cout << "星期一" << std::endl; break; case 2: std::cout << "星期二" << std::endl; break; // 其他case分支 default: std::cout << "无效的日期" << std::endl; }
case
:
在switch
语句中用于定义具体的分支情况,每个case
后面跟着一个常量表达式,当switch
语句中的表达式的值与之匹配时,就会执行该case
下的代码,直到遇到break
等跳转语句,例如上述switch
语句中的各个case
分支定义。default
:
在switch
语句中用于定义默认分支,当switch
表达式的值与所有的case
常量表达式都不匹配时,就会执行default
分支下的代码,用于处理一些意外或者未考虑到的情况,保证程序的逻辑完整性,如上述switch
示例中的default
部分。break
:
用于跳出当前所在的循环(比如for
、while
、do - while
循环)或者switch
语句,提前结束相应的执行流程,避免多余的循环迭代或者执行不必要的switch
分支代码,例如在for
循环中达到某个条件后跳出循环:for (int i = 0; i < 10; i++) { if (i == 5) { break; } std::cout << i << std::endl; }
continue
:
在循环语句(for
、while
、do - while
)中使用,它会跳过本次循环剩余的语句,直接进入下一次循环的条件判断环节,常用于在循环中排除某些不符合要求的值继续进行下一轮循环,例如在循环中跳过偶数:for (int i = 0; i < 10; i++) { if (i % 2 == 0) { continue; } std::cout << i << std::endl; }
return
:
用于从函数中返回值并结束函数的执行。如果函数的返回类型为void
,则可以直接使用return;
来结束函数;如果函数有具体的返回类型,那么就需要返回对应类型的值,例如:int add(int a, int b) { return a + b; }
for
:
一种常用的循环结构,通常适用于已知循环次数的情况,它的语法格式为for(初始化; 条件判断; 迭代更新)
,初始化部分用于定义循环变量并赋初值,条件判断部分决定是否继续执行循环体,迭代更新部分用于在每次循环结束后对循环变量进行更新操作,例如:for (int i = 0; i < 10; i++) { std::cout << i << std::endl; }
while
:
只要条件表达式为真,就会不断执行循环体中的代码,适用于事先不确定循环次数,而是根据某个条件来决定是否继续循环的场景,例如:int num = 0; while (num < 10) { std::cout << num << std::endl; num++; }
do
:
和while
类似,但它先执行一次循环体,再判断条件表达式,这样能保证循环体至少会被执行一次,格式为do { 循环体 } while(条件表达式);
,例如:int num = 0; do { std::cout << num << std::endl; num++; } while (num < 10);
goto
:
可以跳转到程序中指定的标签位置,不过由于它会破坏程序的结构化流程,使得代码的可读性和可维护性变差,所以在实际编程中通常要尽量避免使用,例如:int num = 0; start: num++; if (num < 10) { goto start; }
(四)异常处理相关关键字
try
:
用于异常处理,标记可能抛出异常的代码块,在这个代码块内如果发生了异常,程序会立即停止当前代码的执行,转而去查找与之匹配的catch
块来处理异常,例如:try { // 可能抛出异常的代码,比如动态内存分配失败、文件读取错误等情况 int* ptr = new int[1000000000]; } catch (...) { std::cerr << "发生异常" << std::endl; }
catch
:
与try
配套使用,用于捕获异常,并定义相应的处理逻辑。可以有多个catch
块,每个catch
块可以针对不同类型的异常进行处理,例如:try { throw std::runtime_error("运行时错误"); } catch (const std::runtime_error& e) { std::cerr << "捕获到运行时错误: " << e.what() << std::endl; } catch (...) { std::cerr << "捕获到其他未知异常" << std::endl; }
throw
:
用于在程序中抛出异常,异常可以是基本数据类型、对象等各种类型,通过抛出异常来通知程序的其他部分出现了错误或者意外情况,需要进行相应处理,例如:throw 10;
(抛出一个int
类型的异常值)或者throw std::string("出现错误");
(抛出一个std::string
类型的异常对象)。
(五)函数相关关键字
-
void
:
常用于函数声明中,表示函数没有返回值,例如:void printHello();
声明了一个名为printHello
的函数,它执行完相应操作后不会返回任何数据给调用者;同时void
还可以作为指针类型,表示通用的指针,能够指向任意类型的数据,例如:void* ptr;
,不过在使用这种通用指针时,需要进行适当的类型转换才能正确操作所指向的数据。 -
static
:
当用于函数内部变量声明时,该变量在函数多次调用间会保留其值,具有静态生存期,它只会被初始化一次,例如:void func() { static int count = 0; count++; std::cout << count << std::endl; }
每次调用
func
函数,count
的值都会在上一次调用结束后的基础上继续变化。用于类的成员变量时,所有该类的对象共享这一变量,意味着无论创建了多少个类的对象,这个静态成员变量只有一份,在类的不同对象之间是共享的,例如:
class MyClass { public: static int count; }; int MyClass::count = 0;
用于类的成员函数时,表示该函数不依赖于具体的对象实例,可以通过类名直接调用,常用于实现一些与类的整体状态相关而不依赖于具体对象个体的操作,例如:
class MyClass { public: static void printCount() { std::cout << count << std::endl; } private: static int count; }; int MyClass::count = 0;
-
new
:
用于在堆上动态分配内存,返回所分配内存的指针,可以用来创建对象或者基本数据类型的数组等。例如:- 分配单个
int
类型的内存空间:int* ptr = new int;
,之后可以通过*ptr
来操作这块内存空间。 - 分配包含10个
int
元素的数组内存空间:int* arr = new int[10];
,然后可以像操作普通数组一样使用arr[i]
(i
取值范围为0到9)来访问和修改数组元素。
- 分配单个
-
delete
:
与new
配套使用,用于释放由new
分配的内存空间,要注意正确匹配使用,对于单个对象用delete ptr;
,对于数组要用delete[] arr;
,否则可能导致内存泄漏等问题,例如:int* ptr = new int; *ptr = 10; delete ptr; int* arr = new int[5]; for (int i = 0; i < 5; i++) { arr[i] = i; } delete[] arr;
-
sizeof
:
用于获取数据类型或者变量所占用的字节数,是一个编译时确定的值,可用于了解不同数据类型在当前系统下的内存占用情况,或者计算数组、结构体等复合类型的大小,例如:std::cout << sizeof(int) << std::endl;
会输出int
类型在当前系统下占用的字节数;对于自定义结构体也可以获取其大小,比如:struct Point { int x; int y; }; std::cout << sizeof(Point) << std::endl;
-
extern
:
表示变量或函数是在其他文件中定义的,常用于多文件项目中跨文件的变量或函数引用,通过extern
声明可以让当前文件知道某个变量或函数在其他地方已经定义了,从而可以使用它们。
例如在一个文件(比如main.cpp
)中声明:extern int globalVar;
然后在另一个文件(比如
other.cpp
)中进行定义int globalVar = 10;
,这样在main.cpp
中就能使用这个在other.cpp
中定义的全局变量了。对于函数也是类似的用法,比如在头文件中声明extern void someFunction();
,然后在对应的源文件中去定义这个函数具体的实现内容。
(六)面向对象相关关键字
class
:
用于声明类,类是面向对象编程的基础构建块,它将数据(成员变量)和操作这些数据的函数(成员函数)封装在一起,形成一个独立的抽象数据类型。例如:
通过创建类的对象(如class Person { public: int age; void sayHello() { std::cout << "Hello!" << std::endl; } };
Person p;
),就可以访问类中的成员变量(p.age
)和调用成员函数(p.sayHello()
)来实现相应的功能,体现了面向对象中数据和操作的封装性。public
:
访问控制符,表示类的成员(成员变量和成员函数)对外公开,即可以在类的外部通过对象直接访问,是类对外提供接口的常见方式,方便其他代码与该类进行交互,例如:class Person { public: int age; void sayHello() { std::cout << "Hello!" << std::endl; } }; Person p; p.age = 25; p.sayHello();
protected
:
访问控制符,表示类的成员在类内可以访问,同时在派生类(子类)中也能访问,主要用于实现类的继承体系中的访问控制,能够让子类继承并访问父类中一些不希望对外完全公开的成员,例如:class Base { protected: int protectedData; }; class Derived : public Base { public: void accessProtected() { protectedData = 10; } };
private
:
访问控制符,表示类的成员只能在类内访问,外界无法直接访问,用于隐藏类的内部实现细节,保证类的封装性,使得类内部的数据和操作可以按照设计的逻辑进行管理,避免外部随意修改而导致错误,例如:
外部代码不能直接访问class BankAccount { private: double balance; public: void deposit(double amount) { balance += amount; } };
balance
,只能通过类提供的deposit
等公有函数来间接操作账户余额。explicit
:
用于防止编译器进行隐式类型转换。当构造函数使用了explicit
关键字修饰时,在进行初始化或者赋值操作时,编译器不会自动将其他类型转换为该类类型,除非使用显式的构造函数调用形式,例如:class MyClass { public: explicit MyClass(int num) {} }; // 以下代码会报错,因为不能隐式转换 // MyClass obj = 10; // 正确的做法是显式调用构造函数 MyClass obj(10);
friend
:
声明友元函数或类,允许外部的函数或类访问本类的私有成员,打破了类的访问限制,常用于需要在类外部方便地访问类内部数据的特殊情况,比如实现一些操作符重载函数或者与其他类有紧密关联的功能时,例如:
这里class Point { private: int x, y; friend void printPoint(const Point& p); }; void printPoint(const Point& p) { std::cout << "(" << p.x << ", " << p.y << ")" << std::endl; }
printPoint
函数作为Point
类的友元函数,可以访问Point
类的私有成员x
和y
来实现打印坐标的功能。operator
:
用于重载运算符,使得自定义类型可以像基本数据类型一样使用相应的运算符进行操作,增强了自定义类型在表达式中的使用灵活性和直观性。比如重载+
运算符实现两个自定义类对象的相加逻辑,示例:
这样就可以像class Complex { public: double real, imag; Complex operator+(const Complex& other) { Complex result; result.real = real + other.real; result.imag = imag + other.imag; return result; } };
Complex c1, c2; Complex c3 = c1 + c2;
这样使用+
运算符来操作Complex
类对象了。virtual
:
用于声明虚函数,支持多态性。在基类中声明为虚函数后,当通过基类指针或引用调用该虚函数时,会根据对象的实际类型来决定调用哪个派生类中重写的虚函数,实现了运行时的动态绑定,例如:
虚函数可以被派生类通过class Shape { public: virtual void draw() { std::cout << "Drawing a shape" << std::endl; } }; class Circle : public Shape { public: void draw() override { std::cout << "Drawing a circle" << std::endl; } }; Shape* shape = new Circle(); shape->draw(); // 会调用 Circle 类中重写的 draw 函数
override
标识符标记为重写,这样编译器可以帮忙检查是否正确重写了虚函数(函数签名等是否匹配),增强代码的健壮性。当一个类中包含纯虚函数(形式为virtual void func() = 0;
)时,类变为抽象类,无法直接实例化,抽象类通常作为基类,用于定义派生类共有的接口规范。this
:
指向当前对象的指针,在类的成员函数内部,this
指针自动指向调用该函数的那个对象本身,常用于区分成员变量和局部变量同名的情况,或者在返回当前对象时使用,例如:
这里class Person { public: int age; Person& setAge(int a) { this->age = a; return *this; } };
this->age
明确表示访问的是类的成员变量age
,而不是可能存在的同名局部变量,并且通过return *this
可以实现方法链调用(如p.setAge(20).setAge(25);
)。mutable
:
允许在常成员函数(被const
修饰的成员函数)中修改成员变量的值,通常用于那些虽然成员函数被声明为const
,但仍有一些内部状态需要改变的情况,例如:
即使class MyClass { public: mutable int count; void print() const { count++; std::cout << count << std::endl; } };
print
函数是const
的,也能对mutable
修饰的count
变量进行修改。namespace
:
命名空间,用于组织代码和避免命名冲突。在大型项目中,不同的模块或者库可能会定义相同名称的类、函数等,通过使用命名空间可以将它们区分开来,例如:
使用时可以通过namespace Math { int add(int a, int b) { return a + b; } } namespace Graphics { class Point { //... }; }
Math::add(3, 5)
或者using namespace Math; add(3, 5);
(后者将Math
命名空间下的名称引入当前作用域)等方式来调用相应的函数或访问类等。
(七)结构和枚举相关关键字
union
:
定义联合体,它允许在同一个内存空间存储不同类型的数据,但在某一时刻只能存储其中一种类型的数据,能节省内存空间,常用于一些对内存布局有特殊要求或者需要根据不同情况复用同一块内存的场景,例如:union Data { int num; char ch; }; Data data; data.num = 10; // 此时若再给 data.ch 赋值,那么 data.num 的值就会被覆盖,因为它们共用同一块内存 data.ch = 'A';
enum
:
定义枚举类型,表示一组具名的常量,使代码更具可读性,通过使用枚举可以用有意义的名称来代替一些魔法数字或者固定的常量值,例如:enum Color { RED, GREEN, BLUE }; Color myColor = GREEN; switch (myColor) { case RED: std::cout << "红色" << std::endl; break; case GREEN: std::cout << "绿色" << std::endl; break; case BLUE: std::cout << "蓝色" << std::endl; break; }
struct
:
定义结构体,成员默认是公共的,常用于简单的数据结构组合,将相关的数据成员放在一起方便管理和传递,和class
类似但在访问控制方面有默认差异,例如:struct Point { int x; int y; }; Point p = {1, 2}; std::cout << "坐标为: (" << p.x << ", " << p.y << ")" << std::endl;
(八)模板相关关键字
template
:
声明模板,支持泛型编程,使得可以编写与类型无关的代码,能够提高代码的复用性和通用性。例如函数模板可以用来创建适用于多种数据类型的函数,像:
模板还可以用于类的定义等场景,比如定义一个通用的链表类模板,根据不同的类型参数实例化出不同类型元素的链表。template <typename T> T add(T a, T b) { return a + b; } // 可以使用不同类型调用 add 函数 int result1 = add(3, 5); double result2 = add(3.14, 2.71);
typename
:
用于声明模板类型参数,表示后面的标识符是一个类型名,在某些复杂的模板定义中,用于明确指出依赖名称是类型,避免编译器产生歧义,例如:
这里通过template <typename T> void func() { typename T::iterator it; }
typename
明确告诉编译器T::iterator
是一个类型,以便正确编译代码,尤其在模板参数T
所代表的类型有内部嵌套类型的情况下很关键。requires
:
用于约束模板参数,指定模板函数或类的特定要求,通常用于概念(concepts)中,从 C++20 开始引入,它可以让模板代码更加安全和可读,通过定义清晰的类型要求来限制模板实例化的条件,例如:
这里定义了template <typename T> concept Addable = requires(T a, T b) { a + b; }; template <typename T> requires Addable<T> T add(T a, T b) { return a + b; }
Addable
概念,要求类型T
能够进行+
运算,只有满足这个要求的类型才能作为add
函数模板的参数类型。concept
:
C++20 引入的关键字,用于定义类型的要求或条件,配合requires
一起使用,能够让模板编程在编译阶段就进行更严格的类型检查,避免一些不符合预期的模板实例化情况,使得代码的逻辑更加严谨,比如可以定义多个不同的概念来描述不同的类型特征,然后应用到各种模板函数和类模板中,就像上面Addable
概念的例子一样。
(九)常量和类型相关关键字
inline
:
声明内联函数,提示编译器将函数代码插入到调用处,这样可以减少函数调用的开销(如压栈、跳转等操作),一般用于短小的、频繁调用的函数,不过最终是否真正内联还是由编译器根据实际情况决定,例如:
在编译优化较好的情况下,调用inline int add(int a, int b) { return a + b; }
add
函数的地方可能就直接替换成函数体的代码了,提高了程序的执行效率。volatile
:
用于表示变量可能会被外部因素(如硬件设备、多线程环境中的其他线程等)修改,防止编译器对该变量进行优化,保证每次访问该变量时都从内存中读取最新的值,常用于嵌入式开发、多线程编程中与共享变量相关等场景,例如:volatile int sensorValue; // 可能在其他地方(比如中断处理函数中)会修改 sensorValue 的值, // 编译器不会对其进行常规的优化,每次使用都会从内存获取 while (sensorValue < 100) { // 进行相关处理 }
二、不常用关键字
(一)类型转换相关关键字
reinterpret_cast
:
用于进行底层类型转换,通常用于不同类型指针之间或者指针与整数之间的转换,这种转换很危险,容易导致未定义行为,因为它不进行任何类型安全检查,完全依赖于程序员对底层内存布局的了解,应谨慎使用,例如:int* ptr = reinterpret_cast<int*>(0x12345678);
(将一个地址值强制转换为int
指针),或者在不同类型的指针转换上,如将char*
转换为int*
(但要确保内存布局是符合预期的,否则会出现错误),像:char buffer[4] = {1, 2, 3, 4}; int* intPtr = reinterpret_cast<int*>(buffer); // 这里虽然进行了转换,但后续对 intPtr 的操作要非常小心,因为可能不符合实际内存语义
dynamic_cast
:
主要用于在类的继承体系中进行安全的向下转型(从基类指针或引用转换为派生类指针或引用),运行时会进行类型检查,如果转换失败会返回nullptr
(指针情况)或抛出异常(引用情况),常用于在多态场景下,根据对象的实际类型来准确获取派生类对象指针或引用,例如:class Shape { public: virtual void draw() {} }; class Circle : public Shape { public: void draw() override {} }; Shape* shape = new Circle(); Circle* circle = dynamic_cast<Circle*>(shape); if (circle) { circle->draw(); } else { std::cout << "转换失败" << std::endl; }
static_cast
:
用于常规的类型转换,是编译器能在编译时确定合法性的类型转换,比如基本数据类型之间的转换(在合理范围内)、派生类指针到基类指针的向上转型等,相对比较安全,但也要遵循类型转换的基本规则,例如:double num = static_cast<double>(10);
(将int
转换为double
),或者对于类的继承关系中向上转型:class Base {}; class Derived : public Base {}; Derived d; Base* basePtr = static_cast<Base*>(&d);
const_cast
:
用于去除变量的const
属性,不过只能用于修改那些原本不是真正不可变的数据(比如通过指针或引用指向的对象其本身在内存中是可变的情况),使用时需要小心避免违反const
的语义,因为它可能破坏程序原本期望的常量性,例如:const int num = 10; int* ptr = const_cast<int*>(&num); *ptr = 20; // 这种修改可能不符合设计初衷,要谨慎使用,并且如果 num 所在内存确实是不可变的(如在只读内存区域),会导致运行时错误
(二)编译时控制关键字
-
alignas
:
用于指定类型或变量的对齐要求,通过它可以强制要求数据在内存中的对齐方式,有助于提高内存访问效率,特别是在与硬件交互或者处理一些对内存布局敏感的结构体等情况时很有用,例如:struct alignas(16) AlignedStruct { int data; }; // 这里 AlignedStruct 类型的对象在内存中会按照 16 字节对齐, // 编译器会根据这个要求进行内存布局安排
-
alignof
:
获取类型的对齐要求,用于了解某个数据类型在当前系统和编译器下自然的内存对齐方式,可用于检查或者在一些动态内存分配等场景中参考对齐情况,例如:std::cout << alignof(int) << std::endl; // 输出 int 类型在当前环境下的对齐字节数 std::cout << alignof(AlignedStruct) << std::endl; // 输出上述自定义对齐结构体的对齐字节数
-
export
:
在C++20中用于模板的分离编译,但在许多编译器中尚未完全支持,其设计初衷是希望能够将模板的声明和定义分开放在不同的文件中,方便大型项目中对模板代码的组织和管理,然而由于实现的复杂性以及兼容性等问题,实际应用场景比较有限。理论上的用法示例如下:// header.h export template <typename T> void func(T t); // source.cpp #include "header.h" template <typename T> void func(T t) { // 函数具体实现 }
但目前很多时候还是倾向于采用传统的将模板定义直接放在头文件中的方式来确保在不同编译单元中能正确实例化模板。
-
static_assert
:
用于在编译时进行条件断言检查,如果条件不成立,编译器会生成错误,常用于模板编程中检查类型约束等情况,能帮助在编译阶段就发现代码中不符合预期要求的地方,避免问题遗留到运行时。例如:template <typename T> void func() { static_assert(sizeof(T) > 0, "Type must have a size"); }
这里要求模板参数类型
T
必须是有大小的,如果传入一个不符合要求的类型(比如一个不完整类型),编译时就会报错提示相应的错误信息,保证模板使用的正确性。
(三)C++11及以后引入的关键字
nullptr
:
表示空指针,从C++11开始引入,用于替代传统的NULL
(在C++中NULL
实际上是(void*)0
,可能会带来一些类型不匹配等问题),nullptr
类型更安全、语义更明确,在进行指针赋值、比较等操作时能避免一些潜在的错误。例如:int* ptr = nullptr;
,在函数参数传递、指针初始化等涉及指针为空的场景中都可以清晰准确地表示空指针状态,像:void func(int* ptr) { if (ptr == nullptr) { std::cout << "指针为空" << std::endl; } else { // 正常处理指针指向的数据 } }
noexcept
:
声明函数不会抛出异常,编译器可以基于此进行一些优化,比如可能省略一些异常处理相关的代码生成,同时也能让代码的异常处理逻辑更清晰,明确告知调用者该函数不会抛出异常,例如:void func() noexcept;
,如果在这个函数内部违反了noexcept
声明抛出了异常,会导致程序调用std::terminate
函数来终止程序执行,所以使用时要确保函数内部确实不会抛出异常,常用于一些对性能要求较高且异常情况可提前预知的函数定义中。thread_local
:
声明线程局部存储变量,每个线程有独立的副本,互不干扰,常用于多线程编程场景下每个线程需要独立维护的数据,比如每个线程的唯一标识、线程私有的计数器等。例如:thread_local int threadId;
,不同的线程在访问threadId
时,各自操作的是属于自己线程的那一份独立副本,像:
在此示例中,#include <iostream> #include <thread> thread_local int num = 0; void threadFunc() { num++; std::cout << "线程 " << std::this_thread::get_id() << " 中 num 的值为: " << num << std::endl; } int main() { std::thread t1(threadFunc); std::thread t2(threadFunc); t1.join(); t2.join(); return 0; }
t1
和t2
两个线程分别有自己的num
副本,各自的修改不会影响到其他线程中的值。constexpr
:
声明编译时常量,常用于模板和常量表达式,从C++11引入,它要求所修饰的函数或者变量在编译时就能计算出确定的值,这样的函数或变量可以用于需要常量表达式的地方,例如数组大小的定义等。例如:
对于变量来说,constexpr int square(int x) { return x * x; } int arr[square(5)]; // 因为 square 是 constexpr 函数,所以可以在这里用于定义数组大小
constexpr
修饰的变量必须有编译时可确定的初始值,像constexpr int num = 10;
,并且其后续不能被修改,和普通const
变量相比,更强调编译时就能求值的特性。decltype
:
用于推断表达式的类型,从C++11引入,它可以根据给定的表达式在编译时推导出其类型,方便在一些复杂的类型定义或者模板编程中使用,例如:
在模板中也很有用,比如:int x = 10; decltype(x) y = 20; // 这里 y 的类型会被推导为 int,和 x 的类型一致
template <typename T> void func(T t) { decltype(t) anotherVar; // 根据传入的参数 t 的类型来定义 anotherVar 的类型 }
atomic
:
C++11引入,用于处理原子操作,通常与std::atomic
一起使用,在多线程环境下保证操作的不可分割性,确保数据的一致性和正确性,避免出现数据竞争等问题。例如:std::atomic<int> atomicVar;
,可以对atomicVar
进行原子的读、写、自增、自减等操作,像:
这里通过#include <iostream> #include <thread> #include <atomic> std::atomic<int> atomicCounter(0); void incrementCounter() { for (int i = 0; i < 1000; i++) { atomicCounter++; } } int main() { std::thread t1(incrementCounter); std::thread t2(incrementCounter); t1.join(); t2.join(); std::cout << "原子计数器的值为: " << atomicCounter << std::endl; return 0; }
std::atomic
保证了多个线程对atomicCounter
进行自增操作时的原子性,不会出现因并发访问导致的数据错误。consteval
:
声明一个常量表达式函数,该函数必须在编译时立即求值的函数,从C++20引入,其参数必须是常量表达式,函数体中也只能包含常量表达式操作,例如:
如果试图用非常量表达式作为参数调用consteval int square(int x) { return x * x; } const int result = square(5); // 只能用常量表达式作为参数调用 consteval 函数,并且编译时就会计算出结果
consteval
函数,编译时就会报错,它进一步严格限制了函数求值的时机和使用方式,用于那些必须在编译阶段就能确定结果的场景。
(四)协程相关关键字(C++20)
-
co_await
:
用于协程,表示暂停执行,等待某个操作完成,是实现协程异步操作的关键关键字之一。它使得协程在执行到co_await
语句时可以让出执行权,暂停当前协程的执行,直到等待的操作完成后再继续执行后续代码,例如在进行异步网络请求或者异步文件读取等场景中,协程可以通过co_await
等待这些操作结束,像:#include <iostream> #include <experimental/coroutine> struct Awaitable { bool await_ready() { return false; } void await_suspend(std::experimental::coroutine_handle<> h) {} void await_resume() {} }; struct MyCoroutine { struct promise_type { MyCoroutine get_return_object() { return MyCoroutine(); } std::experimental::suspend_never initial_suspend() { return {}; } std::experimental::suspend_never final_suspend() { return {}; } void return_void() {} void unhandled_exception() {} }; std::experimental::coroutine_handle<> coro; bool resume() { if (!coro.done()) { coro.resume(); return true; } return false; } }; MyCoroutine my_coro() { std::cout << "协程开始" << std::endl; co_await Awaitable(); std::cout << "协程继续执行" << std::endl; } int main() { MyCoroutine coro = my_coro(); coro.resume(); coro.resume(); return 0; }
在这个简化示例中,协程执行到
co_await
处会暂停,通过手动调用resume
函数来恢复执行,展示了co_await
控制协程暂停和继续的基本用法。 -
co_return
:
用于协程,表示返回一个结果,并结束协程,它类似于普通函数中的return
关键字,但专门用于协程中,用于向协程的调用者返回数据并终止协程的执行,例如:#include <iostream> #include <experimental/coroutine> struct ReturnValue { int value; }; struct MyCoroutineWithReturn { struct promise_type { ReturnValue get_return_object() { return ReturnValue{0}; } std::experimental::suspend_never initial_suspend() { return {}; } std::experimental::suspend_never final_suspend() { return {}; } ReturnValue return_value(int v) { return ReturnValue{v}; } void return_void() {} void unhandled_exception() {} }; std::experimental::coroutine_handle<> coro; ReturnValue resume() { if (!coro.done()) { coro.resume(); return coro.promise().get_return_object(); } return ReturnValue{0}; } }; MyCoroutineWithReturn my_coro_with_return() { std::cout << "协程开始" << std::endl; co_return ReturnValue{10}; } int main() { MyCoroutineWithReturn coro = my_coro_with_return(); ReturnValue result = coro.resume(); std::cout << "协程返回值: " << result.value << std::endl; return 0; }
此示例中协程通过
co_return
返回一个包含整数值的ReturnValue
结构体,调用者可以获取到这个返回值。 -
co_yield
:
用于协程,表示暂时中断执行并返回某个值,稍后可以继续执行,它和co_await
有些类似都能暂停协程,但co_yield
更侧重于生成数据并暂停,常用于实现生成器类型的协程,例如可以用来按顺序生成一系列数值或者对象等,像:#include <iostream> #include <experimental/coroutine> struct Generator { struct promise_type { int current_value; Generator get_return_object() { return Generator(this); } std::experimental::suspend_always initial_suspend() { return {}; } std::experimental::suspend_always final_suspend() { return {}; } std::experimental::suspend_always yield_value(int value) { current_value = value; return {}; } void return_void() {} void unhandled_exception() {} }; std::experimental::coroutine_handle<> coro; bool move_next() { if (!coro.done()) { coro.resume(); return true; } return false; } int current() { return coro.promise().current_value; } }; Generator my_generator() { co_yield 1; co_yield 2; co_yield 3; } int main() { Generator gen = my_generator(); while (gen.move_next()) { std::cout << gen.current() << std::endl; } return 0; }
在这个示例中,通过
co_yield
协程依次生成了1、2、3这几个数值,外部循环通过不断调用move_next
函数来推进协程执行并获取每次co_yield
产生的值。
(五)其他关键字
-
asm
:
用于嵌入汇编代码,在一些对性能极致优化或者需要直接操作硬件底层的特殊场景下可能会用到,不过使用asm
会降低代码的可移植性和可读性,因为汇编语言是与具体的硬件架构相关的。例如在某些嵌入式开发中,为了精准控制硬件寄存器等,可以嵌入汇编代码,像(以下是一个简单的示例,实际根据不同架构会有很大差异):asm("movl $1, %eax"); // 将立即数1移动到寄存器eax中,这只是一个示意,不同平台汇编语法不同
-
bitand
、bitor
、compl
、and_eq
、or_eq
、xor_eq
:
这些是按位操作符的关键字形式,分别对应于&
(按位与)、|
(按位或)、~
(按位取反)、&=
(按位与赋值)、|=
(按位或赋值)、^=
(按位异或赋值)操作符,例如:int a = 5; // 二进制表示为 0101 int b = 3; // 二进制表示为 0011 int result = a bitand b; // 等同于 int result = a & b; 结果为 0001,即十进制的1 a and_eq b; // 等同于 a &= b; 此时 a 的值变为 0001,即十进制的1
使用这些关键字形式可以在某些特定的代码风格或者编程场景下,让代码看起来更具一致性或者符合特定的命名规范要求,但实际编程中按位操作符的符号形式使用更为普遍。
-
and
、or
、not
:
逻辑操作符的关键字形式,分别与&&
(逻辑与)、||
(逻辑或)、!
(逻辑非)等价,例如:bool a = true; bool b = false; if (a and b) { // 等同于 if (a && b) // 这里不会执行,因为逻辑与结果为假 } if (a or b) { // 等同于 if (a || b) // 这里会执行,因为逻辑或结果为真 } if (not b) { // 等同于 if (!b) // 这里会执行,因为逻辑非后 b 的值变为真 }
这些关键字形式在某些特定的编程风格或者遵循特定代码规范要求时会被使用,不过在日常的C++编程中,符号形式(
&&
、||
、!
)的逻辑操作符应用更为广泛和常见,因为它们简洁且符合大多数开发者的习惯。 -
xor
、not_eq
:
分别表示按位异或和不等于操作符的关键字形式,xor
对应于^
(按位异或)操作符,not_eq
对应于!=
(不等于)操作符。例如:int num1 = 5; // 二进制为 0101 int num2 = 3; // 二进制为 0011 int result = num1 xor num2; // 等同于 int result = num1 ^ num2,结果为 0110,即十进制的6 int a = 5; int b = 3; if (a not_eq b) { // 等同于 if (a!= b) std::cout << "a和b不相等" << std::endl; }
同样,虽然有这些关键字形式存在,但在实际编写代码时,符号形式的操作符往往使用得更为普遍,它们更符合人们常规的编程认知和书写习惯。不过在一些代码风格统一要求或者特定的历史遗留代码等场景中,也可能会见到对这些关键字形式的运用。
-
atomic_cancel
、atomic_commit
、atomic_noexcept
:
原子操作相关关键字,它们与atomic
关键字配合使用,用于更精细地控制原子操作的行为,不过使用场景相对较专业和特定,例如在一些复杂的并发控制逻辑中,当涉及到对原子操作的事务性处理(类似数据库中的事务概念,需要保证一组操作要么全部成功要么全部失败)时可能会用到这些关键字,具体的使用方式和语义依赖于具体的标准库实现以及应用场景,目前应用相对不是那么广泛。 -
synchronized
:
用于同步代码块,其作用类似于在多线程编程中使用互斥锁来保证在同一时刻只有一个线程能访问被synchronized
修饰的代码块或方法,但在C++中,通常使用std::mutex
等机制来实现同步,synchronized
在C++中的使用并不像在Java等语言中那样普遍,不过在一些特定的库或者框架中可能会提供类似的基于这个关键字的同步功能实现,用于确保多线程环境下数据的一致性和操作的原子性,例如(假设存在这样的实现):synchronized { // 这里是需要同步访问的代码块,同一时刻只有一个线程能进入执行 }
-
typeid
:
用于获取对象的类型信息,通常与std::type_info
类一起使用,可以用来判断对象的实际类型,在运行时进行类型识别等操作,不过要注意它是基于对象的动态类型(即实际的对象类型,在多态情况下可能和指针或引用的声明类型不同)来获取信息的。例如:#include <iostream> #include <typeinfo> class Base {}; class Derived : public Base {}; int main() { Derived d; const std::type_info& t = typeid(d); std::cout << t.name() << std::endl; Base* b = &d; const std::type_info& t2 = typeid(*b); std::cout << t2.name() << std::endl; return 0; }
在这个示例中,通过
typeid
分别获取了Derived
类对象本身以及通过基类指针指向Derived
类对象时的类型信息,输出的结果通常是与编译器相关的类型名称表示形式(不同编译器可能有不同的具体输出内容)。
总之,C++的这96个关键字各有其独特的用途和语法规则,熟练掌握它们对于深入理解和高效运用C++语言进行程序开发起着至关重要的作用,无论是构建简单的控制台应用,还是复杂的大型系统、游戏开发、嵌入式项目等不同领域的编程工作,都离不开对这些关键字的准确运用以及它们之间相互配合所形成的语言逻辑和功能实现机制。 同时,随着C++标准的不断演进,新的关键字也在不断被引入,开发者需要持续学习来适应语言的发展,更好地发挥C++语言强大的编程能力。