Professional Documents
Culture Documents
C O N T E N T S
CHAPTER(1) 1.1 1.2 1.3 FUNDAMENTALS .................................................................................................................................. 1 DATA TYPES .......................................................................................................................................................... 1 FUNCTIONS................................................................................................................................................................ 2 FORMATTING OUTPUT ............................................................................................................................................... 3 ARRAYS AND STRINGS ....................................................................................................................... 4
CHAPTER(2)
2.1 ARRAYS OF STRUCTURES .......................................................................................................................................... 5 2.2 STRUCTURES AND ARRAYS ....................................................................................................................................... 6 2.3 ARRAYS AS CLASS MEMBER DATA ........................................................................................................................... 7 2.4 ARRAYS OF OBJECTS ................................................................................................................................................. 8 2.5 STRINGS .................................................................................................................................................................... 9 2.6 ARRAY SEARCHING ................................................................................................................................................. 10 2.6.1 Linear Search Algorithm ......................................................................................................................................... 10 2.6.2 Binary Search Algorithm ........................................................................................................................................ 11 2.7 SORTING ARRAYS ................................................................................................................................................... 11 2.7.1 Selection Sort .......................................................................................................................................................... 11 2.7.2 Insertion Sort .......................................................................................................................................................... 12 2.7.3 Bubble Sort.............................................................................................................................................................. 12 2.7.4 Quicksort ................................................................................................................................................................. 13 CHAPTER(3) STACKS AND QUEUES ....................................................................................................................... 15
3.1 STACKS ................................................................................................................................................................... 15 3.1.1 Introduction to Stacks ............................................................................................................................................. 15 3.1.2 Evaluation of Arithmetic Expressions ..................................................................................................................... 16 3.1.3 Array Implementation of the Stack .......................................................................................................................... 18 3.2 FIFO QUEUES ......................................................................................................................................................... 20 3.2.1 Introduction to Queues............................................................................................................................................ 20 3.2.2 Array Implementation of a FIFO Queue ................................................................................................................. 21 CHAPTER(4) POINTERS ............................................................................................................................................. 23
4.1 WHAT IS A POINTER? .............................................................................................................................................. 23 4.2 CREATING A POINTER.............................................................................................................................................. 23 4.3 USING POINTERS ..................................................................................................................................................... 25 4.4 POINTERS AND STRUCTURES ................................................................................................................................... 27 4.5 POINTERS AND OBJECTS .......................................................................................................................................... 29 4.5.1 Declaration and Access .......................................................................................................................................... 29 4.5.2 An array of Pointers to Objects .............................................................................................................................. 29 4.6 POINTERS AND FUNCTION ARGUMENTS .................................................................................................................. 30 4.7 POINTERS AND ARRAYS .......................................................................................................................................... 32 4.8 POINTER ARITHMETIC ............................................................................................................................................. 33 4.9 DYNAMIC MEMORY ALLOCATION (NEW, DELETE) .................................................................................................. 35 4.9.1 The new Operator ................................................................................................................................................... 35 4.9.2 The delete Operator ................................................................................................................................................ 35 4.9.3 Dynamic Arrays ...................................................................................................................................................... 36 CHAPTER(5) FILE INPUT/OUTPUT ......................................................................................................................... 37
5.1 DISK FILE I/O WITH STREAMS ................................................................................................................................. 37 5.2 FORMATTED FILE I/O .............................................................................................................................................. 37 5.2.1 Writing data ............................................................................................................................................................ 37 5.2.2 Reading Data .......................................................................................................................................................... 38 5.2.3 Character I/O .......................................................................................................................................................... 39 5.3 BINARY I/O ............................................................................................................................................................. 40 CHAPTER(6) 6.1 6.2 6.3 6.4 6.5 6.6 6.7 LINKED LISTS ...................................................................................................................................... 43
INTRODUCTION........................................................................................................................................................ 43 CREATING A LINKED LIST ....................................................................................................................................... 43 DELETING A NODE FROM A LINKED LIST .................................................................................................................. 44 ADDING NODES TO A LINKED LIST (INSERTING A NODE) .......................................................................................... 44 TRAVERSING A LINKED LIST .................................................................................................................................... 45 WORKING WITH DYNAMICALLY CREATED NODES .................................................................................................. 46 A LINKED LIST EXAMPLE ......................................................................................................................................... 48
Sample program illustrating each data type #include <iostream.h> int main() { int sum, N=3; float money, average; char letter; double pi; sum = 10; // assign money = 2.21; // assign letter = 'A'; // assign pi = 2.01E26; // assign average = ((float) sum) / N; cout<<"value of cout<<"value of cout<<"value of cout<<"value of cout<<"Average return 0; } Sample program output value of sum = 10 value of money = 2.21 value of letter = A value of pi = 2.01e+26 average = 3.333333 Operations are associated with types, not the other way around. When we perform an operation, we need to ensure that its operands and result are of the correct type. Neglecting this responsibility is a common programming error. In some cases C++ performs implicit type conversions; in others we use casts, or explicit type conversions. For example if sum and N are integers (see the above program), the expression ((float) sum) / N includes both types of conversion: the (float) is a cast that converts the value of sum to float; then an implicit conversion is performed for N to make both arguments of the divide operator float. Many of the operations associated with the standard data types are built into the C++ language. Other operations are found in the form of functions defined in standard function libraries or defined by us - programmers. This concept of data type can be extended to userdefined data types. The C++ language provides us with the tools required for defining our own data types. In addition to the basic data types described above C++ allows us to define our own data types. We usually use functions in conjunction with user-defined data types or data structures. integer value float value character value a double value
1.2 Functions
Functions help us structure our programs effectively by subdividing those programs into subprograms, each of which can then be made to deal with one limited task or activity while the main program drives these individual components. Functions are also used within classes to define an objects behavior. In C++ there are two types of functions:
Prog. & Data Structures(702233)/Fall2003/Dr. Sayed A. Hussein
(i) (ii)
functions which compute and return a value; functions which simply carry out some task without returning a specific value.
For further information on functions refer to your textbook: OOP in C++ by Robert Lafore.
We can also use two-dimensional arrays. The following examples show how to declare and initialize two-dimensional array. Example1: A two-dimensional array b[2] [2] is initialized: int b[2] [2] = {{1,2},{3,4}}; which means element b[0] [0]= 1, b[0] [1]= 2, b[1] [0]= 3 and b[1] [1]= 4 Example 2: // displays sales chart, initializes 2-d array #include <iostream.h> #include <iomanip.h> //for setprecision, etc. const int DISTRICTS = 4; const int MONTHS = 3; int main() { int d, m; //initialize array elements double sales[DISTRICTS][MONTHS] = { { 1432.07, 234.50, 654.01 }, { 322.00, 13838.32, 17589.88 }, { 9328.34, 934.00, 4492.30 }, { 12838.29, 2332.63, 32.93 } }; cout << "\n\n"; cout << " Month\n"; cout << " 1 2 3"; for(d=0; d<DISTRICTS; d++) { cout <<"\nDistrict " << d+1; for(m=0; m<MONTHS; m++) cout << setw(10) << setiosflags(ios::fixed) << setiosflags(ios::showpoint) << setprecision(2) << sales[d][m]; //access array element } cout << endl; return 0; } //array dimensions
Let us now create an array called birthdays of the same data type as the structure date
date birthdays[6];
This creates an array of 6 elements which have the structure of date. We may assign values to the second element of the array as:
birthdays[1].month = 3; birthdays[1].day = 8; birthdays[1].year = 1992;
Example:
// Demonstrates using arrays of structures. #include <iostream.h> int main() { // Define a structure to hold entries. struct entry { char fname[20]; char lname[20]; char phone[10]; }; // Declare an array of structures. entry list[4]; int i; // Loop to input data for four people. for (i = 0; i < 4; i++) { cout<<"Enter first name: "; cin>>list[i].fname; cout<<"Enter last name: "; cin>>list[i].lname; cout<<"Enter phone in 123-4567 format: cin>>list[i].phone; } // Print two blank lines. cout<<"\n\n"; // Loop to display data. for (i = 0; i < 4; i++) { cout<<"Name: "<<list[i].fname<<" "<<list[i].lname; cout<<"\t\tPhone: "<<list[i].phone<<endl; } return 0; }
Prog. & Data Structures(702233)/Fall2003/Dr. Sayed A. Hussein
";
Record.y[6]
You access individual elements of arrays that are structure members using a combination of the member operator and array subscripts:
record.x[2] = 100; record.y[1] = x;
struct month { int number_of_days; char name[4]; }; month this_month = { 31, "Jan" }; this_month.number_of_days = 31; strcpy( this_month.name, "Jan" ); cout<<"The month is "<< this_month.name; Note that the array name has an extra element to hold the end of string null character ( '\0').
if(m<0) break; else marks[i]=m; } } void student::show() { cout<<"Name: "<<name<<endl; cout<<"subject\tMark\n"; for (int i=0;i<29;i++) if(marks[i]>=0) cout<<" "<<i<<"\t"<<marks[i]<<endl; } int main() { student s1, s2("Sayed Ahmed Hussein"); s1.getdata(); s1.show(); s2.getdata(); s2.show(); return 0; }
cout << endl; do { //get distances from user cout << "Enter distance number " << n+1; dist[n++].getdist(); //store distance in array cout << "Enter another (y/n)?: "; cin >> ans; } while( ans != 'n' ); //quit if user types 'n' for(int j=0; j<n; j++) //display all distances { cout << "\nDistance number " << j+1 << " is "; dist[j].showdist(); } cout << endl; return 0; }
2.5 Strings
A string in C++ is an array of characters terminated by the ASCII NULL character ('\0'). A string constant is null terminated by the compiler automatically.
\0
Generally, there are two classes of functions (to use them include the header file <string.h>). The first category is concerned with varieties of string copying. When characters are transferred to a destination string no checks are performed to ensure that the destination string is sufficiently large, so be aware! The second class of functions performs tests and returns a non-zero value representing TRUE or zero for FALSE. strcat()
char *strcat(char S1[], char S2[]);
Appends the content of the string S2 to the end of the string S1. The first character of S2 overwrites the null character which terminates S1. Example: #include <iostream.h> #include <string.h> int main() { char str[80]="This is "; strcat(str,"one line text "); cout <<str; return 0; }
Program output: This is one line text
strcmp()
int strcmp(const char S1[], const char S2[]);
< 0
if S1 is less than S2
strcpy()
char *strcpy (char S1[], const char S2[]);
Overwrite the original content of the string S1 with the content of the string S2. You can create a string of length 0, strcpy(str,); strlen() int strlen(const char S[]); Returns the number of characters in S, not counting the null terminating character. Example:
cout << strlen(This is a string); will print 16.
The advantage is its simplicity: It is easy to understand Easy to implement Does not require the array to be in order The disadvantage is its inefficiency: If there are 20,000 items in the array and what you are looking for is in the 19,999th element, you need to search through the entire list.
10
11
12
#include <iostream.h> #include <stdlib.h> void exch(int &x, int &y); void compexch(int &A, int &B); void bubble(int a[], int l, int r); int main() { int t, x[100]; randomize(); for (int i=0; i<100; i++) x[i]=rand()%100; for (i=0; i<100; i++) cout<<x[i]<<" "; cout<<endl<<endl; bubble(x, 0,99); for (i=0; i<100; i++) cout<<x[i]<<" "; return 0; } void bubble(int a[], int l, int r) { for (int i = l; i < r; i++) for (int j = r; j > i; j--) compexch(a[j-1], a[j]); } void exch(int &x, int &y) { int temp=x; x=y; y=temp; } void compexch(int &A, int &B) { if (B < A) exch(A, B); }
2.7.4 Quicksort
This sort method is used more than any other. It is a divide-and-conquer method for sorting. It works by partitioning an array into two parts, then sorting the parts independently. The crux of the method is the partitioning process, which rearranges the array to make the following three conditions hold: l The element a[i] is in its final place in the array for some i. None of the elements in a[l],,a[i-1] is greater than a[i]. None of the elements in a[i+1],,a[r] is less than a[i].
Greater than or equal to v v
The following program illustrates the use of Quicksort (using recursion). The partition function is also given herein.
13
#include <iostream.h> #include <stdlib.h> int partition(int a[], int l, int r); void exch(int &x, int &y); void quicksort(int a[], int l, int r); int main() { int t, x[100]; randomize(); for (int i=0; i<100; i++) x[i]=rand()%100; for (i=0; i<100; i++) cout<<x[i]<<" "; cout<<endl<<endl; quicksort(x, 0,99); for (i=0; i<100; i++) cout<<x[i]<<" "; return 0; } void quicksort(int a[], int l, int r) { if (r <= l) return; int i = partition(a, l, r); quicksort(a, l, i-1); quicksort(a, i+1, r); } int partition(int a[], int l, int r) { int i = l-1, j = r; int v = a[r]; for (;;) { while (a[++i] < v) ; while (v < a[--j]) if (j == l) break; if (i >= j) break; exch(a[i], a[j]); } exch(a[i], a[r]); return i; } void exch(int &x, int &y) { int temp=x; x=y; y=temp; } A carefully tuned version of quicksort is likely to run significantly faster on most computers than will any other sorting method, and quicksort is widely used as a library sort utility and for other serious applications.
14
Stacks: Piles of objects of any kind; Add/Remove objects only at the top of the pile (LIFO).
A common computer data structure is the stack. The stack is a linear data structure. By linear we mean a collection of components arranged in a straight line in which we restrict places where one can add/remove. A stack works like the spring-loaded devices that hold trays in cafeterias. When a tray is put on top, the stack sinks down a little; when you take a tray off, it pops up. The last tray placed on the stack is always the first tray removed. This is called Last-In-FirstOut discipline (LIFO). Stacks are one of the cornerstones of the architecture of the microprocessors used in most modern computers. Functions pass their arguments and store their return addresses on the stack. This kind of stack is implemented partly in hardware and is most conveniently accessed in assembly language. However stacks can be created completely in software. Software stacks offer a useful storage device in certain programming situations, such as parsing (analyzing) algebraic expressions. A stack comprises two basic operations: push a new item (i.e. add or insert a new item), and pop the item that was most recently pushed (i.e. remove it).
Adding a new object to the top: pushing it onto the stack. Adding a new object to the top: pushing it onto the stack. Removing an object from the top: popping it from the stack.
Spring-loaded pile: Add/Remove objects only at the top of the pile. Only the top of the pile is accessible. The top remains in a fixed position.
Pushing and popping are inverse operations: Push object and immediately afterwards pop it. Original stack remains unchanged. 15
The figure below shows how a sample stack evolves through a series of push and pop operations. Each push increases the size of the stack by 1 and each pop decreases it by 1. Figure (S1): This list shows the result of the sequence of operations in the left column (top to bottom), where a letter denotes push and an asterisk denotes pop. Each line displays the operation, letter popped for pop operations, and the contents of the stack after the operations, in order from least recently inserted to most recently inserted, left to right.
L A * S T I * N * F I R * S T * * O U * T * * * * * * * A L L L L L L L L L L L L L L L L L L L L L L L L L L L A S S S S S S S S S S S S S S S S S S S S S S S T T T T T T T T T T T T T T T T T T T T T
I N F F F F F F F F F F F F F F F I I I I I I I I I I I I I
I N
R S S T S O O U O O T O
R T S U T O I F T S L
Exercises: 1. A letter means push and an asterisk means pop in the sequence E A S * Y * Q U E * * * S T * * * I O * N * * *. Give the sequence of values returned by the pop operations. 2. Using the conventions of exercise 1, give a way to insert asterisks in the sequence E A S Y so that the sequence of values returned by the pop operations is (i) E A S Y ; (ii) Y S A E ; (iii) A S Y E ; (iv) A Y E S ; or, in each instance, prove that no such sequence exists.
16
Figure (S2): Conversion of an Infix expression to postfix: This sequence shows the use of a stack to convert the infix expression shown above to its postfix form: 5 9 8 + 4 6 * * 7 + * . We proceed from left to right through the expression: If we encounter a number, we write it to the output; if we encounter a left parenthesis, we ignore it; if we encounter an operator, we push it on the stack; and if we encounter a right parenthesis; we write the operator at the top of the stack to the output.
5 * ( ( ( 9 + 8 ) * ( 4 * 6 ) ) + 7 ) )
5 * * * * * * * * * * * * * * * * * *
9 8 + 4 6 * * 7 + *
+ + * * * * * * * * + +
The following program stores the postfix form of the given expression in an array called p. It uses a class called STACK, defined as a template in a file called stack.cpp [Class templates allow us to use different data types with one class definition-see Lafore, page 690].
#include <iostream.h> #include <string.h> #include "stack.cpp" int main() { char a[] = "(5*(((9+8)*(4*6))+7))"; int N = strlen(a); char p[80]; STACK<char> ops; for (int i = 0, j=0; i < N; i++) { if (a[i] == ')') {p[j++]=ops.pop();p[j++]=' ';} if ((a[i] == '+') || (a[i] == '*')) ops.push(a[i]); if ((a[i] >= '0') && (a[i] <= '9')) {p[j++]=a[i];p[j++]=' ';} } p[j]='\0'; cout << p<<endl; return 0; }
Postfix notation and an associated stack give us a natural way to organize a series of computational procedures. Some calculators and some computer languages explicitly base their method of calculations on postfix and stack operations every operator pops its arguments from the stack and returns the results to the stack. Evaluation of a postfix expression: The following figure shows how to evaluate a postfix expression using a stack. Figure (S3): This sequence shows the use of a stack to evaluate the postfix expression 5 9 8 + 4 6 * * 7 + . Proceeding from left to right through the expression, if we encounter a number, we push it on the stack; and if we encounter an operator, we push the result of applying the operator to the top two numbers on the stack.
5 9 8 + 4 6 * * 7 + * 5 5 5 5 5 5 5 5 5 5 2075 9 9 17 17 17 17 408 408 415
8 4 4 24 7 6
17
The following program evaluates a postfix expression stored in the array a. It uses a class called STACK, defined as a template in a file called stack.cpp
#include <iostream.h> #include <string.h> #include "stack.cpp" int main() { char a[] = "5 9 8 + 4 6 * * 7 + *"; int N = strlen(a); STACK<int> save; for (int i = 0; i < N; i++) { if (a[i] == '+') save.push(save.pop() + save.pop()); if (a[i] == '*') save.push(save.pop() * save.pop()); if ((a[i] >= '0') && (a[i] <= '9')) save.push(0); while ((a[i] >= '0') && (a[i] <= '9')) save.push(10*save.pop() + (a[i++]-'0')); } cout << save.pop() << endl; return 0; }
Exercises 1. Convert to postfix the expression (5*((9*8)+(7*(4+6)))) . 2. Give the contents of the stack as the following expression is evaluated by the above program (Follow the steps shown in the figure which is given before the above program) 59*8746+*213*+*+* . 3. Extend the above programs to include the and / operators.
18
The actual implementation of the stack data structure is given in the program below. It uses the header file given above. By separating the implementation of the data structure from its clients (i.e. programs that use it), we can easily change the implementation without affecting those clients. //file: stack.cpp #include "stack.h" template <class Item> STACK<Item>::STACK() { N = 0; } template <class Item> int STACK<Item>::empty() const { return N == 0; } template <class Item> void STACK<Item>::push(Item item) { s[N++] = item; } template <class Item> Item STACK<Item>::pop() { return s[--N]; } Example The following program show a client program using the stack ADT. #include <iostream.h> #include stack.cpp int main() { STACK<int> s1; s1.push(11); s1.push(22); cout << "1: " cout << "2: " s1.push(33); s1.push(44); s1.push(55); s1.push(66); cout << "3: " cout << "4: " cout << "5: " cout << "6: " return 0; } Output: 1: 2: 3: 4: 5: 6: 22 11 66 55 44 33
//22 //11
To run the client program you put the client, the interface and the implementation modules in a folder and define a project that includes those files. You then need to build the project to produce the executable file.
19
On-Site Registration
Queues are abundant in everyday life. When we wait in line to see a movie, to register courses, or to buy groceries, we are being processed according to a FIFO discipline. Perishable items in a grocery are stored in a FIFO queue rather than in a stack. Similarly, FIFO queues are frequently used within computer systems to hold tasks that are yet to be accomplished when we want to provide services on a first-come, first-served basis. To summarize, FIFO queues are used for regulating the processing of tasks in a system to ensure fair treatment Client-server systems: Clients line up to wait for service Operating systems: Queues of tasks, e.g. print queue Simulation and modeling: Performance analysis of systems under various loads, e.g. air traffic control, urban transport etc. A FIFO queue is defined as an ADT that comprises two basic operations: put a new item (i.e. insert), and get the item that was least recently inserted (i.e. remove it). Figure (Q1): A FIFO queue example This list shows the result of the sequence of operations in the left column (top to bottom), where a letter denotes put and an asterisk denotes get. Each line displays the operation, the letter returned for get operations, and the content of the queue in order from least recently inserted to most recently inserted, left to right.
F I R S * T * I N * * * F I * R S * * * T * O U T * * * * * F F F F I I R R R S T I I I N N N F I R R S S S S T O U T I I I R R S S S T I N N N F F F I R S S T T T T O U T R R S S T T T I N F F I I I R S T O O U O U T U T T
S T I I N N
F I R S T I N F I R
I R R S S
S T O U T
20
F I R S T I N F I R
T T T T T T
I I I I I I I
N N N N N N N N N
F F F F F F
I I I I I I
S T O U T
T T T T T T
R R R R R R
O O O O O
U U U U U
T T T T T
S S S S S S S S S
The QUEUE ADT An array implementation of the QUEUE ADT is given herein. The interface part of the QUEUE ADT is given below: template <class Item> class QUEUE { private: enum Size {maxN=80}; Item q[maxN+1]; int N, head, tail; public: QUEUE(); int empty() const; void put(Item); Item get(); }; The implementation part of the QUEUE ADT is shown below: #include "queue.h" template <class Item> QUEUE<Item>::QUEUE() { N = maxN+1; head = N; tail = 0; }
Prog. & Data Structures(702233)/Fall2003/Dr. Sayed A. Hussein
21
template <class Item> int QUEUE<Item>::empty() const { return head % N == tail; } template <class Item> void QUEUE<Item>::put(Item item) { q[tail++] = item; tail = tail % N; } template <class Item> Item QUEUE<Item>::get() { head = head % N; return q[head++]; } The following example shows a client program that uses the QUEUE ADT. Compare the output with that of the similar program that uses the STACK ADT, which is given in the previous section. #include <iostream.h> #include "queue.cpp" int main() { QUEUE<int> q1; q1.put(11); q1.put(22); cout << "1: cout << "2: q1.put(33); q1.put(44); q1.put(55); q1.put(66); cout << "3: cout << "4: cout << "5: cout << "6: return 0; } Output: 1: 2: 3: 4: 5: 6: 11 22 33 44 55 66
" << q1.get() << endl; " << q1.get() << endl;
//11 //22
Exercises 1. Give the contents of q[0],,q[4] after the execution of the operations illustrated in Figure (Q1), using the QUEUE ADT program. Assume that maxN is 10.as in Figure (Q1). 2. A letter means put and an asterisk means get in the sequence E A S * Y * Q U E * * * S T * * * I O * N * * * . Give the sequence of values returned by the get operations when this sequence of operations is performed on an initially empty FIFO queue.
22
rate
1000
1001 ?
1002
1003 100
1004
prate
rate
memory storage has been allocated for the variable prate The next step is to store the address of the variable rate in the variable prate: prate = &rate;
23
Because prate now contains the address of rate, it indicates the location where rate is stored in memory. In C++ parlance, prate points to rate, or is a pointer to rate. This is shown below: 1000 1001 1003 1002 1003 100 1004
prate
rate
The variable prate contains the address of the variable rate and is therefore a pointer to rate To summarize, a pointer is a variable that contains the address of another variable. Now you can get down to the details of using pointers in your C++ programs. Hint: You should note that the address of the variable rate is itself a number. A pointer is a numeric value, like all variables, must be declared before it can be used. To print the content of rate, we can use the following statement: cout<<rate; or cout<< *prate;
Pointers enable us: to effectively represent complex data structures, to change values as arguments to functions, to work with memory which has been dynamically allocated, and to more concisely and efficiently deal with arrays.
A pointer provides an indirect means of accessing the value of a particular data item. Lets see how pointers actually work with a simple example, int count = 10, *ptr_int;
declares an integer count initialized with a value of 10, and also an integer pointer called ptr_int. Note that the prefix * defines the variable to be of type pointer. To set up an indirect reference between ptr_int and count, the & prefix is used, i.e., ptr_int = &count This assigns the memory address of count to ptr_int, not the actual value of count stored at that address. To reference the value of count using ptr_int, the * is used in an assignment, e.g., x = *ptr_int; Since ptr_int is set to the memory address of count, this operation has the effect of assigning the contents of the memory address pointed to by ptr_int to the variable x, so that after the operation variable x has a value of 10. Note
Prog. & Data Structures(702233)/Fall2003/Dr. Sayed A. Hussein
24
POINTERS CONTAIN MEMORY ADDRESSES, NOT VALUES! The operator & gives the address of an object. The operator * is the indirection or dereferencing operator (used to access the object the pointer points to)
//ip is a pointer to int //ip now points to x //y is now 1 // x is now 0 //ip now points to z[0]
If ip points to the integer x, then *ip can occur in any context where x could, so
= 10, x, * ptr_int;
// this assigns the memory address of count to ptr_int ptr_int = &count; //assigns the value stored at the address specified by ptr_int to x x = *ptr_int; cout<<"count = <<count<< return 0; } x = "<<x;
This however, does not illustrate a good use for pointers. The following program illustrates another way to use pointers, this time with characters,
Prog. & Data Structures(702233)/Fall2003/Dr. Sayed A. Hussein
25
#include <iostream.h> int main() { char c = 'Q'; char *ptr_char = &c; cout<<c<< <<*ptr_char<<endl;
c = 'Z'; cout<<c<< <<*ptr_char<<endl; *ptr_char = 'Y'; //assigns Y to the memory address specified by ptr_char cout<<c<< <<*ptr_char; return 0; }
Exercises Determine the output of the pointer programs 1, 2 and 3 shown below: (1)
// illustrating pointers #include <iostream.h> int main() { int count
= 10, x, *int_pointer;
int_pointer = &count;
//assigns the value stored at the address specified by int_pointer to x
(2)
// Further examples of pointers #include <iostream.h> int main() { char c = 'Q'; char *ptr_char cout<<c<<
= &c;
<<*ptr_char<<endl;
c = '/'; cout<<c<< <<*ptr_char<<endl; *ptr_char = '('; //assigns ( to the memory address specified by ptr_char cout<<c<< return 0; } <<*ptr_char<<endl;
(3) 26
// Another program with pointers #include <iostream.h> int main() { int i1, i2, *p1, *p2; i1 p1 i2 p2 = = = = 5; &i1; *p1 / 2 + 10; p1;
Exercises
1. Declare a pointer to an integer called address. 2. Assign the address of a float variable balance to the float pointer temp. 3. Assign the character value 'W' to the variable pointed to by the char pointer letter. 4. What is the output of the following program segment?
int count = 10, *temp, sum = 0;
temp = &count; *temp = 20; temp = ∑ *temp = count; cout<<"count = <<count<< *temp = <<*temp<< sum = "<<sum;
Pointers to structures are so often used in C++ that a special operator exists. The structure pointer operator, the ->, permits expressions that would otherwise be written as, (*x).y to be more clearly expressed as x->y
Prog. & Data Structures(702233)/Fall2003/Dr. Sayed A. Hussein
27
Therefore, there are three ways to access a structure member: Using the structure name Using a pointer to the structure with the indirection operator (*) Using a pointer to the structure with the indirect membership operator (->) If p_str is a pointer to the structure str, the following expressions are all equivalent: str.memb (*p_str).memb p_str->memb Example:
// Program to illustrate structure pointers #include <iostream.h> int main() { struct date { int month, day, year; }; date today, *date_ptr; date_ptr = &today; date_ptr->month = 6; date_ptr->day = 4; date_ptr->year = 1987; cout<<"Todays date is <<date_ptr->day<</<<date_ptr->month <</<< date_ptr->year % 100; return 0; }
then the following five expressions are equivalent: mybox.topleft.x rp->topleft.x (mybox.topleft).x (rp->topleft).x (*rp).topleft.x So far, all that has been done could've been done without the use of pointers. Shortly, the real value of pointers will become apparent.
28
29
public: void setName() //set the name { cout << "Enter name: "; cin >> name; } void printName() //get the name { cout << "\n Name is: " << name; } };
////////////////////////////////////////////////////////////////
do { //put persons in array persPtr[n] = new person; //make new object persPtr[n]->setName(); //set person's name n++; //count new person cout << "Enter another (y/n)? "; //enter another cin >> choice; //person? } while( choice=='y' ); //quit on 'n' for(int j=0; j<n; j++) //print names of { //all persons cout << "\nPerson number " << j+1; persPtr[j]->printName(); } cout << endl; return 0; } //end main()
//WRONG
30
One way to obtain the desired effect is for the calling program to use call by reference (see chapter 1). Another way is to pass pointers to the values to be changed: swap (&a, &b); Since the operator & produces the address of a variable, &a is a pointer to a. In swap itself, the parameters are declared to be pointers, and the operands are accessed indirectly through them.
void swap (int *px, int *py) { int temp; temp = *px; *px = *py; *py = temp; } //interchange *px and *py
In swap:
px
:
py
Pointer arguments enable a function to access and change objects in the functions that called it. It should be clear by now why we use the & operator with the scanf() function. Without the & operator scanf() fails to pass the input into the actual parameters. Example:
//This programs inputs a time in seconds and converts it to hours, //minutes and seconds. #include <iostream.h> void time_hms(int t, int *h, int *m, int *s); int main() { int time; int hours, minutes, seconds;
cout<<"Enter the time in seconds: "; cin>>time; time_hms(time, &hours, &minutes, &seconds); cout <<"The time <<time<< seconds\n"; cout <<"converts to <<hours<< hours <<minutes << minutes and <<seconds<< seconds\n"; return 0; }
Prog. & Data Structures(702233)/Fall2003/Dr. Sayed A. Hussein
31
void time_hms(int t, int *h, int *m, int *s) { int temp = t/60; //total minutes *s = t % 60; *m = temp % 60; *h = temp / 60; } //number of seconds //number of minutes //number of hours
The notation a[i] refers to the i-th element of the array. If pa is a pointer to an integer, declared as int *pa; then the assignment pa = &a[0]; sets pa to point to element zero of a; that is, pa contains the address of a[0].
pa:
a:
a[0] a[1] a[9]
Now the assignment x = *pa; will copy the contents of a[0] into x. If pa points to a particular element of the array, then by definition pa+1 points to the next element, pa+i points i elements after pa, pa-i points i elements before. Thus, if pa points to a[0], *(pa+1) refers to the content of a[1], pa+i is the address of a[i], and *(pa+i) is the content of a[i].
pa: pa+1: pa+2:
a:
a[0] a[1] a[9]
32
These remarks are true regardless of the type or size of the variables in the array a. The meaning of "adding 1 to a pointer," and by extension, all pointer arithmetic, is that pa+1 points to the next object, and pa+i points to the ith object beyond pa. BY definition, the value of a variable or expression of type array is the address of element zero of the array. Thus after the assignment pa = &a[0]; pa and a have identical values. Since the name of an array is a synonym for the location of the initial element, then the assignment pa=&a[0] can also be written as pa = a; A reference to a[i] can be written as *(a+i). In evaluating a[i], C++ converts it to *(a+i) immediately; the two forms are equivalent. Applying the operator & to both parts of this equivalence, it follows that &a[i] = a+i are also identical: a+i is the address of the ith element beyond a. If pa is a pointer, expressions may use it with a subscript; pa[i] is identical to *(pa+i). An array-and-index expression is equivalent to one written as a pointer and offset. There is one difference between array name and a pointer that must be kept in mind. A pointer is a variable, so pa=a and pa++ are legal. But an array name is not a variable; constructions like a=pa and a++ are illegal. When an array name is passed to a function, what is passed is the location of the initial element. Within the called function, this argument is a local variable, and so an array name parameter is a pointer, that is a variable containing an address. We can use this fact to write strlen, which computes the length of a string. //strlen returns the length of a string s int strlen (char *s) { int n; for (n=0; *s!='\0'; s++) n++; return n; } Since s is a pointer, incrementing it is perfectly legal; s++ has no effect on the character string in the function that called strlen(), but merely increments strlen's private copy of the pointer. A formal parameter in a function definition, char s[]; and char *s; are equivalent.
33
the content of p is 2002, not 2001! Each time p is incremented, it points to the next integer. The same is true of decrements. For example, p--; will cause p to have the value 1998, assuming that it previously was 2000. Each time a pointer is incremented it points to the memory location of the next element of its base type. Pointers increase or decrease by the length of the data type they point to. Consider the following declaration (according to Borland C++ specifications): int long float char double then ip++ lp++ fp++ cp++ dp++ will will will will will increase increase increase increase increase ip lp fp cp dp by by by by by 2, 4, 4, 1, 8. *ip; *lp; *fp; *cp; *dp;
You are not limited to increment and decrement, however. You may also add or subtract integers to or from pointers. The statement p = p + 9; makes p point to the ninth element of p type beyond the one it is currently pointing to. Besides addition and subtraction of a pointer and an integer, the only other operation allowed is to subtract a pointer from another pointer. This makes sense when handling arrays. You cannot multiply or divide pointers. You cannot add pointers. You cannot add or subtract type float or double to pointers. Example: //strlen returns the length of a string s int strlen (char *s) { char *p=s; while (*p != '\0') p++; return p - s; } It is possible to compare pointers in a relational expression, as the following example shows: Assume that p and q are pointers, the following statement is perfectly valid. if (p<q) cout<<"p points to lower memory than q\n"; //pointer is incremented //pointers are subtracted
Exercises
1. In the context of the declaration float table[10]; float *pt, *qt; What is the effect of each of the following statements?
Prog. & Data Structures(702233)/Fall2003/Dr. Sayed A. Hussein
34
(a)
(b)
pt = table + 2; qt = pt; *qt = 2.718; pt = table; qt = table + 10; for (; pt<qt; pt++) *pt = 1.23;
(d) (c)
2. Consider the following declaration: int x, y, z, j, t[7] = {7, 2, 9, 6, 4, 0, 3}; int *p=t; What is the value of x, y and z in the following statements? (i) x = *(t+3) + *(t+5); x = (ii) y = *t - 3; y = (iii) z = *p/2 z = (iv) What is the output of the following for-loop? for (j=2; j<5; j++) cout<<*(t+j)<< ;
35
int main() { char *s="Dynamic memory allocation"; int len = strlen(s); //get length of s char *ptr; ptr=new char[len+1]; //set aside memory: string+'\0'
strcpy(ptr,s); //copy s to new memory area ptr cout << "ptr = "<<ptr; delete [] ptr; //the brackets indicate that an array is deleted return 0; }
36
1 1 1 0 0
Input stream
Output stream
In C++ a stream is represented by an object of a particular class. So far we have used the cin and cout stream objects. Different streams are used to represent different kinds of data flow. Most programs need to save data to disk files and read it back in. Working with disk files requires another set of classes: ifstream for input, fstream for both input and output, and ofstream for output. These classes are declared in the fstream.h file.
37
In the above program we declare outfile object and initialize it to the file fdata.txt. This initialization opens the file on the disk. If the file does not exist, it is created. If it does exist, it is truncated and the new data replaces the old. Note: If the file is in a floppy disk, in a folder called myFolder for example, use the file name as: a:\\myFolder\\fdata.txt
38
To read the file created by the above program we can use the get() function as shown in the next program. #include <fstream.h> #include <iostream.h> #include <string.h> int main() { char ch; ifstream infile("test.txt"); while (!infile.eof()) { infile.get(ch); cout << ch; } cout <<endl; return 0; } Another example on character I/O is given below: #include <fstream.h> #include <iostream.h> int main() { char in_name[25], out_name[25]; char c;
Prog. & Data Structures(702233)/Fall2003/Dr. Sayed A. Hussein
39
cout<<"File to be copied:\n"; cin>>in_name; cout<<"Output filename:\n"; cin>>out_name; //open input file stream ifstream infile(in_name); //open output file stream ofstream outfile(out_name); //Read a character from infile and write it in //outfile, exit loop when eof is reached while( !infile.eof()) { infile.get(c); outfile.put(c); } cout<<"File has been copied.\n"; return 0; }
40
//erase buffer for (j=0;j<MAX;j++) buff[j]=0; //create input stream ifstream is("edata.dat",ios::binary); //read from the input stream is.read((char*) buff, MAX*sizeof(int)); //Display data obtained from file. for (j=0;j<MAX;j++) cout << buff[j]<<' '; return 0; } In the statement os.write((char*)buff, MAX*sizeof(int)); the expression(char*)buff casts the starting address of the buff array into a character pointer. The expression MAX*sizeof(int) is the required number of bytes. Example The following examples stores data in an array called list of type entry. It then saves the whole of the array in a file called list.out. #include <iostream.h> #include <fstream.h> int main() { struct entry { char fname[20]; char lname[20]; char phone[10]; }; entry list[4]; int i; ofstream file("list.out",ios::binary); // Loop to input data for four people. for (i = 0; i < 4; i++) { cout<<"Enter first name: "; cin>>list[i].fname; cout<<"Enter last name: "; cin>>list[i].lname; cout<<"Enter phone in 999-9999 format: cin>>list[i].phone; } file.write((char*)list,4*sizeof(entry)); file.close(); cout<<"data saved"; return 0; } 41
";
Example The following examples reads data stored in the binary file list.out, which was created by the above program, into an array called list. It then displays the array content on the screen. #include <iostream.h> #include <fstream.h> int main() { struct entry { char fname[20]; char lname[20]; char phone[10]; }; entry list[4]; int i; ifstream inf("list.out",ios::binary); inf.read((char*)list,4*sizeof(entry)); //Print two blank lines. cout<<"\n\n"; // Loop to display data. for (i = 0; i < 4; i++) { cout<<"Name: "<<list[i].fname<<" "<<list[i].lname; cout<<"\t\tPhone: "<<list[i].phone<<endl; } return 0; } Practice Exercise: 1. Write the include line that is needed to work with disk file I/O? 2. Write the C++ statement that opens the file text.out for input? 3. Write a while loop that reads a character from that file and display it on the computer screen. Make sure that the while loop ends when there are no more data. 4. Write a statement to close the above file. 5. Write a statement to create file data.bin for binary output of 100 double numbers, stored in an array called doub.
42
A pointer that stores the address of the first node is usually called the head of the list. A structure, which contains a data element and a pointer to the next node, is created by, struct list { int list }; This defines a new data structure called list (actually the definition of a node), which contains two members. The first is an integer called value. The second is called next, which is a pointer to another list structure (or node). Suppose that we declare two structures to be of the same type as list, e.g., list n1, n2; The next pointer of structure n1 may be set to point to the n2 structure by n1.next = &n2; // assign address of first element in n2 to the pointer next of the n1 structure which creates a link between the two structures.
// Program to illustrate linked lists #include <iostream.h> int main() { struct list { int value; list *next; }; list n1, n2, n3, *head=&n1; int i; n1.value = 100; n2.value = 200; n3.value = 300; n1.next = &n2; n2.next = &n3; n3.next = 0; //NULL, as shown in the figure below i = n1.next->value; cout<<i<< <<n2.next->value; return 0; }
Prog. & Data Structures(702233)/Fall2003/Dr. Sayed A. Hussein
value; *next;
43
The following diagram illustrates the linked list created by the above program. n3 n1 n2 head
value=100 next value=200 next value=300
In using linked list structures, it is common to assign the value of 0 to the last pointer in the list, to indicate that there are no more nodes in the list, e.g., n3.next = 0;
n2
value=200 next
n3
value=300
head
n1
value=100 next
n2
value=200 next
n3
value=300
To add a node at the beginning of the list (node n0, say), we use the head of the list: n0.next = head; // adds node n0 head = &n0; n0
value=100 next
head
n1
value=100 next
n2
value=200 next
n3
value=300
44
We can also add a node (let us say node n4) to the end of a list, as shown in the following statements: n4.next = 0; //adds node n4 (it is possible to replace 0 with n3.next) n3.next = &n4; n4
value=100
head
n1
value=100 next
n2
value=200 next
n3
value=300 next
45
This program uses a pointer called ptr to cycle through the linked list. Note that we may simply write the while loop condition to read as: while(ptr) , since any non-zero value is true and 0 is false. Practice Exercise: 1. Define a structure called node, which contains an integer element called data, and a pointer to a structure of type node called next_node. 2. Declare three structures called node1, node2, node3, of type node. 3. Write C++ statements which will link the three nodes together, with node1 at the head of the list, node2 second, and node3 at the tail of the list. Assign the value NULL to node3.next to signify the end of the list. 4. Using a pointer list, of type node, which has been initialized to the address of node1, write C++ statements which will cycle through the list and print out the value of each nodes data field. 5. Assuming the state of the list is that as in 3., write C++ statements which will insert a new node node1a between node1 and node2, using the pointer list (which is currently pointing to node1). Assume that a pointer new_node points to node node1a.
First we create the list head and initialize it with 0, indicating that the list is empty. head list *head=0; //this is represented as: Now, let us create a node and add it to the list. We need a pointer to store the address of the current node of the list; call it listptr. list * listptr; listptr = new list;
head value=? next=? listptr
To add the newly created node to the list we use the information stored in the head pointer as shown in the following statements. Notice that the new node has no identifier (variable name), but we know its address. Making use of the structure pointer operator (arrow operator; ->) we can access its members. head listptr ->next = head; head = listptr;
listptr value=?
WARNING: It's important to switch the pointers in the correct order. If you reassign Prog. Datapointer Structures(702233)/Fall2003/Dr. the & head first, you will lose the Sayed list! A. Hussein
46
To create another node, again we use the new operator, and store the returned address in a pointer newnode, say. newnode = new list;
head value=? listptr value=?
newnode
The following statements add this node to the linked list and set listptr to point to the end node. newnode->next = listptr->next; listptr->next = newnode; listptr = newnode;
head value=? listptr next value=?
To continue adding nodes we keep repeating the above sequence of statements. Let us assume that the value field is to be read from the standard input. We need to reset listptr and then traverse the list. The value field of the node we access is read from the keyboard. This is illustrated in the following statements: listptr = head; //reset listptr while(listptr) { cout<< Enter value please: ; cin >>listptr->value; listptr = listptr->next; }
Practice exercise: 1. Write a function called delete_node, which accepts a pointer to a list, and a pointer to the node to be deleted from the list, e.g. void delete_node(node *head, node *delnode ); 2. Write a function called insert_node, which accepts a pointer to a list, a pointer to a new node to be inserted, and a pointer to the node after which the insertion takes place, eg void insert_node(node *head, node *newnode, node *prevnode);
47
48
// delete a node from list void del_node( node *headptr, node *nodeptr ) { node *deletepointer, *previouspointer; char buffer[20]; deletepointer = headptr->next; previouspointer = headptr; // find the entry cout<<"\nEnter name to be deleted --->"; cin>>buffer; while( deletepointer ) { if( strcmp( buffer, deletepointer->data ) == 0 ) { // delete node pointed to by delete pointer previouspointer->next = deletepointer->next; break; } else { // goto next node in list deletepointer = deletepointer->next; previouspointer = previouspointer->next; } } // did we find it? if( deletepointer == NULL ) cout<<"\n\007Error, "<<buffer<<" not found or list empty\n"; else { delete deletepointer; // adjust nodeptr to the last node in list nodeptr = headptr; while( nodeptr->next != NULL ) nodeptr = nodeptr->next; } }
// print out the list void list( node *headptr ) { node *listpointer; listpointer = headptr->next; if( listpointer == NULL ) cout<<"\nThe list is empty.\n"; else { while( listpointer ) { cout<<"Name : "<<listpointer->data<<endl; listpointer = listpointer->next; } } }
49
// main menu system void menu( node *headp, node *nodep ) { int menuchoice = 1; while( menuchoice != 4 ) { cout<<"1 insert a node\n"; cout<<"2 delete a node\n"; cout<<"3 list nodes\n"; cout<<"4 quit\n"; cout<<"Enter choice -->"; cin>>menuchoice; switch( menuchoice ) { case 1 : if( insert( nodep ) == 0 ) cout<<"\n\007Insert failed.\n"; nodep=nodep->next; break; case 2 : del_node( headp, nodep ); break; case 3 : list( headp ); break; case 4 : break; default : cout<<"\n\007Invalid option\n"; break; } } }
50