Урок 9. Структуры в c++

Theory

Structures in C++

  • A data structure is a group of data elements grouped together under one name. These data elements, known as members, can have different types and different lengths.
    • Example:

      1
      2
      3
      4
      5
      6
      7
      
      struct product {
        int weight;
        double price;
      } ;
       
      product apple;
      product banana, melon;
      This declares a structure type, called product, and defines it having two members: weight and price, each of a different fundamental type. This declaration creates a new type (product), which is then used to declare three objects (variables) of this type: apple, banana, and melon. Note how once product is declared, it is used just like any other type.
    • Right at the end of the struct definition, and before the ending semicolon (;), the optional field object_names can be used to directly declare objects of the structure type. For example, the structure objects apple, banana, and melon can be declared at the moment the data structure type is defined:
    • 1
      2
      3
      4
      
      struct product {
        int weight;
        double price;
      } apple, banana, melon;
      In this case, where object_names are specified, the type name (product) becomes optional: struct requires either a type_name or at least one name in object_names, but not necessarily both.

    Access to the objects’ members

  • Once the three objects of a determined structure type are declared (apple, banana, and melon) its members can be accessed directly. The dot notation is used. Dot (.) must be inserted between the object name and the member name. For example, we could operate with any of these elements as if they were standard variables of their respective types:
  • 1
    2
    3
    4
    5
    6
    
    apple.weight
    apple.price
    banana.weight
    banana.price
    melon.weight
    melon.price
    Each one of these has the data type corresponding to the member they refer to: apple.weight, banana.weight, and melon.weight are of type int, while apple.price, banana.price, and melon.price are of type double.

    Pointers to structures

  • Structures can be pointed to by its own type of pointers:
  • 1
    2
    3
    4
    5
    6
    7
    
    struct Product_t {
      int weight;
      double price;
    } apple, banana, melon;
     
    Product_t aproduct;
    Product_t * pproduct;
    Here aproduct is an object of structure type Product_t, and pproduct is a pointer to point to objects of structure type Product_t.
  • Therefore, the following code would also be valid:
  •  
    pproduct = &aproduct;
    
    The value of the pointer pproduct would be assigned the address of object aproduct.
  • The following example serve to introduce a new operator: the arrow operator (->):
  • //...
    struct Product_t {
      int weight;
      double price;
    } ;
     
    int main ()
    {
      string mystr;
     
      Product_t aproduct;
      Product_t * pproduct;
      pproduct = &aproduct;
     
      cout << "Enter weight: ";
      cin >> pproduct ->weight;
      cout << "Enter price: ";
      cin >> pproduct ->price;
     
      cout << "\nYou have entered:\n";
      cout << pproduct ->weight << " ";
      cout << pproduct ->price;
     
      return 0;
    }

    Tuples

  • A tuple is an object that can hold a collection of items. Each item can be of a different type.
  • Example:

    #include <string>
    #include <tuple> // std::tuple, std::get, std::tie, std::ignore
     
    using namespace std;
     
    int main()
    {
      tuple<int, char, string> t {4, 'z', "C++"}; // the 1-st way of declaration
      auto x = make_tuple(4, 'z', "C++"); // the 2-d way of declaration
     
      auto s1 = get<2>(x);  // access element
      get<0>(t) = 20; // set to another value
      int i; char c; string s;
      tie(i, c, s) = t; // unpack elements
      tie(ignore, ignore, s) = t; //unpack (with ignore) 
    }

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

    Лабораторная работа 1:
    Выполнить: Создать структуру Books_t, хранящую сведения о книгах. Добавить следующие поля структуры:

    • название книги
    • год издания

    Задания:

    1. Создать структуру Books_t
    2. Реализовать метод структуры для вывода сведений:
    3. void printBook(Books_t book);
    4. Создать два объекта (например, Pushkin, Chekhov).
    5. В основной программе создать два объекта-книги и вывести краткую информацию о них.

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

    Lab 1:
    Введите год работы Чехова под названием Три сестры:
    1897
    Работа Пушкина:
     Евгений Онегин 1823
    Работа  Чехова:
     Три сестры 1897
    

    ✍ Алгоритм:

      1. Создайте структуру Books_t для хранения данных о книгах. Добавьте следующие поля объекта: название книги, год издания.

    • Структура должна быть определена внутри заголовочного файла. Откройте structBooks8.h и добавьте код для определения структуры с двумя полями — title и year:
    • struct Books_t
      {
      	string title;
      	int year;
      };
      

      2. Реализуйте метод для вывода информации об объекте.

    • Также внутри заголовочного файла следует определить метод с параметром типа Books_t, чтобы принимать в качестве параметра определенную книгу. Добавьте код после структуры:
    • //метод для вывода информации об объектах
      void printBook(Books_t book);
      
    • Реализация функции должна быть помещена в файл structBooks8.cpp:
    • void printBook(Books_t book)
      {
      	cout << book.title;
      	cout << " " << book.year << endl;
      }
      

      3. Создайте два объекта (например, Пушкин и Чехов).

    • Откройте файл main.cpp и добавьте код для создания двух объектов типа:
    • Books_t Pushkin;
      Books_t Chekhov;
      

      4. В основной программе создайте как минимум 2 книги, выведите информацию по этим книгам.

    • После определений объектов добавьте код для инициализации полей с определенными значениями. Попросите пользователя ввести значение одного из полей, например, года издания книги:
    • Pushkin.title = "Eugene Onegin";
      Pushkin.year = 1823;
      
      Chekhov.title = "Three Sisters";
      cout << "Enter year of Chekhov work Three Sisters: "<cin >> Chekhov.year;
      
    • Выведите информацию о книгах, используя метод printBook:
    • cout << "Pushkin's work is:\n ";
      printBook(Pushkin);
      cout << "And Chekhov's:\n ";
      printBook(Chekhov);
      
      system("pause");
      
    • Запустите программу и проверьте выходные данные.
    Задание 1:

    Выполнить: Создать структуру Cars_t для хранения информации об автомобилях. Добавьте поля для хранения цвета автомобиля, марки автомобиля и года выпуска. Создайте не менее 4 объектов и выведите информацию об этих объектах, используя метод printCars.

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

    void printCars(Cars_t car);

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

    Task 1:
    Введите марку автомобиля:
    >>audi
    Введите цвет автомобиля:
    >>grey
    Введите год выпуска автомобиля:
    >>1997
    Информация:
    audi grey 1997
    bmw green 2009
    suzuki white 2015
    honda black 2019
    

    [Solution and Project name: Lesson_9task1, file name L9Task1main.cpp, L9Task1imp.cpp, L9Task1.h]

    Лабораторная работа №2, массив типа structure:

    Выполнить: Создать структуру Toys_t для хранения данных об игрушках для продажи. Добавьте следующие свойства (поля) структуры:

    • название игрушки - тип string
    • для какого возраста ребенка - тип integer

    Задачи:

    1. Реализовать метод для вывода информации об объектах:
    2. void printBook(Toys_t toy);
    3. Реализовать метод вывода игрушек для маленьких детей (до 4 лет):
    4. void printToy4(Toys_t toy);
    5. Создайте массив объектов игрушек.
    6. В основной программе создайте не менее 4 элементов массива, отобразите информацию по этим игрушкам.

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

    Введите название игрушки:
    >>cat
    Введите возраст ребенка:
    >>2
    Краткая информация:
    car 3
    doll 4
    crocodile 6
    cat 2
    Краткая информация по игрушкам для маленьких детей:
    car 3
    doll 4
    cat 2
    

    ✍ Алгоритм:

      1. Создайте структуру Toys_t для хранения данных об игрушках

    • Структура должна быть определена внутри заголовочного файла. Откройте structToys8.h и добавьте код для определения структуры с двумя свойствами (полями)- title и age:
    • struct Toys_t
      {
      	string title;
      	int age;
      };
      

      2. Реализовать метод для вывода информации об объектах.

    • Также внутри заголовочного файла необходимо определить метод с параметром типа Toys_t. Добавьте код после структуры:
    • //метод для вывода информации
      void printToys(Toys_t toy);
      
    • Реализация функции должна быть помещена в файл structToys8.cpp:
    • void printToys(Toys_t toy)
      {
      	cout << toy.title;
      	cout << " " << toy.age << endl;
      }
      

      3. Реализовать метод вывода информации об игрушках для маленьких детей (до 4 лет)

    • Внутри заголовочного файла определите метод с параметром типа Toys_t :
    • //вывод инфо об игрушках для маленьких
      void printToy4(Toys_t toy);
      
    • Реализация функции должна быть помещена в structToys8.cpp файл:
    • void printToy4(Toys_t toy)
      {
      	if (toy.age <= 4) {
      		printToys(toy);
      	}
      }
      

      4. Создайте массив объектов.

    • Откройте файл main.cpp и внутри функции main добавьте код для создания массива объектов типа структуры:
    • Toys_t toys[4];
      

      5. В основной программе создайте не менее 4 элементов массива, отобразите информацию по этим игрушкам.

    • Добавьте код для инициализации элементов (полей) определенными значениями. Попросите пользователя ввести значения одного из элементов массива:
    • toys[0].title = "car";
      toys[0].age = 3;
      toys[1].title = "doll";
      toys[1].age = 4;
      toys[2].title = "crocodile";
      toys[2].age = 6;
      cout << "Введите название: " << endl;
      cin >> toys[3].title;
      cout << "Введите возраст: " << endl;
      cin >> toys[3].age;
      
    • Выведите информацию об игрушках, используя метод printToys, и информацию об игрушках для маленьких детей:
    • cout << "Информация:\n ";
      for (auto x: toys)
      	printToys(x);
      cout << "Информация об игрушках для маленьких:\n ";
      for (auto x : toys)
      	printToy4(x);
      
      system("pause");
      
    • Запустите программу и проверьте выходные данные.
    Задание 2:

    Выполнить: Создать структуру Tours_t для хранения информации об экскурсиях по тур. поездкам. Добавьте поля, чтобы ввести следующую информацию о туре: название страны, количество человек, количество дней, стоимость поездки по стране (double). Создайте метод для вывода данных, а также метод для расчета стоимости поездки на основе формулы: количество людей * количество дней * тариф по стране. Создайте массив объектов tours. Для элементов массива должны вызываться оба метода.

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

    // возвращает результат вычисления (double)
    double printTourCost(Tours_t tour);

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

    Task 2:
    информация:
     Sweden: people 2, days 3, rate 155.5
     the price is 933
    Norway: people 1, days 3, rate 265.5
     the price is 796.5
    UK: people 2, days 4, rate 455.5
     the price is 3644
    

    [Solution and Project name: Lesson_9task2, file names L9Task2main.cpp, L9Task2imp.cpp, L9Task2.h]

    Лабораторная работа №3, указатели на структуры:

    Выполнить: Создать структуру Movies_t для хранения данных о фильмах. Добавьте следующие свойства (поля) структуры:

    • название фильма - типа string
    • год выпуска (целочисленный тип)

    Создайте массив фильмов (попросите пользователя ввести информацию об одном фильме). Реализуйте метод для вывода информации об объектах, используйте указатель на эти объекты и оператор стрелку.

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

    введите название: 
    >>Matrix
    введите год: 
    >>1999
    
    Краткая информация:
    Matrix 1999
    Titanic 1997
    

    ✍ Алгоритм:

    • Структура должна быть определена в заголовочном файле. Откройте structMovies9.h и добавьте код для определения структуры с двумя свойствами (полями) - title и year:
    • struct Movies_t {
      	string title;
      	int year;
      };
      
    • Также внутри заголовочного файла необходимо определить метод с параметром типа Movies_t для вывода информации. Добавьте код после структуры
    • void printMovies(Movies_t* movie, int n);
      
    • Реализация функции должна быть в файле structMovies9.cpp:
    • void printMovies(Movies_t* array, int n) {
      	Movies_t *ptr = array;
      	for (int i = 0; i < 2; i++) {
      		cout << ptr->title << " " << ptr->year << endl;
      		ptr++;
      	}
      }
      
      *ptr - указатель, указывающий на местоположение, на которое указывает *array (нулевой элемент).
      Стрелочный оператор (->) - это оператор разыменования, который используется исключительно с указателями на объекты, имеющие поля. Этот оператор служит для доступа к элементу объекта непосредственно по его адресу.
    • Откройте файл mainMovies9.cpp и в функции main добавьте код для создания массива объектов структурного типа и указатель, указывающий на этот массив:
    • Movies_t amovie[3];
      Movies_t * pmovie;
      
      pmovie = amovie;
      
    • Добавьте код для инициализации свойств (полей) конкретными значениями. Попросите пользователя ввести значения одного элемента массива:
    • amovie[1].title = "Titanic";
      amovie[1].year = 1997;
      cout << "Введите название: ";
      cin >> pmovie->title;
      cout << "Введите год: ";
      cin >> pmovie->year;
      
    • Выведите информацию о фильмах с помощью метода printMovies :
    • cout << "\nИнформация:\n";
      printMovies(amovie, 2);
      
      system("pause");
      
    • Запустите программу и проверьте выходные данные.
    Задание 3:

    Выполнить: Создать структуру Employee_t для хранения информации о сотрудниках какой-либо компании. Добавьте поля для реализации следующей информации о сотрудниках: имя, отдел, телефон, зарплата. Создайте массив сотрудников. Реализуйте метод для вывода информации об объектах, используйте указатель на эти объекты и стрелочный оператор. Также создайте метод smallSalary для вывода информации о сотрудниках, зарплата которых составляет менее 30 000 рублей.

    Указание: Заголовок методы smallSalary:

    void smallSalary(Employee_t* array, int n);

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

    Task 3:
    Информация:
    Ivanov: бухгалтерия, t. 233-33-33, 28000 roubles
    Petrov: ИТ-отдел, t. 233-33-34, 45000 roubles
    Sidorov: клиентский отдел, t. 233-33-35, 25000 roubles
    
    с зарплатой менее 30 000 рублей:
    Ivanov
    Sidorov
    

    [Solution and Project name: Lesson_9task3, file names L9Task3main.cpp, L9Task3imp.cpp, L9Task3.h]


    Урок 8. Указатель и массивы. Динамическая память

    Теория

      Шаблонные функции

    • Шаблонная переменная объявляется началом класса или функции:
    • template <typename T>
      int max(T a, T b) {
        if (a > b) { return a; }
           return b;
      }
    • Шаблонные переменные проверяются во время компиляции, что позволяет отлавливать ошибки перед запуском программы.
    • #include <iostream>
      using namespace std;
       
      template <typename T>
      T max(T a, T b) {
      	if (a > b) { return a; }
      	return b;
      }
      int main()
      {
      	cout << "max(3, 5): " << max(3, 5) << endl; // 5
      	cout << "max('a', 'd'): " << max('a', 'd') << endl; // d
      	cout << "max(\"Hello\", \"World\"): " << max("Hello", "World") << endl; // Hello
      	cout << "max(3, 5, 6): " << max(3, 5, 6) << endl; // error
       
      	return 0;
      }
    • Шаблонные функции определяют семейство функций.
    • Объявлять их лучше в заголовочном файле, там, где и объявлены функции.
    • lib.cpp

      #include "lib.h"

      lib.h

      template<typename T>
      class My
      {
          My() { }
      T f(T a, T b);
      };
       
      template<typename T>
      T My::f(T a, T b)
      {
        return a + b;
      }
    • Шаблонная функция сама по себе не является типом, или функцией, или какой-либо другой сущностью. Никакой код не генерируется из исходного файла, содержащего только определения шаблонов. Для того чтобы появился какой-либо код, должен быть создан экземпляр шаблона: аргументы шаблона должны быть определены таким образом, чтобы компилятор мог сгенерировать функцию.
    • Явное определение экземпляра принудительно создает экземпляр функции или функции-члена, на которые они ссылаются. Он может появиться в программе в любом месте после определения шаблона, и для данного списка аргументов разрешается появляться в программе только один раз, диагностика не требуется.
    • template<typename T>
      void f(T s)
      {
          std::cout << s << '\n';
      }
       
      template void f<double>(double); // создает экземпляр f<double>(double)
      template void f<>(char); //создает экземпляр f<char>(char)
      template void f(int); // создает экземпляр f<int>(int)
    • Неявное определение экземпляра: Когда код ссылается на функцию в контексте, который требует существования определения функции, и эта конкретная функция не была создана явно, происходит неявное создание экземпляра. Список аргументов шаблона указывать не обязательно, если он может быть выведен из контекста.
    • #include <iostream>
       
      template<typename T>
      void f(T s)
      {
          std::cout << s << '\n';
      }
       
      int main()
      {
          f<double>(1); // создает экземпляр и вызывает f<double>(double)
          f<>('a'); // создает экземпляры и вызывает f<char>(char)
          f(7); // создает экземпляры и вызывает f<int>(int)
          void (*ptr)(std::string) = f; //
      }

      1. Указатель на одномерный массив в динамической памяти

      int* p = new int[10];
      for (int* q = p; q<p+10; q++)
        *q = 1;
       
      for (int i=0; i<10; i++)
        p[i] = 1;
       
      delete [] p;

      Для простых типов можно написать delete p (подсистема знает, что мы выделили массив), но для классов это ошибка.

      Errors related to using of dynamic memory

      Error 1. Accessing a section of dynamic memory that we have already returned using delete

      int* p = new int;
      *p = 777;
      delete p;
      *p = 666;
    • What to do?:
    • int* p = new int;
      *p = 777;
      delete p;
      p = nullptr; // !
      *p = 666;
    • Why it’s not enouph?:
    • int* p = new int;
      int * q = p;delete p;
      p = nullptr; // !
      *q = 666;
    • Error 2. We returned dynamic memory twice using delete
    • int* p = new int;
      *p = 777;
      delete p;delete p;
    • Error 3. Dynamic memory leak
    • int* p = new int[1000];
      *p = 777;// and forgot to delete!
    • Error 3a. Dynamic memory leak in the loop
    • for (int i = 0; i<100000; i++)
      {
        int* p = new int[1000];
        *p = 777;// and forgot to delete!
       
      }
    • Error 3b. Dynamic memory leak in the function.
    • void p()
      {
        int* p = new int[1000];
        *p = 777;// and forgot to delete!
      }
    • The rule. If dynamic memory is allocated in a function, then it must be returned in the same function. There are exceptions.
    • Two-dimensional arrays

    • A two-dimensional array is considered as a one-dimensional array of one-dimensional arrays
    • int a[3][4];
    • Two-dimensional rectangular arrays («matrices») are conveniently modeled using pointer arrays:
    • int ** mat = new int *[rows]; // future matrix of rows of rows

      Each element mat[i] of this array is a pointer, it should be initialized in a loop, assigning it the result new int [cols], where cols is the desired number of columns of the matrix.

    • The reference to the (i, j)-th element of the matrix looks like this: mat[i][j]. To iterate over the matrix, just as in the case of a regular array, you can use pointers.
    • It is necessary to empty (devastate) memory, it should be done in the reverse order: first, the memory for each row is released in the loop: delete [] mat[i]. And then the memory of the array of pointers mat: delete [] mat is released. This procedure should be executed as a function with one argument rows(number of rows).
    • Another example:

    • This is an array of three elements of type int [4]
    • a[i][j] ~ *(a[i] + j) ~ *(*(a + i) + j)

      Printing an array:

      void print(int a[][4], int m, int n)
      {
        for (int i=0; i<m; i++)
        {
          for (int j=0; j<n; j++)
          cout << a[i][j] << " ";
          cout << endl;
        }
      }

      Two-dimentional arrays and dynamic memory

    • In order to pass a two-dimensional array of arbitrary dimension to a function, it should be allocated in dynamic memory:
    • void print(int** a, int m, int n)
      {
        for (int i=0; i<m; i++)
        {
          for (int j=0; j<n; j++)
          cout << a[i][j]; // a[i][j] ~ *(*(a + i) + j)
          cout << endl;
        }
      }
      int main()
      {
      // allocation in memory
        int** p = new int*[3]; // pointer pointing to pointer (3 rows)
        for (int i=0; i<3; i++)
           p[i] = new int[4]; // 4 columns
        print(p,3,4);}
    • After using it in the function, the allocated memory must be returned:
    • int main()
      {
        int** p = new int*[3];
        for (int i=0; i<3; i++)
          p[i] = new int[4];
       
      print(p,3,4); // Нарисовать на доске
      // deallocation of memory:
      for (int i=0; i<3; i++)
        delete [] p[i];
      delete [] p;
      p = nullptr;
      }

      Stepwise two-dimensional dynamic arrays

      int main()
      {
        int** p = new int*[3];
       
        for (int i=0; i<3; i++)
          p[i] = new int[4];
       
        print(p,3,4);
        for (int i=0; i<3; i++)
          delete [] p[i];
        delete [] p;
        p = nullptr;
      }

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

    1. Одномерные массивы в динамической памяти

    Лабораторная работа 1:
    Выполнить: Создайте приложение с двумя шаблонными функциями. Одна из них запрашивает элементы одномерного массива заданного размера, другая — выводит элементы одномерного массива заданного размера.

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

    template <typename T> 
    void inputArr(T* arr, int &size);
     
    template <typename T>
    void printArr(const T* arr, int &size);

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

    Lab 1:
    Array[0] 1.1
    Array[1] 2.1
    Array[2] 3.2
    Array[3] 4.1
    Array[4] 5.4
    
    1.1 2.1 3.2 4.1 5.4 
    

    ✍ Алгоритм:

    • Над функцией main() добавьте код шаблонной функции для ввода элементов массива. Функция будет иметь два параметра — указатель на массив и ссылочную переменную, хранящую размер массива.
    • template <typename T> 
      void inputArr(T* arr, int &size) { // arr - указатель на массив любого типа
      	for (int i = 0; i < size; i++) {
      		cout << "Array[" << i << "] ";
      		cin >> arr[i]; // разыменование указателя
      	}
      }
      
    • В основной функции инициализируем переменную, хранящую размер массива (массив будет состоять из 5 элементов), и объявим указатель на новый массив типа double:
    •   int size = 5;
        double* myArray = new double[size];
      
    • Вызовите функцию:
    • inputArr(myArray, size);
      
    • Над функцией main() добавьте код шаблонной функции для вывода элементов массива:
    • template <typename T>
      void printArr(const T* arr, int &size) {
      	for (int i = 0; i < size; i++) {
      		//cout << arr[i] << " "; // разыменование способ 1:
      		cout << *(arr + i) << " "; //разыменование способ 2:
      	}
      }
      
      Используйте один из двух способов разыменования указателя.
    • Вызовите функцию:
    • printArr(myArray, size);
      
    • Раз для динамического массива была выделена память, то ее необходимо освободить:
    • delete[]myArray;
      myArray = NULL;
      
      Чтобы массив указателей не указывал на какой-либо адрес после оператора удаления, необходимо присвоить значение NULL.
    • Запустите программу.
    Задание 1:

    Выполнить: Дан массив целых чисел (A). Создайте массив, скопировав в него все элементы, но вместо элементов, последняя цифра которых равна 0, необходимо вставить 0. В этой задаче необходимо использовать динамическую память (new / delete). Создайте шаблонную функцию для вывода массива.

    Указание 1: Убедитесь, что в основной программе после вывода результирующего массива память была освобождена (delete []).

    Указание 2: Заголовок функции:

    void MakeArrayWithZero(int* a, int& size);

    Где a - массив, а size - размер массива.

    Указание 3: Шаблонная функция для вывода массива должна быть объявлена в заголовочном файле:

    template<typename T>
    void printArray(T const * arr, int size, char delim = ' ') {
      ...
    }

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

    Task 1:
    Исходный массив:
    1 20 30 4 5 
    Результирующий массив:
    1 0 0 4 5
    

    [Solution and Project name: Lesson_8task1, file name L8Task1main.cpp, L8Task1imp.cpp, L8Task1.h]

    Задание 2:

    Выполнить: Дан целочисленный массив. Создать новый массив, в котором будут удалены из исходного массива все вхождения заданного числа. Исходный массив не изменяется. Не следует использовать какие-либо стандартные функции.

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

    int* delete_all_entries(int* a, int size, int n, int& new_size)

    Указание 2: Создайте шаблонную функцию для печати элементов массива. Используйте следующий заголовок функции:

    template<typename T>
    void print_array(T const* a, int size, char delim = ' ');

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

    Задание 2:
    исходный массив:
    1 2 3 2
    число, которое следует удалить:
    2
    новый массив:
    1 3
    

    [Solution and Project name: Lesson_8task2, file name L8Task2main.cpp, L8Task2imp.cpp, L8Task2.h]

    Двумерные массивы

    Лабораторная работа №2:

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

    int ** createMatrix(int rows, int cols, int value = 0);

    The default value of 0 for the value parameter should be specified only in the header file. In the cpp file, the header contains three parameters in the usual form:
    implementation file:

    int ** createMatrix(int rows, int cols, int value) {
    }

    Значение по умолчанию равное 0 для параметра value должно быть указано только в заголовочном файле. В cpp файле заголовок функции содержит три параметра в обычной форме:

    Указание : Для вывода элементов матрицы следует создать еще одну функцию с заголовком:

    void printMatrix(int ** mat, int rows, int cols);

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

    Lab 2:
    matrix 5 by 5:
    0 0 0 0 0
    0 0 0 0 0
    0 0 0 0 0
    0 0 0 0 0
    

    [Solution and Project name: Lesson_8Lab2, file name L8Lab2main.cpp, L8Lab2imp.cpp, L8Lab2.h]

    ✍ Алгоритм:

    1. Создайте приложение и добавьте необходимые директивы.
    2. Поскольку необходимо создать матрицу в функции, откройте заголовочный файл и добавьте сигнатуру функции:
    3. #pragma once
      
      #ifndef HEADER_H
      #define HEADER_H
      
      int ** createMatrix(int rows, int cols, int value = 0);
      
      #endif
      
    4. Затем откройте файл реализации и добавьте функцию:
    5. // ...
      int ** createMatrix(int rows, int cols, int value) {
        //...
      }
      
    6. Прежде всего, необходимо выделить память для двумерного массива (матрицы). Для этого добавьте код в функцию:
    7. int** res = new int*[rows];
      for (int i = 0; i < rows; i++)
      	{
      		res[i] = new int[cols];
      		for (int j = 0; j < cols; j++) {
      			res[i][j] = value;
      		}
      	}
      
      В первой строке объявляем указатель, указывающий на адреса строк матрицы, которые сами являются указателями, указывающими на адреса столбцов. Таким образом, выделяется память для матрицы.
      В строке #4 для каждой строки выделяется память для столбцов. Количество строк и столбцов берем из значений аргументов.
    8. Функция возвращает матрицу:
    9.   return res;
      } // конец функции
      
    10. Теперь перейдем в функцию main для вызова созданной нами функции.
    11. Поскольку функция вернет созданную матрицу, необходимо создать переменную для хранения этой матрицы и присвоить ей результат вызова функции.
    12. int main() {
        // матрица 4 х 5 , каждый элемент = 0:
        int** matrix = createMatrix(4, 5, 0);
        //...
      }
      
    13. Попробуйте запустить программу и убедитесь, что ошибок нет.
    14. Далее необходимо создать функцию для вывода элементов матрицы. Там будет три аргумента - сама матрица, количество столбцов и строк. Добавьте код в файл реализации:
    15. void printMatrix(int ** mat, int rows, int cols) {
      	for (int i = 0; i < rows; i++) {
      		for (int j = 0; j < cols; j++) {
      			cout << mat[i][j] << " ";
      		}
      	cout << endl;
      	}
      }
      
    16. Добавьте заголовок функции в заголовочный файл. Сделайте это самостоятельно.
    17. Запустите программу и убедитесь в отсутствии ошибок.
    18. После создания динамического массива мы должны освободить память. Создадим функцию для освобождения памяти. Добавьте код функции:
    19. void freeMemory(int ** mat, int rows) {
      	for (int i = 0; i < rows; i++) {
      		delete[] mat[i];
      	}
      	delete[] mat;
      	mat = NULL;
      }
      
      Поскольку нужно использовать указатели, необходимо знать только количество строк, которое передается из основной программы в качестве аргумента.
    20. Добавьте заголовок функции в заголовочный файл и вызовите функцию в основном исходном файле.
    Задание 3:

    Алгоритм: Создайте функцию для нахождения минимального и максимального значений матрицы. Для выполнения задания можно использовать аналогичные функции из лабораторной работы №2 (создать матрицу, вывести ее и освободить память). Элементы матрицы должны быть сгенерированы случайным образом. Заголовок функции для нахождения min и max должен быть следующим:

    void matrixMinMax(int ** mat, int rows, int cols, int &min, int &max);

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

    #include <ctime>
    // ...
    srand(time(0));
    //.. в цикле:
    array_name[i][j] = 1 + rand() % 10;

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

    Task 3:
    matrix 4 by 5:
    5 4 0 7 0
    0 8 0 1 2
    0 5 0 7 0
    4 0 0 9 0
    
    max = 9  min = 0
    

    [Solution and Project name: Lesson_8task3, file name L8Task3main.cpp, L8Task3imp.cpp, L8Task3.h]

    Задание 4:

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

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

    Task 4:
    matrix:
    8 3 3 6
    6 4 9 8
    6 1 9 6
    4 5 1 5
    
    sum = 26
    

    [Solution and Project name: Lesson_8task4, file name L8Task4main.cpp, L8Task4imp.cpp, L8Task4.h]

    Урок 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]



    Урок 6. C++ работа со строками

    Теория

    Строки в C++

    • Для работы со строкой должна быть включена библиотека string:
    • #include <string>
    • Переменные строкового типа можно понимать как массив символов.
    • К определенному символу в строке можно получить доступ как в обычном массиве, то есть по его индексу:
    • string str="it's possible to understand it";
      cout << str[2] << endl; // вывод: '
      for (int i = 0; i <= str.size(); i++) { // для перебора символов строки
      		if (str[i] == ' ')
      			//что-то делаем
      	}

    Строковые функции

      Find()

      size_t find (const string& str, size_t pos = 0) const;
    • Выполняет поиск в строке первого вхождения последовательности, указанного в аргументах.
    • Если указано значение pos, поиск включает только символы в позиции pos или после нее, игнорируя любые возможные вхождения, которые включают символы перед позицией pos.
    • size_t — это целочисленный тип без знака, размер любого объекта в байтах.
    • константа npos — максимальное значение size_t (обычно = 4294967295)
    • Это значение используется в качестве значения параметра len (или sublen) в функциях-членах string, означает «до конца строки».
    • В качестве возвращаемого значения оно обычно используется для указания отсутствия совпадений.
    • Эта константа определена со значением -1, которое, поскольку size_t является целым типом без знака, является максимально возможным представимым значением для этого типа.
    • Метод определения того, сколько раз подстрока встречается в строке:
      string str="it's possible to understand it";
      string substr="it";
      size_t f = -1; 
      int counter = 0;
      while (true) // infinite loop
       {
          // f = str.find(substr); //значение f будет равно 0
          // 'f+1' требуется для перехода на следующую позицию после найденного совпадения
          f = str.find(substr, f+1); // на 2-й итерации f = 28
          // на 3-й итерации f = 4294967295 это означает, что совпадений больше нет
          // string::npos = 4294967295
          if (f == string::npos)
               break;
          counter++;
        }
      cout << counter;

      Управляющие (специальные) символы используются для представления определенных специальных символов в строковых литералах и символьных литералах: \‘ — одинарная кавычка

      char c = '\''

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

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

    Правила оформления работ:

    • Для выполнения заданий необходимо использовать перечисления и оператор switch.
    • Все задания должны выполняться с помощью функций, результаты должны проверяться оператором assert в заголовочном файле.
    • Все функции и файлы должны сопровождаться комментариями с постановкой задания.

    Тип String

    Лабораторная работа1:

    Выполнить: Дана переменная строкового типа (s="Hello world") и дан символ (переменная c) (пользователь его вводит). Проверьте, есть ли символ c среди символов строки. Создайте функцию с именем checkIfChar. Функция должна возвращать 1, если введенный символ найден, или 0 в противном случае..

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

    bool checkIfChar(string s, char c)

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

    lab 1
    "Hello world"
    введите символ:
    >>> o
    результат:
    1
    

    ✍ Алгоритм:

    1. Откройте visual studio и создайте консольное приложение с Lesson6.
    2. Добавьте два файла .cpp в папку исходных файлов проекта (basicStrings.cpp и main.cpp) и один файл заголовка должен быть добавлен в папку заголовочные файлы.
    3. Откройте код main.cpp и добавьте все библиотеки, которые нам нужны для выполнения лабораторной работы:
    4. #include 
      #include  //
      #include  // необходим для работы со строковым типом
      using namespace std;
      
    5. В функции main() добавьте код с просьбой пользователя ввести предложение и присвойте входное значение переменной str. Не забудьте объявить переменную::
    6. int main() {
      	cout << "lab 1:" << endl;
      	string str="Hello world"; 
      	
        // ..
      }
      
    7. Затем попросите пользователя ввести символ. Присвойте значение переменной c:
    8. cout << "Enter character :" << endl;
      	char c;
      	cin >> c;
      
    9. Теперь необходимо создать функцию для проверки, находится ли символ в предложении. Функция должна быть с двумя аргументами — предложением (str) и символом (c). Добавьте заголовок объявления функции перед функцией main():
    10.  //lab 1: 
      bool checkIfChar(string s, char c)
      {
      	// выполнение
      }
      
    11. Добавьте код прохода по символам строки, используя цикл for. Внутри тела цикла мы должны проверить, соответствует ли текущий символ строки символу c. Если это так, функция должна возвращать 1, в случае, когда это не так, функция возвращает 0.
    12. for (int i=0;i<= s.size();i++){
      		if (s[i]==c){
      			return 1;
                              break;
                       }
      	}
      return 0;
      
    13. Вернитесь к функции main() и вызовите созданную функцию. Результаты функции должны быть выведены, поэтому вызов должен быть внутри инструкции cout<<:
    14. cout << "result :" << checkIfChar(str, c) << endl;
      
    15. Не забудьте добавить инструкцию, чтобы окно консоли оставалось открытым:
    16. //...
      system("pause");
      return 0;
      //...
      
    17. Запустите программу и проверьте выходные данные.
    Задание 1:

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

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

    bool findMo(string s)

    Указание 2: Чтобы проверить два или более условия в операторе if, вы должны использовать && (логическое И):

    if (condition_1 && condition_2){
    }

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

    Задание 1
    результат для 'Hello world':
    0
    ---
    Задание 1
    результат для 'My mom':
    1
    

    Задание 3:

    Выполнить: Дано предложение: str = "слово1 слово2 слово3 слово4". Подсчитайте количество слов в нем. Не допускается использование каких-либо стандартных функций.

    Указание 1: Вы должны подсчитать количество слов по количеству пробелов между словами.

    Указание 2: Не забудьте проверить, не содержит ли предложение дополнительных пробелов в начале и конце предложения.

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

    void countWords(string s, int& counter)

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

    задание 3
    Введите предложение:
    >>>  word1 word2 word3 word4
    результат:
    4
    

    Задание 4:

    Выполнить: Дана строка. Проверьте, совпадают ли ее первый и последний символы. Создайте функцию с идентификаторами LastAndFirst(). Не допускается использование каких-либо стандартных функций (кроме функции size()).

    Указание 1: Заголовок функции:

    bool LastAndFirst(string s)

    Указание 2: Метод size() может быть полезен для поиска последнего символа (s.size()).

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

    задание 4
    Для предложения Hello world
    результат:
    false
    ---
    задание 4
    Для предложения lucky ball
    результат:
    true
    

    Строковые функции

    Задание 5:

    Выполнить: Создайте функцию для проверки, содержит ли текст слово cat. Выведите позицию (индекс) слова в тексте. Необходимо использовать стандартную функцию поиска (find).

    Указание 1: Заголовок функции:

    void findCatFunction(string s, string s0)

    Указание 2: Функция find():

    s.find(s0) // возвращает -1, если в тексте s нет совпадений с s0

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

    задание 5
    для текста: cat word1 word3 word4
    результат:
    0
    ---
    задание 5
    для текста: word1 word3 word4
    результат:
    cat не найдено
    ---
    задание 5
    для текста: word1 cat word3 word4
    результат:
    cat найдено в позиции 6
    
    Лабораторная работа 2:

    Выполнить: Даны две строки S и S0 (вводятся). Найдите количество вхождений строки S0 в строку S.

    Указание 1: В задании удобно использовать функцию find класса string, которая возвращает позицию вхождения типа size_t (целое число без знака) или значение string :: npos в случае, если вхождение не было найдено. Рассмотрите пример, чтобы понять, как их использовать.

    Указание 2: Заголовок функции:

    int countOccurrence(string s, string s0)

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

    Lab 2
    строка 1: it's possible to understand it 
    строка 2: it
    результат: 2
    

    ✍ Алгоритм:

    1. Открытый файл main.cpp и в коде основной функции, под предыдущим кодом задания, добавьте комментария с номером задания. После этого объявите переменные строкового типа и инициализируйте их значениями:
    2. //lab 2
      cout << "lab 2:" << endl;
      string str1, str2;
      str1 = "it's possible to understand it";
      str2 = "it";
      cout << "string 1: " << str1 << "\n string 2: "<< str2 << endl;
      
      \n используется для перехода на новую строку.
    3. Откройте файл basicStrings.cpp и добавьте код заголовка функции:
    4. // lab 2: 
      int countOccurrence(string s, string s0)
      {
         // to do:
      } 
      
    5. Функция должна возвращать целочисленный тип.
    6. Функция имеет два параметра строкового типа: s - строка, содержащая предложение, и s0 - подстрока, которую мы должны найти в строке s.
    7. Чтобы найти подстроку, мы собираемся использовать стандартный метод find() и переменную f типа size_t (целочисленный тип без знака). Переменная f хранит положение подстроки внутри строки s. И если нет искомой подстроки, то f сохранит число 0, так как оно будет увеличено на 1 (f+1). Когда функция find() достигнет конца строки, значение npos (npos — это константа - максимальное значение для size_t (обычно = 4294967295)) будет присвоено переменной f:
    8.  size_t f = -1;
       int res = 0; // counter of the numbers of occurrences 
       while (true) // infinite loop
      	{
      		f = s.find(s0,f+1); 
      		if (f == string::npos) // if the end of the string is reached
      			break; // exit loop
      		++res;
      	}
      	return res;
      
      Бесконечный цикл удобно использовать, когда мы не знаем, сколько повторений цикла будет. Здесь оператор break будет выполнен только в том случае, если при поиске подстрок мы дойдем до конца строки.
    9. Вернитесь к main.cpp файлу и внутри основной функции добавьте вызов нашей функции. Поскольку функция вернет целочисленное значение, то есть количество вхождений, мы должны сохранить результат функции в переменной:
    10. int counter = countOccurrence(str1, str2);
      cout << "result :"  << counter << endl;
      
    11. Запустите программу и посмотрите выходные данные.
    Задание 6:

    Выполнить: Даны строки S и S0. Создайте функцию для удаления последней подстроки, соответствующей S0, из строки S. Если совпадающих подстрок нет, то верните неизмененную строку S.

    Указание 1: Вы можете использовать функцию rfind класса string, чтобы найти последнее вхождение подстроки, или можете использовать функцию erase для удаления.

    Указание 2: Заголовок функции:

    string eraseLast(string s, string s0)
    {
    	// to do:
            // 
            //
    	return s;
    }

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

    задание 6:
    строка 1: it's possible to understand it
    строка 2: it
    результат: it's possible to understand
    
    Задание 7:

    Выполнить: Дана строка, содержащая по крайней мере один символ пробела. Необходимо возвратить подстроку между первым и последним пробелами строки. Если строка содержит один пробел, должна быть возвращена пустая строка.

    Указание 1: Можно использовать функцию substr member function of the string.

    Указание 2: Заголовок функции:

    string betweenSpaces(string s)
    {
    	size_t f1, f2;
    	f1 = s.find(" "); // индекс первого пробела
    	f2 = s.rfind(" "); // индекс последнего пробела
    	// ...;
            // ...;
    	return  // ...;
    }

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

    задание 7:
    строка: it's possible to understand it
    результат: possible to understand
    
    Задания для самостоятельного выполнения:

    Указания: задания следует сохранить в файле с именем задания, и обязательно в коде вставить комментарий с постановкой задачи.
      
    STRING:
    String13. Дана строка. Подсчитать количество содержащихся в ней цифр.
    String28. Дан символ C и строка S. Удвоить каждое вхождение символа C в строку S.
    String35. Даны строки S и S0. Удалить из строки S все подстроки, совпадающие с S0. Если совпадающих подстрок нет, то вывести строку S без изменений.
    String43. Дана строка, состоящая из русских слов, набранных заглавными буквами и разделенных пробелами (одним или несколькими). Найти количество слов, которые содержат хотя бы одну букву «А».
    String61. Дана строка, содержащая полное имя файла. Выделить из этой строки название последнего каталога (без символов «\»). Если файл содержится в корневом каталоге, то вывести символ «\».
    Вставить формулу как
    Блок
    Строка
    Дополнительные настройки
    Цвет формулы
    Цвет текста
    #333333
    Используйте LaTeX для набора формулы
    Предпросмотр
    \({}\)
    Формула не набрана
    Вставить