Указатели

Пример

Трябва да си направим функция, която да приема две цели числа - begin и end и да връща масив от числа - вторите степени на числата в интервала.
Как да стане тази работа?
Използвайки само текущите си умения, имаме няколко проблема.
Как да се направи една функция да не връща променлива (int, char …), а масив?
И как да се направи масив с точно (begin - start) елемента, нали досега всички масиви трябваше да бъде предварително казано точно колко елемента да съдържат?

А, много просто, с указатели става. Гледай сега.

$[begin,end)$

#include<iostream>
using namespace std;

int * square_roots(int begin, int end)
{
    int * ints = new int[end-begin];
    for(int x=begin; x<end;x++)
    {
        ints[x] = x*x;
    }
    return ints;
}

int main()
{
    int * a = square_roots(1,10);
    for(int x=0;x<10;x++)
    {
        cout<<*(a + x)<<endl;
    }
    delete a;
    return 0;
}

А, какво?

Този код най-вероятно е изглеждал много апокрифен.
Спокойно, нормално е. Както най-вероятно е станало ясно, магията е в редовете, в които има звездички. Другите са някакви стандартни неща :) Затова има звездички, за да внимаваш като четеш кода.

int * ints = new int[end-begin];

ints е нещо ново, досега не сме пипали такова. Това е указател към тип int. Кръстил съм го ints, но името не е от значение, можех да го кръстя Gosho. Какво прави new и защо му се подават разни работи, ще се изучава по подробно в курса по обектно ориентирано програмиране. Нека първо да представим официално указателя.

Здрасти, аз съм:

Указател / Pointer

Указател / Pointer към променливата A се нарича число, което сочи къде точно в паметта се намира A.
Просто едно число, нищо повече. Но е доста полезно, на практика.
"int * a" означава "Декларирам указател към тип int и той се казва a"
"*a" означава "Това, към което сочи указателят a"
Забележи, че "int * a" НЕ ти прави int, а указател към int. Т.е. ако просто го декларираш, там няма да има int. "int * a = new int" е това, което ти трябва.

Например, ако имаш масив от int-ове и първият му елемент на 19018236-тия байт в паметта, то можеш да намериш къде е вторият.
Тъй като int заема 4 байта, то вторият елемент ще е на: 19018240-тия байт. И така нататък.
В C/C++ има едно улеснение за програмистите. Операцията събиране разботи различно за променливи от тип * (звездичката означава указател).
например:

cout<<*(ints + 2)<<endl; // ще отпечата ints[2]

Тоест, добавя му не 2 байта, а 2 пъти размера на това, към което сочи. Тоест, указател към char ще добавя 1 байт, указател към double ще добавя 8 байта, а указател към структурата, която сам си си направил и заема 2 мегабайта(браво на теб), ще добавя 2 мегабайта. Но да не избързваме.

Как това ни помага?

Гледал ли си матрицата? Оня момент, в който Нео започна да изкривява пространството насам-натам и да вижда нули и единици навсякъде? Е, той използваше указатели.
Ок, ок, тук няма ефекти за сто милиона долара, но все пак точно това се прави. Ако имаш указател към дадена променлива, имаш променливата. Тя може да е използвана в някаква функция на другия край на програмата и ти да не си я чувал въобще. Но, ако в някой момент получиш указател към нея, имаш я.
Виж примера. Деклрариам си указател към масив от int-ове, с (begin - end) елемента.

int * ints = new int[end-begin];

Правя с него разни неща и после връщам само указателя. Програмата ми след това ползва целия масив. Супер.
Има и много по-сложни употреби на указателите, но за сега това ти стига. Хайде, Нео също не овладя силите си веднага.

Предупреждение

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

cout<<*(a + 200)<<endl;

Нищо. Абсолютно нищо.
"Ама там няма масив, той е само 10 елемента?" Е и?
"Ама там не знаеш какво има, може да е число, може да забие програмата, да е код на някакъв вирус…!" МДа. Всъщност, повечето пъти, когато програмата, която ползваш забие, тя е забила точно защото някой програмист е направил някаква простотия с указатели.
А простотии с тях може много да се правят. Това е най-голямата сила на C/C++. В почти всички други езици просто нямаш възможност да пипаш указатели. Ако се опиташ да излезеш извън масив - карат ти се и ти спират програмата. А тук ще излезеш, ще си свършиш работата и може даже да не си усетил какво си затрил в процеса.
Е, вярно, че всички наистина сложни програми имат нужда от средство като указателите, но защо да се занимаваш с такива трудни неща? Така де, някои биха казали, че не си истински програмист докато не разбереш указателите, но хайде, винаги можеш да пишеш програми на Visual Basic?

Ако все пак решиш да се занимаваш с указатели знай, че:
С голямата сила идва голяма отговорст
И сега, след като вече те оприличих на Нео и Спайдърмен едновременно, време е да се пробваш да напишеш една програмка сам.

Обща култура

В C/C++ абсолютно всичко е указател. Знам, знам, досега си си мислел, че има такива неща като int, char, double … Позволи ми да ти разбия съзнанието. Всичко, което някога си ползвал, е лъжа. Това са просто битове в паметта и единствената причина, поради която програмата ти третира едни битове различно от други битове, е че към тях сочат указатели от различен тип. Ако, например имаш следното:

int a = 5;
int * b = &a;
float * c = (float *)&a;
cout<< b <<endl;    //И двата реда ще отпечатат едно и също число
cout<< c <<endl;    //Това е позицията в паметта, към която сочат
cout<< *b <<endl;    //Ще отпечата 5 (битовете му са 00000000000000000000000000000101)
cout<< *c <<endl;    //Ще отпечата някаква глупост. Така де, каквото и да означават битовете 00000000000000000000000000000101, когато се подадат на float, ще отпечата това

Ако не разбираш какво означава &a, не питай. Повярвай ми, в момента не искаш да знаеш :) Просто така се прави тази работа, после ще ти обясня.

Така де, светът ти е лъжа. Всичко са указатели. Всъщност, ти и досега си ги използваше, просто не ти беше ясно, че го правиш :)
Например, ти пишеш:

int a[3] = {1,2,3};
cout << a[2] <<endl;

А като компилираш, програмата ти всъщност променя последния ред така:
cout << *(a + 2) <<endl;

Да си виждал нещо подобно наскоро? Мхм, указатели. Ако си внимавал, сега ще се сетиш, че следващите два реда са абсолютно еквивалентни:
cout << a[2] <<endl;
cout << 2[a] <<endl;

Макар че второто е забележимо по-грозно :)

Задача

Да се състави програма, която по даден указател към масив от тип int и число - размера на този масив, да намира сумата на числата в него.

int find_sum(int * array, int size)
{
    ...
}

Успех!

И спокойно, когато става дума за указатели, трябва да помниш следното:

Всички бъркат указателите първия път

Повечето хора ги бъркат още 83915 пъти докато ги научат.

Unless otherwise stated, the content of this page is licensed under Creative Commons Attribution-NonCommercial-ShareAlike 3.0 License