在 Cython 中声明结构体、共同体、枚举

简介: 在 Cython 中声明结构体、共同体、枚举


楔子




C 语言里面存在结构体、共同体、枚举,而这些在 Cython  里面也是支持的,只不过声明的方式不太一样,下面来看一看。


结构体




在 C 里面定义一个结构体,一般有两种方式。

#include <stdio.h>
// 直接定义,此时 struct Girl 整体是一个类型
struct Girl {
    char *name;
    int age;
};
// 使用 typedef 起一个别名
// 此时 Boy 是一个类型
typedef struct {
    char *name;
    int age;
} Boy;
int main() {
    // 声明一个 struct Girl 类型的实例
    struct Girl g;
    g.name = "mercy";
    g.age = 37;
    // 声明一个 Body 类型的实例
    Boy b;
    b.name = "hanzo";
    b.age = 38;
    printf("name = %s, age = %d\n", g.name, g.age);
    printf("name = %s, age = %d\n", b.name, b.age);
    /*
     name = mercy, age = 37
     name = hanzo, age = 38
     */
}

以上是 C 的结构体,在 Cython 里面要如何定义呢?

# 相当于 C 的 struct Girl{...};
cdef struct Girl:
    char *name
    int age
# 相当于 C 的 typedef struct {...} Boy;
ctypedef struct Boy:
    char *name
    int age
# 创建结构体实例,无论结构体使用哪一种方式定义
# 在创建实例的时候,格式都是一样的
cdef Girl g1
cdef Boy b1
# 创建的时候也可以直接赋值,支持位置参数和关键字参数
# 但是使用关键字参数要注意顺序
# 结构体字段出现的顺序,就是参数的顺序
cdef Girl g2 = Girl("mercy", 37)
cdef Boy b2 = Boy("hanzo", age=38)
# 打印的时候会以字典的形式打印
print(g2)
print(b2)
"""
{'name': b'mercy', 'age': 37}
{'name': b'hanzo', 'age': 38}
"""
# 当然啦,也可以先声明,然后单独赋值
# 我们上面创建了 g1 和 b1,下面赋值
g1.name, g1.age = "mercy", 37
b1.name, b1.age = "hanzo", 38
print(g1)
print(b1)
"""
{'name': b'mercy', 'age': 37}
{'name': b'hanzo', 'age': 38}
"""
# 通过 Python 的字典赋值
# 显然它涉及数据转换,会有额外开销
# 不建议使用此方式
cdef Girl g3 = {"name": "mercy", "age": 37}
cdef Boy b3 = {"name": "hanzo", "age": 38}
print(g3)
print(b3)
"""
{'name': b'mercy', 'age': 37}
{'name': b'hanzo', 'age': 38}
"""

在 C 里面还存在结构体的嵌套:

struct Girl {
    struct {
        char *name;
        int age;
    } Person;
    int length;
};

Cython 也是允许结构体嵌套的,但是定义必须要单独拿出来,什么意思呢?看个例子就明白了。

cdef struct Person:
    char *name
    int age
# Person 的定义必须要单独拿出来定义
# 不可以嵌套定义
cdef struct Girl:
    Person person
    int length
cdef Girl g = Girl(person=Person("mercy", 37), length=167)
print(g)
"""
{'person': {'name': b'mercy', 'age': 37}, 'length': 167}
"""

另外,当定义结构体的时候,字段的类型必须都是 C 的类型。


共同体




共同体的声明在 C 和 Cython 里面都和结构体类似,我们来看一下。

cdef union U1:
    short n1
    int n2
ctypedef union U2:
    short n1
    int n2
cdef U1 u1
u1.n2 = 0X1234_4321
print(u1.n1 == 0X4321)  # True
cdef U2 u2
u2.n2 = 0X4321_1234
print(u2.n1 == 0X1234)  # True

使用方法和结构体类似,关于共同体的具体知识这里就不多说了,可以查询 C 语言共同体相关的内容。总之它的目的是为了节省内存,一个结构体实例所占的内存,等于内部所有字段所占内存之和;而一个共同体实例所占的内存,等于内部占用内存最大的字段。

cdef struct S:
    short field1  # 2 字节
    int field2  # 4 字节
    ssize_t field3  # 8 字节
cdef union U:
    short field1
    int field2
    ssize_t field3
cdef S s
cdef U u
# 等于所有字段所占内存之和
# 2 + 4 + 8 = 14,但由于存在内存对齐
# 所以是 16
print(sizeof(s))  # 16
# 最长字段所占的内存
# 因此是 8
print(sizeof(u))  # 8

因此共同体明显要省内存,但是修改某一个字段,会影响其它字段,因为字段之间共用一组内存。而结构体则不会,字段之间互不影响,因为它们使用的是不同的内存。


枚举




定义枚举也很简单,我们可以在多行中定义,也可以在单行中定义然后用逗号隔开。

# 相当于 C 的 enum COLOR1 {};
cdef enum COLOR1:
    RED = 1
    YELLOW = 3
    GREEN = 5
# 相当于 C 的 typedef enum {} COLOR2;
ctypedef enum COLOR2:
    PURPLE, BROWN
# 在 C 里面声明一个枚举变量
# 可以是 enum COLOR1,整体是一个类型
# 也可以直接使用 COLOR2
# 但在 Cython 里面声明方式是一样的
# cdef COLOR1、cdef COLOR2
cdef COLOR1 c1 = YELLOW
cdef COLOR2 c2 = BROWN
print(YELLOW)
print(BROWN)
"""
3
1
"""

枚举比较简单。

以上就是结构体、共同体和枚举在 Cython 中的使用方式,但说实话,如果只是写 Cython,那么我们很少会用到这些 C 级结构。像结构体、共同体、枚举这些结构,一般都是在和外部的 C 代码交互的时候才会用到,比如我们要调用一个现有的 C 库函数,但这个函数接收一个结构体,那么这个时候我们会构造一个结构体然后传过去。


使用 ctypedef 给类型起别名




在 C 语言中有一个 typedef 关键字,可以用来给类型起别名,就像我们上面演示的那样。如果是使用 struct S {}; 这种方式定义结构体,那么 struct S 整体是一个类型。但我们可以通过 typedef 起一个别名,比如 typedef struct S{} S2; 那么声明的时候既可以使用 struct S,也可以直接使用 S2,因为 typedef 给 struct S 起了一个别名叫 S2。

注意:typedef 并不是定义了一个新的类型,它只是给一个已经存在的类型起了一个别名而已,像 CPython 里面有一个 Py_ssize_t,它就是 ssize_t 的别名。所以这个关键字的名字起不好,会让人以为使用 typedef 是定义一个新类型一样。

#include <stdio.h>
typedef int MY_INT;
int main() {
    MY_INT age = 17;
    printf("%d\n", age);  // 17
}

我们上面给 int 起了一个别名叫 MY_INT,而在 Cython 里面也是支持的,只不过关键字不叫 typedef,而是叫 ctypedef。

ctypedef list MY_LIST
def foo(MY_LIST lst):
    pass

文件名叫 cython_test.pyx,我们调用一下看看。

import pyximport
pyximport.install(language_level=3)
import cython_test
try:
    cython_test.foo(123)
except TypeError as e:
    print(e)
"""
Argument 'lst' has incorrect type (expected list, got int)
"""

参数接收的是 list 类型,因为 MY_LIST 是 list 的别名,但是我们传了一个整数进去,所以报错了。当然不管什么 Python 类型,None 都是满足的。

ctypedef 可以作用于 C 的类型也可以作用于 Python 类型,起别名之后,这个别名可以像上面那样作用于函数参数,也可以用于声明一个变量,比如 cdef MY_LIST lst。

但是不可以像这样:MY_LIST("123"),或者 isinstance(xxx, MY_LIST)。起的别名只能用在 C 语义当中,比如类型声明、或者 <MY_LIST?> 将变量静态化。但是不能用在 Python 语义当中,比如调用之类的,否则会报错:'MY_LIST' is not a constant, variable or function identifier。

ctypedef 一般也是用在和外部 C 代码交互上面,如果是纯 Cython,那么很少使用 ctypedef 起别名。但是对于 C++ 来说起别名则很常见,因为使用 typedef 可以显著的缩短长模板类型。

另外 ctypedef 对出现的位置也是有要求的,如果不和外部 C 代码交互的话,它应该出现在全局作用域中,不可以出现在函数等局部作用域里,也不可以出现在 if, for, while 语句块内。

相关文章
|
12月前
|
安全 C++
【自定义类型:结构体,枚举,联合】内存对齐的原理和原因
【自定义类型:结构体,枚举,联合】内存对齐的原理和原因
70 0
|
12月前
|
编译器 Linux C语言
C语言的自定义类型:结构体,枚举,联合
C语言的自定义类型:结构体,枚举,联合
31 0
|
4月前
|
编译器 C语言 C++
【C语言基础】:自定义类型(二) -->联合和枚举
【C语言基础】:自定义类型(二) -->联合和枚举
|
4月前
|
编译器 C语言 C++
【再识C进阶4】详细介绍自定义类型——结构体、枚举和联合
【再识C进阶4】详细介绍自定义类型——结构体、枚举和联合
|
5月前
|
存储 编译器 Linux
【C语言】自定义类型(结构体+枚举+联合)
【C语言】自定义类型(结构体+枚举+联合)
|
5月前
|
存储 编译器 C语言
C语言:自定义类型——联合和枚举
C语言:自定义类型——联合和枚举
|
C语言
C语言-自定义类型-枚举和联合(11.3)
C语言-自定义类型-枚举和联合(11.3)
66 0
|
11月前
|
存储 C语言
C语言自定义类型_枚举&联合(3)
C语言自定义类型_枚举&联合(3)
57 1
|
编译器 C语言 C++
【C语言】自定义类型:结构体、枚举、联合(上)
【C语言】自定义类型:结构体、枚举、联合(上)
59 0
|
存储 开发框架 .NET
【C语言】自定义类型:结构体、枚举、联合(下)
【C语言】自定义类型:结构体、枚举、联合(下)
70 0