欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

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

 更新時(shí)間:2023年01月06日 08:45:02   作者:古明地覺  
如果你在使用 Cython 加速 Python 時(shí)遇到了瓶頸,但還希望更進(jìn)一步,那么可以考慮將數(shù)據(jù)的類型替換成 C 的類型,所以本文為大家介紹了Cython處理C字符串的方法,希望對(duì)大家有所幫助

楔子

在介紹數(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)文章

  • PyTorch的nn.Module類的定義和使用介紹

    PyTorch的nn.Module類的定義和使用介紹

    在PyTorch中,nn.Module類是構(gòu)建神經(jīng)網(wǎng)絡(luò)模型的基礎(chǔ)類,所有自定義的層、模塊或整個(gè)神經(jīng)網(wǎng)絡(luò)架構(gòu)都需要繼承自這個(gè)類,本文介紹PyTorch的nn.Module類的定義和使用介紹,感興趣的朋友一起看看吧
    2024-01-01
  • pip指定python位置安裝軟件包的方法

    pip指定python位置安裝軟件包的方法

    今天小編就為大家分享一篇pip指定python位置安裝軟件包的方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧
    2019-07-07
  • python中的繼承機(jī)制super()函數(shù)詳解

    python中的繼承機(jī)制super()函數(shù)詳解

    這篇文章主要介紹了python中的繼承機(jī)制super()函數(shù)詳解,super 是用來解決多重繼承問題的,直接用類名調(diào)用父類方法在使用單繼承的時(shí)候沒問題,但是如果使用多繼承,會(huì)涉及到查找順序、重復(fù)調(diào)用等問題,需要的朋友可以參考下
    2023-08-08
  • Python實(shí)戰(zhàn)之疫苗研發(fā)情況可視化

    Python實(shí)戰(zhàn)之疫苗研發(fā)情況可視化

    2020年底以來,歐美,印度,中國,俄羅斯等多國得制藥公司紛紛推出了針對(duì)新冠/肺炎的疫苗,這部分主要分析了2020年以來全球疫情形勢(shì),各類疫苗在全球的地理分布,疫苗在各國的接種進(jìn)度進(jìn)行可視化展示,需要的朋友可以參考下
    2021-05-05
  • python argparse 模塊命令行參數(shù)用法及說明

    python argparse 模塊命令行參數(shù)用法及說明

    這篇文章主要介紹了python argparse 模塊命令行參數(shù)用法及說明,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-11-11
  • PyTorch中torch.utils.data.DataLoader實(shí)例詳解

    PyTorch中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-09
  • Python中強(qiáng)大的命令行庫click入門教程

    Python中強(qiáng)大的命令行庫click入門教程

    click是Python的一個(gè)命令行工具,極其好用。不信?一試便知。下面這篇文章主要給大家介紹了Python中強(qiáng)大的命令行庫click,需要的朋友可以參考學(xué)習(xí),下面來一起看看吧。
    2016-12-12
  • Python利用imshow制作自定義漸變填充柱狀圖(colorbar)

    Python利用imshow制作自定義漸變填充柱狀圖(colorbar)

    這篇文章主要介紹了Python利用imshow制作自定義漸變填充柱狀圖(colorbar),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-12-12
  • Python實(shí)現(xiàn)的北京積分落戶數(shù)據(jù)分析示例

    Python實(shí)現(xiàn)的北京積分落戶數(shù)據(jù)分析示例

    這篇文章主要介紹了Python實(shí)現(xiàn)的北京積分落戶數(shù)據(jù)分析,結(jié)合實(shí)例形式分析了Python針對(duì)北京積分落戶數(shù)據(jù)的分析、運(yùn)算、展示等相關(guān)操作技巧,需要的朋友可以參考下
    2020-03-03
  • 使用Python和OpenCV進(jìn)行圖像處理和分析

    使用Python和OpenCV進(jìn)行圖像處理和分析

    圖像處理和分析是計(jì)算機(jī)視覺領(lǐng)域的重要組成部分,本文將介紹如何使用Python編程語言和OpenCV庫進(jìn)行圖像處理和分析,我們將涵蓋圖像讀取、顯示、濾波、邊緣檢測(cè)和圖像分割等常見的圖像處理操作,并提供相應(yīng)的代碼示例
    2023-07-07

最新評(píng)論