说明
在C语言中,当我们谈论“执行”时,通常是指执行代码,而不是执行变量。变量本身不执行任何操作,但是它们可以存储数据,这些数据可以被程序中的代码访问和修改。
当提到在 main 函数之外声明的变量时,我们通常指的是全局变量或静态变量。这些变量的初始化和使用发生在程序的执行过程中,但它们并不“执行”。以下是它们在程序执行过程中的行为:
-
全局变量(Global Variables):
- 全局变量是在所有函数之外声明的变量,它们在程序的整个执行期间都是可见和可用的。
- 全局变量在程序启动时初始化(如果提供了初始化器),并且在
main函数执行之前就已经存在。 - 程序中的任何函数都可以访问和修改全局变量(除非它们被声明为
static,这将限制它们的作用域仅在定义它们的文件内)。
-
静态变量(Static Variables):
- 在函数内部声明的静态变量在函数调用之间保持其值。即使函数返回,静态变量的值也不会丢失。
- 静态变量在第一次调用包含它们的函数时初始化,并且在
main函数执行之前就已经初始化。
-
局部变量(Local Variables):
- 局部变量是在函数内部声明的变量,它们仅在该函数的作用域内可见。
- 局部变量在函数被调用时创建,并在函数返回时销毁。
-
执行时机:
- 全局变量和静态变量的初始化是在
main函数执行之前完成的。确切地说,它们是在程序的启动阶段,也就是在调用main函数之前初始化的。 - 局部变量的创建和初始化是在它们所在函数被调用时进行的。
- 全局变量和静态变量的初始化是在
-
代码执行:
- 程序的执行始于
main函数,但全局变量和静态变量在main函数执行之前就已经存在并可能已经被初始化。 - 程序中的代码(包括在
main函数中的代码)可以在任何时候访问和修改全局变量和静态变量,只要它们的作用域允许。
- 程序的执行始于
-
程序结束:
- 当程序结束时(例如,通过在
main函数中返回或调用exit函数),程序会释放它使用的资源,包括局部变量所占用的栈内存。全局变量和静态变量的生命周期也会随着程序的结束而结束。
- 当程序结束时(例如,通过在
总结来说,“在 main 之外的变量”(即全局变量和静态变量)在程序的整个执行期间都是可见的,它们在程序启动时初始化,并可以在程序的任何部分被访问和修改,直到程序结束。局部变量则只在它们所在函数的作用域内存在和可见。
数据类型&大小
跳转到“数据类型&大小”(1)基本整数类型
| 数据类型 | 描述 | 32位系统大小 | 64位系统大小 | 取值范围 |
|---|---|---|---|---|
bool | 布尔类型 | 1 | 1 | true 或 false |
char | 字符型 | 1 | 1 | -128 到 127 |
signed char | 有符号字符型 | 1 | 1 | -128 到 127 |
unsigned char | 无符号字符型 | 1 | 1 | 0 到 255 |
int | 整型 | 4 | 4 | -2,147,483,648 到 2,147,483,647 |
unsigned int | 无符号整型 | 4 | 4 | 0 到 4,294,967,295 |
short (或 short int) | 短整型 | 2 | 2 | -32,768 到 32,767 |
unsigned short | 无符号短整型 | 2 | 2 | 0 到 65,535 |
long (或 long int) | 长整型 | 4 | 8 | -2,147,483,648 到 2,147,483,647(32位系统) |
| -9,223,372,036,854,775,808 到 9,223,372,036,854,775,807(64位系统) | ||||
unsigned long | 无符号长整型 | 4 | 8 | 0 到 4,294,967,295(32位系统) |
| 0 到 18,446,744,073,709,551,615(64位系统) | ||||
long long (或 long long int) | 长长整型 | 8 | 8 | -9,223,372,036,854,775,808 到 9,223,372,036,854,775,807 |
unsigned long long | 无符号长长整型 | 8 | 8 | 0 到 18,446,744,073,709,551,615 |
(2)宽字符和多字节字符类型
| 数据类型 | 描述 | 32位系统大小 | 64位系统大小 | 取值范围 |
|---|---|---|---|---|
wchar_t | 宽字符类型 | 2或4 | 2或4 | 取决于实现,通常为 0 到 65535 或 0 到 4,294,967,295 |
char16_t | UTF-16 字符类型 (C++11) | 2 | 2 | 0 到 65535 |
char32_t | UTF-32 字符类型 (C++11) | 4 | 4 | 0 到 4,294,967,295 |
(3)浮点数类型
为了添加一列表示数据类型的取值范围,更新后的表格如下:
| 数据类型 | 描述 | 32位系统大小 | 64位系统大小 | 备注 | 取值范围 |
|---|---|---|---|---|---|
float | 单精度浮点数 | 4 | 4 | IEEE 754 标准的单精度浮点数 | 约 -3.4E+38 至 +3.4E+38 |
double | 双精度浮点数 | 8 | 8 | IEEE 754 标准的双精度浮点数 | 约 -1.7E+308 至 +1.7E+308 |
long double | 长双精度浮点数 | 10, 12 或 16 | 10, 12 或 16 | 大小可能依赖于编译器和平台 | 约 -1.2E+4932 至 +1.2E+4932 (依赖于实现) |
(4)标准定义的整数类型(C99/C++11)
| 数据类型 | 描述 | 典型大小 | 取值范围 |
|---|---|---|---|
int8_t | 8位有符号整数 | 1 | -128 到 127 |
uint8_t | 8位无符号整数 | 1 | 0 到 255 |
int16_t | 16位有符号整数 | 2 | -32,768 到 32,767 |
uint16_t | 16位无符号整数 | 2 | 0 到 65,535 |
int32_t | 32位有符号整数 | 4 | -2,147,483,648 到 2,147,483,647 |
uint32_t | 32位无符号整数 | 4 | 0 到 4,294,967,295 |
int64_t | 64位有符号整数 | 8 | -9,223,372,036,854,775,808 到 9,223,372,036,854,775,807 |
uint64_t | 64位无符号整数 | 8 | 0 到 18,446,744,073,709,551,615 |
intmax_t | 最大整型 | 8(通常) | 取决于实现,通常是 int64_t 的范围 |
uintmax_t | 最大无符号整型 | 8(通常) | 取决于实现,通常是 uint64_t 的范围 |
(5)其他标准类型
你可以通过添加一列来描述这些数据类型的取值范围。下面是更新后的表格:
| 数据类型 | 描述 | 32位系统大小 | 64位系统大小 | 取值范围 |
|---|---|---|---|---|
size_t | 无符号整数,用于大小和长度,至少为 unsigned int 的大小 | 4 | 8 | 0 到 2^32-1(32位)或 2^64-1(64位) |
ptrdiff_t | 有符号整数,用于指针差,至少为 int 的大小 | 4 | 8 | -2^31 到 2^31-1(32位)或 -2^63 到 2^63-1(64位) |
intptr_t | 用于存储指针的大端或小端字节序的有符号整数 | 4 | 8 | -2^31 到 2^31-1(32位)或 -2^63 到 2^63-1(64位) |
uintptr_t | 用于存储指针的无符号整数 | 4 | 8 | 0 到 2^32-1(32位)或 2^64-1(64位) |
void | 无值或无类型 | - | - | - |
这样,表格不仅说明了每种类型的描述、大小,还补充了它们的取值范围。
(6)复合类型
- 指针类型:指针类型的大小在32位系统上通常是4字节,在64位系统上通常是8字节。
- 数组类型:数组的大小是数组中所有元素大小的总和。
(7)枚举类型(enum)
- 枚举类型的大小取决于实际的值和编译器的实现,但通常是最小的足以包含所有枚举值的整数类型。
数组
跳转到“数组”(1)用数组名作函数参数
- 当数组名作为参数传递给函数时,实际上是传递了数组首元素的地址。
- 形参数组名在函数内部被视为指针变量,因此可以通过指针运算来访问数组元素。
(2)数组定义&初始化
/* 格式 类型符 数组名[常量 或 常量表达式] // 注意,定义时不能用变量。 起始号从 0 开始,左闭右开。*/int a[3 + 5];int a[3][4]; // 定义3行4列数组,注意与引用区分a[2][3]是最后一个元素。在内存中存储是按照行存储,一行元素存完,再存下一行。扩大到多维数组也是这样。
/*初始化- 定义数值数组,指定长度并初始化,凡未被初始化的元素会自动初始化为0,若是指针类型,则会被自定初始化为NULLL。*/// 1) 全部初始化int a[5] = {0, 1, 2, 3, 4};int a[] = {0, 1, 2, 3, 4}; // 全部初始化可不指定数组长度int a[3][4] = {{1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12}}; // 分行初始化int a[3][4] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}; // 或者全部写在一个花括号,按照在内存中的顺序对应赋值。int a[][4] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}; // qu// 2) 部分初始化,只要初始化了,没被赋值的会自动赋0 或 NULLint a[5] = {0, 1}; // 后面自动填充0int a[3][4] = {{1}, {5}, {9}} // 只对每行第1列赋值int a[3][4] = {{1}, {}, {0, 0, 11}} // 对行的某元素赋值
// 3) 全部初始化为0int a[5] = {0};结构体
跳转到“结构体”(1)结构体指针
typedef void (*KeyFunction)(WORD, int);typedef关键字用于为数据类型创建一个新的别名。当你看到typedef,基本上可以认为它是在说“我将给某个数据类型起一个新的名字”。
枚举(enum)
跳转到“枚举(enum)”| 特性/关键字 | enum (传统枚举) | enum class (枚举类) |
|---|---|---|
| 类型安全性 | 低,可隐式转换为整数 | 高,不可隐式转换为整数 |
| 作用域 | 全局作用域,可能导致命名冲突 | 限定在枚举类内部,使用需要作用域解析 |
| 底层类型 | 默认为 int,可指定其他类型 | 默认为 int,但编译器可自行选择类型 |
| 声明和使用 | 直接使用枚举值 | 必须通过枚举类名限定枚举值 |
| 向前声明 | 可以向前声明 | 不能向前声明,需要完全定义 |
| 与类型系统的集成 | 较差,不能作为模板参数 | 较好,可以作为类成员和模板参数 |
| 访问控制 | 无访问控制 | 可以指定枚举值的访问控制(public, protected, private) |
// 基本枚举声明和使用// 声明一个枚举类型enum Color { RED, GREEN, BLUE};
int main() { Color c = RED; // 使用枚举类型 if (c == RED) { std::cout << "The color is red." << std::endl; } return 0;}// 带初始值的枚举enum Color { RED = 1, GREEN = 2, BLUE = 4};
int main() { Color c = GREEN; std::cout << "The color code is " << static_cast<int>(c) << std::endl; return 0;}// 前向声明枚举// 前向声明enum class Color; // 注意使用 class 限定符
Color favoriteColor = Color::RED; // 错误,Color未定义
// 完整的枚举定义enum class Color { RED, GREEN, BLUE};
int main() { // 现在可以正确使用 favoriteColor return 0;}// 枚举类(强类型枚举)// 枚举类定义enum class Color { RED, GREEN, BLUE};
int main() { Color c = Color::RED; // 下面的代码将引发编译错误,因为 Color 是强类型枚举 // if (c == 0) { ... }
// 正确的比较方式 if (c == Color::RED) { std::cout << "The color is red." << std::endl; } return 0;}// 枚举作为函数参数void printColorName(Color color) { switch (color) { case Color::RED: std::cout << "Red" << std::endl; break; case Color::GREEN: std::cout << "Green" << std::endl; break; case Color::BLUE: std::cout << "Blue" << std::endl; break; }}
int main() { printColorName(Color::GREEN); return 0;}// 枚举作为函数参数枚举和迭代器enum class Color { RED, GREEN, BLUE};
void printAllColors() { for (Color color = Color::RED; color != Color::BLUE; ++color) { std::cout << static_cast<int>(color) << std::endl; }}
int main() { printAllColors(); return 0;}#include <iostream>
enum class Color { RED, GREEN, BLUE};
#define ENUM_COLOR(XX) \ XX(Color::RED) \ XX(Color::GREEN) \ XX(Color::BLUE)
void processColor(Color color) { std::cout << "Processing color: " << static_cast<int>(color) << std::endl;}
int main() { // 使用宏生成多个函数调用 ENUM_COLOR(processColorWrapper) return 0;}
// 包装器宏,生成的代码为每个枚举值调用 processColor 函数#define processColorWrapper(color) processColor(color);| 情景 | 是否使用逗号 | 示例 |
|---|---|---|
| 宏用于生成独立的函数调用或语句 | 否 | ENUM_COLOR(processColor) 生成: processColor(Color::RED);processColor(Color::GREEN);processColor(Color::BLUE); |
| 宏用于生成带逗号分隔的列表(如枚举、参数列表) | 是 | ENUM_COLOR_LIST(Color) 生成: Color::RED,Color::GREEN,Color::BLUE |
不使用逗号
#define ENUM_COLOR(XX) \ XX(Color::RED) \ XX(Color::GREEN) \ XX(Color::BLUE)
void processColor(Color color);
int main() { ENUM_COLOR(processColor) // 生成代码如下: // processColor(Color::RED); // processColor(Color::GREEN); // processColor(Color::BLUE); return 0;}使用逗号
#define ENUM_COLOR_LIST(XX) \ XX(Color::RED), \ XX(Color::GREEN), \ XX(Color::BLUE)
enum class Color { ENUM_COLOR_LIST(IDENT) // 生成代码如下: // Color::RED, // Color::GREEN, // Color::BLUE};
#define IDENT(x) x