[Swift]属性和下标

1. 属性的基本概念:

    1) Swift将结构体和类中可以通过外部访问的非方法类成员都叫做属性;

    2) 注意和OC的属性作区分,OC中的属性并不是数据成员,而是专门为访问封装后的数据成员所设计的(通过get和set设置private封装过的数据成员),而Swift的属性既包括数据成员也包括这些访问封装后的数据成员的方法(get和set等);

    3) Swift中的属性主要有:存储属性(即OC和C++中的数据成员)、计算属性(用于访问封装后的数据成员的方法(精确地讲应该是一段脚本)、属性观察者(主要是指didSet和willSet脚本,在存储属性被修改前后被调用,用以观察属性变化的过程,非常方便调试)、静态属性(和C++概念一样,即属于类而不属于对象)、索引属性(即为类或结构体定义下标访问,即C++中的operator[]的重载);


2. 存储属性:

    1) 即前面例子中的数据成员;

    2) 这里主要要强调的是常量属性和变量属性之间的关系以及常量结构体、常量类对象(实际上是常引用,Swift只能通过引用访问对象)和常/变量属性之间的关系:

class A
{
    let a: Int = 10
    var b: Int = 20
}

struct B
{
    let a: Int = 10
    var b: Int = 20
}

// 对于结构体来说

var b = B()
// b.a = 7 // 常量成员不可修改
b.b = 8 // 变量成员可以随意修改

let bb = B() // 对于结构体的常量任何成员都不可修改,因为结构体是值类型的,bb就代表结构体实体
// bb.a = 8
// bb.b = 7

// 再来看看类对象

var a = A() // 注意!此时a只是个引用
// a.a = 7 // 常量成员无论如何都是不可修改的
a.b = 77 // 变量成员当然可以通过引用修改,毕竟引用就相当于C语言中的指针

let aa = A() // 注意!aa在这里是常引用,就等价于C++中的class A * const aa = &A()
// 懂C++的朋友都知道这个是场指针而不是指向常量的指针
// 意思就是指针的指向不能改变,但是可以通过该指针来修改成员,因此
aa.b = 43 // 这个是对的!
// aa = A() // 这就不对了,aa的指向不能改变,这里让它指向了另一个出炉的新对象实体,因此肯定是会报错的了!
// aa.a = 7 // 还是那句话,常量成员是无论如何都不能修改的
    3) 惰性存储属性——延迟加载:将存储属性声明为lazy就变成了惰性存储属性,它的功能就是延迟加载,即在实例化的时候并不会在内存中加载该存储属性,只有在第一次访问它的时候才会在内存中加载该属性,这在处理非常消耗资源的初始化工作时非常有用,比如一个类中有一个存储属性是另一个类的对象,而该对象非常庞大(该对象的实例化有可能是读取一个非常庞大的资源,但是该资源又不经常使用,也有可能会在程序运行的后期才大量使用),因此一开始就加载该对象将会造成非常严重的资源浪费,而声明为lazy将会非常有帮助!

!!注意!lazy只能用于var不能用于let,因为let要求定义时必须立即初始化的,如果lazy用于let会直接编译报错!

func testProc() -> Int
{
    println("hahaha!")
    return 15
}

class Test
{
    // lazy let a: Int = 2 // Bad! lazy不能用于let
    lazy var a: Int = testProc()
    var ss: [String] = []
}

var t = Test()

t.ss += ["111"]
t.ss += ["222"]
t.ss.append("333")
println(t.ss) // [111, 222, 333]

println(t.a)
// hahaha! 先加载
// 15 生成a的内存后才可以打印a的值


3. 计算属性:

    1) 计算属性本身不能存储数据,但不过可以通过它间接地访问一些存储属性,而计算属性顾名思义就是通过其它存储属性计算而得的数据,比如属性c的值是由属性a和属性b连接而得,但是我们又没有必要重新定义一个c成员而增加不必要的内存空间,但是又不想非常麻烦地增加一条方法专门用于返回a和b的连接(并且调用的是否非常麻烦,必须要通过调用成员方法的形式),有没有一种解决方案,让我们仿佛定义了一个成员c,并且可以通过非常简洁的“类名.c”的方式访问该成员,但实际上该成员的内存空间并不存在,通过这种方式访问的时候背后实际上是执行了return a + b这条语句,这不就达到上述要求了嘛(即不浪费空间同时访问的方式也非常简洁),同时还可以通过对c的赋值而达到间接设置a和b的目的!答案时肯定的,Swift提供了计算属性来实现上述目的;

    2) 计算属性的组成:有一个取值访问器getter和一个设置访问器setter组成,只要定义setter,就可以通过上述.c = XXX的方式将XXX分解并设置a和b,而通过定义getter就可以通过.c的方式间接访问a + b连接值:

class Add
{
    var a: Int = 1
    var b: Int = 2
    var sum: Int {
        get { // getter取值访问器器
            return a + b
        }
        set { // setter设置访问器
            a = newValue / 2 // newValue是默认传入设置数据的名字,当然也可以自己定义改名字
            b = a
        }
        // set(myValue) {} // 自己定义myValue的名字,但是要以传参的方式写!newValue默认名可以加参数
    }
}

var t = Add()
println(t.sum) // 3,相当于调用了get方法,实际并没有存储sum内存,而是通过a + b计算而得!
t.sum = 6 // 相当于调用了set方法,将值平分给a和b
println(t.a) // 3
println(t.b) // 3
上述是可读写的计算属性,即可以读getter,也可以写setter,再来看一个经典的例子,即类中有两个成员,一个是姓一个是名,定义一个计算属性“全名”,通过getter获得“姓.名”形式的字符串,通过setter分别设置姓和名两个属性的值:

import Cocoa

class Name
{
    var firstname = ""
    var lastname = ""
    var fullname: String {
        get {
            return firstname + lastname
        }
        set(thefullname) { // 我们这里使用的String来自Cocoa框架
            var names = thefullname.componentsSeparatedByString(".")
            firstname = names[0]
            lastname = names[1]
        }
    }
}

var name = Name()
name.fullname = "Jhon.Lecter"
println("First name = \(name.firstname), last name = \(name.lastname)")
println(name.fullname)

    3) 只读计算属性:上述的计算属性是可读可写的,其实就只写get而不写set就可以了(但是早一点的版本不允许这样做),而更简洁的写法就是不写get也不写set直接在花括号中写语句,最后返回(return)即可:

class Cube
{
    var lentgh = 5
    var width = 10
    var height = 34
    var volume: Int { // 只读属性直接写代码
        return lentgh * width * height
    }
}

var cube = Cube()
println(cube.volume) // 1700
// cube.volume = 53 // Bad! 只读计算属性不能写
    4) 结构体中的计算属性和class一样,这里再示范一下枚举的计算属性,其实都是一样的:

enum WeekDays: String
{
    case Monday = "Monday"
    case Tuesday = "Tuesday"
    case Wednesday = "Wednesday"
    case Thursday = "Thursday"
    case Fridaya = "Friday"
    
    var output: String { // self就是指实例本身,就跟C++的this指针的一样相同
        return "Today is \(self.rawValue)."
    }
}

var day = WeekDays.Thursday
println(day.output) // Today is Thursday.


4. 属性观察器:

    1) 是Swift的一种监听机制,只能作用于变量存储属性,可以捕获变量存储属性在改变前后的值,不能作用于let定义的常量属性,也不能作用于lazy定义的惰性存储属性;

    2) 由于属性观察器不是计算属性,因此不能包含get和set,实际上它提供了两种方法willSet(在修改之前调用,捕获修改的新值)和didSet(在修改之后调用,捕获修改之前的旧值):

class Counter
{
    var cnt: Int = 0 { // 由于属性观察器只能作用于普通变量,因此必须对其初始化,如果不显示初始化也必须得定义构造器,否则会报错!
        // !注意,cnt必须指定类型!否则会报错!这是设定属性观察器时比较特殊的地方!捕获新设置值,在修改之前调用
        willSet { // 不写自定义参数默认名称为newValue,或者willSet(myNewValueName)
            println("The new value is \(newValue)")
        }
        // 捕获设置之前的旧值,在修改之后调用
        didSet { // 默认参数名为oldValue,或者didSet(myOldValueName)
            println("The old value is \(oldValue)")
        }
    }
}

var one = Counter()
one.cnt = 12
one.cnt = 23
// The new value is 12
// The old value is 0
// The new value is 23
// The old value is 12

    3) 属性观察者不能用于枚举,而只能用于结构体和类,因为枚举不能定义存储属性(但是枚举可以定义计算属性(只要不是存储的都可以定义))!


5. 计算属性和属性观察者在全局也可以使用(即在类、结构体、枚举外部也可以使用):

var a = 0
var b = 0
var c: Int {
    get {
        return a + b
    }
    set {
        a = newValue / 2
        b = a
    }
}

c = 5
println(a) // 2
println(b) // 2
a = 7
b = 15
println(c) // 22

var d: Int = 1 {
    willSet {
        println("newValue = \(newValue)")
    }
    didSet {
        println("oldValue = \(oldValue)")
    }
}

d = 2
d = 10
// newValue = 2
// oldValue = 1
// newValue = 10
// oldValue = 2

6. 静态属性:

    1) 和C++静态属性的概念,即属于类而不属于对象,举个很简单的例子,属性有账户、余额、利率三个,只有账户和余额这两个属性会因人而异但是利率对于所有账户来说都是一样的,而这类属性就可以定义成静态属性;

    2) 如上述,像账户、余额这样的属性属于实例属性,而利率这样的属性就是类型属性,也叫做静态属性!

    3) 访问规则和C++一样,直接用“类名.静态属性名”的方式访问,并且不需要实例化,因为属于类而不属于实例;

    4) 静态属性和费静态属性之间的调用关系也和C++一样,静态属性可以调用任意静态属性,但是静态属性不能访问费静态属性,而非静态属性可以访问或调用静态属性,总的来说就是静态属性作用范围更大一点;

    5) 在Swift中枚举、结构体和类都可以定义静态属性,现在来看一下这三者的区别:

class A
{
    // 只支持计算属性!
    // 不支持不同存储属性以及带属性观察器的存储属性!
    // 感觉非常奇怪!
    
    class var a: Int {
        return 10
    }
    class var b: Int {
        get {
            return 5
        }
        set {
            println("haha")
        }
    }
}

struct B // 结构体支持的最多!基本能支持的都支持了!
{
    static var a: Int = 1 // 静态属性必须初始化!否则报错!
    static var b: Int { // 支持只读计算属性
        return 10
    }
    static var c: Int { // 支持可读可写计算属性
        get {
            return 5
        }
        set {
            a = newValue // 静态属性中访问静态属性不需要加类前缀
        }
    }
    static var d: Int = 10 { // 支持具有属性观察器的存储属性
        willSet {
            println("haha")
        }
        didSet {
            println("haha")
        }
    }
    
    var e: Int {
        return B.a // 但在非静态属性中访问静态属性必须要加类前缀,否则会报错!很奇怪!
    }
}

enum C
{
    case AA
    case BB
    case CC
    
    static var a: Int = 35 // 虽然不能定义普通存储属性但是可以定义静态存储属性
    static var b: Int {
        return 33
    }
    static var c: Int {
        get {
            return 23
        }
        set {
            a = 7
        }
    }
//    static var c: Int = 25 { // 虽然可以定义静态存储属性,但是不能定义静态的属性观察者!
//        didSet {
//            println("haha")
//        }
//        willSet {
//            println("hahaha")
//        }
//    }
}
    6) 一个关于枚举的静态属性的不错的例子:

enum Account
{
    case 中国银行
    case 中国工商银行
    case 中国建设银行
    case 中国农业银行
    
    static var interestRate: Double = 0.668
    static var staticProp: Double {
        return interestRate * 1_000_000.0
    }
    var instanceProp: Double {
        switch (self)
        {
        case .中国银行: Account.interestRate = 0.667
        case .中国工商银行: Account.interestRate = 0.669
        case .中国建设银行: Account.interestRate = 0.666
        case .中国农业银行: Account.interestRate = 0.668
        }
        
        return Account.interestRate * 1_000_000.0
    }
}

println(Account.staticProp) // 668000.0

var myAccount = Account.中国工商银行
println(myAccount.instanceProp) // 669000.0


7. 索引属性——添加下标访问方式:

    1) 类似于C++的重载operator[]运算符,只不过Swift的功能更强大,可以设定多维下标(三维、四维任意的),并且下标的类型任意(可以是Int也可以是String等等),由于Swift并没有提供二维数组,因此可以用下标属性来模拟二维数组或者多维数组;

    2) Swift对枚举、结构体、类都支持下标属性,但是下标属性不能定义为static的!以下是下标定义的一般形式:

subscript(索引参数列表) -> 返回值类型 { set和get的可读可写访问 或 直接return的只读访问 }
    3) 示例:

enum A
{
    case aa
    case bb
    case cc
    
    // 注意!下标不可以是静态的,因此下标只属于实例而不属于类型
    subscript(#index: Int) -> String { // 可以为下标添加外部变量名
        switch index // 这里只有return,因此是只读索引
        {
        case 0: return "aa"
        case 1: return "bb"
        case 2: return "cc"
        default: return ""
        }
    }
}

var a = A.aa
println(a[index: 0]) // aa
println(a[index: 2]) // cc

struct B
{
    subscript(str1 s1: String, str2 s2: String) -> String { // 下标(索引)的类型可以是任意的
        return s1 + s2
    }
}

var b = B()
println(b[str1: "xx", str2: "yy"]) // xxyy

class DoubleArrary
{
    var rows: Int // 二维数组的行数和列数
    var cols: Int
    var grid: [Int] // 用一位数组模拟二维数组
    
    init(rows: Int, cols: Int) { // 构造器
        self.rows = rows
        self.cols = cols
        grid = Array(count: rows * cols, repeatedValue: 0)
    }
    
    subscript(index: Int) -> Int { // 普通的一维数组访问,这里设定为只读
        return grid[index]
    }
    
    subscript(row: Int, col: Int) -> Int { // 可以重载,模拟二维数组访问,这里设定为可读可写下标访问
        get { // 取值访问器getter
           return grid[row * cols + col]
        }
        set { // 设置访问器setter
            grid[row * cols + col] = newValue
        }
    }
}

var arr = DoubleArrary(rows: 5, cols: 5) // 定义一个5 × 5的二维数组

for var i = 0; i < 5; i++
{
    for var j = 0; j < 5; j++
    {
        arr[i, j] = i * j
    }
}

for var i = 0; i < 5; i++
{
    for var j = 0; j < 5; j++
    {
        print("\(arr[i, j]) ")
    }
    println()
}

// 0 0 0 0 0
// 0 1 2 3 4
// 0 2 4 6 8
// 0 3 6 9 12
// 0 4 8 12 16
!!注意,下标个数没有限制,但是定义太多会影响性能!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值