[Swift]9.0swift语言中的面向对象特性

Swift语言中的面向对象特性

在现代计算机语言中,面向对象是非常重要的特性,Swift语言也提供了面向对象的支持。而且在Swift语言中,不仅类具有面向对象特性,结构体和枚举也都具有面向对象特性。

9.1 面向对象概念和基本特征

面向对象(OOP)是现代流行的程序设计方法,是一种主流的程序设计规范。其基本思想是使用对象、类、继承、封装、属性、方法等基本概念来进行程序设计。从现实世界中客观存在的事物出发来构造软件系统,并且在系统构造中尽可能运用人类的自然思维方式。

**OOP的基本特征包括:封装性、继承性和多态性。

•封装性 封装性就是尽可能隐蔽对象的内部细节,对外形成一个边界,只保留有限的对外接口使之与外部发生联系。

•继承性 一些特殊类能够具有一般类的全部属性和方法,这称做特殊类对一般类的继承。通常我们称一般类为父类(或基类),特殊类为子类(或派生类)。

•多态性 对象的多态性是指在父类中定义的属性或方法被子类继承之后,可以使同一个属性或方法在父类及其各个子类中具有不同的含义,这称为多态性。例如动物都有吃饭的方法,但是老鼠的吃饭方法和猫的吃饭方法是截然不同的。

9.2 Swift中的面向对象类型

面向对象,在不同的计算机语言中,其具体的体现也是不同的。在C++和Java等语言中通过类实现面向对象,在Swift语言中通过类和结构体(struct)实现面向对象,在Swift语言中,枚举(enum)也具有面向对象特性。结构体和枚举在其他语言中完全没有面向对象特性,Swift语言赋予了它们面向对象生命。

提示:由于OOP中的类在Swift语言中涵盖了枚举、类和结构体。为了防止与OOP中的类发生冲突,在此把Swift中的这3种类型称为“Swift面向对象类型”。

在面向对象中,将类创建对象的过程称为实例化,因此将对象称为实例,但是在Swift中,结构体和枚举的实例不称为“对象”,因为结构体和枚举并不是彻底的面向对象类型,而是包含了一些面向对象的特点。例如,在Swift中继承只发生在类上,结构体和枚举不能继承。

在Swift中,面向对象的概念还有:属性、方法、扩展和协议等,这些概念对于枚举、类和结构体等不同类型有可能不同。

9.3 枚举

在C和Objective-C中,枚举用于管理一组相关常量集合,通过使用枚举可以提高程序的可读性,使代码更清晰,更易于维护。而在Swift中,枚举的作用已经不仅仅是定义一组常量、提高程序的可读性了,它还具有了面向对象特性。

枚举的语法格式:

enum 枚举名
{
枚举的定义
}

“枚举名”是该枚举类型的名称。它首先应该是有效的标识符,其次应该是遵守面向对象的命名规范。它应该是一个名称,如果采用英文单词命名,首字母应该大写,尽量用一个英文单词。这个命名规范也适用于类和结构体的命名。“枚举的定义”是枚举的核心,它由一组成员值和一组相关值组成。

9.3.1 成员值

在枚举类型中定义一组成员,与C和Objective-C中枚举的主要作用是一样的,不同的是,在C和Objective-C中成员值是整数类型,因此在C和Objective-C中枚举类型就是整数类型。

而在Swift中,枚举的成员值默认情况下不是整数类型。

声明枚举示例:
print(“–枚举示例—”)

enum WeekDays {
case Monday
case Tuesday
case Wednesday
case Thursday
case Friday
}

上述代码声明了WeekDays枚举,表示一周中的每个工作日,其中定义了5个成员值:Monday、Tuesday、Wdenesday、Thursday、Friday,这些成员值并不是整数类型。

在这些成员值前面还要加上case关键字,也可以将多个成员值放在同一行,用逗号隔开,如下:
enum WeekDays_ {
case Monday, Tuesday, Wednesday, Thursday, Friday
}

看一个示例,代码如下:
print(“—使用枚举示例—-“)

var day = WeekDays.Friday
day = WeekDays.Wednesday
day = .Monday

func writeGreeting(day : WeekDays) {
switch day {
case .Monday:
print(“星期一好!”)
case .Tuesday:
print(“星期二好!”)
case .Wednesday:
print(“星期三好!”)
case .Thursday:
print(“星期四好!”)
case .Friday:
print(“星期五好!”)
}
}

writeGreeting(day)
writeGreeting(WeekDays.Friday)

前三行代码均是给变量day赋值,可以采用完整的“枚举类型名.成员值”的形式,也可以省略枚举类型,采用“.成员值”的形式。这种省略形式能够访问的前提是,Swift能够根据上下文环境推断类型。

枚举类型与switch语句能够很好地配合使用,在switch语句中使用枚举类型可以没有default分支,这在使用其他类型时是不允许的。

需要注意,在switch中使用枚举类型时,switch语句中的case必须全面包含枚举中的所有成员,不能多也不能少,包括使用default的情况下,default也表示某个枚举成员。

使用default分支的代码:
print(“—使用default分支—”)

func writeGreeting_(day :WeekDays) {

switch day {
case .Monday:
    print("星期一好!")
case .Tuesday:
    print("星期二好!")
case .Wednesday:
    print("星期三好!")
case .Thursday:
    print("星期四好!")
default:
    print("星期五好!")
}

}

在上面示例中,default表示的是Friday枚举成员,在这种情况下,Friday枚举成员的case分支不能再出现了。

9.3.2 原始值

为每个成员提供某种具体类型的默认值,可以为枚举类型提供原始值(raw values)声明,这些原始值类型可以是:字符、字符串、整数和浮点数等。

原始值枚举的语法格式如下:

enum 枚举名 : 数据类型
{
case 成员名 = 默认值

}

在“枚举名”后面跟“:”和“数据类型”就可以声明原始值枚举的类型,然后在定义case成员的时候需要提供默认值。

以下代码是声明枚举示例:
print(“—原始值—”)

enum WeekDays__ : Int {
case Monday = 0
case Tuesday = 1
case Wednesday = 2
case Thursday = 3
case Friday = 4
}

声明的WeekDays枚举类型的原始值类型是Int,需要给每个成员赋值,只要是Int类型都可以,但是每个分支不能重复。还可以采用如下简便写法,只需要给第一个成员赋值即可,后面的成员值会依次加1。
print(“–原始值–简便写法–”)

enum WeekDays___ : Int {
case Monday = 0, Tuesday, Wednesday, Thursday, Friday
}

完整示例代码:
print(“—完整示例代码—-“)

print(“—原始值—”)

enum WeekDays : Int {
case Monday = 0
case Tuesday = 1
case Wednesday = 2
case Thursday = 3
case Friday = 4
}

var day = WeekDays.Friday
day = WeekDays.Wednesday

func writeGreeting(day : WeekDays) {
switch day {
case .Monday:
print(“星期一好!”)
case .Tuesday:
print(“星期二好!”)
case .Wednesday:
print(“星期三好!”)
case .Thursday:
print(“星期四好!”)
case .Friday:
print(“星期五好!”)
}
}

let friday = WeekDays.Friday.rawValue

let thursday = WeekDays(rawValue: 3)

if (WeekDays.Friday.rawValue == 4) {
print(“今天是星期五”)
}

writeGreeting(day)
writeGreeting(WeekDays.Friday)

let friday = WeekDays.Friday.rawValue该句代码是通过WeekDays.Friday的方法rawValue转换为原始值。虽然在定义的时候Friday被赋值为4,但是并不等于WeekDays.Friday就是整数4了,而是它的原始值为整数4,因此下面的比较是错误的。
if (WeekDays.Friday == 4) {
print(“今天是星期五”)
}

.rawValue方法是将成员值转换为原始值,相反,WeekDays(rewValue : 3)方法是将原始值转换为成员值。

9.3.3 相关值

在Swift中除了定义一组成员值,还可以定义一组相关值(associated values),它有点类似于C中的联合类型。

枚举类型的声明:
print(“–相关值—”)

enum Figure {
case Rectangle(Int, Int)
case Circle(Int)
}

枚举类型Figure(图形)有两个相关值:Rectangle(矩形)和Circle(圆形)。Rectangle和Circle是与Figure有关联的相关值,它们都是元组类型,对于一个特定的Figure实例,只能是其中一个相关值。

示例,代码如下:
func printFigure(figure : Figure) {

switch figure {
case .Rectangle(let width, let height):
    print("矩形的宽:\(width) 高:\(height)")
case .Circle(let radius) :
    print("圆形的半径:\(radius)")
}

}

var figure = Figure.Rectangle(1024, 768)
printFigure(figure)

figure = .Circle(600)
printFigure(figure)

如果某个相关值元组中字段类型一致,需要全部提取,则可以在相关值前面添加let或var。可以使用如下方式修改Rectangle分支:
switch figure {
case let .Rectangle(width, height):
print(“矩形的宽:(width) 高: (height)”)
case .Circle(let radius):
print(“圆形的半径:(radius)”)
}

9.4 结构体与类

在面向过程的编程语言中,结构体用的比较多,但是面向对象之后,如在C++和Objective-C中,结构体已经很少使用了。这是应为结构体能够做的事情,类完全可以取而代之。

而Swift语言却非常重视结构体,把结构体作为实现面向对象的重要手段。Swift中的结构体与C++和Objective-C中的结构体有很大的差别,C++和Objective-C中的结构体只能定义一组相关的成员变量,而Swift中的结构体不仅可以定义成员变量(属性),还可以定义成员方法。因此,可以把结构体看做是一种轻量级的类。

Swift中的类和结构体非常类似,都具有定义和使用属性、方法、下标和构造器等面向对象特性,但是结构体不具有继承性,也不具备运行时强制类型转换、使用析构器和使用引用计等能力。

9.4.1 类和结构体定义

Swift中的类和结构体定义的语法也是非常相似的。我们可以使用class关键词定义类,使用struct关键词定义结构体,它们的语法格式如下:

class 类名 {
定义类的成员
}

struct 结构体名 {
定义结构体的成员
}

从语法格式上看,Swift中的类和结构体的定义更类似于Java语法,不需要像C++和Objective-C那样把接口部分和实现部分放到不同的文件中。
示例:
print(“—类和结构体定义—”)
class Employee { //定义员工类
var no : Int = 0 //定义员工编号属性
var name : String = “” //定义员工姓名属性
var job : String? //定义工作属性
var salary : Double = 0 //定义薪资属性
var dept : Department? //定义所在部门属性
}

struct Department { //定义部门结构体
var no : Int = 0 //定义部门编号属性
var name : String = “” //定义部门名称属性
}

Employee是定义的类,Department是定义的结构体。
可以通过下列语句实例化:
var emp = Employee()
var dept = Department()

Employee()和Department()是调用它们的构造器实现实例化。

提示:实例化之后会开辟内存空间,emp和dept被称为“实例”,但只有类实例化的“实例”才能被称为“对象”。事实上,不仅仅是结构体和类可以实例化,枚举、函数类型和闭包开辟内存空间的过程也可以称为实例化,结果也可以叫“实例”,但不能叫“对象”。

9.4.2 再谈值类型和引用类型

数据类型可以分为:值类型和引用类型,这是由赋值或参数传递方式决定的。值类型就是在赋值或给函数传递参数时候,创建一个副本,把副本传递过去,这样在函数的调用过程中不会影响原始数据。引用类型就是在赋值或给函数传递参数的时候,把本身数据传递过去,这样在函数的调用过程中会影响原始数据。

在众多的数据类型中,只需记住:只有类是引用类型,其他类型全部是值类型。即便结构体与类非常相似,它也是值类型。值类型还包括整型、浮点型、布尔型、字符串、元组、集合和枚举。

Swift中的引用类型与Java中的引用类型是一样的,Java中的类也是引用类型。如果没有Java经验,可以把引用类型理解为C、C++和Objective-C语言中的指针类型,只不过不需要在引用类型变量或常量前面加星号(*)。

示例:
print(“–值类型和引用类型–”)

var dept = Department()
dept.no = 10
dept.name = “Sales”

var emp = Employee()
emp.no = 1000
emp.name = “Martin”
emp.job = “Salesman”
emp.salary = 1250
emp.dept = dept

func updateDept (inout dept : Department) {
dept.name = “Research”
}

print(“Department更新前:(dept.name)”)
updateDept(&dept)

print(“Department更新后:(dept.name)”)

func updateEmp (emp : Employee) {
emp.job = “Clerk”
}

print(“Employee更新前:(emp.job)”)
updateEmp(emp)

print(“Employee更新后:(emp.job)”)

对比上面代码,说明Employee类是引用类型,在调用的时候不用在变量前面添加&符号。

9.4.3 引用类型的比较

恒等于(===)和不恒等于(!===)关系运算符。===用于比较两个引用是否为同一个实例,!===则恰恰相反,它只能用于引用类型,也就是类的实例。

示例:
print(“—引用类型的比较—”)

var emp1 = Employee() //1
emp1.no = 1000
emp1.name = “Martin”
emp1.job = “Salesman”
emp1.salary = 1250

var emp2 = Employee() //2
emp2.no = 1000
emp2.name = “Martin”
emp2.job = “Salesman”
emp2.salary = 1250

if emp1 === emp2 //3
{
print(“emp1 === emp2”)
}

if emp1 === emp1 //4
{
print(“emp1 === emp1”)
}

var dept1 = Department() //5

dept1.no = 10
dept1.name = “Sales”

var dept2 = Department() //6
dept2.no = 10
dept2.name = “Sales”

if dept1 == dept2 //编译失败 //7
{
print(“dept1 === dept2”)
}

上述代码第1行和第2行分别创建了emp1和emp2两个Employee实例。在代码第3行比较emp1和emp2两个引用是否为一个实例。可以看到,比较结果为False(第3行没有输出emp1 === emp2),也就是emp1和emp2两个引用不是一个实例,即便是它们内容完全一样,结果也是False,而第4行的比较结果为True。如果对于第3行采用==比较,代码如下:
if emp1 == emp1
{
print(“emp1 === emp1”)
}

答案是有编译错误。==比较要求两个实例的类型(类、结构体、枚举等)必须要在该类型中重写==运算符,定义相等规则。同样的错误也会发生在第7行代码。

第7行使用==比较dept1和dept2两个值是否相等,不仅不能比较,而且还会发生编译错误,这在上面已经解释过了。

如果采用恒等于===比较dept1和dept2,代码如下:
if dept1 === dept2
{
print(“dept1 === dept2”)
}

这会发生编译错误。===不能比较值类型,而Department结构体是值类型,因此不能使用===比较。

9.5 类型嵌套

Swift语言中的类、结构体和枚举可以进行嵌套,即在某一类型的{}内部定义类。这种类型嵌套在Java中称为内部类,在C#中称为嵌套类,它们的形式和设计目的都是类似的。

类型嵌套的优点是能够访问它外部的成员(包括方法、属性和其他的嵌套类型),嵌套还可以有多个层次。

示例:
print(“—类型嵌套—”)

class Employee {
var no : Int = 0
var name : String = “”
var job : String = “”
var salary : Double = 0
var dept : Department = Department()

var day : WeekDays = WeekDays.Friday

struct Department {
    var no : Int = 10
    var name : String = "SALES"
}

enum WeekDays {
    case Monday
    case Tuesday
    case Wednesday
    case Thursday
    case Friday

    struct Day {
        static var message : String = "Today is ..."
    }
}

}

var emp = Employee()
print(emp.dept.name)
print(emp.day)

let friday = Employee.WeekDays.Friday
if emp.day == friday {
print(“相等”)
}
print(Employee.WeekDays.Day.message)

类型嵌套便于我们访问外部类的成员,但它会使程序结构变得不清楚,使程序的可读性变差。

**9.6 可选类型与可选链
9.6.1 可选类型**

有时使用一个变量或常量,它保存的值可能有也可能没有。示例代码:
print(“—可选类型—”)

func divide(n1 : Int, n2 : Int) -> Double? {
if n2 == 0 {
return nil
}

return Double(n1) / Double(n2)

}
let result : Double? = divide(100, n2: 200)

9.6.2 可选绑定

可选类型可以用于判断,如下代码:
print(“—可选绑定—”)

if let result2 : Double? = divide(100, n2: 0) {
print(“Success.”)
} else {
print(“failure.”)
}

输出结果为failure.

这种可选类型在if或while语句中赋值并进行判断的写法,叫做可选绑定。

9.6.3 强制拆封

如果我们能确定可选类型一定有值,那么在读取它的时候,可以在可选类型的后面加一个感叹号(!)来获取该值。这种感叹号的表示方式称为可选值的强制拆封(forced unwrapping)。如下代码:
print(“—强制拆封—”)

let result1 : Double? = divide(100, n2: 200)
print(result1)

print(result1!)//语句中的result1就进行了强制拆封

9.6.4 隐式拆封

为了能够方便地访问可选类型,可以将可选类型后面的问号(?)换成感叹号(!),这种可选类型在拆封时变量或常量后面不加感叹号(!)的表示方式成为隐式拆封。如下代码:
print(“–隐私拆封—”)

let result3 : Double! = divide(100, n2: 200)
print(result3)

在变量或常量声明的时候,数据类型Double后面跟的是感叹号(!)而不是问号(?),在拆封的时候,变量或常量后面不用加感叹号(!),这就是隐式拆封。隐式拆封的变量或常量使用起来就像普通变量或常量一样,也可以把它看成是普通的变量或常量。

9.6.5 可选链

示例代码:
print(“–可选链—”)

class Employee {
var no : Int = 0
var name : String = “Tony”
var job : String?
var salary : Double = 0
var dept : Department = Department()
}

class Department {
var no : Int = 10
var name : String = “SALES”
var comp : Company = Company()
}

class Company {
var no : Int = 1000
var name : String = “EOrient”
}

var emp = Employee()
print(emp.dept.comp.name)

Employee通过dept属性与Department关联,Department通过comp属性与Company关联。
通过代码emp.dept.comp.name可以引用到Company实例,形成一个引用的链条,但是这个“链条”任何一个环节“断裂”(为nil)都无法引用到最后的目标(Company实例)。

var dept : Department = Department()是使用Department()构造器实例化dept属性的,这说明给定一个Employee实例,一定会有一个Department与其关联。但是现实世界并非如此,这种关联关系有可能有值,也有可能没有值,我们需要使用可选类型(Department?)声明dept实例。

修改代码如下:
class Employee {
var no : Int = 0
var name : String = “Tony”
var job : String?
var salary : Double = 0
var dept : Department?
}

class Department {
var no : Int = 10
var name : String = “SALES”
var comp : Company?
}

class Company {
var no : Int = 1000
var name : String = “EOrient”
}

var dept : Department?代码声明dept为可选类型,代码var comp : Company?声明comp为可选类型,那么原来的引用方式emp.dept.comp.name已经不能应对可选类型了。之前介绍过可选类型的引用,可以使用感叹号(!)进行强制拆封,代码修改如下:
print(emp.dept!.comp!.name)

但是强制拆封有一个弊端,如果可选链中某个环节为nil,将会导致代码运行时错误。可以采用更加“温柔”的引用方式,使用问号(?)来代替原来感叹号(!)的位置。如下所示:
print(emp.dept?.comp?.name)

问号(?)表示引用的时候,如果某个环节为nil,它不会抛出错误,而是会把nil返回给引用者。这种由问号(?)引用可选类型的方式就是可选链。

可选链是一种“温柔”的引用方式,它的引用目标不仅仅是属性,还可以是方法、下标和嵌套类型等。

具有嵌套类型的示例:
class Employee {
var no : Int = 0
var name : String = “”
var job : String = “”
var salary : Double = 0
var dept : Department?

struct Department {
    var no : Int = 10
    var name : String = "SALES"
}

}

var emp = Employee()
print(emp.dept?.name)

代码var dept : Department?定义可选类型Department?的属性dept,Department是嵌套结构体类型。print(emp.dept?.name)采用可选链方式引用。输出结果为nil,这是因为emp.dept环节为nil。如果将代码var dept : Department?修改一下:
var dept : Department? = Department()

则输出结果为SALES。这说明可选链可以到达目标name。

9.6.6 <1.可选类型中的问号(?)**

声明这个类型是可选类型,访问这种类型的变量和常量时要使用感叹号(!),下列代码是强制拆封:
let result1 : Double? = divide(100, 200)
print(result1!)

<2.可选类型中的感叹号(!)

声明这个类型也是可选类型,但是访问这种类型的变量或常量时可以不使用感叹号(!),下列代码是隐式拆封:
let result3 : Double? = divide(100, 200)
print(result3)

<3.可选链中的感叹号(!)

多个对象具有关联关系,当从一个对象引用另外对象的方式、属性和下标等成员时就会形成引用连,由于这个“链条”某些环节可能有值,也可能没有值,因此需要采用如下方式访问:
emp.dept!.comp!.name

<4.可选链中的问号(?)

在可选链中使用感叹号(!)访问时,一旦“链条”某些环节没有值,程序就会发生异常,于是我们把感叹号(!)改为问号(?)
,代码如下所示:
emp.dept?.comp?.name

这样某些环节没有值的时候返回nil,程序不会发生异常。

9.7 访问限定

作为一种面向对象的语言封装性是不可缺少的,Swift语言在正式版中增加了访问控制,这样一来Swift语言就可以实现封装特性了。由于在Swift语言中类、结构体和枚举类型都具有面向对象的特性,因此Swift语言的封装就变得比较复杂。

9.7.1 访问范围

访问范围主要有两个: 模块和源文件。

模块是指一个应用程序包或一个框架。在Swift中,可以用import关键字将模块引入到自己的工程中。应用程序包是可执行的,其内部包含了很多Swift文件以及其他文件。

框架也是很多Swift文件及其他文件的集合,但是应用程序包不同的是,它编译的结果是不可以执行文件。

源文件指的是Swift中的.swift文件,编译之后它被包含在应用程序包或框架中,通常一个源文件包含一个面向对象类型(类、结构体和枚举),在这些类型中又包含函数、属性等。

9.7.2 访问级别

Swift提供了3种不同访问级别,对应的访问修饰符为:public、internal和private。这些访问修饰符可以修饰类、结构体、枚举等面向对象的类型,还可以修饰变量、常量、下标、元组、函数、属性等内容。

提示:为了便于描述,我们把类、结构体、枚举、变量、常量、下标、元组、函数、属性等内容统一称为“实体”。

•public 可以访问自己模块中的任何public实体。如果使用import语句引入其他模块,可以访问其他模块中的public实体。

•internal 只能访问自己模块的任何internal实体,不能访问其他模块中的internal实体。internal可以省略,换句话说,默认访问限定是internal

•private 只能在当前源文件中使用的实体,称为私有实体。使用private修饰,可以用作隐藏某些功能的实现细节。

使用访问修饰符的示例代码如下:
print(“–使用访问修饰符示例代码—”)

public class PublicClass {}

internal class InternalClass {}

private class PrivateClass {}

public var intPublicVariable = 0
let intInternalConstant = 0//internal访问级别
private func intPrivateFunction()

9.7.3 使用访问级别最佳实践

<1.统一性原则
•原则1:如果一个类型(类、结构体、枚举)定义为internal或private,那么类型声明的变量或常量不能使用public访问级别。因为public的变量或常量可以被任何人访问,而internal或private的类型不可以。

代码:
print(“–原则1–”)

private class Employee {
var no : Int = 0
var name : String = “”
var job : String?
var salary : Double = 0
var dept : Department?
}

internal struct Department {
var no : Int = 0
var name : String = “”
}

public let emp = Employee()//编译错误

public var dept = Department()//编译错误

<2.设计原则

如果编写的是应用程序,应用程序包中的所有Swift文件和其中定义的实体,都是供本应用使用的,而不是提供其他模块使用,那么就不用设置访问级别了,即使用默认的访问级别。

如果开发的是框架,框架编译的文件不能独立运行,因此它天生就是给别人使用的,这种情况下要详细设计其中的Swift文件和实体的访问级别,让别人使用的可以设定为public,不想让别人看到的可以设定为internal或private。

<3.元组类型的访问级别
元组类型的访问级别遵循元组中字段最低级的访问级别。代码如下:
print(“—元组类型的访问级别—”)

private class Employee {
var no : Int = 0
var name : String = “”
var job : String?
var salary : Double = 0
var dept : Department?
}

struct Department {
var no : Int = 0
var name : String = “”
}

private let emp = Employee()
var dept = Department()

private var student1 = (dept, emp)

private var student1 = (dept, emp)定义了元组student1,其中的字段dept和emp的最低访问级别是private,所以student1访问级别也是private,这也符合统一性原则。

<4.枚举类型的访问级别

枚举中成员的访问级别继承自该枚举,因此不能为枚举中的成员指定访问级别。示例代码如下:
print(“—枚举类型的访问级别—”)

public enum WeekDays {
case Monday
case Tuesday
case Wednesday
case Thursday
case Friday
}

由于WeekDays枚举类型是public访问级别,因而它的成员也是public级别。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值