Форум программистов, компьютерный форум CyberForum.ru

Понять указатели:) - C++

Восстановить пароль Регистрация
 
 
Рейтинг: Рейтинг темы: голосов - 50, средняя оценка - 4.64
Svid
 Аватар для Svid
5 / 5 / 1
Регистрация: 02.03.2011
Сообщений: 40
10.03.2011, 13:35     Понять указатели:) #1
Добрый день!
Совсем недавно начал изучать С++, в основном по методичке, выданной в ВУЗе и по нескольким книгам (Страуструп Б., Стефан Дэвис) и плюс то, что нахожу в интернете. Практически, всё это привязано к ряду лабораторных и контрольных, которые необходимо сделать.
Дошел до указателей....О_о. Проблема вот в чем: те практические примеры, которые показывают использование указателей, не отвечают на вопрос - зачем? То есть, все что сделано в этих примерах с помощью указателей, можно сделать и без них, и нельзя сказать, что это будет сложнее. Из-за этого очень трудно понять смысл указателей и они для меня пока что пустая абстракция.
Есть ли какие-нибудь простые практические примеры, показывающие... почему без указателей не обойтись? Например, очень легко практически объяснить суть цикла или там функции.
После регистрации реклама в сообщениях будет скрыта и будут доступны все возможности форума.
dimon1984
40 / 38 / 0
Регистрация: 22.01.2011
Сообщений: 670
10.03.2011, 19:00     Понять указатели:) #21
так же дополню
C++
1
2
3
4
5
6
7
8
9
void real_swap(int *a, int *b)
{
    int temp = *a;
    *a = *b;
    *b = temp;
 
    printf("Реальный обмен местами:\ta = %d\tb = %d\n", *a, *b);
}
... real_swap(&a, &b);
Здесь указатели как бы указавают, где находятся переменные в памяти, чтобы можно было их изменить.
C++
1
2
3
4
5
6
7
8
void image_swap(int a, int b)
{
    int temp = a;
    a = b;
    b = temp;
 
    printf("Мнимый обмен местами:\ta = %d\tb = %d\n", a, b);
}
а здесь a,b в функции это не те же самые переменные.
Реальные переменные a,b из main соприкасаются с функцией в момент вызова функции
image_swap(a, b);
при этом создаётся копия значений a,b , и в функции уже совсем другие переменные, которые уничтожатся при выходе из функции, а про реальные переменные эта функция и не знает, где они находятся. Поэтому и не может их изменить.
После регистрации реклама в сообщениях будет скрыта и будут доступны все возможности форума.
silent_1991
Эксперт C++
4938 / 3014 / 149
Регистрация: 11.11.2009
Сообщений: 7,024
Завершенные тесты: 1
10.03.2011, 19:06     Понять указатели:) #22
dimon1984, перечитайте пятое и вторую половину двенадцатого сообщения в этой теме, и тогда, возможно, перестанете повторять то, что уже не один раз было разжёвано и на что ТС отозвался вполне удовлетворительным ответом "вроде понял".
talis
 Аватар для talis
789 / 541 / 37
Регистрация: 11.05.2010
Сообщений: 1,298
Записей в блоге: 1
10.03.2011, 19:19     Понять указатели:) #23
Сообщение было отмечено автором темы, экспертом или модератором как ответ
Svid, попробую вербализовать некоторые аспекты в менее гуманитарной манере Для начала - думайте об указателях, как об адресах памяти. То есть:

C++
1
2
3
4
5
int var = 18; //допустим, лежит по адресу 0x0022ff74
int * ptr; //указатель
ptr = &var; // ptr = 0x0022ff74
 
cout << ptr << ": " << *ptr << endl;
Вывод:
Код
0x22ff74: 18
Амперсанд (&) - это операция взятия адреса; Вы присваиваете АДРЕС переменной var, а не её значение. Звёздочка перед именем указателя ( *ptr ) - это операция разыменовывания указателя, то есть вы берёте не значение указателя (то есть адрес), а значение, лежащее по этому адресу.

Теперь о практике: представьте себе, что вам нужно написать функцию, которая выводит массив int, но вы заранее не знаете, массив какого размера ей передадут. Это могут быть 2 int'а, а может быть десь-тыщ. В этом случае проще сделать так:

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
void int_out( int * array, int size )
{
   for( int i = 0; i < size; i++ )
      cout << array[i] << "; " << endl;
 
   cout << "=============\nTotal " << size << " numbers" << endl;
}
 
int main()
{
   int var = 18; 
   int test[15] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 };
 
   int_out( &var, 1 ); //передаём адрес var
   cout << endl;
   int_out( test, 15 ); //массивы всегда передаются по указателю; операция взятия адреса не требуется
 
   return 0;
}
Теперь вопрос: вы задумывались, почему в C и C++ нумерация элементов массива идёт с нуля, а не с единицы? Смотрите, вот так выглядит массив в памяти:

00 AF 14 28 A8 76 42 00 00 00 01 00 00 00 02 00 00 04 A1 00 00 A7 70 21 17 86 61 0A

При синтаксисе var[5] берётся адрес начала массива var и к нему прибавляется ( размер_элемента_массива_var * 5 ). То есть нулевой элемент массива int будет равен ( адрес_начала + (4 * 0) = адрес_начала ), первый - ( адрес_начала + (4 * 1) = адрес_начала + 4 ) и так далее. Вы в любом случае работаете с адресами, указатели просто позволяют это делать более просто.

Теперь на счёт ссылки. По сути, ссылка - это константный указатель на неконстантные данные, то есть вы можете изменить данные по ссылке, но не можете изменить адрес, на который она указывает.

Теперь на счёт синтаксиса printf():

C++
1
2
3
4
5
int var = 18;
   
// сначала функции передаётся значение переменной var,
// а затем её адрес
printf( "var = %i\naddress = 0x%x\n", var, &var );
Всё очень просто, если всё понимать.

Другой пример: вам нужно передать функции структуру, которая занимает в памяти 1 килобайт (ну, жирная такая структура). Вы можете либо это запихать целиком и подавиться, а можете вместо 1 килобайта записать 4 байта адреса этой структуры и на этом успокоиться. Что проще будет?

Если что - спрашивайте.

Добавлено через 1 минуту

Не по теме:

О, пока я писал, тема развилась... Если что - простите за повторения, ещё не ознакомился

Svid
 Аватар для Svid
5 / 5 / 1
Регистрация: 02.03.2011
Сообщений: 40
10.03.2011, 19:24  [ТС]     Понять указатели:) #24
Да нет, я не
упрямо не желаем согласиться
, я и как раз и хочу понять.
Как объяснить, начинаешь изучать С++ - все понятно и последовательно, переменные,циклы, функции - есть какая-то задача, которая реализовывается с помощью цикла и т.д. А с указателями, такое впечатление, что их понятие вводится намного раньше того, когда их использование становится необходимым.
talis
 Аватар для talis
789 / 541 / 37
Регистрация: 11.05.2010
Сообщений: 1,298
Записей в блоге: 1
10.03.2011, 19:25     Понять указатели:) #25
Возможно, так оно и есть в вашей книжке. Но ведь указатели-то от этого своего значения не теряют Постарайтесь разобраться.
silent_1991
Эксперт C++
4938 / 3014 / 149
Регистрация: 11.11.2009
Сообщений: 7,024
Завершенные тесты: 1
10.03.2011, 19:32     Понять указатели:) #26
Циклы и функции - это понятия, которые позволяют решать задачи (условный переход, как мы помним, совершил революцию в развитии вычислительной техники). Указатели - инструмент, который помогает их решать. Во многих языках нету указателей, поскольку там это не так необходимо. Поэтому указатели можно, по сути, вводить в любом месте материала, и везде они на первых парах будут одинаково непонятны.
Svid
 Аватар для Svid
5 / 5 / 1
Регистрация: 02.03.2011
Сообщений: 40
10.03.2011, 19:46  [ТС]     Понять указатели:) #27
Спасибо, talis!
Стало намного понятней, особенно с функцией вывода массива.
Я еще усвою, что Вы написали по поводу того, как выглядит массив в памяти, и думаю, все встанет на свои места.
В методичке, блин, всего этого нет - там указатели появляются как сама собой разумеющаяся вещь
talis
 Аватар для talis
789 / 541 / 37
Регистрация: 11.05.2010
Сообщений: 1,298
Записей в блоге: 1
10.03.2011, 20:08     Понять указатели:) #28
Не за что, Svid. Главное запомните: указатели - это точно такие же переменные, как и char, int, float, double и технически от них не отличаются ровно ничем. Более того, это самые обычные числа, то есть их можно (хотя и не нужно) складывать и вычитать. Иногда даже для перехода на следующий элемент массива используется инкремент указателя - это не всегда хорошая идея, но иногда так делают, и ничего плохого в этом нет, разве что тут выше риск сделать первоклассную утечку памяти. Единственный нюанс - тут значение указателя увеличивается не на 1, а на ( 1 * размер_элемента_массива ), то есть, в случае с int - сразу на 4.

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
int main()
{
   int array[18];
   
   
   // arr = адрес начала массива. Пока arr != адресу последнего элемента + 1 (аккурат конец массива)
   for( int * arr = &array[0]; arr != &array[19]; arr++ )
      *arr = 12;
      
   // оно же:
   //for( int i = 0; i < 18; i++ )
   //   arr[i] = 12;
   
   
   
   
   for( int * arr = &array[0]; arr != &array[18]; arr++ )
      cout <<  *arr << "; ";
   
   // оно же:
   //for( int i = 0; i < 18; i++ )
   //   cout << arr[i] << "; ";
   
   cout << endl;
   system("pause > nul");
   
   return 0;
}
У неё тоже есть свои плюсы.
Feax
9 / 9 / 2
Регистрация: 04.03.2010
Сообщений: 40
11.03.2011, 07:34     Понять указатели:) #29
talis, спасибо за развернутый ответ. Теперь еще понятнее стало Я так понял, что при создании объекта без использования new, объект помещается в кучу, а в стек только указатель на адрес в куче. В таком случае, какие плюсы создания объекта без new? Получается, что структуры, объекты классов лучше создавать в динамической памяти. А тогда почему в книжках ( по Qt например ) создаются часть экземпляров класса без new, а часть с new?
Например, GUI элементы, объект приложения ( QAplication ) в книге создаются без new.
instagib
122 / 85 / 3
Регистрация: 14.02.2011
Сообщений: 341
11.03.2011, 17:21     Понять указатели:) #30
хоть тему я не открывал, но благодаря Вам всем. я тоже усек что такое указатели и как их использовать . спс удачи всем!
п.с. поставил закладку..если что еще раз прочту
talis
 Аватар для talis
789 / 541 / 37
Регистрация: 11.05.2010
Сообщений: 1,298
Записей в блоге: 1
12.03.2011, 13:03     Понять указатели:) #31
Сообщение было отмечено автором темы, экспертом или модератором как ответ
Feax, для ответа на ваш вопрос нужно объяснить, что такое пространство имён.

Запустите этот пример:

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#include <cstdlib>
#include <iostream>
 
using namespace std;
 
int main(int argc, char *argv[])
{
    int var = 18;
    
    cout << "main() var = " << var << endl;
    
    
    if( 1 == 1 ) //всегда истинно
    {
        int var = 24;
        
        cout << "   if var = " << var << endl;
    }
    
    cout << "main() var = " << var << endl;
    
    
    
    {
         int var = 36;
         cout << "   another var = " << var << endl;
    }
    
    
    {
         cout << "var = " << var << endl;
    }
    
    cout << "main() var = " << var << endl;
     
        
    system("pause > nul");
    return 0;
}
Обратите внимание, что int var, созданный непосредственно в операторных скобках main() отличается от int var, созданного в операторных скобках if. Так же на строчке 24 и 30 есть отдельно стоящие операторные скобки, которые не привязаны ни к функции, ни к оператору if, while, for, switch и так далее. Созданная в них int var тоже отличается от int var функции main().

Теперь давайте всё назовём своими именами. Часть кода между операторными скобками называется пространством имён. Подумайте над названием, оно вам многое скажет. У функции main() есть своё пространство имён (тело функции), в нём есть вложенные пространства имён - у оператора if и отдельно стоящее на строчках 24 и 30. int var, объявленная внутри пространств имён if и на строчке 24, перекрывает int var функции main(), но не удаляет её. Они существуют только внутри своих пространств имён и уничтожаются при выходе из них. Теперь посмотрите на пространство имён строчки 30. В нём не объявлена своя int var, поэтому берётся int var из родительского пространства имён, если такая существует. Если нет - просматривается пространство имён ещё более высокого уровня, и так, пока мы не дойдём до глобального. Разумеется, это делается на этапе компиляции и никак не влияет на производительность программы.

Теперь на счёт вашего вопроса. Если вы хотите, чтобы объект существовал после выхода из пространства имён, в котором он создан, создавайте его динамически (new (C++) или malloc (C), или аналогичные). То есть на этапе выполнения программы, память выделяется из кучи (heap). Если он вам нужен только внутри этого пространства имён, и не требуется после него, создавайте его как обычно. Вот пример:

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#include <cstdlib>
#include <iostream>
 
using namespace std;
 
char * text = 0; //В глобальном пространстве имён.
                 //ВСЕГДА инициализируйте указатели нулём.
 
void allocText()
{
     text = new char[ 256 ];
     cout << "> ";
     cin.getline( text, 256 ); //ввести текст
     
     // выделенная память будет существовать и после выхода из allocText().
}
 
void coutText()
{
    if( text == 0 )
       cout << "text does not exist\n"; //в данный момент text не существует
    else
       cout << "text: " << text << endl;
}
 
int main(int argc, char *argv[])
{
    coutText();
    
    allocText();
    
    coutText();
    
    // удалить text
    delete [] text;
    text = 0; // после удаления ВСЕГДА обнуляйте указатель
    cout << "[deleted]\n";
    
    coutText();
        
    system("pause > nul");
    return 0;
}
Здесь память выделяется динамически функцией allocText(). Ей же заполняется чем-то вменяемым. coutText() выводит содержимое этой памяти, находя нужный участок памяти через char * text. Если text = 0 (нулевой указатель), то память ещё (или уже) не выделена. Всем этим функциям буфер доступен при помощи указателя, который находится в родительском пространстве имён (глобальном в данном случае, хотя это и не обязательно). Тут есть по крайней мере три замечания:

1) Всегда удаляйте динамически выделенную память сами, за вас это никто не сделает. Иначе будут утечки памяти. Вспомните патч к вашей любимой игрушке, где сказано "увеличена производительность за счёт исправления множественных утечек памяти", после которого игрушка стала жрать на 300 метров оперативки меньше. Вот это оно.

2) Если вы случайно удалите указатель (например, при выходе из его пространства имён), вы потеряете буфер. Вы уже никогда не узнаете, в каком участке памяти лежит ваша драгоценная информация. Ну и, опять же, сделаете утечку памяти.

3) Если память не выделена, предназначенный для неё указатель ВСЕГДА должен быть равен нулю. Во-первых, так вы всегда сможете сказать, выделили ли вы память под что-то или нет. Во-вторых, в случае ошибки, операционка отстрелит вашу программу (Инструкция по адресу "0x0022ff77" обратилась к памяти по адресу "0x00000000". Память не может быть read. Ну или written. Помните? ). Подобная ошибка происходит, когда вы пытаетесь обратиться к участку памяти, не доступному вашей программе. Если в указателе будет адрес, который доступен вашей программе, вы можете затереть свою собственную память. Нулевой адрес никогда вам доступен не будет.
Feax
9 / 9 / 2
Регистрация: 04.03.2010
Сообщений: 40
12.03.2011, 13:24     Понять указатели:) #32
talis, большое спасибо за разъяснение. Очень понятно объясняешь С пространством имен был знаком до этого, но возможность использования фигурных скобок для создания нового пространства имен, для меня новость. За это отдельное спасибо
Не понятно только, почему в книжках не говорят про эту особенность указателей.Про замечания очень интересно, также как и интересно все, чего не знал раньше
С++ мне с каждым днем все больше нравится, особенно в сравнении с Delphi.
P.S.: думаю тема скоро перерастет из "Понять указатели" в "Понять С++"
Temirlan90
 Аватар для Temirlan90
131 / 131 / 8
Регистрация: 30.09.2010
Сообщений: 333
12.03.2011, 21:57     Понять указатели:) #33
1) Всегда удаляйте динамически выделенную память сами, за вас это никто не сделает. Иначе будут утечки памяти. Вспомните патч к вашей любимой игрушке, где сказано "увеличена производительность за счёт исправления множественных утечек памяти", после которого игрушка стала жрать на 300 метров оперативки меньше. Вот это оно.
talis, Типа в памяти будут хранится лишние данные?
silent_1991
Эксперт C++
4938 / 3014 / 149
Регистрация: 11.11.2009
Сообщений: 7,024
Завершенные тесты: 1
12.03.2011, 21:59     Понять указатели:) #34
Temirlan90, типа система не будет знать, что память приложению уже не нужна и что её теперь можно отдать под более актуальные нужды.
Temirlan90
 Аватар для Temirlan90
131 / 131 / 8
Регистрация: 30.09.2010
Сообщений: 333
12.03.2011, 22:04     Понять указатели:) #35
И еще вопрос, в Java и C# это вроде делается на автомате? Я про удаления динамической памяти.

Добавлено через 2 минуты
silent_1991, и в Вашем примере вы показали, что в функцию копируются лишние данные?
silent_1991
Эксперт C++
4938 / 3014 / 149
Регистрация: 11.11.2009
Сообщений: 7,024
Завершенные тесты: 1
12.03.2011, 22:08     Понять указатели:) #36
Вряд-ли вы имеете ввиду мой пример, примеры в основном тут показывал tails.
И да, в Яве и шарпе мусор собирается автоматически.
Temirlan90
 Аватар для Temirlan90
131 / 131 / 8
Регистрация: 30.09.2010
Сообщений: 333
12.03.2011, 22:13     Понять указатели:) #37
silent_1991, Я про Real_swap и Image_swap это ведь ваш пример =) В этом исходнике как Я понял, там копируются не нужные данные в функции Image_swap?
Яве и шарпе мусор собирается автоматически
Это лучше или хуже? (Простите за глупый вопрос)
silent_1991
Эксперт C++
4938 / 3014 / 149
Регистрация: 11.11.2009
Сообщений: 7,024
Завершенные тесты: 1
12.03.2011, 22:16     Понять указатели:) #38
Нет, там вообще никакой речи об утечке памяти нету. Там просто было показано, как могут быть использованы указатели.

Добавлено через 2 минуты
Цитата Сообщение от Temirlan90 Посмотреть сообщение
Это лучше или хуже?
Ну, с одной стороны лучше, поскольку вы можете систематически забывать очищать память, и утечки станут критическими. А с другой, когда вы делаете это вручную, то осуществляете более строгий и точный контроль за объектами (можете в любое время удалить объект, когда он станет ненужным). Так что у всего есть и плюсы, и минусы.
Temirlan90
 Аватар для Temirlan90
131 / 131 / 8
Регистрация: 30.09.2010
Сообщений: 333
12.03.2011, 22:18     Понять указатели:) #39
C
1
2
3
4
5
6
7
8
void image_swap(int a, int b)
{
    int temp = a;
    a = b;
    b = temp;
 
    printf("In image swap:  \ta = %d\tb = %d\n", a, b);
}
Нет, там вообще никакой речи об утечке памяти нету. Там просто было показано, как могут быть использованы указатели.
Я не про утечку говорю. Вот в этой функции ведь создаются 2 новые переменные? Которые как Я понял Нам не нужны...
MoreAnswers
Эксперт
37091 / 29110 / 5898
Регистрация: 17.06.2006
Сообщений: 43,301
12.03.2011, 22:21     Понять указатели:)
Еще ссылки по теме:

Стек на основе массива структур - эт как понять читаю литературу и не могу понять! C++
C++ Написать программу сортировки через указатели на указатели
C++ Символьные литералы, указатели и функция. Не могу понять, почему именно так

Искать еще темы с ответами

Или воспользуйтесь поиском по форуму:
silent_1991
Эксперт C++
4938 / 3014 / 149
Регистрация: 11.11.2009
Сообщений: 7,024
Завершенные тесты: 1
12.03.2011, 22:21     Понять указатели:) #40
С чего вы взяли? Что в этой функции, что в real_swap, создаются три переменные, только в данном случае это переменные типа int (a, b, temp), а в случае real_swap - два указателя на int и одна int (тоже a, b (указатели) и temp).
Yandex
Объявления
12.03.2011, 22:21     Понять указатели:)
Ответ Создать тему
Опции темы

Текущее время: 04:27. Часовой пояс GMT +3.
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin® Version 3.8.9
Copyright ©2000 - 2016, vBulletin Solutions, Inc.
Рейтинг@Mail.ru