Урок 7. Основы работы с указателями

Теория

1. Основы работы с указателями

  • Указатели — это способ указать местоположение переменной. Вместо хранения значения (5 или символ ‘c’), значением указателя является адрес другой переменной.
  • Переменные-указатели имеют размер (количество байт в памяти), имя и значение. Они должны быть объявлены и инициализированы.
  • «Указатель» (сам по себе) — это не тип. Это конструктор типов — языковая конструкция, которая при применении к другому типу дает нам новый тип. В частности, добавление звездочки ( * ) после любого типа задает название типу, который является указателем на этот тип.
  • /*
    объявляет переменную с именем my_char_pointer 
    с указателем типа на char 
    (переменная, указывающая на символ)
    */
    char * my_char_pointer;
    Объявление указателя
  • Объявление указателя сообщает нам имя переменной и тип переменной, на которую будет указывать этот указатель.
  • Присваивание указателю
  • Можно выполнять присваивание указателям, изменяя их значения. Изменение значения указателя означает изменение того, куда он указывает.
  • Прежде чем использовать указатель, необходимо инициализировать его (дать ему что-то, на что можно указывать).
  • Если мы не инициализируем указатель перед его использованием, то он будет указывать на какое-то случайное место в программе (это может привести к сбою программы).
  • Чтобы получить указатель, указывающий на некоторый адрес, нужно назначить имя полю памяти, а затем нам нужно использовать оператор &.
  • /* код, который объявляет целое число x и указатель на целое число xPtr */
    int x = 5;
    int *xPtr;
    xPtr = &x; // устанавливает значение переменной xPtr на адрес x
  • x инициализируется значением 5 в той же строке, в которой оно объявлено;
  • значение xptr инициализируется местоположением x;
  • после того, как он инициализирован, xPtr указывает на переменную x.
  • Код &x = 5; не будет скомпилирован. Программист может получить доступ к местоположению переменной, но изменить местоположение переменной невозможно.
  • Разыменовывание указателя

  • Как только у нас появятся указатель на адреса переменных, мы хотим использовать их, «следуя за стрелкой» и изменить ячейку, на которую ссылается указатель.
  • Символ звездочка * — унарный оператор, который разыменовывает указатель.
  • Обратите внимание, что зеленая стрелка указывает на то, что эта строка еще не была извлечена, следовательно, x все еще имеет значение 5 в концептуальном представлении. Однако, как только строка 7 будет выполнена, значение будет равно 6.
  • 6
    7
    
    // Пример использования оператора разыменования:
    *xPtr = 6; // изменяет значение, которое хptr указывает на 6
  • Обратите внимание, что зеленая стрелка указывает на то, что эта строка еще не была выполнена, следовательно, x все еще имеет значение 5 в концептуальном представлении. Однако, как только строка 7 будет выполнена, значение будет равно 6.
  • Два контекста в которых используется (*), не путайте их:
    1. В объявлении переменной, такой как int *p;, звездочка является частью имени типа и сообщает нам, что нам нужен указатель на какой-либо другой тип (в нашем примере int * — это тип p).
    2. Когда звездочка является оператором разыменования. Например, код r = *p; присваивает переменной r новое значение, а именно значение внутри ячейки памяти, на которую указывает p. Код *p = r; изменяет значение внутри ячейки памяти, на которую указывает p, на новое значение, а именно на значение переменной r.

  • При работе с указателями, вы будете использовать звездочку сначала для объявления переменной, а затем для ее разыменования. Только переменные, имеющие тип указателя, могут быть разыменованы.
  • int * q = &y; // совпадает с двумя утверждениями:
    int *q; 
    q = &y;
    Выводы
  • Существует только три основных действия с указателем: объявление, присваивание (включая инициализацию) и разыменование.

обмен значениями

Выполнение: Необходимо передавать указатели a и b через аргументы функции, как показано ниже:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <iostream>
using namespace std;
 
void swap(int *x, int *y) {
	int temp = *x; // temp временно сохраняет значение, на которое указывает x, =3
	*x = *y; // берем значение, на которое указывает y, которое является значением b (=4)
                  //и сохраняем это в значении, на которое указывает x, а именно в переменной a
	*y = temp; // берем значение temp = 3 и сохраняем его в виде целого числа, 
//на которое указывает y, т.е. в переменной b
}
 
int main() {
	int a = 3;
	int b = 4;
 
	swap(&a, &b);
	cout<<"a = "<< a<< ", b = "<<  b << endl;
	system("pause");
	return 0;
}
— функция swap принимает два аргумента, x и y, которые являются указателями на целые числа;
— поскольку мы получаем доступ к значениям целых чисел, на которые указывают x и y в коде внутри swap, нам придется отменить ссылки на x и y. Итак, вот почему мы будем видеть эти звездочки (*) перед x и y по всему коду;
— внутри main, вместо передачи в a и b, мы теперь должны передать в адрес a и адрес b.

Работа с памятью

  • На 32-разрядной машине, где размер адресов составляет 32 бита, все пространство памяти начинается с адреса 0x000000000 (в шестнадцатеричном формате каждый 0 представляет 4 бита 0) и заканчивается на 0xFFFFFFFF (напомним, что 0x обозначает шестнадцатеричный, и что каждое F представляет четыре двоичных 1). Каждая программа имеет в своем распоряжении все адресное пространство, и существует соглашение о том, как программа использует этот диапазон адресов.
  • Давайте посмотрим на рисунок, чтобы понять, где в памяти хранятся различные компоненты программы.

    Null
  • Если используется значение NULL — указатель ни на что не указывает.
  • Нулевой указатель имеет особый тип — void *. Указатель void указывает на на любой тип — мы можем присвоить ему значение int *, double* или любой другой тип указателя, который мы хотим.

2. Указатели и массив

  • Массив представляет собой константный указатель на свой первый элемент.
  • Доступ к элементу массива с помощью арифметики указателей работает нормально и иногда является естественным способом доступа к элементам. Но в случае, когда нам нужен просто n-й элемент массива, было бы громоздко объявлять указатель, добавлять к нему, а затем разыменовывать его.
  • Пример:
  • // * array : имя массива является указателем на его первый элемент, то есть на 3
    void printArr(int *array, int n) { 
    	int *ptr = array; // *ptr - это указатель, который указывает на то, куда указывает *array 
    	for (int i = 0; i < n; i++) {
    		cout << *ptr; // вывод 1-й итерации - 3, вывод 2-й итерации - 1, 
                    // вывод 3-й итерации - 7, 
                     //чтобы указатель указывал на ячейку памяти следующего элемента в массиве:
                    // пример адреса ptr = 0x00b5f9a0
    		ptr++;  // 1st 0x00b5f9a4, 2nd 0x00b5f9a8, 0x00b5f9ac, 
                            // в массиве нет элемента, но ошибки быть не может
                            // это вызвало бы проблему, если бы мы разыменовали указатель
    	}
    }
    int main(){
    	int arr[4] = { 3,1,7,2 };
    	printArr(arr, 4);
    }
  • Можно достичь этой цели, проиндексировав массив. Когда мы индексируем массив, мы записываем имя массива, за которым следуют квадратные скобки, содержащие номер элемента, на который мы хотим сослаться. Важно отметить, что в C++ индексы массивов начинаются с нуля — первым элементом массива является myArray[0].
  • // * array : имя массива является указателем на его первый элемент, то есть на 3
    void printArr(int *array, int n) { 
         for (int i = 0; i < n; i++) {
    	 cout << array[i]; // 1-я итерация выводит 3, 2-я итерация - 1, 3-я итерация - 7, а затем 2
         }
    }
    int main(){
    	int arr[4] = { 3,1,7,2 };
    	printArr(arr, 4);
    }
    Поставим указатель p на начало массива:
    int a[] {1,3,5,7,9};
    int* p = a;
    // переместите указатель на второй элемент:
    p++;
    p++;
    Доступ по индексу
    int a[] {1,3,5,7,9};
    int* p = a;
    cout << *(p + 1) << endl; // = a[1]=3
    cout << *(p + 2) << endl; // = a[2]=5

Символьная строка и указатели

  • Символьная строка -это строка (массив символов) с нулевым символом в конце (\0).
  • char s[6] = "Hello"; // в конце автоматически добавляется \0 
    char s[] = "Hello"; // тот же самый результат
    char* p = s; // H

    Для перебора символьной строки:

    char* p = s;
    while (*p != 0)
      cout << *p++;

    или

    char* p = s;
    while (*p)
      cout << *p++;

    Лабораторные работы

    Все задания, которые вы не успели выполнить на уроке, автоматически становятся вашим домашним заданием. Домашнее задание должно быть выполнено до следующего урока.

    1. Основы работы с указателями

    Лабораторная работа 1:
    Выполнить: Создайте функцию для вычисления суммы значения указателя и переменной целочисленного типа. То есть функция должна возвращать сумму значения, на которое указывает указатель, и значения переменной.

    Указание: Объявление функции должно быть таким:

    int addP(int var1, int var2);

    Примерный вывод:

    введите первое число (указатель):
    >>>2
    введите второе число (которое прибавить):
    >>>500
    результат: указатель + 500 = 502
    

    ✍ Алгоритм:

    1. Создайте проект Lesson7. Добавьте в соответствующие папки проекта файлы: main7lab1.cpp, L7lab1.cpp, L7lab1.h.
    2. Откройте main7.cpp и подключите все необходимые директивы и файлы. Выведите сообщение пользователю для ввода двух значений. Первое значение — указатель, второе — для добавления:
    3. cout << "Lab 1:" << endl;
      cout << "пожалуйста, введите первое число (для указателя): "<< endl;
      int var1; 
      cin >> var1;
      cout << "пожалуйста, введите второе число (для добавления): " << endl;
      int var2; 
      cin >> var2;
      
    4. Переключитесь на код файла string7.h и подключите необходимые файлы и директивы:
    5. #pragma once
      #ifndef STRING7_H
      #define STRING7_H
      
      #include 
      // здесь ваш код
      #endif
      
    6. Добавим объявление функции, как это было рекомендовано в тексте задания:
    7. //lab 0: Создайте функцию для вычисления суммы указателя и переменной целочисленного типа
      int addP(int var1, int var2);
      
    8. Теперь создадим реализацию функции. Откройте файл string7.cpp, подключите все необходимые директивы и добавьте код:
    9. //lab 1: для вычисления суммы значения указателя и переменной целочисленного типа
      int addP(int var1, int var2) {
      	int* p_var = &var1;
      	return *p_var + var2;
      }
      
      Здесь p_var инициализируется адресом переменной var1. Функция возвращает *p_var + var2 — что означает, что мы берем значение, на которое указывает p_var (то есть значение var1), и добавляем к нему значение var2.
    10. Теперь можно вызвать созданную нами функцию и вывести результаты. Это необходимо сделать в файле main7.cpp:
    11. cout << "Результат:" << endl;
      cout << "указатель + " << var2 << " = " << addP(var1,var2) << endl;
      
    12. Запустите программу и проверьте выходные данные.
    Задание 1:

    Выполнить: Создайте приложение с функцией, которая принимает два аргумента - целые числа (var1 и var2), и выполняет следующие вычисления со своими аргументами, используя при этом указатели на переменные (p1 и p2):

    1) произведение указателя и переменной: p1 * var1
    2) сумма указателей:  p1 + p2
    3) изменение значения var1 = 5, используя при этом указатель p1
    4) изменение адреса указателя: p1(address) = p2(address)
    

    Указание 1: Чтобы изменить значение переменной:

    int* p1 = &var1; // объявляем и инициализируем указатель p1 
    *p1 = 1; // 1 - это новое значение переменной

    Указание 2: Объявление функции должно быть следующим:

    void task1(int var1, int var2);

    Примерный вывод:

    Задание 1:
    Введите первое значение:
    >>5
    Введите второе значение:
    >>10
    произведение указателя и переменной: 25
    сумма указателей: 15
    результат смены значения var1, var1 = 5
    результат ДО смены адреса, p1 = 0098F7E8
    результат смены адреса, p1 = 0116FC90
    

    [Solution and Project name: Lesson_7task1, file name L7Task1main.cpp, L7Task1imp.cpp, L7Task1.h]

    2. Указатель на массив

    Лабораторная работа 2:
    Выполнить: Создайте функцию для вычисления суммы элементов массива. Задача должна быть выполнена с использованием двух функций. Первая функция не использует указатель, вторая функция должна использовать указатель, обращающийся к элементу массива по индексу.

    Указание 1: Объявление функции:

    int sumArray1(int * array, int n);
    int sumArray2(int * array, int n);

    Примерный вывод:

    array:
    1 2 3 4 5
    результат выполнения первой функции: sum = 15
    результат выполнения второй функции: sum = 15
    

    ✍ Алгоритм:

    1. Сначала давайте создадим функцию, которая выполняет итерацию по элементам массива без использования указателя:
    2. int sumArray1(int * array, int n) // array указывает на значение 1
      {
      	// начинаем с суммы, равной 0
      	int AnswerSum = 0;
      	// count from 0 to n
      	for (int i = 0; i < n; i++) {
      		// добавляем в сумму array[i]
      		AnswerSum += array[i];
      
      	}
      	// возвращаем сумму в качестве результата
      	return AnswerSum;
      }
      
      int main() {
      	int data[] = { 1,2,3,4,5 };
      	cout << "array: " << endl;
      	for (auto x : data) {
      		cout << x << " ";
      	}
      	cout << endl;
      	int sum= sumArray1(data, 5);
      	cout << "результат с первой функцией: sum = " << sum<< endl;
      
    3. Рассмотрим работу с памятью:
    4. 1. Первая итерация цикла:
      2. Вторая итерация цикла:
      3.Третья итерация цикла:
      4.Последняя итерация цикла:
      Возвращаемся к функции main и область sumArray1 уничтожается:
    5. Теперь давайте создадим функцию, которая выполняет итерацию по элементам массива, используя указатель, обращающийся к элементу массива по индексу:
    6. int sumArray2(int * array, int n) // массив указывает на значение 1
      {
      	// начинаем с суммы, равной 0
      	int AnswerSum = 0;
      	//  счетчик цикла - это указатель, указывающий на адрес 1-го элемента массива
      	for (int *p = array; p < array + n; p++) {
      		// добавим в сумму ссылку на p
      		AnswerSum += *p;
      
      	}
      	// возвращаем сумму
      	return AnswerSum;
      }
      
    7. Вызовите функцию в функции main и запустите программу.
    Лабораторная работа 3, Массивы:
    Выполнить: Дан массив целых чисел (1, 2, 3, 4, 5, 6, 7, 8, 9). Необходимо каждый второй элемент массива установить в 0 (то есть четные по порядку элементы должны быть = 0). Необходимо использовать индексацию массива.

    Указание 1: Объявление функции должно быть следующим:

    void DelEveryEven(int* array, int n);

    Примерный вывод:

    Лабораторная работа 3, Массивы:
    Исходный массив:
    1 2 3 4 5 6 7 8 9
    Результат:
    1 0 3 0 5 0 7 0 9
    

    ✍ Алгоритм:

    1. Откройте приложение, которое вы создали для лабораторной 1, или, если вы ее не сделали, сделайте следующее: создайте пустое консольное приложение с именем Lesson7. Добавьте файлы: main7.cpp, string7.cpp, string7.h.
    2. Подключите все необходимые библиотеки и файлы (если вы не делали этого раньше).
    3. Откройте файл main7.cpp. Выведите сообщение для пользователя, объявите массив и инициализируйте его значениями:
    4. cout << "Лабораторная работа 3, Массивы:\n";
      int my_arr[] = { 1,2,3,4,5,6,7,8,9 };
      
    5. Распечатайте элементы исходного массива:
    6. cout << "Исходный массив: " << endl;
      for (auto x : my_arr) {
      	cout << x << " ";
      }
      
    7. После этого откройте файл string7.h и подключим все необходимые директивы (если это не было сделано ранее):
    8. #pragma once
      #ifndef STRING7_H
      #define STRING7_H
      
      #include <iostream>
      
      #endif
      
    9. Объявление функции должно быть таким, как рекомендовано в задании:
    10. // Лабораторная работа 3, Массивы: Дан массив (1, 2, 3, 4, 5, 6, 7, 8, 9). 
      // каждый второй элемент установите в 0
      void DelEveryEven(int* , int);
      
    11. Теперь необходимо написать саму функцию, т.е. ее реализацию. Откройте файл string7.cpp, подключите все необходимые директивы и добавьте код:
    12. //Лабораторная работа 3, массивы
      void DelEveryEven(int* array, int n) {
      	for (int* p = array + 1; p < array + n; p = p + 2) {
      		*p = 0;
      	}
      }
      
      Параметр *array инициализируется адресом массива.
      Здесь, в качестве счетчика цикла используется указатель на первый элемент массива+1.
      Рассмотрим строку int* p = array + 1;.

    13. array возвращает ячейку памяти первого элемента. Но нам необходимо установить в 0 второй элемент. Поэтому имеем array + 1.
    14. В строке *p = 0 мы разыменовываем указатель, и второй элемент устанавливаем = 0.
    15. Чтобы проделать те же манипуляции с четвертым элементом (четным), мы можем увеличить указатель на 2 (p = p + 2).
    16. array + n - ячейка памяти первого элемента + n, это 9. Затем происходит выход их цикла.
    17. Теперь можно вызвать функцию и вывести результаты. Это необходимо сделать вmain7.cpp:
    18. DelEveryEven(my_arr, 9);
      cout << "Результат: " << endl;
      for (auto x : my_arr) {
      	cout << x << " ";
      }
      cout << endl;
      
    19. Запустите программу и проверьте выходные данные.
    Задание 2:

    Выполнить: Задан массив целых чисел. Создайте две функции, которые возвращают значение максимального элемента массива. Первая функция не использует указатель, вторая функция должна использовать указатель, обращающийся к элементу массива по индексу.

    Указание: Объявления функций должны быть следующими:

    int maxArray1(int * array, int n);
    int maxArray2(int * array, int n);

    Примерный вывод:

    array:
    1 2 3 4 5
    результат для первой функции: max = 5
    результат для второй функции: max = 5
    

    [Solution and Project name: Lesson_7task2, file name L7Task2main.cpp, L7Task2imp.cpp, L7Task2.h]

    Задание 3:

    Выполнить: Задан массив положительных и отрицательных целых чисел. Создайте приложение с функцией, которая отрицательные элементы массива трансформирует в положительные значения.

    Указание: Объявление функции должно быть следующим:

    void SetNegToPos(int* array, int n);

    Примерный вывод:

    Task 2:
    исходный массив:
    1 -2 3 4 5 -6 7 8 -9
    результат:
    1 2 3 4 5 6 7 8 9
    

    [Solution and Project name: Lesson_7task3, file name L7Task3main.cpp, L7Task3imp.cpp, L7Task3.h]

    Задание 4:

    Выполнить: Задан массив целых чисел. Создайте функцию, которая возвращает индекс максимального элемента массива.

    Указание 1: Объявление функции должно быть следующим:

    int findLargestInd(int * array, int n);

    Указание 2: Внутри функции должно быть сравнение:

    if (array[i] > array[largestIndex]) 
    {
    ...
    }

    Примерный вывод:

    Задание 4:
    массив:
    1 5 3 2 1
    индекс максимального элемента = 1
    

    [Solution and Project name: Lesson_7task4, file name L7Task4main.cpp, L7Task4imp.cpp, L7Task4.h]

    3. Доступ к с-строкам указателем

    Лабораторная работа 4:
    Выполнить: Даны две строки. Создайте функцию, которая возвращает 1, если строки равны, и возвращает 0 в противном случае.

    Указание 1: Чтобы получить доступ к символам с-строки указателем, необходимо использовать тип char для этой строки:

    char str1[] = "Apple";

    Указание 2: Объявление функции должно быть следующим:

    int stringEqual(const char * str1, const char * str2);

    Примерный вывод:

    // lab 4:
    результат для 'apple' и 'apple' : 1
    ++++
    // lab 4:
    результат для 'apple' и 'apples': 0
    

    ✍ Алгоритм:

    1. Объявим функцию с двумя аргументами - константными с-строками:
    2. int stringEqual(const char * str1, const char * str2){
      ...
      }
      
    3. Затем объявим указатели, указывающие на первые символы строк str1 и str2. Добавьте код в функцию:
    4. const char* p1 = str1;
      const char* p2 = str2;
      
    5. После этого необходимо создать цикл, который будет повторяться до тех пор, пока совпадают символы, на которые указывают указатели p1 и p2. Следует использовать оператор разыменования для получения значений:
    6.  while (*p1 == *p2) {
      
      	}
      
    7. Кроме того, в цикле следует проверять, не достиг ли указатель p1 конца строки: \0 (означает конец строки). В этом случае функция должна возвращать 1 (то есть ответ "true"):
    8. if (*p1 == '\0') {
          return 1;
      }
      
    9. После этого, мы передвигаем p1 и p2 к следующей букве:
    10. p1++;
      p2++;
      
    11. После цикла необходимо вернуть значение "нет", что в нашей функции означает 0:
    12. return 0;
      
    13. Теперь откройте функцию main и создайте две символьных строки. Затем вызовите функцию:
    14. char str1[] = "apple";
      char str2[] = "apples";
      cout << "результат для \'"<< str1 <<"\' и \'"<< str2 <<"\' = " << stringEqual (str1,str2) << endl;
      
    15. Запустите проект и проверьте результат.
    Задание 5:
    Выполнить: Создайте функцию проверки, содержит ли строка сочетание букв 'mo'. Функция должна возвращать 1 (если сочетание найдено) или 0 (не найдено). Запрещено использовать стандартные функции. Необходимо использовать указатель.

    Указание 1: Заголовок функции должен быть следующим:

    bool findMo(const char * str);

    Указание 2: Чтобы проверить, достигнут ли конец строки, необходимо использовать условие цикла:

    while (*str != '\0')

    Примерный вывод:

    Задание 5:
    результат для строки 'the movie was nice' =: 1
    

    [Solution and Project name: Lesson_7task5, file name L7Task5main.cpp, L7Task5imp.cpp, L7Task5.h]

    Задание 6: Символьные строки

    Выполнить: Создайте функцию для проверки, содержит ли строковая переменная слово "dog". Функция возвращает 1 или 0. Необходимо использовать символьную строку (с-строку) и указатель. Не допускается использование каких-либо стандартных функций.

    Указание 1: Сигнатура функции должна быть следующей:

    bool findDog(char* p)

    Примерный вывод:

    задание 6
    строка: Hello world
    результат:
    0
    ---
    задание 6
    строка: my dog is nice
    результат:
    1
    
    Лабораторная работа 5:
    Выполнить: Измените порядок символов в строке на обратный. Используйте указатели на символы в начале и конце строки, и, перемещая оба указателя к середине, меняйте местами соответствующие символы.

    Указание 1: Чтобы использовать указатель на символы строки, необходимо, чтобы строка была массивом символьного типа (c-строкой):

    char str[] = "Hello world";

    Указание 2: Заголовок функции должен быть следующим:

    void reverseString (char * str);

    Примерный вывод:

    // lab 4:
    Исходная строка:
    Hello world
    Результат:
    dlrow olleH
    

    ✍ Алгоритм:

    1. Создайте консольное многофайловое приложение и добавьте в все необходимые библиотеки в (директивы) в файлы проекта.
    2. Откройте код главного файла (с функцией main()). Выведите сообщение пользователю и инициализируйте массив типа char (строка будет обрабатываться как массив символов):
    3. cout << "Lab 4:" << endl;
      char str[] = "Hello world"; 
      cout << "Исходная строка: " << str << endl; 
      
    4. Затем откройте заголовочный файл и подключите необходимые директивы (если они требуются):
    5. #pragma once
      #ifndef STRING7_H
      #define STRING7_H
      
      #include <iostream>
      
      #endif
      
    6. Объявление функции было указано в тексте задачи, давайте добавим этот код:
    7. // Lab 5: Измените порядок символов в строке на обратный. 
      void reverseString(char* str);
      
      str получает адрес на str[0].
    8. Создадим реализацию функции. Откройте файл ....cpp, добавьте необходимые директивы и вставьте следующий код:
    9. // Lab 5: Измените порядок символов в строке на обратный.  
      void reverseString(char* str) {
      	char* str1 = str;
      	char* res = nullptr; // пустой указатель
      	while (*str1) {
      		res = str1; 
      		*(str1++); 
              } // конец while
      // ...
      } // конец функции
      
      Объявляем указатель на первый символ строки. Строка (char* res = nullptr;) присваивает значение "null", что означает адрес 0x00000000. Затем цикл while перебирает адреса элементов массива. После цикла переменная res присваивает адрес последнего символа строки, например, адрес 0x00cffae6 переменной, хранящей символ "d" ).
    10. После этого будем присваивать значения переменной *str, сдвигая указатель с правого конца к середине. Для этого нам пригодится временная переменная t. В то же самое время мы заменяем значения элементов массива: крайнему элемент справа присваиваем значение первого элемента. Здесь нам необходимо использовать разыменовывание указателя:
    11. 	auto l = res;
      	while ((l) > (str)) {
      		auto t = *l; // временная переменная для хранения значений, начиная с последнего, 'd'
      		*l = *str; // разыменовывая, мы берем значение, на которое указывает *str 
                                 // и сохраняем по адресу, на который указывает *l 
      		*str = t; // изменяем значение, на которое указывает указатель *str 
      		l--; // передвигаемся по адресам с конца к середине
      		str++; // передвигаемся по адресам с начала к середине
      	}
      
      
    12. Вызовите созданную функцию и распечатайте результаты:
    13. reverseString(str);
      cout << "Исходная строка: " << str << endl;
      
    14. Запустите программу и проверьте результат.
    Задание 7:
    Выполнить: Дана строка, содержащая, помимо слов, положительное целое число ("С наступающим 2021 годом вас!"). Создайте функцию для отображения подстроки, содержащей это число.

    Указание 1: Используйте следующий заголовок функции (необходимо будет сделать число из полученных цифр):

    void numbFromStr(char* str);

    Чтобы понять, когда "наступит" конец строки, можно использовать следующий цикл:

    while (*str1)
      {
    	  pLast = str1; // адрес последнего символа
    	  *(str1++);
       }

    Указание 2: Результат должен иметь строковый тип, поэтому следует подключить библиотеку:

    #include <string>

    Указание 3: для проверки того, является ли символ цифрой, следует создать логическую функцию:

    bool isdigit(char c) {
    	// проверка символа: например, можно использовать c<='9' и т.п.

      
    Примерный вывод:

    Задание 7:
    Строка: Happy 2021 year to u!
    подстрока с числом: 2021
    

    [Solution and Project name: Lesson_7task7, file name L7Task7main.cpp, L7Task7imp.cpp, L7Task7.h]

    Задание 8:

    Выполнить: Дана строка символов. Преобразуйте в нем все заглавные латинские буквы в строчные. Функция должна возвращать количество выполненных замен.

    Указание 1: Необходимо использовать тип символьной строки:

    char s[] ="...";

    Указание 2: Используйте следующий заголовок функции. Функция возвращает целое число (количество замен):

     int to_lower(char * str);

    Указание 3: Для проверки, не достигнут ли конец строки, можно использовать следующее условие цикла:

    while (*str != '\0')

    Указание 3: Чтобы проверить, является ли буква прописной, нужно использовать следующую функцию:

    bool isCappital(char ch)
    {
        return (ch >= 'A' && ch <= 'Z');
    }

    Указание 4: Для конвертации букв в нижний регистр (в маленькие буквы) нужно использовать стандартную функцию tolower(ch).
      
    Примерный вывод:

    Задание 8:
    Исходная строка: HelLo wOrLD
    Результат: hello world
    

    [Solution and Project name: Lesson_7task8, file name L7Task8main.cpp, L7Task8imp.cpp, L7Task8.h]

    Задание 9:

    Выполнить: Дана символьная строка (с-строка), которая представляет арифметическое выражение в форме: "<digit>±<digit>±...±<digit>", где арифметические операции "+" или "" могут располагаться на месте символа "±" (например, "4+7-2-8"). Посчитайте результат выражения. Для решения использовать указатель.

    Указание 1: Используйте следующий заголовок функции:

    int eval(char * expr);

    Указание 2: Чтобы выделить первый символ из строки, можно использовать следующий код:

    int res = *expr++ - '0'; // здесь *expr++  это первый символ в строке

    или так:

    char r = *expr; //*expr  - код в таблице ascii
    int res = atoi(&r); // конвертирует код в число

    Указание 3: Для хранения числа или знака операции можно использовать следующую концепцию:

    char ch1 = *expr++; // может быть как знаком операции (символом), так и числом
    // чтобы использовать переменную, как число:
    ch1-'0';

    Примерный вывод:

    Задание 9:
    Строка: 9+4-2-0
    Результат: 11
    

    [Solution and Project name: Lesson_7task9, file name L7Task9main.cpp, L7Task9imp.cpp, L7Task9.h]