【问题标题】:Swift generic constructorSwift 泛型构造函数
【发布时间】:2015-06-15 23:13:31
【问题描述】:

问题嵌入在 cmets 以及此处:

  1. 如何在参数列表中为泛型 T 定义默认值?
  2. 在 Swift 中复制参数时,是指针还是对象?
    • XCode Beta 7 告诉我无法更改“head”,因为它是“let”,请参阅代码
  3. 任何类似 Swift 的方法来循环检查 nil 以避免展开?

链表实现:

public class Node<T>
{
    public var data: T
    public var next: Node<T>?

    //1.
    //How can I define a default datatype for T?
    //In C++ I would let T ctor do it: "data: T = T()" gives 
    //error: 'T' cannot be constructed because it has no accessible initializers
    public init(data: T, next: Node<T>?)
    {
        self.data = data
        self.next = next
    }
}

func print<T>(head: Node<T>)
{
    var tmp = head  //2. Is this a copy of a pointer or an object?
    print(tmp.data)
    while(tmp.next != nil)  //3. Any Swiftier way to do it?
    {
        tmp = tmp.next!
        print(tmp.data)
    }
}

func insert<T>(head: Node<T>, _ value: T)
{
    var tmp = head
    while tmp.next != nil
    {
        tmp = tmp.next!
    }
    tmp.next = Node<T>(data: value, next: nil)
}

var head = Node<Int>(data: 1, next: nil)
insert(head, 2)
insert(head, 4)
insert(head, 8)
insert(head, 16)
print(head)

还有,还有其他的cmets吗?我对 Swift 很陌生

【问题讨论】:

    标签: swift generics


    【解决方案1】:

    1.

    泛型是关于代码安全的,而不知道编译时的类型。这意味着将泛型类型初始化为某种东西是没有意义的。但是通过声明一个像

    这样的协议
    protocol Initializable {
        init()
    }
    

    ,通过做扩展你想要使用的类型

    extension Int : Initializable {}
    

    (也许自己实现init() 方法)并声明你的节点

    public class Node<T: Initializable>
    

    您可以在初始化程序中使用T() 来创建初始值。因为现在编译器知道这个类型确实有这样一个初始化器,因为它符合那个协议。

    2.

    总是作为引用传递,而结构总是复制

    为了能够更改函数的输入值,必须将其声明为 inout 之类的

    func insert<T>(inout head: Node<T>, _ value: T)
    

    那你还需要调用insert(&amp;head, 2)这样的函数

    3.

    你可以这样写循环

    while let next = tmp.next {
        tmp = next
        print(tmp.data)
    }
    

    4.

    Swift 是一门优美的语言,我强烈建议您总体上使用更多 Swiftier 代码。

    您编写这 2 个方法的方式就像在 C 中一样。您可以将函数放在类中,这是最佳方式,而且也更简单。

    我承认不是很重要,但是这种大括号样式已经过时了

    如果您真的不需要函数,则无需将函数声明为 public。只有在编写框架或类似的东西时才需要它。默认行为(无访问控制修饰符)是可以访问整个项目中的所有 Swift 文件。

    我在这里创建的这个Initializable 协议也不是真正的 Swifty

    while (boolean) 周围的括号不是必需的,也不是 Swifty

    命名参数的存在是有原因的:使代码更具可读性。仅当您绝对确定它很烦人并且清楚参数代表什么时才删除它们。

    我最初并不想这样做,但我以更 Swifty 的方式重新编写了您的代码,希望可以帮助您:

    protocol DefaultValuable {
        static func defaultValue() -> Self
    }
    
    extension Int : DefaultValuable {
        static func defaultValue() -> Int {
            return 1
        }
    }
    
    class Node<T: DefaultValuable> {
        var data: T
        var next: Node<T>?
    
        init(data: T = T.defaultValue(), next: Node<T>? = nil) {
            self.data = data
            self.next = next
        }
    
        func printNode() {
            var tmp = self
            print(tmp.data)
    
            while let next = tmp.next {
                tmp = next
                print(tmp.data)
            }
        }
    
        func insert(value: T) {
            var tmp = self
    
            while let next = tmp.next {
                tmp = next
            }
    
            tmp.next = Node<T>(data: value)
        }
    }
    
    let head = Node<Int>()
    head.insert(2)
    head.insert(4)
    head.insert(8)
    head.insert(16)
    head.printNode()
    

    (抱歉有点啰嗦)

    【讨论】:

    • 哇。非常感谢您的提示。我对所有的cmets都没有问题。谢谢!
    【解决方案2】:
    1. 在 Swift 中你不能这样做。您可以将data 设为可选,或将init 重载为特定类型,但self.data = T() 或类似的内容是不可能的。

    2. 在 Swift 中,由类创建的对象的变量始终是引用类型,因此基本上是指针。

    3. 你的print 函数可以写得更好:

      func print<T>(head: Node<T>)
      {
          var tmp: Node<T>? = head
          while let currentNode = tmp
          {
              print(currentNode.data)
              tmp = tmp?.next
          }
      }
      

    【讨论】:

      【解决方案3】:

      我认为带有勾选标记的答案是迄今为止最好的和详细的答案。

      我要补充的是我对泛型的看法。

      如果我正确理解了 Swift 中值、类型和类型构造函数之间的关系。

      首先,我认为需要执行或操作的所有内容都是值,例如1,2,3"I am a string" 等等。

      其次,类型是值的类型。有两种类型,Reference TypeValue TypeReference Type 包含class,表示这些值是由class 创建的,而Value Type 包含StructEnum。此外,我认为Functionclosure 也是引用类型,即使它们在创建后是不可变的。 protocol 怎么样,我的想法是protocol 是一种类型,即使有时protocol 不能用作类型,而protocol 内部使用了一些associatedtypeSelf,然后是protocol只能在Generic中用作Type Constraint

      最后一个是Generic,它是需要generic parameters的类型构造函数,具体类型,意思是GenericArray&lt;Element&gt;Elementgeneric parameterGeneric数组,用于创建一个具体typeArray&lt;Int&gt;,然后依次创建消耗品value[1,2,3,4]

      因此,如果您想将Generic 用作普通的type,如某些类、NSStringUILabel 左右,则必须提供类型为generic parameters 以预先派生具体类型。

      更新

      首先,我对我关于协议作为一种类型的声明表示歉意。这是不正确的。是的,协议可以表现得像一个类型。但实际上它是一个抽象类型的接口,描述了符合者应该实现的一组行为。当协议内部使用了一些associatedtype和/或Self constraint时,它获得了一些通用特性,使其更适合Type Constraintwhere,附加条件,并且有力地扩展了它的扩展默认实现.

      在一天结束时,我将protocol 视为一个包装器,它没有自己的实例或值,但包装了它的conformer 类型的值,此外,protocol extension 还提供默认行为。

      【讨论】:

        猜你喜欢
        • 2010-10-16
        • 1970-01-01
        • 2013-10-13
        • 2019-02-20
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多