![](/user_photo/_userpic.png)
C++ For Mathematicians (2006) [eng]
.pdf![](/html/611/317/html_XkIwBBZW6e.0JgL/htmlconvd-64c5QR421x1.jpg)
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).
![](/html/611/317/html_XkIwBBZW6e.0JgL/htmlconvd-64c5QR423x1.jpg)
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.
![](/html/611/317/html_XkIwBBZW6e.0JgL/htmlconvd-64c5QR424x1.jpg)
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. |
|
|
![](/html/611/317/html_XkIwBBZW6e.0JgL/htmlconvd-64c5QR426x1.jpg)
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--; }
...
};