Chapter 14. Let C++ Simplify Your Life

Our life is frittered away by detail . . . Simplify, simplify.

—H. D. Thoreau, Walden

THE PROGRAMMING LANGUAGE C++, UNDER development since 1979 by Bjarne Stroustrup[44] at Bell Laboratories, is an extension of C that promises to dominate the field of software development. C++ supports the principles of object-oriented programming, which is based on the tenet that programs, or, better, processes, comprise a set of objects that interact exclusively through their interfaces. That is, they exchange information or accept certain external commands and process them as a task. In this the methods by which an object carries out a task are an internal affair "decided upon" autonomously by the object alone. The data structures and functions that represent the internal state of an object and effect transitions between states are the private affair of the object and should not be detectable from the outside. This principle, known as information hiding, assists software developers in concentrating on the tasks that an object has to fulfill within the framework of a program without having to worry about implementation details. (Another way of saying this is that the focus is on "what," not on "how.")

The structural designs for what goes on in the "internal affairs" of objects, containing complete information on the organization of data structures and functions, are the classes. With these the external interface of an object is established, and this is decisive for the suite of behaviors that an object can perform. Since all objects of a class reflect the same structural design, they also possess the same interface. But once they have been created (computer scientists say that classes are instantiated by objects), they lead independent lives. Their internal states are changed independently of one another and they execute different tasks corresponding to their respective roles in the program.

Object-oriented programming propagates the use of classes as the building blocks of larger structures, which can again be classes or groups of classes, into complete programs, just as houses or automobiles are constructed of prefabricated modules. In the ideal case programs can be cobbled together from libraries of preexisting classes without the necessity for the creation of a significant amount of new code, at least not on the order of magnitude as is typical in conventional program development. As a result it is easier to orient program development to reflect the actual situation, to model directly the actual processes, and thereby to achieve successive refinement until the result is a collection of objects of particular classes and their interrelations, in which the underlying real-world model can still be recognized.

Such a way of proceeding is well known to us from many aspects of our lives, for we do not generally operate directly with raw materials if we wish to build something, but we use, rather, completed modules about whose construction or inner workings we have no detailed knowledge, nor the necessity of such knowledge. By standing on the shoulders of those who built before us, it becomes possible for us to create more and more complex structures with a manageable amount of effort. In the creation of software this natural state of affairs has not previously found its true expression, as software developers turn again and again to the raw materials themselves: Programs are constructed out of atomic elements of a programming language (this constructive process is commonly called coding). The use of run-time libraries such as the C standard library does not improve this situation to any great degree, since the functions contained in such libraries are too primitive to permit a direct connection to a more complex application.

Every programmer knows that data structures and functions that provide acceptable solutions for particular problems only seldom can be used for similar but different tasks without modification. The result is a reduction in the advantage of being able to rely on fully tested and trusted components, since any alteration contains the risk of new errors—as much in the design as in programming. (One is reminded of the notification in manuals that accompany various consumer products: "Any alteration by other than an authorized service provider voids the warranty.")

In order that the reusability of software in the form of prefabricated building blocks not founder on the rocks of insufficient flexibility, the concept of inheritance, among a number of other concepts, has been developed. This makes it possible to modify classes to meet new requirements without actually altering them. Instead, the necessary changes are packaged in an extension layer.

The objects that thus arise take on, in addition to their new properties, all the properties of the old objects. One might say that they inherit these properties. The principle of information hiding remains intact. The chances of error are greatly reduced, and productivity is increased. It is like a dream come true.

As an object-oriented programming language C++ possesses the requisite mechanisms for the support of these principles of abstraction.[45] These, however, represent only a potential, but not a guarantee, of being used in the sense of object-oriented programming. To the contrary, the switch from conventional to object-oriented software development requires a considerable intellectual retooling. This is particularly apparent in two respects: On the one hand, the developer who has hitherto achieved good results is forced to devote considerably more attention to the modeling and design phases than what was usually required in traditional methods of software development. On the other hand, in the development and testing of new classes the greatest care is required to obtain error-free building blocks, since they will go on to be used in a great variety of future applications. Information hiding can also mean bug hiding, since it defeats the purpose of the idea of object-oriented programming if the user of a class must become familiar with its inner workings in order to find a bug. The result is that errors contained in a class implementation are inherited together with the class, so that all subclasses will be infected with the same "hereditary disease." On the other hand, the analysis of errors that occur with the objects of a class can be restricted to the implementation of the class, which can greatly reduce the scope of the search for the error.

All in all, we must say that while there are strong trends in the direction of using C++ and Java as programming languages, nonetheless, the principles of object-oriented programming beyond an understanding of the essentially complex elements of these languages are multifaceted, and it will be a long time before they are used as a standard method of software development. However, in the meantime, there are powerful and robust tools available that strongly support the development process, from modeling up through the generation of executable code.

Thus the title of this chapter refers not to object-oriented programming and the use of C++ in general, but to the mechanisms offered therein and their significance for our project. These enable the formulation of arithmetic operations with large numbers in a way that is so natural that it is as if they belonged to the standard operations of the programming language. In the following sections, therefore, we will not be presenting an introduction to C++, but a discussion of the development of classes that represent large natural numbers and that export functions to work with these numbers as abstract methods.[46] The (few) details of the data structures will be hidden both from the user and the client of the class, as will the implementation of the numerous arithmetic and number-theoretic functions. However, before we can use the classes they must be developed, and in this regard we shall have to get our hands dirty with the internal details. Nonetheless, it will surprise no one that we are not going to begin from scratch, but rather make use of the implementation work that we accomplished in the first part of the book and formulate the arithmetic class as an abstract layer, or shell, around our C library.

We shall give the name LINT (Large INTegers) to our class. It will contain data structures and functions as components with the attribute public, which determine the possibilities for external access. Access to the structures of the class declared as private, on the other hand, can be accomplished only with functions that have been declared either a member or friend of the class. Member functions of the class LINT can access the functions and data elements of LINT objects by name and are required for servicing the external interface, for processing instructions to the class, and serving as fundamental routines and auxiliary functions for managing and processing internal data structures. Member functions of the class LINT always possess a LINT object as implied left argument, which does not appear in its parameter list. Friend functions of the class do not belong to the class, but they can nonetheless access the internal structure of the class. Unlike the member functions, the friend functions do not possess an implicit argument.

Objects are generated as instances of a class by means of constructors, which complete the allocation of memory, the initialization of data, and other management tasks before an object is ready for action. We shall require several such constructors in order to generate our LINT objects from various contexts. Complementary to the constructors we have destructors, which serve the purpose of removing objects that are no longer needed and releasing the resources that have been bound to them.

The elements of C++ that we shall particularly use for our class development are the following:

  • the overloading of operators and functions;

  • the improved possibilities, vis à vis C, for input and output.

The following sections are devoted to the application of these two principles in the framework of our LINT class. To give the reader an idea of the form that the LINT class will assume, we show a small segment of its declaration:

class LINT
{
  public:
    LINT (void); // constructor
    ~LINT (); // destructor

    const LINT& operator= (const LINT& );
    const LINT& operator+= (const LINT& );
    const LINT& operator−= (const LINT& );
    const LINT& operator*= (const LINT& );
    const LINT& operator/= (const LINT& );
    const LINT& operator LINT gcd (const LINT& );
    LINT lcm (const LINT& );
    int jacobi (const LINT& );

    friend const LINT operator + (const LINT& , const LINT& );
    friend const LINT operator − (const LINT& , const LINT& );
    friend const LINT operator * (const LINT& , const LINT& );
    friend const LINT operator / (const LINT& , const LINT& );
    friend const LINT operator
    friend LINT mexp (const LINT& , const LINT& , const LINT& );
    friend LINT mexp (const USHORT, const LINT& , const LINT& );
    friend LINT mexp (const LINT& , USHORT, const LINT& );
    friend LINT gcd (const LINT& , const LINT& );
    friend LINT lcm (const LINT& , const LINT& );
    friend int jacobi (const LINT& , const LINT& );

  private:
    clint *n_l;
    int status;
};

One may recognize the typical subdivision into two blocks: First the public block is declared with a constructor, a destructor, arithmetic operators, and member functions as well as the friend functions of the class. A short block of private data elements is joined to the public interface, identified by the label private. It is an aid to clarity and is considered good style to place the public interface before the private block and to use the labels "public" and "private" only once each within a class declaration.

The list of operators appearing in the section of the class declaration shown here is by no means complete. It is missing some arithmetic functions that cannot be represented as operators as well as most of the number-theoretic functions, which we know already as C functions. Furthermore, the announced constructors are as little represented as the functions for input and output of LINT objects.

In the following parameter lists of the operators and functions the address operator & appears, which has the effect that objects of the class LINT are passed not by value, but by reference, that is, as pointers to the object. The same holds for the return value of LINT objects. This use of & is unknown in C. On close inspection, however, one recognizes that only certain of the member functions return a pointer to a LINT object, while most of the others return their results by value. The basic rule that determines which of these two methods is followed is this: Functions that alter one or more of the arguments passed to them can return this result as a reference, while other functions, those that do not alter their arguments, return their results by value. As we proceed we shall see which method goes with which of the LINT functions.

Classes in C++ are an extension of the complex data type struct in C, and access to an element x of a class is accomplished syntactically in the same way as access to an element of a structure, that is, by A.x, where A denotes an object and x an element of the class.

One should note that in the parameter list of a member function an argument is less completely named than in a like-named friend function, as the following example illustrates:

friend LINT gcd (const LINT&, const LINT&);

versus

LINT LINT::gcd (const LINT&);

Since the function gcd() as a member function of the class LINT belongs to an object A of type LINT, a call to gcd() must be in the form A.gcd(b) without A appearing in the parameter list of gcd(). In contrast, the friend function gcd() belongs to no object and thus possesses no implicit argument.

We shall fill in the above sketch of our LINT class in the following chapters and work out many of the details, so that eventually we shall have a complete implementation of the LINT class. The reader who is also interested in a general discussion of C++ is referred to the standard references [Deit], [ElSt], [Lipp], and especially [Mey1] and [Mey2].

Not a Public Affair: The Representation of Numbers in LINT

And if my ways are not as theirs

Let them mind their own affairs.

—A. E. Housman, Last Poems IX

The representation of large numbers that has been chosen for our class is an extension of the representation presented in Part I for the C language. We take from there the arrangement of the digits of a natural number as a vector of clint values, where more-significant digits occupy the places of higher index (cf. Chapter 2). The memory required for this is automatically allocated when an object is generated. This is carried out by the constructors, which are invoked either explicitly by the program or implicitly by the compiler using the allocation function new(). In the class declaration we therefore require a variable of type clint *n_l, to which is associated within one of the constructor functions a pointer to the memory allocated there.

The variable status is used to keep track of various states that can be taken by LINT objects. For example, with status, an overflow or underflow (cf. page 20) can be reported if such an event occurs as a result of operations on LINT objects that would result in the status variable being assigned the value E_LINT_OFL or E_LINT_UFL. Furthermore, we would like to determine whether a LINT object has been initialized, that is, whether any numerical value at all has been assigned to it, before it is used in a numerical expression on the right side of the equal sign. If a LINT object does not possess a numerical value, then status contains the value E_LINT_INV, which all functions must check before an operation is executed. We shall organize our LINT functions and operators in such a way that an error message results if the value of a LINT object, and consequently the value of an expression, is undefined.

The variable status is, strictly speaking, not an element of our numerical representation. It serves rather for reporting and handling error states. The types and mechanisms of error handling are discussed in detail in Chapter 16.

The class LINT defines the following two elements for representing numbers and storing the states of objects:

clint* n_l;
int status;

Since we are dealing here with private elements, access to these class elements is possible only by means of member or friend functions or operators. In particular, there is no possibility of direct access to the individual digits of a number represented by a LINT object.

Constructors

Constructors are functions for the generation of objects of a particular class. For the LINT class this can occur with or without initialization, where in the latter case an object is created and the required memory for the storage of the number is allocated, but no value is assigned to the object. The constructor required for this takes no argument and thus takes on the role of the default constructor of the class LINT (cf. [Str1], Section 10.4.2). The following default constructor LINT(void) in flintpp.cpp creates a LINT object without assigning it a value:

LINT::LINT (void)
  {
    n_l = new CLINT;
    if (NULL == n_l)
      {
        panic (E_LINT_NHP, "constructor 1", 0, __LINE__);
      }
    status = E_LINT_INV;
  }

If a newly generated object is also to be initialized with a numerical value, then a suitable constructor must be invoked to generate a LINT object and then assign to it a predefined argument as the value. Depending on the type of argument various overloaded constructors must be provided. The class LINT contains the constructor functions as shown in Table 14-1.

We would now like to consider a further example for the LINT construction of the function LINT (const char*), which generates a LINT object and associates to it a value taken from a character string with ASCII digits. A prefix can be given to the digits contained in the string that contains information about the base of the numerical representation. If a character string is prefixed with 0x or 0X, then hexadecimal digits from the domains {0,1,. . .,9} and {a,b,. . .,f}, respectively {A,B,. . .,F}, are expected. If the prefix is 0b or 0B, then binary digits from the set {0, 1 } are expected. If there is no prefix at all, then the digits are interpreted as decimal digits. The constructor employs the function str2clint_l() to transform the character string into an object of type CLINT, from which then in the second step a LINT object is created:

LINT:: LINT (const char* str)
  n_l = new CLINT;
  if (NULL == n_l) // error with new?
    {
      panic (E_LINT _NHP, "constructor 4", 0, __LINE__);
    }
  if (strncmp (str, "0x", 2) == 0 || strncmp (str, "0X", 2) == 0)
    {
      int error = str2clint_l (n_l, (char*)str+2, 16);
    }
  else
{
      if (strncmp (str, "0b", 2) == 0 || strncmp (str, "0B", 2) == 0)
        {
          error = str2clint_l (n_l, (char*)str+2, 2);
        }
      else
        {
          error = str2clint_l (n_l, (char*)str, 10);
        }
    }
  switch (error)     {
      case E_CLINT_OK:
        status = E_LINT_OK;
        break;
      case E_CLINT_NPT:
        status = E_LINT_INV;
        panic (E_LINT_NPT, "constructor 4", 1, __LINE__);
        break;
      case E_CLINT_OFL:
        status = E_LINT_OFL;
        panic (E_LINT_OFL, "constructor 4", 1, __LINE__);
        break;
      default:
        status = E_LINT_INV;
        panic (E_LINT_ERR, "constructor 4", error, __LINE__);
    }
}

Constructors make possible the initialization of LINT objects amongthemselves as well as LINT objects with standard types, constants, and characterstrings, as the following examples demonstrate:

LINT a;
LINT one (1);
int i = 2147483647;
LINT b (i);
LINT c (one);
LINT d ("0x123456789abcdef0");

The constructor functions are called explicitly to generate objects of type LINT from the specified arguments. The LINT constructor, which, for example, changes unsigned long values into LINT objects, is embodied in the following function:

LINT::LINT (USHORT ul)
{
  n_l = new CLINT;
  if (NULL == n_l)
    {
      panic (E_LINT_NHP, "constructor 11", 0, __LINE__);
    }
  ul2clint_l (n_l, ul);
  status = E_LINT_OK;
}

Table 14-1. LINT constructors

Constructor

Semantics: Generation of a LINT Object

LINT (void);

without initialization (default constructor)

LINT (const char* const, char);

from a character string, with the basis of the numerical representation given in the second argument

LINT (const UCHAR*, int)

from a byte vector with the length given in the second argument

LINT (const char*);

from a character string, optionally with prefix 0X for hex numbers or 0B for binary digits

LINT (const LINT&);

from another LINT object (copy constructor)

LINT (int);

from a value of type char, short, or integer

LINT (long int);

from a value of type long integer

LINT (UCHAR);

from a value of type UCHAR

LINT (USHORT);

from a value of type USHORT

LINT (unsigned int);

from a value of type unsigned integer

LINT (ULONG);

from a value of type ULONG

LINT (const CLINT);

from a CLINT object

Now we must provide a destructor function to go with the constructors of the class LINT, which enable the release of objects and, in particular, the memory bound to them. To be sure, the compiler would gladly make a default destructor available to us, but this would release only the memory that the elements of a LINT object possess. The additional memory allocated by the constructors would not be released, and memory leakage would result. The following short destructor fulfills the important tasks of releasing memory occupied by LINT objects:

~LINT()
  {
    delete [] n_l;
  }

Overloaded Operators

The overloading of operators represents a powerful mechanism that makes it possible to define functions with the same name but with different parameter lists, functions that can then carry out differing operations. The compiler uses the specified parameter list to determine which function is actually meant. To make this possible C++ employs strong type-checking, which tolerates no ambiguity or inconsistency.

The overloading of operator functions makes it possible to use the "normal" way of expressing a sum c = a + b with LINT objects a, b, and c instead of having to invoke a function like, for example, add_l(a_l, b_l, c_l). This enables the seamless integration of our class into the programming language and significantly improves the readability of programs. For this example it is necessary to overload both the operator "+" and the assignment "=".

There are only a few operators in C++ that cannot be overloaded. Even the operator "[]", which is used for access to vectors, can be overloaded, for example by a function that simultaneously checks whether the access to a vector oversteps the vector's bounds. However, please note that the overloading of operators opens the door to all possible mischief. To be sure, the effect of the operators of C++ on the standard data types cannot be altered; nor can the predefined precedence order of the operators (cf. [Str1], Section 6.2) be changed or new operators "created." But for individual classes it is fully possible to define operator functions that have nothing in common with what one traditionally has associated with the operator as it is normally employed. In the interest of maintainability of programs one is well advised to stick close to the meaning of the standard operators in C++ when overloading operators if one is to avoid unnecessary confusion.

One should note in the above outline of the LINT class that certain operators have been implemented as friend functions and others as member functions. The reason for this is that we would like, for example, to use "+" or "*" as two-position operators that can not only process two equivalent LINT objects but accept alternatively one LINT object and one of the built-in C++ integer types, and moreover, accept the arguments in either order, since addition is commutative. To this end we require the above-described constructors, which create LINT objects of out integer types. Mixed expressions such as in

LINT a, b, c;
int number;
// Initialize a, b, and number and calculate something or other
// . . .
c = number * (a + b / 2)

are thus possible. The compiler takes care of calling the appropriate constructor functions automatically and sees to it that the transformation of the integer type number and the constant 2 into LINT objects takes place at run time, before the operators + and * are invoked. We thereby obtain the greatest possible flexibility in the application of the operators, with the restriction that expressions containing objects of type LINT are themselves of type LINT and can thereafter be assigned only to objects of type LINT.

Before we get ourselves involved in the details of the individual operators, we would like to give an overview of the operators defined by the class LINT, for which the reader is referred to Tables 14-2 through 14-5,

Table 14-2. LINT arithmetic operators

+

addition

++

increment (prefix and postfix operators)

subtraction

--

decrement (prefix and postfix operators)

*

multiplication

/

division (quotient)

%

remainder

Table 14-3. LINT bitwise operators

&

bitwise AND

|

bitwise OR

^

bitwise exclusive OR (XOR)

<<

shift left

>>

shift right

We now would like to deal with the implementation of the operator functions "*", "=", "*=", and "==", which may serve as examples of the implementation of the LINT operators. First, with the help of the operator "*=" we see howmultiplication of LINT objects is carried out by the C function mul_l(). The operator is implemented as a friend function, to which both factors associated with the operation are passed as references. Since the operator functions do not change their arguments, the references are declared as const:

Table 14-4. LINT logical operators

==

equality

!=

inequality

<, <=

less than, less than or equal to

>, >=

greater than, greater than or equal to

Table 14-5. LINT assignment operators

=

simple assignment

+=

assignment after addition

−=

assignment after subtraction

*=

assignment after multiplication

/=

assignment after division

%=

assignment after remainder

&=

assignment after bitwise AND

|=

assignment after bitwise OR

^=

assignment after bitwise XOR

<<=

assignment after left shift

>>=

assignment after right shift

const LINT operator* (const LINT& lm, const LINT& ln)
{
  LINT prd;
  int error;

Note

The first step is to query the operator function as to whether the arguments lm and ln passed by reference have been initialized. If this is not the case for both arguments, then error handling goes into effect, and the member function panic(), declared as static, is called (cf. Chapter 15).

if (lm.status == E_LINT_INV)
  LINT::panic (E_LINT_VAL, "*", 1, __LINE__);
if (ln.status == E_LINT_INV)
  LINT::panic (E_LINT_VAL, "*", 2, __LINE__);

Note

The C function mul_l() is called, to which are passed as arguments the vectors lm.n_l, ln.n_l as factors, as well as prd.n_l for storing the product.

error = mul_l (lm.n_l, ln.n_l, prd.n_l);

Note

The evaluation of the error code stored in error distinguishes three cases: If error == 0, then all is right with the world, and the object prd can be marked as initialized. This takes place by setting the variable prd.status to a value unequal to E_LINT_INV, which in normal cases (error == 0) is E_LINT_OK. If an overflow occurred with mul_l(), then error contains the value E_CLINT_OFL. Since the vector prd.n_l contains in this case a valid CLINT integer, the status variable prd.status is simply set to E_LINT_OFL, though without a call to error handling. If error has neither of these two values after the call to mul_l(), then something has gone awry in these functions without our being able to identify more precisely what error has occurred. Therefore, the function panic() is called for further error handling.

switch (error)
  {
    case 0:
      prd.status = E_LINT_OK;
      break;
    case E_CLINT_OFL:
      prd.status = E_LINT_OFL;
      break;
    default:
      lint::panic (E_LINT_ERR, "*", error, __LINE__);
  }

Note

If the error cannot be repaired by panic(), there would be no point in returning to this location. The mechanism for error recognition leads here to a defined termination, which in principle is better than continuing the program in an undefined state. As a final step we have the elementwise return of the product prd.

return prd;
}

Since the object prd exists only within the context of the function, the compiler makes sure that a temporary object is created automatically, which represents the value of prd outside the function. This temporary object is generated with the aid of the copy constructor LINT(const LINT&) (cf. page 328) and exists until the expression within which the operator was used has been processed, that is, until the closing semicolon has been reached. Due to the declaration of the function value as const such nonsensical constructs as (a * b) = c; will not get past the compiler. The goal is to treat LINT objects in exactly the same way as the built-in integer types.

We can extend the operator functions by the following detail: If the factors to be multiplied are equal, then the multiplication can be replaced by squaring, so that the advantage in efficiency associated with this changeover can be utilized automatically (cf. Section 4.2.2). However, since in general it costs an elementwise comparison of the arguments to determine whether they are equal, which is too expensive for us, we shall be satisfied with a compromise: Squaring will be brought into play only if both factors refer to one and the same object. Thus we test whether ln and lm point to the same object and in this case execute the squaring function instead of multiplication. Here is the relevant code:

if (&lm == &ln)
  {
    error = sqr_l (lm.n_l, prd.n_l);
  }
else
  {
    error = mul_l (lm.n_l, ln.n_l, prd.n_l);
  }

This falling back on the functions implemented in C from Part I is a model for all of the remaining functions of the class LINT, which is formed like a shell around the kernel of C functions and protects it from the user of the class.

Before we turn our attention to the more complex assignment operator "*=", it seems a good idea to take a closer look at the simple assignment operator "=". Already in Part I we established that assignment of objects requires particular attention (cf. Chapter 8). Therefore, just as in the C implementation we had to pay heed that in assigning one CLINT object to another the content and not the address of the object was assigned, we must likewise for our LINT class define a special version of the assignment operator "=" that does more than simply copy elements of the class: For the same reasons as were introduced in Chapter 8 we must therefore take care that it is not the address of the numerical vector n_l that is copied, but the digits of the numerical representation pointed to by n_l.

Once one has understood the fundamental necessity for proceeding thus, the implementation is no longer particularly complicated. The operator "=" is implemented as a member function, which returns as a result of the assignment a reference to the implicit left argument. Of course, we use internally the C function cpy_l() to move digits from one object into the other. For executing the assignment a = b the compiler calls the operator function "=" in the context of a, where a takes over the role of an implicit argument that is not given in the parameter list of the operator function. Within the member function reference to the elements of the implicit argument is made simply by naming them without context. Furthermore, a reference to the implicit object can be made via the special pointer this, as in the following implementation of the operator "=":

const LINT& LINT::operator= (const LINT& ln)
{
  if (ln.status == E_LINT_INV)
    panic (E_LINT_VAL "=", 2 __LINE__);

Note

First, a check is made as to whether the references to the right and left arguments are identical, since in this case copying is unnecessary. Otherwise, the digits of the numerical representation of ln are copied into those of the implied left argument *this, just as the value of status, and with *this the reference to the implicit argument is returned.

if (&ln != this)
    {
      cpy_l (n_l, ln.n_l);
      status = ln.status;
    }
  return *this;
}

One might ask whether the assignment operator must necessarily return any value at all, since after LINT::operator =(const LINT&) is called the intended assignment appears to have been accomplished. However, the answer to the question is clear if one recalls that expressions of the form

f (a = b);

are allowed. According to the semantics of C++, such an expression would result in a call to the function f with the result of the assignment a = b as argument. Thus is it imperative that the assignment operator return the assigned value as result, and for reasons of efficiency this is done by reference. A special case of such an expression is

a = b = c;

where the assignment operator is called two times, one after the other. At the second call the result of the first assignment b = c is assigned to a.

In contrast to the operator "*", the operator "*=" changes the leftmost of the two passed factors by overwriting it with the value of the product. The meaning of the expression a *= b as an abbreviated form of a = a * b should, of course, remain true for LINT objects. Therefore, the operator "*=" can, like the operator "=", be set up as a member function that for the reasons given above returns areference to the result:

const LINT& LINT::operator*= (const LINT& ln)
{
    int error;

    if (status == E_LINT_INV)
      panic (E_LINT_VAL, "*=", 0, __LINE__);
    if (ln.(status == E_LINT_INV)
      panic (E_LINT_VAL, "*=", 1, __LINE__);
    if (&ln == this)
      error = sqr_l (n_l, n_l);
    else
      error = mul_l (n_l, ln.n_l, n_l);

    switch (error)
      {
        case 0:
          status = E_LINT_OK;
          break;
        case E_CLINT_OFL:
          status = E_LINT_OFL;
          break;
        default:
          panic (E_LINT_ERR, "*=", error, __LINE__);
        }
  return *this;
  }

As our last example of a LINT operator we shall describe the function "==", which tests for the equality of two LINT objects: As result the value 1 is returnedin the case of equality, and otherwise 0. The operator == also illustrates theimplementation of other logical operators.

const int operator == (const LINT& lm, const LINT& ln)
{
  if (lm.(status == E_LINT_INV)
    LINT::panic (E_LINT_VAL, "==", 1, __LINE__);
  if (ln.(status == E_LINT_INV)
    LINT::panic (E_LINT_VAL, "==", 2, __LINE__);
  if (&ln == &lm)
    return 1;
  else
    return equ_l (lm.n_l, ln.n_l);
}


[44] The following, from Bjarne Stroustrup's Internet home page (http://www.research.att.com/~bs/), may help to answer the question, How do you pronounce "Bjarne Stroustrup"?: "It can be difficult for non-Scandinavians. The best suggestion I have heard yet was 'start by saying it a few times in Norwegian, then stuff a potato down your throat and do it again' :-). Both of my names are pronounced with two syllables: Bjar-ne Strou-strup. Neither the B nor the J in my first name are stressed and the NE is rather weak so maybe Be-ar-neh or By-ar-ne would give an idea. The first U in my second name really should have been a V making the first syllable end far down the throat: Strov-strup. The second U is a bit like the OO in OOP, but still short; maybe Strov-stroop will give an idea."

[45] C++ is not the only object-oriented language. Others are Simula (the precursor of all object-oriented languages), Smalltalk, Eiffel, Oberon, and Java.

[46] The reader is referred to several works in the standard literature for an introduction to C++ and discussions about it, namely [ElSt], [Str1], [Str2], [Deit], [Lipp], just to name a few of the more important titles. In particular, [ElSt] was taken as the basis for the standardization by the ISO.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.14.251.57