[Swift基础语法入门] Swift 字符串一口闷

觉得不错就关注我吧,不定期更新文章,QQ:1345614869

字符串初始化

1 最常用的几种方法

var emptyString = ""      // 初始化一个空字符串,这里注意空字符串 ≠ nil
var stillEmpty = String() // 使用init()来初始化一个字符串
let str = "Hello Swift!"  // 使用字符串字面量来初始化

2 使用特定字符串init()方法

let boolStr = String(true)      // 传入布尔类型 true or false -> "true" or "false"
let catCharacters:[Character] = ["C","a","t","!","🐱"]//  先声明一个字符数组
let catStr = String(catCharacters)// 通过传入字符数组初始化一个字符串
let floatStr = String(3.14) // 传入一个浮点类型数初始化一个字符串
let intStr = String(1234)   // 传入一个整型数初始化一个字符串
let unicodeStr = "\u{4e00}" // 传入unicode码转字符串,这是一个中文“一”
let rcStr = String(count: 2, repeatedValue: Character("x")) // 重复多个Character字符生成一个字符串
let ruStr = String(count: 2, repeatedValue: UnicodeScalar(0x4e00)) // 这里注意UnicodeScalar 可以使用8位 16位 32位

3 使用插值法构造字符串

这是我比较喜欢的语法之一,不像C语言需要使用诸如printf("number is %d",number)这种占位符,清爽很多。

let name = "pmst"
let age = 26
let info = "name is \(name),age is \(age)"

字符串实用方法

1.字符串的前缀和后缀判断

var psStr = "111 pmst 222"
psStr.hasPrefix("111") // 判断前缀 true
psStr.hasSuffix("222") // 判断后缀 true

2.字符串是否为空

var emptyStr = ""
emptyStr.isEmpty  // true 这是一个只读属性
emptyStr == ""    // true

3.获得字符串的大小写

var ulStr = "aBcDeF"
let upper = ulStr.uppercaseString // "ABCDEF" 这货是一个只读属性
let lower = ulStr.lowercaseString // "abcdef" 同上

4.字符串的存储方式

常常我们会问一个字符串到底有多少个字符,不如看看这个特殊的例子:

// 其中café中的e是带第二声调的。其中\u{E9} 就是é
let unicodeString = "caf\u{E9}"     // "café"

// swift中为我们提供了 
// 1. characters(String.ChacracterView类型) 
// 2. unicodeScalars 21位编码,官方博客有篇文章说了
// 3. utf16
// 4. utf8  oc貌似是这货
unicodeString.characters.count      // 4
unicodeString.unicodeScalars.count  // 4
unicodeString.utf8.count            // 5
unicodeString.utf16.count           // 4

那不禁要思考下é 是否可以看成 e + 声调呢? 答案显然是ok!

let anotherCafeString = "caf\u{65}\u{301}" // "café"

anotherCafeString.characters.count         // 4
anotherCafeString.unicodeScalars.count     // 5
anotherCafeString.utf8.count               // 6
anotherCafeString.utf16.count              // 5

可以看到两者还是不同的!<##原因待补充##>

5.遍历

Swift 中我们并不能将字符串看做是一个Character类型的数组,因此str[1] 这种传入整数值下标取值的方式是不被允许的,但是!如果你用Index类型下标取值是可以的!请见下面内容。

除此之外我们需要讨论下关于一些特殊的字符串,譬如"café"中的"é"是看成一个字符呢?还是两个字符(韵母+声调)?或许你已经发现 Swift 贴心地帮你处理了一切,是作为一个整体存在,即类型为String.ChacracterView

扯了那么多开始本小节的话题,如何遍历字符串:

var cafeSentence = "café"

for c in cafeSentence.characters{
    /*
        依次输出:
        character in cafe Sentence is c
        character in cafe Sentence is a
        character in cafe Sentence is f
        character in cafe Sentence is é
    */
    print("character in cafe Sentence is \(c)")
}

使用for-in方式遍历当然是一个不错的选择,但是如果想要从特定位置开始获取某个字符串片段又该如何实现呢?这时候需要引出两个关键属性了!

  • startIndex:非空字符串的首个元素位置。
  • endIndex:非空字符串的结束元素位置的后一个位置!这里一定要记住!!

不妨看看 Swift 中是怎么定义这两货的:

extension String {
    public typealias Index = String.CharacterView.Index
    /// The position of the first `Character` in `self.characters` if
    /// `self` is non-empty; identical to `endIndex` otherwise.
    public var startIndex: Index { get }
    /// The "past the end" position in `self.characters`.
    ///
    /// `endIndex` is not a valid argument to `subscript`, and is always
    /// reachable from `startIndex` by zero or more applications of
    /// `successor()`.
    public var endIndex: Index { get }
    
    // 注意这里!其实是有下标取值的 但是传入类型要是Index类型
    public subscript (i: Index) -> Character { get }
}

先测试下startIndexendIndex的值,方便理解:

var cafeStr = "café"
cafeStr.startIndex  // 0
cafeStr.endIndex    // 4 而不是3哦!

别被 0 、4误导,这两个参数的类型是String.CharacterView.Index,作为一个Struct,它们包含了2个方法:

public struct Index : BidirectionalIndexType, Comparable, _Reflectable {
    /// Returns the next consecutive value after `self`.
    ///
    /// - Requires: The next value is representable.
    public func successor() -> String.CharacterView.Index
    /// Returns the previous consecutive value before `self`.
    ///
    /// - Requires: The previous value is representable.
    public func predecessor() -> String.CharacterView.Index
}

其中:

  • successor() 方法是取到下一个字符的索引值Index
  • predecessor() 方法是取到前一个字符的索引值Index

不妨看看String.UFT16View.Index 的 extension 内容

extension String.UTF16View.Index {
    @warn_unused_result
    public func distanceTo(end: String.UTF16View.Index) -> Distance
    @warn_unused_result
    public func advancedBy(n: Distance) -> String.UTF16View.Index
    @warn_unused_result
    public func advancedBy(n: Distance, limit: String.UTF16View.Index) -> String.UTF16View.Index
}

这里需要了解advanceBy(n:Distance)方法,其中Distance是这样的:public typealias Distance = Int,因此也木有神秘感。

讲了那么多,来稍微实践下:

cafe[cafe.startIndex]                         // "c"
cafe[cafe.startIndex.successor()]             // "a"
cafe[cafe.startIndex.successor().successor()] // "f"

// 前面说endIndex是最后一个字符的还要后面一个位置
// 因此cafe[endIndex] 会报错哦 就是数组越界嘛!
cafe[cafe.endIndex.predecessor()]             // "é"
cafe[cafe.startIndex.advancedBy(2)]           // "f"

如果你觉得用successor()以及predecessor()方法向前或向后取索引值麻烦的话,不妨用用swift为你提供的indeces属性,类型为:Range<Self.Index>,不是 NSRange !

// 通过这种方式我们先获取的是每个字符的索引值,而非字符
for index in cafe.characters.indices {
  print(cafe[index])
}

Swift String Cheat Sheet 中说我们不能将某个字符串的索引用于另外一个字符串的索引值来取字符,但是我测试时候没问题的,这里大家可以讨论下。

let word1 = "ABCDEF"
let word2 = "012345"
let indexC = word1.startIndex.advancedBy(2)
let char_2 = word2[indexC]                                               // "2"
let distance = word1.startIndex.distanceTo(indexC) // 2 指代第一个索引距离第二个索引的距离
let digit = word2[word2.startIndex.advancedBy(distance)] // "2"

使用 Range 来截取字符串片段

实际开发中经常遇到这样的需求,截取某个字符段片段,然后设置特定的属性,譬如颜色,字体大小等等。

此时就要用到范围Range这一术语了,比如这么描述一个范围,获取字符串第二个字符开始到字符串倒数第二个字符为止的中间所有内容。

不妨试试:

let swiftGG = "www.swift.gg"
let rangeOfSwift = Range(start: swiftGG.startIndex.advancedBy(4), end: swiftGG.endIndex.advancedBy(-3))   // 4..<9 = [4,9) 左闭右开
let swift = swiftGG[rangeOfSwift] // "swift"

其实吧,我们也可以使用“...”和“..<”构建一个Range,注意前者是闭区间,后者是左闭右开,换句话说startIndex...endIndex 等价于 startIndex..<endIndex.successor()

为了更清楚地方便大家理解,我打算再啰嗦几句,Range始终构造一个[s,e)的区间,正如你所看到的是左边闭合,右边开区间。即使使用"..."也是如此,不如测试下:

1...4  //1..<5 很神奇吧 不过呢这是Range<Int> 而非Range<Index>类型
swiftGG.startIndex ... swiftGG.endIndex.predecessor() // 0..<12

因此倘若你要截取一个字符串中的某个片段,你要把头指向要截取字符串的首字符之上,把尾指向要截取字符串的尾字符的后一个位置

获取字符串子串的方法

前面我们是通过构建一个Range范围,下标取子串的方式实现,这里我们介绍另外几个实用的方法:

  • substringFromIndex(index:Index),获取[index,endIndex)之间的子串
  • substringToIndex(index:Index),获取[startIndex,index)之间的子串
  • substringWithRange(range:Range),获取range范围的子串
let rangeStr = "abcdefg"
rangeStr.substringFromIndex(rangeStr.endIndex.predecessor())
rangeStr.substringToIndex(rangeStr.endIndex.predecessor())

处理字符串的前缀和后缀字符

有时候免不了要处理下字符串的前缀和后缀,比如去头去尾拉等等,然后生成一个新的字符串,《Swift String Cheat Sheet》一文的例子已经非常地描述了一切,因此我直接引用过来

let digits = "0123456789"
let tail = String(digits.characters.dropFirst())  // "123456789"
let less = String(digits.characters.dropFirst(3)) // "3456789"
let head = String(digits.characters.dropLast(3))  // "0123456"

let prefix = String(digits.characters.prefix(2))  // "01"
let suffix = String(digits.characters.suffix(2))  // "89"

let index4 = digits.startIndex.advancedBy(4)
let thru4 = String(digits.characters.prefixThrough(index4)) // "01234"
let upTo4 = String(digits.characters.prefixUpTo(index4))    // "0123"
let from4 = String(digits.characters.suffixFrom(index4))    // "456789"

主要还是对characters的操作,简要介绍下出现的几个方法:

  • dropFirst() 去掉首字符,返回剩余字符串
  • dropFirst(n) 去掉首部开始n个字符,返回剩余字符串
  • dropLast(n) 同理,但是从尾部去除n个
  • prefix(n) 获得字符串前n个字符
  • suffix(n) 获得字符串后n个字符
  • prefixThough(Index) 获取[startIndex,Index]的字符串子串
  • prefixUpTo(Index) 获取[startIndex,Index)的字符串子串
  • suffixFrom(Index) 获取[Index,endIndex)的字符串子串

总结来说,不是很难,用多了自然熟。

插入

插入操作无非就是在某个位置(Index)插入一个字符或者一个片段喽

var insertStr = "abcdefg" //"abcdefg"
insertStr.insert(Character("x"), atIndex: insertStr.startIndex) //"xabcdefg"
insertStr.insertContentsOf("xyz".characters, at: insertStr.startIndex)

注意是在给定索引值的前面开始插入!

删除

删除操作无非就是删除特定位置(Index)字符和特定范围(Range)字符串。

var deleteStr = "abcdefg"

// 删除特定位置
deleteStr.removeAtIndex(deleteStr.startIndex) // 返回删除的"a"
deleteStr       // "bcdefg"  
deleteStr.removeRange(deleteStr.startIndex..<deleteStr.endIndex.predecessor().predecessor()) // "fg"
deleteStr // "bcde"

是时候要强调下,删除和插入都是会改变原数组的!替换也是一样的!

替换

替换就是将某个范围的内容替换成传入的内容

var replaceStr = "abcdefg"
replaceStr.replaceRange(replaceStr.startIndex..<replaceStr.endIndex.advancedBy(-2), with: "!!!")  
replaceStr  //!!!fg

这里注意替换范围内的元素个数和即将替换的元素个数是不一定要匹配的!

附加

这个用的比较多,通常来说“+”操作即可,不过使用appendappendContentsOf,这里不得不提的就是我们在使用代码方式设置视图约束时,就有这个概念了,不妨去翻翻?

// 引自Swift String Cheat Sheet例子
var message = "Welcome"
message += " pmst" // "Welcome Tim"
message.appendContentsOf("!!!") // "Welcome pmst!!!

找出字符串中的 emoji 字符

首先定义emoji表情的范围,哪些需要,哪些忽略,前面说到字符的编码方式有三种,这里主要谈及scalar,21位编码方式,譬如😃对应了0x1F601,最后通过union我们获得到了一个集合,集合内元素是不重复的! 通过遍历String字符串中的每一个字符(characters为字符串所有字符)并使用filter过滤函数判断是否包含在早前获得emoji集合中,将符合的元素留下返回。

// 获取字符编码对应的数值
extension Character{
    // 取得对应的值
    private var unicodeScalarCodePoint:Int{
        let characterString = String(self)
        let scalars = characterString.unicodeScalars    // 貌似是21位的编码字符集?
        return Int(scalars[scalars.startIndex].value) // 先通过startIndex获取 然后获取到Int32值
    }
}

extension String{
    var emojis : [Character]{
        let emojiRanges = [0x1F601...0x1F64F, 0x2702...0x27B0]
        // 将上述的范围变成一个集合
        let emojiSet = emojiRanges.reduce(Set<Int>(), combine: {
            (result, elem) -> Set<Int> in
            // union方法是并集的意思 即集合(1 3 5 7)和集合(0 2 4 6) 操作后为(0,1,2,3,4,5,6,7)
            return result.union(elem)
        })
        // 使用filter过滤
        return self.characters.filter{
            emojiSet.contains($0.unicodeScalarCodePoint)
        }
    }
}

String 和 NSString 的桥接使用

TODO: 整理中

参考文献

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,542评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,596评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,021评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,682评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,792评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,985评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,107评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,845评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,299评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,612评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,747评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,441评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,072评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,828评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,069评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,545评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,658评论 2 350

推荐阅读更多精彩内容