• 《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'
    """
    
  • 相关阅读:
    idea maven 经常主目录自动变回默认怎么办
    .net5 quartz
    sed命令
    ubuntu静态ip
    powershell基础
    restsharp 107.3 json自动转小写 自动驼峰 问题
    CQRS与事件溯源模式
    abp 切换默认项目为pgsql
    .net ASPNETCORE_ENVIRONMENT 根据环境切换不同的配置文件
    交换机端口三种模式Access,Hybrid,Trunk收发数据处理过程 yi
  • 原文地址:https://www.cnblogs.com/traditional/p/13362977.html
Copyright © 2020-2023  润新知