中文版本由空白的貝塔君
整理發(fā)布
第五章 字符串處理
SystemVerilog語(yǔ)言本身提供了許多字符串操作。然而,經(jīng)驗(yàn)表明,內(nèi)置方法不足以滿足工作中的字符串處理任務(wù),svlib
提供了進(jìn)一步的操作集來幫助滿足這些需求。
在大多數(shù)情況下,字符串操作有兩種不同的形式,用戶可以自由選擇更適合自己需要的形式。
- 第一種形式是關(guān)于字符串變量的簡(jiǎn)單函數(shù),通常(但不總是)返回字符串結(jié)果。這些函數(shù)在svlib包中定義,名稱都以str_開頭。第二種形式是
Str
類對(duì)象的方法(注意大寫的S
)。Str
類是SystemVerilog字符串的wrapper
,通過引用傳遞字符串,并使一些操作更方便。
對(duì)比使用簡(jiǎn)單函數(shù),使用Str
對(duì)象必須在所有操作之前構(gòu)造對(duì)象。不過通過Str
對(duì)象的許多操作的效率和便利性通常收益是利大于弊的。程序員可以自由選擇對(duì)他們來說最方便的方法。如果只需要對(duì)一個(gè)字符串執(zhí)行一個(gè)操作,那么pkg級(jí)函數(shù)可能是最方便的。如果要對(duì)同一個(gè)字符串執(zhí)行許多連續(xù)操作,最好創(chuàng)建一個(gè)Str
對(duì)象來進(jìn)行處理。
5.1 Str
類
5.1.1 處理Str對(duì)象和成員的方法
static function Str Str::create(string s = "");
function void set (string s);
function string get ();
function Str copy ();
function int len ();
前文提到過,用戶不能直接通過new
函數(shù)創(chuàng)建對(duì)象,必須使用Str::create
方法。當(dāng)然,創(chuàng)建對(duì)象是可以無(wú)視參s
。
對(duì)象創(chuàng)建以后,隨時(shí)可以使用set
方法更新字符串成員。而get
方法則返回對(duì)象保存的字符串。len
方法則返回字符串長(zhǎng)度。copy
函數(shù)則返回一個(gè)新的對(duì)象,并且它的內(nèi)容與調(diào)用的對(duì)象一致。
5.1.2 枚舉類型
typedef enum {NONE, LEFT, RIGHT, BOTH} side_enum;
typedef enum {START, END} origin_enum;
這兩個(gè)枚舉用于指定某些方法的各種可選行為。ide_enum
用于指定字符串的哪一側(cè)將參與各種操作,特別是trim
和pad
。origin_enum
用于指定在range
和replace
操作時(shí)從字符串的哪端計(jì)數(shù)。START
指定字符串最左端,END
指定最右端。這些選項(xiàng)的細(xì)節(jié)將在后面的小節(jié)中展開。
5.1.3 在Str對(duì)象的字符串后面拼接一個(gè)字符串
function void append(string s);
這個(gè)函數(shù)通過使用簡(jiǎn)單的字符串連接,將指定的字符串拼接到一個(gè)Str
對(duì)象的字符串成員后面,從而修改該對(duì)象的現(xiàn)有字符串內(nèi)成員。
5.1.4 查找子字符串
function int first (string substr, int ignore=0);
function int last (string substr, int ignore=0);
first()
在對(duì)象的字符串內(nèi)容中搜索字符串子str的第一次出現(xiàn)的位置。它返回子字符串的最左邊字符在原始字符串中的位置。如果搜索失敗(在原始字符串中沒有出現(xiàn)子字符串),則函數(shù)返回-1。這個(gè)方法的搜索是精確的文字匹配,不使用通配符或正則表達(dá)式匹配。
參數(shù)ignore
指定搜索從哪里開始。默認(rèn)值(ignore=0
)將掃描整個(gè)字符串,并返回第一個(gè)匹配項(xiàng)。如果ignore
大于零,搜索將從指定的字符位置開始。不管ignore
的值是多少,成功匹配后的返回值都是匹配在原始字符串中的絕對(duì)起始位置。
last
的行為方式類似,但它從字符串的最右端開始掃描,因此,如果查找的子字符串在原始字符串中出現(xiàn)多次,它將返回最后一個(gè)可能的匹配結(jié)果。最后,ignore
參數(shù)指定在字符串最右端的要忽略的字符數(shù)——它的作用等效于這部分字符不存在。
「注意」:Str
類的first
和last
方法提供了一個(gè)簡(jiǎn)單快速的子字符串搜索方法。在第六章中,使用正則表達(dá)式匹配可以更靈活地進(jìn)行搜索匹配,但這種靈活性的代價(jià)是參數(shù)配置增加和速度下降。在大多數(shù)情況下,是利大于弊的,正則表達(dá)式是首選。
5.1.5 切割和連接操作
function string sjoin (qs elements);
function qs split (string splitset="", bit keepSplitters=0);
「注意」:svlib
內(nèi)部定義了類型名qs
,表示“queue of strings”,但用戶代碼不能調(diào)用它。如果你需要一個(gè)類型名來表示字符串隊(duì)列,你應(yīng)該自己定義類型名,能完全兼容(類型等效)qs。另外,也可以簡(jiǎn)單地聲明字符串隊(duì)列的變量,并使用它們作為參數(shù)和結(jié)果變量。
sjoin
方法(不使用join
作為名稱,是因?yàn)楹蚐ystemVerilog關(guān)鍵字沖突)使用Str
對(duì)象的內(nèi)容作為“joiner
”,將字符串隊(duì)列中的元素組裝成單個(gè)字符串。例如,它可以方便地創(chuàng)建逗號(hào)分隔的列表。
split
方法獲取Str
對(duì)象的現(xiàn)有字符串(保持不變),并使用單個(gè)字符分割標(biāo)記("splitter
")將其分割成字符串隊(duì)列。參數(shù)splitset
是一個(gè)字符串,但它被視為一組單獨(dú)的字符;對(duì)象的字符串變量被分割,分割的位置是出現(xiàn)splitset
中字符的位置。如果splitset
是一個(gè)空字符串,那么對(duì)象的字符串會(huì)被分割后的字符串隊(duì)列的每個(gè)元素都將是單個(gè)字符。
如果keepsplitter
為true
(1)且splitset
不是空字符串,則拆分字符將作為結(jié)果隊(duì)列的單個(gè)成員出現(xiàn)在其對(duì)應(yīng)的位置。如果keepsplitter
為false
(默認(rèn)值),拆分字符將不會(huì)出現(xiàn)在結(jié)果中。
「注意」:從svlib的0.5版開始,Regex類中有一個(gè)新的split方法(見第6章)。它提供了比這里的Str::split方法靈活得多的功能,在大多數(shù)情況下是首選方法。
5.1.6 提取子字符串和替換操作
function string range (int p, int n, origin_enum origin=START);
function void replace(string rs, int p, int n, origin_enum origin=START);
range
提供了比SystemVerilog原生字符串的substr
操作的更通用和統(tǒng)一的方法。當(dāng)其中一個(gè)邊界超出字符串時(shí),它的表現(xiàn)會(huì)更加正常。在第5.3節(jié)中,詳細(xì)地介紹了如何使用p
、n
和origin
參數(shù)指定字符串的一個(gè)切片的詳細(xì)信息。range
只返回指定的子字符串,返回類型為SystemVerilog的字符串類型。
replace以
完全相同的方式指定子字符串,然后用rs
替換該子字符串,并修改Str對(duì)象的內(nèi)容。replace
非常靈活,有時(shí)可以單獨(dú)使用。例如:
- 通過傳入空的
rs
參數(shù),刪除指定子字符串通過下面的方式可以實(shí)現(xiàn)在尾部添加一個(gè)字符串
s.replace(append_string, 0, 0, Str::END);
- 通過下面的方式可以實(shí)現(xiàn)在開頭添加一個(gè)字符串
s.replace(prefix_string, 0, 0, Str::START);
傳入的rs
字符串的長(zhǎng)度沒有限制,不需要和被替換的字符串長(zhǎng)度一致。
5.1.7 在字符串的開頭和結(jié)尾刪除或添加空白字符
function void trim (side_enum side=BOTH);
function void pad (int width, side_enum side=BOTH);
trim
刪除字符串的開頭或者結(jié)尾的所有空白字符,它會(huì)修改Str對(duì)象的現(xiàn)有內(nèi)容。參數(shù)side指定要修剪字符串的哪一端。如果side是Str::LEFT
,則從字符串的左端刪除空白;RIGHT
刪除尾隨空格;BOTH
刪除兩端的空格。最后,如果指定了NONE
,就不會(huì)產(chǎn)生任何效果。
空白字符包括任何空格、制表符、換行符、回車符和不間斷空格(ASCII碼160)。
如果字符串完全由空格組成,并且side
參數(shù)不是NONE
,則結(jié)果將是一個(gè)空字符串。
pad
會(huì)在開頭或者結(jié)尾添加空白字符(使用空格字符),使結(jié)果字符串的長(zhǎng)度正好是width
。如果字符串已經(jīng)大于width
,則不進(jìn)行任何操作。如果side
為NONE
,則字符串不變。否則,將根據(jù)需要在指定的字符串末尾添加空格。如果side
為BOTH
,則在兩邊添加相同數(shù)量的空格(必要時(shí)在右側(cè)添加一個(gè)額外的空格)。此方法對(duì)于以表格格式打印的文本對(duì)齊非常有用。
5.1.8 刪除字符串中不想要的字符
function void strip (string chars = " tn131415240177");
strip
刪除Str對(duì)象中以字符形式出現(xiàn)的所有字符。默認(rèn)情況下是刪除所有空白字符,但您可以指定一個(gè)包含您想要?jiǎng)h除的任何字符的字符串。
5.1.9 將字符串轉(zhuǎn)換為systemverilog的標(biāo)準(zhǔn)字符串
function void quote ();
此方法會(huì)更新對(duì)象,對(duì)字符串的進(jìn)行轉(zhuǎn)義處理。使用轉(zhuǎn)義字符,如"和n,將特殊字符(反斜杠,雙引號(hào),控制字符等)替換為等價(jià)字符。在需要的地方使用更通用的xNN表示法。最后,整個(gè)字符串由一對(duì)字符串引號(hào)(")包圍。結(jié)果總是一個(gè)完整的、合法的SystemVerilog字符串。
這個(gè)函數(shù)是用來編寫SystemVerilog的,用于生成SystemVerilog源代碼。在以逗號(hào)分隔值(CSV)等格式寫入文件時(shí),也很有用。
5.2 包級(jí)字符串函數(shù)
function string str_sjoin(qs elements, string joiner);
function string str_trim(string s, Str::side_enum side=Str::BOTH);
function string str_pad(
string s, int width, Str::side_enum side=Str::BOTH);
function string str_quote(string s);
function string str_replace(
string s, string rs,
int p, int n, Str::origin_enum origin=Str::START);
function string strip (
string s, string chars = " tn131415240177");
如果只想進(jìn)行簡(jiǎn)單的操作,創(chuàng)建一個(gè)對(duì)象其實(shí)是很不方便的。因此,svlib
提供了一些字符串操作作為包級(jí)函數(shù),作為類方法的替代。這些函數(shù)執(zhí)行的操作與Str類的相應(yīng)方法完全相同。在方法內(nèi)部,都用參數(shù)string s填充Str對(duì)象,然后再執(zhí)行操作,并最終返回對(duì)應(yīng)的結(jié)果。這些方法性能開銷很小,因?yàn)閹?kù)維護(hù)了一個(gè)Str對(duì)象池,專門用于此類操作。
5.3 指定字符串范圍
svlib
使用單一且一致的方式指定子字符串范圍(字符串的切片)。它顯式地在Str
的方法range和replace(以及相應(yīng)的包級(jí)函數(shù)str_range和str_replace)中使用,也在其他地方隱式地使用。它的設(shè)計(jì)是為了降低SystemVerilog的自帶的字符串類型的substr操作的復(fù)雜性。
5.3.1 起點(diǎn)的定義
不根據(jù)字符數(shù)指定字符串范圍,因?yàn)檫@會(huì)導(dǎo)致在處理零長(zhǎng)度字符串切片時(shí)出現(xiàn)奇怪的不連續(xù)。字符串切片的邊界是根據(jù)字符之間的位置指定的。為了說明這一點(diǎn),考慮5個(gè)字符的字符串“Hello”:
使用這種方法,我們有一種一致的方式來指定子字符串的邊界位置,使用參數(shù)p
,將origin
參數(shù)指定為Str::START
(默認(rèn)值)。通過這種方式可以直觀地理解,負(fù)的,或者大于字符串的長(zhǎng)度,所代表的位置。
也可以根據(jù)字符串的Str::END
(最右邊的位置)指定邊界。在下例中,修改了對(duì)不同p
參數(shù)值的定義,p
從右(結(jié)束)字符邊界向左計(jì)算:
我們直接定義了p
的超出范圍值時(shí)的意義。因此,如果將origin
指定為Str::END
,我們就可以指定字符串的末尾部分,而不必關(guān)心字符串的確切長(zhǎng)度。
5.3.2 長(zhǎng)度參數(shù)n的定義
在為字符串范圍建立了起點(diǎn)之后,現(xiàn)在需要考慮希望獲取的切片長(zhǎng)度。這個(gè)參數(shù)n的解釋不受原始值的任何影響。它指定從p指定的邊界移動(dòng)多遠(yuǎn),以找到我們的子字符串的第二個(gè)邊界。n為正表示向右移動(dòng)。負(fù)值表示向左移動(dòng)。
5.3.3 最終范圍的定義
origin
、n
和p
三者指定了字符范圍。例如,如果我們要調(diào)用函數(shù)str_range(.s("Hello"), .p(3), .n(4), .origin(Str::START))
,它將指定下面圖表中陰影代表的范圍:
p=3
, origin=START
指定了起始位置3;n=4
指定了從p指定的位置右側(cè)開始的四個(gè)字符位置。然而,其中兩個(gè)字符位置不在原來的5個(gè)字符的字符串中,因此范圍操作的結(jié)果是兩個(gè)字符的字符串“lo”。
5.3.4 一些例子
下面是各種情況的一些例子。