目录
常量
字面常量
整数常量
浮点常量
布尔常量
字符常量
符号常量
const关键字
宏定义
修饰符类型
类型限定符
存储类
auto 存储类
register 存储类(被弃用)
static 存储类
extern 存储类
mutable 存储类
thread_local 存储类
在C++中,常量是指在程序执行过程中其值不会发生改变的数据。常量可以分为字面常量和符号常量。
字面常量是直接出现在程序中的具体数值,它们的值在编译时就已经确定。例如:
在C++中,整数常量可以使用不同的进制表示:十进制、八进制和十六进制。
整数常量还可以带有后缀来指定其类型,后缀可以是U和L的组合,大小写不敏感。其中,U表示无符号整数,L表示长整数。
例如:
需要注意的是,在编程中,如果整数常量超出了特定类型的表示范围,可能会导致溢出错误。因此,在选择整数常量时应该谨慎考虑。
在C++中,浮点常量是由整数部分、小数点、小数部分和指数部分组成。
浮点常量可以使用小数形式或指数形式来表示。
小数形式的浮点常量需要包含整数部分、小数点、小数部分,或者同时包含这两者。例如:3.14159。
指数形式的浮点常量需要包含小数点、指数,或者同时包含这两者,并且可以带有可选的符号。指数形式使用字母 'e' 或 'E' 引入指数部分。例如:314159E-5L,表示的是3.14159乘以10的负5次方。
需要注意的是,浮点常量必须是完整的,如果缺少整数部分、小数部分或指数部分,将会导致语法错误。
在C++中,布尔常量只有两个,分别是true和false,它们都是关键字。其中true代表真,false代表假。
布尔类型用于表示逻辑值,通常用于条件测试和控制流程。在条件测试中,如果布尔表达式的值为true,则执行相关代码,否则跳过。
需要注意的是,虽然true的值可以在某些情况下被解释为1,false的值可以被解释为0,但是我们不应该直接把它们看成数值。因为布尔类型只有真和假两种状态,没有其他的数值含义。
字符常量是括在单引号中。如果常量以 L(仅当大写时)开头,则表示它是一个宽字符常量(例如 L'x'),此时它必须存储在 wchar_t 类型的变量中。否则,它就是一个窄字符常量(例如 'x'),此时它可以存储在 char 类型的简单变量中。
字符常量可以是一个普通的字符(例如 'x')、一个转义序列(例如 '\t'),或一个通用的字符(例如 '\u02C0')。
在 C++ 中,有一些特定的字符,当它们前面有反斜杠时,它们就具有特殊的含义,被用来表示如换行符(\n)或制表符(\t)等。下表列出了一些这样的转义序列码:
转义序列 | 含义 |
---|---|
\\ | \ 字符 |
\' | ' 字符 |
\" | " 字符 |
\? | ? 字符 |
\a | 警报铃声 |
\b | 退格键 |
\f | 换页符 |
\n | 换行符 |
\r | 回车 |
\t | 水平制表符 |
\v | 垂直制表符 |
\ooo | 一到三位的八进制数 |
\xhh . . . | 一个或多个数字的十六进制数 |
以下是一些关于字符常量和转义序列的示例:
#include
int main() {
// 普通字符常量
char c1 = 'A';
std::cout << c1 << std::endl; // 输出: A
// 转义序列字符常量
char c2 = '\t'; // 制表符
std::cout << "Hello" << c2 << "World" << std::endl; // 输出: Hello World
// 宽字符常量
wchar_t wc = L'中';
std::wcout << wc << std::endl; // 输出: 中
// 转义序列在字符串字面值中的应用
std::cout << "This is a \"quoted\" string." << std::endl; // 输出: This is a "quoted" string.
std::cout << "This is a backslash: \\" << std::endl; // 输出: This is a backslash: \
return 0;
}
以上示例演示了如何使用普通字符常量、转义序列和宽字符常量。通过使用转义序列,我们可以插入特殊字符或实现格式化输出。同时,通过在字符串字面值中使用转义序列,我们可以插入引号或反斜杠等特殊字符。
符号常量:符号常量是通过标识符来表示的,其值在程序运行过程中不可修改。在C++中定义符号常量通常使用const关键字或者宏定义(#define)。例如:
在C++中,可以使用const关键字来定义符号常量。const关键字用于声明一个不可修改的变量,即常量。它可以用于各种数据类型,包括基本数据类型、自定义类型以及指针类型。
下面是使用const关键字定义符号常量的示例:
#include
int main() {
const int MAX_VALUE = 100;
const double PI = 3.14159;
const std::string MESSAGE = "Hello, world!";
std::cout << MAX_VALUE << std::endl;
std::cout << PI << std::endl;
std::cout << MESSAGE << std::endl;
return 0;
}
在上述示例中,我们使用const关键字定义了三个不可修改的符号常量:MAX_VALUE是一个整数常量,PI是一个双精度浮点数常量,MESSAGE是一个字符串常量。注意,符号常量的名称通常使用全大写字母来表示,以增加其可读性。
在C++中,宏定义是一种预处理指令,通过#define关键字来创建简单的替换文本。尽管宏定义可以用来定义符号常量,但它们没有类型检查,并且可能导致一些潜在的问题。因此,在C++中,推荐使用const关键字来定义符号常量,因为它提供了类型检查和更好的代码安全性。
以下是使用宏定义定义符号常量的示例:
#include
#define MAX_VALUE 100
#define PI 3.14159
#define MESSAGE "Hello, world!"
int main() {
std::cout << MAX_VALUE << std::endl;
std::cout << PI << std::endl;
std::cout << MESSAGE << std::endl;
return 0;
}
在上述示例中,我们使用#define指令创建了三个宏定义,将它们分别替换为对应的值。然后,在主函数中使用这些宏定义。
需要注意的是,宏定义没有作用域的概念,它们是在预处理阶段进行简单的文本替换,可能会导致意外的副作用。因此,在C++中,推荐使用const关键字来定义符号常量。
使用常量的好处是可以提高代码的可读性和维护性,同时避免了不必要的错误。在程序中,常量可以像变量一样使用,但不能对其进行修改。
C++ 允许在 char、int 和 double 数据类型前放置修饰符。
修饰符是用于改变变量类型的行为的关键字,它更能满足各种情境的需求。
下面列出了数据类型修饰符:
修饰符 signed、unsigned、long 和 short 可应用于整型,signed 和 unsigned 可应用于字符型,long 可应用于双精度型。这些修饰符也可以组合使用,修饰符 signed 和 unsigned 也可以作为 long 或 short 修饰符的前缀。
例如:unsigned long int。C++ 允许使用速记符号来声明无符号短整数或无符号长整数。您可以不写 int,只写单词 unsigned、short 或 long,int 是隐含的。
下面是一些使用修饰符的示例:
#include
int main() {
signed int signedVariable = -10; // 可以存储负数
unsigned int unsignedVariable = 15; // 不能存储负数
short int shortVariable = 100; // 范围比 int 更小
long int longVariable = 1000; // 范围比 int 更大
long long int longLongVariable = 10000; // 范围比 long 更大
float floatVariable = 3.14; // 单精度浮点数
double doubleVariable = 3.1415; // 双精度浮点数
bool boolVariable = true; // 布尔类型,只有 true 和 false
char charVariable = 'A'; // 字符类型
wchar_t wideCharVariable = L'中'; // 宽字符类型,可以存储 Unicode 字符
std::cout << "signedVariable: " << signedVariable << std::endl;
std::cout << "unsignedVariable: " << unsignedVariable << std::endl;
std::cout << "shortVariable: " << shortVariable << std::endl;
std::cout << "longVariable: " << longVariable << std::endl;
std::cout << "longLongVariable: " << longLongVariable << std::endl;
std::cout << "floatVariable: " << floatVariable << std::endl;
std::cout << "doubleVariable: " << doubleVariable << std::endl;
std::cout << "boolVariable: " << boolVariable << std::endl;
std::cout << "charVariable: " << charVariable << std::endl;
std::wcout << "wideCharVariable: " << wideCharVariable << std::endl; // 注意使用 std::wcout 输出宽字符
return 0;
}
在上述示例中,我们演示了如何使用不同的修饰符类型来声明不同的变量。signed 和 unsigned 可以应用于整型,long 可以应用于双精度型。修饰符也可以组合使用,例如 unsigned long int。
C++中的类型限定符用于在定义变量或函数时改变它们的默认行为。下面是常见的C++类型限定符:
1、const:const关键字用于定义常量,表示该变量的值不能被修改。例如:
const int NUM = 10; // 定义常量NUM,其值不可修改
const int* ptr = &NUM; // 定义指向常量的指针,指针所指的值不可修改
2、volatile:volatile修饰符告诉编译器该变量的值可能会被程序以外的因素改变,如硬件或其他线程。这可以防止编译器进行某些优化,以确保每次访问该变量时都从内存中获取最新的值。例如:
volatile int num = 20; // 定义变量num,其值可能会在未知的时间被改变
3、restrict:restrict修饰的指针是唯一一种访问它所指向的对象的方式。该限定符只在C99标准中引入。它用于告诉编译器,通过这个指针对指向的内存进行的读写操作不会与其他指针冲突,从而进行更多的优化。例如:
int* restrict ptr = nullptr; // 声明一个restrict修饰的指针
4、mutable:mutable关键字用于类中的成员变量,表示这些成员变量可以在const成员函数中被修改。默认情况下,const成员函数不能修改对象的数据成员,但使用mutable关键字可以例外。例如:
class Example {
public:
int get_value() const {
return value_; // const成员函数不会修改对象中的数据成员
}
void set_value(int value) const {
value_ = value; // mutable关键字允许在const成员函数中修改成员变量
}
private:
mutable int value_;
};
5、static:static关键字用于定义静态变量,表示该变量的作用域仅限于当前文件或当前函数内,不会被其他文件或函数访问。静态变量在程序运行期间只有一份实例化的副本,即使函数多次调用,其值也会保持。例如:
void example_function() {
static int count = 0; // static关键字使变量count存储在程序生命周期内都存在
count++;
}
6、register:register关键字用于定义寄存器变量,表示该变量被频繁使用,可以存储在CPU的寄存器中,以提高程序的运行效率。然而,现代编译器通常会自动优化寄存器变量,因此使用register关键字并不一定能够强制将变量存储在寄存器中。例如:
void example_function(register int num) {
// register关键字建议编译器将变量num存储在寄存器中
// 以提高程序执行速度
// 实际上是否会存储在寄存器中由编译器决定
}
这些类型限定符提供了额外的语义信息,可以帮助开发人员更好地控制变量的行为和性能。
根据C++标准,auto关键字在C++11及以后的版本中有两种用法。
1、类型推断:在变量声明时,根据初始化表达式自动推断变量的类型。例如:
auto f = 3.14; // 推断为double类型
auto s = "hello"; // 推断为const char*类型
auto z = new auto(9); // 推断为int*类型
2、函数返回类型占位符:在函数声明时,用auto作为函数返回类型的占位符,编译器会根据函数体中的返回语句推断函数的返回类型。例如:
auto add(int a, int b) -> int {
return a + b;
}
需要注意的是,C++11之前的标准并不支持函数返回类型占位符的使用,而是使用了其他方式来实现相似的功能,例如使用模板或尾返回类型声明。
总结起来,C++11以后的auto关键字用于类型推断和函数返回类型占位符两种情况。类型推断用于变量声明时根据初始化表达式自动推断变量的类型,函数返回类型占位符用于函数声明时作为返回类型的占位符,由编译器根据函数体中的返回语句推断实际的返回类型。
注意:C++98标准中auto关键字用于自动变量的声明,但由于使用极少且多余,在 C++17 中已删除这一用法。
register是一个C++中的存储类说明符,它被用来建议编译器将变量存储在寄存器中以获得更快的访问速度。然而,现代编译器通常已经足够聪明,能够自动将需要频繁访问的变量存储到寄存器中,因此使用register关键字的效果可能不会像预期的那样有效。
register关键字的语法如下:
register int count;
在这里,count变量被建议存储在寄存器中。但是,编译器可以自行决定是否实际将该变量存储在寄存器中,因为在某些情况下,寄存器可能已被分配用于其他目的。因此,使用register关键字不一定能够获得性能上的提升。
需要注意的是,从C++17开始,register 关键字被弃用。编译器不再需要严格遵循register关键字的建议,它可能会忽略该关键字并将变量存储在内存中而不是寄存器中。这意味着register关键字的使用已经不再具有实际意义,并且在现代C++代码中应该避免使用。
当然,编译器仍然可以选择将变量存储在寄存器中,但它不再受到register关键字的强制要求。由于现代编译器通常能够智能地进行寄存器分配优化,因此人工使用register关键字的效果往往不如预期。
static 存储类可以用于修饰局部变量、全局变量和类成员变量,具体含义如下:
注意:
#include
void foo() {
static int count = 0; // 静态局部变量,保持其值在函数调用之间
count++;
std::cout << "Count: " << count << std::endl;
}
static int x = 10; // 静态全局变量,只能在该文件内访问
void printX() {
std::cout << "x: " << x << std::endl;
}
int main() {
foo(); // 输出:Count: 1
foo(); // 输出:Count: 2
foo(); // 输出:Count: 3
printX(); // 输出:x: 10
return 0;
}
extern 用于在一个文件中声明一个全局变量或函数,以便在其他文件中引用它们。
当我们有多个文件共享相同的全局变量或函数时,可以在其中一个文件中进行定义,并在其他文件中使用 extern 来声明该变量或函数的存在。这样,在编译和链接过程中,编译器就会知道该变量或函数的定义在其他文件中。
这里是一个示例:
在 file1.cpp 文件中定义一个全局变量:
// file1.cpp
int globalVariable = 10;
在 file2.cpp 文件中使用 extern 来引用 globalVariable 变量:
// file2.cpp
extern int globalVariable;
int main() {
// 使用 globalVariable
// ...
return 0;
}
通过使用 extern 关键字,我们可以在 file2.cpp 中引用 globalVariable 变量,而不需要重新定义它。
同样,我们也可以使用 extern 来引用在其他文件中定义的全局函数。
mutable 是 C++ 中的一个存储类,用于声明一个非常量的类成员,即使该成员在一个 const 对象中,也可以被修改。通常情况下,const 对象的数据成员是不能被修改的,但使用 mutable 关键字可以使某些数据成员例外。
这个关键字通常用于需要缓存的变量,它们的值可以在 const 对象中被修改,而无需改变 const 对象的可靠性。mutable 成员变量不受 const 限制,可以在 const 对象上被修改,因此被称为“可变的 const 成员”。
当我们将一个成员变量声明为 mutable 时,即使该成员变量在 const 对象中,也可以被修改。下面是一个使用 mutable 的代码示例,展示了如何在 const 成员函数中修改 mutable 成员变量:
#include
class Example {
public:
Example(int value) : data(value) {}
void printData() const {
std::cout << "Data: " << data << std::endl;
incrementData();
std::cout << "Modified Data: " << data << std::endl;
}
private:
mutable int data;
void incrementData() const {
++data;
}
};
int main() {
const Example obj(10);
obj.printData();
return 0;
}
在这个示例中,我们定义了一个名为 Example 的类,它有一个私有的 mutable 成员变量 data。在 printData() 函数中,我们首先输出 data 的值,然后调用 incrementData() 函数来对 data 进行递增操作,并再次输出 data 的值。
在主函数中,我们创建了一个 const 的 Example 对象 obj,并调用其 printData() 函数。由于 printData() 函数是 const 成员函数,并且 data 被声明为 mutable,所以我们可以在 const 对象中修改它。运行上述代码,输出将会是:
Data: 10
Modified Data: 11
从输出结果可以看出,尽管对象 obj 是 const 对象,但在 const 成员函数中我们仍然可以修改 mutable 成员变量 data 的值。
这个示例展示了 mutable 存储类的用法,它使某些成员变量可以在 const 对象中被修改。但是请注意,虽然 mutable 可以使某些数据成员例外,但应该谨慎使用它,因为它可能会使 const 对象的状态变得不可预测,从而导致一些问题。因此,建议只在确实需要缓存数据的情况下才使用 mutable。
thread_local 是 C++11 引入的一个存储类说明符,用于声明线程局部存储(Thread Local Storage, TLS)。它告诉编译器每个线程都有其自己的变量实例,每个线程对该变量的修改不会影响其他线程的实例。
使用 thread_local 关键字可以在多线程程序中创建线程本地变量,这些变量的生命周期与线程的生命周期相同,并且每个线程都有自己的变量副本。这样,每个线程都可以独立地操作自己的变量副本,而不会互相干扰。
thread_local 说明符可以与 static 或 extern 合并,用于静态或外部变量的声明和定义。例如:
thread_local static int count = 0;
这将创建一个静态的 thread_local 变量 count,并将其初始化为 0。
请注意,thread_local 不能用于函数声明或定义,它只能用于数据声明和定义。
下面是一个使用 thread_local 的示例代码:
#include
#include
thread_local int threadId; // 声明一个 thread_local 变量
void printThreadId() {
std::cout << "Thread ID: " << threadId << std::endl;
}
int main() {
std::thread t1([]() {
threadId = 1; // 每个线程都有自己的 threadId 变量副本
printThreadId();
});
std::thread t2([]() {
threadId = 2;
printThreadId();
});
t1.join();
t2.join();
return 0;
}
在上述代码中,我们首先声明了一个 thread_local 整数变量 threadId。然后,我们创建了两个线程 t1 和 t2,每个线程都分别给 threadId 赋值,并调用 printThreadId() 函数打印线程的 ID。
运行这个程序,输出结果(随机)可能类似于:
Thread ID: 1
Thread ID: 2
从输出结果可以看出,每个线程都有自己的 threadId 变量副本,并且修改其中一个线程的变量不会对其他线程产生影响。
thread_local 的作用是为每个线程创建独立的变量副本,在多线程编程中非常有用。它可以避免线程间的竞争条件和数据共享问题,并提供了一种简单而有效的方式来处理线程特定的数据。