Laboratory Training 5
Working with Pointers, Strings, and Files
1 Training Tasks
1.1 Sum of the Minimum and Maximum Items
Write a program that calculates the sum of the minimum and maximum items of an array of double precision floating point values. Use two separate functions. The program must meet the following requirements:
- the array length should be read from the keyboard using the
scanf()
function; - the array is created in free store;
- the array should be filled with random values in the range from 0 to 100;
- the array should be bypassed using address arithmetic;
- the parameters of the function of calculating the minimum and maximum items are the pointers to
double
and the array size; - the result should be shown using
printf()
function; - data must be deallocated from the free store.
1.2 Checking Palindromes
Read a sentence (array of characters) from the keyboard using the getline()
function, check whether
it is a palindrome and display the corresponding message. Recommendation: do not use capital letters when entering.
The result should be displayed using printf()
function.
A palindrome is a sentence that reads the same in both directions (from left to right and from right to left), for example, "never odd or even" Spaces and punctuation are not taken into account. In the software implementation, it is advisable to remove them and check the resulting string.
1.3 Individual Assignment
Prepare a text file that contains information about the size of the two-dimensional array, determined in the individual task of the previous laboratory training. Sizes must be represented by two integers separated space character. On the following rows place the array items linewise.
The program should contain the following activities:
- creation of a two-dimensional array of integer items in free store by reading its sizes from the file;
- reading array items from file;
- creation of a second array of required size in free store;
- processing the first array and fill the second array according to the individual task of the previous laboratory training;
- creation of a text file and write the modified first array items in it;
- creation of another text file and write the items of the second array in it;
- deallocation arrays from free store.
In parallel with the output to the file, you should provide output to the console window using the printf()
function. When working with a one-dimensional array, use an address arithmetic instead of working with the index is required.
2 Instructions
2.1 Definition of Pointers
Each variable is located at a unique location in computer memory. This location is known as an address. A pointer
is a variable that holds a memory address. Pointers record the addresses of some other objects, of some
specified types. A pointer is declared with an asterisk preceding the variable name. In the following example,
variable p
can hold an address of an integer variable:
int *p = 0;
A pointer whose value is zero is called a null pointer. All pointers, when they are created, should be initialized to something. If you don't know what you want to assign to the pointer, you can assign zero to it. A pointer that is not initialized is called a wild pointer. Wild pointers are very dangerous.
To assign or initialize a pointer, set an address of operator (&
) before the name of
the variable whose address is being assigned. For example,
int i = 5; int *p = &i;
You can use the so called dereferencing to access the data stored at the address in a pointer. To
dereference a pointer, the dereference operator (*
) is set before the pointer name. For example,
int j = *p + i; // j = 5 + 5 = 10
Essentially, *p
is a synonym of i
. You can handle with *p
like any other
integer variable.
*p = 6; // i = 6
You can create an array of pointers:
int i = 0; int j = 1; int k = 10; int *pa[3]; pa[0] = &i; pa[1] = &j; pa[2] = &k; for (int i = 0; i < 3; i++) { cout << *pa[i] << ' '; // 0 1 10 }
You can also initialize items of an array with addresses of variables:
int *pb[] = {&i, &j, &k}; for (int i = 0; i < 3; i++) { cout << *pb[i] << ' '; // 0 1 10 }
The rules of type compatibility for pointers are stricter than analogous rules for data types. You can assign integer to double and double to integer you but cannot assign pointers of different types to each other:
int k = 1; double d = k; int j; j = d; int *pk = &k; double *pd; int *pj; pj = pk; // OK. Two pointers hold address of same variable pd = pk; // Syntax error pi = pd; // Syntax error float *pf = pd; // Syntax error
Pointers can be assigned the integer 0 or the nullptr
constant (since C ++ 11).
It is a special type of pointers: void
*
. You can assign pointers of any types
to void
*
. Variables of type void
*
are used for
holding pointers of different types. In the following example, an array of
void
*
holds addresses of different variables:
int i = 0; double d = 1.5; char c = 'A'; void *p[4] = {&i, &d, &c}; // p[3] = 0 double *pd = &d; p[3] = pd;
You cannot dereference void
*
. To obtain pointers of particular types from the
array, you must cast pointers to necessary types using type casting operator:
char *pc = (char*)p[2]; cout << *pc; // 'A'
You can create a pointer to a constant to be defined such that the value to which the pointer points cannot be changed but the pointer itself can be moved to another variable or constant.
int k = 4; const int *pk = &k; // Pointer to a constant k = 5; cout << *pk; // 5 *pk = 6; // Error!
A constant pointer (const
keyword is placed after *
) is a
pointer that cannot be changed. However, the value to which the pointer points can be changed. Constant pointers
must be initialized:
int i; int * const cp &i; cout << *pc; // 'A'
You can also create constant pointers to constant objects.
Arguments of a function can be declared as pointers. This allows you to send addresses of given variables to a function and modify those variables using their addresses. Assume that we need to swap values of two integer variables. The function that swaps two integers can be as follows:
void swap(int *p1, int *p2) { int x = *p1; *p1 = *p2; *p2 = x; }
We can invoke swap()
from main()
function:
void main() { int a = 1; int b = 2; swap(&a, &b); // Taking addresses of variables cout << a << endl; // 2 cout << b << endl; // 1 }
2.2 Arrays and Pointers
In C++, an array name is a constant pointer to the first item of the array. Therefore, in the declaration
int a[50];
a
is a constant pointer to &a[0]
, which is the address of the staring item of the
array. It is legal to use array names as constant pointers and contrariwise.
int a[5] = {1, 2, 4, 8, 16}; int *p = a; cout << *a << endl; // 1 cout << p[2] << endl; // 4
You can apply some operators to pointers. Pointer arithmetic is limited to addition, subtraction, and
comparison. When performing arithmetic with pointers, it is assumed that the pointer points to an array of
objects. Thus, if a pointer is declared to point to type T
, adding an integral value to the pointer
advances the pointer by that number of objects of type T
. If type T
has size 10 bytes,
then adding an integer 5 to a pointer to type T
advances the pointer 50 bytes in memory. You can
apply increment and decrement operators and self-assignment operations to non-constant pointers.
int a[5] = {1, 2, 4, 8, 16}; int *p = a; // p points to a[0]; cout << *(a + 3) << endl; // 8 p++; // p points to a[1]; cout << p << endl; // 2 p += 3; // p points to a[4]; cout << p << endl; // 16 a++; // Error! a is a constant pointer
The difference has as its value the number of array items separating the two pointer values. For example,
int a[5] = {1, 2, 4, 8, 16}; int *p1 = a; // p1 points to a[0]; int *p2 = a + 3; // p2 points to a[3]; cout << p2 - p1 // 3;
The difference between two pointers has meaning only if both pointers point into the same array.
The compiler does not check the range of an allocated array. In the following example, p
points to
nonexistent item:
int a[5] = {1, 2, 4, 8, 16}; int *p3 = a + 5; // No such item
The dereference of p3
is very dangerous.
You can use pointers for putting an array-type argument into argument list by calling a function. The initial array can be modified inside of a function:
void modifyStartingItem(int *p) { p[0] = 0; } int main() { int a[] = {1, 2, 3}; modifyStartingItem(a); for (int i = 0; i < 3; i++) { cout << a[i] << ' '; // 0 2 3 } return 0; }
Using pointers, you can transfer multidimensional arrays as parameters. To ensure flexibility, you should
describe such parameters as pointers to data (one-dimensional array). When calling the function you need to
explicitly convert type. For example, the function below can be used to calculate the sum of all items of a
two-dimensional array of m
rows and n
columns. The implementation of the function
involves calculating the index, taking into account the location of a two-dimensional array items:
double sum(double a[], int m, int n) { double result = 0; for (int i = 0; i < m; i++) { for (int j = 0; j < n; j++) { result += a[i * n + j]; } } return result; } int main() { double arr[][3] = { { 1, 2, 3 }, { 4, 5, 6 } }; cout << sum((double*) arr, 2, 3) << endl; // explicit type conversion return 0; }
2.3 Using Free Store
One of most widespread applications of pointers is handling with free store.
When a program starts, operating system sets up various areas of memory:
- Global variables are in global name space.
- Registers are a special area of memory built right into the Central Processing Unit.
- The call stack is a special area of memory allocated for your program to hold the data required by each of the functions in program. It is called a stack because it is a last-in, first-out queue.
- The remainder of memory area apportioned for a program is called free store.
Programmers can use free store for controlled allocation and deallocation of variables. The new
operator is used for allocation variables in free store. The new
operation returns pointer
to an object allocated in free store.
int *p = new int; *p = 65; . . .// *p can be free used until deallocation
You can initialize variable on the spot:
int *p = new int(65); // *p = 65
Any useful variable some time or other becomes unavailing. Variable created in free store must be deallocated
using delete
operator:
delete p;
After deallocation, the memory previously used for a variable *p
can be used for allocation of
other variables.
You can use square brackets after type name for allocation of an array of given type. You can use any
expressions or variables reducible to int
to determine size of an array. After creation, the
use of dynamically allocated arrays and conventional arrays cannot be distinguished.
int n; cin >> n; double *pa = new double[n]; for (int i = 0; i < n; i++) { pa[i] = 0; } . . .
The dynamically allocated array can be deallocated in such way:
delete [] pa;
You must ensure that all memory allocated by the new
operator is freed by the
delete
operator.
If we create an array of pointers in free store, and then create several one-dimensional arrays and write their addresses into array of pointers, we get an array of arrays that can be worked with as a normal two-dimensional array:
cin >> m >> n; double** a = new double*[m]; for (int i = 0; i < m; i++) { a[i] = new double[n]; } for (int i = 0; i < m; i++) { for (int j = 0; j < n; j++) { a[i][j] << i + j; } }
It is very important to free memory in the right order:
for (int i = 0; i < m; i++) { delete[] a[i]; } delete[] a;
Similarly, you can create arrays with more dimensions.
The disadvantage of multidimensional arrays in dynamic memory is a certain decrease in efficiency at runtime, but there are significant advantages:
- You can obtain (calculate) the required number of rows and columns during program execution.
- You can create arrays with different row lengths, for example, you can create an array to store a triangular matrix.
- You can create functions for working with two-dimensional arrays.
The latter advantage can be explained by the following example.
The fill()
function writes the specified value to all array items:
#include <iostream> using namespace std; void fill(double **arr, int m, int n, double value) { for (int i = 0; i < m; i++) { for (int j = 0; j < n; j++) { arr[i][j] = value; } } } int main() { int m, n; cin >> m >> n; double** a = new double*[m]; for (int i = 0; i < m; i++) { a[i] = new double[n]; } fill(a, m, n, 10); for (int i = 0; i < m; i++) { for (int j = 0; j < n; j++) { cout << a[i][j] << " "; } cout << endl; } for (int i = 0; i < m; i++) { delete[] a[i]; } delete[] a; }
Keep in mind that this function can only be used for two-dimensional arrays in free store. The last line of code will cause the compiler to fail:
double b[3][4]; fill(b, 3, 4, 10); // compiler error
2.4 Character Arrays
In C++, a string is an array of chars ending with a null character (a character with code 0). You can declare and initialize a string just as you would any other array. For example,
char name[] = {'A', 'n', 'd', 'r', 'e', 'w', '\0'};
The last character, '\0'
, is the null character, which many C++ functions recognize as the terminator
for a string. C++ allows an initialization of character array with string literal:
char name[] = "Andrew";
You don't need to add the null character manually because the compiler adds it for you. Thus, this array has seven items.
C++ allows you to read character array without cycle. It is important to ensure that there is enough memory for allocation of characters:
char s[30]; cin >> s;
Pointers to characters can be initialized with string literals:
char *st = "C++";
The compiler allocates memory for holding four characters (including '\0'
) and puts an address of
starting character into st
.
The Standard C library offers a set of functions those handle with null-terminated strings. For example,
// Returns a length of the string (without '\0'): int strlen(const char *s); // Compares two strings and returns a negative value // if s1 less than s2, zero if they are equal, // and a positive value otherwise: int strcmp(const char *s1, const char *s2); // copies s2 into s1: strcpy(char *s1, const char *s2);
The strcmp()
function implements alphabetical comparison of stings. The use of those functions requires
inclusion of <cstring>
header file. The function declarations in this file are located in the std
namespace.
2.5 Use of C-Style Output and Input
The C++ programming language allows you to use output and input functions derived from C language. To access these
functions you should add the inclusion of stdio.h
header file. The required functions are declared
in the global scope.
Note. A more modern version of the header file is cstdio
. The function declarations in this
file are located in the std
namespace.
The printf()
function can be used for console output. It can be invoked with one or more arguments.
The string constant can be shown by sending it as the single argument:
printf("Hello"); // the same as cout << "Hello";
If you want to print numbers, you should put into the contents of the first argument (constant string) the so called format characters. The format sequence starts with % character followed by symbols count (size) followed by format character. Sometimes you don't need to set size. For example:
int k = 12; printf("%i", k);
The most important format characters are:
Character | Conversion |
---|---|
%d or %i |
int |
%c |
single character |
%e or %E |
float or
double in
the format [-]d.ddd e±dd or [-]d.ddd E ±dd |
%f |
float or
double in
the format [-]ddd.ddd |
%p |
the address (pointer) |
%s |
output string |
%u |
unsigned int |
%x or %X |
int as a hexadecimal number |
%% |
percentage character |
The minus character before format sequence forces left alignment of data.
The scanf()
function allows reading from the standard input stream. The first argument is format string.
It allows setting delimiters. Other arguments are pointers to variables, which values should be read. The function
returns count of bytes that were read. Use lf
instead of f
for reading doubles. For example:
int n; float x; double y; scanf("%d, %f %lf", &n, &x, &y); printf("%10d ", n); printf("%10.5f \n", x); printf("%-10.5f", y);
When you enter the first two numbers, they should be divided by a comma.
Unlike C++, C does not support cin
and cout
streams. Therefore, the simplest program
in C will differ from the previous one:
#include <stdio.h> int main() { printf("Hello, world!\n"); return 0; }
Note: the inclusion of cstdio
header file is not acceptable for C, which does not support
namespaces.
The disadvantage of C functions compared to C++ streams is that these functions are type unsafe. This means that you can get numerous runtime errors concerned with inappropriate types of variables. Compiler cannot check accordance at compile time.
The scanf()
function is more dangerous. Improper use of this function may cause the program to hang.
Therefore, in modern versions of Visual Studio, attempting to compile a program that contains scanf()
calls
causes a compiler error "'scanf': This function or variable may be unsafe". In order to compile
such a program, you should disable the appropriate control by adding a preprocessor directive #define _CRT_SECURE_NO_WARNINGS
.
The following program reads an integer value, increments it by one, and outputs the result to the console:
#define _CRT_SECURE_NO_WARNINGS #include <stdio.h> int main() { int k; scanf("%d", &k); k++; printf("%d", k); return 0; }
There are also getchar()
and putchar()
functions for reading and output of individual
characters, respectively.
2.6 Working with Files
File streams allow input and output based on text files. File streams provide an advantage in that you can open a file on construction of a stream, and the file will be closed automatically on destruction of the stream (because of call so called destructor).
File stream classes std::ifstream
and std::ofstream
are declared in fstream
header
file. A file stream needs to be connected to a file before it can be used. In the following example, program reads
integer value from a file "data.txt"
into variable k
. This value will be written
into another file:
#include <fstream> . . . int k; std::ifstream inFile("data.txt"); inFile >> k; std::ofstream outFile("result.txt"); outFile << k; . . .
If you need to check the readiness of the stream to read data from a file you can test the result of eof()
member
function. You can also convert stream objects into integers and therefore test the stream state. The second approach
is more efficient.
Another approach is the usage of pointer to file (FILE *
) and fscanf
/ fprintf
pair
of functions. This approach is type unsafe.
3 Sample Programs
3.1 Maximum Item
Suppose we need to create a program that reads from the text file the number of array items, as well as
the items themselves. The array of floating point values is created in a free store. Next we find maximal item
displayed if using printf()
function.
First we need to create a text file (in.txt
) in the folder of the project, e. g. with such content:
5 0.1 1.6 0.2 0.8 0.4
In this file, the first line is the number of array items and the second line contains array items.
In the program, we'll use pointers and address arithmetic instead of indices. The program will be as follows:
#include <iostream> #include <fstream> #include <cstdio> using std::ifstream; using std::printf; int main() { ifstream in("in.txt"); // open the file int n; in >> n; // read from the file the number of array items double* a = new double[n]; for (double* p = a; p != a + n; p++) { in >> *p; // read items } double* max = a; // max points first to the item with index 0 for (double* p = a + 1; p != a + n; p++) { if (*p > *max) { max = p; // change the value of the pointer } } printf("max item: %5.2f", *max); delete[] a; return 0; }
3.2 Sum of Products
In the program below, a two-dimensional array of real numbers is filled with random values in the range from 0
to 3. Then the sum of the products of array rows is found. The user enters the count of rows (m
) and columns (n
)
at runtime. The scanf()
function is used for this purpose.
Since the standard cstdlib
header file does not provide function for generating
floating point pseudo-random numbers, it can be created manually.
#define _CRT_SECURE_NO_WARNINGS #include <cstdlib> #include <cstdio> #include <ctime> using std::srand; using std::time; using std::rand; using std::scanf; using std::printf; // The function returns a pseudo-random value in the range // from 0 (including) to 1 (not including). // RAND_MAX is an integer constant, maximum value // that returns rand() double doubleRand() { // Add 1.0 to include 0 and not include 1: return rand() / (RAND_MAX + 1.0); } int main() { srand(time(0)); int m, n; // Data input and checking the number of bytes read: if ((scanf("%d %d", &m, &n)) < 2) { return -1; } // Creating and filling the array: double** a = new double*[m]; for (int i = 0; i < m; i++) { a[i] = new double[n]; for (int j = 0; j < n; j++) { a[i][j] = doubleRand() * 3; } } // Linewise output of array items: for (int i = 0; i < m; i++) { for (int j = 0; j < n; j++) { printf("a[%d][%d] = %f\t", i, j, a[i][j]); } printf("\n"); } // Calculating the sum of products: double sum = 0; for (int i = 0; i < m; i++) { double product = 1; for (int j = 0; j < n; j++) { product *= a[i][j]; } sum += product; } printf("sum: %f", sum); // Deallocation: for (int i = 0; i < m; i++) { delete[] a[i]; } delete[] a; return 0; }
3.3 Search for Negative Items
It is necessary to create an array of doubles. The program must calculate number of negative items and create an array of a corresponding size. The negative values must be placed into a new array.
A code of a main()
function can be as follows:
const int n = 7; double a[n] = { 10, 11, -3, -2, 0, -1, 4 }; int count = 0; int i; for (i = 0; i < n; i++) { if (a[i] < 0) { count++; } } double *b = new double [count]; // b will contain negative items int j = 0; // index in b for (i = 0; i < n; i++) { if (a[i] < 0) { b[j] = a[i]; j++; // the next index } } for (j = 0; j < count; j++) { cout << b[j] << ' '; // output } delete [] b; // deallocation
3.4 Deleting Digits from the String
The following program reads string from the keyboard until the user presses Enter. All characters except the numbers are copied into a new string.
#include <iostream> #include <cstring> using namespace std; int main() { char str[80], result[80]; cin.getline(str, 80); int i, k; // Need to copy strlen(str) + 1 character, including '\0': for (i = 0, k = 0; i <= strlen(str); i++) { if (str[i] < '0' || str[i] > '9') { result[k] = str[i]; k++; } } cout << result; return 0; }
As can be seen from the program code, all characters whose codes are not in the range of decimal digits are put into a new string. In the code table, these codes are arranged sequentially.
The getline()
function of the cin
object is used to read the string to the end. You can
also use the operator >>
:
cin >> str;
In this case, string will not be read to the end, but to the first delimiter (space character).
4 Exercises
- Write a program that calculates sum of positive items of an array of doubles. Use pointers and address arithmetic instead of indices.
- Read the string, replace the spaces with underscores and display the result.
- Write a program that reads integers until zero value and calculates product of integers without last zero value.
- Write a program that reads integers until end of file and calculates product of non-zero values.
- Write a program that defines an array of doubles and writes into text file sum of elements with odd indices.
5 Quiz
- What are pointers?
- How to get an address of a variable?
- What is dereferencing operator?
- For what purposes pointers are used?
- What is the difference between constant pointer and pointer to constant?
- How to get an address of an array?
- What is the relationship between arrays and pointers?
- What is address arithmetic?
- What is the result of the "minus" operator applied to two pointers?
- What areas of computer memory are used for variables allocation?
- What is free store?
- How to allocate variable in free store?
- Why do you need to delete unnecessary variables in free store?
- How to allocate two-dimensional array in free store?
- What is the specific of character arrays?
- What is null-terminated string?
- What C functions can be used for output and input?
- What are the disadvantages of using
printf()
andscanf()
? - Why explicit closing of files in C++ is optional?
- How to check for the end of the file?