《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'\xe5\x8f\xa4'
古
"""
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或者'\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碼就是'\0'
memset(s1, 0, 5 * sizeof(char))
# 清空三個
memset(s2, 0, 3 * sizeof(long))
# 清空四個
memset(s3, 0, 4 * sizeof(double))
# 字串遇到'\0'就結束了,所以列印一個空字串
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'停止搜尋
# 這裡我們沒有賦值,所以預設都是'\0',因此長度為0
print(strlen(s1)) # 0
# 賦上值
s1[0: 5] = "ab\0de"
# 出現了'\0',就表示結束了,所以只有兩個字元
print(strlen(s1)) # 2
# 將'\0'改掉, 因為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計算的字元長度,不包括'\0',但是'\0'確實存在於字串中,所以sizeof的計算結果會比strlen的結果多1,因此sizeof計算的位元組數,顯然'\0'也是屬於字串的一部分的。
from libc.string cimport strlen
cdef:
char s1[5]
# 儘管s1裡面都是'\0',但是它確實佔用了位元組
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作用一樣,都是計算字元長度,遇到'\0'停止
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裡面全是'\0'的話,strcpy和strcat是等價的
strcat(s1, "love ")
print(s1) # b'love '
# 但是現在s1的前5個字元不是'\0'了,所以此時strcat和strcpy的區別就出來了
# 如果是strcpy(s1, s2),那麼s1的結果就是"satori",從頭覆蓋
# 如果是strcat(s1, s2),那麼s1的結果就是"love satori",也就是會從第一個'\0'開始追加
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'
"""