EduLadder(ELADR) - CRYPTO

This is designed to incentify community members as a proof of contribution token.


Using this You can,Buy courses,Reward others and exchange for real money.


WHITE PAPER COURSES

Real Problems! Real Experts!

Join Our Telegram Channel !


The Eduladder is a community of students, teachers, and programmers just interested to make you pass any exams. So we help you to solve your academic and programming questions fast.
In eduladder you can Ask,Answer,Listen,Earn and Download Questions and Question papers.
Watch related videos of your favorite subject.
Connect with students from different parts of the world.
Apply or Post Jobs, Courses ,Internships and Volunteering opportunity. For FREE
See Our team
Wondering how we keep quality?
Got unsolved questions? Ask Questions


You are here:Open notes-->VTU-->Object-oriented--programming-with-c10CS36-unit-1

Object oriented programming with c++[10CS36] unit-1

UNIT I INTRODUCTION

Topics covered

Introduction: Overview of C++, Sample C++ program, Different data types, operators, expressions, and statements, arrays and strings, pointers & user defined types Function Components, argument passing, inline functions, function overloading, recursive functions

UNIT-1 Summary

To understand C++ is to understand the forces that drove its creation, the ideas that shaped it, and the legacy it inherits. Thus, the story of C++ begins with C. This chapter presents an overview of the C programming language, its origins, its uses, and its underlying philosophy. Because C++ is built upon C, this chapter provides an important historical perspective on the roots of C++. Much of what “makes C++, C++” had its genesis in the C language

1. Overview of C++

C++ began as an expanded version of C. The C++ extensions were first invented by Bjarne Stroustrup in 1979 at Bell Laboratories in Murray Hill, New Jersey. He initially called the new language "C with Classes." However, in 1983 the name was changed to C++. Although C was one of the most liked and widely used professional programming languages in the world, the invention of C++ was necessitated by one major programming factor: increasing complexity.

2. Sample C++ program

A Sample C++ Program Let's start with the short sample C++ program shown here.

#include

int main()

{

int i;

cout << "This is output.\n"; // this is a single line comment

/* you can still use C style comments */

// input a number using >>

cout << "Enter a number: ";

cin >> i;

// now, output a number using <<

cout << i << " squared is " << i*i << "\n";

return 0;

}

3. Different data types

4. Operators, expressions, and statements,

C/C++ is rich in built-in operators. In fact, it places more significance on operators than do most other computer languages. There are four main classes of operators: arithmetic, relational, logical, and bitwise. In addition, there are some special operators for particular tasks.

4.1 The Assignment Operator

You can use the assignment operator within any valid expression. This is not the case with many computer languages (including Pascal, BASIC, and FORTRAN), which treat the assignment operator as a special case statement. The general form of the assignment operator is variable_name = expression; where an expression may be as simple as a single constant or as complex as you require. C/C++ uses a single equal sign to indicate assignment (unlike Pascal or Modula-2, which use the := construct). The target, or left part, of the assignment must be a variable or a pointer, not a function or a constant. Frequently in literature on C/C++ and in compiler error messages you will see these two terms: lvalue and rvalue. Simply put, an lvalue is any object that can occur on the left side of an assignment statement. For all practical purposes, "lvalue" means "variable." The term rvalue refers to expressions on the right side of an assignment and simply means the value of an expression.

4.2 Type Conversion in Assignments

When variables of one type are mixed with variables of another type, a type conversion will occur. In an assignment statement, the type conversion rule is easy: The value of the right side (expression side) of the assignment is  converted to the type of the left side (target variable), as illustrated here: Table 2-3 summarizes the assignment type conversions. Remember that the conversion of an int to a float, or a float to a double, and so on, does not add any precision or accuracy. These kinds of conversions only change the form in which the value is represented. In addition, some compilers always treat a char variable as positive, no matter what value it has, when converting it to an int or float. Other compilers treat char variable values greater than 127 as negative numbers when converting. Generally

-------------------table 2.3The Outcome of Common Type Conversion--------------------

Target Type             Expression Type             Possible Info Loss

signed char                 char                                 If value > 127, target is negative

char short                   int                                    High-order 8 bits

char                           int (16 bits)                       High-order 8 bits

char                           int (32 bits)                       High-order 24 bits

char                           long int                             High-order 24 bits

short int                      int (16 bits)                       None

short int                      int (32 bits)                       High-order 16 bits

int (16 bits)                long int High-order             16 bits

int (32 bits)                long int                             None

int float                      Fractional part and            possibly more

float                         double                               Precision,result rounded

double                      long double                       Precision, result rounded

-----------------------------------Ends here---------------------------------

4.3 Multiple Assignments

C/C++ allows you to assign many variables the same value by using multiple assignments in a single statement. For example, this program fragment assigns x, y, and z the value 0: x = y = z = 0; In professional programs, variables are frequently assigned common values using this method.

4.4 Arithmetic Operators

Table 2-4 lists C/C++'s arithmetic operators

4.5 Increment and Decrement

C/C++ includes two useful operators not found in some other computer languages.These are the increment and decrement operators, ++ and - -. The operator ++ adds 1 toits operand, and - - subtracts 1. In other words:

x++;

is the same as

++x;

and

x=x+1;

5. Arrays and strings

An array is a collection of variables of the same type that are referred to through a common name. A specific element in an array is accessed by an index. In C/C++, all arrays consist of contiguous memory locations. The lowest address corresponds to the first element and the highest address to the last element. Arrays may have from one to several dimensions. The most common array is the null-terminated string, which is simply an array of characters terminated by a null.

5.1 Single-Dimension Arrays

The general form for declaring a single-dimension array is type var_name[size]; Like other variables, arrays must be explicitly declared so that the compiler may allocate space for them in memory. Here, type declares the base type of the array, which is the type of each element in the array, and size defines how many elements the array will hold. For example, to declare a 100-element array called balance of type double, use this statement:

double balance[100];

An element is accessed by indexing the array name. This is done by placing the index of the element within square brackets after the name of the array. For example,

balance[3] = 12.23;

Null-Terminated Strings

By far the most common use of the one-dimensional array is as a character string. C++ supports two types of strings. The first is the null-terminated string, which is a null-terminated character array. (A null is zero.) Thus a null-terminated string contains the characters that comprise the string followed by a null. This is the only type of string defined by C, and it is still the most widely used. Sometimes null-terminated strings are called C-strings. C++ also defines a string class, called string, which provides an object-oriented approach to string handling. It is described later in this book. Here, null-terminated strings are examined. When declaring a character array that will hold a null-terminated string, you need to declare it to be one character longer than the largest string that it is tohold. For example, to declare an array str that can hold a 10-character string, you would write This makes room for the null at the end of the string. When you use a quoted string constant in your program, you are also creating a null-terminated string. A string constant is a list of characters enclosed in double quotes. For example, "hello there" You do not need to add the null to the end of string constants manually—the compiler does this for you automatically. C/C++ supports a wide range of functions that manipulate null-terminated strings.

The most common are

Name                                                             Function

strcpy(s1, s2)                                                 Copies s2 into s1.

strcat(s1, s2)                                                 Concatenates s2 onto the end of s1.

strlen(s1)                                                      Returns the length of s1.

strcmp(s1, s2)                                               Returns 0 if s1 and s2 are the same; less                                                                       than

                                                                    0 if s1<s2; greater than 0 if s1>s2.

strchr(s1, ch)                                                Returns a pointer to the first occurrence of

ch in s1.

strstr(s1, s2)                                                Returns a pointer to the first occurrence of

s2 in s1.

These functions use the standard header file string.h. (C++ programs can also use the C++-style header <cstring>.) The following program illustrates the use of these string functions:

5.2 Two-Dimensional Arrays

C/C++ supports multidimensional arrays. The simplest form of the multidimensional array is the two-dimensional array. A two-dimensional array is, essentially, an array of one-dimensional arrays. To declare a twodimensional integer array d of size 10,20, you would write int d[10][20]; Pay careful attention to the declaration. Some other computer languages use commas to separate the array dimensions; C/C++, in contrast, places each dimension in its own set of brackets. Similarly, to access point 1,2 of array d, you would use

Two-dimensional arrays are stored in a row-column matrix, where the first index indicates the row and the second indicates the column. This means that the rightmost index changes faster than the leftmost when accessing the elements in the array in the order in which they are actually stored in memory. In the case of a two-dimensional array, the following formula yields the number of bytes of memory needed to hold it: bytes = size of 1st index x size of 2nd index x sizeof(base type) Therefore, assuming 4-byte integers, an integer array with dimensions 10,5 would have 10 x 5 x 4 or 200 bytes allocated.

5.3 Multidimensional Arrays

C/C++ allows arrays of more than two dimensions. The exact limit, if any, is determined by your compiler. The general form of a multidimensional array declaration is type name[Size1][Size2][Size3]. . .[SizeN]; Arrays of more than three dimensions are not often used because of the amount of memory they require. For example, a four-dimensional character array with dimensions 10,6,9,4 requires 10 * 6 * 9 * 4

or 2,160 bytes. If the array held 2-byte integers, 4,320 bytes would be needed. If the array held doubles (assuming 8 bytes per double), 17,280 bytes would be required.

The storage required increases exponentially with the number of dimensions. For example, if a fifth dimension of size 10 was added to the preceding array, then 172, 800 bytes would be required. In multidimensional arrays, it takes the computer time to compute each index. This means that accessing an element in a multidimensional array can be slower than accessing an element in a single-dimension array.

6. Pointers & user defined types

The correct understanding and use of pointers is critical to successful C/C++ programming. There are three reasons for this: First, pointers provide the means by which functions can modify their calling arguments. Second, pointers support dynamic allocation. Third, pointers can improve the efficiency of certain routines. Also, as you will see in Part Two, pointers take on additional roles in C++. Pointers are one of the strongest but also one of the most dangerous features in C/C++. For example, uninitialized pointers (or pointers containing invalid values) can cause your system to crash. Perhaps worse, it is easy to use pointers incorrectly, causing bugs that are very difficult to find. Because of both their importance and their potential for abuse, this chapter examines the subject of pointers in detail.

7. Function Components

A function is uniquely represented by a name and a set of operand types. Its operands, referred to as parameters, are specified in a comma-separated list enclosed in parentheses. The actions that the function performs are specified in a block, referred to as the function body. Every function has an associated return type . As an example, we could write the following function to find the greatest common divisor of two int s:

// return the greatest common divisor

int gcd(int v1, int v2)

{

while (v2) {

int temp = v2;

v2 = v1 % v2;

v1 = temp;

}

return v1;

}

Here we define a function named gcd that returns an int and has two int parameters. To call gcd , we must supply two int values and we get an int in return.

7.1 Calling a Function

To invoke a function we use the call operator , which is a pair of parentheses. As with any operator, the call operator takes operands and yields a result. The operands to the call operator are the name of the function and a (possibly empty) comma-separated list of arguments . The result type of a call is the return type of the called function, and the result itself is the value returned by the function:

// get values from standard input

cout << "Enter two values: \n";

int i, j;

cin >> i >> j;

// call gcd on arguments i and j

// and print their greatest common divisor

cout << "gcd: " << gcd(i, j) << endl;

If we gave this program 15 and 123 as input, the output would be 3. Calling a function does two things: It initializes the function parameters from the corresponding arguments and transfers control to the function being invoked. Execution of the calling function is suspended and execution of the called function begins. Execution of a function begins with the (implicit) definition and initialization of its parameters. That is, when we invoke gcd , the first thing that happens is that variables of type int named v1 and v2 are created. These variables are initialized with the values passed in the call to gcd . In this case, v1 is initialized by the value of i and v2 by the value of j .

7.2 Function Body Is a Scope

The body of a function is a statement block, which defines the function's operation. As usual, the block is enclosed by a pair of curly braces and hence forms a new scope. As with any block, the body of a function can define variables. Names defined inside a function body are accessible only within the function itself. Such variables are referred to as local variables . They are "local" to that function; their names are visible only in the scope of the function. They exist only while the function is executing. Execution completes when a return statement is encountered. When the called function finishes, it yields as its result the value specified in the return statement. After the return is executed, the suspended, calling function resumes execution at the point of the call. It uses the return value as the result of evaluating the call operator and continues processing whatever remains of the statement in which the call was performed.

7.3 Parameters and Arguments

Like local variables, the parameters of a function provide named, local storage for use by the function. The difference is that parameters are defined inside the function's parameter list and are initialized by arguments passed to the function when the function is called. An argument is an expression. It might be a variable, a literal constant or an expression involving one or more operators. We must pass exactly the same number of arguments as the function has parameters. The type of each argument must match the corresponding parameter in the same way that the type of an initializer must match the type of the object it initializes: The argument must have the same type or have a type that can be implicitly converted

7.3 Function Return Type

The return type of a function can be a built-in type, such as int or double , a class type, or a compound type, such as int& or string* . A return type also can be void , which means that the function does not return a value. The following are example definitions of possible function return types:

bool is_present(int *, int); // returns bool

int count(const string &, char); // returns int

Date &calendar(const char*); // returns reference to Date

void process(); // process does not return a value

A function may not return another function or a built-in array type. Instead, the

function may return a pointer to the function or to a pointer to an element in the array:

// ok: pointer to first element of the array

int *foo_bar() { /* ... */ }

This function returns a pointer to int and that pointer could point to an element in an array.

7.4 Functions Must Specify a Return Type

It is illegal to define or declare a function without an explicit return type:

// error: missing return type

test(double v1, double v2) { /* ... */ }

Eariler versions of C++ would accept this program and implicitly define the return type of test as an int . Under Standard C++, this program is an error.

7.5 Function Parameter List

The parameter list of a function can be empty but cannot be omitted. A function with no parameters can be written either with an empty parameter list or a parameter list containing the single keyword void. For example, the following declarations of process are equivalent:

void process() { /* ... */ } // implicit void parameter list

void process(void){ /* ... */ } // equivalent declaration

A parameter list consists of a comma-separated list of parameter types and (optional) parameter names. Even when the types of two parameters are the same, the type must be repeated:

int manip(int v1, v2) { /* ... */ } // error

int manip(int v1, int v2) { /* ... */ } // ok

No two parameters can have the same name. Similarly, a variable local to a function may not use the same name as the name of any of the function's parameters. Names are optional, but in a function definition, normally all parameters are named. A parameter must be named to be used.

7.6 Parameter Type-Checking

When we call a function, the type of each argument must be either the same type as the corresponding parameter or a type that can be converted to that type. The function's parameter list provides the compiler with the type information needed to check the arguments. For example, the function gcd , which we defined on page 226 , takes two parameters of type int :

gcd("hello", "world"); // error: wrong argument types

gcd(24312); // error: too few arguments

gcd(42, 10, 0); // error: too many arguments

Each of these calls is a compile-time error. In the first call, the arguments are of type const char* . There is no conversion from const char* to int , so the call is illegal. In the second and third calls, gcd is passed the wrong number of arguments. The function must be called with two arguments; it is an error to call it with any other number. But what happens if the call supplies two arguments of type double ? Is this call legal?

gcd(3.14, 6.29); // ok: arguments are converted to int In C++, the answer is yes; the call is legal. This call involves such a conversion we want to use double values to initialize int objects. Therefore, flagging the call as an error would be too severe. Rather, the arguments are implicitly converted to int Because this conversion might lose precision, most compilers will issue a warning. In this case, the call becomes gcd(3, 6);

and returns a value of 3.A call that passes too many arguments, omits an argument, or passes an argument of the wrong type almost certainly would result in serious run-time errors. Catching these so-called interface errors at compile time greatly reduces the compile-debug-test cycle for large programs.

8. Argument passing

Each parameter is created anew on each call to the function. The value used to initialize a parameter is the corresponding argument passed in the call.

8.1 Nonreference Parameters

Parameters that are plain, nonreference types are initialized by copying the corresponding argument. When a parameter is initialized with a copy, the function has no access to the actual arguments of the call. It cannot change the arguments. Let's look again at the definition of gcd :

// return the greatest common divisor

int gcd(int v1, int v2)

{

while (v2) {

int temp = v2;

v2 = v1 % v2;

v1 = temp;

}

return v1;

}

Inside the body of the while , we change the values of both v1 and v2 . However, these changes are made to the local parameters and are not reflected in the arguments used to call gcd . Thus, when we call

gcd(i, j)

the values i and j are unaffected by the assignments performed inside gcd .

8.2 Pointer Parameters

A parameter can be a pointer in which case the argument pointer is copied. As with any nonreference type parameter, changes made to the parameter are made to the local copy. If the function assigns a new pointer value to the parameter, the calling pointer value is unchanged.

void reset(int *ip)

*ip = 0; // changes the value of the object to which ip points

ip = 0; // changes only the local value of ip; the argument is unchanged

}

After a call to reset , the argument is unchanged but the object to which the argument points will be 0:

int i = 42;

int *p = &i;

cout << "i: " << *p << '\n'; // prints i: 42

reset(p); // changes *p but not p

cout << "i: " << *p << endl; // ok: prints i: 0

If we want to prevent changes to the value to which the pointer points, then the parameter should be defined as a pointer to const :

void use_ptr(const int *p)

{

// use_ptr may read but not write to *p

}

Whether a pointer parameter points to a const or nonconst type affects the arguments that we can use to call the function. We can call use_ptr on either an int* or a const int* ; we can pass only on an int* to reset . This distinction follows from the initialization rules for pointers. We may initialize a pointer to const to point to a nonconst object but may not use a pointer to nonconst to point to a const object.

8.3 const Parameters

We can call a function that takes a nonreference, nonconst parameter passing either a const or nonconst argument. For example, we could pass two const int s to our gcd function:

const int i = 3, j = 6;

int k = rgcd(3, 6); // ok: k initialized to 3

This behavior follows from the normal initialization rules for const objects. Because the initialization copies the value of the initializer, we can initialize a nonconst object from a const object, or vice versa. If we make the parameter a const nonreference

type:

void fcn(const int i) { /* fcn can read but not write to i */ then the function cannot change its local copy of the argument. The argument is still passed as a copy so we can pass fcn either a const or nonconst object. What may be surprising, is that although the parameter is a const inside the function, the compiler otherwise treats the definition of fcn as if we had defined the parameter as a plain int:

void fcn(const int i) { /* fcn can read but not write to i */ }

void fcn(int i) { /* ... */ } // error: redefines fcn(int)

This usage exists to support compatibility with the C language, which makes no distinction between functions taking const or nonconst parameters.

Limitations of Copying Arguments

Copying an argument is not suitable for every situation. Cases where copying doesn't work

include:

When we want the function to be able to change the value of an argument. When we want to pass a large object as an argument. The time and space costs to copy the object are often too high for real-world applications. When there is no way to copy the object. In these cases we can instead define the parameters as references or pointers 

8.4 Reference Parameters

As an example of a situation where copying the argument doesn't work, consider a function to swap the values of its two arguments:

// incorrect version of swap: The arguments are not changed!

void swap(int v1, int v2)

{

int tmp = v2;

v2 = v1; // assigns new value to local copy of the argument

v1 = tmp;

} // local objects v1 and v2 no longer exist

In this case, we want to change the arguments themselves. As defined, though, swap cannot affect those arguments. When it executes, swap exchanges the local copies of its arguments. The arguments passed to swap are unchanged:

int main()

{

int i = 10;

int j = 20;

cout << "Before swap():\ti: "

<< i << "\tj: " << j << endl;

swap(i, j);

cout << "After swap():\ti: "

<< i << "\tj: " << j << endl;

return 0;

}

Compiling and executing this program results in the following output:

Before swap(): i: 10 j: 20

After swap(): i: 10 j: 20

For swap to work as intended and swap the values of its arguments, we need to make the parameters references:

// ok: swap acts on references to its arguments

void swap(int &v1, int &v2)

{

int tmp = v2;

v2 = v1;

v1 = tmp;

}

Like all references, reference parameters refer directly to the objects to which they are bound rather than to copies of those objects. When we define a reference, we must initialize it with the object to which the reference will be bound. Reference parameters work exactly the same way. Each time the function is called, the reference parameter is created and bound to its corresponding argument. Now, when we call swap

swap(i, j);

the parameter v1 is just another name for the object i and v2 is another name for j . Any change to v1 is actually a change to the argument i . Similarly, changes to v2 are actually made to j . If we recompile main using this revised version of swap , we can see that the output is now correct:

Before swap(): i: 10 j: 20

After swap(): i: 20 j: 10

8.5 Using Reference Parameters to Return Additional Information

We've seen one example, swap , in which reference parameters were used to allow the function to change the value of its arguments. Another use of reference parameters is to return an additional result to the calling function. Functions can return only a single value, but sometimes a function has more than one thing to return. For example, let's define a function named find_val that searches for a particular value in the elements of a vector of integers. It returns an iterator that refers to the element, if the element was found, or to the end value if the element isn't found.

We'd also like the function to return an occurrence count if the value occurs more than once. In this case the iterator returned should be to the first element that has the value for which we're looking. How can we define a function that returns both an iterator and an occurrence count? We could define a new type that contains an iterator and a count. An easier solution is to pass an additional reference argument that find_val can use to return a count of the number of occurrences:

// returns an iterator that refers to the first occurrence of value

// the reference parameter occurs contains a second return value

vector<int>::const_iterator find_val(

vector<int>::const_iterator beg, // first element

vector<int>::const_iterator end, // one past last element

int value, // the value we want

vector<int>::size_type &occurs) // number of times it occurs

{

// res_iter will hold first occurrence, if any

vector<int>::const_iterator res_iter = end;

occurs = 0; // set occurrence count parameter

for ( ; beg != end; ++beg)

if (*beg == value) {

// remember first occurrence of value

if (res_iter == end)res_iter = beg;

++occurs; // increment occurrence count

}

return res_iter; // count returned implicitly in occurs

}

When we call find_val , we have to pass four arguments: a pair of iterators that denote the range of elements in the vector in which to look, the value to look for, and a size_type) object to hold the occurrence count. Assuming ivec is a vector<int>, it is an iterator of the right type, and ctr is a size_type , we could call find_val as follows: it = find_val(ivec.begin(), ivec.end(), 42, ctr); After the call, the value of ctr will be the number of times 42 occurs, and it will refer to the first occurrence if there is one. Otherwise, it will be equal to ivec.end() and ctr will be zero.

8.6 Using (const ) References to Avoid Copies

The other circumstance in which reference parameters are useful is when passing a large object to a function. Although copying an argument is okay for objects of built-in data types and for objects of class types that are small in size, it is (often) too inefficient for objects of most class types or large arrays. By using a reference parameter, the function can access the object directly without copying it. As an example, we'll write a function that compares the length of two string s. Such a function needs to access the size of each string but has no need to write to the string s. Because string s can be long,

we'd like to avoid copying them. Using const references we can avoid the copy:

// compare the length of two strings

bool isShorter(const string &s1, const string &s2)

{

return s1.size() < s2.size();

}

Each parameter is a reference to const string . Because the parameters are references the arguments are not copied. Because the parameters are const references, is Shorter may not use the references to change the arguments.

8.7 Passing a Reference to a Pointer

Suppose we want to write a function that swaps two pointers, similar to the program we earlier that swaps two integers. We know that we use * to define a pointer and & to define a reference. The question here is how to combine these operators to obtain a reference to a pointer. Here is an example:

// swap values of two pointers to int

void ptrswap(int *&v1, int *&v2)

{

int *tmp = v2;

v2 = v1;

v1 = tmp;

}

The parameter int *&v1 should be read from right to left: v1 is a reference to a pointer to an object of type int . That is, v1 is just another name for whatever pointer is passed to ptrswap . We could rewrite the main function from page 233 to use ptrswap and swap pointers to the values 10 and 20:

int main()

{

int i = 10;

int j = 20;

int *pi = &i; // pi points to i

int *pj = &j; // pj points to j

cout << "Before ptrswap():\t*pi: "

<< *pi << "\t*pj: " << *pj << endl;

ptrswap(pi, pj); // now pi points to j; pj points to i

cout << "After ptrswap():\t*pi: "

<< *pi << "\t*pj: " << *pj << endl;

return 0;

}

When compiled and executed, the program generates the following output: 

Before ptrswap(): *pi: 10 *pj: 20

After ptrswap(): *pi: 20 *pj: 10

What happens is that the pointer values are swapped. When we call ptrswap, pi points to i and pj points to j . Inside ptrswap the pointers are swapped so that after ptrswap, pi points to the object pj had addressed. In other words, pi now points to j . Similarly, pj points to i . In order to avoid copying the vector , we might think that we'd make the parameter a reference.

8.8 Array Parameters

Arrays have two special properties that affect how we define and use functions that operate on arrays: We cannot copy an array and when we use the name of an array it is automatically converted to a pointer to the first element. Because we cannot copy an array, we cannot write a function that takes an array type parameter. Because arrays are automatically converted to pointers, functions that deal with arrays usually do so indirectly by manipulating pointers to elements in the array.

Defining an Array Parameter

Let's assume that we want to write a function that will print the contents of an array of int s. We could specify the array parameter in one of three ways:

// three equivalent definitions of printValues

void printValues(int*) { /* ... */ }

void printValues(int[]) { /* ... */ }

void printValues(int[10]) { /* ... */ }

Even though we cannot pass an array directly, we can write a function parameter that looks like an array. Despite appearances, a parameter that uses array syntax is treated as if we had written a pointer to the array element type. These three definitions are equivalent; each is interpreted as taking a parameter of type int* .

8.9 Parameter Dimensions Can Be Misleading

The compiler ignores any dimension we might specify for an array parameter. Relying, incorrectly, on the dimension, we might write printValues as

// parameter treated as const int*, size of array is ignored

void printValues(const int ia[10])

{

// this code assumes array has 10 elements;

// disaster if argument has fewer than 10 elements!

for (size_t i = 0; i != 10; ++i)

{

cout << ia[i] << endl;}

}

Although this code assumes that the array it is passed has at least 10 elements, nothing in the language enforces that assumption. The following calls are all legal:

int main()

{

int i = 0, j[2] = {0, 1};

printValues(&i); // ok: &i is int*; probable run-time error

printValues(j); // ok: j is converted to pointer to 0th

// element; argument has type int*;

// probable run-time error

return 0;

}

Even though the compiler issues no complaints, both calls are in error, and probably will fail at run time. In each case, memory beyond the array will be accessed because printValues assumes that the array it is passed has at least 10 elements. Depending on the values that happen to be in that memory, the program will either produce spurious output or crash.

8.10 Passing an Array by Reference

As with any type, we can define an array parameter as a reference to the array. If the parameter is a reference to the array, then the compiler does not convert an array argument into a pointer. Instead, a reference to the array itself is passed. In this case, the array size is part of the parameter and argument types. The compiler will check that the size of the array argument matches the size of the parameter:

// ok: parameter is a reference to an array; size of array is fixed

void printValues(int (&arr)[10]) { /* ... */ }

int main()

{

int i = 0, j[2] = {0, 1};

int k[10] = {0,1,2,3,4,5,6,7,8,9};

printValues(&i); // error: argument is not an array of 10 ints

printValues(j); // error: argument is not an array of 10 ints

printValues(k); // ok: argument is an array of 10 ints

return 0;

}

This version of printValues may be called only for arrays of exactly 10 int s, limiting which

arrays can be passed. However, because the parameter is a reference, it is safe to rely on the

size in the body of the function:

// ok: parameter is a reference to an array; size of array is fixed

void printValues(int (&arr)[10])

{

for (size_t i = 0; i != 10; ++i) {

cout << arr[i] << endl;

}

}

8.11 Passing a Multidimensioned Array

Recall that there are no multidimensioned arrays in C++ Instead, what appears to be a multidimensioned array is an array of arrays. As with any array, a multidimensioned array is passed as a pointer to its zeroth element. An element in a multidimenioned array is an array. The size of the second (and any subsequent dimensions) is part of the element type and must be specified:

// first parameter is an array whose elements are arrays of 10 ints

void printValues(int (matrix*)[10], int rowSize); declares matrix as a pointer to an array of ten int s. We could also declare a multidimensioned array using array syntax. As with a single- dimensioned array, the compiler ignores the first dimension and so it is best not to include it:

// first parameter is an array whose elements are arrays of 10 ints

void printValues(int matrix[][10], int rowSize);

declares matrix to be what looks like a two-dimensioned array. In fact, the parameter is a pointer to an element in an array of arrays. Each element in the array is itself an array of ten

int s.

9. Inline functions

There is an important feature in C++, called an inline function that is commonly used with classes. In C++, you can create short functions that are not actually called; rather, their code is expanded in line at the point of each invocation. This process is similar to using a function-like macro. To cause a function to be expanded in line rather than called, precede its definition with the inline keyword. For example, in this program, the function max() is expanded in line instead of called:

#include <iostream>

using namespace std;

inline int max(int a, int b)

{

return a>b ? a : b;

}

int main()

{

cout << max(10, 20);

cout << " " << max(99, 88);

return 0;

}

As far as the compiler is concerned, the preceding program is equivalent to this one:

#include <iostream>

using namespace std;

int main()

{

cout << (10>20 ? 10 : 20);

cout << " " << (99>88 ? 99: 88);

return 0;

}

The reason that inline functions are an important addition to C++ is that they allow you to create very efficient code. Since classes typically require several frequently executed interface functions (which provide access to private data), the efficiency of these functions is of critical concern. As you probably know, each time a function is called, a significant amount of overhead is generated by the calling and return mechanism. Typically, arguments are pushed onto the stack and various registers are saved when a function is called, and then restored when the function returns. The trouble is that these instructions take time. However, when a function is expanded in line, none of those operations occur. Although expanding function calls in line can produce faster run times, it can also result in larger code size because of duplicated code. For this reason, it is best to inline only very small functions. Further, it is also a good idea to inline only those functions that will have significant impact on the performance of your program.

10. Function overloading

Function overloading is the process of using the same name for two or more functions. The secret to overloading is that each redefinition of the function must use either different types of parameters or a different number of parameters. It is only through these differences that the compiler knows which function to call in any given situation. For example, this program overloads myfunc() by using different types of parameters.

#include <iostream>

int myfunc(int i); // these differ in types of parameters

double myfunc(double i);

int main()

{

cout << myfunc(10) << " "; // calls myfunc(int i)

cout << myfunc(5.4); // calls myfunc(double i)

return 0;

}

double myfunc(double i)

{

return i;

}

int myfunc(int i)

{

return i;

}

The next program overloads myfunc() using a different number of parameters:

#include <iostream>

using namespace std;

int myfunc(int i); // these differ in number of parameters

int myfunc(int i, int j);

int main()

{

cout << myfunc(10) << " "; // calls myfunc(int i)

cout << myfunc(4, 5); // calls myfunc(int i, int j)

return 0;

}

int myfunc(int i)

{

return i;

}

int myfunc(int i, int j)

{

return i*j; }

As mentioned, the key point about function overloading is that the functions must differ in regard to the types and/or number of parameters. Two functions differing only in their return types cannot be overloaded. For example, this is an invalid attempt to overload myfunc():

int myfunc(int i); // Error: differing return types are

float myfunc(int i); // insufficient when overloading.

Sometimes, two function declarations will appear to differ, when in fact they do not. For example,

consider the following declarations.

void f(int *p);

void f(int p[]); // error, *p is same as p[]

Remember, to the compiler *p is the same as p[ ]. Therefore, although the two prototypes appear to differ in the types of their parameter, in actuality they do not.

11. Recursive functions

In C/C++, a function can call itself. A function is said to be recursive if a statement inthe body of the function calls itself. Recursion is the process of defining something in terms of itself, and is sometimes called circular definition. A simple example of a recursive function is factr( ), which computes the factorial of an integer. The factorial of a number n is the product of all the whole numbers between 1 and n. For example, 3 factorial is 1 x 2 x 3, or 6. Both factr( ) and its iterative equivalent are shown here: The non recursive version of fact( ) should be clear. It uses a loop that runs from 1 to n and progressively multiplies each number by the moving product. The operation of the recursive factr( ) is a little more complex. When factr( ) is called with an argument of 1, the function returns 1. Otherwise, it returns the product of factr(n-1)*n. To evaluate this expression, factr( ) is called with n-1. This happens until n equals 1 and the calls to the function begin returning. Computing the factorial of 2, the first call to factr( ) causes a second, recursive call with the argument of 1. This call returns 1, which is then multiplied by 2 (the original n value). The answer is then 2. Try working through the computation of 3 factorial on your own. (You might want to insert printf( ) statements into factr( ) to see the level of each call and what the intermediate answers are.)

When a function calls itself, a new set of local variables and parameters are allocated storage on the stack, and the function code is executed from the top with these new variables. A recursive call does not make a new copy of the function. Only the values being operated upon are new. As each recursive call returns, the old local variables and parameters are removed from the stack and execution resumes at the point of the function call inside the function. Recursive functions could be said to "telescope" out and back. Often, recursive routines do not significantly reduce code size or improve memory utilization over their iterative counterparts. Also, the recursive versions of most routines may execute a bit slower than their iterative equivalents because of the overhead of the repeated function calls. In fact, many recursive calls to a function could cause a stack overrun. Because storage for function parameters and local variables is on the stack and each new call creates a new copy of these variables, the stack could be exhausted. However, you probably will not have to worry about this unless a recursivefunction runs wild.

The main advantage to recursive functions is that you can use them to create clearer and simpler versions of several algorithms. For example, the Quicksort algorithm is difficult to implement in an iterative way. Also, some problems, especially ones related to artificial intelligence, lend themselves to recursive solutions. Finally, some people seem to think recursively more easily than iteratively. When writing recursive functions, you must have a conditional

statement, such as an if, somewhere to force the function to return without the recursive call being executed. If you don't, the function will never return once you call it. Omitting the conditional statement is a common error when writing recursive functions. Use printf( ) liberally during program development so that you can watch what is going on and abort execution if you see a mistake.

Editors




You might like this video:Watch more here

Watch more videos from this user Here

Learn how to upload a video over here