变量与类型:从 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 = 42
graph 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 的 boolint 的子类——这意味着 True + 1 == 2。这在 C 中不可想象(C 的 bool 通常定义为 _Boolint)。

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 自动完成递归复制

练习

以下题目用于验证本章所学内容:

题号题目链接涉及知识点
P1001A+B Problemhttps://www.luogu.com.cn/problem/P1001输入输出、变量
P1000超级玛丽游戏https://www.luogu.com.cn/problem/P1000顺序结构、输出
P1003铺地毯https://www.luogu.com.cn/problem/P1003数组、循环