《Cython标准库》1. libc.string

楔子

我们从现在开始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] = "abde"
# 出现了'',就表示结束了,所以只有两个字符
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'
"""
原文地址:https://www.cnblogs.com/traditional/p/13362977.html