在go语言的官方包 strings中,官方提供了Title函数用于将字符串中的单词首字母转换为大写,这个函数很绕,对于要转换的字符串先是一个Map循环,然后接着又是一个Map循环,且函数调函数掉了好多层,而且最新版本中已经标记为过时,推荐使用一个更绕的golang.org/x/text/cases包中的函数进行转换。
下面的函数使用了高效的正则来切割字符串,同时支持自定义切割字符来对字符串中的所有单词的首字母进行转换, 废话不多说,直接上代码:
// 转换字符串中单词的首字母大小写
//
// s 待转换的字符串
// sep 单词分隔符 如果指定了分隔符且不为空同时字符串中包含指定分隔符,则返回的字符串中的单词将会带上这个分隔符,否则分隔符全部会被设置为空
// isUpper 是否转换为大写 true 是, false 否(转换为小写)
//
// 使用示例:
//
// ConvertWrodsFirstUpperLower("hello word","",true) // HelloWorld
// ConvertWrodsFirstUpperLower("Hello Word"," ",false) // hello world
//
// 返回转换后的字符串
func ConvertWrodsFirstUpperLower(s, sep string, isUpper bool) string {
// 定义切割字符串的正则
regexp := `(\s+|\n|\r|\t|\f|\v|_|-|\b)`
// 如果sep不为空,且字符串中包含用户提供的分隔符,则将分隔符放入到正则中
if sep != "" && strings.Contains(s, sep) {
regexp = fmt.Sprintf(`(%s|\s+|\n|\r|\t|\f|\v|_|-|\b)`, sep)
} else {
sep = "" // 其他情况将分隔符设置为空
}
re, err := GetRegexp(regexp)
if err != nil {
return s
}
ss := re.Split(s, -1) // 按照上面的正则切割字符串
var sb strings.Builder
sb.Grow(len(ss)) // 指定容量为切割后的切片个数
for i, v := range ss {
if v == "" {
continue
}
r0 := []rune(v)[0]
// 如果单词第一个rune是小写或者大写字母 大写字母 65-90 小写字母97-122
if r0 <= 122 && ((r0 >= 'A' && r0 <= 'Z') || (r0 >= 'a' && r0 <= 'z')) {
wr := false
if isUpper && 'a' <= r0 && r0 <= 'z' {
wr = true
r0 -= 'a' - 'A' // 转换为大写 小写字母比大写字母的ascii码大32 注意这里的转换大小写必须的前提
} else if !isUpper && 'A' <= r0 && r0 <= 'Z' {
wr = true
r0 += 'a' - 'A' // 转换为小写
}
if wr {
sb.WriteRune(r0) // 将转换后的单词第一个rune写入缓存
sb.WriteString(string([]rune(v)[1:])) // 写入剩余的rune写入到缓存
} else {
sb.WriteString(v)
}
} else { // 非小写或者大写字母,直接原样写入
sb.WriteString(v)
}
if sep != "" && i < len(ss)-1 {
sb.WriteString(sep) // 写入单词分隔符
}
}
if sb.Len() > 0 {
return sb.String()
}
return s // 根据单词分隔符切割后的字符为空,原样返回
}
// 将字符串中所有单词的第一个字母转换为大写
func Title(s string) string {
if len(s) == 0 {
return s
}
return ConvertWrodsFirstUpperLower(s, " ", true)
}
// 将字符串中所有单词的第一个字母转换为小写
func UnTitle(s string) string {
if len(s) == 0 {
return s
}
return ConvertWrodsFirstUpperLower(s, " ", false)
}
上面代码实现并增强了官方的strings.Title的功能,可以说是Title的升级版, 完整代码见github仓库:http://github.com/tekintian/go-str-utils
官方的string.Title函数参考:
func Title(s string) string {
// Use a closure here to remember state.
// Hackish but effective. Depends on Map scanning in order and calling
// the closure once per rune.
prev := ' '
return Map(
func(r rune) rune {
if isSeparator(prev) {
prev = r
return unicode.ToTitle(r)
}
prev = r
return r
},
s)
}
// ToTitle maps the rune to title case.
func ToTitle(r rune) rune {
if r <= MaxASCII {
if 'a' <= r && r <= 'z' { // title case is upper case for ASCII
r -= 'a' - 'A'
}
return r
}
return To(TitleCase, r)
}
// To maps the rune to the specified case: [UpperCase], [LowerCase], or [TitleCase].
func To(_case int, r rune) rune {
r, _ = to(_case, r, CaseRanges)
return r
}
func to(_case int, r rune, caseRange []CaseRange) (mappedRune rune, foundMapping bool) {
if _case < 0 || MaxCase <= _case {
return ReplacementChar, false // as reasonable an error as any
}
// binary search over ranges
lo := 0
hi := len(caseRange)
for lo < hi {
m := int(uint(lo+hi) >> 1)
cr := caseRange[m]
if rune(cr.Lo) <= r && r <= rune(cr.Hi) {
delta := cr.Delta[_case]
if delta > MaxRune {
// In an Upper-Lower sequence, which always starts with
// an UpperCase letter, the real deltas always look like:
// {0, 1, 0} UpperCase (Lower is next)
// {-1, 0, -1} LowerCase (Upper, Title are previous)
// The characters at even offsets from the beginning of the
// sequence are upper case; the ones at odd offsets are lower.
// The correct mapping can be done by clearing or setting the low
// bit in the sequence offset.
// The constants UpperCase and TitleCase are even while LowerCase
// is odd so we take the low bit from _case.
return rune(cr.Lo) + ((r-rune(cr.Lo))&^1 | rune(_case&1)), true
}
return r + delta, true
}
if r < rune(cr.Lo) {
hi = m
} else {
lo = m + 1
}
}
return r, false
}