Заглавная страница Избранные статьи Случайная статья Познавательные статьи Новые добавления Обратная связь FAQ Написать работу КАТЕГОРИИ: АрхеологияБиология Генетика География Информатика История Логика Маркетинг Математика Менеджмент Механика Педагогика Религия Социология Технологии Физика Философия Финансы Химия Экология ТОП 10 на сайте Приготовление дезинфицирующих растворов различной концентрацииТехника нижней прямой подачи мяча. Франко-прусская война (причины и последствия) Организация работы процедурного кабинета Смысловое и механическое запоминание, их место и роль в усвоении знаний Коммуникативные барьеры и пути их преодоления Обработка изделий медицинского назначения многократного применения Образцы текста публицистического стиля Четыре типа изменения баланса Задачи с ответами для Всероссийской олимпиады по праву Мы поможем в написании ваших работ! ЗНАЕТЕ ЛИ ВЫ?
Влияние общества на человека
Приготовление дезинфицирующих растворов различной концентрации Практические работы по географии для 6 класса Организация работы процедурного кабинета Изменения в неживой природе осенью Уборка процедурного кабинета Сольфеджио. Все правила по сольфеджио Балочные системы. Определение реакций опор и моментов защемления |
Ministry of education and Science, youth and sport of ukraine↑ Стр 1 из 6Следующая ⇒ Содержание книги
Поиск на нашем сайте
MINISTRY OF EDUCATION AND SCIENCE, YOUTH AND SPORT OF UKRAINE TERNOPIL NATIONAL ECONOMIC UNIVERSITY (TNEU) FACULTY OF COMPUTER INFORMATION TECHNOLOGIES AMERICAN-UKRAINIAN SCHOOL OF COMPUTER SCIENCE
LAB MANUAL of discipline “Algorithmization and Programming” for students of specialty 6.050101 – “Computer Science”
Ternopil Lab manual of discipline “Algorithmization and Programming” / I.Paliy, M.Komar. – Ternopil. – 2012. – 68 p. Approved by Department of Information Computing Systems and Control Meeting, Protocol # 11 of 15 May 2012
Author: Ihor Paliy, PhD, Assistance Professor of the Department for Information Computing Systems and Control, TNEU Myroslav Komar, Lecturer of the Department for Information Computing Systems and Control, TNEU
Reviewers: Anatoly Sachenko, DSc, Professor, Head of the Department for Information Computing Systems and Control, TNEU
Vasyl Yatskiv, PhD, Associate Professor, Department of Specialized Computer Systems, TNEU
CONTENT
INTRODUCTION Since its introduction less than a decade ago, C++ has experienced growing acceptance as a practical object-oriented programming language suitable for teaching, research, and commercial software development. The language has also rapidly evolved during this period and acquired a number of new features (e.g., templates and exception handling) which have added to its richness. It is the basis for Java, JavaScript and C#. This course is designed to present the basics of the language in a straight forward, easy to understand manner. It studies students the approaches for solving different problems using algorithms development and following computer programming. It teaches how to program in C++ and how to properly use its features. It does not attempt to teach object-oriented design to any depth. At the completion of this course, the student will be able to 1. Design algorithmic solutions to problems; 2. Understand the structure of a C/C++ language program including the use of variable definitions, data types, functions, scope and operators; 3. Translate a specific algorithm into correct, good commented C++ code using generally accepted programming style using: - various forms of Input/Output including files; - assignments; - if-else logic; - while, do, and for loops; - functions; - arrays; - strings and string functions, etc. 4. Be able to test a program, find and correct compile-time, run-time and logic errors.
Lab #1. C++ OPERATORS Goal: Learn how to program solutions for simple computing problems using C++ operators. Theory Arithmetic Operators C++ provides operators for composing arithmetic, relational, logical, bitwise, and conditional expressions. It also provides operators which produce useful side-effects, such as assignment, increment, and decrement. We will look at each category of operators in turn. We will also discuss the precedence rules which govern the order of operator evaluation in a multi-operator expression. C++ provides five basic arithmetic operators. These are summarized in the Table below.
Except for remainder (%) all other arithmetic operators can accept a mix of integer and real operands. Generally, if both operands are integers then the result will be an integer. However, if one or both of the operands are reals then the result will be a real (or double to be exact). When both operands of the division operator (/) are integers then the division is performed as an integer division and not the normal division we are used to. Integer division always results in an integer outcome (i.e., the result is always rounded down). For example:
9 / 2 // gives 4, not 4.5! -9 / 2 // gives -5, not -4!
Unintended integer divisions are a common source of programming errors. To obtain a real division when both operands are integers, you should cast one of the operands to be real:
int cost = 100; int volume = 80; double unitPrice = cost / (double) volume; // gives 1.25
The remainder operator (%) expects integers for both of its operands. It returns the remainder of integer-dividing the operands. For example 13%3 is calculated by integer dividing 13 by 3 to give an outcome of 4 and a remainder of 1; the result is therefore 1. It is possible for the outcome of an arithmetic operation to be too large for storing in a designated variable. This situation is called an overflow. The outcome of an overflow is machine-dependent and therefore undefined. For example:
unsigned char k = 10 * 92; // overflow: 920 > 255
It is illegal to divide a number by zero. This results in a run-time division-by-zero failure which typically causes the program to terminate.
Relational Operators C++ provides six relational operators for comparing numeric quantities. These are summarized in the Table below. Relational operators evaluate to 1 (representing the true outcome) or 0 (representing the false outcome).
Note that the <= and >= operators are only supported in the form shown. In particular, =< and => are both invalid and do not mean anything. The operands of a relational operator must evaluate to a number. Characters are valid operands since they are represented by numeric values. For example (assuming ASCII coding):
'A' < 'F' // gives 1 (is like 65 < 70)
The relational operators should not be used for comparing strings, because this will result in the string addresses being compared, not the string contents. For example, the expression
"HELLO" < "BYE"
causes the address of "HELLO" to be compared to the address of "BYE". As these addresses are determined by the compiler (in a machine-dependent manner), the outcome may be 0 or may be 1, and is therefore undefined. C++ provides library functions (e.g., strcmp) for the lexicographic comparison of string. These will be described later in the book.
Logical Operators C++ provides three logical operators for combining logical expression. These are summarized in the Table below. Like the relational operators, logical operators evaluate to 1 or 0.
Logical negation is a unary operator, which negates the logical value of its single operand. If its operand is nonzero it produces 0, and if it is 0 it produces 1. Logical and produces 0 if one or both of its operands evaluate to 0. Otherwise, it produces 1. Logical or produces 0 if both of its operands evaluate to 0. Otherwise, it produces 1. Note that here we talk of zero and nonzero operands (not zero and 1). In general, any nonzero value can be used to represent the logical true, whereas only zero represents the logical false. The following are, therefore, all valid logical expressions:
!20 // gives 0 10 && 5 // gives 1 10 || 5.5 // gives 1 10 && 0 // gives 0
Increment/Decrement Operators The auto increment (++) and auto decrement (--) operators provide a convenient way of, respectively, adding and subtracting 1 from a numeric variable. These are summarized in the Table below. The examples assume the following variable definition:
int k = 5;
Both operators can be used in prefix and postfix form. The difference is significant. When used in prefix form, the operator is first applied and the outcome is then used in the expression. When used in the postfix form, the expression is evaluated first and then the operator applied. Both operators may be applied to integer as well as real variables, although in practice real variables are rarely useful in this form.
Assignment Operator The assignment operator is used for storing a value at some memory location (typically denoted by a variable). Its left operand should be an lvalue, and its right operand may be an arbitrary expression. The latter is evaluated and the outcome is stored in the location denoted by the lvalue. An lvalue (standing for left value) is anything that denotes a memory location in which a value may be stored. The only kind of lvalue we have seen so far in this book is a variable. The assignment operator has a number of variants, obtained by combining it with the arithmetic and bitwise operators. These are summarized in the Table below. The examples assume that n is an integer variable.
An assignment operation is itself an expression whose value is the value stored in its left operand. An assignment operation can therefore be used as the right operand of another assignment operation. Any number of assignments can be concatenated in this fashion to form one expression. For example:
int m, n, p; m = n = p = 100; // means: n = (m = (p = 100)); m = (n = p = 100) + 2; // means: m = (n = (p = 100)) + 2;
This is equally applicable to other forms of assignment. For example:
m = 100; m += n = p = 10; // means: m = m + (n = p = 10);
Conditional Operator The conditional operator takes three operands. It has the general form:
operand1? operand2: operand3
First operand1 is evaluated, which is treated as a logical condition. If the result is nonzero then operand2 is evaluated and its value is the final result. Otherwise, operand3 is evaluated and its value is the final result. For example:
int m = 1, n = 2; int min = (m < n? m: n); // min receives 1
Note that of the second and the third operands of the conditional operator only one is evaluated. This may be significant when one or both contain side-effects (i.e., their evaluation causes a change to the value of a variable). For example, in
int min = (m < n? m++: n++);
m is incremented because m++ is evaluated but n is not incremented because n++ is not evaluated. Because a conditional operation is itself an expression, it may be used as an operand of another conditional operation, that is, conditional expressions may be nested. For example:
int m = 1, n = 2, p =3; int min = (m < n? (m < p? m: p): (n < p? n: p));
Comma Operator Multiple expressions can be combined into one expression using the comma operator. The comma operator takes two operands. It first evaluates the left operand and then the right operand, and returns the value of the latter as the final outcome. For example:
int m, n, min; int mCount = 0, nCount = 0; //... min = (m < n? mCount++, m: nCount++, n);
Here when m is less than n, mCount++ is evaluated and the value of m is stored in min. Otherwise, nCount++ is evaluated and the value of n is stored in min.
The sizeof Operator C++ provides a useful operator, sizeof, for calculating the size of any data item or type. It takes a single operand which may be a type name (e.g., int) or an expression (e.g., 100) and returns the size of the specified entity in bytes. The outcome is totally machine-dependent. Listing below illustrates the use of sizeof on the built-in types we have encountered so far.
void main () { cout << "char size = " << sizeof(char) << " bytes\n"; cout << "char* size = " << sizeof(char*) << " bytes\n"; cout << "short size = " << sizeof(short) << " bytes\n"; cout << "int size = " << sizeof(int) << " bytes\n"; cout << "long size = " << sizeof(long) << " bytes\n"; cout << "float size = " << sizeof(float) << " bytes\n"; cout << "double size = " << sizeof(double) << " bytes\n"; cout << "1.55 size = " << sizeof(1.55) << " bytes\n"; cout << "1.55L size = " << sizeof(1.55L) << " bytes\n"; cout << "HELLO size = " << sizeof("HELLO") << " bytes\n"; }
When run, the program will produce the following output: char size = 1 bytes char* size = 2 bytes short size = 2 bytes int size = 2 bytes long size = 4 bytes float size = 4 bytes double size = 8 bytes 1.55 size = 8 bytes 1.55L size = 10 bytes HELLO size = 6 bytes
Operator Precedence The order in which operators are evaluated in an expression is significant and is determined by precedence rules. These rules divide the C++ operators into a number of precedence levels (see the Table below). Operators in higher levels take precedence over operators in lower levels. For example, in
a == b + c * d
c * d is evaluated first because * has a higher precedence than + and ==. The result is then added to b because + has a higher precedence than ==, and then == is evaluated. Precedence rules can be overridden using brackets. For example, rewriting the above expression as
a == (b + c) * d
causes + to be evaluated before *.
Operators with the same precedence level are evaluated in the order specified by the last column of the Table above. For example, in
a = b += c
the evaluation order is right to left, so first b += c is evaluated, followed by
Simple Type Conversion A value in any of the built-in types we have seen so far can be converted (type-cast) to any of the other types. For example:
(int) 3.14 // converts 3.14 to an int to give 3 (long) 3.14 // converts 3.14 to a long to give 3L (double) 2 // converts 2 to a double to give 2.0 (char) 122 // converts 122 to a char whose code is 122 (unsigned short) 3.14 // gives 3 as an unsigned short
As shown by these examples, the built-in type identifiers can be used as type operators. Type operators are unary (i.e., take one operand) and appear inside brackets to the left of their operand. This is called explicit type conversion. When the type name is just one word, an alternate notation may be used in which the brackets appear around the operand:
int(3.14) // same as: (int) 3.14
In some cases, C++ also performs implicit type conversion. This happens when values of different types are mixed in an expression. For example:
double d = 1; // d receives 1.0 int i = 10.5; // i receives 10 i = i + d; // means: i = int(double(i) + d)
In the last example, i + d involves mismatching types, so i is first converted to double (promoted) and then added to d. The result is a double which does not match the type of i on the left side of the assignment, so it is converted to int (demoted) before being assigned to i. The above rules represent some simple but common cases for type conversion. More complex cases will be examined later in the book after we have discussed other data types and classes.
Lab Overview 2.1. Read the theory and try Control Exercises. 2.2. Develop the algorithm flowchart to solve a problem according to individual case from the Table below. 2.3. Write the program code according to the developed algorithm. 2.4. Debug the program, run it and make screenshots. 2.5. Prepare the Lab Report according to the required structure.
Report Structure - Title page (Annex A) - Task overview - Algorithm’s flowchart - Program code - Program running screenshots - Conclusions
Control Exercises 4.1. Write expressions for the following: - To test if a number n is even. - To test if a character c is a digit. - To test if a character c is a letter. - To do the test: n is odd and positive or n is even and negative. - To give the absolute value of a number n. - To give the number of characters in a null-terminated string literal s.
4.2. Add extra brackets to the following expressions to explicitly show the order in which the operators are evaluated: (n <= p + q && n >= p - q || n == 0) (++n * q-- / ++p - q) (n | p & q ^ p << 2 + q) (p < q? n < p? q * n - 2: q / n + 1: q - n)
4.3. What will be the value of each of the following variables after its initialization? double d = 2 * int(3.14); long k = 3.14 - 3; char c = 'a' + 2; char c = 'p' + 'A' - 'a';
References 5.1. Juan Soulié. C++ Language Tutorial. – 2007. – p. 21-33. 5.2. Sharam Hekmat. C++ Essentials. – PragSoft Corporation 2005. – p. 17-29. 5.3. Prata S. C++ Primer Plus (5th Edition). – Sams, 2004. – p. 95-108. Lab #2. CONTROL STRUCTURES Goal: program the problem solving using C++ control structures Theory Statements represent the lowest-level building blocks of a program. A running program spends all of its time executing statements. The order in which statements are executed is called control flow. This term reflect the fact that the currently executing statement has the control of the CPU, which when completed will be handed over (flow) to another statement. Flow control in a program is typically sequential, from one statement to the next, but may be diverted to other paths by branch statements. Flow control is an important consideration because it determines what is executed during a run and what is not, therefore affecting the overall outcome of the program. Like many other procedural languages, C++ provides different forms of statements for different purposes. Declaration statements are used for defining variables. Assignment-like statements are used for simple, algebraic computations. Branching statements are used for specifying alternate paths of execution, depending on the outcome of a logical condition. Loop statements are used for specifying computations which need to be repeated until a certain logical condition is satisfied. Flow control statements are used to divert the execution path to another part of the program.
The if Statement It is sometimes desirable to make the execution of a statement dependent upon a condition being satisfied. The if statement provides a way of expressing this, the general form of which is:
if (expression) statement;
First expression is evaluated. If the outcome is nonzero then statement is executed. Otherwise, nothing happens. For example, when dividing two values, we may want to check that the denominator is nonzero:
if (count!= 0) average = sum / count;
To make multiple statements dependent on the same condition, we can use a compound statement:
if (balance > 0) { interest = balance * creditRate; balance += interest; }
A variant form of the if statement allows us to specify two alternative statements: one which is executed if a condition is satisfied and one which is executed if the condition is not satisfied. This is called the if-else statement and has the general form:
if (expression) statement1; else statement2;
First expression is evaluated. If the outcome is nonzero then statement1 is executed. Otherwise, statement2 is executed. For example:
if (balance > 0) { interest = balance * creditRate; balance += interest; } else { interest = balance * debitRate; balance += interest; }
If statements may be nested by having an if statement appear inside another if statement. For example:
if (callHour > 6) { if (callDuration <= 5) charge = callDuration * tarrif1; else charge = 5 * tarrif1 + (callDuration - 5) * tarrif2; } else charge = flatFee;
The switch Statement The switch statement provides a way of choosing between a set of alternatives, based on the value of an expression. The general form of the switch statement is:
switch (expression) { case constant1: statements; ... case constantn: statements; default: statements; }
First expression (called the switch tag) is evaluated, and the outcome is compared to each of the numeric constants (called case labels), in the order they appear, until a match is found. The statements following the matching case are then executed. Note the plural: each case may be followed by zero or more statements (not just one statement). Execution continues until either a break statement is encountered or all intervening statements until the end of the switch statement are executed. The final default case is optional and is exercised if none of the earlier cases provide a match. For example, suppose we have parsed a binary arithmetic operation into its three components and stored these in variables operator, operand1, and operand2. The following switch statement performs the operation and stored the result in result.
switch (operator) { case '+': result = operand1 + operand2; break; case '-': result = operand1 - operand2; break; case '*': result = operand1 * operand2; break; case '/': result = operand1 / operand2; break; default: cout << "unknown operator: " << operator << '\n'; } As illustrated by this example, it is usually necessary to include a break statement at the end of each case. The break terminates the switch statement by jumping to the very end of it. There are, however, situations in which it makes sense to have a case without a break.
The while Statement The while statement (also called while loop) provides a way of repeating an statement while a condition holds. It is one of the three flavors of iteration in C++. The general form of the while statement is:
while (expression) statement;
First expression (called the loop condition) is evaluated. If the outcome is nonzero then statement (called the loop body) is executed and the whole process is repeated. Otherwise, the loop is terminated. For example, suppose we wish to calculate the sum of all numbers from 1 to some integer denoted by n. This can be expressed as:
n = 5; i = 1; sum = 0; while (i <= n) { sum += i; i++; }
For n set to 5, Table 1 provides a trace of the loop by listing the values of the variables involved and the loop condition.
The do Statement The do statement (also called do loop) is similar to the while statement, except that its body is executed first and then the loop condition is examined. The general form of the do statement is:
do statement; while (expression);
First statement is executed and then expression is evaluated. If the outcome of the latter is nonzero then the whole process is repeated. Otherwise, the loop is terminated. The do loop is less frequently used than the while loop. It is useful for situations where we need the loop body to be executed at least once, regardless of the loop condition. For example, suppose we wish to repeatedly read a value and print its square, and stop when the value is zero. This can be expressed as the following loop:
do { cin >> n; cout << n * n << '\n'; } while (n!= 0);
Unlike the while loop, the do loop is never used in situations where it would have a null body. Although a do loop with a null body would be equivalent to a similar while loop, the latter is always preferred for its superior readability.
The for Statement The for statement (also called for loop) is similar to the while statement, but has two additional components: an expression which is evaluated only once before everything else, and an expression which is evaluated once at the end of each iteration. The general form of the for statement is:
for (expression1; expression2; expression3) statement;
First expression1 is evaluated. Each time round the loop, expression2 is evaluated. If the outcome is nonzero then statement is executed and expression3 is evaluated. Otherwise, the loop is terminated. The general for loop is equivalent to the following while loop:
expression1; while (expression2) { statement; expression3; }
The most common use of for loops is for situations where a variable is incremented or decremented with every iteration of the loop. The following for loop, for example, calculates the sum of all integers from 1 to n.
sum = 0; for (i = 1; i <= n; ++i) sum += i;
This is preferred to the while-loop version we saw earlier. In this example, i is usually called the loop variable. C++ allows the first expression in a for loop to be a variable definition. In the above loop, for example, i can be defined inside the loop itself:
for (int i = 1; i <= n; ++i) sum += i;
Contrary to what may appear, the scope for i is not the body of the loop, but the loop itself. Any of the three expressions in a for loop may be empty. For example, removing the first and the third expression gives us something identical to a while loop:
for (; i!= 0;) // is equivalent to: while (i!= 0) something; // something;
Removing all the expressions gives us an infinite loop. This loop's condition is assumed to be always true:
for (;;) // infinite loop something;
For loops with multiple loop variables are not unusual. In such cases, the comma operator is used to separate their expressions:
for (i = 0, j = 0; i + j < n; ++i, ++j) something;
Because loops are statements, they can appear inside other loops. In other words, loops can be nested. For example,
for (int i = 1; i <= 3; ++i) for (int j = 1; j <= 3; ++j) cout << '(' << i << ',' << j << ")\t";
produces the product of the set {1,2,3} with itself, giving the output:
(1,1) (1,2) (1,3) (2,1) (2,2) (2,3) (3,1) (3,2) (3,3)
The continue Statement The continue statement terminates the current iteration of a loop and instead jumps to the next iteration. It applies to the loop immediately enclosing the continue statement. It is an error to use the continue statement outside a loop. In while and do loops, the next iteration commences from the loop condition. In a for loop, the next iteration commences from the loop’s third expression. For example, a loop which repeatedly reads in a number, processes it but ignores negative numbers, and terminates when the number is zero, may be expressed as:
do { cin >> num; if (num < 0) continue; // process num here... } while (num!= 0);
When the continue statement appears inside nested loops, it applies to the loop immediately enclosing it, and not to the outer loops.
The break Statement A break statement may appear inside a loop (while, do, or for) or a switch statement. It causes a jump out of these constructs, and hence terminates them. Like the continue statement, a break statement only applies to the loop or switch immediately enclosing it. It is an error to use the break statement outside a loop or a switch. For example, suppose we wish to read in a user password, but would like to allow the user a limited number of attempts:
for (i = 0; i < attempts; ++i) { cout << "Please enter your password: "; cin >> password; if (Verify(password)) // check password for correctness break; // drop out of the loop cout << "Incorrect!\n"; }
Here we have assumed that there is a function called Verify which checks a password and returns true if it is correct, and false otherwise.
The goto Statement The goto statement provides the lowest-level of jumping. It has the general form:
goto label;
where label is an identifier which marks the jump destination of goto. The label should be followed by a colon and appear before a statement within the same function as the goto statement itself. For example, the role of the break statement in the for loop in the previous section can be emulated by a goto:
for (i = 0; i < attempts; ++i) { cout << "Please enter your password: "; cin >> password; if (Verify(password)) // check password for correctness goto out; // drop out of the loop cout << "Incorrect!\n"; } out: //etc...
Because goto provides a free and unstructured form of jumping (unlike break and continue), it can be easily misused. Most programmers these days avoid using it altogether in favor of clear programming.
The return Statement The return statement enables a function to return a value to its caller. It has the general form:
return expression;
where expression denotes the value returned by the function. The type of this value should match the return type of the function. For a function whose return type is void, expression should be empty:
return;
The only function we have discussed so far is main, whose return type is always int. The return value of main is what the program returns to the operating system when it completes its execution. Under UNIX, for example, it its conventional to return 0 from main when the program executes without errors. Otherwise, a non-zero error code is returned. For example:
int main (void) { cout << "Hello World\n"; return 0; }
When a function has a non-void return value (as in the above example), failing to return a value will result in a compiler warning.
Lab Overview 2.1. Read the theory and try Control Exercises. 2.2. Develop the algorithm flowchart to solve a problem according to individual case from the Table below. 2.3. Write the program code according to the developed algorithm. 2.4. Debug the program, run it and make screenshots. 2.5. Prepare the Lab Report according to the required structure.
Report Structure - Title page - Task overview - Algorithm’s flowchart - Program code - Program running screenshots - Conclusions
Control Exercises 4.1. Assuming that n is 20, what will the following code fragment output when executed? if (n >= 0) if (n < 10) cout << "n is small\n"; else cout << "n is negative\n";
4.2. Assuming that n is 1, what will the following code fragment output when executed? switch(n) { case 0: cout << “Zero”; case 1: cout << “One”; case 2: cout << “Two”; }
4.3. Assuming that n is 2, what will the following code fragment output when executed? switch(n) { case 0: cout << “Zero”; break; case 1: cout << “One”; break; case 2: cout << “Two”; break; default: cout << “Other”; }
4.4. Assuming that n is 3, what will the following code fragment output when executed? switch(n) { case 0: cout << “Zero”; break; case 1: cout << “One”; break; case 2: cout << “Two”; break; default: cout << “Other”; }
4.5. What will the following code fragment output when executed? for (i=5; i>=-5; i--) { if (i == 0) continue; cout << i; }
4.6. What will the following code fragment output when executed? n = 3; while (n=5) cout << n;
4.7. How many ‘*’ will appear on the screen when executed? i=1; do { cout << “*”; i++; } while (i<5);
References 5.1. Juan Soulié. C++ Language Tutorial. – 2007. – p. 34-40. 5.2. Sharam Hekmat. C++ Essentials. – PragSoft Corporation 2005. – p. 30-44. 5.3. Prata S. C++ Primer Plus (5th Edition). – Sams, 2004. – p. 178-262. Lab #3. C++ USER-DEFINED FUNCTIONS Goal: program numerical integration with different integration methods using C++ user-defined functions Theory A function provides a convenient way of packaging a computational recipe, so that it can be used as often as required. A function definition consists of two parts: interface and body. The interface of a function (also called its prototype) specifies how it may be used. It consists of three entities: - The function name. This is simply a unique identifier. - The function parameters (also called its signature). This is a set of zero or more typed identifiers used for passing values to and from the function. - The function return type. This specifies the type of value the function returns. A function which returns nothing should have the return type void. The body of a function contains the computational steps (statements) that comprise the function. Using a function involves ‘calling’ it. A function call consists of the function name followed by the call operator brackets ‘()’, inside which zero or more comma-separated arguments appear. The number of arguments should match the number of function parameters. Each argument is an expression whose type should match the type of the corresponding parameter in the function interface. When a function call is executed, the arguments are first evaluated and their resulting values are assigned to the corresponding parameters. The function body is then executed. Finally, the function return value (if any) is passed to the caller. Since a call to a function whose return type is non- void yields a return value, the call is an expression and may be used in other expressions. By contrast, a call to a function whose return type is void is a statement.
A Simple Function Listing 1 shows the definition of a simple function which raises an integer to the power of another, positive integer.
Listing 1 1 int Power (int base, unsigned int exponent) 2 { 3 int result = 1; 4 for (int i = 0; i < exponent; ++i) 5 result *= base; 6 return result; 7 }
Annotation 1 This line defines the function interface. It starts with the return type of the function (int in this case). The function name appears next followed by its parameter list. Power has two parameters (base and exponent) which are of types int and unsigned int, respectively Note that the syntax for parameters is similar to the syntax for defining variables: type identifier followed by the parameter name. However, it is not possible to follow a type identifier with multiple comma-separated parameters:
int Power (int base, exponent) // Wrong!
2 This brace marks the beginning of the function body. 3 This line is a local variable definition. 4-5 This for-loop raises base to the power of exponent and stores the outcome in result. 6 This line returns result as the return value of the function. 7 This brace marks the end of the function body. Listing 2 illustrates how this function is called. The effect of this call is that first the argument values 2 and 8 are, respectively, assigned to the parameters base and exponent, and then the function body is evaluated.
Listing 2 1 #include <iostream> 2 void main () 3 { 4 cout << "2 ^ 8 = " << Power(2,8) << '\n'; 5 }
When run, this program will produce the following output: 2 ^ 8 = 256
In general, a function should be declared before its is used. A function declaration simply consists of the function prototype, which specifies the function name, parameter types, and return type. Line 2 in Listing 3 shows how Power may be declared for the above program. A function may be declared without its parameter names. Listing 3 1 #include <iostream>
2 int Power (int base, unsigned int exponent); // function declaration
3 void main (void) 4 { 5 cout << "2 ^ 8 = " << Power(2,8) << '\n'; 6 }
7 int Power (int base, unsigned int exponent) 8 { 9 int result = 1; 10 for (int i = 0; i < exponent; ++i) 11 result *= base; 12 return result; 13 }
Because a function definition contains a prototype, it also serves as a declaration. Therefore if the definition of a function appears before its use, no additional declaration is needed.
Parameters and Arguments C++ supports two styles of parameters: value and reference. A value parameter receives a copy of the value of the argument passed to it. As a result, if the function makes any changes to the parameter, this will not affect the argument. For example, in
#include <iostream>
void Foo (int num) { num = 0; cout << "num = " << num << '\n'; }
void main () { int x = 10; Foo(x); cout << "x = " << x << '\n'; }
the single parameter of Foo is a value parameter. As far as this function is concerned, num behaves just like a local variable inside the function. When the function is called and x passed to it, num receives a copy of the value of x. As a result, although num is set to 0 by the function, this does not affect x. The program produces the following output:
num = 0; x = 10;
A reference parameter, on the other hand, receives the argument passed to it and works on it directly. Any changes made by the function to a reference parameter is in effect directly applied to the argument. Within the context of function calls, the two styles of passing arguments are, respectively, called pass-by-value and pass-by-reference.
Global and Local Scope Everything defined at the program scope level (i.e., outside functions and classes) is said to have a global scope. Thus the sample functions we have seen so far all have a global scope. Variables may also be defined at the global scope:
int year = 1994; // global variable int Max (int, int); // global function
int main (void) // global function { //... }
Uninitialized global variables are automatically initialized to zero. Since global entities are visible at the program level, they must also be unique at the program level. This means that the same global variable or function may not be defined more than once at the global level. (However, as we will see later, a function name may be reused so long as its signature remains unique.) Global entities are generally accessible everywhere in the program. Each block in a program defines a local scope. Thus the body of a function represents a local scope. The parameters of a function have the same scope as the function body. Variables defined within a local scope are visible to that scope only. Hence, a variable need only be unique within its own scope. Local scopes may be nested, in which case the inner scopes override the outer scopes. For example, in
int xyz; // xyz is global
void Foo (int xyz) { // xyz is local to the body of Foo if (xyz > 0) { double xyz; // xyz is local to this block //... } }
there are three distinct scopes, each containing a distinct xyz. Generally, the lifetime of a variable is limited to its scope. So, for example, global variables last for the duration of program execution, while local variables are created when their scope is entered and destroyed when their scope is exited. The memory space for global variables is reserved prior to program execution commencing, whereas the memory space for local variables is allocated on the fly during program execution.
Scope Operator Because a local scope overrides the global scope, having a local variable with the same name as a global variable makes the latter inaccessible to the local scope. For example, in
int error;
void Error (int error) { //... }
the global error is inaccessible inside Error, because it is overridden by the local error parameter. This problem is overcome using the unary scope operator:: which takes a global entity as argument:
int error;
void Error (int error) { //... if (::error!= 0) // refers to global error //... }
Symbolic Constants Preceding a variable definition by the keyword const makes that variable read-only (i.e., a symbolic constant). A constant must be initialized to some value when it is defined. For example:
const int maxSize = 128; const double pi = 3.141592654;
Once defined, the value of a constant cannot be changed:
maxSize = 256; // illegal!
A constant with no type specifier is assumed to be of type int:
const maxSize = 128; // maxSize is of type int
With pointers, two aspects need to be considered: the pointer itself, and the object pointed to, either of which or both can be constant:
const char *str1 = "pointer to constant"; char *const str2 = "constant pointer"; const char *const str3 = "constant pointer to constant"; str1[0] = 'P'; // illegal! str1 = "ptr to const"; // ok str2 = "const ptr"; // illegal! str2[0] = 'P'; // ok str3 = "const to const ptr"; // illegal! str3[0] = 'C'; // illegal!
A function parameter may also be declared to be constant. This may be used to indicate that the function does not change the value of a parameter:
int Power (const int base, const unsigned int exponent) { //... }
A function may also return a constant result:
const char* SystemVersion () { return "5.2.1"; }
Inline Functions Suppose that a program frequently requires to find the absolute value of an integer quantity. For a value denoted by n, this may be expressed as:
(n > 0? n: -n)
However, instead of replicating this expression in many places in the program, it is better to define it as a function:
int Abs (int n) { return n > 0? n: -n; }
The function version has a number of advantages. First, it leads to a more readable program. Second, it is reusable. And third, it avoid undesirable side-effects when the argument is itself an expression with side-effects. The disadvantage of the function version, however, is that its frequent use can lead to a considerable performance penalty due to the overheads associated with calling a function. For example, if Abs is used within a loop which is iterated thousands of times, then it will have an impact on performance. The overhead can be avoided by defining Abs as an inline function:
inline int Abs (int n) { return n > 0? n: -n; }
The effect of this is that when Abs is called, the compiler, instead of generating code to call Abs, expands and substitutes the body of Abs in place of the call. While essentially the same computation is performed, no function call is involved and hence no stack frame is allocated. Because calls to an inline function are expanded, no trace of the function itself will be left in the compiled code. Therefore, if a function is defined inline in one file, it may not be available to other files. Consequently, inline functions are commonly placed in header files so that they can be shared. Generally, the use of inline should be restricted to simple, frequently used functions.
Recursion A function which calls itself is said to be recursive. Recursion is a general programming technique applicable to problems which can be defined in terms of themselves. Take the factorial problem, for instance, which is defined as: - Factorial of 0 is 1. - Factorial of a positive number n is n times the factorial of n-1. The second line clearly indicates that factorial is defined in terms of itself and hence can be expressed as a recursive function:
int Factorial (unsigned int n) { return n == 0? 1: n * Factorial(n-1); }
For n set to 3, the Table below provides a trace of the calls to Factorial. The stack frames for these calls appear sequentially on the runtime stack, one after the other.
A recursive function must have at least one termination condition which can be satisfied. Otherwise, the function will call itself indefinitely until the runtime stack overflows. The Factorial function, for example, has the termination condition n == 0 which, when satisfied, causes the recursive calls to fold back. As a general rule, all recursive functions can be rewritten using iteration. For factorial, for example, an iterative version will be:
int Factorial (unsigned int n) { int result = 1; while (n > 0) result *= n--; return result; }
Default Arguments Default argument is a programming convenience which removes the burden of having to specify argument values for all of a function’s parameters. For example, consider a function for reporting errors:
void Error (char *message, int severity = 0);
Here, severity has a default argument of 0; both the following calls are therefore valid:
Error("Division by zero", 3); // severity set to 3 Error("Round off error"); // severity set to 0
As the first call illustrates, a default argument may be overridden by explicitly specifying an argument.
Lab Overview 2.1. Read the theory and try Control Exercises. 2.2. Develop the algorithm flowchart to solve a numerical integration problem using Rectangle method, Trapezoidal rule and Simpson's rule for a function according to individual case from the Table below. Rectangle method: , where Trapezoidal rule: , where Simpson's rule: , where An interval beginning (a), end (b) and subintervals number (n) should be inputted from a keyboard. User-defined functions should be used to calculate a function value and integral values according to each numerical integration method. 2.3. Write the program code according to the developed algorithm. 2.4. Debug the program, run it and make screenshots. 2.5. Prepare the Lab Report according to the required structure.
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Последнее изменение этой страницы: 2016-04-18; просмотров: 286; Нарушение авторского права страницы; Мы поможем в написании вашей работы! infopedia.su Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав. Обратная связь - 3.15.148.203 (0.015 с.) |