日本免费高清视频-国产福利视频导航-黄色在线播放国产-天天操天天操天天操天天操|www.shdianci.com

學無先后,達者為師

網站首頁 編程語言 正文

Cython處理C字符串的示例詳解_python

作者:古明地覺 ? 更新時間: 2023-02-14 編程語言

楔子

在介紹數據類型的時候我們說過,Python 的數據類型相比 C 來說要更加的通用,但速度卻遠不及 C。如果你在使用 Cython 加速 Python 時遇到了瓶頸,但還希望更進一步,那么可以考慮將數據的類型替換成 C 的類型,特別是那些頻繁出現的數據,比如整數、浮點數、字符串。

由于整數和浮點數默認使用的就是 C 的類型,于是我們可以從字符串入手。

創建 C 字符串

先來回顧一下如何在 Cython 中創建 C 字符串。

cdef?char?*s1?=?b"abc"
print(s1)??#?b'abc'

C 的數據和 Python 數據如果想互相轉化,那么兩者應該存在一個對應關系,像整數和浮點數就不必說了。但 C 的字符串本質上是一個字符數組,所以它和 Python 的 bytes 對象是對應的,我們可以將 b"abc" 直接賦值給 s1。并且在打印的時候,也會轉成 Python 的 bytes 對象之后再打印。

或者還可以這么做:

cdef?char?s1[4]
s1[0],?s1[1],?s1[2]?=?97,?98,?99

cdef?bytes?s2?=?bytes([97,?98,?99])

print(s1)??#?b'abc'
print(s2)??#?b'abc'

直接聲明一個字符數組,然后再給數組的每個元素賦值即可。

Python 的 bytes 對象也是一個字符數組,和 C 一樣,數組的每個元素不能超過 255,所以兩者存在對應關系。在賦值的時候,會相互轉化,其它類型也是同理,舉個例子:

#?Python?整數和?C?整數是存在對應關系的
#?因為都是整數,所以可以相互賦值
py_num?=?123
#?會根據?Python?的整數創建出?C?的整數,然后賦值給?c_num
cdef?unsigned?int?c_num?=?py_num
#?print?是?Python?的函數,它接收的一定是?PyObject?*
#?所以在打印?C?的整數時,會轉成?Python?的整數再進行打印
print(c_num,?py_num)
"""
123?123
"""
#?但如果寫成?cdef?unsigned?int?c_num?=?"你好"?就不行了
#?因為?Python?的字符串和?C?的整數不存在對應關系
#?兩者無法相互轉化,自然也就無法賦值

#?浮點數也是同理,Python?和?C?的浮點數可以相互轉化
cdef?double?c_pi?=?3.14
#?賦值給?Python?變量時,也會轉成?Python?的浮點數再賦值
py_pi?=?3.14
print(c_pi,?py_pi)
"""
3.14?3.14
"""

#?Python?的?bytes?對象和?C?的字符串可以相互轉化
cdef?bytes?py_name?=?bytes("古明地覺",?encoding="utf-8")
cdef?char?*c_name?=?py_name
print(py_name?==?c_name)
"""
True
"""

#?注意:如果 Python 字符串所有字符的 ASCII ??均不超過 255
#?那么也可以賦值給?C?字符串
cdef?char?*name1?=?"satori"
cdef?char?*name2?=?b"satori"
print(name1,?name2)
"""
b'satori'?b'satori'
"""
#?"satori"?會直接當成?C?字符串來處理,因為它里面的字符均為?ASCII
#?就像寫?C?代碼一樣,所以?name1?和?name2?是等價的
#?而在轉成?Python?對象的時候,一律自動轉成?bytes?對象
#?但是注意:cdef char *c_name =?"古明地覺"?這行代碼不合法
#?因為里面出現了非?ASCII?字符,所以建議在給?C?字符串賦值的時候,一律使用?bytes?對象


#?C?的結構體和?Python?的字典存在對應關系
ctypedef?struct?Girl:
????char?*name
????int?age

cdef?Girl?g
g.name,?g.age?=?b"satori",?17
?#?在打印的時候,會轉成字典進行打印
#?當然前提是結構體的所有成員,都能用?Python?表示
print(g)
"""
{'name':?b'satori',?'age':?17}
"""

所以 Python 數據和 C 數據是可以互相轉化的,哪怕是結構體,也是可以的,只要兩者存在對應關系,可以互相表示。但像指針就不行了,Python 沒有任何一種原生類型能和 C 的指針相對應,所以 print 一個指針的時候就會出現編譯錯誤。

以上這些都是之前介紹過的內容,但很多人可能都忘了,這里專門再回顧一下。

引用計數陷阱

這里需要再補充一個關鍵點,由于 bytes 對象實現了緩沖區協議,所以它內部有一個緩沖區,這個緩沖區內部存儲了所有的字符。而在基于 bytes 對象創建 C 字符串的時候,不會拷貝緩沖區里的內容(整數、浮點數都是直接拷貝一份),而是直接創建一個指針指向這個緩沖區。

#?合法的代碼
py_name?=?"古明地覺".encode("utf-8")
cdef?char?*c_name1?=?py_name

#?不合法的代碼,會出現如下編譯錯誤
#?Storing?unsafe?C?derivative?of?temporary?Python?reference
cdef?char?*c_name2?=?"古明地覺".encode("utf-8")

為啥在創建 c_name2 的時候就會報錯呢?很簡單,因為這個過程中進行了函數調用,所以產生了臨時對象。換句話創建的 bytes 對象是臨時的,這行代碼執行結束后就會因為引用計數為 0 而被銷毀。

問題來了,c_name2 不是已經指向它了嗎?引用計數應該為 1 才對啊。相信你能猜到原因,這個 c_name2 的類型是 char *,它是一個 C 的變量,不會增加對象的引用計數。這個過程就是創建了一個 C 級指針,指向了臨時的 bytes 對象內部的緩沖區,而解釋器是不知道的。

所以臨時對象最終會因為引用計數為 0 被銷毀,但是這個 C 指針卻仍指向它的緩沖區,于是就報錯了。我們需要先創建一個 Python 變量指向它,讓其不被銷毀,然后才能賦值給 C 級指針。為了更好地說明這個現象,我們使用 bytearray 舉例說明。

cdef?bytearray?buf?=?bytearray("hello",?encoding="utf-8")
cdef?char?*c_str?=?buf

print(buf)??#?bytearray(b'hello')
#?基于?c_str?修改數據
c_str[0]?=?ord("H")
#?再次打印?buf
print(buf)??#?bytearray(b'Hello')
#?我們看到?buf?被修改了

bytearray 對象可以看作是可變的 bytes 對象,它們內部都實現了緩沖區,但 bytearray 對象的緩沖區是可以修改的,而 bytes 對象的緩沖區不能修改。所以這個例子就證明了上面的結論,C 字符串會直接共享 Python 對象的緩沖區。

因此在賦值的時候,我們應該像下面這么做。

print(
????"你好".encode("utf-8")
)??#?b'\xe4\xbd\xa0\xe5\xa5\xbd'

#?如果出現了函數或類的調用,那么會產生臨時對象
#?而臨時對象不能直接賦值給?C?指針,必須先用?Python?變量保存起來
cdef?bytes?greet?=?"你好".encode("utf-8")
cdef?char?*c_greet1?=?greet

#?如果非要直接賦值,那么賦的值一定是字面量的形式
#?這種方式也是可以的,但顯然程序開發中我們不會這么做
#?除非它是純?ASCII?字符
#?比如?cdef?char?*c_greet2?=?b"hello"
cdef?char?*c_greet2?=?b"\xe4\xbd\xa0\xe5\xa5\xbd"

print(c_greet1.decode("utf-8"))??#?你好
print(c_greet2.decode("utf-8"))??#?你好

以上就是 C 字符串本身相關的一些內容。

那么重點來了,假設我們將 Python 的字符串編碼成 bytes 對象之后,賦值給了 C 字符串,那么 C 語言都提供了哪些 API 讓我們去操作呢?

strlen

strlen 函數會返回字符串的長度,不包括末尾的空字符。C 字符串的結尾會有一個?\0,用于標識字符串的結束,而 strlen 不會統計 \0。

#?C?的庫函數,一律通過?libc?進行導入
from?libc.string?cimport?strlen

cdef?char?*s?=?b"satori"
print(strlen(s))??#?6

注意:strlen 和 sizeof 是兩個不同的概念,strlen 計算的是字符串的長度,只能接收字符串。而 sizeof 計算的是數據所占用的內存大小,可以接收所有 C 類型的數據。

from?libc.string?cimport?strlen

cdef?char?s[50]
#?strlen?是從頭遍歷,只要字符不是?\0,那么數量加?1
#?遇到?\0?停止遍歷,所以?strlen?計算的結果是?0
print(strlen(s))??#?0
#?而?sizeof?計算的是內存大小,當前數組?s?的長度為?50
print(sizeof(s))??#?50

s[0]?=?97
print(strlen(s))??#?1
s[1]?=?98
print(strlen(s))??#?2
print(sizeof(s))??#?50

當然啦,你也可以手動模擬 strlen 函數。

from?libc.string?cimport?strlen

cdef?ssize_t?my_strlen(const?char?*string):
????"""
????計算?C?字符串?string?的長度
????"""
????cdef?ssize_t?count?=?0
????while?string[count]?!=?b"\0":
????????count?+=?1
????return?count

cdef?char?*name?=?b"Hello?Cruel?World"
print(strlen(name))??#?17
print(my_strlen(name))??#?17

還是很簡單的,當然啦,我們也可以調用內置函數 len 進行計算,結果也是一樣的。只不過調用 len 的時候,會先基于 C 字符串創建 bytes 對象,這會多一層轉換,從而影響效率。

strcpy

然后是拷貝字符串,這里面有一些需要注意的地方。

from?libc.string?cimport?strcpy

cdef?char?name[10]
strcpy(name,?b"satori")
print(name)??#?b'satori'

strcpy(name,?b"koishi")
print(name)??#?b'koishi'

#?以上就完成了字符串的拷貝,但要注意?name?是數組的名字
#?我們不能給數組名賦值,比如?name?=?b"satori"
#?這是不合法的,因為它是一個常量
#?我們需要通過?name[索引]?或者?strcpy?的方式進行修改


#?或者還可以這么做,創建一個?bytearray?對象,長度?10
#?注意:這里不能用 bytes 對象,因為 bytes 對象的緩沖區不允許修改
cdef?buf?=?bytearray(10)
cdef?char?*name2?=?buf
strcpy(name2,?b"marisa")
print(buf)??#?bytearray(b'marisa\x00\x00\x00\x00')
print(name2)??#?b'marisa'

#?不過還是不建議使用?bytearray?作為緩沖區
#?直接通過?cdef?char?name2[10]?聲明即可

char name[10] 這種形式創建的數組是申請在棧區的,如果想跨函數調用,那么應該使用 malloc 申請在堆區。

然后 strcpy 這個函數存在一些隱患,就是它不會檢測目標字符串是否有足夠的空間去容納源字符串,因此可能導致溢出。

from?libc.string?cimport?strcpy

cdef?char?name[6]
#?會發生段錯誤,解釋器異常退出
#?因為源字符串有?6?個字符,再加上一個?\0
#?那么?name?的長度至少為?7?才可以
strcpy(name,?b"satori")
print(name)

因此如果你無法保證一定不會發生溢出,那么可以考慮使用 strncpy 函數。它和 strcpy 的用法完全一樣,只是多了第三個參數,用于指定復制的最大字符數,從而防止目標字符串發生溢出。

第三個參數 size?定義了復制的最大字符數,如果達到最大字符數以后,源字符串仍然沒有復制完,就會停止復制。如果源字符串的字符數小于目標字符串的容量,則 strncpy 的行為與 strcpy 完全一致。

from?libc.string?cimport?strncpy

cdef?char?name[6]
#?最多拷貝?5?個字符,因為要留一個給?\0
strncpy(name,?b"satori",?5)
print(name)??#?b'sator'


#?當然,即使目標字符串容量很大,我們也可以只拷貝一部分
cdef?char?words[100]
strncpy(words,?b"hello?world",?5)
print(words)??#?b'hello'

以上就是字符串的拷貝,并且對于目標字符串來說,每一次拷貝都相當于一次覆蓋,什么意思呢?舉個例子。

from?libc.string?cimport?strcpy

cdef?char?words[10]
strcpy(words,?b"abcdef")
#?此時的?words?就是?{a,?b,?c,?d,?e,?f,?\0,?\0,?\0,?\0}
#?然后我們繼續拷貝,會從頭開始覆蓋
strcpy(words,?b"xyz")
#?此時的?words?就是?{x,?y,?z,?\0,?e,?f,?\0,?\0,?\0,?\0}
#?因為字符串自帶?\0,所以?z?的結尾會有一個?\0
#?而?C?字符串在遇到?\0?的時候會自動停止
print(words)??#?b'xyz'
#?將?words[3]?改成?d
words[3]?=?ord("d")
print(words)??#?b'xyzdef'

所以要注意 \0,它是 C 編譯器判斷字符串是否結束的標識。

strcat

strcat?函數用于連接字符串,它接收兩個字符串作為參數,把第二個字符串的副本添加到第一個字符串的末尾。這個函數會改變第一個字符串,但是第二個字符串不變。

from?libc.string?cimport?strcpy,?strcat

cdef?char?words1[20]
strcpy(words1,?b"Hello")
print(words1)??#?b'Hello'
strcpy(words1,?b"World")
print(words1)??#?b'World'

cdef?char?words2[20]
strcat(words2,?b"Hello")
print(words2)??#?b'Hello'
strcat(words2,?b"World")
print(words2)??#?b'HelloWorld'

注意,strcat 會從目標字符串的第一個 \0 處開始,追加源字符串,所以目標字符串的剩余容量,必須足以容納源字符串。否則拼接后的字符串會溢出第一個字符串的邊界,寫入相鄰的內存單元,這是很危險的,建議使用下面的 strncat 代替。

strncat 和 strcat 的用法一致,但是多了第三個參數,用于指定追加的最大字符數。

from?libc.string?cimport?strncat,?strlen


cdef?char?target[10]
cdef?char?*source?=?b"Hello?World"
#?追加的最大字符數等于:容量?-?當前的長度?- 1
strncat(target,?source,
????????sizeof(target)?-?strlen(target)?-?1)
print(target)??#?b'Hello?Wor'

為了安全,建議使用 strncat。

strcmp

strcmp 用于字符串的比較,它會按照字符串的字典序比較兩個字符串的內容。

from?libc.string?cimport?strcmp

#?s1?==?s2,返回?0
print(
????strcmp(b"abc",?b"abc")
)??#?0

#?s1?>?s2,返回?1
print(
????strcmp(b"abd",?b"abc")
)??#?1

#?s1?<?s2,返回?0
print(
????strcmp(b"abc",?b"abd")
)??#?-1

由于 strcmp 比較的是整個字符串,于是 C 語言又提供了 strncmp 函數。strncmp 增加了第三個參數,表示比較的字符個數。

from?libc.string?cimport?strcmp,?strncmp

print(
????strcmp(b"abcdef",?b"abcDEF")
)??#?1

#?只比較?3?個字符
print(
????strncmp(b"abcdef",?b"abcDEF",?3)
)??#?0

比較簡單,并且比較規則和 strcmp 一樣。

sprintf

sprintf 函數 printf 類似,但是用于將數據寫入字符串,而不是輸出到顯示器。

from?libc.stdio?cimport?sprintf

cdef?char?s1[25]
sprintf(s1,?b"name:?%s,?age:?%d",?b"satori",?17)
print(s1)
"""
b'name:?satori,?age:?17'
"""

#?也可以指向?bytearray?的緩沖區
cdef?buf?=?bytearray(25)
cdef?char?*s2?=?buf
sprintf(s2,?b"name:?%s,?age:?%d",?b"satori",?17)
print(s2)
print(buf)
"""
b'name:?satori,?age:?17'
bytearray(b'name:?satori,?age:?17\x00\x00\x00\x00')
"""

#?或者申請在堆區
from?libc.stdlib?cimport?malloc
cdef?char?*s3?=?<char?*>malloc(25)
sprintf(s3,?b"name:?%s,?age:?%d",?b"satori",?17)
print(s3)
"""
b'name:?satori,?age:?17'
"""

同樣的,sprintf 也有嚴重的安全風險,如果寫入的字符串過長,超過了目標字符串的長度,sprintf 依然會將其寫入,導致發生溢出。為了控制寫入的字符串的長度,C 語言又提供了另一個函數 snprintf。

snprintf 多了一個參數,用于控制寫入字符的最大數量。

from?libc.stdio?cimport?snprintf

cdef?char?s1[10]
#?寫入的字符數量不能超過:?最大容量?-?1
snprintf(s1,?sizeof(s1)?-?1,?
?????????b"name:?%s,?age:?%d",?b"satori",?17)
print(s1)
"""
b'name:?sa'
"""

建議使用 snprintf,要更加的安全,如果是 sprintf,那么當溢出時會發生段錯誤,這是一個非常嚴重的錯誤。

動態申請字符串內存

我們還可以調用 malloc, calloc, realloc 函數為字符串動態申請內存,舉個例子:

from?libc.stdlib?cimport?(
????malloc,?calloc
)
from?libc.string?cimport?strcpy

#?這幾個函數所做的事情都是在堆上申請一塊內存
#?并且返回指向這塊內存的?void?*?指針
cdef?void?*p1?=?malloc(4)
#?我們想用它來存儲字符串,那么就將?void?*?轉成?char?*
strcpy(<char?*>p1,?b"abc")
#?或者也可以這么做
cdef?char?*p2?=?<char?*>malloc(4)
strcpy(p2,?b"def")

print(<char?*>p1)??#?b'abc'
print(p2)??#?b'def'

#?當然,申請的內存不光可以存儲字符串,其它數據也是可以的
cdef?int?*p3?=?<int?*>?malloc(8)
p3[0],?p3[1]?=?11,?22
print(p3[0]?+?p3[1])??#?33


#?以上是?malloc?的用法,然后是?calloc
#?它接收兩個參數,分別是申請的元素個數、每個元素占用的大小
cdef?int?*p4?=?<int?*>calloc(10,?sizeof(int))
#?它和下面是等價的
cdef?int?*p5?=?<int?*>calloc(10?*?4)

如果是在 C 里面,那么 malloc 申請的內存里面的數據是不確定的,而 calloc 申請的內存里面的數據會被自動初始化為 0。但在 Cython 里面,它們都會被初始化為 0。

并且還要注意兩點:

  • 1)malloc 和 calloc 在申請內存的時候可能會失敗,如果失敗則返回 NULL,因此在申請完之后最好判斷一下指針是否為 NULL;
  • 2)malloc 和 calloc 申請的內存都在堆區,不用了之后一定要調用 free 將內存釋放掉,free 接收一個 void *,用于釋放指向的堆內存。當然啦,為了安全起見,在釋放之前,先判斷指針是否為 NULL,不為 NULL 再釋放;

最后一個函數是 realloc,它用于修改已經分配的內存塊的大小,可以放大也可以縮小,返回一個指向新內存塊的指針。

from?libc.stdlib?cimport?(
????malloc,?realloc
)
from?libc.string?cimport?strcpy

cdef?char?*p1?=?<char?*>malloc(4)
strcpy(p1,?b"abc")

#?p1?指向的內存最多能容納?3?個有效字符串
#?如果希望它能容納更多,那么就要重新分配內存
p1?=?<char?*>realloc(p1,?8)

#?如果新內存塊小于原來的大小,則丟棄超出的部分;
#?大于原來的大小,則返回一個全新的地址,數據也會自動復制過去
#?如果第二個參數是?0,那么會釋放掉內存塊

#?如果?realloc?的第一個參數是?NULL,那么等價于?malloc
cdef?char?*p2?=?<char?*>realloc(NULL,?40)
#?等價于?cdef?char?*p2?=?<char?*>malloc(40)


#?由于有分配失敗的可能,所以調用?realloc?之后
#?最好檢查一下它的返回值是否為?NULL
#?并且分配失敗時,原有內存塊中的數據不會發生改變。

在 C 里面,malloc 和 realloc 申請的內存不會自動初始化,一般申請完之后還要手動初始化為 0。但在 Cython 里面,一律會自動初始化為 0,這一點就很方便了。

memset

memset 是一個初始化函數,它的作用是將某一塊內存的所有字節都設置為指定的值。

from?libc.stdlib?cimport?malloc
from?libc.string?cimport?memset

#?函數原型
#?void?*memset??(void?*block,?int?c,?size_t?size)
cdef?char?*s1?=?<char?*>malloc(10)
memset(<void?*>?s1,?ord('a'),?10?-?1)
#?全部被設置成了?a
print(s1)??#?b'aaaaaaaaa'

cdef?char?*s2?=?<char?*>malloc(10)
#?只設置前三個字節
memset(<void?*>?s2,?ord('a'),?3)
print(s2)??#?b'aaa'

在使用 memset 的時候,一般都是將內存里的值都初始化為 0。

memcpy

memcpy?用于將一塊內存拷貝到另一塊內存,用法和 strncpy 類似,但前者不光可以拷貝字符串,任意內存都可以拷貝,所以它接收的指針是 void *。

from?libc.string?cimport?memcpy

cdef?char?target[10]
cdef?char?*source?=?"Hello?World"

#?接收的指針類型是?void?*,它與數據類型無關
#?就是以字節為單位,將數據逐個拷貝過去
#?并且還有第三個參數,表示拷貝的最大字節數
memcpy(<void?*>?target,?<void?*>?source,?9)
print(target)??#?b'Hello?Wor'


#?同樣的,整數數組也可以
cdef?int?target2[5]
cdef?int?source2[3]
source2[0],?source2[1],?source2[2]?=?11,?22,?33
memcpy(<void?*>?target2,?<void?*>?source2,?5?*?sizeof(int))
print(target2[0],?target2[1],?target2[2])??#?11,?22,?33


#?當然你也可以自己實現一個?memcpy
cdef?void?my_memcpy(void?*src,?void?*dst,?ssize_t?count):
????#?不管?src?和?dst?指向什么類型,統一當成?1?字節的?char
????#?逐個遍歷,然后拷貝過去即可
????cdef?char?*s?=?<char?*>src
????cdef?char?*d?=?<char?*>dst
????#?在?Cython?里面解引用不可以通過?*p?的方式,而是要使用?p[0]
????#?因為?*p?這種形式在?Python?里面有另外的含義
????while?count?!=?0:
????????s[0]?=?d[0]
????????s?+=?1
????????d?+=?1

#?測試一下
cdef?float?target3[5]
cdef?float?source3[3]
source3[0],?source3[1],?source3[2]?=?3.14,?2.71,?1.732
memcpy(<void?*>?target3,?<void?*>?source3,?5?*?sizeof(float))
print(target3[0],?target3[1],?target3[2])??#?3.14,?2.71,?1.732

所以在拷貝字符串的時候,memcpy 和 strcpy 都可以使用,但是推薦 memcpy,速度更快也更安全。

memmove

memmove?函數用于將一段內存數據復制到另一段內存,它跟?memcpy 的作用相似,用法也一模一樣。但區別是 memmove 允許目標區域與源區域有重疊。如果發生重疊,源區域的內容會被更改;如果沒有重疊,那么它與?memcpy?行為相同。

from?libc.string?cimport?memcpy,?memmove

cdef?char?target1[20]
cdef?char?target2[20]
cdef?char?*source?=?"Hello?World"
#?target1、target2?和?source?均不重疊
#?所以?memcpy?和?memmove?是等價的
memcpy(<void?*>target1,?<void?*>source,?20?-?1)
memmove(<void?*>target2,?<void?*>source,?20?-?1)
print(target1)??#?b'Hello?World'
print(target2)??#?b'Hello?World'

#?但?&target1[0]?和?&target[1]?是有重疊的
#?將?target1[1:]?拷貝到?target1[0:],相當于每個字符往前移動一個位置
memmove(<void?*>&target1[0],?<void?*>&target1[1],?19?-?1)
print(target1)??#?b'ello?World'
#?顯然此時內容發生了覆蓋,這時候應該使用?memmove

應該很好理解。

memcmp

memcmp 用于比較兩個內存區域是否相同,前兩個參數是 void * 指針,第三個參數比較的字節數,所以它的用法和 strncmp 是一致的。

from?libc.string?cimport?memcmp,?strncmp

cdef?char?*s1?=?b"Hello1"
cdef?char?*s2?=?b"Hello2"
#?s1?==?s2?返回?0;s1?>=?s2?返回?1;s1?<=?s2?返回?-1
print(memcmp(<void?*>?s1,?<void?*>?s2,?6))??#?-1
print(memcmp(<void?*>?s1,?<void?*>?s2,?5))??#?0
print(strncmp(s1,?s2,?6))??#?-1
print(strncmp(s1,?s2,?5))??#?0

#?所以?memcmp?和?strncmp?的用法是一樣的
#?但?memcmp?在比較的時候會忽略?\0
cdef?char?s3[5]
cdef?char?s4[5]
#?'\0'?的?ASCII?碼就是?0
#?所以?s3?就相當于?{'a',?'b',?'c',?'\0',?'e'}
s3[0],?s3[1],?s3[2],?s3[3],?s3[4]?=?97,?98,?99,?0,?100
#?s4?就相當于?{'a',?'b',?'c',?'\0',?'f'}
s4[0],?s4[1],?s4[2],?s4[3],?s4[4]?=?97,?98,?99,?0,?101
#?strncmp?在比較的時候,如果遇到?\0,那么字符串就結束了
print(strncmp(s3,?s4,?5))??#?0
#?memcmp?支持所有數據類型的比較,不單單針對字符串
#?所以它在比較的時候不會關注?\0,就是逐一比較每個字節,直到達到指定的字節數
#?因為?e?的?ASCII?碼小于?f,所以結果是?-1
print(memcmp(<void?*>?s3,?<void?*>?s4,?5))??#?-1

以上就是 memcmp 的用法,我們總結一下出現的函數。

小結

以上就是在 Cython 中處理?C 字符串的一些操作,說實話大部分都是和 C 相關的內容,如果你熟悉 C 的話,那么這篇文章其實可以不用看。

因為 Cython 同理理解 C 和 Python,在加速的時候不妨把字符串換成 char * 試試吧。比如有一個操作 pgsql 的異步驅動 asyncpg 就是這么做的,因此速度非常快。

原文鏈接:https://mp.weixin.qq.com/s/mwVpSlLOuq--foHoJdTi7A

欄目分類
最近更新