Cython處理C字符串的示例詳解
楔子
在介紹數(shù)據(jù)類型的時(shí)候我們說過,Python 的數(shù)據(jù)類型相比 C 來說要更加的通用,但速度卻遠(yuǎn)不及 C。如果你在使用 Cython 加速 Python 時(shí)遇到了瓶頸,但還希望更進(jìn)一步,那么可以考慮將數(shù)據(jù)的類型替換成 C 的類型,特別是那些頻繁出現(xiàn)的數(shù)據(jù),比如整數(shù)、浮點(diǎn)數(shù)、字符串。
由于整數(shù)和浮點(diǎn)數(shù)默認(rèn)使用的就是 C 的類型,于是我們可以從字符串入手。
創(chuàng)建 C 字符串
先來回顧一下如何在 Cython 中創(chuàng)建 C 字符串。
cdef?char?*s1?=?b"abc" print(s1)??#?b'abc'
C 的數(shù)據(jù)和 Python 數(shù)據(jù)如果想互相轉(zhuǎn)化,那么兩者應(yīng)該存在一個(gè)對(duì)應(yīng)關(guān)系,像整數(shù)和浮點(diǎn)數(shù)就不必說了。但 C 的字符串本質(zhì)上是一個(gè)字符數(shù)組,所以它和 Python 的 bytes 對(duì)象是對(duì)應(yīng)的,我們可以將 b"abc" 直接賦值給 s1。并且在打印的時(shí)候,也會(huì)轉(zhuǎn)成 Python 的 bytes 對(duì)象之后再打印。
或者還可以這么做:
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'
直接聲明一個(gè)字符數(shù)組,然后再給數(shù)組的每個(gè)元素賦值即可。
Python 的 bytes 對(duì)象也是一個(gè)字符數(shù)組,和 C 一樣,數(shù)組的每個(gè)元素不能超過 255,所以兩者存在對(duì)應(yīng)關(guān)系。在賦值的時(shí)候,會(huì)相互轉(zhuǎn)化,其它類型也是同理,舉個(gè)例子:
#?Python?整數(shù)和?C?整數(shù)是存在對(duì)應(yīng)關(guān)系的 #?因?yàn)槎际钦麛?shù),所以可以相互賦值 py_num?=?123 #?會(huì)根據(jù)?Python?的整數(shù)創(chuàng)建出?C?的整數(shù),然后賦值給?c_num cdef?unsigned?int?c_num?=?py_num #?print?是?Python?的函數(shù),它接收的一定是?PyObject?* #?所以在打印?C?的整數(shù)時(shí),會(huì)轉(zhuǎn)成?Python?的整數(shù)再進(jìn)行打印 print(c_num,?py_num) """ 123?123 """ #?但如果寫成?cdef?unsigned?int?c_num?=?"你好"?就不行了 #?因?yàn)?Python?的字符串和?C?的整數(shù)不存在對(duì)應(yīng)關(guān)系 #?兩者無法相互轉(zhuǎn)化,自然也就無法賦值 #?浮點(diǎn)數(shù)也是同理,Python?和?C?的浮點(diǎn)數(shù)可以相互轉(zhuǎn)化 cdef?double?c_pi?=?3.14 #?賦值給?Python?變量時(shí),也會(huì)轉(zhuǎn)成?Python?的浮點(diǎn)數(shù)再賦值 py_pi?=?3.14 print(c_pi,?py_pi) """ 3.14?3.14 """ #?Python?的?bytes?對(duì)象和?C?的字符串可以相互轉(zhuǎn)化 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"?會(huì)直接當(dāng)成?C?字符串來處理,因?yàn)樗锩娴淖址鶠?ASCII #?就像寫?C?代碼一樣,所以?name1?和?name2?是等價(jià)的 #?而在轉(zhuǎn)成?Python?對(duì)象的時(shí)候,一律自動(dòng)轉(zhuǎn)成?bytes?對(duì)象 #?但是注意:cdef char *c_name =?"古明地覺"?這行代碼不合法 #?因?yàn)槔锩娉霈F(xiàn)了非?ASCII?字符,所以建議在給?C?字符串賦值的時(shí)候,一律使用?bytes?對(duì)象 #?C?的結(jié)構(gòu)體和?Python?的字典存在對(duì)應(yīng)關(guān)系 ctypedef?struct?Girl: ????char?*name ????int?age cdef?Girl?g g.name,?g.age?=?b"satori",?17 ?#?在打印的時(shí)候,會(huì)轉(zhuǎn)成字典進(jìn)行打印 #?當(dāng)然前提是結(jié)構(gòu)體的所有成員,都能用?Python?表示 print(g) """ {'name':?b'satori',?'age':?17} """
所以 Python 數(shù)據(jù)和 C 數(shù)據(jù)是可以互相轉(zhuǎn)化的,哪怕是結(jié)構(gòu)體,也是可以的,只要兩者存在對(duì)應(yīng)關(guān)系,可以互相表示。但像指針就不行了,Python 沒有任何一種原生類型能和 C 的指針相對(duì)應(yīng),所以 print 一個(gè)指針的時(shí)候就會(huì)出現(xiàn)編譯錯(cuò)誤。
以上這些都是之前介紹過的內(nèi)容,但很多人可能都忘了,這里專門再回顧一下。
引用計(jì)數(shù)陷阱
這里需要再補(bǔ)充一個(gè)關(guān)鍵點(diǎn),由于 bytes 對(duì)象實(shí)現(xiàn)了緩沖區(qū)協(xié)議,所以它內(nèi)部有一個(gè)緩沖區(qū),這個(gè)緩沖區(qū)內(nèi)部存儲(chǔ)了所有的字符。而在基于 bytes 對(duì)象創(chuàng)建 C 字符串的時(shí)候,不會(huì)拷貝緩沖區(qū)里的內(nèi)容(整數(shù)、浮點(diǎn)數(shù)都是直接拷貝一份),而是直接創(chuàng)建一個(gè)指針指向這個(gè)緩沖區(qū)。
#?合法的代碼 py_name?=?"古明地覺".encode("utf-8") cdef?char?*c_name1?=?py_name #?不合法的代碼,會(huì)出現(xiàn)如下編譯錯(cuò)誤 #?Storing?unsafe?C?derivative?of?temporary?Python?reference cdef?char?*c_name2?=?"古明地覺".encode("utf-8")
為啥在創(chuàng)建 c_name2 的時(shí)候就會(huì)報(bào)錯(cuò)呢?很簡單,因?yàn)檫@個(gè)過程中進(jìn)行了函數(shù)調(diào)用,所以產(chǎn)生了臨時(shí)對(duì)象。換句話創(chuàng)建的 bytes 對(duì)象是臨時(shí)的,這行代碼執(zhí)行結(jié)束后就會(huì)因?yàn)橐糜?jì)數(shù)為 0 而被銷毀。
問題來了,c_name2 不是已經(jīng)指向它了嗎?引用計(jì)數(shù)應(yīng)該為 1 才對(duì)啊。相信你能猜到原因,這個(gè) c_name2 的類型是 char *,它是一個(gè) C 的變量,不會(huì)增加對(duì)象的引用計(jì)數(shù)。這個(gè)過程就是創(chuàng)建了一個(gè) C 級(jí)指針,指向了臨時(shí)的 bytes 對(duì)象內(nèi)部的緩沖區(qū),而解釋器是不知道的。
所以臨時(shí)對(duì)象最終會(huì)因?yàn)橐糜?jì)數(shù)為 0 被銷毀,但是這個(gè) C 指針卻仍指向它的緩沖區(qū),于是就報(bào)錯(cuò)了。我們需要先創(chuàng)建一個(gè) Python 變量指向它,讓其不被銷毀,然后才能賦值給 C 級(jí)指針。為了更好地說明這個(gè)現(xiàn)象,我們使用 bytearray 舉例說明。
cdef?bytearray?buf?=?bytearray("hello",?encoding="utf-8") cdef?char?*c_str?=?buf print(buf)??#?bytearray(b'hello') #?基于?c_str?修改數(shù)據(jù) c_str[0]?=?ord("H") #?再次打印?buf print(buf)??#?bytearray(b'Hello') #?我們看到?buf?被修改了
bytearray 對(duì)象可以看作是可變的 bytes 對(duì)象,它們內(nèi)部都實(shí)現(xiàn)了緩沖區(qū),但 bytearray 對(duì)象的緩沖區(qū)是可以修改的,而 bytes 對(duì)象的緩沖區(qū)不能修改。所以這個(gè)例子就證明了上面的結(jié)論,C 字符串會(huì)直接共享 Python 對(duì)象的緩沖區(qū)。
因此在賦值的時(shí)候,我們應(yīng)該像下面這么做。
print( ????"你好".encode("utf-8") )??#?b'\xe4\xbd\xa0\xe5\xa5\xbd' #?如果出現(xiàn)了函數(shù)或類的調(diào)用,那么會(huì)產(chǎn)生臨時(shí)對(duì)象 #?而臨時(shí)對(duì)象不能直接賦值給?C?指針,必須先用?Python?變量保存起來 cdef?bytes?greet?=?"你好".encode("utf-8") cdef?char?*c_greet1?=?greet #?如果非要直接賦值,那么賦的值一定是字面量的形式 #?這種方式也是可以的,但顯然程序開發(fā)中我們不會(huì)這么做 #?除非它是純?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 字符串本身相關(guān)的一些內(nèi)容。
那么重點(diǎn)來了,假設(shè)我們將 Python 的字符串編碼成 bytes 對(duì)象之后,賦值給了 C 字符串,那么 C 語言都提供了哪些 API 讓我們?nèi)ゲ僮髂兀?/p>
strlen
strlen 函數(shù)會(huì)返回字符串的長度,不包括末尾的空字符。C 字符串的結(jié)尾會(huì)有一個(gè) \0,用于標(biāo)識(shí)字符串的結(jié)束,而 strlen 不會(huì)統(tǒng)計(jì) \0。
#?C?的庫函數(shù),一律通過?libc?進(jìn)行導(dǎo)入 from?libc.string?cimport?strlen cdef?char?*s?=?b"satori" print(strlen(s))??#?6
注意:strlen 和 sizeof 是兩個(gè)不同的概念,strlen 計(jì)算的是字符串的長度,只能接收字符串。而 sizeof 計(jì)算的是數(shù)據(jù)所占用的內(nèi)存大小,可以接收所有 C 類型的數(shù)據(jù)。
from?libc.string?cimport?strlen cdef?char?s[50] #?strlen?是從頭遍歷,只要字符不是?\0,那么數(shù)量加?1 #?遇到?\0?停止遍歷,所以?strlen?計(jì)算的結(jié)果是?0 print(strlen(s))??#?0 #?而?sizeof?計(jì)算的是內(nèi)存大小,當(dāng)前數(shù)組?s?的長度為?50 print(sizeof(s))??#?50 s[0]?=?97 print(strlen(s))??#?1 s[1]?=?98 print(strlen(s))??#?2 print(sizeof(s))??#?50
當(dāng)然啦,你也可以手動(dòng)模擬 strlen 函數(shù)。
from?libc.string?cimport?strlen cdef?ssize_t?my_strlen(const?char?*string): ????""" ????計(jì)算?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
還是很簡單的,當(dāng)然啦,我們也可以調(diào)用內(nèi)置函數(shù) len 進(jìn)行計(jì)算,結(jié)果也是一樣的。只不過調(diào)用 len 的時(shí)候,會(huì)先基于 C 字符串創(chuàng)建 bytes 對(duì)象,這會(huì)多一層轉(zhuǎn)換,從而影響效率。
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?是數(shù)組的名字 #?我們不能給數(shù)組名賦值,比如?name?=?b"satori" #?這是不合法的,因?yàn)樗且粋€(gè)常量 #?我們需要通過?name[索引]?或者?strcpy?的方式進(jìn)行修改 #?或者還可以這么做,創(chuàng)建一個(gè)?bytearray?對(duì)象,長度?10 #?注意:這里不能用 bytes 對(duì)象,因?yàn)?bytes 對(duì)象的緩沖區(qū)不允許修改 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?作為緩沖區(qū) #?直接通過?cdef?char?name2[10]?聲明即可
char name[10] 這種形式創(chuàng)建的數(shù)組是申請(qǐng)?jiān)跅^(qū)的,如果想跨函數(shù)調(diào)用,那么應(yīng)該使用 malloc 申請(qǐng)?jiān)诙褏^(qū)。
然后 strcpy 這個(gè)函數(shù)存在一些隱患,就是它不會(huì)檢測(cè)目標(biāo)字符串是否有足夠的空間去容納源字符串,因此可能導(dǎo)致溢出。
from?libc.string?cimport?strcpy cdef?char?name[6] #?會(huì)發(fā)生段錯(cuò)誤,解釋器異常退出 #?因?yàn)樵醋址?6?個(gè)字符,再加上一個(gè)?\0 #?那么?name?的長度至少為?7?才可以 strcpy(name,?b"satori") print(name)
因此如果你無法保證一定不會(huì)發(fā)生溢出,那么可以考慮使用 strncpy 函數(shù)。它和 strcpy 的用法完全一樣,只是多了第三個(gè)參數(shù),用于指定復(fù)制的最大字符數(shù),從而防止目標(biāo)字符串發(fā)生溢出。
第三個(gè)參數(shù) size 定義了復(fù)制的最大字符數(shù),如果達(dá)到最大字符數(shù)以后,源字符串仍然沒有復(fù)制完,就會(huì)停止復(fù)制。如果源字符串的字符數(shù)小于目標(biāo)字符串的容量,則 strncpy 的行為與 strcpy 完全一致。
from?libc.string?cimport?strncpy cdef?char?name[6] #?最多拷貝?5?個(gè)字符,因?yàn)橐粢粋€(gè)給?\0 strncpy(name,?b"satori",?5) print(name)??#?b'sator' #?當(dāng)然,即使目標(biāo)字符串容量很大,我們也可以只拷貝一部分 cdef?char?words[100] strncpy(words,?b"hello?world",?5) print(words)??#?b'hello'
以上就是字符串的拷貝,并且對(duì)于目標(biāo)字符串來說,每一次拷貝都相當(dāng)于一次覆蓋,什么意思呢?舉個(gè)例子。
from?libc.string?cimport?strcpy cdef?char?words[10] strcpy(words,?b"abcdef") #?此時(shí)的?words?就是?{a,?b,?c,?d,?e,?f,?\0,?\0,?\0,?\0} #?然后我們繼續(xù)拷貝,會(huì)從頭開始覆蓋 strcpy(words,?b"xyz") #?此時(shí)的?words?就是?{x,?y,?z,?\0,?e,?f,?\0,?\0,?\0,?\0} #?因?yàn)樽址詭?\0,所以?z?的結(jié)尾會(huì)有一個(gè)?\0 #?而?C?字符串在遇到?\0?的時(shí)候會(huì)自動(dòng)停止 print(words)??#?b'xyz' #?將?words[3]?改成?d words[3]?=?ord("d") print(words)??#?b'xyzdef'
所以要注意 \0,它是 C 編譯器判斷字符串是否結(jié)束的標(biāo)識(shí)。
strcat
strcat 函數(shù)用于連接字符串,它接收兩個(gè)字符串作為參數(shù),把第二個(gè)字符串的副本添加到第一個(gè)字符串的末尾。這個(gè)函數(shù)會(huì)改變第一個(gè)字符串,但是第二個(gè)字符串不變。
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 會(huì)從目標(biāo)字符串的第一個(gè) \0 處開始,追加源字符串,所以目標(biāo)字符串的剩余容量,必須足以容納源字符串。否則拼接后的字符串會(huì)溢出第一個(gè)字符串的邊界,寫入相鄰的內(nèi)存單元,這是很危險(xiǎn)的,建議使用下面的 strncat 代替。
strncat 和 strcat 的用法一致,但是多了第三個(gè)參數(shù),用于指定追加的最大字符數(shù)。
from?libc.string?cimport?strncat,?strlen cdef?char?target[10] cdef?char?*source?=?b"Hello?World" #?追加的最大字符數(shù)等于:容量?-?當(dāng)前的長度?- 1 strncat(target,?source, ????????sizeof(target)?-?strlen(target)?-?1) print(target)??#?b'Hello?Wor'
為了安全,建議使用 strncat。
strcmp
strcmp 用于字符串的比較,它會(huì)按照字符串的字典序比較兩個(gè)字符串的內(nèi)容。
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 比較的是整個(gè)字符串,于是 C 語言又提供了 strncmp 函數(shù)。strncmp 增加了第三個(gè)參數(shù),表示比較的字符個(gè)數(shù)。
from?libc.string?cimport?strcmp,?strncmp print( ????strcmp(b"abcdef",?b"abcDEF") )??#?1 #?只比較?3?個(gè)字符 print( ????strncmp(b"abcdef",?b"abcDEF",?3) )??#?0
比較簡單,并且比較規(guī)則和 strcmp 一樣。
sprintf
sprintf 函數(shù) printf 類似,但是用于將數(shù)據(jù)寫入字符串,而不是輸出到顯示器。
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?的緩沖區(qū) 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') """ #?或者申請(qǐng)?jiān)诙褏^(qū) 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 也有嚴(yán)重的安全風(fēng)險(xiǎn),如果寫入的字符串過長,超過了目標(biāo)字符串的長度,sprintf 依然會(huì)將其寫入,導(dǎo)致發(fā)生溢出。為了控制寫入的字符串的長度,C 語言又提供了另一個(gè)函數(shù) snprintf。
snprintf 多了一個(gè)參數(shù),用于控制寫入字符的最大數(shù)量。
from?libc.stdio?cimport?snprintf cdef?char?s1[10] #?寫入的字符數(shù)量不能超過:?最大容量?-?1 snprintf(s1,?sizeof(s1)?-?1,? ?????????b"name:?%s,?age:?%d",?b"satori",?17) print(s1) """ b'name:?sa' """
建議使用 snprintf,要更加的安全,如果是 sprintf,那么當(dāng)溢出時(shí)會(huì)發(fā)生段錯(cuò)誤,這是一個(gè)非常嚴(yán)重的錯(cuò)誤。
動(dòng)態(tài)申請(qǐng)字符串內(nèi)存
我們還可以調(diào)用 malloc, calloc, realloc 函數(shù)為字符串動(dòng)態(tài)申請(qǐng)內(nèi)存,舉個(gè)例子:
from?libc.stdlib?cimport?( ????malloc,?calloc ) from?libc.string?cimport?strcpy #?這幾個(gè)函數(shù)所做的事情都是在堆上申請(qǐng)一塊內(nèi)存 #?并且返回指向這塊內(nèi)存的?void?*?指針 cdef?void?*p1?=?malloc(4) #?我們想用它來存儲(chǔ)字符串,那么就將?void?*?轉(zhuǎn)成?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' #?當(dāng)然,申請(qǐng)的內(nèi)存不光可以存儲(chǔ)字符串,其它數(shù)據(jù)也是可以的 cdef?int?*p3?=?<int?*>?malloc(8) p3[0],?p3[1]?=?11,?22 print(p3[0]?+?p3[1])??#?33 #?以上是?malloc?的用法,然后是?calloc #?它接收兩個(gè)參數(shù),分別是申請(qǐng)的元素個(gè)數(shù)、每個(gè)元素占用的大小 cdef?int?*p4?=?<int?*>calloc(10,?sizeof(int)) #?它和下面是等價(jià)的 cdef?int?*p5?=?<int?*>calloc(10?*?4)
如果是在 C 里面,那么 malloc 申請(qǐng)的內(nèi)存里面的數(shù)據(jù)是不確定的,而 calloc 申請(qǐng)的內(nèi)存里面的數(shù)據(jù)會(huì)被自動(dòng)初始化為 0。但在 Cython 里面,它們都會(huì)被初始化為 0。
并且還要注意兩點(diǎn):
- 1)malloc 和 calloc 在申請(qǐng)內(nèi)存的時(shí)候可能會(huì)失敗,如果失敗則返回 NULL,因此在申請(qǐng)完之后最好判斷一下指針是否為 NULL;
- 2)malloc 和 calloc 申請(qǐng)的內(nèi)存都在堆區(qū),不用了之后一定要調(diào)用 free 將內(nèi)存釋放掉,free 接收一個(gè) void *,用于釋放指向的堆內(nèi)存。當(dāng)然啦,為了安全起見,在釋放之前,先判斷指針是否為 NULL,不為 NULL 再釋放;
最后一個(gè)函數(shù)是 realloc,它用于修改已經(jīng)分配的內(nèi)存塊的大小,可以放大也可以縮小,返回一個(gè)指向新內(nèi)存塊的指針。
from?libc.stdlib?cimport?( ????malloc,?realloc ) from?libc.string?cimport?strcpy cdef?char?*p1?=?<char?*>malloc(4) strcpy(p1,?b"abc") #?p1?指向的內(nèi)存最多能容納?3?個(gè)有效字符串 #?如果希望它能容納更多,那么就要重新分配內(nèi)存 p1?=?<char?*>realloc(p1,?8) #?如果新內(nèi)存塊小于原來的大小,則丟棄超出的部分; #?大于原來的大小,則返回一個(gè)全新的地址,數(shù)據(jù)也會(huì)自動(dòng)復(fù)制過去 #?如果第二個(gè)參數(shù)是?0,那么會(huì)釋放掉內(nèi)存塊 #?如果?realloc?的第一個(gè)參數(shù)是?NULL,那么等價(jià)于?malloc cdef?char?*p2?=?<char?*>realloc(NULL,?40) #?等價(jià)于?cdef?char?*p2?=?<char?*>malloc(40) #?由于有分配失敗的可能,所以調(diào)用?realloc?之后 #?最好檢查一下它的返回值是否為?NULL #?并且分配失敗時(shí),原有內(nèi)存塊中的數(shù)據(jù)不會(huì)發(fā)生改變。
在 C 里面,malloc 和 realloc 申請(qǐng)的內(nèi)存不會(huì)自動(dòng)初始化,一般申請(qǐng)完之后還要手動(dòng)初始化為 0。但在 Cython 里面,一律會(huì)自動(dòng)初始化為 0,這一點(diǎn)就很方便了。
memset
memset 是一個(gè)初始化函數(shù),它的作用是將某一塊內(nèi)存的所有字節(jié)都設(shè)置為指定的值。
from?libc.stdlib?cimport?malloc from?libc.string?cimport?memset #?函數(shù)原型 #?void?*memset??(void?*block,?int?c,?size_t?size) cdef?char?*s1?=?<char?*>malloc(10) memset(<void?*>?s1,?ord('a'),?10?-?1) #?全部被設(shè)置成了?a print(s1)??#?b'aaaaaaaaa' cdef?char?*s2?=?<char?*>malloc(10) #?只設(shè)置前三個(gè)字節(jié) memset(<void?*>?s2,?ord('a'),?3) print(s2)??#?b'aaa'
在使用 memset 的時(shí)候,一般都是將內(nèi)存里的值都初始化為 0。
memcpy
memcpy 用于將一塊內(nèi)存拷貝到另一塊內(nèi)存,用法和 strncpy 類似,但前者不光可以拷貝字符串,任意內(nèi)存都可以拷貝,所以它接收的指針是 void *。
from?libc.string?cimport?memcpy cdef?char?target[10] cdef?char?*source?=?"Hello?World" #?接收的指針類型是?void?*,它與數(shù)據(jù)類型無關(guān) #?就是以字節(jié)為單位,將數(shù)據(jù)逐個(gè)拷貝過去 #?并且還有第三個(gè)參數(shù),表示拷貝的最大字節(jié)數(shù) memcpy(<void?*>?target,?<void?*>?source,?9) print(target)??#?b'Hello?Wor' #?同樣的,整數(shù)數(shù)組也可以 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 #?當(dāng)然你也可以自己實(shí)現(xiàn)一個(gè)?memcpy cdef?void?my_memcpy(void?*src,?void?*dst,?ssize_t?count): ????#?不管?src?和?dst?指向什么類型,統(tǒng)一當(dāng)成?1?字節(jié)的?char ????#?逐個(gè)遍歷,然后拷貝過去即可 ????cdef?char?*s?=?<char?*>src ????cdef?char?*d?=?<char?*>dst ????#?在?Cython?里面解引用不可以通過?*p?的方式,而是要使用?p[0] ????#?因?yàn)?*p?這種形式在?Python?里面有另外的含義 ????while?count?!=?0: ????????s[0]?=?d[0] ????????s?+=?1 ????????d?+=?1 #?測(cè)試一下 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
所以在拷貝字符串的時(shí)候,memcpy 和 strcpy 都可以使用,但是推薦 memcpy,速度更快也更安全。
memmove
memmove 函數(shù)用于將一段內(nèi)存數(shù)據(jù)復(fù)制到另一段內(nèi)存,它跟 memcpy 的作用相似,用法也一模一樣。但區(qū)別是 memmove 允許目標(biāo)區(qū)域與源區(qū)域有重疊。如果發(fā)生重疊,源區(qū)域的內(nèi)容會(huì)被更改;如果沒有重疊,那么它與 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?是等價(jià)的 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:],相當(dāng)于每個(gè)字符往前移動(dòng)一個(gè)位置 memmove(<void?*>&target1[0],?<void?*>&target1[1],?19?-?1) print(target1)??#?b'ello?World' #?顯然此時(shí)內(nèi)容發(fā)生了覆蓋,這時(shí)候應(yīng)該使用?memmove
應(yīng)該很好理解。
memcmp
memcmp 用于比較兩個(gè)內(nèi)存區(qū)域是否相同,前兩個(gè)參數(shù)是 void * 指針,第三個(gè)參數(shù)比較的字節(jié)數(shù),所以它的用法和 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?在比較的時(shí)候會(huì)忽略?\0 cdef?char?s3[5] cdef?char?s4[5] #?'\0'?的?ASCII?碼就是?0 #?所以?s3?就相當(dāng)于?{'a',?'b',?'c',?'\0',?'e'} s3[0],?s3[1],?s3[2],?s3[3],?s3[4]?=?97,?98,?99,?0,?100 #?s4?就相當(dāng)于?{'a',?'b',?'c',?'\0',?'f'} s4[0],?s4[1],?s4[2],?s4[3],?s4[4]?=?97,?98,?99,?0,?101 #?strncmp?在比較的時(shí)候,如果遇到?\0,那么字符串就結(jié)束了 print(strncmp(s3,?s4,?5))??#?0 #?memcmp?支持所有數(shù)據(jù)類型的比較,不單單針對(duì)字符串 #?所以它在比較的時(shí)候不會(huì)關(guān)注?\0,就是逐一比較每個(gè)字節(jié),直到達(dá)到指定的字節(jié)數(shù) #?因?yàn)?e?的?ASCII?碼小于?f,所以結(jié)果是?-1 print(memcmp(<void?*>?s3,?<void?*>?s4,?5))??#?-1
以上就是 memcmp 的用法,我們總結(jié)一下出現(xiàn)的函數(shù)。
小結(jié)
以上就是在 Cython 中處理 C 字符串的一些操作,說實(shí)話大部分都是和 C 相關(guān)的內(nèi)容,如果你熟悉 C 的話,那么這篇文章其實(shí)可以不用看。
因?yàn)?Cython 同理理解 C 和 Python,在加速的時(shí)候不妨把字符串換成 char * 試試吧。比如有一個(gè)操作 pgsql 的異步驅(qū)動(dòng) asyncpg 就是這么做的,因此速度非常快。
到此這篇關(guān)于Cython處理C字符串的示例詳解的文章就介紹到這了,更多相關(guān)Cython處理C字符串內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
python中的繼承機(jī)制super()函數(shù)詳解
這篇文章主要介紹了python中的繼承機(jī)制super()函數(shù)詳解,super 是用來解決多重繼承問題的,直接用類名調(diào)用父類方法在使用單繼承的時(shí)候沒問題,但是如果使用多繼承,會(huì)涉及到查找順序、重復(fù)調(diào)用等問題,需要的朋友可以參考下2023-08-08Python實(shí)戰(zhàn)之疫苗研發(fā)情況可視化
2020年底以來,歐美,印度,中國,俄羅斯等多國得制藥公司紛紛推出了針對(duì)新冠/肺炎的疫苗,這部分主要分析了2020年以來全球疫情形勢(shì),各類疫苗在全球的地理分布,疫苗在各國的接種進(jìn)度進(jìn)行可視化展示,需要的朋友可以參考下2021-05-05python argparse 模塊命令行參數(shù)用法及說明
這篇文章主要介紹了python argparse 模塊命令行參數(shù)用法及說明,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-11-11PyTorch中torch.utils.data.DataLoader實(shí)例詳解
torch.utils.data.DataLoader主要是對(duì)數(shù)據(jù)進(jìn)行batch的劃分,下面這篇文章主要給大家介紹了關(guān)于PyTorch中torch.utils.data.DataLoader的相關(guān)資料,文中通過實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2022-09-09Python中強(qiáng)大的命令行庫click入門教程
click是Python的一個(gè)命令行工具,極其好用。不信?一試便知。下面這篇文章主要給大家介紹了Python中強(qiáng)大的命令行庫click,需要的朋友可以參考學(xué)習(xí),下面來一起看看吧。2016-12-12Python利用imshow制作自定義漸變填充柱狀圖(colorbar)
這篇文章主要介紹了Python利用imshow制作自定義漸變填充柱狀圖(colorbar),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-12-12Python實(shí)現(xiàn)的北京積分落戶數(shù)據(jù)分析示例
這篇文章主要介紹了Python實(shí)現(xiàn)的北京積分落戶數(shù)據(jù)分析,結(jié)合實(shí)例形式分析了Python針對(duì)北京積分落戶數(shù)據(jù)的分析、運(yùn)算、展示等相關(guān)操作技巧,需要的朋友可以參考下2020-03-03