clos 类的定义与访问

一:类的定义

DEFCLASS

(defclass name (direct-superclass-name) (slot-specifier*))

二:对象初始化与槽描述符:

Make-instance的参数是想要实例化的类的名字,而返回值就是新的对象。

Slot-specifier 槽描述符也就是我们平常说的成员变量。

槽描述符中的一些选项:

:initarg  即initial argument,指定随后make-instance用作形参时的名字

:initform 给你提供一个默认值。

Slot-value 读取槽的值,然后跟setf合用来设置槽的值。

CL-USER> (defclass bank-account()
	   		 (customer-name
	   		 balance))
#<STANDARD-CLASS BANK-ACCOUNT>
CL-USER> (make-instance 'bank-account)
#<BANK-ACCOUNT {24F862F9}>
CL-USER> (defparameter *account* (make-instance 'bank-account))
*ACCOUNT*
CL-USER> (setf (slot-value *account* 'customer-name) "Joe")
"Joe"
CL-USER> (slot-value *account* 'customer-name)
"Joe"

三:对象初始化:

当有参数描述的时候,整个槽就会被括号包含起来。否则不用如account-type

CL-USER> (defclass bank-account()

   ((customer-name

       :initarg :customer-name

       :initform (error "must supply a customer name"))

    (balance

     :initarg :balance

    :initform 0)

       account-type))

现在你想定义account-type为gold,silver,bronze,这个时候你就要考虑到底什么时候给他初始化这些值了,因为我们只能给一个具体的对象赋予具体什么值,但是我们无法访问一个正在初始化的对象,因此不能基于一个槽的值来初始化另一个槽的值。

基于:initarg选项,standard-object上特化的initial-instance主方法负责槽的初始化,所以你最好添加一个:after方法。

四:访问槽

Slot-value我们是用来访问对象中的位置。

CL-USER> (setf (slot-value *account* 'customer-name) "Joe")
"Joe"
CL-USER> (slot-value *account* 'customer-name)
"Joe"

但是这就有个问题了,假设一个程序中多处用到'customer-name,你不能说每次都是像上面这样把slot-value每一次都输入。

于是就有了下面的扩展setf的方式。你也许回想的是下面这种函数,用defun来定义。如果是这样的话,你最好能够定义一个广义函数的形式,这样就为子类提供了不同的方法或附加方法。

CL-USER> (defun (setf customer-name)(name account)
	        (setf (slot-value account 'customer-name) name))
(SETF CUSTOMER-NAME)
CL-USER> (setf (customer-name *account*) "sall") 
"sall"

CL-USER> (defun customer-name(account)
	        (slot-value account 'customer-name))
CL-USER> (customer-name *account*)
"sall"

广义函数版:

CL-USER> (defgeneric (setf customer-name)(value account))
CL-USER> (defmethod (setf customer)(value (account bank-account))
	   		(setf (slot-value account 'customer-name)value))

CL-USER> (defgeneric customer-name(account))
CL-USER> (defmethod customer-name((account bank-account))
	  		 (slot-value account 'customer-name))

疑问:为啥扩展setf可以写成(setf (customer-name accout) value)?

Setf 扩展定义了一种新的位置类型使其知道如何设置它,setf函数的名字是一个两元素列表,其第一个元素是符号setf而第二个元素是一个符号,通常是一个用来访问该setf函数将要设置的位置的函数名。Setf函数可以接受任何数量的参数,但第一个参数总是复制到位置上的值。

为了省去你每次都需要重新定义这几个函数,程序又提供了三个槽选项:reader/writer/accessor它与寻你为一个特定的槽自动创建读取和写入函数。

具体应用如下。

(defclass bank-account5()
	   ((customer-name
	     :initarg :customer-name
	     :initform 0	     
         :reader customer-name
	     :writer (setf customer-name))
	    (balance
	     :initarg :balance
	     :initform 0)
	    account-type))
#<STANDARD-CLASS BANK-ACCOUNT5>
CL-USER> (defparameter *a* (make-instance 'bank-account5))  *A*
CL-USER> (setf (customer-name *A*) "fd")                    "fd"
CL-USER> (customer-name *A*)                                "fd"

Compare :reader/:writer with :accessor,we could get just the first half of this behavior, or just the second.

With-slots和with-accessors

现在我们就有两种形式来访问槽了。

(defun assess-low-balance-penalty (account)
	   (when (< (balance account) 99992000)
	     (decf (slot-value account 'balance)(* (balance account) .01))))
(defun assess-low-balance-penalty (account)
	   (when (< (slot-value account 'balance) 99992000)
	     (decf (slot-value account 'balance)(* (balance account) .01))))

你会发现decf用的形式不变,应该记得以前说过decf是setf的修改宏,我感觉因为我们已经扩展了setf函数,那么同样适用于decf吧,但是最后你会发现他是不适用的,所以decf都是最古老的方式来访问的。

WITH-SLOTS的基本形式如下:

(With-slots (slot*) instance-form 

Body-form*)

每一个slot元素可以使一个槽的名字,也可以是一个变量名。

或者一个两元素列表,第一个元素一个用作变量的名字,它的作用如下为了区分当对不同对象的同一个槽进行的处理,第二个元素则是对应槽的名字。

CL-USER> (defmethod assess-low-balance-penalty ((account bank-account))
	   		(with-slots (balance) account
	    	 (when (< balance *minimum-balance*)
	      	 (decf balance (* balance .01)))))

CL-USER>(defmethod merge-account((account1bank-account)(account2 bank-account))
	   		(with-accessors ((balance1 balance)) account1   两元素列表
	    	  with-accessors (customer-name balance-type) account2  slot槽集
	     	    (incf balance 1)))

五:分配在类上的槽。

:allocation,它的值可以使:instance:class,字面上理解的话,一个是实例,另一个是类,也就是说当是:instance的时候,在每一次创建实例的时候你都需要创建一个它,但是如果是:class的话,说明这个槽是类的,只有单一值存储在类中并且被所有实例所共享。

可以通过slot-value或函数来访问该槽的值,尽管他不保存在任何一个实例中。:initform

:initarg仍可以使用,只是initform在类的定义的时候就需要创建。并且你可以通过创建实例make-instance调用:initarg来改变这个值,从而影响了所有实例。

we would want to use shared slots tocontain properties that all the instances would have in common.We do this by declaring the slot to have :allocation :class.When we change the value of such a slot in one instance, that slot will get the same value in every other instance.

注意点:

我本来以为initarg 就相当于一个槽的别名呢,可以再任何位置对于这个别名的访问就相当于直接对槽的访问,但实际上他就只是在make-instance 的时候可以等价于它所指的槽,其他地方如果你用这个initarg在访问的话,他会报下面的问题。

现在我感觉有个问题就是既然initarg不能够在除make-instance 的地方访问,那么他到底有什么用,难道仅仅是为了在初始化的时候,把你想要设置为一样值的参数设置为同一个initarg关键字,实现初始化方便吗?

CL-USER> (defclass initarg-test()
	   ((a :initarg :a-1 :accessor a-a)))
#<STANDARD-CLASS INITARG-TEST>
CL-USER> (defparameter *hh* (make-instance 'initarg-test :a-1 "bb"))  *HH*
CL-USER> (a-a *hh*)                                                   "bb"
CL-USER> (slot-value *hh* 'a)                                         "bb"

2::accessor customer-name,形式要注意,他后面不是关键字,也不是引用名,同时更不是一个string。

其实你完全可以用关键字来替换:the-b,因为下面的plist形式你应该清楚,后面是对前面的赋值。而本身:accessor后面跟的是一个能够接受函数对象的参数。你可以让:the-b来接收,但是一般plist的话,他后面接受的是一个自求值对象,因为他后面后面还可以指向另一个对象。而如果你用一个关键字指向一个函数相当于这个plist就此结束了(第二种结构不推荐),实际上无可非议,但是你得这个功能完全就是可以让the-b这个变量来实现的。所以最好还是用一个变量而不是关键字。

clos 类的定义与访问_第1张图片
CL-USER> (defclass par()
	  		 ((b :initarg :b :initform "b" :accessor :the-b)
	   		 (a :initarg :b :initform "a" :accessor the-b)))
#<STANDARD-CLASS PAR>
CL-USER> (defparameter *jj* (make-instance 'par))  *JJ*
CL-USER> (:the-b *jj*)                             "b"

然后再说为啥:initarg的值还为关键字:b,现在你应该明确一点除了全局变量跟自求值以外,能够被赋值的也就是变量跟关键字了,现在咱们说如果不是:b 而是b,会有什么问题。执行表达式:(defparameter *hh* (make-instance kk a "me"))会报A is unbound.,你应该明白的就是lisp的每一步都是必须得有值的,现在说为啥在defclass的时候他没有报b is unbound.我感觉他应该对定义的时候不检查他,但同样accessor后也是一个变量,为啥(the-b *jj*)不报错呢?我感觉究其原因应该是在定义对象的时候,他没有检查关键字后面的对象的是否bound,而像b就是落网之鱼,而the-b在defclass的时候已经bound好了,他指向一个函数,所以可以用,而现在b出门就被卡,所以我说这个应该是个漏洞。因为你即使defclass可以,但是不能继续接着操作,或者干脆是像the-b一样,表面上是unbound,实际上内部一些逻辑已经实现让他绑定一个对象了。

总结一下就是b在defclass的时候之所以可以过,关键是看在了前面是关键字的面子,并且它是在定义的时候处理的。比方说你执行(defparameter *hh* (make-instance 'kk :a b))他通用报B is unbound。所以单纯说因为关键字后面的东西不查值是不对的。

我怀疑这个为啥是:initarg而不是initarg,主要也是因为如果是initarg他会说unbound.

CL-USER> (defclass kk()
	  		 ((b :initarg b :initform "b" :accessor :the-b)
	   		 (a :initarg a :initform "a" :accessor the-b)))
#<STANDARD-CLASS KK>
CL-USER> (defparameter *hh* (make-instance kk :a "me"))


你可能感兴趣的:(clos 类的定义与访问)