- Python科学计算(第2版)
- 张若愚
- 1139字
- 2025-03-09 06:39:27
2.1.6 结构数组
在C语言中我们可以通过struct关键字定义结构类型,结构中的字段占据连续的内存空间。类型相同的两个结构所占用的内存大小相同,因此可以很容易定义结构数组。和C语言一样,在NumPy中也很容易对这种结构数组进行操作。只要NumPy中的结构定义和C语言中的结构定义相同,就可以很方便地读取C语言的结构数组的二进制数据,将其转换为NumPy的结构数组。
假设我们需要定义一个结构数组,它的每个元素都有name、age和weight字段。在NumPy中可以如下定义:
persontype = np.dtype({ ❶ 'names':['name', 'age', 'weight'], 'formats':['S30','i', 'f']}, align=True) a = np.array([("Zhang", 32, 75.5), ("Wang", 24, 65.2)], ❷ dtype=persontype)
❶我们先创建一个dtype对象persontype,它的参数是一个描述结构类型的各个字段的字典。字典有两个键:'names'和'formats'。每个键对应的值都是一个列表。'names'定义结构中每个字段的名称,而'formats'则定义每个字段的类型。这里我们使用类型字符串定义字段类型:
●'S30':长度为30个字节的字符串类型,由于结构中的每个元素的大小必须固定,因此需要指定字符串的长度。
●'i':32位的整数类型,相当于np.int32。
●'f':32位的单精度浮点数类型,相当于np.float32。
❷然后调用array()以创建数组,通过dtype参数指定所创建的数组的元素类型为persontype。下面查看数组a的元素类型:
a.dtype dtype({'names':['name','age','weight'], 'formats':['S30','<i4','<f4'], 'offsets':[0,32,36], 'itemsize':40}, align=True)
还可以用包含多个元组的列表来描述结构的类型:
dtype([('name', '|S30'), ('age', '<i4'), ('weight', '<f4')])
其中形如“(字段名,类型描述)”的元组描述了结构中的每个字段。类型字符串前面的'|'、'<'、'>'等字符表示字段值的字节顺序:
●|:忽视字节顺序。
●lt;:低位字节在前,即小端模式(little endian)。
●>:高位字节在前,即大端模式(big endian)。
结构数组的存取方式和一般数组相同,通过下标能够取得其中的元素,注意元素的值看上去像是元组,实际上是结构:
print a[0] a[0].dtype ('Zhang', 32, 75.5) dtype({'names':['name','age','weight'], 'formats':['S30','<i4','<f4'], 'offsets':[0,32,36], 'itemsize':40}, align=True)
我们可以使用字段名作为下标获取对应的字段值:
a[0]["name"] 'Zhang'
a[0]是一个结构元素,它和数组a共享内存数据,因此可以通过修改它的字段来改变原始数组中对应元素的字段:
c = a[1] c["name"] = "Li" a[1]["name"] 'Li'
我们不但可以获得结构元素的某个字段,而且可以直接获得结构数组的字段,返回的是原始数组的视图,因此可以通过修改b[0]来改变a[0]["age"]:
b=a["age"] b[0] = 40 print a[0]["age"] 40
通过a.tostring()或a.tofile()方法,可以将数组a以二进制的方式转换成字符串或写入文件:
a.tofile("test.bin")
利用下面的C语言程序可以将test.bin文件中的数据读取出来。%%file为IPython的魔法命令,它将该单元格中的文本保存成文件read_struct_array.c:
%%file read_struct_array.c #include <stdio.h> struct person { char name[30]; int age; float weight; }; struct person p[3]; void main () { FILE *fp; int i; fp=fopen("test.bin","rb"); fread(p, sizeof(struct person), 2, fp); fclose(fp); for(i=0;i<2;i++) { printf("%s %d %f\n", p[i].name, p[i].age, p[i].weight); } }
在IPython中可以通过!执行系统命令,下面调用gcc编译前面的C语言程序并执行:
!gcc read_struct_array.c -o read_struct_array.exe !read_struct_array.exe Zhang 40 75.500000 Li 24 65.199997
内存对齐
为了内存寻址方便,C语言的结构类型会自动添加一些填充用的字节,这叫做内存对齐。例如上面C语言中定义的结构的name字段虽然是30个字节长,但是由于内存对齐问题,在name和age中间会填补两个字节。因此,如果数组中所配置的内存大小不符合C语言的对齐规范,将会出现数据错位。为了解决这个问题,在创建dtype对象时,可以传递参数align=True,这样结构数组的内存对齐就和C语言的结构类型一致了。在前面的例子中,由于创建persontype时指定align参数为True,因此它占用40个字节。
结构类型中可以包括其他的结构类型,下面的语句创建一个有一个字段f1的结构,f1的值是另一个结构,它有字段f2,类型为16位整数:
np.dtype([('f1', [('f2', np.int16)])]) dtype([('f1', [('f2', '<i2')])])
当某个字段类型为数组时,用元组的第三个元素表示其形状。在下面的结构体中,f1字段是一个形状为(2, 3)的双精度浮点数组:
np.dtype([('f0', 'i4'), ('f1', 'f8', (2, 3))]) dtype([('f0', '<i4'), ('f1', '<f8', (2, 3))])
用下面的字典参数也可以定义结构类型,字典的键为结构的字段名,值为字段的类型描述。但是由于字典的键是没有顺序的,因此字段的顺序需要在类型描述中给出。类型描述是一个元组,它的第二个值给出字段的以字节为单位的偏移量,例如下例中的age字段的偏移量为25个字节:
np.dtype({'surname':('S25',0),'age':(np.uint8,25)}) dtype([('surname', 'S25'), ('age', 'u1')])