Swift学习:2.14 构造过程
参考Swift 官方教程《The Swift Programming Language》中文版
构造过程(Initialization)构造过程是为了使用某个类、结构体或枚举类型的实例而进行的准备过程。这个过程包含了为实例中的每个属性设置初始值和为其执行必要的准备和初始化任务。 构造过程是通过定义构造器( 类实例也可以通过定义析构器( 存储型属性的初始赋值类和结构体在实例创建时,必须为所有存储型属性设置合适的初始值。存储型属性的值不能处于一个未知的状态。 你可以在构造器中为存储型属性赋初值,也可以在定义属性时为其设置默认值。以下章节将详细介绍这两种方法。
构造器构造器在创建某特定类型的新实例时调用。它的最简形式类似于一个不带任何参数的实例方法,以关键字 下面例子中定义了一个用来保存华氏温度的结构体 struct Fahrenheit {
var temperature: Double
init() {
temperature = 32.0
}
}
var f = Fahrenheit()
println("The default temperature is (f.temperature)° Fahrenheit")
// 输出 "The default temperature is 32.0° Fahrenheit”
这个结构体定义了一个不带参数的构造器 默认属性值如前所述,你可以在构造器中为存储型属性设置初始值;同样,你也可以在属性声明时为其设置默认值。
你可以使用更简单的方式在定义结构体 struct Fahrenheit {
var temperature = 32.0
}
定制化构造过程你可以通过输入参数和可选属性类型来定制构造过程,也可以在构造过程中修改常量属性。这些都将在后面章节中提到。 构造参数你可以在定义构造器时提供构造参数,为其提供定制化构造所需值的类型和名字。构造器参数的功能和语法跟函数和方法参数相同。 下面例子中定义了一个包含摄氏度温度的结构体 struct Celsius {
var temperatureInCelsius: Double = 0.0
init(fromFahrenheit fahrenheit: Double) {
temperatureInCelsius = (fahrenheit - 32.0) / 1.8
}
init(fromKelvin kelvin: Double) {
temperatureInCelsius = kelvin - 273.15
}
}
let boilingPointOfWater = Celsius(fromFahrenheit: 212.0)
// boilingPointOfWater.temperatureInCelsius 是 100.0
let freezingPointOfWater = Celsius(fromKelvin: 273.15)
// freezingPointOfWater.temperatureInCelsius 是 0.0”
第一个构造器拥有一个构造参数,其外部名字为 内部和外部参数名跟函数和方法参数相同,构造参数也存在一个在构造器内部使用的参数名字和一个在调用构造器时使用的外部参数名字。 然而,构造器并不像函数和方法那样在括号前有一个可辨别的名字。所以在调用构造器时,主要通过构造器中的参数名和类型来确定需要调用的构造器。正因为参数如此重要,如果你在定义构造器时没有提供参数的外部名字,Swift 会为每个构造器的参数自动生成一个跟内部名字相同的外部名,就相当于在每个构造参数之前加了一个哈希符号。
以下例子中定义了一个结构体
struct Color {
let red = 0.0,green = 0.0,blue = 0.0
init(red: Double,green: Double,blue: Double) {
self.red = red
self.green = green
self.blue = blue
}
}
每当你创建一个新的 let magenta = Color(red: 1.0,green: 0.0,blue: 1.0)
注意,如果不通过外部参数名字传值,你是没法调用这个构造器的。只要构造器定义了某个外部参数名,你就必须使用它,忽略它将导致编译错误: let veryGreen = Color(0.0,1.0,0.0)
// 报编译时错误,需要外部名称
可选属性类型如果你定制的类型包含一个逻辑上允许取值为空的存储型属性--不管是因为它无法在初始化时赋值,还是因为它可以在之后某个时间点可以赋值为空--你都需要将它定义为可选类型 下面例子中定义了类 class SurveyQuestion {
var text: String
var response: String?
init(text: String) {
self.text = text
}
func ask() {
println(text)
}
}
let cheeseQuestion = SurveyQuestion(text: "Do you like cheese?")
cheeseQuestion.ask()
// 输出 "Do you like cheese?"
cheeseQuestion.response = "Yes,I do like cheese."
调查问题在问题提出之后,我们才能得到回答。所以我们将属性回答 构造过程中常量属性的修改只要在构造过程结束前常量的值能确定,你可以在构造过程中的任意时间点修改常量属性的值。
你可以修改上面的 class SurveyQuestion {
let text: String
var response: String?
init(text: String) {
self.text = text
}
func ask() {
println(text)
}
}
let beetsQuestion = SurveyQuestion(text: "How about beets?")
beetsQuestion.ask()
// 输出 "How about beets?"
beetsQuestion.response = "I also like beets. (But not with cheese.)"
默认构造器Swift 将为所有属性已提供默认值的且自身没有定义任何构造器的结构体或基类,提供一个默认的构造器。这个默认构造器将简单的创建一个所有属性值都设置为默认值的实例。 下面例子中创建了一个类 class ShoppingListItem {
var name: String?
var quantity = 1
var purchased = false
}
var item = ShoppingListItem()
由于 结构体的逐一成员构造器除上面提到的默认构造器,如果结构体对所有存储型属性提供了默认值且自身没有提供定制的构造器,它们能自动获得一个逐一成员构造器。 逐一成员构造器是用来初始化结构体新实例里成员属性的快捷方法。我们在调用逐一成员构造器时,通过与成员属性名相同的参数名进行传值来完成对成员属性的初始赋值。 下面例子中定义了一个结构体 由于这两个存储型属性都有默认值,结构体 struct Size {
var width = 0.0,height = 0.0
}
let twoByTwo = Size(width: 2.0,height: 2.0)
值类型的构造器代理构造器可以通过调用其它构造器来完成实例的部分构造过程。这一过程称为构造器代理,它能减少多个构造器间的代码重复。 构造器代理的实现规则和形式在值类型和类类型中有所不同。值类型(结构体和枚举类型)不支持继承,所以构造器代理的过程相对简单,因为它们只能代理给本身提供的其它构造器。类则不同,它可以继承自其它类(请参考继承),这意味着类有责任保证其所有继承的存储型属性在构造时也能正确的初始化。这些责任将在后续章节类的继承和构造过程中介绍。 对于值类型,你可以使用 注意,如果你为某个值类型定义了一个定制的构造器,你将无法访问到默认构造器(如果是结构体,则无法访问逐一对象构造器)。这个限制可以防止你在为值类型定义了一个更复杂的,完成了重要准备构造器之后,别人还是错误的使用了那个自动生成的构造器。
下面例子将定义一个结构体 struct Size {
var width = 0.0,height = 0.0
}
struct Point {
var x = 0.0,y = 0.0
}
你可以通过以下三种方式为 struct Rect {
var origin = Point()
var size = Size()
init() {}
init(origin: Point,size: Size) {
self.origin = origin
self.size = size
}
init(center: Point,size: Size) {
let originX = center.x - (size.width / 2)
let originY = center.y - (size.height / 2)
self.init(origin: Point(x: originX,y: originY),size: size)
}
}
第一个 let basicRect = Rect()
// basicRect 的原点是 (0.0,0.0),尺寸是 (0.0,0.0)
第二个 let originRect = Rect(origin: Point(x: 2.0,y: 2.0),size: Size(width: 5.0,height: 5.0))
// originRect 的原点是 (2.0,2.0),尺寸是 (5.0,5.0)
第三个 let centerRect = Rect(center: Point(x: 4.0,y: 4.0),size: Size(width: 3.0,height: 3.0))
// centerRect 的原点是 (2.5,2.5),尺寸是 (3.0,3.0)
构造器
类的继承和构造过程类里面的所有存储型属性--包括所有继承自父类的属性--都必须在构造过程中设置初始值。 Swift 提供了两种类型的类构造器来确保所有类实例中存储型属性都能获得初始值,它们分别是指定构造器和便利构造器。 指定构造器和便利构造器指定构造器是类中最主要的构造器。一个指定构造器将初始化类中提供的所有属性,并根据父类链往上调用父类的构造器来实现父类的初始化。 每一个类都必须拥有至少一个指定构造器。在某些情况下,许多类通过继承了父类中的指定构造器而满足了这个条件。具体内容请参考后续章节自动构造器的继承。 便利构造器是类中比较次要的、辅助型的构造器。你可以定义便利构造器来调用同一个类中的指定构造器,并为其参数提供默认值。你也可以定义便利构造器来创建一个特殊用途或特定输入的实例。 你应当只在必要的时候为类提供便利构造器,比方说某种情况下通过使用便利构造器来快捷调用某个指定构造器,能够节省更多开发时间并让类的构造过程更清晰明了。 构造器链为了简化指定构造器和便利构造器之间的调用关系,Swift 采用以下三条规则来限制构造器之间的代理调用: 规则 1指定构造器必须调用其直接父类的的指定构造器。 规则 2便利构造器必须调用同一类中定义的其它构造器。 规则 3便利构造器必须最终以调用一个指定构造器结束。 一个更方便记忆的方法是:
这些规则可以通过下面图例来说明: 如图所示,父类中包含一个指定构造器和两个便利构造器。其中一个便利构造器调用了另外一个便利构造器,而后者又调用了唯一的指定构造器。这满足了上面提到的规则2和3。这个父类没有自己的父类,所以规则1没有用到。 子类中包含两个指定构造器和一个便利构造器。便利构造器必须调用两个指定构造器中的任意一个,因为它只能调用同一个类里的其他构造器。这满足了上面提到的规则2和3。而两个指定构造器必须调用父类中唯一的指定构造器,这满足了规则1。
下面图例中展示了一种针对四个类的更复杂的类层级结构。它演示了指定构造器是如何在类层级中充当“管道”的作用,在类的构造器链上简化了类之间的相互关系。 两段式构造过程Swift 中类的构造过程包含两个阶段。第一个阶段,每个存储型属性通过引入它们的类的构造器来设置初始值。当每一个存储型属性值被确定后,第二阶段开始,它给每个类一次机会在新实例准备使用之前进一步定制它们的存储型属性。 两段式构造过程的使用让构造过程更安全,同时在整个类层级结构中给予了每个类完全的灵活性。两段式构造过程可以防止属性值在初始化之前被访问;也可以防止属性被另外一个构造器意外地赋予不同的值。
Swift 编译器将执行 4 种有效的安全检查,以确保两段式构造过程能顺利完成: 安全检查 1指定构造器必须保证它所在类引入的所有属性都必须先初始化完成,之后才能将其它构造任务向上代理给父类中的构造器。 如上所述,一个对象的内存只有在其所有存储型属性确定之后才能完全初始化。为了满足这一规则,指定构造器必须保证它所在类引入的属性在它往上代理之前先完成初始化。 安全检查 2指定构造器必须先向上代理调用父类构造器,然后再为继承的属性设置新值。如果没这么做,指定构造器赋予的新值将被父类中的构造器所覆盖。 安全检查 3便利构造器必须先代理调用同一类中的其它构造器,然后再为任意属性赋新值。如果没这么做,便利构造器赋予的新值将被同一类中其它指定构造器所覆盖。 安全检查 4构造器在第一阶段构造完成之前,不能调用任何实例方法、不能读取任何实例属性的值, 类实例在第一阶段结束以前并不是完全有效,仅能访问属性和调用方法,一旦完成第一阶段,该实例才会声明为有效实例。 以下是两段式构造过程中基于上述安全检查的构造流程展示: 阶段 1
阶段 2
下图展示了在假定的子类和父类之间构造的阶段1: · 在这个例子中,构造过程从对子类中一个便利构造器的调用开始。这个便利构造器此时没法修改任何属性,它把构造任务代理给同一类中的指定构造器。 如安全检查1所示,指定构造器将确保所有子类的属性都有值。然后它将调用父类的指定构造器,并沿着造器链一直往上完成父类的构建过程。 父类中的指定构造器确保所有父类的属性都有值。由于没有更多的父类需要构建,也就无需继续向上做构建代理。 一旦父类中所有属性都有了初始值,实例的内存被认为是完全初始化,而阶段1也已完成。 以下展示了相同构造过程的阶段2: 父类中的指定构造器现在有机会进一步来定制实例(尽管它没有这种必要)。 一旦父类中的指定构造器完成调用,子类的构指定构造器可以执行更多的定制操作(同样,它也没有这种必要)。 最终,一旦子类的指定构造器完成调用,最开始被调用的便利构造器可以执行更多的定制操作。 构造器的继承和重载跟 Objective-C 中的子类不同,Swift 中的子类不会默认继承父类的构造器。Swift 的这种机制可以防止一个父类的简单构造器被一个更专业的子类继承,并被错误的用来创建子类的实例。 假如你希望自定义的子类中能实现一个或多个跟父类相同的构造器--也许是为了完成一些定制的构造过程--你可以在你定制的子类中提供和重载与父类相同的构造器。 如果你重载的构造器是一个指定构造器,你可以在子类里重载它的实现,并在自定义版本的构造器中调用父类版本的构造器。 如果你重载的构造器是一个便利构造器,你的重载过程必须通过调用同一类中提供的其它指定构造器来实现。这一规则的详细内容请参考构造器链。
自动构造器的继承如上所述,子类不会默认继承父类的构造器。但是如果特定条件可以满足,父类构造器是可以被自动继承的。在实践中,这意味着对于许多常见场景你不必重载父类的构造器,并且在尽可能安全的情况下以最小的代价来继承父类的构造器。 假设要为子类中引入的任意新属性提供默认值,请遵守以下2个规则: 规则 1如果子类没有定义任何指定构造器,它将自动继承所有父类的指定构造器。 规则 2如果子类提供了所有父类指定构造器的实现--不管是通过规则1继承过来的,还是通过自定义实现的--它将自动继承所有父类的便利构造器。 即使你在子类中添加了更多的便利构造器,这两条规则仍然适用。
指定构造器和便利构造器的语法类的指定构造器的写法跟值类型简单构造器一样: init(parameters) {
statements
}
便利构造器也采用相同样式的写法,但需要在 convenience init(parameters) {
statements
}
指定构造器和便利构造器实战接下来的例子将在实战中展示指定构造器、便利构造器和自动构造器的继承。它定义了包含三个类 类层次中的基类是 class Food {
var name: String
init(name: String) {
self.name = name
}
convenience init() {
self.init(name: "[Unnamed]")
}
}
下图中展示了 类没有提供一个默认的逐一成员构造器,所以 let namedMeat = Food(name: "Bacon")
// namedMeat 的名字是 "Bacon”
let mysteryMeat = Food()
// mysteryMeat 的名字是 [Unnamed]
类层级中的第二个类是 class RecipeIngredient: Food {
var quantity: Int
init(name: String,quantity: Int) {
self.quantity = quantity
super.init(name: name)
}
override convenience init(name: String) {
self.init(name: name,quantity: 1)
}
}
下图中展示了
注意, 在这个例子中, 所有的这三种构造器都可以用来创建新的 let oneMysteryItem = RecipeIngredient()
let oneBacon = RecipeIngredient(name: "Bacon")
let sixEggs = RecipeIngredient(name: "Eggs",quantity: 6)
类层级中第三个也是最后一个类是 购物单中的每一项总是从 class ShoppingListItem: RecipeIngredient {
var purchased = false
var description: String {
var output = "(quantity) x (name.lowercaseString)"
output += purchased ? " ?" : " ?"
return output
}
}
由于它为自己引入的所有属性都提供了默认值,并且自己没有定义任何构造器, 下图种展示了所有三个类的构造器链: 你可以使用全部三个继承来的构造器来创建 var breakfastList = [
ShoppingListItem(),ShoppingListItem(name: "Bacon"),ShoppingListItem(name: "Eggs",quantity: 6),]
breakfastList[0].name = "Orange juice"
breakfastList[0].purchased = true
for item in breakfastList {
println(item.description)
}
// 1 x orange juice ?
// 1 x bacon ?
// 6 x eggs ?
如上所述,例子中通过字面量方式创建了一个新数组 可失败构造器如果一个类,结构体或枚举类型的对象,在构造自身的过程中有可能失败,则为其定义一个可失败构造器,是非常有必要的。这里所指的“失败”是指,如给构造器传入无效的参数值,或缺少某种所需的外部资源,又或是不满足某种必要的条件等。 为了妥善处理这种构造过程中可能会失败的情况。你可以在一个类,结构体或是枚举类型的定义中,添加一个或多个可失败构造器。其语法为在
可失败构造器,在构建对象的过程中,创建一个其自身类型为可选类型的对象。你通过
下例中,定义了一个名为 struct Animal {
let species: String
init?(species: String) {
if species.isEmpty { return nil }
self.species = species
}
}
你可以通过该可失败构造器来构建一个Animal的对象,并检查其构建过程是否成功。 let someCreature = Animal(species: "Giraffe")
// someCreature 的类型是 Animal? 而不是 Animal
if let giraffe = someCreature {
println("An animal was initialized with a species of (giraffe.species)")
}
// 打印 "An animal was initialized with a species of Giraffe"
如果你给该可失败构造器传入一个空字符串作为其参数,则该可失败构造器失败。 let anonymousCreature = Animal(species: "")
// anonymousCreature 的类型是 Animal?,而不是 Animal
if anonymousCreature == nil {
println("The anonymous creature could not be initialized")
}
// 打印 "The anonymous creature could not be initialized"
枚举类型的可失败构造器你可以通过构造一个带一个或多个参数的可失败构造器来获取枚举类型中特定的枚举成员。还能在参数不满足你所期望的条件时,导致构造失败。 下例中,定义了一个名为TemperatureUnit的枚举类型。其中包含了三个可能的枚举成员( enum TemperatureUnit {
case Kelvin,Celsius,Fahrenheit
init?(symbol: Character) {
switch symbol {
case "K":
self = .Kelvin
case "C":
self = .Celsius
case "F":
self = .Fahrenheit
default:
return nil
}
}
}
你可以通过给该可失败构造器传递合适的参数来获取这三个枚举成员中相匹配的其中一个枚举成员。当参数的值不能与任意一枚举成员相匹配时,该枚举类型的构建过程失败: let fahrenheitUnit = TemperatureUnit(symbol: "F")
if fahrenheitUnit != nil {
println("This is a defined temperature unit,so initialization succeeded.")
}
// 打印 "This is a defined temperature unit,so initialization succeeded."
let unknownUnit = TemperatureUnit(symbol: "X")
if unknownUnit == nil {
println("This is not a defined temperature unit,so initialization failed.")
}
// 打印 "This is not a defined temperature unit,so initialization failed."
带原始值的枚举类型的可失败构造器带原始值的枚举类型会自带一个可失败构造器 因此上面的 TemperatureUnit的例子可以重写为: enum TemperatureUnit: Character {
case Kelvin = "K",Celsius = "C",Fahrenheit = "F"
}
let fahrenheitUnit = TemperatureUnit(rawValue: "F")
if fahrenheitUnit != nil {
println("This is a defined temperature unit,so initialization succeeded.")
}
// prints "This is a defined temperature unit,so initialization succeeded."
let unknownUnit = TemperatureUnit(rawValue: "X")
if unknownUnit == nil {
println("This is not a defined temperature unit,so initialization failed.")
}
// prints "This is not a defined temperature unit,so initialization failed."
类的可失败构造器值类型(如结构体或枚举类型)的可失败构造器,对何时何地触发构造失败这个行为没有任何的限制。比如在前面的例子中,结构体 下例子中,定义了一个名为 class Product {
let name: String!
init?(name: String) {
if name.isEmpty { return nil }
self.name = name
}
}
因为 if let bowTie = Product(name: "bow tie") {
// 不需要检查 bowTie.name == nil
println("The product's name is (bowTie.name)")
}
// 打印 "The product's name is bow tie"
构造失败的传递可失败构造器同样满足在构造器链中所描述的构造规则。其允许在同一类,结构体和枚举中横向代理其他的可失败构造器。类似的,子类的可失败构造器也能向上代理基类的可失败构造器。 无论是向上代理还是横向代理,如果你代理的可失败构造器,在构造过程中触发了构造失败的行为,整个构造过程都将被立即终止,接下来任何的构造代码都将不会被执行。
下面这个例子,定义了一个名为 class CartItem: Product {
let quantity: Int!
init?(name: String,quantity: Int) {
super.init(name: name)
if quantity < 1 { return nil }
self.quantity = quantity
}
}
和 可失败构造器总是先向上代理调用基类, 如果由于 如果你构造了一个 if let twoSocks = CartItem(name: "sock",quantity: 2) {
println("Item: (twoSocks.name),quantity: (twoSocks.quantity)")
}
// 打印 "Item: sock,quantity: 2"
如果你构造一个 if let zeroShirts = CartItem(name: "shirt",quantity: 0) {
println("Item: (zeroShirts.name),quantity: (zeroShirts.quantity)")
} else {
println("Unable to initialize zero shirts")
}
// 打印 "Unable to initialize zero shirts"
类似的,如果你构造一个 if let oneUnnamed = CartItem(name: "",quantity: 1) {
println("Item: (oneUnnamed.name),quantity: (oneUnnamed.quantity)")
} else {
println("Unable to initialize one unnamed product")
}
// 打印 "Unable to initialize one unnamed product"
覆盖一个可失败构造器就如同其它构造器一样,你也可以用子类的可失败构造器覆盖基类的可失败构造器。或者你也可以用子类的非可失败构造器覆盖一个基类的可失败构造器。这样做的好处是,即使基类的构造器为可失败构造器,但当子类的构造器在构造过程不可能失败时,我们也可以把它修改过来。 注意当你用一个子类的非可失败构造器覆盖了一个父类的可失败构造器时,子类的构造器将不再能向上代理父类的可失败构造器。一个非可失败的构造器永远也不能代理调用一个可失败构造器。
下例定义了一个名为 class Document {
var name: String?
// 该构造器构建了一个name属性值为nil的document对象
init() {}
// 该构造器构建了一个name属性值为非空字符串的document对象
init?(name: String) {
if name.isEmpty { return nil }
self.name = name
}
}
下面这个例子,定义了一个名为 class AutomaticallyNamedDocument: Document {
override init() {
super.init()
self.name = "[Untitled]"
}
override init(name: String) {
super.init()
if name.isEmpty {
self.name = "[Untitled]"
} else {
self.name = name
}
}
}
###可失败构造器 init! 通常来说我们通过在```init```关键字后添加问号的方式来定义一个可失败构造器,但你也可以使用通过在```init```后面添加惊叹号的方式来定义一个可失败构造器```(init!)```,该可失败构造器将会构建一个特定类型的隐式解析可选类型的对象。 你可以在 ```init?```构造器中代理调用 ```init!```构造器,反之亦然。 你也可以用 ```init?```覆盖 ```init!```,反之亦然。 你还可以用 ```init```代理调用```init!```,但这会触发一个断言:是否 ```init!```构造器会触发构造失败? <a name="required_initializers"></a> ##必要构造器 在类的构造器前添加```required```修饰符表明所有该类的子类都必须实现该构造器: ```swift class SomeClass { required init() { // 在这里添加该必要构造器的实现代码 } } 当子类覆盖基类的必要构造器时,必须在子类的构造器前同样添加 class SomeSubclass: SomeClass {
required init() {
// 在这里添加子类必要构造器的实现代码
}
}
通过闭包和函数来设置属性的默认值如果某个存储型属性的默认值需要特别的定制或准备,你就可以使用闭包或全局函数来为其属性提供定制的默认值。每当某个属性所属的新类型实例创建时,对应的闭包或函数会被调用,而它们的返回值会当做默认值赋值给这个属性。 这种类型的闭包或函数一般会创建一个跟属性类型相同的临时变量,然后修改它的值以满足预期的初始状态,最后将这个临时变量的值作为属性的默认值进行返回。 下面列举了闭包如何提供默认值的代码概要: class SomeClass {
let someProperty: SomeType = {
// 在这个闭包中给 someProperty 创建一个默认值
// someValue 必须和 SomeType 类型相同
return someValue
}()
}
注意闭包结尾的大括号后面接了一对空的小括号。这是用来告诉 Swift 需要立刻执行此闭包。如果你忽略了这对括号,相当于是将闭包本身作为值赋值给了属性,而不是将闭包的返回值赋值给属性。
下面例子中定义了一个结构体 西洋跳棋游戏在一副黑白格交替的 10x10 的棋盘中进行。为了呈现这副游戏棋盘,
struct Checkerboard {
let boardColors: [Bool] = {
var temporaryBoard = [Bool]()
var isBlack = false
for i in 1...10 {
for j in 1...10 {
temporaryBoard.append(isBlack)
isBlack = !isBlack
}
isBlack = !isBlack
}
return temporaryBoard
}()
func squareIsBlackAtRow(row: Int,column: Int) -> Bool {
return boardColors[(row * 10) + column]
}
}
每当一个新的 let board = Checkerboard()
println(board.squareIsBlackAtRow(0,column: 1))
// 输出 "true"
println(board.squareIsBlackAtRow(9,column: 9))
// 输出 "false" (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |