变量与常量
需要预先了解:
1. 变量
变量就是编程里用来存放数据的盒子。——豆包
- 变量有名字(变量名,是一个标识符),可以用于找到它
- 变量里能存储数据(数字、文字、真假值等)
- 变量里的数据可以修改
1.1 常见的变量类型
整型变量
修饰符signed和unsigend
- 默认为有符号(
signed),表面变量可以表示正数、负数和0。 - 用
unsigned修饰后,变量只能表示非负整数,即0和正数
常见整型变量空间占用:
| 数据类型 | 32位系统(x86) | 64位系统(x64) |
|---|---|---|
| int | 4字节(32位) | 8字节(64位) |
| short/short int | 2字节(16位) | 2字节(16位) |
| long/long int | 4字节(32位) | 8字节(64位) |
| long long/long long int | 8字节(64位) | 8字节(64位) |
| unsigned 类型 | 和对应的有符号类型相同 | 和对应的有符号类型相同 |
浮点型变量
float:单精度浮点型,占用4个字节(32位),大约能提供7位有效数字。double:双精度浮点型,占用8个字节(64位),大约能提供15-17位有效数字,精度比float更高。long double:长双精度浮点型,占用的字节数在不同系统中可能不同,通常比double提供更高的精度。
字符型变量
char 类型变量用于表示单个字符,占用 1 个字节(8 位)。
字符编码
计算机内部只能存储二进制数。为了能让计算机表示文字,人们建立了 字符编码(Character Encoding)——为每个字符分配一个数字编号,存储时存的是这个编号,显示时再将编号映射回对应的字形。
最常见的编码是 ASCII(American Standard Code for Information Interchange,美国信息交换标准代码),它用 7 个二进制位(0 ~ 127)来表示字符:
| 十进制 | 十六进制 | 字符 | 说明 |
|---|---|---|---|
| 0 ~ 31 | 0x00~0x1F | N/A | 控制字符(不可见) |
| 32 | 0x20 | 空格 | 空格 |
| 48 ~ 57 | 0x30~0x39 | 0 ~ 9 | 数字 |
| 65 ~ 90 | 0x41~0x5A | A ~ Z | 大写字母 |
| 97 ~ 122 | 0x61~0x7A | a ~ z | 小写字母 |
由于 ASCII 只用 7 位,最高位(第 8 位)始终为 0。当需要表示中文、日文、韩文等非英语字符时,一个字节远远不够,于是出现了多种扩展编码方案:
GBK(国标扩展):
- 专门为中文设计的编码标准,兼容 ASCII(0 ~ 127 与 ASCII 完全一致)。
- 使用 变长编码:英文和数字占 1 个字节(与 ASCII 相同),中文字符占 2 个字节。
- 高位字节(第一个字节)的范围是
0x81 ~ 0xFE,低位字节(第二个字节)的范围是0x40 ~ 0xFE。 - 优点:在中文环境下兼容性好、历史积累深厚;缺点:存在生僻字支持不全、在国际化场景中不如 UTF-8 通用。
UTF-8(Unicode Transformation Format - 8 bit):
- Unicode 是国际统一的字符集,为全世界所有语言的每个字符分配唯一编号(码点,code point)。UTF-8 是 Unicode 最通用的实现方式之一。
- 同样兼容 ASCII(0 ~ 127 与 ASCII 完全一致)。
- 使用 变长编码,每个字符占用 1 ~ 4 个字节:
| 码点范围 | UTF-8 字节数 | 字节格式(二进制) |
|---|---|---|
| U+0000 ~ U+007F | 1 字节 | 0xxxxxxx |
| U+0080 ~ U+07FF | 2 字节 | 110xxxxx 10xxxxxx |
| U+0800 ~ U+FFFF | 3 字节 | 1110xxxx 10xxxxxx 10xxxxxx |
| U+10000 ~ U+10FFFF | 4 字节 | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx |
- 例如中文字符"编"的 Unicode 码点为
U+7F16,UTF-8 编码结果为0xE7 0xBC 0x96(3 个字节)。
char 与整型的联系
在 C++ 中,char 本质上就是一个 1 字节的整数类型。每个字符存储的是它在编码表中对应的整数值(编码值):
#include <iostream>
int main()
{
char ch1 = 'A'; // 存储的是 'A' 的 ASCII 码 65
char ch2 = 65; // 直接赋整数值,等价于 ch2 = 'A'
std::cout << "ch1 = " << ch1 << std::endl; // 输出: A
std::cout << "ch2 = " << ch2 << std::endl; // 输出: A
return 0;
}
因此 char 和 int 之间可以隐式地相互转换:
#include <iostream>
int main()
{
char ch = 'A';
// char → int:字符参与算术运算时自动提升为 int
int ascii_val = ch; // ascii_val = 65
int next = ch + 1; // next = 66,即 'B' 的 ASCII 码
std::cout << ascii_val << std::endl; // 输出: 65
std::cout << (char)next << std::endl; // 输出: B
// 大小写转换(利用 ASCII 编码的规律)
char lower = 'g';
char upper = lower - ('a' - 'A'); // 小写转大写
std::cout << upper << std::endl; // 输出: G
// 数字字符转整数
char digit = '7';
int num = digit - '0'; // num = 7
std::cout << num << std::endl; // 输出: 7
return 0;
}
signed char、unsigned char 与 char
C++ 标准规定了三种字符类型,它们都占用 1 个字节:
| 类型 | 取值范围 |
|---|---|
signed char |
-128 ~ 127 |
unsigned char |
0 ~ 255 |
char |
由编译器决定,可能等价于 signed char 或 unsigned char |
关键区别:
char、signed char、unsigned char是 三种不同的类型。char到底是有符号还是无符号,由编译器和平台决定(大多数 x86/x64 平台上char等于signed char,范围是 -128 ~ 127)。
为什么字符类型也要分有符号和无符号?
表面上看,字符只需要 0 ~ 127 的 ASCII 值就够了,似乎没必要引入符号。但实际上,char 在 C/C++ 中承担了 双重角色——它既是字符类型,也是 最小的整数类型。这导致了 signed/unsigned 的区分:
1. char 常被当作"1 字节整数"使用
当 char 参与算术运算时,它的最高位(第 8 位)如何解释,会直接决定运算结果:
unsigned char uc = 200; // 二进制: 1100 1000,值为 200
signed char sc = 200; // 同样二进制,但按有符号解释:值为 -56
int a = uc + 10; // 200 + 10 = 210
int b = sc + 10; // -56 + 10 = -46
同样一块二进制数据 11001000,被当作 unsigned char 就是 200,被当作 signed char 就是 -56。是否区分 signed/unsigned,取决于编译器如何解释这 8 个位。
2. 符号扩展(sign extension)问题
当 char 隐式转换为 int(更宽的类型)时,有符号和无符号的行为截然不同:
signed char sc = -1; // 二进制: 1111 1111
unsigned char uc = 255; // 二进制: 1111 1111
// 二者内存中的二进制完全相同!
int a = sc; // 有符号扩展:高位全部填 1 → 0xFFFFFFFF = -1(32位)
int b = uc; // 无符号扩展:高位全部填 0 → 0x000000FF = 255(32位)
同为 11111111,signed char 扩展为 int 后变成 -1,unsigned char 变成 255。在涉及位运算、网络字节流、二进制文件读写等场景中,这个差异至关重要。
3. 历史原因与平台兼容
早期的 C 语言设计时,不同硬件平台对 1 字节整数的默认符号处理不同。标准将 char 的符号交给编译器决定(implementation-defined),既能保证在不同架构上的灵活性,又给开发者提供了 signed char 和 unsigned char 来精确控制。到了 C++,这一设计被继承下来。
简单总结:如果你只是存储和输出 ASCII 文字(a-z、A-Z、0-9),符号无关紧要——它们都在 0~127 范围内,signed 和 unsigned 的解释完全相同。但一旦把
char当作整数做运算、存储超过 127 的字节值、或与二进制数据打交道,signed/unsigned 的区别就变得非常重要。
给 char 赋非 ASCII 值的问题
由于 char 通常是有符号的(范围 -128 ~ 127),当赋予超出其表示范围的值时,会发生 溢出(overflow) 或 截断(truncation):
#include <iostream>
int main()
{
// 情况1:赋大于 127 的值
char ch1 = 128; // 128 超出 signed char 范围(-128~127)
std::cout << "ch1 的值: " << (int)ch1 << std::endl; // 可能输出: -128
// 情况2:赋负数
char ch2 = -1; // -1 在 signed char 范围内,可以存储
std::cout << "ch2 的值: " << (int)ch2 << std::endl; // 输出: -1
// 情况3:int 截断为 char
int large = 300; // 二进制:0b 1 0010 1100
char ch3 = large; // 只保留低 8 位:0010 1100 = 44
std::cout << "ch3 的值: " << (int)ch3 << std::endl; // 输出: 44 (即 ',')
// 情况4:赋值给 unsigned char
unsigned char uch = 128; // unsigned char 范围 0~255,正常存储
std::cout << "uch 的值: " << (int)uch << std::endl; // 输出: 128
return 0;
}
布尔型变量
bool 类型用于表示逻辑真值,只有两个可能的取值:true 和 false。
#include <iostream>
int main()
{
bool is_ready = true;
bool is_empty = false;
std::cout << "is_ready = " << is_ready << std::endl; // 默认输出: 1
std::cout << "is_empty = " << is_empty << std::endl; // 默认输出: 0
return 0;
}
内存占用
虽然 bool 只有两种状态(理论上 1 个二进制位就够),但在 C++ 中 bool 类型通常占用 1 个字节(8 位),这是由 C++ 标准规定的最小可寻址单元决定的。
std::cout << sizeof(bool) << std::endl; // 通常输出: 1
bool 与整型的转换
bool 和整型之间可以隐式转换:
- 整型 →
bool:非零值转换为true,零转换为false bool→ 整型:true转换为1,false转换为0
#include <iostream>
int main()
{
// 整型转 bool
bool b1 = 42; // true(非零)
bool b2 = 0; // false(零)
bool b3 = -1; // true(非零,负值也算)
bool b4 = 3.14; // true(非零浮点数)
// bool 转整型
int n1 = true; // n1 = 1
int n2 = false; // n2 = 0
// 算术运算中的 bool——true 被提升为 1,false 被提升为 0
int sum = true + true + false; // sum = 1 + 1 + 0 = 2
std::cout << sum << std::endl; // 输出: 2
return 0;
}
使用 std::boolalpha 输出文字
默认情况下,std::cout 输出 bool 变量时显示 1 或 0。使用 std::boolalpha 操纵符,可以让它输出 true 或 false:
#include <iostream>
int main()
{
bool flag = true;
std::cout << flag << std::endl; // 输出: 1
std::cout << std::boolalpha << flag << std::endl; // 输出: true
std::cout << std::noboolalpha << flag << std::endl; // 恢复,输出: 1
return 0;
}
布尔型字面量
C++ 中布尔字面量只有两个:true 和 false。它们是 C++ 的 关键字,不能用作标识符名,类型就是 bool。
1.2 变量的初始化方式
1. 默认初始化:变量不写任何初值,由编译器自动处理。
int global_var; // 未初始化全局变量被自动置 0
int main()
{
int a; // 局部内置变量,垃圾值
std::string str; // 类:调用无参构造函数
int* p = new int; // 堆内存默认初始化,垃圾值
}
2. 空值初始化:用空括号(()或{}),强制清零/调用默认构造
int a{};
int b();
int* p = new int();
int arr[5]{ };
3. 列表初始化:用花括号{},统一语法,禁止窄化转换
int a{10};
int b = {20};
int arr[]{1, 2, 3};
std::vector<int> v{1, 2, 3, 4};
1) 禁止窄化转换:
int a = 3.14; // 允许阶段
int b{3.14}; // 编译报错
2) 支持std::initializer_list多参数批量构造:
struct Vec
{
Vec(std::initializer_list<int> list);
}
4. 直接初始化:用圆括号(),直接调用构造函数
int a(100);
struct Point { int x, y; }
Point p(1, 2);
5. 拷贝初始化:用=赋值,触发拷贝构造/隐式转换
int a = 10;
std::string s = "Hello";
class A;
A a = A();
6. 聚合初始化:针对简单的结构体、数组,用{}批量赋值
struct Position { double x, y; }
Position p = { 2.5, 3.4 };
int arr[4] = { 1, 2 };
2. 常量
常量(constant) 是指在程序运行过程中其值不能改变的量。
在C++中,使用const关键字来定义常量,语法为const 数据类型 常量名 = 常量值;。
const double pi = 3.14159;
2.1 定义常量的细节
1. 常量必须在定义时初始化。
#include <iostream>
int main()
{
const int a;
return 0;
}
编译后,得到以下报错:
code.cpp: In function 'int main()':
code.cpp:5:15: error: uninitialized 'const a' [-fpermissive]
5 | const int a;
| ^
2. 常量的值在程序运行过程中不能被修改,如果尝试修改常量的值,编译器会报错。
#include <iostream>
int main()
{
const int b = 10;
b = 20;
return 0;
}
编译后,得到以下报错:
code.cpp: In function 'int main()':
code.cpp:6:7: error: assignment of read-only variable 'b'
6 | b = 20;
| ~~^~~~
2.2 字面常量
字面常量
字面常量是在代码中直接书写的固定值,其类型由书写形式决定。
- 整数常量:如
10、-20、30000等,默认是int类型,如果值过大,可能会是long或long long类型。 - 浮点常量:如
3.14、2.71828等,默认是double类型。 - 字符常量:用单引号括起来的单个字符,如
'a'、'A'、'0'等。 - 字符串常量:用双引号括起来的零个或多个字符序列,如
"Hello"、"what the hell"等
字面量后缀
可以用后缀来明确字面量的类型。
1. 整数字面量后缀
u或U:表示无符号整数(unsigned)。l或L:表示长整数(long)。ll或LL:表示长长整数(long long)。
unsigned int num1 = 10u;
unsigned long num2 = 100UL;
long long big_num = 1000000000000000000000LL;
2. 浮点字面量后缀
f或F:表示单精度浮点数(float)。l或L:表示长双精度浮点数(long double)。
float pi = 3.14f;
long double e = 2.71828182845904523536L;
2.3 编译时常量/常量表达式(constant expression)
注:constexpr关键字在 C++11 中才引入。
编译时常量和普通常量一样,也必须在定义时初始化,并且在程序运行的过程中不能被修改。
在C++中,使用constexpr关键字来定义编译时常量:
// after C++11
#include <iostream>
int main()
{
constexpr int a = 10;
return 0;
}
编译时常量的值在编译阶段就能确定,随后就会被直接替换到代码中,这样在程序运行时就不需要再查找地址、访问内存来获取值了,因此编译时常量的访问效率更高。
编译时就能确定的值:只由字面量和其它编译时能确定的值组成。
const int x = 10; // 10是字面量,因此x可以在编译时求出结果
const int y = 20 *x; // x可在编译时求出结果,故y也可在编译时求出结果
普通的常量既可以在编译时就确定值,也可以在运行的时候才确定值,前者可以被用来初始化编译时常量,后者则不能。
#include <iostream>
int main()
{
int variable = 10; // variable 是一个变量
const int a = variable; // a 是一个常量,值在运行时才确定
constexpr int b = variable; // 错误,编译时常量不能被变量初始化
constexpr int c = a; // 错误,编译时常量不能被运行时才确定的常量初始化
return 0;
}


