-
Notifications
You must be signed in to change notification settings - Fork 97
Chapter 6
For optimization considerations, Berry did not consider simple types as
objects. These simple types include nil
types, numeric types, boolean
types, and string types. But Berry provides classes to implement the
object mechanism. Among Berry’s basic data types, list
, map
and
range
are class objects. An object is a collection containing data and
methods, where data is composed of some variables, and methods are
functions. The type of an object is called a class, and the entity of an
object is called an instance.
To use a class, you must first declare it. The declaration of a class
starts with the keyword class
. The member variables and methods of the
class must be specified in the declaration. This is an example of
declaring a class:
class person
static majority = 18
var name, age
def init(name, age)
self.name = name
self.age = age
end
def tostring()
return'name: '+ str(self.name) +', age:' + str(self.age)
end
def isadult()
return self.age >= self.majority
end
end
Class member variables are declared with keyword var
, while member
methods are declared with keyword def
. Currently, Berry does not
support initializing member variables at the time of definition, so the
initialization of member variables should be done by the constructor.
The properties of the class cannot be modified after the declaration is
completed, so the class is a read-only object [1].
Berry’s class does not support access restrictions, and all properties of the class are visible to the outside. In native classes, you can use some tricks to make properties invisible to Berry code (usually let the member name start with dot "."). You can use some conventions to restrict access to the members of the class, such as the convention that the attributes starting with an underscore are private attributes. This convention does not have any use at the grammatical level, but is conducive to the logical structure of the code.
The class itself is just an abstract description. Taking cars as an example, I know the concept of cars, and when we really want to use cars, we need real cars. The use of classes is similar. We will not only use this abstract description, but need to produce a concrete object based on this description. This process is called Instantiation of the class, abbreviated as instantiation, and the concrete object produced by instantiation is called Instance. The class itself does not have data, and instantiation produces an instance based on the information described by the class and gives the instance specific data.
Class methods are essentially functions. Unlike ordinary functions,
methods implicitly pass in a self
parameter, and is always the first
parameter, which stores a reference to the current instance. Due to the
existence of self
parameters, the number of parameters of the method
will be one more than the number of parameters defined in the
declaration. Here we use a simple example to demonstrate:
class Test
def method()
return self
end
end
object = Test()
print(object)
print(object.method())
This example defines a Test
class, which has a method
method, which
returns its self
parameter. The last two lines in the routine print
the value of the instance object
of the Test
class and the return
value of the method method
respectively. The running result of this
example is [2]
<instance: 00E880D4>
<instance: 00E880D4>
It can be seen that the self
parameter of the method and the name of
the use instance (object
in the example) both represent the same
object, and they are both instance references. Use self
to access the
members or attributes of the instance in the method.
TODO
The constructor of the class is the init
method. The constructor is
called when the class is instantiated. Therefore, the constructor is
generally used for member initialization, for example:
class Test
var a
def init()
self.a ='this is a test'
end
end
The constructor in this example initializes the a
member of the Test
class to the string ’this is a test’
. If we instantiate the class, we
can get the value of member a
:
print(Test().a) # this is a test
The destructor of the class is the deinit
method. The destructor is
called when the instance is destroyed. The destructor is generally used
to complete some cleanup work. Because the garbage collection mechanism
automatically releases the memory of useless objects, there is no need
to release the memory in the destructor (and there is no way to release
the memory in the destructor). In most cases, there is no need to use a
destructor, unless a certain class requires certain processing when it
is destroyed. A typical example is that a file object must close the
file when it is destroyed.
Berry only supports single inheritance, that is, a class can only have
one base class, and the base class uses the operator :
to declare:
class Test: Base
...
end
Here the Test
class inherits from the Base
class. The subclass will
inherit all the methods and properties of the base class, and you can
override them in the subclass. This mechanism is called Overload.
Under normal circumstances, we will only overload methods, not
properties.
The inheritance mechanism of the Berry class is relatively simple. Subclasses will contain references to the base class, and instance objects are similar. When instantiating a class with a base class, multiple objects are actually generated. These objects will be chained together according to the inheritance relationship, and finally we will get the instance object at the end of the inheritance chain.
Overload means that the subclass and the base class use the same name method, and the subclass method will override the mechanism of the base class method. To be precise, member variables can also be overloaded, but this overloading has no meaning. Method overloading is divided into ordinary method overloading and operator overloading.
You can use the operator overloading of the class to make the instance support the operation of the built-in operator. For example, for a class overloaded with the addition operator, we can use the addition operator to perform operations on the instance. An overloaded operator is a method with a special name, and the overloaded function form of a binary operator is
´def’ operator ´(´ other ´)´
block
´end’
operato****r is an overloaded binary
operator. The left operand of the binary operator is the self
object,
and the right operand is the value of the parameter
other. The overloaded function form of the unary
operator is
´def’ operator ´()´
block
´end’
operatorr is an overloaded unary
operator. To distinguish it from the subtraction operator, the unary
minus sign is written as -*
when overloaded. Operator overloaded
functions should have a return value, because the default nil
return
value is usually not the expected result. Let’s take an integer class as
an example to illustrate the use of operator overloading. First define
the integer
class:
class integer
var value
def init(v)
self.value = v
end
def +(other)
return integer(self.value + other.value)
end
def *(other)
return integer(self.value * other.value)
end
def -*()
return integer(-self.value)
end
def tostring(other)
return str(self.value)
end
end
The integer
class overloads the plus, multiplication, and symbolic
operators, and the tostring
method is to make the instance use the
print
function to output the result. We can use a simple line of code
to test the operator overloading function of the class:
integer(1) + integer(2) * -integer(3) # -5
The result of this line of code is an instance of integer
. The value
of the value
member of this instance is -5
, which is the same as the
result of the same four arithmetic operations on integers.
Logical operators cannot be overloaded directly. If you need an instance
to support logical operations, you must implement the tobool
method.
The method has no parameters and the return value must be of Boolean
type. The logic operation of the instance is actually realized by
converting the instance into a Boolean value, so the logic operation of
the instance is completely in line with the nature of the general logic
operation. The subscript operator is not directly overloaded, but is
implemented by the methods item
and setitem
. item
The method is
used for subscript reading, its first parameter is the subscript value,
and the return value is the result of the subscript operation; setitem
is used for subscript writing, and its first parameter is the subscript
Value, the second parameter is the value to be written, this method does
not use the return value.
The overloaded operator can be assigned any meaning, even not satisfying the usual properties of operators. Considering the versatility of the code and the difficulty of understanding, it is not recommended that users give overloaded operators a function far from the general meaning.
The compound assignment operator cannot be directly overloaded, but we
can achieve the purpose of "overloading" the compound assignment
operator by overloading the binary operator corresponding to the
compound assignment operator. For example, after overloading the “+
”
operator, you can use the “+=
” operator for instances of related
classes. It is worth noting that the use of compound assignment
operations on the instance will cause the variables of the bound
instance to lose their reference to the instance.
class integer
var value
def init(x)
self.value = x
end
def +(other)
return integer(self.value + other.value)
end
end
a = integer(4) # a: <instance: 0x55edff400a78>
a += integer(5) # a: <instance: 0x55edff4011b8>
print(a.value) # 9
After the 11th line of code is executed, the instance bound in the
variable a
has actually changed. This line of code is equivalent to
a = integer(4) + integer(5)
. If the binary operator of the class
overload does not modify the state of the instance, then the
corresponding compound assignment operator will not modify any instance
(it may generate new instances).
Instance is an object generated after class instantiation. A class can be instantiated multiple times to generate different instances. Berry instances are referenced by the class they belong to and the corresponding data fields. All instances of a class will refer to this class, but the data fields of these instances are independent of each other.
The built-in function super
is used to access base class objects.
TODO: explain how super()
works with inheritance and added parameters to super()
. See #108
[1] This design is to ensure that the class can be statically
constructed in the C language when the interpreter is implemented and
the const
property can be used Modified to save RAM
[2] Because the instance objects are dynamically allocated and their memory addresses are random, the result of the reader running this code may be different from here.