Heading 1 - SJSU



Appendix 1: Programming Notes

Overview

The purpose of this appendix is to "review" certain features of C++ (and several other languages) that are used throughout the text, yet may be unfamiliar to readers. These notes are not comprehensive. Readers should consult on-line documentation, [STR], or any reasonably up-to-date reference for details.

Note A.1: Packages in C++: Namespaces

A package is a named collection of declarations that may span several files (see chapter 1). A package defines the scope of the declarations it contains and may be separated into an interface package and an implementation package.

C++ provides several types of scopes: global, file, class, and block. We can also create a package scope using a namespace declaration:

namespace NAME { DECLARATION ... }

References to names outside of their namespace must be qualified using the scope resolution operator. For example, assume a function named jupiter() is declared in a namespace called Planet:[1]

namespace Planet

{

void jupiter() { ... }

// etc.

}

Clients of the Planet namespace must qualify calls to jupiter():

Planet::jupiter();

All of the declarations in the standard C++ library are contained in a namespace called std. This means names defined in the header file, such as ostream, istream, cout, and cin must be qualified by "std::" when they are used by clients (see Programming Note A.3). For example, here's the official version of the Hello world program:

#include

int main()

{

std::cout s3;

cout x;

// etc.

double result = Math::log(x);

cout setBonus(1000000);

}

}

Of course review(smith) only changes smith.bonus if smith was not declared as a constant.

The reinterpret_cast() operator is the elephant gun of casting operators. Using it, we can force the compiler to perform static casts it would otherwise disallow. For example, the compiler would normally disallow a static cast from a pointer to an integer::

Executive* smith = new Executive();

int location = static_cast(smith); // error!

But if we are real sure we know better than the compiler, then we can force the retyping operation:

int location = reinterpret_cast(smith);

Note A.7.1: Programmer-Defined Coercions

We can declare a coercion from an existing type, T, to a programmer-defined class, C, by simply declaring a constructor for C that expects a single parameter of type T.

C::C(T t) { ... }

We can declare coercion from C to T by defining a member function for C that overloads the T() casting operator (i.e., the operator called by T(exp)):

C::operator T() { ... }

The C++ compiler will automatically call the appropriate coercion when it sees an instance of C appearing in a context where an instance of T is expected, and vice-versa.

For example, suppose we define a rational number class with two integer member variables representing numerator and denominator:

class Rational

{

int num, den; // = num/den

public:

Rational(int n = 0, int d = 1) { num = n; den = d; }

// etc.

};

Every integer, n, can be interpreted as the rational n/1, therefore we would like to be able to use integers in contexts where rational numbers are expected. For example, we would like to be able to assign an integer to a Rational variable:

Rational r;

r = 6; // r = 6/1

In this case the compiler will automatically search for a Rational constructor that expects a single integer parameter. If it finds one, it will translate the assignment as:

r = Rational(6);

In our example, the compiler will use the constructor above with the default second argument:

r = Rational(6, 1); // r = 6/1

Every rational number can be approximated by a floating point number. For example, 7/6 could be approximated by 1.166660. In general, the rational n/m is approximated by the floating point result of dividing n by m. Therefore, we would like to be able to use rationals in contexts where floating point numbers are expected. For example, we may want to assign a rational number to a variable of type double:

Rational r(7, 6);

double x;

x = r; // x = 1.166660

Defining a Rational constructor that expects a single parameter of type double doesn't help, because it would coerce doubles to rationals, and we need to go the other way; we need to coerce rationals to doubles. This is easy to do if we realize that static casts such as:

double(r);

are actually calls to an operator that can be overloaded. The syntax is a little weird, though. The official name of the operator is "operator double()" (i.e., a blank space is part of the name!). Also, this operator must always return a double, so as with constructors, C++ doesn't allow programmers to specify the return type. (Otherwise the compiler would need to decide how to respond if the programmer specified the wrong return type.) Here is our definition of the double() operator for rationals:

class Rational

{

int num, den; // = num/den

public:

Rational(int n = 0, int d = 1) { num = n; den = d; }

operator double() { return double(num)/double(den); }

// etc.

};

The compiler will automatically translate the assignment given above to:

x = double(r);

Note A.8: A few facts about LISP

We occasionally contrast C++ with LISP, a flexible, interpreted, expression-oriented language developed by John McCarthy in 1960. LISP is commonly used to implement artificial intelligence applications such as expert systems. Our examples use the Scheme dialect of LISP. Technically speaking, Scheme isn't a dialect of LISP, because it doesn't conform to the Common LISP standard.

LISP syntax is strange. There are no infix operators, only prefix operators, and a function's name or operator symbol is placed inside its argument list! For example, the expression

30 + 12 is written as:

(+ 30 12)

the expression 6 * 5 + 4 * 3 is written as:

(+ (* 6 5) (* 4 3))

and the expression sin(atan(x, y)) is written as:

(sin (atan x y))

Unlike C++ functions, the body of a LISP function consists of a single return statement. This is because although assignment statements are available in LISP, their use is de emphasized, and so there's not much else to do in the body of a LISP function other than to return the value produced by a parameterized expression. Furthermore, because return statements are the only statements in the body, there's no need for an explicit return command as in C++. For example, the C++ function:

double square(double x) { return x * x; }

Would be defined in LISP by:

(define (square x) (* x x))

See [PEA] for more details on the Scheme dialect of LISP.

Note A.9: A few facts about Java

Note A.9.1: The Object Base Class

All Java classes implicitly specialize Java's Object class. This makes it easy to create heterogeneous containers such as stacks, because we can generically refer to the items stored in the stack as objects:

class Stack

{

private int MAX = 100;

priave int size = 0;

private Object[] items = new Object[MAX];

public void push(Object x) { if (size < 100) items[size++] = x; }

public Object pop() throws Exception

{

if (size ................
................

In order to avoid copyright disputes, this page is only a partial summary.

Google Online Preview   Download