变量与类型:从 C 迁移 (Variables & Types)
章节概述
本章带你从 C 的静态类型系统迁移到 Python 的动态类型世界。你将理解 Python 的”名字绑定”与 C 的”内存分配”之间的本质区别:在 C 中,int x = 5 是在栈上分配 4 字节并写入值;在 Python 中,x = 5 是给整数对象 5 贴上一个标签 x。我们还会探讨 Python 的任意精度整数、不可变类型、None 空值,以及与 C 类型转换的对比。
核心理念:C 的变量是”装着值的盒子”,Python 的变量是”贴在对象上的便签”。理解这个思维转变,是 C 程序员掌握 Python 最关键的一步。
第一节:名字绑定 vs 内存分配
1.1 同一个赋值,两种世界观
// C 语言:变量是内存中的一块区域
int x = 42;graph LR subgraph Stack["栈内存"] ADDR["地址: 0x7fff..."] --> CONTENT["内容: 0x0000002A<br/>int 固定占 4 字节"] end
# Python:变量是对对象的引用
x = 42graph LR NAME["名字 x"] --> OBJ["int 对象 42<br/>(引用计数: 1)"]
在 Python 中,x 只是一个名字(name),不是内存地址。你可以随时改变 x 指向的对象:
python -c "
x = 42 # x 指向整数 42
print(type(x), x)
x = 'hello' # x 现在指向字符串 'hello'(类型变了!)
print(type(x), x)
x = [1, 2, 3] # x 又指向列表了
print(type(x), x)
"在 C 中这完全不可能——int x 一旦声明,就永远是 int。
1.2 多重绑定和引用共享
python -c "
a = [1, 2, 3]
b = a # b 和 a 指向同一个列表对象!(不复制)
a.append(4)
print('a =', a)
print('b =', b) # b 也变了!
"对比 C 语言的赋值:
int a[] = {1, 2, 3};
int *b = a; // b 是指向 a 首元素的指针
a[0] = 99; // b 指向的内容也变了Python 的 b = a 行为类似于 C 的指针赋值——共享底层对象,而不是复制。但 Python 不必管理 free,由垃圾回收自动处理。
1.3 id() 查看对象身份
python -c "
import sys
x = 42
y = 42
print('x id:', id(x))
print('y id:', id(y))
print('Same object?', x is y)
print('Reference count:', sys.getrefcount(x))
"小整数(-5 到 256)在 Python 中会被缓存复用——这相当于 C 中的编译时常量池:
// C 也有类似行为(常量折叠)
const char *s1 = "hello";
const char *s2 = "hello";
// s1 和 s2 可能指向相同的只读内存区域第二节:核心数据类型全览
2.1 整型 int —— 任意精度
python -c "
x = 2**100 # 2 的 100 次方
print(x)
print('位数:', x.bit_length()) # 101 位
print('类型:', type(x))
"C 语言的整数:
#include <stdio.h>
#include <stdint.h>
#include <limits.h>
int main() {
int x = 2147483647; // INT_MAX(32 位)
// unsigned long long 也只有 64 位
uint64_t y = UINT64_MAX; // 18446744073709551615
printf("%lu\n", y);
return 0;
}Python 的 int 没有上限(除了可用内存),内部使用变长数组存储大数。这相当于 C 语言中引入 GMP 库才能实现的功能。
python -c "
# 这是 Python 的整数,不是 float
huge = 2**10000
print('2**10000 的位数:', huge.bit_length())
print('前 50 位:', str(huge)[:50])
"2.2 浮点数 float —— 双精度
Python 的 float 对应 C 的 double(64 位 IEEE 754):
python -c "
import sys
print('float 大小:', sys.float_info)
x = 0.1 + 0.2
print('0.1 + 0.2 =', x) # 0.30000000000000004
print('x == 0.3?', x == 0.3) # False!浮点精度问题
"这跟 C 完全一样:
#include <stdio.h>
int main() {
double x = 0.1 + 0.2;
printf("%.17f\n", x); // 0.30000000000000004
return 0;
}Python 的
float就是 C 的double,精度和陷阱完全一致。如果你需要高精度小数,用decimal模块。
2.3 字符串 str —— Unicode 原生支持
python -c "
s = '你好, World!'
print('类型:', type(s))
print('长度(字符数):', len(s))
print('字节表示:', s.encode('utf-8'))
print('字节数:', len(s.encode('utf-8')))
"对比 C 语言的字符串:
// C 语言:字符串是字节数组
char s[] = "Hello"; // 每个字符 1 字节(ASCII)
char ws[] = L"你好"; // 宽字符需要特殊处理
// strlen 返回字节数,不是字符数Python 的 str 是不可变 Unicode 字符串——内部用变长编码存储(PEP 393),自动处理多字节字符。你不需要纠结 char vs wchar_t。
2.4 布尔 bool —— 只有两个值
python -c "
print('True:', True, 'type:', type(True))
print('False:', False)
# bool 是 int 的子类!
print('True == 1:', True == 1)
print('False == 0:', False == 0)
print('True + True:', True + True) # 2
# 真假值规则
print('bool(\"\") =', bool('')) # 空字符串 → False
print('bool(\"hello\") =', bool('hello'))
print('bool(0) =', bool(0))
print('bool(42) =', bool(42))
print('bool([]) =', bool([]))
print('bool([0]) =', bool([0])) # 非空列表 → True
"Python 的
bool是int的子类——这意味着True + 1 == 2。这在 C 中不可想象(C 的bool通常定义为_Bool或int)。
2.5 None —— 这不是空指针
python -c "
x = None
print('type:', type(x))
print('x is None:', x is None)
# None 是单例,不是空指针
import sys
print('None 的引用计数:', sys.getrefcount(None))
"C 语言中的对应概念:
// C: NULL 是一个空指针
int *p = NULL;
// Python: None 是一个真实存在的对象
x = None
// Python 中不存在"空指针"概念,None 是有类型(NoneType)有身份的判断 None 时必须用 is 而非 ==:
python -c "
class MyObj:
def __eq__(self, other):
return True # 这个类说"我等于一切"
obj = MyObj()
print(obj == None) # True(误导!)
print(obj is None) # False(正确)
"第三节:动态类型与类型检查
3.1 type() 和 isinstance()
python -c "
x = 42
print('type(x):', type(x))
print('type(x) == int:', type(x) == int)
print('isinstance(x, int):', isinstance(x, int))
print('isinstance(x, (int, float)):', isinstance(x, (int, float)))
"type() 返回精确类型,isinstance() 检查继承链:
python -c "
class A: pass
class B(A): pass
b = B()
print('type(b) == B:', type(b) == B) # True
print('type(b) == A:', type(b) == A) # False
print('isinstance(b, A):', isinstance(b, A)) # True(考虑继承)
"推荐使用
isinstance():它支持继承,支持元组多类型检查,更符合 Python 的鸭子类型哲学。
3.2 动态类型的优势和代价
优势一:写通用代码不需要泛型/模板:
python -c "
def twice(x):
return x + x
print(twice(10)) # 20
print(twice('hello')) # hellohello
print(twice([1,2])) # [1, 2, 1, 2]
"对比 C 需要重载或泛型:
int twice_int(int x) { return x + x; }
char* twice_str(...) { /* 需要 strdup 等操作 */ }
// C++ 需要模板: template<typename T> T twice(T x) { return x + x; }代价:类型错误推迟到运行时:
python -c "
def add(a, b):
return a + b
print(add(1, 2)) # 3
print(add('a', 'b')) # 'ab'
print(add(1, 'b')) # TypeError! 在 C 中编译期就会报错
"3.3 类型注解与可选类型检查
Python 3.5+ 支持类型注解,但默认不强制检查:
python -c "
def greet(name: str) -> str:
return 'Hello, ' + name
print(greet('World')) # 正常
print(greet(42)) # 也正常!(运行时不检查类型注解)
"要用 mypy 做静态类型检查:
# 安装 mypy
pip install mypy
# 检查类型
echo 'def greet(name: str) -> str:
return 42' > /tmp/t.py
mypy /tmp/t.py
# error: Incompatible return value type (got "int", expected "str")类型注解让 Python 获得了类似 C 的类型安全感——但它是可选的、渐进式的。你可以从完全不写注解,逐步过渡到严格类型检查。
第四节:可变性(Mutability)概念
4.1 不可变 vs 可变类型
python -c "
# 不可变类型:int, float, str, tuple, frozenset
x = 'hello'
# x[0] = 'H' # TypeError: 'str' object does not support item assignment
x = 'Hello' # 这没有修改原字符串——是创建了新字符串,重新绑定
# 可变类型:list, dict, set, bytearray
arr = [1, 2, 3]
arr[0] = 99 # 直接修改列表的内容
print(arr)
"对比 C 语言:
// C 中,const 和原始内存的区分
const char *s = "hello"; // 不可修改(通常在只读段)
char buf[] = "hello"; // 可修改(在栈上)
buf[0] = 'H'; // 合法Python 的不可变性不是 const 关键字——它是类型级别的保证。int 类型没有提供修改自身的方法。
4.2 可变对象作为默认参数——经典陷阱
python -c "
def bad_append(item, target=[]): # 默认参数只求值一次!
target.append(item)
return target
print(bad_append(1)) # [1]
print(bad_append(2)) # [1, 2] ← 不是 [2]!
"python -c "
def good_append(item, target=None):
if target is None:
target = [] # 每次调用都创建新列表
target.append(item)
return target
print(good_append(1)) # [1]
print(good_append(2)) # [2] ← 正确!
"这个陷阱类似于 C 语言的静态局部变量——
def语句中的默认参数在函数定义时求值一次,之后所有调用共享同一个对象。
4.3 嵌套可变对象的浅拷贝问题
python -c "
import copy
original = [[1, 2], [3, 4]]
shallow = original[:] # 浅拷贝
deep = copy.deepcopy(original) # 深拷贝
original[0][0] = 99
print('original:', original) # [[99, 2], [3, 4]]
print('shallow:', shallow) # [[99, 2], [3, 4]] ← 受影响!
print('deep:', deep) # [[1, 2], [3, 4]] ← 不受影响
"对比 C 语言:
// C: 手工复制嵌套结构
struct Node { int val; struct Node *next; };
struct Node* shallow_copy(struct Node *n) { ... } // 只复制指针
struct Node* deep_copy(struct Node *n) { ... } // 递归复制整个链表
// Python 的 copy.deepcopy 自动完成递归复制练习
以下题目用于验证本章所学内容:
| 题号 | 题目 | 链接 | 涉及知识点 |
|---|---|---|---|
| P1001 | A+B Problem | https://www.luogu.com.cn/problem/P1001 | 输入输出、变量 |
| P1000 | 超级玛丽游戏 | https://www.luogu.com.cn/problem/P1000 | 顺序结构、输出 |
| P1003 | 铺地毯 | https://www.luogu.com.cn/problem/P1003 | 数组、循环 |