static 静态变量用途
一、函数内部 static 局部变量
核心特点
- 存储在全局静态存储区,不是栈;程序启动分配内存、程序结束才销毁
- 只初始化1次,首次进入函数执行初始化,后续调用跳过初始化
- 作用域仍仅限当前函数,外部无法访问
用途
- 保存函数跨调用的持久状态(计数器、缓存上次结果)
c
void count() {
static int num = 0; // 仅第一次赋值0
num++;
printf("%d", num);
}
// 连续调用输出:1 2 3 4...
- 避免重复创建大数组/结构体,减少栈溢出
二、文件域 static 全局变量(函数外)
核心特点
- 仍是全局生命周期,但作用域限制在当前.c/.cpp文件
- 其他源文件无法通过
extern访问,实现文件私有
用途
- 封装模块私有数据,防止多文件命名冲突
- 实现模块化隔离,仅本文件读写,降低耦合
三、类中 static 成员(C++专属)
1. static 成员变量
- 不属于某个对象,属于整个类,所有对象共享同一份内存
- 必须类外初始化
- 用途:统计类实例总数、全局共享配置(统一参数)
cpp
class Student {
public:
static int cnt; // 所有学生共用计数器
Student(){ cnt++; }
};
int Student::cnt = 0;
2. static 成员函数
- 没有
this指针,只能访问static成员,不能操作普通成员变量 - 可直接用
类名::函数()调用,无需创建对象 - 用途:工具类通用方法、操作类全局静态数据
补充:static 修饰函数(文件内)
static void func(){}
- 函数仅本文件可见,其他文件不能调用,隔离模块接口,防止重名。
组块分隔符
C++14 新增了用单引号表示的组块分隔符(chunking separator)。使用这种分隔符可提高代码的可读性,如下面的初始化语句所示:
cpp
int moneyInBank = -70'000; // -70000
long populationChange = -85'000; // -85000
long long countryGDPChange = -70'000'000'000; //
-70 billion
double pi = 3.141'592'653'59; // 3.14159265359
固定宽度整型
C++11 引入了固定宽度的整型,让您能够以位为单位指定整数的宽度。这些类型为 int8_t 和 unit8_t,分别用于存储 8 位的有符号和无符号整数。您还可能使用 16 位、32 位和 64位的整型,它们为 int16_t、uint16_t、int32_t、uint32_t、int64_t 和 uint64_t。要使用这些类型,必须包含头文件<cstdint>。
使用列表初始化避免缩窄转换错误
使用取值范围较大的变量来初始化取值范围较小的变量时,将面临出现缩窄转换错误的风险,因为编译器必须将大得多的值存储到容量没那么大的变量中,下面是一个这样的示例:
cpp
int largeNum = 5000000;
short smallNum = largeNum; // compiles OK, yet narrowing error
缩窄转换并非只能在整型之间进行,但如果使用 double 值来初始化 float 变量、使用 int 值来初始化 float 或 double 变量,或者使用 float 值来初始化 int 变量,可能导致缩窄转换错误。
有些编译器可能发出警告,但这种警告并不会导致程序无法通过编译。在这种情况下,程序可能在运行阶段出现 bug,
但这种 bug 并非每次运行时都会出现。
为避免这种问题,C++11 引入了列表初始化来禁止缩窄。要使用这种功能,可将用于初始化的变量或值放在大括号({})内。列表初始化的语法如下:
cpp
int largeNum = 5000000;
short anotherNum{ largeNum }; // error! Amend types (修改类型)
int anotherNum{ largeNum }; // OK!
float someFloat{ largeNum }; // error! An int may be narrowed
float someFloat{ 5000000 }; // OK! 5000000 can be accomodated (该数字刚好能无损存入)
这种功能的作用虽然不明显,但可避免在执行阶段对数据进行缩窄转换导致的 bug:这种 bug 是不合理的初始化导致的,难以发现。
使用 typedef 替换变量类型
C++允许您将变量类型替换为您认为方便的名称,为此可使用关键字 typedef。在下面的示例中,程序员想给 unsigned int 指定一个更具描述性的名称------STRICTLY_POSITIVE_INTEGER:
cpp
typedef unsigned int STRICTLY_POSITIVE_INTEGER;
STRICTLY_POSITIVE_INTEGER numEggsInBasket = 4532;
编译时,第 1 行告诉编译器,STRICTLY_POSITIVE_INTEGER 就是 unsigned int。以后编译器再遇到已定义的类型 STRICTLY_POSITIVE_INTEGER 时,就会将它替换为 unsigned int 并继续编译。
再例如,算法竞赛中常用:
cpp
typedef long long ll;
typedef unsigned long long ull;
typedef long double ld;
constexpr 编译期常量表达式
一、核心定义
constexpr:编译期常量表达式 ,修饰的变量/函数能在编译阶段直接算出结果 ,运行时无计算开销。
对比 const:
const:仅代表只读,不一定编译期求值;constexpr:强制要求值编译时可确定。
二、constexpr 修饰变量
规则
- 右侧表达式必须是编译期可计算;
- 类型只能是字面类型(int、long long、double、数组、字面结构体等);
- 不能用运行时变量赋值。
cpp
#include <iostream>
using namespace std;
// 编译期常量
constexpr int N = 1000;
constexpr ll MOD = 998244353;
constexpr double PI = 3.1415926;
int main() {
int a = 5;
// constexpr int x = a; // 报错:a是运行时变量,无法编译期求值
constexpr int x = N * 2; // OK,全部编译期计算
int arr[x]; // C++11后,constexpr常量可直接做数组长度
return 0;
}
和 const 变量区别
cpp
int n = rand();
const int c1 = n; // OK,只读,但运行时才确定值
// constexpr int c2 = n; // 编译报错
三、constexpr 修饰函数(C++11)
C++11 限制
- 函数体只能单条 return 语句;
- 参数、返回值必须是字面类型;
- 调用时传入常量,函数就在编译期计算;传运行时变量则降级为普通函数(运行时计算)。
cpp
constexpr int pow2(int x) {
return x * x;
}
int main() {
constexpr int res = pow2(10); // 编译期算出100
int a = 5;
int b = pow2(a); // 运行时计算
return 0;
}
C++17 放宽
constexpr 函数允许分支、循环、多语句,写复杂编译期计算。
四、constexpr可让常量声明像函数
cpp
constexpr double GetPi() {return 22.0 / 7;}
在一个常量表达式中,可使用另一个常量表达式:
cpp
constexpr double TwicePi() {return 2 * GetPi();}
常量表达式看起来像函数,但在编译器和应用程序看来,它们提供了优化可能性。只要编译器能够从常量表达式计算出常量,就可在语句和表达式中可使用常量的地方使用它。
在前面的示例中,TwicePi()是一个常量表达式,它使用了另一个常量表达式 GetPi()。
这可能引发编译阶段优化,即编译器将所有的 TwicePi()都替换为 6.28571,从而避免在代码执行时计算 2×22/7 的值。
五、constexpr 构造函数(编译期对象)
字面类可以用 constexpr 构造,在编译期创建对象:
cpp
struct Point {
int x, y;
constexpr Point(int a, int b) : x(a), y(b) {}
};
constexpr Point p(3,4); // 编译期构造完成
六、const / constexpr / consteval 简单区分
const:只读,不限定编译期;constexpr:能编译期算就编译期算,不行就运行时;consteval(C++20):强制只能编译期调用,传运行时参数直接报错。
七、注意
常量表达式必须包含简单的实现,并返回简单类型,如整数、双精度浮点数等。在C++14 中,常量表达式可包含决策结构,如 if 和 switch 语句。
使用 constexpr 并不能保证一定会进行编译阶段优化。例如,如果您使用常量表达式来计算用户输入的数字的两倍,由于编译器无法计算这种表达式的结果,因此它们可能忽略关键字 constexpr,进而将常量表达式视为常规函数进行编译。
高精度pi常量
常量 M_PI 提供了精度相当高的 pi 值,您可在程序中使用
这个常量,但必须包含头文件<cmath>。
枚举
在有些情况下,变量只能有一组特定的取值。例如,彩虹不能包含青绿色,指南针的方位不能为"左"。在这些情况下,需要定义这样一种变量,即其可能取值由您指定。为此,可使用关键字 enum 来声明枚举。枚举由一组称为枚举量(emumerator)的常量组成。
在下面的示例中,枚举 RainbowColors 包含彩虹的各种颜色,如 Violet 等枚举量:
cpp
enum RainbowColors
{
Violet = 0,
Indigo,
Blue,
Green,
Yellow,
Orange,
Red
};
下面的枚举包含基本方位:
cpp
enum CardinalDirections
{
North,
South,
East,
West
};
可使用枚举来指定变量的类型,这样声明的变量只能取指定的值。因此,如果要声明一个变量,用于存储彩虹的颜色,可以像下面这样做:
cpp
RainbowColors MyFavoriteColor = Blue; // Initial value
上述代码声明了变量 MyFavoriteColor,其类型为RainbowColors。这个变量只能取 RainbowColors中指定的值,而不能取其他值。
编译器将枚举量(Voilet 等)转换为整数,每个枚举量都比前一个大 1。您可以指定起始值,如果没有指定,编译器认为起始值为 0,因此 North 的值为 0。
如果愿意,还可通过初始化显式地给每个枚举量指定值。