$30
This lab focuses mainly on pointers. Following the recent and near future lectures on pointers, this lab contains four major parts: Part I: Pointers and passing address of scalar variables. Part II:
Pointer arithmetic. Part III: Pointers and passing char arrays (strings) to functions; Part IV:
Pointers and passing general arrays to functions.
Before delving into pointers, let’s start with exercises on rand() library function and simple recursions, which we covered recently. We also look at system() library function as an preparation of the Unix materials that will be covered soon in class.
0 system() library function
Download file lab5randSys.c, compile and run it. Observe that,
• the program calls function rand() to generate random numbers. Randomization is a fundamental technique in algorithm design. Function rand(), declared in <stdlib.h, returns a random integer in the range 0 to RAND_MAX every time it is called.
RAND_MAX is a constant whose default value may vary between implementations but it is granted to be at least 32767. On machines using the GNU C library RAND_MAX is equal to INT_MAX.
• to generate a random number in a certain range, e.g., [1, 6] to represent dice rolls, a typical approach is to use modules operation and then shift by adding the lower bound. Try to understand the mathematic trick.
• note that rand() is a pseudorandom number generator: the sequence of values it returns is predictable. Run the program several times and you will notice the same sequence of random number.
• if you want to get different sequences, you need to seed the random number generator using srand(). A typical use might be: strand(time(0)). Here time(0) returns the number of seconds since the epoch (00:00:00 UTC, January 1, 1970, for POSIX systems). As you observed above, If random numbers are generated with rand() without first calling srand(), your program will create the same sequence of numbers each time it runs. Now uncomment the first line, compile and run the program again for several times, and you will observe different sequence of numbers. (Note that this still might give repeated values if you run it fast – e.g., twice in the same second). Explore other approaches if you are interested. Also explore how to generate random number in Java.
Next, uncomment the commented block in the second half of the program.
The code block calls a standard library function system(), whose prototype is int system(char *command) as given in stdlib.h. Taking as input a string command, which is a valid Unix command, system() executes a Unix shell command specified in command, much as if you enter the command in terminal. Compile and run it. Observe that, • current location is displayed, with pwd command.
• the current directory is listed, and new directories xxxDir were created in the current directory, and the current directory is listed again. These are performed using ls, mkdir commands.
Issue commands ls -l in the terminal to verify that directory xxxDir was generated.
Remove the directory each time before you run the program again. (Can you do that in terminal using commands? Try issue command rmdir xxxDir but don’t spend too much time if you cannot make it. We will learn how to do this in terminal when we cover unix commands later in the course.)
lab3D0
0. A Math Library functions, simple recursions
Specification
Write an ANSI-C program that reads input from the standard input, which contains one double and one integer representing a base b and exponent (i.e., power) n, and then calculates bn. After reading base and exponent from the user, the program first calls the math library function pow(), and then call function my_pow(), which is a recursive function that you are going to implement here.
The program keeps on prompting user and terminates when user enters -1000 for base
(followed by any number for exponent).
Implementation
Download file lab5pow.c and start from there. Note that to read a double using scanf, we need to use %lf. (%f is use for float).
• Your function my_pow(double, double) should be RECURSIVE, not ITERATIVE. That is, the function should be implemented using RECURSION, not loops. In a recursive solution, the function calls itself with different (usually smaller) inputs, until the input becomes small enough so that we can solve the case directly. This case is called a base case.
• Note that although the function’s parameters are of type double, the actual argument for exponent is assumed to be an integer literal (i.e. the power will not be 3.5). However, the power can be negative. Your functions should handle this.
Sample Inputs/Outputs red 117% a.out
Enter base and power: 10 2
pow: 100.0000 my_pow: 100.0000
Enter base and power: 10 4 pow: 10000.0000 my_pow: 10000.0000
Enter base and power: 2 3 pow: 8.0000 my_pow: 8.0000
Enter base and power: 2.3 5 pow: 64.3634 my_pow: 64.3634
Enter base and power: -2 4
pow: 16.0000 my_pow: 16.0000
Enter base and power: -2.75 5
pow: -157.2764 my_pow: -157.2764
Enter base and power: 2 -3 pow: 0.1250 my_pow: 0.1250
Enter base and power: 2 -5 pow: 0.0312 my_pow: 0.0312
Enter base and power: 2.7 -3 pow: 0.0508 my_pow: 0.0508
Enter base and power: -2 -6 pow: 0.0156 my_pow: 0.0156
Enter base and power: -2.75 -3 pow: -0.0481 my_pow: -0.0481
Enter base and power: -1000 4
red 118%
Part I Pointers and passing address of scalar variables 1. Problem A
Subject
Experiencing “modifying scalar arguments by passing addresses/pointers”.
Specification
Write an ANSI-C program that reads three integers line by line, and modify the input values.
Implementation Download file lab5swap.c to start off.
• The program reads user inputs from stdin line by line. Each line of input contains 3 integers separated by blanks. A line that has the first number being -1 indicates the end of input.
• Store the 3 input integers into variable a, b and c;
• Function swapIncre() is called in main() with an aim to change the values of a, b and c in such a way that, after function swapIncre returns, b’s value is doubled, a stores c’s original value incremented by 100, and c stores the original value of a. As an example, suppose a is 1, b is 2 and c is 3, then after function returns, a has value 103, b has value 4 and c has value 1.
• Compile and run the program and observe unsurprisingly that the values of a, b and c are not changed at all (why?).
• Modify the program so that it works correctly, as shown in the sample inputs/outputs below. You should only modify function swapIncre and the statement in main that calls this function.
No global variables should be used.
Sample Inputs/Outputs:
red 309 % a.out
4 8 9
Original inputs: a:4 b:8 c:9
Rearranged inputs: a:109 b:16 c:4
5 12 7
Original inputs: a:5 b:12 c:7
Rearranged inputs: a:107 b:24 c:5
12 20 -3
Original inputs: a:12 b:20 c:-3
Rearranged inputs: a:97 b:40 c:12
12 -3 30
Original inputs: a:12 b:-3 c:30
Rearranged inputs: a:130 b:-6 c:12
-1 2 3
red 309 % cat inputA.txt
3 5 6
2 67 -1
-12 45 66
66 55 1404
22 3 412
-2 44 6 -1 55 605
red 310 % a.out < inputA.txt Original inputs: a:3 b:5 c:6
Rearranged inputs: a:106 b:10 c:3
Original inputs: a:2 b:67 c:-1
Rearranged inputs: a:99 b:134 c:2
Original inputs: a:-12 b:45 c:66
Rearranged inputs: a:166 b:90 c:-12
Original inputs: a:66 b:55 c:1404
Rearranged inputs: a:1504 b:110 c:66
Original inputs: a:22 b:3 c:412
Rearranged inputs: a:512 b:6 c:22
Original inputs: a:-2 b:44 c:6
Rearranged inputs: a:106 b:88 c:-2
red 311%
2. Problem A2
Modify program lab5swap.c, by defining a new function void swap(int *, int *) which swaps the values of a and c. This function should be called in function swapIncre().
Specifically, swapIncre()only increases the value of parameters, and delegates the swapping task to swap().
You should not change the code of main, and the parameter list of swapIncre given in your lab5swap.c.
Again, no global variables should be used.
Sample Inputs/Outputs: Same as above.
3. Problem A3
Modify the above program, by defining a new function void swap(int **, int **) which swaps the values of a and c. This function should be called in function swapIncre().
Specifically, swapIncre()only increases the value of parameters, and delegates the swapping task to swap().
You should not change the code of main, and the parameter list of swapIncre given in your lab5swap.c. Again, no global variables should be used.
Sample Inputs/Outputs: Same as above.
fact 1
Part II Pointer/address arithmetic
C supports some arithmetic operations on pointers. For expression p ± n, where p is a pointer and n is an integer, the result is another address (pointer).
Download program lab5pArithmetic.c and study the code. Then compile and run it several times. You will get different values each time, but you should always observe the following:
• For pChar which is a pointer to char, expression pChar+1 results in an address (pointer) whose value is the value of pchar plus 1. For pShort which is a pointer to short, expression pShort+1 results in an address whose value is the value of pShort plus 2. For integer pointer pInt, expression pInt+1 results in an address whose value is the value of pInt plus 4. For Double pointer pDouble, expression pDouble+1 results in an address whose value is the value of pDouble plus 8. Likewise, these pointers + 2 result in addresses whose values are the original values plus 2, 4, 8 and 16 respectively. Why was C designed this way?
• As discussed in class, the rule here is that for a pointer p, arithmetic expression p ± n results in an address (pointer) whose value is the value of p ± n × s where s is the size of the type of p’s pointee, in bytes. That is, the result is “scaled” by the size of the pointee type. Thus, for an integer pointer pInt, expression pInt + n results in an address whose value is the value of pInt + n×4, assuming size of int is 4 bytes. (Because of this, pointer arithmetic in C is sometimes colloquially termed “p+1 is p+4”.)
• This rule is further verified by the outputs for p++, which assign the pointers to resulting addresses, jumping the pointers by 1, 2, 4 and 8 bytes respectively, and the outputs for p += 4, which jump the pointers by 4×1, 4×2, 4×4 and 4×8 bytes respectively.
• For an array arr, its elements are stored continuously in memory, with arr[0] occupying the lowest address. For an integer array like arr, each element occupies 4 bytes in memory. So the address of arr[i+1] is 4 bytes higher than the address of arr[i]. For a double array, as another example, address of arr[i+1] is 8 bytes higher than the address of arr[i].
• If we have a pointer ptr0 that points the first element of the array, i.e., ptr0=&arr[0]¸ then according to the pointer arithmetic rule above (fact 1), ptr0+i results in an address of value ptr0+i×4, which, due to the fact that array elements are stored continuously in memory (fact 2), is the address of element i of arr. That is, if ptr0==&arr[0], then ptr0+i == &arr[i], which in turn implies that *(ptr0+i) == arr[i].
• Array name arr contains the address of its first element, that is, arr and &arr[0] contain the same address. So array name can be treated as a pointer (to its first element). Following pointer arithmetic, arr+i results in an address of value arr+i×4, which is the address of element i of arr. That is, arr+i == &arr[i], and *(arr +i) == arr[i];
• Since array name arr is a pointer, assignment operation ptr = &arr[0] can be rewritten as ptr = arr, which assigns ptr the address of the first element of the array, making ptr point to arr[0]. Consequently, ptr0, ptr, arr and &arr[0] contain the same value. Hence, we have the rule that if ptr == arr (i.e., ptr points to arr[0]), then ptr+i == arr+i == &arr[i], and *(ptr+i) == *(arr+i) == arr[i].
fact 2
fact 3
fact 4
fact 5
Based on the above observations, complete the program so that arr[i] can also be accessed in two other ways which involve pointer arithmetic, generating the following outputs
arr[i] *(arr+i) *(ptr0+i) *(ptr+i) ==========================================================================
Element[0]: -100 -100 -100 -100
Element[1]: 100 100 100 100
Element[2]: 200 200 200 200
Element[3]: 300 300 300 300
Element[4]: 400 400 400 400
Element[5]: 500 500 500 500
Element[6]: 600 600 600 600
Element[7]: 700 700 700 700
Element[8]: 800 800 800 800
Element[9]: 900 900 900 900
• observe how a pointer to pointer is declared, initialized, and dereferenced. Understand the results. For example, why the two (**pp)-- statements result in different values?
• Finally, since array name can be used as a pointer, is there any difference between array name and other pointers such as ptr? Uncomment the last line and compile again. What did you get? Observe that ptr and pp can be changed as they are pointer variables. Array name arr, on the other hand, is a pointer constant so cannot be changed.
Why does C have pointer arithmetic and why is the result scaled based on the type? Why array name contains the address of its first element?
It turns out that all the above rules were designed with an aim to facilitate passing array to functions, which is the subject of Part III and Part IV below.
Part III Pointers and passing char arrays to functions
Motivation
In C when an array is passed as an argument to a function, it is ‘decayed’ into a single value which is the (starting) memory address of the array, contained in the array name that is passed to the function. That is, the function only receives a single address value, rather than the whole array -- whether the pointee at this address is a single variable or it is the first element of an array, or something else, it is the same form of information that is passed to the function. Thus, a function that expects an integer array as argument can specify its parameter (formal argument) either as int[] or int *. Likewise, a function that expects a char array (string) as argument can specify its parameter (formal argument) either as char[] or char *. (See prototype of functions in string.h). In calling the function, you can pass as the actual argument either the array name (which contains the address of its first element), or a pointer to an element of the array. Either way, passing array by address allows the invoked function to not only access the argument array but also modify it, even it is called-by-value.
Problem C0
Passing char array as argument, and pointer notation in place of array index notation [].
Download the program lab5strlen.c, which shows more than 10 ways to implement strlen(). Read the code and run it, and observe the following:
• Firstly, since the array name contains the address of the first element (fact 4), in addition to array name as we did so far, we can also pass the address &arr[0] explicitly into this functions. If we have a pointer that points to the start of the string, then we can also pass the pointer to the functions.
• Functions expecting a char array can specify the parameter (formal argument) either as char [], or, char *.
• Functions expecting a char array can be called by passing either the array name or a pointer to an array element as its actual argument.
• Even a function’s formal argument is declared as char[], you can always use pointer notations to manipulate the argument in the function.
• Even a function’s formal argument is declared as char *, you can always use array notation [] to manipulate the argument in the function
• Address/pointer arithmetic can be exploited strategically to calculate the string length
• Because of ‘decaying’, sub-arrays can be passed to a function easily. Note the 3 ways to pass sub-arrays of msg.
• By passing sub-arrays, recursion can be exploited to solve the problem.
• Based on the fact that array elements are stored continuously in memory, and assuming the array is fully populated, the length of an array can be calculated using sizeof operator, with sizeof(arr)/sizeof(char) or sizeof(arr)/sizeof(arr[i]). o In case of char array, we subtract 1 to exclude the '\0'.
Note that this approach does not work when used on a pointer variable that points to the array: sizeof ptr gives the memory size of the pointer variable ptr itself, which is usually 8 bytes. Note, sizeof is an operator, not a function. (As shown later in this lab (lab5E0), in a function that receives a array argument, using sizeof on parameter also does not work.)
4.1 Problem C
Subject Passing char array as argument, accessing argument array. Pointer notion in place of array index notation.
Specification
Write an ANSI-C program that reads inputs line by line, and determines whether each line of input forms a palindrome. A palindrome is a word, phrase, or sequence that reads the same backward as forward, e.g., "madam", "dad". The program terminates when quit is read in.
Implementation
Download file lab5palin.c to start with.
• Assume that each line of input contains at most 30 characters but it may contain blanks.
• Use fgets to read line by line o note that the line that is read in using fgets will contain a new line character '\n', right before '\0'. Then you either need to exclude it when processing the array, or, remove the trailing new line character before processing the array. As discussed in class, one common approach for the latter is replacing the '\n' with '\0'.
• Define a function void printReverse (char *) which prints the argument array reversely.
o Do not use array indexing [] throughout your implementation. Instead, use pointers and pointer arithmetic to manipulate the array.
o Do not create extra arrays. Manipulate the original array only.
• Define a function int isPalindrome (char *) which determines whether the argument array (string) is a palindrome. o Do not use array indexing [] throughout your implementation. Instead, use pointers and pointer arithmetic to manipulate the array.
o Do not create extra arrays. Manipulate the original array only.
• Do not use global variables.
• [Bonus] Define another function int isPalindromeR (char *) which determines whether the argument array (string) is a palindrome, using recursion.
o Do not use array indexing [] throughout your implementation. Instead, use pointers and pointer arithmetic to manipulate the array.
o Do not create extra arrays. Manipulate the original array only.
o isPalindromeR(char*) itself is not necessarily a recursive function. You may want to create a recursive helper function, which is called by isPalindromeR(char *).
Hint: the reason for a helper function is that the recursive function needs more argument than isPanlidromR(char*)
Sample Inputs/Outputs:
red 339 % a.out hello olleh Not a palindrome
lisaxxasil lisaxxasil Is a palindrome.
that is a SI taht that IS a si taht Not a palindrome.
that is a si taht that is a si taht
Is a palindrome.
quit red 340 %
red 340 % a.out < inputPalin.txt
olleh
Not a palindrome.
doogsisiht Not a palindrome.
dad
Is a palindrome.
daD
Not a palindrome.
LI Saxxas il Not a palindrome.
123454321 Is a palindrome.
madam
Is a palindrome.
qwerty uiopoiu ytrewq
Is a palindrome.
33
Is a palindrome.
A
Is a palindrome.
lisaxxtsil
Is a palindrome.
that si a si taht Not a palindrome.
that is a si taht
Is a palindrome.
abCdyfxDCBA
Not a palindrome.
abcdefedcba Is a palindrome.
red 342 %
4.2 Problem D
Subject
Array name contains address. Thus when an array is passed to a function as argument, the function is able to not only access the array, but also modify argument array. Pointer notion in place of array index notation.
Specification
Complete program lab5sort.c, which reads inputs line by line, and then for each line, sorts it alphabetically, according to the indexes of the characters in ASCII table, in ascending order, using two approaches. That is, the letter that appears earlier in the ASCII table should appear earlier in the sorted array. The program terminates when quit is read in.
Implementation
• Assume that each line of input contains at most 50 characters and may contain blanks.
• Use fgets to read line by line
• Define a function void sortArray (char *) which sorts characters in the argument array according to the index in the ASCII table.
o Don’t use extra arrays. The function should sort and modify the argument array directly.
• Define a function void sortArray2 (char *) which sorts characters in the argument array according to the index in the ASCII table, using another approach. o Do not use extra arrays. The function should sort and modify the argument array directly.
• Do not use array indexing [] throughout the program, except for array declarations in main.
Instead, use pointers and pointer arithmetic to manipulate arrays.
• Do not use global variables.
• People have been investigating sorting problems for centuries and there exist various sorting algorithms, so don’t try to invent a new one. Also, don’t call library function such as qSort to do the sorting. Instead, you can implement any one of the existing famous sorting algorithms, e.g., Bubble Sort, Insertion Sort, Selection Sort, Quick Sort, Merge Sort. (Compared against recursive algorithms such as Quick Sort, Merge Sort which has O(nlgn) complexity, the first three algorithms are simpler but slower - O(n2) complexity). Pseudo-
code for Bubble Sort and Selection Sort are given below for you. You can implement these algorithms or some others. Don’t use [] in your implementation.
BUBBLE-SORT(A)
0. n ← number of elements in A
1. for i ← 0 to n-2 // ≤ n-2
2. for j ← n-1 to i+1 // right to left
3. if A[j] < A[j-1]
4. swap A[j] ↔ A[ j – 1 ]
SELECTION-SORT(A)
0. n ← number of elements in A
1. for i ← 0 to n-2 // ≤ n-2
2. smallest ← i // smallest: index of current smallest, initially i
3. for j ← i + 1 to n-1
4. if A[ j ] appears earlier than A[ smallest ] in ASCII table
5. smallest ← j // update smallest
6. swap A[ i ] ↔ A[ smallest ] // move smallest element to index i
Sample Inputs/Outputs:
red 340 % a.out hello ehllo ehllo
7356890
0356789
0356789
DBECHAGIF
ABCDEFGHI
ABCDEFGHI hello world dehllloorw dehllloorw
2031ON 2021W
00112223NOW
00112223NOW
quit
red 341 % a.out < inputSort.txt
02eehortt
02eehortt
023456ERbbdggjnnos
023456ERbbdggjnnos
agghhrrtvy agghhrrtvy
024667uy
024667uy
000001112239ABCEF
000001112239ABCEF
0123456789opqrstuvwxy
0123456789opqrstuvwxy
abcdefghijklmnopqrstuvwxyz abcdefghijklmnopqrstuvwxyz
red 342 %
Part IV Pointers and passing general arrays to functions
In C when an array is passed into a function, it is ‘decayed’ into a single memory address. That is, the function only receives a single address value, rather than the whole array structure itself. Without a view of the whole structure, the function does not “know” if the pointee at this address is a single variable or it is the first element of an array. No array length information is passed to the function automatically, thus the caller should provide the function with the information about where the array ends. In the case of a character array (string), the special sentinel character '\0' is used to mark the end of array. For other type of arrays such as int [], double [], however, the caller needs to provide the function with the length information explicitly. In this section you will explore different approaches to providing the length info of an argument array.
5.0. Problem E0
Subject
Exploiting array memory size. (Not working).
Some people think that the function does not necessarily need a terminator token or an extra argument of length information. The seemingly plausible trick is to use sizeof on parameter. As implemented in lab5E0.c, one attempt is to get the array length by exploiting the memory size of the array. Specifically, assuming the array is fully populated, then the number of elements can be derived with operation sizeof(array)/sizeof(int).
Compile and run lab5E0.c. Observer that,
• For an array, both the functions receive the correct starting address of the argument array.
• sizeof(arrName)/sizeof(type) works in main.
• in both the functions, however, sizeof(formal argument) / sizeof(type) does not give the correct length of the actual argument array, even when the formal argument is declared as int [] or char[].
Think about why this happens.
Hint:1) array passed to a function is “decayed” to an address. Thus argument c, even if defined as int c[], is converted to int *c by the compiler; 2) sizeof is an operator, not a function. (strlen is a function, so it works inside the function). 3) some compiler will give you warning message about this.
5.1. Problem E. Using terminator token
Subject
Explore putting a special sentinel token at the end of array, like the case of string. Use scanf to detect end of file.
“My data are in my lockers. I occupy several (consecutive) lockers, starting at locker #10, and the last locker contains a bunny teddy bear in it” – so given starting locker number #10, the function knows that the locker with a bunny bear is the end.
Specification
Write an ANSI-C program that reads a list of non-negative integer values (including 0), until EOF is read in, and then outputs the largest value among in the input integers.
Assume there are no more than 20 integers. All inputs are non-negative integer literals.
Implementation
Download lab5E.c to start with.
• Keep on reading integers using scanf and a loop, and put the integers into an array, until EOF is read.
In earlier labs we have experienced how getchar detects end of file (end of input file or Ctrl+D). We have used scanf to read input and we have ignored the fact that scanf also has a return value, which is an integer indicating the number of characters read in, and, same as getchar, function scanf also returns EOF if end of file is reached. You can issue man 3 scanf | grep return or man 3 scanf | grep EOF in the terminal to see details.
• Note that several input integers can appear on the same line. So far we have used scanf to read a line of input a time (which contains no spaces). Here you can observe that scanf with a loop can read inputs that appear on the same line, as well as on multiple lines.
• In main, index notation [] should only be used in declaring the array. For the rest of code in main, you should use pointer indirection and address arithmetic to access and update the array. No array index [] should be used.
• Define a function void display(int *), which, given an integer array, prints the array elements.
Note that this function takes just one argument, which is the starting address of array. How could the caller let the function know where the end of the array is? (Hint: could the caller add a bunny bear in the array before passing the array to the function?)
In this function, use pointer indirection and address arithmetic to access and traverse the array. No array index [] should be used in the function.
• Define a function int largest(int *), which, given an integer array, returns the largest integer in the array.
Note that this function also takes just one argument, which is the starting address of array. In this function, use pointer indirection and address arithmetic to access and traverse the array. No array index [] should be used in the function.
• Do not use global variables.
Sample Inputs/Outputs:
red 330 % a.out
1 2 0 33
445
23
^D
Inputs: 1 2 0 33 445 23
Largest value: 445
red 331 % a.out < inputE.txt
Inputs: 7 5 3 6 9 18 33 44 5 12 9 0 34 534 128 78
Largest value: 534
Submit using submit 2031A lab5 lab5E.c
5.2 Problem E2 Length info as argument
Subject
Passing length info explicitly. Use scanf to detect end of file.
The above approach provides the length info about argument array by putting a special sentinel terminator token at the end of the array, like the case of string. This is possible because the inputs are assumed to be non-negative integer literals. But putting a terminator might not always be possible.
A more general approach, which is more common for general arrays, is to pass the length info explicitly to the function (as an additional argument).
“My data are in my lockers. I occupy several (consecutive) lockers, starting at lock #10, and I occupy eight lockers” – so given starting locker number #10, the function knows that locker #17 is the end.
Specification
Same problem and requirement as above, but this time suppose the input numbers can be both positive and negative (so we could not store a special terminator token in the array).
Implementation
• Implement function int largest(int *, int) and void display(int *, int).
Same as before, no array index [] should be used in main, except the array declaration. No array index [] should be used in largest and display at all.
• Do not use global variables.
Sample Inputs/Outputs: red 340 % a.out
1 2 0 33
-445
23
^D
Inputs: 1 2 0 33 -445 23
Largest value: 33
red 341 % a.out < inputE2.txt
Inputs: 7 5 3 6 9 18 -33 44 5 -12 0 9 -34 534 128 78
Largest value: 534
5.3 Problem E2-void
Rewrite lab5E2.c such that function largest is void and has one more parameters. That is, void largest(int *, int, ?) where ? is a type that you decide. Call the function properly in main so it has the same input and output as problem E2. Note that function largest should not print anything. Generate output in main as before.