Вторник, 16.04.2024, 09:12
Хранилще данных
Приветствую Вас Гость | RSS
Главная | Каталог статей | Регистрация | Вход
Меню сайта

Категории раздела
Статьи о программировании [4]
Игры [0]
Новости техники и IT [0]

Статистика

Онлайн всего: 1
Гостей: 1
Пользователей: 0

Главная » Статьи » Статьи о программировании

Указатели C++ для чайников Часть 2
 

3. Динамическое выделение памяти


Вот та замечательная плюшка, из-за которой я использую указатели. Начнем с динамических массивов. Зачастую при решении какой-либо задачи возникает потребность в использовании массива неопределенного размера, то есть размер этот заранее неизвестен. Здесь нам на помощь приходят динамические массивы — память под них выделяется в процессе выполнения программы. Пример:
 
int size = -1;
// здесь происходят какие - то 
// действия, которые изменяют
// значение переменной size
int* dyn_arr = new int[size];

Что здесь происходит: мы объявляем указатель и инициализируем его началом массива, под который выделяется память оператором new на sizeэлементов. Следует заметить, что в этом случае мы можем использовать те же приемы в работе с указателями, что и с статическим массивом. Что следует из этого извлечь — если вам нужна какая — то структура (как массив, например), но ее размер вам заранее неизвестен, то просто сделайте объявление этой структуры, а проинициализируете ее уж позже. Более полный пример приведу чуть позже, а пока что — рассмотрим двойные указатели.

Что такое указатель на указатель? Это та же переменная, которая хранит адрес другого указателя „более низкого порядка“. Зачем он нужен? Для инициализации двумерного динамического массива, например:

 
const int size = 7;
// двумерный массив размером 7x7
int** i_arr = new int*[size];
for(int i = 0; i != size; i++){
 i_arr[i] = new int[size];
}

А тройной указатель? Трехмерный динамический массив. Неинтересно, скажите вы, так можно продолжать до бесконечности. Ну хорошо. Тогда давайте представим себе ситуацию, когда нам нужно разместить динамические объекты какого-нибудь класса MyClass в двумерном динамическом массиве. Как это выглядит (пример иллюстрирует исключительно использование указателей, приведенный в примере класс никакой смысловой нагрузки не несет):
 
class MyClass{
 public:
 int a;
 public:
 MyClass(int v){ this->a = v; };
 ~MyClass(){};
};

void main(){
 MyClass*** v = new MyClass**[7];
 for (int i = 0; i != 7; i++){
 v[i] = new MyClass*[3];
 for (int j = 0; j != 3; j++){
 v[i][j] = new MyClass(i*j);
 }
 }
}
Здесь два указателя нужны для формирования матрицы, в которой будут располагаться объекты, третий — собственно для размещения там динамических объектов (не MyClass a, а MyClass* a). Это не единственный пример использования указателей такого рода, чуть ниже будут рассмотрены еще примеры.
 

4. Указатель как аргумент функции


Для начала создадим два динамических массива размером 4x4 и проинициализируем их элементы некоторыми значениями:
 
void f1(int**, int);
void main(){
 const int size = 4;
 // объявление и выделение памяти 
 // под другие указатели
 int** a = new int*[size];
 int** b = new int*[size];
 // выделение памяти под числовые значения
 for (int i = 0; i != size; i++){
 a[i] = new int[size]; 
 b[i] = new int[size];
 // собственно инициализация
 for (int j = 0; j != size; j++){
 a[i][j] = i * j + 1;
 b[i][j] = i * j - 1;
 }
 }
}
void f1(int** a, int c){
 for (int i = 0; i != c; i++){
 for (int j = 0; j != c; j++){
 cout.width(3);
 cout << a[i][j];
 }
 cout << endl;
 }
 cout << endl;
}

Функция f1 выводит значения массивов на экран: первый ее аргумент указатель на двумерный массив, второй — его размерность (указывается одно значение, потому как мы условились для простоты работать с массивами, где количество строк совпадает с количеством столбцов).

Задача: заменить значения элементов массива a соответствующими элементами из массива b, учитывая, что это должно произойти в некоторой функции, которая так или иначе занимается обработкой массивов. Цель: разобраться в способе передачи указателей для их дальнейшей модификации.
  1. Вариант первый. Передаем собственно указатели a и b в качестве параметров функции:
     
    void f2(int** a, int** b, int c){
     for (int i = 0; i != c; i++){
     for (int j = 0; j != c; j++){
     a[i][j] = b[i][j];
     }
     }
    }
    
    После вызова данной функции в теле main — f2(a, b, 4) содержимое массивов a и b станет одинаковым.
  2. Вариант второй. Заменить значение указателя: просто присвоить значение указателя b указателю a.
     
    void main(){
     const int size = 4;
     // объявление и выделение памяти 
     // под другие указатели
     int** a = new int*[size];
     int** b = new int*[size];
     // выделение памяти под числовые значения
     for (int i = 0; i != size; i++){
     a[i] = new int[size]; 
     b[i] = new int[size];
     // собственно инициализация
     for (int j = 0; j != size; j++){
     a[i][j] = i * j + 1;
     b[i][j] = i * j - 1;
     }
     }
     // Здесь это сработает
     a = b;
    }
    

    Однако, нам интересен случай, когда массивы обрабатываются в некоторой функции. Что первое приходит на ум? Передать указатели в качестве параметров нашей функции и там сделать то же самое: присвоить указателю a значение указателя b. То есть реализовать следующую функцию:
     
    void f3(int** a, int** b){
     a = b;
    }
    
    Сработает ли она? Если мы внутри функции f3 вызовем функцию f1(a, 4), то увидим, что значения массива действительно поменялись. НО: если мы посмотрим содержимое массива a в main — то обнаружим обратное — ничего не изменилось. Так в чем же причина? Все предельно просто: в функции f3 мы работали не с самим указателем a, а с его локальной копией! Все изменения, которые произошли в функции f3 — затронули только локальную копию указателя, но никак не сам указатель a. Давайте посмотрим на следующий пример:
     
    void false_eqv(int, int);
    void main(){
     int a = 3, b = 5;
     false_eqv(a, b);
     // Поменялось значение a? 
     // Конечно же, нет
    }
    false_eqv(int a, int b){
     a = b;
    }
    
    Итак, я думаю, вы поняли, к чему я веду. Переменной a нельзя присвоить таким образом значение переменной b — ведь мы передавали их значения напрямую, а не по ссылке. То же самое и с указателями — используя их в качестве аргументов таким образом, мы заведомо лишаем их возможности изменения значения.
    Вариант третий, или работа над ошибками по второму варианту:

     
    void f4(int***, int**);
    void main(){
     const int size = 4;
     int** a = new int*[4];
     int** b = new int*[4];
     for (int i = 0; i != 4; i++){
     a[i] = new int[4]; 
     b[i] = new int[4];
     for (int j = 0; j != 4; j++){
     a[i][j] = i * j + 1;
     b[i][j] = i * j - 1;
     }
     }
     int*** d = &a;
     f4(d, b);
    }
    void f4(int*** a, int** b){
     *a = b;
    }
    

    Таким образом, в main'е мы создаем указатель d на указатель a, и именно его передаем в качестве аргумента в функцию замены. Теперь,разыменовав d внутри f4 и приравняв ему значение указателя b, мы заменили значение настоящего указателя a, а не его локальной копии, на значение указателя b

    Кстати, а чего это мы создаем динамические объекты? Ну ладно размер массива не знали, а экземпляры классов мы зачем динамическими делали? Да потому что зачастую, созданный нами объекты свое — они генерились, порождали новые данные/объекты для дальнейшей работы, а теперь пришло им время...умереть [фу, как грубо] уйти со сцены. И как мы это сделаем? Просто:

     
    delete(a);
    delete(b);
    // Вот и кончились наши двумерные массивы
    delete(v);
    // Вот и нет больше двумерного массива с динамическими объектами
    delete(dyn_array);
    // Вот и удалился одномерный массив
    






  3.  
  4.  
  5.  
  6. На данной ноте я хотел бы закончить свое повествование. Если найдется хотя бы пара ребят, которым понравится стиль изложения материала, то я постараюсь продолжить… ой, да кого я обманываю, мне нужен инвайт и все на этом, дайте инвайт и вашим глазам больше не придется видеть это околесицу. Шучу, конечно. Ругайте, комментируйте.

 

 

http://habrahabr.ru/post/256443/

Категория: Статьи о программировании | Добавил: TypicalUbuntu (23.11.2015)
Просмотров: 624 | Рейтинг: 0.0/0
Всего комментариев: 0
avatar
Вход на сайт

Поиск

Друзья сайта
  • Официальный блог
  • Сообщество uCoz
  • FAQ по системе
  • Инструкции для uCoz

  • Copyright MyCorp © 2024 uCoz