Урок 5. Векторы и массивы в C++

На занятии объясняется тема «Работа с массивами в C++, векторы»

Теория

Одномерные массивы в C++

  • Размер массива фиксирован на все время существования в памяти и не может быть изменен.
  • Доступ к элементам массива производится с помощью операции индексации [ ].
  • При работе с массивом поэлементно следует учитывать, что нумерация элементов массива в языке С++ всегда начинается с 0.
  • В С++ при передаче массивов в качестве параметров функции передается адрес первого элемента массива
  • Пример 1:

    // Объявление массива из 5 целочисленных элементов 
    int b[5];

    Пример 2:

    #include <iostream>
    using namespace std;
    int main()
    {
     // Объявление и инициализация массива
     int a[] = {2, 3, 5, 7};
     cout << a[3] << endl; // output: 1
     // переприсваивание значения последнему элементу массива
     a[3] = 1;
     
     a[4] = 2; // возникнет ошибка
    }
    Использование цикла foreach:

    1. x (элемент массива) — только для чтения

    int a[] = {3, 7, 9, 5, 7, 2, 7};
    for (int x : a)     
        cout << x << " ";  // только вывод: 3 7 9 5 7 2 7

    2. x (элемент массиваt) может изменяться

    int a[] = {3, 7, 9, 5, 7, 2, 7};
    for (int &x : a)  // x по ссылке
    { 
        x += 1;  
        cout << x << " ";  // вывод: 4 8 10 6 8 3 8
    }
    Использование генератора случайных чисел:

    Пример 1:

    // будут сгенерированы случайные числа в диапазоне от 1 до 10    
    #include <iostream>
    #include <ctime>
    using namespace std;
     
    int main()
    {
    // чтобы получать новые случайно сгенерированные числа каждый раз, когда мы запускаем программу:
    	srand(time(0)); 
    	int b[5];
    	for (int &x : b)
    	{
    		x = 1 + rand() % 10;
    		cout << x << " "; // пример вывода: 8 4 2 6 5
    	}
    	return 0;
    }
    }

    Пример 2: мы можем сделать то же самое с классическим циклом for:

    #include <iostream>
    #include <ctime>
    using namespace std;
     
    int main()
    {
    // чтобы получать новые случайно сгенерированные числа каждый раз, когда мы запускаем программу:
    	srand(time(0));
    	int b[5];
    	for (int i=0;i<5;i++)
    	{
    		b[i] = 1 + rand() % 10;
    		cout << b[i] << " "; // пример вывода: 8 4 2 6 5
    	}
    	return 0;
    }
    Формула для определения границ сгенерированных чисел:
    min + (rand() % ((max — min) + 1))

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

    Пример:

    int numbMatrix[2][3] = { {1,2,3},{4,5,6} };
     
    	for (int i =0; i < 2; i++){
    		for (int j = 0; j < 3; j++) {
    			cout << numbMatrix[i][j] << ' ';
    		}
    	}

    Векторы

    • Векторы относятся к шаблонному типу. Шаблонный тип — это особый тип, который может принимать различные типы при инициализации.
    • std::vector — стандартная библиотека, которая обеспечивает функциональность динамического массива шаблонного типа.
    • Основное отличие от массивов заключается в том, что векторы являются динамическими массивами. Это означает, что размер вектора не нужно задавать изначально, элементы могут добавляться динамически.
    • std::vector использует шаблонный тип:

    • Пример:

      #include <vector>   // Определение
      using namespace std;
      int main()
      {
      vector<int> mm(5);  // Инициализация
      // или он может быть пустым: vector<int> mm(); 
      vector<int> a {1, 2, 3, 7};  // Инициализация
      // Цикл для итерации и печати значений вектора
      for (int i=0; i < a.size(); i++)  // size() - Количество элементов
         cout << a[i] << " "; // 1, 2, 3, 7
      // Другой вид цикла for (foreach) для итерации и печати значений вектора
      for (auto x : a)
         cout << x << " "; // 1, 2, 3, 7
      for (int& x : a)
         x++;
      }
      Для использования вектора необходимо поместить директиву #include <vector> .
    • При инициализации “шаблонного” типа сам тип помещается внутри < >:
    • std::vector<char> v1;
      std::vector<int> v2;
      
    • pop_back функция удаления последнего элемента
    • push_back функция добавления элемента в конец
    • Пример 1:

      #include <iostream>
      #include <vector>
       
      int main()
      {
          // Создание вектора, содержащего целые числа
          std::vector<int> v = {7, 5, 16, 8};
       
          // Добавление к вектору еще двух целых чисел
          v.push_back(25);
          v.push_back(13);
       
          // печать конкретных элементов
          cout << v.at(0) << endl; // 7
          // или:
          cout << v[1] << std::endl; // 5
          cout << v[2] << std::endl;
       
          // вставка в конкретное место, начиная с начала
          v.insert(v.begin() + 2, 5); // number 5 into third position
       
          // проход по элементам и вывод значений вектора
          for(int n : v) {
              std::cout << n << '\n';
          }
      }

      Пример 2:

      #include <vector>
      #include <iostream>
       
      int main() {
      vector<int> v;
      for (int i = 0; i < 100; i++) {
         v.push_back( i * i );
      }
       
      cout << v[12] << std::endl;
       
      return 0;
      }

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

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

      Замечание: не использование <>полностью разрешает перегрузку для проверки как шаблонных, так и нешаблонных перегрузок.

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

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

    Для выполнения всех заданий и лабораторных работ урока вы должны создать три файла:

    1. заголовочный файл (basic Vectors.h) (определения функций для всех заданий должны быть размещены здесь);
    2. файл basicVectors.cpp (здесь должны быть размещены реализации функций с комментариями ко всем задачам)
    3. main.cpp должен предоставлять пользовательский ввод, вызывать созданные функции и выводить результаты для всех задач.
    4. Каждая функция должна сопровождаться комментарием о том, для чего она предназначена, и должен быть указан номер задания.

    Одномерные и двумерные массивы

    Задание 0: Одномерные массивы

    Выполнить:
    Дан одномерный массив из 10 элементов. Элементы массива генерируются случайным образом в интервале [5;15]. Выведите количество четных и нечетных элементов массива.

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

    Массив:
    5 10 19 16 6 7 8 11 9 11 
    кол-во четных = 4, кол-во нечетных = 6
    

    [Solution and Project name: Lesson_4, files: Task0header.h, Task0imp.cpp, Task0main.cpp]

    Задание 00: Двумерные массивы

    Выполнить:
    Дан двумерный массив размером 2х3. Элементы этого массива генерируются случайным образом в интервале [1;10]. Создайте функцию для печати максимального значения массива.

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

    Массив:
    2 8 5
    1 10 5
    максимальный элемент = 10
    

    [Solution and Project name: Lesson_4, files: Task00header.h, Task00imp.cpp, Task00main.cpp]

    Векторы

    Лабораторная работа:
    Задания этого урока будут продолжением данной лабораторной работы.

    Выполнить: Задается целое число N (N > 0) и целочисленный вектор (массив) длины N. Заполните вектор первыми N положительными нечетными числами: 1, 3, 5,… N. Не используйте условный оператор. Создайте функцию oddVectorGenerator() для выполнения этой задачи.

    Указание: Для вывода результирующего вектора вы должны создать шаблонную функцию print_vector(). Функция должна быть помещена в специальный заголовочный файл printVect.h (вы должны добавить новый файл в свое приложение).

    Скопируйте код функции print_vector и вставьте его в файл printVect.h:

    #ifndef PRINTVECT_H
    #define PRINTVECT_H
     
    #include <iostream>
    #include <vector>
     
    // Указание: шаблонные функции всегда должны находиться внутри заголовочных файлов
     
    // шаблонная функция для вывода вектора
    template<typename T>
    void print_vector(const std::vector<T>& v, char delim = ' ') {
    	for (auto x : v)
    		std::cout << x << delim;
    	std::cout << std::endl;
    }
     
    /* #ifndef PRINT_VECT_H: */
    #endif

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

    lab 1
    введите N - число элементов вектора
    >> 25
    1 3 5 7 9 11 13 15 17 19 21 23
    

    ✍ Алгоритм:

    • Создайте новое консольное приложение с названием Lesson 5.
    • В папке исходных файлов окна обозревателя решений добавьте новые файлы: basicVectors.cpp и main.cpp. В папку Файлы заголовков добавьте новый файл заголовка: basicVectors.h.
    • Шаблонные функции всегда помещаются в заголовочные файлы, в отличие от обычных функций. Добавьте новый заголовочный файл и назовите его printVector.h. Скопируйте и вставьте в него код функции.
    • Открой файл main.cpp и подключите к нему все заголовочные файлы, которые понадобятся программе:
    • #include <iostream>
      #include "basicVectors.h"
      #include "printVect.h"
      using namespace std;
      
      
    • Затем необходимо объявить целочисленный вектор. Вы должны сделать это в файле main.cpp, сразу после всех подключенных директив:
    • vector<int> v;
      
    • Добавьте комментарий с номером лабораторной работы. Затем попросите пользователя ввести количество элементов вектора. Присвойте введенное значение переменной n:
    • cout << "lab 1"<< endl;
      cout << "введите N - кол-во элементов вектора";
      int n;
      cin >> n;
      
    • Откройте файл заголовка basicVectors.h. Подключите все необходимые директивы:
    • #pragma once
      #include <iostream>
      #include <vector>
      using namespace std;
      
    • Мы собираемся объявить функцию oddVectorGenerator(), которая должна заполнить вектор первыми N положительными нечетными числами:
    • void oddVectorGenerator(vector<int>&, int);
      
      Мы собираемся передать параметр vector по ссылке (&), потому что нам не нужно использовать новое место в памяти для его хранения.
    • Откройте файл basicVectors.cpp для кода реализации созданной функции. Но сначала добавьте все директивы в файл:
    • #include 
      #include <cassert>
      #include "printVect.h"
      #include "basicVectors.h"
      using namespace std;
      
    • После этого создайте функцию oddVectorGenerator() с кодом для выбора нечетных значений:
    • void oddVectorGenerator(vector<int> &v, int n)
      {
      	for (int i = 1; i < n; i += 2)
      	{
      		v.push_back(i);
      	}
      }
      
      Функция push_back добавляет элемент в конец вектора.
      При использовании шага цикла for равного двум, только нечетные числа будут присвоены значениям вектора.
    • Откройте файл main.cpp, сохраните его и вызовите функцию. Для вывода результатов также вызовите функцию print_vector():
    • oddVectorGenerator(v, n);
      print_vector(v);
      system("pause");
      
    • Запустите программу. Затем перейдите к выполнению Задания 1.
    Задание 1:

    Выполнить: Задан вектор (1, 2, 3, 4, 5, 6, 7, 8, 9, 10). Выведите его элементы в обратном порядке. Для выполнения создайте функцию.

    Указание 1: Рекомендуется выполнять задание следует в том же проекте, что и Lab 1. Просто пишите комментарий с номером задания и постановкой задачи. .

    cout << "task 1" << endl;

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

    v.resize(0);
    v = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };

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

    task 1
    до:
    1 2 3 4 5 6 7 8 9 10
    после:
    10 9 8 7 6 5 4 3 2 1

    [Solution and Project name: Lesson_5
    or you can have individual files: L5Task1header.h, L5Task1imp.cpp, L5Task1main.cpp]

    Задание 2:

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

    Указание 1: Рекомендуется выполнять задание следует в том же проекте, что и Lab 1. Просто пишите комментарий с номером задания и постановкой задачи.

    Указание 2: Следует использовать функцию abs, так как среди элементов массива может встретиться отрицательный элемент.

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

    задание 2
    вектор:
    -1 2 3 4 -5 6 7 8 -9 10
    положительных: 7, нечетных: 5
    

    [Solution and Project name: Lesson_5
    or you can have individual files: L5Task2header.h, L5Task2imp.cpp, L5Task2main.cpp]

    Задание 3:

    Выполнить: Задан целочисленный вектор. Выведите индекс его первого четного элемента.

    Указание: Рекомендуется выполнять задание следует в том же проекте, что и Lab 1. Просто пишите комментарий с номером задания и постановкой задачи.

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

    задание 3
    вектор:
    -1 5 3 4 -5 6 7 8 -9 10
    первый четный элемент массива: 4
    

    [Solution and Project name: Lesson_5
    or you can have individual files: L5Task3header.h, L5Task3imp.cpp, L5Task3main.cpp]

    Задание 4:

    Выполнить: Задан целочисленный вектор. Найдите наибольший из его локальных минимумов (локальный минимум — это элемент, который меньше любого из его соседей: например, … 5 2 7 … => число 2 является локальным минимумом, потому что 2 < 5 и 2 < 7).

    Указание: Рекомендуется выполнять задание следует в том же проекте, что и Lab 1. Просто пишите комментарий с номером задания и постановкой задачи.

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

    задание 4
    вектор:
    -1 5 3 4 -5 8 7 8 -9 10
    наибольший локальный минимум: 7
    

    [Solution and Project name: Lesson_5
    you can have individual files: L5Task4header.h, L5Task4imp.cpp, L5Task4main.cpp]

    Задание 5:

    Выполнить: Задан целочисленный вектор A размера N (> 0). Заполните его степенями двойки от 1 до показателя степени N:

    A = 21, 22, 23, 24,… 2N

    Указание: Рекомендуется выполнять задание следует в том же проекте, что и Lab 1. Просто пишите комментарий с номером задания и постановкой задачи.

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

    задание 5
    введите N:
    >>> 5
    вектор:
    2  4  8  16  32
    

    [Solution and Project name: Lesson_5
    you can have individual files: L5Task5header.h, L5Task5imp.cpp, L5Task5main.cpp]

    Задание 6:

    Выполнить: Задан целочисленный вектор A размера N. Вектор содержит по крайней мере одно четное значение. Найдите первое и последнее четные значения вектора. Для выполнения задачи создайте функцию getFirstAndLastEvenNumber().

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

    Указание 2: Помимо функции getFirstAndLastEvenNumber(), вам следует создать еще одну функцию логического типа, чтобы проверить, является ли число четным:

    inline bool isEven(int n)
    {
        // ...
    }

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

    задание 6
    введите N:
    >>> 5
    вектор:
    2  12  1  16  3
    первый: 2, последний: 16
    

    [Solution and Project name: Lesson_5
    you can have individual files: L5Task6header.h, L5Task6imp.cpp, L5Task6main.cpp]

    Задание 7:

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

    Указание 1: Чтобы сравнить два значения, вам следует использовать стандартную функцию max:

    int maximum = std::max(a, b);

    Указание 2: Следует проверить, равен ли размер векторов, для этого нежно использовать функцию assert():

     assert((a.size() == b.size()) && (c.size() == a.size()));

    Указание 4: Следует использовать следующую сигнатуру функции:

    void maxInVectors(std::vector<int> &a, std::vector<int> &b, std::vector<int> &c)
    {
    // тело функции
    }

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

    задание 7
    вектор A:
    0 1 2 3 4 42 6 7 8 422
    вектор B:
    9 8 7 6 5 4 3 2 1 0
    вектор C:
    9 8 7 6 5 42 6 7 8 422
    

    [Solution and Project name: Lesson_5
    you can have individual files: L5Task7header.h, L5Task7imp.cpp, L5Task7main.cpp]

    Extra tasks

    *Доп. задание 1:

    Выполнить: Задан целочисленный вектор А. Необходимо создать вектор B того же размера и заполнить всеми числами из вектора, среди цифр которых есть цифра 2 в десятичной системе счисления.

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

    bool hasDigit (int n, int digit);

    Указание 2: Необходимо проверить, равны ли размеры векторов (используя функцию assert()).

    Указание 4: Следует использовать метод push_back(value), чтобы добавить новое значение в вектор. Метод добавляет новый элемент в конец вектора, например:

    b.push_back(x);

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

    задание доп.1
    вектор A:
    0 1 2 3 4 42 6 7 8 422
    вектор B:
    2 42 422
    

    [Solution and Project name: Lesson_5
    you can have individual files: L5ExTask1header.h, L5ExTask1imp.cpp, L5ExTask1main.cpp]

    *Доп. задание 2:

    Выполнить: Задан целочисленный вектор. Выведите наибольшую длину последовательности из нулей.

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

    задание доп.2
    вектор:
    0 1 0 0 0 42 6 7 8 422
    максимальная длина нулей: 3
    

    [Solution and Project name: Lesson_5
    you can have individual files: L5ExTask2header.h, L5ExTask2imp.cpp, L5ExTask2main.cpp]

    *Доп. задание 3:

    Выполнить: Удалите все нечетные элементы из вектора, сместив оставшиеся элементы.

    Указание 1: Необходимо использовать стандартный метод remove_if(), который удаляет элементы из вектора:

    auto newEnd = std::remove_if(a.begin(), a.end(), [](int n)
    	{
    		return // some test;
    	});

    Указание 2: To delete the elements from the vector you can use the erase() standard method.

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

    task 8
    before task:
    0 1 2 3 4 42 6 7 8 422
    after task:
    0 2 4 42 6 8 422
    

    [Solution and Project name: Lesson_5
    you can have individual files: L5ExTask3header.h, L5ExTask3imp.cpp, L5ExTask3main.cpp]

    *Доп. задание 4:

    Выполнить: Задан вектор размера N и целое число K (0 ≤ K < N). Удалите элемент с индексом K из вектора, сдвинув все следующие элементы на одну позицию влево и уменьшив размер вектора на единицу.

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

    if(index < 0 || index > size)
            throw "Неверный индекс!";

    Указание 2: Чтобы удалить элементы из вектора, нельзя использовать стандартный метод erase().

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

    Задание 9
    введите индекс для удаления
    >>> 2
    до:
    1 2 3 4 5 6 7 8 
    после:
    1 2 4 5 6 7 8
    

    [Solution and Project name: Lesson_5
    you can have individual files: L5ExTask4header.h, L5ExTask4imp.cpp, L5ExTask4main.cpp]

    Задания для самостоятельного выполнения:

    Указания: задания следует сохранить в файле с именем задания, и обязательно в коде вставить комментарий с постановкой задачи.

    Array
    Массивы
    Array3. Дано целое число N (> 1), а также первый член A и разность D арифметической прогрессии. Сформировать и вывести массив размера N, содержащий N первых членов данной прогрессии:

    A,    A + D,    A + 2·D,    A + 3·D,    … 
    Примерный вывод:

    Array14. Дан массив A размера N. Вывести вначале его элементы с четными номерами (в порядке возрастания номеров), а затем — элементы с нечетными номерами (также в порядке возрастания номеров). Условный оператор не использовать.

    A2,    A4,    A6,    …,    A1,    A3,    A5,    … 
    Примерный вывод:

    Array27. Дан массив ненулевых целых чисел размера N. Проверить, чередуются ли в нем положительные и отрицательные числа. Если чередуются, то вывести 0, если нет, то вывести порядковый номер первого элемента, нарушающего закономерность.

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

    Векторы:
    Array58. Дан массив (вектор) A размера N. Сформировать новый массив (вектор) B того же размера по следующему правилу: элемент BK равен сумме элементов массива A с номерами от 1 до K.

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

    Array93. Дан целочисленный массив (вектор) размера N (> 2). Удалить из массива все элементы с четными номерами (2, 4, …). Условный оператор не использовать.

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

    Алгоритмы работы с массивами:

    Algo1. Напишите программу, в которой сортировка выполняется «методом камня» – самый «тяжёлый» элемент опускается в конец массива.

    Algo2. Напишите программу, которая сортирует массив по убыванию суммы цифр числа. Используйте функцию, которая определяет сумму цифр числа.

    Algo3. Массив содержит четное количество элементов. Напишите программу, которая сортирует первую половину массива по возрастанию, а вторую – по убыванию. Каждый элемент должен остаться в «своей» половине. Выполнить задание методом сортировки Вставка.

    Пример:

    Массив:
    5 3 4 2 1 6 3 2
    После сортировки:
    2 3 4 5 6 3 2 1

    Algo4. Заполнить массив случайными числами и отсортировать его. Ввести число X. Используя двоичный поиск, определить, есть ли в массиве число, равное X. Подсчитать количество сравнений.
    Пример:

    Массив:
    1 4 7 3 9 2 4 5 2
    После сортировки:
    1 2 2 3 4 4 5 7 9
    Введите число X:
    2
    Число 2 найдено.
    Количество сравнений: 2

    Algo5. Заполнить массив случайными числами и ввести число и отсортировать его. Ввести число X. Используя двоичный поиск, определить, есть ли в массиве число, равное X. Если такого числа нет, вывести число, ближайшее к X.
    Пример:

    Массив:
    1 4 7 3 9 2 4 5 2
    После сортировки:
    1 2 2 3 4 4 5 12 19
    Введите число X:
    12
    Число 12 найдено. 
    Пример:
    Массив:
    1 4 7 3 9 2 4 5 2
    После сортировки:
    1 2 2 3 4 4 5 12 19
    Введите число X:
    11
    Число 11 не найдено. Ближайшее число 12.