楔子
我们从现在开始Cython的新篇章,会学习一下Cython提供的标准模块。当然按照我们之前学习的知识,其实已经能够实现很好的加速效果了,但是思来想去,觉得还是研究一下Cython提供的标准模块会比较好。
顺便,巩固一下之前学习的知识。
注意:从这里开始,可能需要你有一定的C语言基础。
我们这一次学习的是libc.string这个模块,从名字上也能看出这是用来处理C中字符串的,那么我们就来看看这个模块都提供了哪些关于字符串的操作吧。
libc.string
memcpy, 函数原型: void *memcpy (void *pto, const void *pfrom, size_t size)
将一个字符串拷贝到一个字符数组中,并且可以指定拷贝的字节数。举个栗子:
# cython_test.pyx
from libc.string cimport memcpy
cdef:
char s_to[10] # 创建一个字符数组
char *s_from = "hello satori" # 创建一个字符串
# 将字符串的内容拷贝到字符数组中, 拷贝3个字节
memcpy(s_to, s_from, 3)
print(s_to)
print(s_to.decode("utf-8-sig"))
import pyximport
pyximport.install(language_level=3)
import cython_test
"""
b'hel'
hel
"""
我们在声明s_to的时候,不可以使用char *,如果你懂C的话那么不用我多说。因此使用char *的话,那么s_to是一个不可变的值,我们无法改变它的内容。
另外,这里给s_from赋值的时候只能是ascii字符组成的字符串,如果是非ascii字符的话,那么必须要先转换成Python中的bytes,然后再赋值给char *。因此如果使用libc.string中的函数的话,那么最好是ascii字符,否则的话使用Python的方法就足够了。
from libc.string cimport memcpy
name = "古明地觉".encode("utf-8")
cdef:
char s_to[10]
# 我们不能将一个非ascii字符串赋值给char *,只能先转为Python中的bytes才可以赋值
char *s_from = name
# 将字符串的内容拷贝到字符数组中, 拷贝3个字节
# 函数接收的实际上是void *、const void *,所以我们可以转换一下,当然不转也是可以的
memcpy(<void *>s_to, <const void*>s_from, 3)
print(s_to)
print(s_to.decode("utf-8-sig"))
import pyximport
pyximport.install(language_level=3)
import cython_test
"""
b'xe5x8fxa4'
古
"""
memmove, 函数原型: void *memmove (void *pto, const void *pfrom, size_t size)
将一个字符串拷贝到一个字符串数组中,和memcpy类似,但是更安全。
from libc.string cimport memmove
cdef:
char s1[10]
char *s2 = "hello world"
# 此时的字符数组s1内部全部是0或者' ',我们可以给s1填上一部分值
# 我们看到可以通过切片的方式,这在C里面是不允许的
# 我们右边的字符长度要多一些,而多余的部分直接截断了,但是最好长度保持一致
s1[0: 3] = "mashiro"
print(s1) # b'mas'
# 这里拷贝5个字节
memmove(s1, s2, 5)
print(s1) # b'hello'
我们说char *对应Python中的bytes,所以会打印出字节。
另外,这里的pyx文件我们是单独导入的,只不过为了方便和直观,我们将输出直接写在了pyx文件里面。
memset, 函数原型: void *memset (void *block, int c, size_t size)
将字符数组里面的内容进行清空,它里面是void *,所以可以适用于整型数组、浮点型数组、字符数组,最后的size表示清空的字节数
from libc.string cimport memset
cdef:
char s1[5]
long s2[5]
double s3[5]
s1[0: 5] = "hello"
s2[0: 5] = [1, 2, 3, 4, 5]
s3[0: 5] = [1., 2., 3., 4., 5.]
print(s1) # b'hello'
print(s2) # [1, 2, 3, 4, 5]
print(s3) # [1.0, 2.0, 3.0, 4.0, 5.0]
# memset是专门用于清空一个数组的,这里的清空指的是设置为0
# 所以memset设置的时候直接设置成0即可,字符串的话也是0,因为0对应的ascii码就是' '
memset(s1, 0, 5 * sizeof(char))
# 清空三个
memset(s2, 0, 3 * sizeof(long))
# 清空四个
memset(s3, 0, 4 * sizeof(double))
# 字符串遇到' '就结束了,所以打印一个空字符串
print(s1) # b''
print(s2) # [0, 0, 0, 4, 5]
print(s3) # [0.0, 0.0, 0.0, 0.0, 5.0]
strlen, 函数原型: size_t strlen (const char *s)
返回一个字符串的长度
size_t:当成unsigned long即可
ssize_t:当成signed long即可
from libc.string cimport strlen
cdef:
char s1[10]
char *s2 = "satori"
# 可以容纳10个字符,但是strlen是计算字符长度,遇到' '停止搜索
# 这里我们没有赋值,所以默认都是' ',因此长度为0
print(strlen(s1)) # 0
# 赋上值
s1[0: 5] = "ab de"
# 出现了' ',就表示结束了,所以只有两个字符
print(strlen(s1)) # 2
# 将' '改掉, 因为s1[2]对应的是C中的char,而C中的char对应Python中的bytes
# 因此这里不能赋值为字符串了,因为赋值字符串的话会被解析成C的字符串,但这里接收的是一个char
# 所以要赋一个字节,但如果非要想赋值字符串,那么通过s1[2: 3] = "c"即可
s1[2: 3] = "c"
print(strlen(s1)) # 5
# s2的长度显然是6个
print(strlen(s2)) # 6
这里强调一下,strlen计算的字符长度,不包括' ',但是' '确实存在于字符串中,所以sizeof的计算结果会比strlen的结果多1,因此sizeof计算的字节数,显然' '也是属于字符串的一部分的。
from libc.string cimport strlen
cdef:
char s1[5]
# 尽管s1里面都是' ',但是它确实占用了字节
print(strlen(s1), sizeof(s1)) # 0 5
strcpy, 函数原型: char *strcpy (char *pto, const char *pfrom)
将一个字符串拷贝到一个字符数组中,和memcpy比较像,只不过strcpy只能接受char *。
from libc.string cimport strcpy
cdef:
char s1[10]
char *s2 = "satori"
# len和strlen作用一样,都是计算字符长度,遇到' '停止
print(len(s1)) # 0
print(sizeof(s1)) #10
strcpy(s1, s2)
print(s1) # b'satori'
print(sizeof(s1)) # 10
另外,如果字符数组本身就有字符的话,会怎么样呢?
from libc.string cimport strcat, strcpy
cdef:
char s1[20]
char *s2 = "satori"
# 注意:对于数组来说,它是一个常量,所以我们不能这样做s = "xxx"
# 比如通过索引或者切片(不支持负数),使用负数的话,解释器会异常退出
s1[0: 10] = "abcdefghij"
print(s1, sizeof(s1)) # b'abcdefghij' 20
strcpy(s1, s2)
print(s1, sizeof(s1)) # b'satori' 20
我们看到strcpy相当于文件读写中的w,先清空、再重头写。
strncpy, 函数原型: char *strcpy (char *pto, const char *pfrom, size_t size)
用法和strcpy一样,只不过可以多指定一个拷贝的字符数量。
from libc.string cimport strncpy
cdef:
char s1[10]
char *s2 = "satori"
strncpy(s1, s2, 3)
print(s1) # b"sat"
strdup, 函数原型: char *strdup (const char *s)
接收一个char *,将其指向的空间拷贝一份,然后再返回char *。
from libc.string cimport strdup
from libc.stdlib cimport free
cdef:
char *s1
char *s2 = "satori"
# 注意:返回的char *指向的是堆区的空间,所以一定要记得释放
s1 = strdup(s2)
print(s1) # b'satori'
# 既然是堆区,那么就可以随便修改
# 将第一个字符修改成'S'
s1[0] = b"S"
# s1是指向第一个字符的指针, 那么s1 + 3就是指向第4个字符的指针
(s1 + 4)[0] = b'O'
print(s1) # b'SatoOi'
# 最后要记得释放,当然你程序比较小的话,不释放也没关系,因为程序结束时也会释放
# 但这显然不是一个好习惯,有可能就是将来造成你内存泄漏的根源,所以堆区的内存要手动释放掉
# 因为这不是Python,没有人自动帮我们管理内存了,所以一切要靠我们手动管理了
free(s1)
strcat, 函数原型: char *strcat (char *pto, const char *pfrom)
和strcpy用法一致,只不过strcpy是从头覆盖,strcat是追加。举个栗子:
from libc.string cimport strcat
cdef:
char s1[20]
char *s2 = "satori"
# s1里面全是' '的话,strcpy和strcat是等价的
strcat(s1, "love ")
print(s1) # b'love '
# 但是现在s1的前5个字符不是' '了,所以此时strcat和strcpy的区别就出来了
# 如果是strcpy(s1, s2),那么s1的结果就是"satori",从头覆盖
# 如果是strcat(s1, s2),那么s1的结果就是"love satori",也就是会从第一个' '开始追加
strcat(s1, s2)
print(s1) # b'love satori'
strncat, 函数原型: char *strncat (char *pto, const char *pfrom, size_t size)
和strncat类似,追加指定个数的字符。
from libc.string cimport strncat, memset
from libc.stdlib cimport malloc, free
cdef:
# 我们用malloc动态申请内存,当然返回的是void *,我们需要转成char *
# char *可以自动转成void *,但是void *不能自动转成char *
char *s1 = <char *>malloc(sizeof(char) * 10)
char *s2 = "satori"
# 动态申请的内存,本身可能带有脏数据,因此我们需要设置成0
memset(s1, 0, sizeof(char) * 10)
# 拷贝3个字符
strncat(s1, s2, 3)
print(s1, sizeof(s1)) # b'sat' 8
free(s1)
strcmp, 函数原型: int strcmp (const char *s1, const char *s2)
比较两个字符串,如果相等返回0,s1大于s2返回1,s1小于s2返回-1。
from libc.string cimport strcmp
cdef:
char *s1 = "satori"
char *s2 = "satori"
print(strcmp(s1, s2)) # 0
# s1指向一个静态字符串,虽然不可以修改,但可以指向其它的字符串
# 换句话说,只要是一个字符指针,都可以赋值给它,同理s2也是如此
s1 = "satori1"
print(strcmp(s1, s2)) # 1
s2 = "satori2"
print(strcmp(s1, s2)) # -1
strncmp, 函数原型: int strncmp (const char *s1, const char *s2, size_t size)
和strcmp类似,strncmp是比较前n个字符。
from libc.string cimport strcmp, strncmp
cdef:
char *s1 = "satori"
char *s2 = "satorI"
print(strcmp(s1, s2)) # 1
print(strncmp(s1, s2, 5)) # 0
strchr, 函数原型: char *strchr (const char *string, int c)
查找第一次出现的字符之后的字符(包括本身)
from libc.string cimport strchr
cdef:
char *s1 = "satori"
# 如果C中接收一个char或者int,那么自python中则要传递bytes或者int
print(strchr(s1, ord('o'))) # b'ori'
# 或者b'o'也是可以的,但是'o'不行,因为C中接收的不是char *,而是int或char
print(strchr(s1, b'o')) # b'ori'
# 如果不存在则返回NULL
cdef char * res = strchr(s1, b"k")
if res == NULL: # 也可以用res is NULL
print("未找到该字符")
strrchr, 函数原型: char *strrchr (const char *string, int c)
和strchr类似,只不过strrchr是从右往左查。
from libc.string cimport strchr, strrchr
cdef:
char *s1 = "hello satori"
print(strchr(s1, b'o')) # b'o satori'
print(strrchr(s1, b'o')) # b'ori'
strstr, 函数原型: char *strstr (const char *haystack, const char *needle)
和strchr类似,strstr查询的是第一次出现的字符串以及其后面的所有字符。
from libc.string cimport strstr
cdef:
char *s1 = "hello satori"
# 对于char *,可以是Python中的ASCII字符串,也可以是bytes
# 但是char只能接收bytes(并且字节长度为1),当然int也是可以的,因为底层int和char是互转的
print(strstr(s1, "sato")) # b'satori'
# b"satori"、"satori".encode("utf-8")、bytes("satori", encoding="utf-8")都是可以的
# 因此一个char对应一个bytes,一个char *还是对应一个bytes,只不过此时的bytes可以包含多个字节,前者只能有一个
print(strstr(s1, b"sato")) # b'satori'
strcspn, 函数原型: size_t strcspn (const char *string, const char *stopset)
从左往右遍历string,找到第一个出现在stopset中的字符的位置。
from libc.string cimport strcspn
cdef:
char *s1 = "hello satori"
# 第一次出现'o'是在索引为4的地方
print(strcspn(s1, "o")) # 4
# 查找l、o第一次在s1中出现的位置,哪个先出现就返回哪个
print(strcspn(s1, "lo")) # 2
print(strcspn(s1, "ol")) # 2
# 如果不存在,那么返回的值为strlen(s1)
cdef int res = strcspn(s1, "K")
if res == strlen(s1):
print("没有该字符") # 没有该字符
strspn, 函数原型: size_t strspn (const char *string, const char *set)
从左往右遍历string,找到第一个不出现在set中的字符的位置。
from libc.string cimport strspn, strlen
cdef:
char *s1 = "hello satori"
# 从左往右遍历s1,第一个没有出现在"hel o"中的字符
# 显然是字符s
print(strspn(s1, "hel o")) # 6
# 如果是strcspn,那么表示第一次出现在"hel o"中的字符, 显然是0,上来就出现了
# 如果都出现了,那么返回值也是strlen
print(strspn(s1, "hello satori")) # 12
print(strlen(s1)) # 12
strtok, 函数原型: char *strtok (char *newstring, const char *delimiters)
对newstring使用delelimiters进行分隔,返回分隔后的第一个结果。
from libc.string cimport strtok
cdef:
char *s1 = "he-ll-o"
char *s2
s2 = strtok(s1, "-")
print(s2) # b'he'
如果想要每一个字符都分隔的话,怎么做呢?
from libc.string cimport strtok
cdef:
char *s1 = "he-ll-o-sa-to-ri"
char *s2
s2 = strtok(s1, "-")
while s2 != NULL:
print(s2)
s2 = strtok(NULL, "-")
"""
b'he'
b'll'
b'o'
b'sa'
b'to'
b'ri'
"""