Home Previous Next

CSC240 :: Lecture Note :: Week 08
Assignments | Code | Handouts | Resources | Email Thurman {Twitter::@compufoo Facebook::CSzero}
{GDT::Bits:: Time  |  Weather  |  Populations  |  Special Dates}

Overview

Assignment(s): #midterm (due 11/11/2017)

Code: bitfields.cpp | directio.c | stats2.cpp | stats3.cpp | stats4.cpp | string.cpp | string2.cpp
[Wednesday] {TestCard.cpp | Card.cpp | Card.h} | FunctionTemplate.cpp | ClassTemplate.cpp


Classes

A  class  specifies the data and behavior of objects.

A class can contain data variables call  member variables  and it can contain  member functions . The member functions describe the behavior of class objects.

Class variables and functions have  access specifiers  associated with them -- the are:

The public member functions in a class form an interface to the class's private variables and functions. This interface is often called the class's  application programming interface  (or API).

Notes about classes:

The contents of private and public parts of a class are normally:
   private area:
      data variables
      utility functions

   public area:
      methods (functions)
      operator overloads
      data variables (poor OOD style)
Typical data types found in class definitions are: An example class:
   class Student {
      enum { NAME_LEN = 64 };
      enum { FROSH, SOPH, JUNIOR, SENIOR };
      private:
         char name[NAME_LEN];
         int major;
         float gpa;
      public:
         void printStudent();
         char* getName();
         void setMajor(int);
   };
After a class has been defined, objects (i.e. variables) of that class data type can be defined. A C++ object is a variable (or  instance ) of some class data type.

The following code snippet defines (i.e. instantiates) some objects of type  Student :

   Student joe;   //an automatic object
   Student* mary = new Student();  //dynamic object
   Student mike = joe;  //mike is a bit-wise copy of joe
A  class  is like a  struct  with the following exception: in a class, data members and methods are private by default; whereas in a struct they are public.

{TopOfPage} {Resources}


Accesser and Mutator Methods

Good OOP practice is to keep data members of class private. Access to private data is granted to the client by providing a set of methods that are called  accesser  and  mutator .

Accesser methods (also referred to as  getter  methods) are used to get the value of private data members.

Mutator methods (also called  setter ) methods are used to modify private data members.

Naming convention is to prefix accesser methods with the word "get" and mutator methods with "set."

The creator of the class determines access to the private data members by defining (or not defining) accesser/mutator methods.

These methods help ensure encapsulation and data hiding. The client does not need to know the internal representation of the data accessed and/or modified via the accesser/mutator methods.

   class Date {
      private int m, d, y;
      public int getM() const;
      public int getD() const;
      public int getY() const;
      public void setM(int);
      public void setD(int);
      public void setY(int);
   };
Accesser methods should be declared to be  const . Constant methods do not change the state of an object (i.e. do not modify the object's instance variables).

{TopOfPage} {Resources}


The this Pointer

Every non-static member function is passed a pointer to the receiving (or target) object. The pointer is obtained via the  this  keyword.

Said another way: In a non-static member function, the keyword  this  is a pointer to the object for which the function was invoked.

   class X {
      int x, y, z;
      public void setXYZ(int a, int b, int c) {
         this->x = a;
         this->y = b;
         z = c;
      }
   };
In a non-const member function of class X, the type of  this  is  X* const  which prevents the user from changing the value of  this  . In a const member function, the type of of  this  is  const X* const  to prevent modifications of the object itself.

{TopOfPage} {Resources}


Constructors

A class method that has the same name as the class is a special method called a  constructor .

{TopOfPage} {Resources}


Constructor Member Initialization Lists

A member initialization list starts with a colon (:) and consists of a comma-separated list of member name and argument value pairs. The following rules apply: The member initialization lists have the following format:
   member_name(EXPR list)
A member initializer list is required when a non-static member is either a  const  or a reference.
   class Date {
      private:
         const int maxYear;
         int& curYear;
         int mon, day, year;
      public: 
         Date(int m, int d, int y, int max, int cur) : 
              maxYear(max), curYear(cur), mon(m), day(d), year(y) {
            //! maxYear = max;   // illegal
            //! curYear = cur;   // illegal
         }
   };
Array member variables cannot be set via the member initialization list.

Style: the member initialization list should be used as much as possible to set member variable values.

When members are themselves classes with constructors, the EXPR list is matched to the appropriate constructor signature to invoke the correct overloaded constructor. (This was seen when we covered composition and it will be seem in more detail when we study inheritance.)

   class Name {
      private:
         char name[64];
      public:
         Name(const char* n) { strcpy(name, n); }
      ...
   };

   class Address {
      private:
         char addr[64];
      public:
         Address(const char* a) { strcpy(addr, a); }
   };

   class Student {
      private:
         long id;
         Name name;
         Address address;
      public:
         //! Student(long i) : name("???"), address("???"), id(i);
             // not legal because the member initialization list must 
             // be specified with the constructor definition, not its
             // declaration
         
         Student(const* char n, const* char addr, long i) :
                 name(n), address(addr), id(i) {}
             // in many cases, the use a member initialization list
             // will result in an empty function body
      ...
   };

   Student joe("John Doe", "111 N. 4th St., Tempe, AZ, 85283", 999);

{TopOfPage} {Resources}


Destructors

A class method that has the same name as the class and has a tilde (~) prefixed to it is a special method called a  destructor .

When an object is created or during its lifetime it may obtain resources that must be deallocated upon destruction of the object. If this is the case, then the class should contain a destructor. (Example resources are: a file, a lock, some memory.)

{TopOfPage} {Resources}


Friend Functions

The keyword  friend  is a function specifier. It allows a non-member function access to the private members of the class of which it is a friend. Example:
   class Foo {
      friend void Bar::someMethod(Foo* someFooObject);
      friend bool isVowel(Foo* someFooObject);

      private: 
         int i;
         char c;
   };

      //someMethod()  is member function of class  Bar
   void Bar::someMethod(Foo* f) {
      f->i = 100;  //is legal since  someMethod()  is a friend of  Foo
      ...
   }

      //isVowel()  is a global function not affiliated with any class
   bool isVowel(Foo* f) {
      if (f->c == 'a') //is legal since  isVowel()  is a friend of  Foo
      ...
   }

      //isConsonant()  is a global function (doesn't belong to a class)
   bool isConsonant(Foo* f)
      if (f->c != 'a')  //is *not* legal -- isConsonant() is not a 
                        //friend of  Foo
      ...
   }
A friend function must be declared inside the class definition of which it is a friend.

Member functions of one class can be friend functions of another class. Globally defined functions can also be friend functions.

It doesn't matter if friend functions are declared in private or public.

If all member functions of one class are friend functions of a second class, then this can be specified by writing  friend class SomeClassName .

Friend functions are commonly used for operator overloading.

{TopOfPage} {Resources}


Operator Overloading

Operator overloading enables the definition of C++ operators to be extended so that they operate on objects.

Operating overloading can lead to more intuitive and readable code. For example, in C string concatenation is accomplished as follows:

   strcpy(str, s1);
   strcat(str, s2);
In C++, given a String class, string objects and operator overloadings, string concatentation becomes:
   s3 = s1 + s2

      The strings in objects  s1  and  s2  are concatenated
      using the overloaded  +  operator and then the resulting
      string is copied into the  s3  object using an  =  operator
      overload.
In general, operator overloads are regular functions of the form:
   return-type operator <symbol> (signature) { }

      where  operator  is a C++ keyword and  <symbol>  
      is the operator symbol (or word); whitespace between
      the keyword  operator  and the symbols is not needed
      unless the symbol is a word (e.g.  operator new)
All operators can be overloaded with the following exceptions:
   ::   class::   .   .*  sizeof  ?:   typeid  
   static_cast<>  dynamic_cast<>  reinterpret_cast<>  const_cast<>
All operator overloads can be either member functions (i.e. methods), friend functions or global functions. The following restriction applies:
 ()  =  ->  [] 
must be implemented as member functions.

Although operator overloads are functions, they are not allowed to have default arguments.

All operator precedences and associativities remain the same as for the primitive types; they cannot be altered and no new operators can be defined.

At least one operand of an operator overload must be an object. C++ does not allow the programmer to change the definition of operators that take primitive type operands.

Binary Operator Signatures

As a member function:

   return-type operator <symbol> (data-type rhs);
      
      The lhs (left hand side) is the target object  this.
      NOTE:  There are no automatic type conversions ever done
             on the target object. 

As a friend function:

   return-type operator <symbol> (data-type lhs, data-type rhs);

      NOTE:  keyword  friend  must be in befriending class's class 
             definition.
                  
As a non-friend global function:    
   Same as  friend  but limited to  public  access members.

Unary Operator Signatures

As a member function...

   return-type operator <symbol> ( ) 
   
      The lhs/rhs is the target object  this  .
      NOTE:  There are NO automatic type conversions ever done
             on the target object this.
      Obj++ 's member signature:  operator++ (int)
           

As a friend function...

   return-type operator <symbol> (const type& side)

      NOTE:  keyword friend must be in befriending classes class   
             definition.
      Obj++ 's non-member signature:  operator++ (const type& side, int)
                  

As a global function...    

   Same as friend but limited to public access methods.

Free Store Data Member Requirements

Equal operator overload must be provided to ensure that two objects do not have the same member space. You must remember to handle self-copy (Obj = Obj) anomolies:
   if (&rhs != this) {   
      delete member space (free())
      grab new space of correct size (new ...)
      copy in rhs.member -> new member space (copy(rhs))
   }
   return *this  (so can code o1 = o2 = o3 .... )

Copy constructor needs to be provided for same reason as equal overload. Remember,
   Class obj = obj1;  //Copy constructor
        
          and

   Class obj;
   obj = obj1;      //= overload

           should yield the same result.
Destructor to clean up member free-store space must be provided.

It is suggested that whenever there is free-store data members, copy() and free() should be provided as private member functions for use in copy constructor, destructor and overloaded operator=.

When copying arrays from TargetArray <- SourceArray, an element-wise copy needs to occur. For this you cannot use memcpy() since then you will have two arrays of objects where the i'th element in each array points to the same free-store member. When you delete[] the array space, each element's destructor will run and delete the free store space leaving you with an array of elements each with a pointer to free'd memory. Instead you need to use the overloaded operator= for the element so that new space is acquired properly.

Automatic Type Conversion Rules

One parameter constructors act as automatic type converters (promotions) for all object parameters in the signature.
    Rational(int numerator = 0, int denominator = 1);

    acts as a type converter so
        Rational + 1  ==>  Rational + Rational
        Print(1)      ==>  Print(Rational)
        
    These conversions can be coupled with primitive type conversions
    (implicit and standard).
Typecast overloads act as automatic type converters as well (demotions) and will yield results similar to those just described.

General Rules for Operator Overloads

  1. Only define operators to act in an intuitive way - like the way they work for the primitive types. Don't get carried away.
  2. If an operator modifies the target operand (e.g., ++), it should be a member operator.
  3. Operands whose lhs is an object that was "purchased" (e.g., cout << ) must be implemented as friends.
  4. All binary operators that are not covered by 2) are usually best done as friends to pick up the automatic type conversions and not suffer the additional function call overhead of public access limitations.
  5. One operand in the expression must be a user-defined class in order for signatures to match; you cannot overload primitive operator definitions.
  6. If you can have object reference returns, you should; it is always faster. Be careful NOT to return a reference to stack space that is about to be popped.

    Examples

    A  String  offers a good example of using overloaded operators.
       String s1("hello, world"), s2("goodbye");
       char ch;
    
    
       ch = s1[3];   // [] operator returns character at index 3 -- we
                     // not accessing the 4 string of an array of strings
       ch = s1.charAt(3);
    
       s1 >> 3;      // rotate the string 3 places to the right
       s1.rotate(3);
    
       s1 += 4;      // encrypt the string using 4 as the key 
       s1.encrypt(4);
    
       if (s1 > s2)...   // compare if s1 greater than s2
       if (s1.equals(s2) > 0)...
    
       s2 = s1++;  // ???
       s2 = --s1;  // ???
    

{TopOfPage} {Resources}


About private Class Member Functions

private  class member functions are used internally by the class. They can only be called by the  public  member functions. Typically, private methods are referred to as  utility  or  helper  methods.

{TopOfPage} {Resources}


const Member Functions

If a member function is declared and/or defined to be  const  , then that implies it does not modify the state of an object.
   class X {
         int i, j;
      public:
         int getI() const { return i; }
         int getJ() const;
   };
When a  const  member function is defined outside of a class, then the  const  suffix is required.
   int X::getJ() const {
      return j;
   }
Attempting to modify a data member of the class from with a  const  member function is a compile-time error.
   X::getJ() const {
      j++;  //error!  attempting to modify object state in const function
   }
If you have a data member of a const method that must be modified, the qualify its definition with the  mutable  keyword.

{TopOfPage} {Resources}


static Members

Static Data Members

A  static data member  is shared across all object instances.

When a  static  data member is specified in a class declaration, no space is allocated for it -- it is simply a declaration, not a definition. Consequently, somewhere in your program the static class variable must be defined. Typically, static class variables are defined in a ".cpp" file rather than in a header (".h") file. If defined in a header file, then including the header file in multiple source files causes the static class variable to be multiply defined.

Avoid initializing static data members from within constructors. Reasons:

Style: using the class name with the scope resolution operator instead of the variable name with the dot operator makes it clear that the variable being referenced is  static.

Static Member Functions

A  static member function  operates on behalf of the class, not for a particular object instance. A static member function cannot access any of the members using the  this  pointer.

A  static  member function can be invoked using the scope resolution operator or using specific object.

static  member functions do not have a reference to the  this  pointer.

Style: Use  ::  when invoking  static  member functions.

{TopOfPage} {Resources}


Inheritance

Inheritance  is one way of building new classes from existing classes. It is a key concept of object-oriented design.

Inheritance is a mechanism for enhancing existing, working classes (i.e. inheritance promptes re-use).

Inheritance supports a way to implement "is-a" relationships. For example, a man is a person, a woman is a person, a car is a vehicle, a bus is a vehicle, a toaster is an appliance, a stove is an appliance, a circle is a shape, a rectangle is a shape, and so on.

Bundled with inheritance is  polymorphism , the ability to use objects without knowing their exact type at compile time.

The following is example of how to use inheritance in C++:

   class Animal {
      private:
         char name[64];
         int age;
      public:
         Animal(int a, const char* n) : age(a) {
            strcpy(name, n);
         }
         void print() {
            cout << name << " is " << age << " years old." << endl;
         }
   };

   class Zebra : public Animal {   //a zebra is an animal
      private:
         int stripeCnt;
      public:
         Zebra(int a, const char* n, int s) : 
            Animal(a, n), stripCnt(s) {}
   };
Animal is the  base  class. It is also referred to as the  superclass , or   parent  class.

Zebra is the  derived  class. It is also referred to as the  subclass , or   child  class.

How many ways can it be said...

   Zebra is derived from Animal.
   Zebra inherits from Animal.
   Zebra is a subclass of Animal.
   Zebra is a child of Animal.
The primary benefit of inheritance is a higher level of code modularization and localization. Variables and code of the base class do not need to be duplicated in the derived classes.

C++ utilizes a hierarchical inheritance system.

                  GUI Component
                       |
     +------------+----+--------+-------------+
     |            |             |             |
   Button       List        TextArea        Label
     |                                        |
 BigButton                                StyledLabel



                        Machine Language
                               |
                        Assembly Language
                               |
            C         Pascal      Basic      Cobol
            |
            C++

Protected Access Specifier

By default, the members of a class are private. Members are made public by using the  public  access specifier. A derived class does not automatically gain access to the private members of the base class: the private members of the base class remain private. However, in some cases, it is convienent for a derived class to have access to the private members of the base class without restriction. In these cases, the  protected  access specifier can be used in the base class.

Member variables and functions in the  protected  part of a class can be referenced only in member or friend functions of the class or in member or friend functions of classes derived from the class. The  protected  section is relevant only if a class will be used for derivations.

In a nutshell:

The base class  protected  specifier makes  private  members available to the derived class.

Receiving Inherited Members (Base Class Access)

By default, derivation is private. Private derivations prevent clients from referencing anything in the base class. The base class is effectively totally privatized.

Most derivations are public. In fact, only public derivations support polymorphism.

A  base class access  specifier is used to dictate how base class members are received by derived classes.

   class DerivedClass : [base class access] BaseClass {
      ...
   }

      If the  base class access  is not specified, then it defaults
      to  private.

   class DerivedClass : public    BaseClass { ... }
   class DerivedClass : protected BaseClass { ... }
   class DerivedClass : private   BaseClass { ... }
Base class access works as follows:

{TopOfPage} {Resources}


C++ set_new_handler()

C++ provides the set_new_handler() function that can be used to "catch" memory allocation errors.

Anytime memory is dynamically allocated, the pointer returned from the new operator must be checked to see if it is equal to the NULL pointer. If it is, then the dynamic memory allocation failed and the program needs to react accordingly. This technique for handling errors is prone to the following:

The aforementioned problems can be alleviated by using the set_new_handler() function. Using this function is straightforward: Example code snippet:
   #include <new.h>

   //function prototype for the new handler function...
   //the new handler function has no return value, nor does
   //it receive any arguments
   //
   void processNewFailures(void);   

   ...
   set_new_handler(processNewFailures);
   ...
   ptr1 = new char[n];
   ptr2 = new char[n];
   ptr3 = new char[n];

      There is no need to check to see if the pointers are equal 
      to the NULL pointer because if the  new  operator fails,
      then the flow control of the program "jumps" to the new
      handler function  processNewFailures().


   void processNewFailures(void) {
      //
      // Oops, cannot dynamically allocate memory from the free
      // store.  In many cases there is not much that can be
      // done about this except maybe print a message, do
      // various and sundry cleanup and exit the program.
      //
      // The actual processing that occurs in this function
      // varies from program-to-program.
      //
      cout << "This program had a dynamic memory allocation error.\n";
      exit(EXIT_FAILURE);
   }
Note: using set_new_handler() relies on the use of  exceptions . Programming using exceptions requires study.

{TopOfPage} {Resources}


Scope Resolution Operator

The  scope resolution operator   ::  is used to "attach" a function definition with a class name. In addition, it is used to attach a name defined in an  enum  with a class name.

   class Foo {
      public: 
         Bar();
         enum { NO, YES };
   }

   ...

   // define the function  Bar()  declared in the class  Foo 
   // the function prints the names  NO  and  YES  enumerated in 
   // class Foo...
   //
   Foo::Bar() { 
      cout << Foo::NO << '\t' << Foo::YES << endl; 
   }
Recall, the other use of the :: operator is to access a global variable whose name has been "hidden" due to the declaration and/or definition of a local variable.

{TopOfPage} {Resources}


Introduction to Templates

Template Functions

A  template  function defines a "family" of functions.

A family of functions have similar behavior, but they operate on differing types of parameters. The programmer needs to implement only one function -- the compiler will generate the other functions.

Templates support the concept of  generic programming .

   template <class T> return-type functionName (...); //prototype

      Template functions must be declared before they are called.

      The use of the keyword  class  indicates that the element type
      can be any type (i.e. char, int, struct, class, etc.).

      The name  T  implies a parameter data type.  It can be any
      name, but usually it is expressed in uppercase. 


   template <class T> return-type functionName (...) { //definition
      /* code goes in here */
   }
      At least one parameter in the parameter list of a template
      function must depend on  T.  Typically, the body of a
      template function will also depend on  T  (although this is
      not required).
The following is the prototype for a template function that have more than one element type.
   template <class T1,class T2> double min(T1, T2);

      T1 and T2 can take the values of any valid data types in
      the program (e.g. T1 can be  int  and T2 can be  double).

      Both T1 and T2 must appear in the parameter list (i.e.
      the function's signature).  The following is not a valid
      template function:

         template <class A, class B> void foo(A&, int);

      because  B  is not used in the parameter list; however,
      the following template function is valid:

         template <class A, class B> void foo(A&, int, B*);

      because both  A  and  B  are specified in the parameter list.
In some cases, the code in the body of a template function restricts the allowed choices of values for element types. Example:
   template <class T> T abs(T x) {
      return x < 0 ? -x : x;
   }

      The conditional operator  ?:  cannot always be portably 
      overloaded (some compilers don't allow it); therefore,
      the element type  T  is restricted to the primitive
      data types (e.g. char, int, float, ...).

Template Classes

A  template  class defines a "family" of classes.

The classes are similar with respect to both data and behavior. The family of classes is parameterized by one or more element types.

   template <class T> class SomeClassName {
      /* the body of the class */
   };
Template class members have the values of the their element types determined from the element types of the recipient object.
   template <class T> class Array {
      T* array;
      int capacity;
      int size;

      public:
         Array(int length) : size(0), capacity(length) {
            array = new T[length];
            assert(array);
         }
         int find(T& value);
   };

   ...

   Array charArray;
   Array intArray;
   Array objArray;
   ...
   int index = charArray.find('A');
   index = intArray(210);
   index = objArray(someObject_of_type_SomeClassName);
Template classes can contain  friend  functions. A friend function that does not use a template type element is universally a friend to all instantiations of the template class; whereas a friend function that does use template arguments is a friend onlyl of its instantiated class.
   template <class T> class Foo {
      friend void bar();         // universal
      friend void xxx(vect<>);  //instantiated
      ...
   };
Static members are not univerisal, but are specific to each instantiation of the class.
   template <class T> class Foo {
      public:
         static int cnt;
      ...
   };

   ...

   foo<int> f1, f2;
   foo<char> f3;

      The static variables  foo<int>::cnt  and
      foo<char>::cnt are distinct.

      cout << foo<int>::cnt;

         Prints the  cnt  static variable that exists for
         the  f1  and  f2  objects.

{TopOfPage} {Resources}


Introduction to Lisp

Lisp (LISt Processing) was created by John McCarthy. Via Wikipedia.org: "John McCarthy (September 4, 1927 - October 24, 2011) was an American computer scientist and cognitive scientist. McCarthy was one of the founders of the discipline of artificial intelligence. He coined the term "artificial intelligence" (AI), developed the Lisp programming language family [...]." John McCarthy was awarded the ACM Turing Award in 1971.

Note: Lisp does not stand for "Lots of Irritating Superfluous Parentheses."

The clisp command executes the "Common Lisp" interpreter and compiler.

clisp runs in interactive mode (read-eval-print loop) when executed with no arguments. Nutshell: READ an expression from the standard input; EVALuate the expression; PRINT results to standard output.

The format of clisp prompts: [n]> (where n is the command number).

$ clisp
  i i i i i i i       ooooo    o        ooooooo   ooooo   ooooo
  I I I I I I I      8     8   8           8     8     o  8    8
  I  \ `+' /  I      8         8           8     8        8    8
   \  `-+-'  /       8         8           8      ooooo   8oooo
    `-__|__-'        8         8           8           8  8
        |            8     o   8           8     o     8  8
  ------+------       ooooo    8oooooo  ooo8ooo   ooooo   8

Welcome to GNU CLISP 2.49 (2010-07-07) 

Copyright (c) Bruno Haible, Michael Stoll 1992, 1993
Copyright (c) Bruno Haible, Marcus Daniels 1994-1997
Copyright (c) Bruno Haible, Pierpaolo Bernardi, Sam Steingold 1998
Copyright (c) Bruno Haible, Sam Steingold 1999-2000
Copyright (c) Sam Steingold, Bruno Haible 2001-2010

Type :h and hit Enter for context help.

[1]> (bye)
Bye.

Many commands like clisp support a "quiet mode" that is turned on by executing them using either a -q or --quiet option. {clisp.org::man clisp (manual page)}

$ clisp --quiet
[1]> (bye)

Let's start again using "quiet mode."

$ clisp -q

[1]> "hello, world"
"hello, world"

[2]> "0 or more characters in double quotes is a string"
"0 or more characters in double quotes is a string"

[3]> "comments start with a ; and end at end-of-line" ; this is a comment
"comments start with a ; and end at end-of-line"

[4]> "three types of numbers: integer, floating-point, ratio"
"three types of numbers: integer, floating-point, ratio"

[5]> 240  ; is-an integer
240

[6]> -240 ; is-an integer
-240

[7]> 3.14 ; is-a floating-point
3.14

[8]> "number and strings evaluate to themselves"
"number and strings evaluate to themselves"

[9]> "lisp supports ratios when integer arguments are used"
"lisp supports ratios when integer arguments are used"

[10]> 5/7
5/7

[11]> "you can use ratios to reduce fractions"
"you can use ratios to reduce fractions"

[12]> 42/240  ; proper fraction
7/40

[13]> 240/7  ; improper fraction
240/7

[14]> 240/8  ; prints an integer if denominator is 1
30

[15]> 240/100
12/5

[16]> 240/100.0  ; ratio arguments must be integers
*** - SYSTEM::READ-EVAL-PRINT: variable |240/100.0| has no value

[17]> 6.62607004e-34   ; floating-point in scientific notation
6.62607E-34

[18]> "let's look at symbols"
"let's look at symbols"

[19]> T       ; true (any value that's not nil)
T

[20]> t       ; not case sensitive (case insensitive?)
T

[21]> nil     ; false (or empty list)
NIL

[22]> Nil     ; not case sensitive
NIL

[23]> NIL
NIL

[24]> (exit)  ; same as (bye) and (quit)
Numbers in Other Number Systems
[1]> #b1010    ; base-2 (binary)
10

[2]> #o31      ; base-8 (octal)
25

[3]> #x2b      ; base-16 (hexadecimal)
43

[4]> #12r180   ; base-12 (can go up to 36)
240

[5]> #36r6o   ; base-36
240

[6]> #60r240  ; base-36
***error: The base 60 given between # and R should lie between 2 and 36

{TopOfPage} {Resources}


Number Operations

Lisp is a "prefix notation" language.

Arithmetic operators are functions having the following syntax.

   (function_name arguments) 

The arithmetic functions are named + - * / for addition, subtraction, multiply, and divide, respectively.

Quoting Peter Seibel: "Lisp was originally designed by a mathematician as a tool for studying mathematical functions."

Addition and Subtraction
$ clisp -q

[1]> (+ )
0

[2]> (+ 2)
2

[3]> (+ 2 3)
5

[4]> (+ 2 3 4)
9

[5]> (- )
*** - EVAL: too few arguments given to -: (-)

[7]> (- 4)
-4

[8]> (- 4 2)
2

[9]> (- 2 4)
-2

[10]> (- 4 2 1)
1

[11]> (- -3 -2)
-1

[12]> (- pi)
-3.1415926535897932385L0
Multiplication
$ clisp -q

[1]> (* )
1

[2]> (* 5)
5

[3]> (* 4 2)
8

[4]> (* 4 2 3)
24

[5]> (* 4 -5)
-20
Division
$ clisp -q

[1]> (/ )
*** - EVAL: too few arguments given to /: (/)

[3]> (/ 5)
1/5

[4]> (/ 5 2)
5/2

[5]> (/ 5 2.0)
2.5

[6]> (/ 6 2)
3

[7]> (/ 3 6)
1/2

[8]> (/ 12 2 2)
3

[9]> (/ 4 -2)
-2

[10]> (/ 4 0)
*** - /: division by zero
Predicates
$ clisp -q

[3]> (oddp 5)            ; predicate function names usually end in p
T

[4]> (oddp 10)
NIL

[5]> (evenp 5)
NIL

[6]> (evenp 10)
T

[13]> (zerop 10)
NIL

[14]> (zerop 0)
T

[15]> (plusp 12)            
T

[16]> (plusp -12)
NIL

[17]> (typep 240 'integer)    ; ' is the quote character
T

[18]> (typep 240 'float)      ; or do (typep 240 (quote float))
NIL

[19]> (typep 240 'long)
*** - TYPEP: invalid type specification LONG

[20]> (typep 240 'string)     ; or do (typep 240 (quote string))
NIL

[21]> (typep 240 'double)
*** - TYPEP: invalid type specification DOUBLE

[22]> (typep 240 'real)
T

[23]> (typep 240 'number)
T

[24]> (typep 240 'character)
NIL

[25]> (typep 240 'complex)
NIL

[26]> (integerp 240)
T

[27]> (floatp 240)
NIL

[28]> (complexp 240)
NIL

[29]> (stringp 240)
NIL

[30]> (characterp 240)
NIL

[31]> (numberp 240)
T

[32]> (typep "240" (quote string))
T

[33]> (typep "240" 'string)
T

[34]> (stringp "240")
T
Increment/Decrement
$ clisp -q

[1]> (1+ 0)
1

[2]> (1+ 1)
2

[3]> (1+ 2)
3

[4]> (1- 3)
2

[5]> (1- 2)
1

[6]> (1- 1)
0

[7]> (1- 0)
-1

[8]> (1+ 3.1416)            ; strange evaluation
4.1415997

[9]> (1- 3.1416)
2.1416
Math Functions
$ clisp -q

[1]> (gcd 20 30 40 50)   ; greatest common divisor
10

[2]> (lcm 1 2 3 4)       ; least common multiple
12

[3]> (rem 5 2)           ; the % operator in C and Java
1

[4]> (abs -5)            ; absolute value
5

[5]> (max 5 92 1 3 1 6)
92

[7]> (min 5 92 1 3 1 6)
1

[18]> (random 10)
9

[19]> (random 92.31)
27.658089

[20]> (floor pi)
3 ;
0.14159265358979323851L0

[21]> (ceiling pi)
4 ;
-0.8584073464102067615L0

[22]> (round pi)
3 ;
0.14159265358979323851L0

[23]> (truncate pi)
3 ;
0.14159265358979323851L0

[24]> (mod 240 7)
2

[25]> (ext:! 4)    ; ext: indicates an extension
24
Exponents/Logarithms
$ clisp -q

[1]> (expt 2 5)
32

[2]> (expt 5)
*** - EVAL: too few arguments given to EXPT: (EXPT 5)

[4]> (exp 5)
148.41316

[5]> (expt 0)
*** - EVAL: too few arguments given to EXPT: (EXPT 0)

[6]> (expt 5 0)
1

[7]> (sqrt 5)
2.236068

[8]> (sqrt 25)
5

[9]> (log 100)           ; base-e (natural logarithm)
4.6051702

[10]> (sqrt -1)          ; complex numbers supported
#C(0 1)

[11]> (isqrt 5)          
2

[12]> (log e)
*** - SYSTEM::READ-EVAL-PRINT: variable E has no value

[13]> (exp 1)
2.7182817


[14]> (log pi)
1.1447298858494001742L0

[15]> (log 8 2)        ; base-2 log
3

[16]> (log 100 10)     ; log is base-10 by default
2

[17]> (expt 25 0.5)    ; (expt base power)
5.0

; Lisp handles numbers greater than 2^262144 (via Peter Seibel)
[18]> (expt 2 262144)
Evaluating Expressions
$ clisp -q

; evaluate: 237 + 24 / 8
[1]> (+ 237 (/ 24 8))  
240

; evaluate:  240 + 14 * 2 / 4 - 7
[2]> (- (+ 240 (/ (* 14 2) 4)) 7)
240

; evaluate:  2 * (22 % 7)
[3]> (* 2 (mod 22 7))
2

; evaluate:  f(x) = 15 + 86(x)  when x = 4
[4]> (+ 15 (* 86 4))
359

; convert 240 Kelvin to degree Fahrenheit: 240 x (9/5) - 459.67
[5]> (- (* 240 (/ 9 5.0)) 459.67)
-27.670013

; calculate the golden ratio (phi):  (1 + sqrt(5)) / 2
[6]> (/ (+ 1 (sqrt 5)) 2)
1.618034

; calculate the average of min { 1 2 3 } and max { 4 5 6 }
[7]> (/ (+ (min 1 2 3) (max 4 5 6)) 2)
7/2

[8]> (/ (+ (min 1 2 3) (max 4 5 6)) 2.0)
3.5

; triple the sum of -2, 0, 5
[9]> (* 3 (+ -2 0 5))
9

; evaluate:  4^3 - 3^2 + 3^4
[10]> (+ (- (expt 4 3) (expt 3 2)) (expt 3 4))
136

; evaluate Avogardo number:  6.022140857 * 10^23
[12]> (* 6.022140857 (expt 10 23))
6.022141E23

; evaluate Plank constant:  6.62607004 * 10^-34
[13]> (* 6.62607004 (expt 10 -34))
6.62607E-34

; evaluate Plank constant and store in a variable
[14]> (setf plank (* 6.62607004 (expt 10 -34)))
6.62607E-34

; evaluate f(x) = 3x^2 - 5x + 2 when x = 2
[15]> (setf x 2)
[15]> (+ 2 (- (* 3 (expt x 2)) (* 5 x)))
4

; evaluate g(x) = x^3 - 6x^2 + 11x - 6
[16]> (setf x 3)
[16]> (+ (expt x 3) (* -6 (expt x 2)) (* 11 x) -6)
0

; evaluate:  5^2 + 5 * 2 - 4 / 2^2
[17]> (+ (expt 5 2) (* 5 2) (/ -4 (expt 2 2)))
34

; evaluate:  (2 * (4 + (5 + 3)))
[18]> (* 2 (+ 4 (+ 5 3)))
24

; evaluate:  (4 + 6) - ((((8 / 4) - 1) * 6))
[19]> (- (+ 4 6) (* (- (/ 8 4) 1) 6))
4

; evaluate:  ((8 / 4) + 6) - (2 * (9 - 8))
[20]> (- (+ (/ 8 4) 6) (* 2 (- 9 8)))
6

; format Plank constant as a string using FORMAT function
[21]> (format nil "~f" plank)
"0.000000000000000000000000000000000662607"

; print Plank constant to standard output stream using FORMAT function
[22]> (format t "~f" plank) 
0.000000000000000000000000000000000662607
NIL

{TopOfPage} {Resources}


Lisp Programs Stored in a File

The following was excuted using BASH on a Linux system. In BASH, # starts a comment that ends at end-of-line.

$ vim hw.lisp    # lisp code inserted into hw.lisp using vim text editor

$ ls             # lisp code stored in a dot-lisp file
hw.lisp

$ cat hw.lisp    # display contents of hw.lisp
;;; 
;;; This Lisp program prints the phrase "hello world"
;;; followed by a newline to the standard output stream.
;;;

(write "hello, world")

$ clisp -q -c hw.lisp   # submit file to the clisp compiler
;; Compiling file /home/gdt/lisp/src/hw.lisp ...
;; Wrote file /home/gdt/lisp/src/hw.fas
0 errors, 0 warnings

$ ls                    # dot-fas (bytecode) and dot-lib files created
hw.fas   hw.lib   hw.lisp

$ clisp hw              # execute the program using clisp
"hello, world"

$ clisp hw | od -c      # 'od' (octal dump) to see newline character
0000000   "   h   e   l   l   o   ,       w   o   r   l   d   "  \n
0000017

But... the phrase "hello, world" should not include double quotes.

$ vi hw2.lisp

$ cat hw2.lisp
(format t "~a" "hello, world")

$ clisp -q -c hw2.lisp
;; Compiling file /home/gdt/lisp/src/hw2.lisp ...
;; Wrote file /home/gdt/lisp/src/hw2.fas
0 errors, 0 warnings

$ clisp hw2
hello, world

$ clisp hw2 | od -c
0000000   h   e   l   l   o   ,       w   o   r   l   d  \n
0000015

{TopOfPage} {Resources}


Relational Functions

The relational functions (=, /=, <, >, <=, >=) evaluate to NIL to T.

$ clisp -q
[1]> (= 3 5)
NIL

[2]> (/= 3 5)
T

[3]> (> 3 5)
NIL

[4]> (< 3 5)
T

[5]> (>= 3 5)
NIL

[6]> (<= 3 5)
T

[7]> (= 5 5 5 5)
T

[8]> (= 5 5 2 5)
NIL

[9]> (/= 5 5 5 5)
NIL

[10]> (/= 5 4 8 2)
T

[11]> (/= 5 4 5 2)
NIL

[12]> (< 2 3 5 9)
T

[13]> (< 2 5 3 9)
NIL

[14]> (> 9 5 3 0)
T

[15]> (> 9 3 5 0)
NIL

[16]> (< 2 5 5 8)
NIL

[17]> (<= 2 5 5 8)
T

[18]> (> 3 2 2 1)
NIL

[19]> (>= 3 2 2 1)
T

; hmm...

[20]> (= 5)
T

[21]> (= 0)
T

[22]> (/= 5)
T

[23]> (/= 0)
T

[24]> (> 3)
T

; what about strings?

[25]> (= "foo" "foo")
*** - =: "foo" is not a number

; eq, eql, equal, equalp will be covered later...
; nutshell:
;    + eq compares addresses, while equal compares content
;    + never use eq to compare numbers
;    + eql is the "standard" comparison predicate

[26]> (eql 3 3)
T

[27]> (eql 3 2)
NIL

[28]> (equal 3 2)
NIL

[29]> (equal 3 3)
T

[30]> (eql 3 3.0)     ; different types... use =
NIL

[31]> (= 3 3.0)
T

[32]> (equal "Foo" "foo")
NIL

[33]> (equal "foo" "foo")
T

[34]> (eq "foo" "foo")    ; eq compares addresses
NIL

{TopOfPage} {Resources}


Home Previous Next