Laboratory Training 6

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;
  • the array is created in the 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 recorded in a text file;
  • data must be deallocated from the free store.

C++ tools should be used for input and output, as well as working with files.

1.2 Multiplication of Matrices

Implement a program in which the user enters the sizes of the matrices, and if the sizes of the matrices do not match, the program issues an error message. Otherwise, the matrices are filled with pseudorandom numbers, after which the product of the matrices is found and the result is displayed on the screen. To represent matrices, create two-dimensional arrays (arrays of pointers to one-dimensional arrays) in the free store. Separate actions (matrix filling, multiplication, output) are implemented in separate functions.

1.3 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.4 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.

Individual actions can be implemented in different functions. Do not use global variables.

In parallel with the output to the file, you should provide output to the console window. When working with a one-dimensional array, use an address arithmetic instead of working with the index is required.

1.5 Using C Language Tools (Additional Task)

Create a C application in which to implement task 1.1 above.

2 Instructions

2.1 Definition of Pointers

2.1.1 Description 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. A constant NULL is defined specifically for working with pointers in the stddef.h header file. Starting with the C++11 version, a new nullptr keyword has been added, the use of which is more correct from the point of view of type compatibility. The nullptr keyword should be used instead of 0 or NULL, if we want to indicate that the pointer does not point to anything yet.

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 = 1;
int * const cp = &i;
cout << *cp;         // 1
int j = 2;
cp = &j;             // Error!

It is advisable to use a constant pointer in cases where it is desirable not to lose the address of an important object.

You can also create constant pointers to constant objects:

int i = 1;
const int * const cp = &i;

2.1.2 Pointers as Parameters and Result of Functions

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
}

You can also create functions to work with an array of pointers:

#include<iostream>
using namespace std;

double sum(double* arr[], int size)
{
    double result = 0;
    for (int i = 0; i < size; i++)
    {
        result += *arr[i];
    }
    return result;
}

int main()
{
    double x = 1;
    double y = 2;
    double z = 2.5;
    double* a[] = { &x, &y, &z };
    cout << sum(a, 3); // 5.5
    return 0;
}

You can return a pointer from a function. Restrictions are similar to returning references. You cannot return the addresses of automatic local variables, because the corresponding local variables are destroyed when the function terminates:

int* f()
{
    int k;
    return &k; // Error: k is destroyed after leaving the function
}

You can return the address of a static local variable or a global variable:

int* f()
{
    static int k;
    return &k; // OK
}

int main()
{
    *f() = 10;
    cout << *f(); // 10
    return 0;
}

2.1.3 The void* Pointer

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 operators:

double *pd = (double*) p[1];
cout << *pd << endl; // 1.5
char *pc = static_cast<char*>(p[2]);
cout << *pc << endl; // 'A'

Using arrays of void* pointers on the one hand allows you to create polymorphic data structures that allow performing actions on items regardless of the types of real data. On the other hand, there are no mechanisms for obtaining information about a specific pointer type at runtime. Types should be kept separate.

2.2 Arrays and Pointers

2.2.1 Overview

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

The subscript operator can always be used instead of dereferencing, even if we are not talking about arrays:

int i = 5;
int *p = &i;
p[0] = 6;
cout << i; // 6

The use of dereferencing in such a context is preferable.

2.2.2 Address Arithmetic

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
p--;                      // p points to a[3]
cout << *p << endl;       // 8
cout << *(p - 2) << endl; // 2
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.

2.2.3 Loops using Pointers

Instead of loops built on the use of indexes, you can use a pointer as a parameter. This can slightly increase the efficiency of the program.

In the example below, we traverse the array for filling and output using address arithmetic:

#include <iostream>

int main()
{
    const int n = 4;
    double a[n] = { };
    double item = 1; 
    for (double* p = a; p < a + n; p++)
    {
        *p = item;
        item *= 2;
    }
    for (const double* p = a; p < a + n; p++)
    {
        std::cout << *p << " "; // 1 2 4 8
    }  
}

The const modifier before the pointer description is optional, but recommended in cases where the dereferenced array item is read-only.

Another example demonstrates traversing array items in reverse order:

#include <iostream>

int main()
{
    double a[] = { 1, 2, 4, 8 };
    int n = sizeof(a) / sizeof(double);
    double sum = 0;
    for (const double *p = a + n - 1; p >= a; p--)
    {
        std::cout << *p << " ";
    }
}

This is how you can find the sum of items with even indices:

#include <iostream>

int main()
{
    const int n = 4;
    double a[] = { 1, 2, 4, 8};
    double sum = 0;
    for (double *p = a; p < a + n; p += 2)
    {
        sum += *p;
    }
    std::cout << sum; // 5
}
      

2.2.3 Using Pointers to Pass Arrays to Functions

You can use pointers for putting an array-type argument into argument list by calling a function. Pointers are usually used instead of arrays when describing parameters. Example:

#include <iostream>

double sum(double *arr, int n)
{
    double result = 0;
    for (int i = 0; i < n; i++)
    {
        result += arr[i];
    }
    return result;
}

int main()
{
    double a[] = { 1, 2, 4, 8, 16 };
    double y = sum(a, 5);
    std::cout << sum(a, 5) << std::endl; // 31
    std::cout << sum(a, 4) << std::endl; // 15
    return 0;
}

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;
}

In order not to accidentally change the value of the array items, array can be passed through a pointer to a constant object:

void f(const int* p)
{
    p[0] = 0; // Compile error
    ...
}

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 = 64;
. . .// *p can be free used until deallocation

You can initialize variable on the spot:

int *p = new int(64); // *p = 64

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.

Arrays created in dynamic memory cannot support range-based loops.

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 Using C Language Tools

2.5.1 Overview

The C language was created in the early seventies of the twentieth century. The year of release of the language is considered to be 1972. The C language was directly used to create the UNIX operating system. This operating system became the prototype of all modern operating systems that support POSIX standards, in particular, all versions of the Linux OS. The language was created by Dennis Ritchie at Bell Telephone Laboratories (Bell Labs). Later the language was standardized. The latest official standard is C11.

The C language does not support object-oriented tools, namespaces, lambda expressions, templates, and exceptions. At the level of imperative and procedural programming, there are significant limitations associated with the lack of a mechanism for overloading function names, default parameters, inline functions, range-based loops, and references.

The C language is less strongly typed compared to C++. For example, you can create functions with a variable number of parameters (varargs), the types of which the compiler is unable to check. Using macros is also not type-safe.

In order to create a program in the C language in the Visual Studio environment, after creating a console application, you should delete the file with the main() function and .cpp extension from the project and then manually add the file (Add | New Item... | C++ File) with the .c extension.

2.5.2 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. When creating a program in C++, it is advisable to use a more modern version of the header file: 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.5.3 Using Macros

Both languages (C and C++) support preprocessing of raw code. Previously, in all programs, we used the #include directive. The #define directive is also relatively often used, thanks to which you can define a string constant. An example of such a constant is _CRT_SECURE_NO_WARNINGS . This constant was defined in the previous example. In order to make it easier to find such constants, capital letters are usually used for them.

You can remove certain parts of the source text using the #define, #ifdef and #ifndef directives. A #define directive can be used to define a new preprocessor constant anywhere. Elsewhere, this fact can be checked using the #ifdef or #ifndef directives. Example:

#define NEW_NAME
...
#ifdef NEW_NAME
// Include this code
#else
// Do not include this code
#endif

If a sequence of characters is placed after the constant in the #define definition, the preprocessor will replace the specified constant with this sequence. For example, if you can replace the word BEGIN with an opening brace, and the word END with a closing brace, you can create code that looks a bit like Pascal code:

#include <stdio.h>
#define BEGIN {
#define END }

int main(void)

BEGIN
    printf("Hello, Pascal!");
    return 0;   
END

Of course, such directives do not make sense in real programming.

In the first versions of the C language, it was not possible to create constants using the const keyword. Constants were created using the #define directive. So, for example, you can define the π constant for value of π:

#include <stdio.h>
#define PI 3.14159265

int main(void)
{
    double x = PI;
    printf("%f", x);
    return 0;
}

The disadvantage of such constants is the lack of type control.

There are no inline functions in C. Preprocessor constants with parentheses can be used instead. Example

#include <stdio.h>
#define MIN(A, B) A < B ? A : B

int main(void)
{
    int i = 3, k = 4;
    printf("%d\n", MIN(i, k)); // 3
    double x = 4.5, y = 3.5;
    printf("%f\n", MIN(x, y)); // 3.500000
    return 0;
}

Using macros can cause some problems. In addition to the lack of type checking, mechanical replacing a string with an expression can lead to unexpected consequences. For example, you can create a macro for finding the second power and apply it in the program:

#include <stdio.h>
#define SQUARE(X) X * X

int main(void)
{
    int i = 3;
    printf("%d\n", SQUARE(i)); // 9
    printf("%f\n", SQUARE(1.5)); // 2.250000
    return 0;
}

For individual variables and constants, everything works correctly. But, if you call a function whose parameters are not individual variables, but expressions, the result can be unexpected:

#include <stdio.h>
#define SQUARE(X) X * X

int main(void)
{
    int x = 3;
    printf("%d\n", SQUARE(x + 2)); // 11
    return 0;
}

This result is due to the fact that instead of an argument, a substitution is made without parentheses: 3 + 2 * 3 + 2. In order for the macro to work correctly, all parameters should be enclosed in parentheses:

#include <stdio.h>
#define SQUARE(X) (X) * (X)

int main(void)
{
    int x = 3;
    printf("%d\n", SQUARE(x + 2)); // 25
    return 0;
}

2.5.4 Working with Free Store using C Language Tools

The basic work with pointers in C is similar to C++. The most significant differences concern the use of free store. C has no new and delete operators. Instead of these operators, the functions malloc() and free() are used.

For example, this is how you can create an array of integers in dynamic store, perform certain actions on the array, and free the memory:

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    int n;
    int* p;
    scanf("%d", &n);
    p = (int*)malloc(n * sizeof(int));
    for (int i = 0; i < n; i++)
    {
        p[i] = i + 1;
    }
    for (int i = 0; i < n; i++)
    {
        printf("%d\t", p[i]);
    }
    free(p);
    return 0;
}

Similarly, you can create a two-dimensional array (array of arrays). The memory should also be freed line by line:

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    int m, n;
    int** arr;
    printf("Enter m and n\n");
    scanf("%d", &m);
    scanf("%d", &n);
    arr = (int**)malloc(m * sizeof(int*));
    for (int i = 0; i < m; i++)
        arr[i] = (int*)malloc(n * sizeof(int));
    for (int i = 0; i < m; i++)
        for (int j = 0; j < n; j++)
            arr[i][j] = i + j;
    for (int i = 0; i < m; i++)
    {
        for (int j = 0; j < n; j++)
            printf("%d\t", arr[i][j]);
        printf("\n");
    }
    for (int i = 0; i < m; i++)
        free(arr[i]);
    free(arr);
    return 0;
}

Passing arrays to functions via pointers is similar to C++.

2.6 Working with Text 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 <iostream>
#include <fstream>

int main()
{
    int k;
    std::ifstream inFile("data.txt");
    inFile >> k;
    std::ofstream outFile("result.txt");
    outFile << k;
    return 0;
}

The text file data.txt with the value k must be created in advance. If the project is created in Visual Studio, the file should be created in the project folder. The file result.txt will also be written there.

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. In the following example, real numbers are read from a file (up to the end of the file) and the sum of these numbers is found. If the integer value that the stream variable can be cast to is zero, it means that there is no more data in the file. The example 3.6 shows the usage of this approach.

You can pass streams to functions only by reference.

Another approach is the usage of pointer to file (FILE *) and fscanf / fprintf pair of functions. The fgets() function allows you to read string from file. The previous example can be implemented using C language functions:

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>

int main()  
{
    FILE *f_in, *f_out;
    f_in = fopen("data.txt", "r");
    int k;
    fscanf(f_in, "%d", &k);
    fclose(f_in);
    f_out = fopen("result.txt", "w");
    fprintf(f_out, "%d", k);
    fclose(f_out);
    return 0;
}

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 Multiplication of Matrices

In the previous lab, the program for finding the sum of matrices was considered. In the new version, we will work with arrays in dynamic memory (arrays of pointers). The user must enter the sizes of the matrices. The source matrices, as in the previous version, will be filled with pseudorandom numbers. Individual actions will be performed in different functions. The program will be as follows:

#include <cstdio>
#include <iostream>
using namespace std;

// Returns a random value between 0 and 100
double nextRandom()
{
    return (rand() % 10000) / 100.;
}

// Reads m and n from the keyboard
// with validity check
bool readData(int& m, int& n)
{
    cout << "Enter m and n: ";
    cin >> m >> n;
    if (m <= 0 || n <= 0)
    {
        cout << "Wrong values";
        return false;
    }
    return true;
}

// Creates a two-dimensional array of the required dimensions
// and returns a pointer to it
double** createMatrix(int m, int n)
{
    double** matrix = new double* [m];
    for (int i = 0; i < m; i++)
    {
        matrix[i] = new double[n];
    }
    return matrix;
}

// Fills the array with random values
void fillMatrix(double** matrix, int m, int n)
{
    for (int i = 0; i < m; i++)
    {
        for (int j = 0; j < n; j++)
        {
            matrix[i][j] = nextRandom();
        }
    }
}

// Displays the array items on the screen
void showMatrix(double** matrix, int m, int n)
{
    for (int i = 0; i < m; i++)
    {
        for (int j = 0; j < n; j++)
        {
            cout << matrix[i][j] << "\t";
        }
        cout << endl;
    }
    cout << endl;
}

// Finds and returns the sum of matrices
double** sumOfMatrices(double** a, double** b, int m, int n)
{
    double** c = createMatrix(m, n);
    for (int i = 0; i < m; i++)
    {
        for (int j = 0; j < n; j++)
        {
            c[i][j] = a[i][j] + b[i][j];
        }
    }
    return c;
}

// Removes the array from free store
void deleteMatrix(double** matrix, int m)
{
    for (int i = 0; i < m; i++)
    {
        delete[] matrix[i];
    }
    delete[] matrix;
}

int main()
{
    int m, n;
    if (readData(m, n))
    {
        double **a = createMatrix(m, n);
        fillMatrix(a, m, n);
        showMatrix(a, m, n);
        double** b = createMatrix(m, n);
        fillMatrix(b, m, n);
        showMatrix(b, m, n);
        double **c = sumOfMatrices(a, b, m, n);
        showMatrix(c, m, n);
        deleteMatrix(a, m);
        deleteMatrix(b, m);
        deleteMatrix(c, m);
        return 0;
    }
    return 1;
}

As you can see from the code, range-bsed loops cannot be used.

3.3 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.4 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.5 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).

3.6 Sum

Assume that we prepared a text file called "Numbers.txt" that contains integer values separated by spaces, tabs, or new line characters. For example:

1 22 -3 11	-9 144 
1000
1 1 1 -3 5	4
3 55	2

The length of the file is not limited. We must write a program that reads integer values until the end of file occurred and calculates a sum of these integers.

The file with source data must be placed into project's working directory. The source text of a program can be as follows:

#include <iostream>
#include <fstream>

int main()
{
    std::ifstream in("Numbers.txt");
    int x;
    int sum = 0;
    while (in >> x) // successful reading
    {
        sum += x;
    }
    std::cout << sum;
    return 0;
}

4 Exercises

  1. Write a program that calculates sum of positive items of an array of doubles. Use pointers and address arithmetic instead of indices.
  2. Read the string, replace the spaces with underscores and display the result.
  3. Write a program that reads integers until zero value and calculates product of integers without last zero value.
  4. Write a program that reads integers until end of file and calculates product of non-zero values.
  5. Write a program that defines an array of doubles and writes into text file sum of items with odd indices.

5 Quiz

  1. What are pointers?
  2. How to get an address of a variable?
  3. What is dereferencing operator?
  4. For what purposes pointers are used?
  5. What is the difference between constant pointer and pointer to constant?
  6. How to get an address of an array?
  7. What is the relationship between arrays and pointers?
  8. What is address arithmetic?
  9. What is the result of the "minus" operator applied to two pointers?
  10. What areas of computer memory are used for variables allocation?
  11. What is free store?
  12. How to allocate variable in free store?
  13. Why do you need to delete unnecessary variables in free store?
  14. How to allocate two-dimensional array in free store?
  15. What is the specific of character arrays?
  16. What is null-terminated string?
  17. What C functions can be used for output and input?
  18. What are the disadvantages of using printf() and scanf()?
  19. Why explicit closing of files in C++ is optional?
  20. How to check for the end of the file?

 

up