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