Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

C++ For Mathematicians (2006) [eng]

.pdf
Скачиваний:
194
Добавлен:
16.08.2013
Размер:
31.64 Mб
Скачать

396

C++ for Mathematicians

C.3 Control statements

Statements in C++ must be terminated by a semicolon. Collections of statements can be grouped together to form a compound statement by enclosing those statements in curly braces. All statements enclosed in the curly braces must end in a semicolon, but the compound does not require a semicolon after the closing brace. (Exception: When defining a class, the closing semicolon must be followed by a semicolon.)

Normally, statements are executed in the order encountered. However, various control structures may be used to modify this behavior.

C.3.1 if-else

The simplest form of this structure is this:

if (expression) { statements;

}

Here, expression is evaluated. If its value is true, then the statements are executed; if false, then the statements are skipped.

We also have the following richer form,

if (expression) { statements1;

}

else { statements2;

}

Again, expression is evaluated. If it yields true, then statements1 are executed and statements2 are skipped. Otherwise (expression evaluates to false) the opposite occurs: statements1 are skipped and statements2 are executed.

The ?: operator is a compact version of the if-else structure.

C.3.2 Looping: for, while, and do

The for statement has the following format,

for (start_statement; test_expression; advance_statement) { work_statements;

}

This statement results in the following actions. First, the start_statement is executed. Then test_expression is evaluated; if false then the loop exits and control passes to the statement following the close brace. Otherwise (test_expression is true), the work_statements are executed, then the advance_statement is executed, and finally the test_expression is evaluated. If false, the loop exits and if true, the entire process repeats.

C++ Reference

397

The while statement is structured as follows,

while (test_expression) { work_statements;

}

The test_expression is evaluated first and, if false, the work_statements are skipped and execution passes to the next statement after the close brace. Otherwise (test_expression is true), the work_statements are executed, then test_expression is re-evaluated and the process repeats.

The do statement is structured as follows,

do { work_statements;

} while (test_expression);

Here, the work_statements are executed first, and then the test_expression is evaluated. If true, the process repeats; if false, the loop terminates.

All three looping constructs (for, while, and do) support the use of the statements break; and continue;. A break; statement causes the loop to exit immediately. A continue; statement causes the loop to skip the remaining work statements and attempt the next loop (starting with test_expression).

C.3.3 switch

A switch statement controls execution depending on the value of an expression. The format is this:

switch (expression) { case val1:

statements1;

break;

case val2: statements2; break;

...

default: default_statements;

}

Here, expression is evaluated to yield an integer result. If there is a case statement whose label matches the value of expression, control passes to that point and the statements following the case label are executed until a break statement is reached. If no matching label can be found, then statements following the default label are executed. Groups of statements may be preceded with more than one label.

The labels val1, val2, and so on, must be specific numbers (not variables).

398

C++ for Mathematicians

C.3.4 goto

C++ contains a goto statement whose syntax is goto label;. The causes execution to pass to a statement that has been flagged with the name label. In general, the use of goto is discouraged as it can lead to unintelligible programs. However, one use is for breaking out of a double loop:

for (int a=0; a<N; a++) { for (int b=0; b<N; b++) {

if (beta(a,b) < 0) goto aftermath; // other stuff

}

}

aftermath: cout << "All finished" << endl;

C.3.5 Exceptions

The keywords try, throw, and catch are used to implement C++’s exceptionhandling mechanism. Typical code looks like this:

try { statements;

if (something_bad) throw x; more_statements;

}

catch(type z) { recovery_statements;

}

If something_bad is false, execution continues with more_statements and the recovery_statements are skipped. However, if something_bad evaluates to true, then more_statements are skipped and the value x is “thrown”. Assuming that x is of type type, the exception is “caught” by the catch statement and recovery_statements are executed.

The statements inside the try block need not have an explicit throw statement; the procedures invoked inside this block may throw exceptions. See Section 15.3.

C.4 Procedures

Procedures (often called functions in the programming community) are subprograms designed to do a particular job.

Procedures are declared by specifying a return type (if none, write void), followed by the procedure’s name, followed by a list of arguments (with their types). The value returned by the procedure is given by a return statement.

Two procedures may have the same name provided they have different number and/or types of arguments.

C++ Reference

399

 

 

C.4.1

File organization

 

 

 

 

In general, it is best to separate the declaration of a procedure from its definition.

 

 

 

 

The declaration is placed in a header file (suffix .h) and the full definition is placed

 

 

 

 

in a code file (suffix .cc).

 

 

 

 

For example, suppose we wish to declare a procedure named nroots that returns

 

 

 

 

the number of real roots of a quadratic polynomial ax2 + bx + c. The header file

 

 

 

 

would contain the following single line,

 

 

 

 

int nroots(double a, double b, double c);

 

 

 

 

The .cc file would contain the full specification:

 

 

 

 

int nroots(double a, double b, double c) {

 

 

 

 

double d = b*b - 4.*a*c;

 

 

 

 

if (d < 0.) return 0;

 

 

 

 

if (d > 0.) return 2;

 

 

 

 

return 1;

 

 

}

 

 

 

 

 

 

C.4.2 Call by value versus call by reference

 

 

 

 

By default, C++ procedures use call-by-value semantics. That is, when a proce-

 

 

 

 

dure (such as nroots) is invoked, the values of the arguments in the calling proce-

 

 

 

 

dure are copied to the local variables in the procedure. Although procedures may

 

 

 

 

modify the copies of the arguments, the original values (in the parent procedure) are

 

 

 

 

unaffected.

 

 

 

 

However, variables can be designated to use call-by-reference semantics. This is

 

 

 

 

indicated by inserting an ampersand between the type and the argument. In this case,

 

 

 

 

a procedure can change a value from its calling procedure.

 

 

 

 

Here is an example:

 

 

 

 

void alpha(int x) { x++; }

 

 

 

 

void beta(int &x) { x++; }

 

 

 

 

int main() {

 

 

 

 

int a = 5;

 

 

 

 

int b = 5;

 

 

 

 

alpha(a);

 

 

 

 

beta(b);

 

 

 

 

cout << a << endl;

 

 

 

 

cout << b << endl;

 

 

 

 

return 0;

 

 

}

 

 

 

 

 

 

The procedure alpha increases a copy of a, so main’s a is unaffected. However, the

 

 

 

 

procedure beta increases the variable b itself, so its value becomes 6. The output of

 

5

 

 

 

 

this program is this:

 

 

 

6

 

 

 

 

 

 

 

 

 

 

Call by reference is useful if the objects passed to a procedure are large. Passing a

 

 

 

 

reference is faster than making a copy of the object.

 

 

400

C++ for Mathematicians

C.4.3 Array (and pointer) arguments

When an array is passed to a procedure, C++ does not make a copy of the array; instead it sends a pointer to the first element of the array.

For example, suppose we write a procedure to sum the elements in an array of double values. Here is the code.

double sum(const double* array, long nels) { double ans = 0.;

for (long j=0; j<nels; j++) ans += array[j]; return ans;

}

Here, nels specifies the number of elements in the array. No duplication of the array is made. Instead, a pointer to the first element is passed. One implication of this is that a procedure can modify the entries in an array argument. Because the sum example we presented here does not, in fact, modify the elements of the array, we certify that with the keyword const.

More generally, a pointer can be passed to a procedure. In this case, the value pointed to by the pointer can be modified by the procedure. However, it is simpler to use reference arguments.

C.4.4 Default values for arguments

Arguments to procedures may be given default values. If an argument is given a default value, then all arguments to its right must also be given default values. For example:

void example(int a, double x = 3.5, int n = 0) {

...

}

Calling example(4) is tantamount to example(4,3.5,0).

C.4.5 Templates

Earlier we considered a procedure to sum the elements in an array. This procedure works only for floating point (double) arrays. The identical code (but with different types) would be used for a procedure to sum an integer array. Rather than write different versions for each type of array, we can write a procedure template such as this:

template <class T>

T sum(const T* array, long nels) { T ans = T(0);

for (long j=0; j<nels; j++) ans += array[j]; return ans;

}

In this example, the symbol T acts as a “type variable”—it may stand for any type.

C++ Reference

401

C.4.6 inline procedures

A procedure may be declared as inline; this causes the compiler to generate a different style of object code that uses more memory but runs faster. In general, it is not necessary to use this keyword because modern compilers automatically inline procedures when they determine it is advantageous to do so.

C.5 Classes

New data types are created in C++ through the use of classes and class templates. Various ready-to-use classes are provided with C++ such as string, vector, and complex. Other classes are available for download from the Web and from commercial software vendors. Finally, programmers can create their own classes.

C.5.1 Overview and file organization

Suppose we wish to create a class named MyClass. The specification for the class is broken across two files: MyClass.h contains a declaration of the class and MyClass.cc contains code for the class’s methods.

The code in MyClass.h typically looks like this:

class MyClass { private:

// private data (and methods) public:

MyClass(); // basic constructor // other constructors

int method1(double x, double y); // other methods

};

In the file MyClass.cc we give the code for the constructors and other methods for the class, like this:

#include "MyClass.h"

MyClass::MyClass() {

// code for the constructor

}

int MyClass::method1(double x, double y) { // code for this method

}

Alternatively, code for constructors and methods may be given inline in the .h file. This is advisable when the code is only a few lines long.

402

C++ for Mathematicians

Data and methods listed in the private section are accessible only to the methods inside the class (but see Appendix C.5.7). Data and methods listed in the public section are accessible to all parts of the program.

It is wise to designate all data in a class private and to provide get/set methods to inspect/manipulate the data.

C.5.2 Constructors and destructors

A constructor is a class method that is invoked when an object of the class is created (e.g., when declared). Constructors do not have a return type, but may have arguments.

Be sure to have a zero-argument constructor for your class. This constructor is invoked when a variable is declared in the simple form: MyClass X;. The object X is then initialized using the zero-argument constructor.

Classes may have constructors with arguments. For example, if MyClass has a constructor with a single integer argument, then the declaration MyClass X(17); invokes that constructor. Such a constructor is also invoked in all the following situations.

MyClass X;

X = MyClass(17);

MyClass Y = 17;

MyClass Z;

Z = 17;

Some constructors allocate storage (with new). When such an object goes out of scope, the allocated memory needs to be recovered (or the program suffers a memory leak). To accomplish this, a destructor needs to be specified. In the .h file, a public method is declared like this:

class MyClass { private:

int* BigTable; // table of numbers

...

public:

...

˜MyClass(); // destructor declaration

...

};

and in the .cc file:

MyClass::˜MyClass() {

delete[] BigTable; // or other clean-up code

}

Alternatively, with a short destructor, the code may be written inline in the .h file.

C++ Reference

403

C.5.3 Operators

The usual C++ operators (such as + and *) apply to the built-in data types (int, double, and so on). These operators may also be used for classes by creating operator methods and procedures.

Suppose we wish to define + for objects of type MyClass; that is, if A and B are type MyClass, then we want to ascribe a meaning to A+B. Typically, we would declare a method within the body of the MyClass declaration (in the .h file) like this:

class MyClass {

private:

...

public:

...

MyClass operator+(const MyClass& Z) const;

};

and in the .cc file give the code:

#include "MyClass.h"

MyClass MyClass::operator+(const MyClass& Z) const {

...

}

Then, when the compiler encounters the expression A+B, it applies the operator+ method for object A with object B passed (by reference) to Z.

(Note the double appearance of the keyword const. The first const certifies that this method does not modify the argument Z and the second certifies that this method does not modify the object on which it is invoked.)

Alternatively, we could implement A+B with a procedure that is not a class method. In a .h file we declare the procedure like this:

MyClass operator+(const MyClass& U, const MyClass& V);

and in the corresponding .cc file we give the code:

MyClass operator+(const MyClass& U, const MyClass& V) {

...

}

Unary operators (e.g., for negation) are declared as class methods like this:

class MyClass {

...

MyClass operator-() const;

};

or as procedures like this:

MyClass operator-(const MyClass& U);

Binary operators may be used to combine objects of different types. If the left operand of the operator is of type MyClass, then the operator may be defined as a method of MyClass. For example, for the operator MyClass+int, use this:

404

C++ for Mathematicians

class MyClass {

...

MyClass operator+(int j) const;

};

However, for int+MyClass, a procedure needs to be declared like this:

MyClass operator+(int j, const MyClass& Z);

The increment ++ and decrement -- operators have two forms: ++A and A++. We recommend defining only the prefix form. This is done with an class method like this:

class MyClass {

...

MyClass operator++();

};

(Note: Typically ++A is used to increase the value of A by one, so this method modifies A. That is why we do not include const after the parentheses.)

It is possible to declare a postfix form of these operators. To do this, we give a “dummy” argument of type int like this:

class MyClass {

...

MyClass operator--(int x);

};

C.5.4 Copy and assign

If objects A and B are of type MyClass, then the expression A=B has a default meaning that can (and sometimes should) be overridden.

The default meaning of A=B is to copy the data fields of B into the corresponding data fields of A. That is, if class MyClass has data fields x, y, and z, then A=B; has the effect of performing the three assignments

A.x = B.x; A.y = B.y; A.z = B.z;

Finally, the new value of A is returned.

This behavior is appropriate in many cases, especially if the data fields are the basic types. However, if one of these fields, say x, is an array, then the action A.x = B.x; does not copy the array B.x into A.x (as, presumably, we would want). Rather, it causes A.x to point to the same location in memory as B.x, so subsequent modifications to array elements in B.x are also applied to the array A.x because these arrays are now housed in the same memory.

To achieve the desired behavior, we need to write a new assignment operator. The effect of this operator is to copy the data from B to A. In addition, an assignment operator should return the value of A (after it is updated).

To do this, we declare an operator= method inside the class definition like this:

class MyClass {

...

C++ Reference

405

MyClass operator=(const MyClass& Z);

};

In the .cc file, the code looks like this:

MyClass MyClass::operator(const MyClass& Z) {

//set the fields x, y, and z

//so they are duplicates of Z.x, Z.y, Z.z return *this;

}

The final return *this; statement causes a copy of the object to be the return value of this method; see Appendix C.5.6.

Just as C++ provides a default assignment operator, it also provides a default copy constructor. The default behavior of the declaration MyClass A(B); (where B is a previously declared object of MyClass) is to do a field-by-field copy of B’s data into A. As in the case of assignment, this default behavior may be unacceptable. In such cases (e.g., when MyClass data includes an array), we must write our own copy constructor.

To declare a copy constructor, we have the following in the .h file,

class MyClass {

...

MyClass(MyClass& Z);

};

and in the .cc file:

MyClass::MyClass(MyClass& Z) {

//set the fields x, y, and z

//so they are duplicates of Z.x, Z.y,, Z.z

}

C.5.5 static data and methods

In a typical class, each object of the class has its own values for each data field. Sometimes it is desirable to have a value that is shared among all objects in the class. For example, if we wish to keep track of how many objects of type MyClass are currently in existence, we can define a variable object_count that is shared among all objects of type MyClass. Constructors would increment this variable and destructors would decrement it. To distinguish variables that are shared among all objects from ordinary data members that are particular to each instance of the class, we use the keyword static. For example, in the .h file we have

class MyClass { private:

...

static int object_count; public:

MyClass() { ...; object_count++; } ˜MyClass() { ...; object_count--; }

...

};