В предыдущей части я рассказал вам об основах указателей в С/С++. Поскольку работа с памятью немного отличается в С++(там она упрощена), эта статья будет ориентирована именно на С. Чистый С сложнее, но если его понять, С++ покажется легким. В конце статьи, я коротко расскажу о работе с памятью в С++. И так сегодня мы узнаем:
- Что общего у массивов и указателей;
- Как выделять память;
- Что из себя представляют строки;
Кому интересно, прошу под спойлер!
И так. Для начала узнаем, что же такое массив. Вот небольшой код:
#include <stdio.h> void main() { int array[3]; //массив из трех элементов array[0] = 0; array[1] = 1; array[2] = 2; printf("%p, %irn", array, *array); }
Странный код, правда? Создается массив. Потом мы его пытаемся вывести на экран как указатель(wtf?). Мало того, получаем значение массива, как будто он указатель. Бред? А вот и нет! Массив и есть указатель. Почти. Дело в том, что написав:
int array[3];
Мы скомандовали компьютеру: «выделить последовательно(один блок за одним) память, для трех элементов типа int, указатель на первый элемент поместить в переменную array«. Теперь попробуем наоборот, работать с указателем как с массивом:
#include <stdio.h> #include <stdlib.h> //для работы с функциями malloc и free void main() { int* pointer = NULL; //создаем указатель на тип int pointer = (int*) malloc(sizeof(int) * 3); //выделяем память для 3 элементов типа int if (pointer) { pointer[0] = 0; //работаем с указателем как с массивом pointer[1] = 1; pointer[2] = 2; int i = 0; for (i = 0; i < 3; ++i) printf("%irn", pointer[i]); } free(pointer); //освобождаем память }
С функциями malloc и free мы разберемся чуть пожже, просто запомните что они работают с памятью(выделяет malloc, free — освобождает). Поскольку указатель указывает(да да, я знаю) на область памяти, то этой области не обязательно должна быть присвоена переменная. То есть, можно как в нашем примере выделить память для указателя, и работать с ней.
Примечание: переменные и массивы при объявлении получают память в стеке, при выделении памяти вручную, блоки создаются в куче. Гугл ваш друг.
Как вы уже знаете, переменная массива тоже указатель(на первый элемент), так что нам мешает создать указатель, выделить блок памяти и присвоить ему значение первого элемента? А ничего. Функция malloc выделает указанный размер памяти в байтах и возвращает указатель на его первый элемент. Поскольку она универсальная, то возвращает указатель типа void*, его нужно явно преобразовать в тип int*. Для того, что бы вычислить необходимое количество байт я использовал оператор sizeof, он возвращает размер переменной или типа. Функция free освобождает память, выделенную под указатель.
Примечание: sizeof вычисляется на этапе компиляции, и если ее применять в указателям(то есть пытаться вычислить динамическую память) она всегда будет возвращать размер указателя.
Важно: всегда освобождайте выделенную память! Думаю все слышали о такой вещи, как «утечка памяти»? Это и есть блоки памяти, которую не освободили. То есть, создался указатель, ему выделился блок памяти(назовем его блок1). Потом указателю выделили другой блок памяти(блок2). В результате этого, блок1 попрежнему воспринимается операционной системой как используемый, но доступа к нему уже нет(указателя нет, ничего на него не указывает). Освободится он лишь в случае закрытия программы или перезагрузки компьютера. Поэтому следуйте правилу: «Использовал указатель? — Освободи память! Присвой указателю NULL!».
Все очень запутанно, поэтому я попробую графически вам показать. Память состоит из бит. 8 бит = 1 байт. Это думаю все знают. Операционная система работает с памятью, как с блоками байт. Не будем заморачиваться размерами, а представим себе что int и int* занимают одинаковый размер блока в памяти.
Примечание: на всех компьютерах реализация размеров int разная. То есть для x86 — один размер, для x64 — другой.
Вот иллюстрация нашей памяти:
Создадим переменную, типа int:
int var1 = 0;
Компьютер выделит память для нашей переменной, и мы получим что-то вроде:
Теперь создадим указатель:
int* pointer1 = NULL;
Получим что-то вроде:
Теперь сделаем так:
pointer1 = &var1;
Как вы уже догадались в указатель pointer1 помещается адрес переменной var1. То есть получается вот так:
Что же происходит с массивами? Объявим массив:
int array1[3];
В результате компьютер выделит память размером в 3 блока. Указатель на первый элемент поместит в переменную array1. Абстрактно получится так:
Блоки памяти для элементов массива выделяются последовательно, указатель на первый элемент помещается в array1. По сути, когда вы пишите array1[2] компьютер это расценивает как «переместится на две позиции в памяти из указателя array1«. Вот вам пример:
#include <stdio.h> void main() { int array1[2]; array1[0] = 0; array1[1] = 1; int* pointer1 = NULL; pointer1 = array1; ++pointer1; printf("%irn", *pointer1); }
В результате получим:
А можно и так:
#include <stdio.h> void main() { int array1[2]; array1[0] = 0; array1[1] = 1; int* pointer1 = NULL; pointer1 = array1; printf("%irn", pointer1[1]); }
Получили тоже самое. Выходит, что с указателем и с массивом можно работать почти одинаково, но нужно не забывать про осторожность. Указатель можно сдвинуть через-чур и получить ошибки в программе. Например, если под указатель выделили 4 байта, а сдвинулись на 5(5 раз сделать ++%pointer_name%) то теперь указатель указывает на блок памяти, в котором может быть системная/важная информация, вовсе не принадлежащая нашей программе.Теперь о магических сложных строках в С. Строка в С — это массив символов заканчивающийся символом конца строки (‘\0’). Строки можно инициализировать явно:
char str[] = "My string!";
В результате будет инициализирован массив символов размером в 11 символов(10 символы строки и символ ‘\0’).
Или например вот так:
char* str = "My string!";
Можно создать сначала массив, а потом заполнить его строкой. Например с помощью функции strcpy:
char str1[20]; strcpy(str1, "My string!");
Строки в чистом С очень отличаются от строк в паскале. Например что бы их сравнить нужно использовать функцию strcmp, потому что выражение типа:
char* str1 = "Joke!"; char* str2 = "Joke!"; if (str1 == str2) //do something
Будет давать неверный результат, ведь str1 и str2 — указатели на первый элемент массива, и сравнивается их равенство, но никак не строки! Это необходимо запомнить. Есть функция strcmp, возвращает ноль — если строки равны. Использовать вот так:
char* str1 = "My STR!"; char* str2 = "My STR!"; if (!strcmp(str1, str2)) //do something
Поскольку strcmp вернет 0, то !0(не 0) будет true.
Теперь напишем функцию, которая будет добавлять символ в конец строки. Прототип нашей функции:
char* AppendChar(const char* input, char character);
Принимает в качестве аргументов строку input, модификатор const который, означает, что в процессе выполнения функции, строка изменена не будет, и символ character. Теперь реализация:
char* AppendChar(const char* input, char character) { //создадим новую строку и выделим память для нее char* output = (char*) malloc(strlen(input) + sizeof(char) * 2); strcpy(output, input); //копируем туда входящую строку output[strlen(input)] = character; //добавляем наш символ output[strlen(input) + 1] = '\0'; //добавляем признак конца строки return output; }
Сначала мы выделяем память для «выходной» строки. Размер памяти задаем как strlen(input) + sizeof(char) * 2, то есть размер входящей строки плюс два символа: новый символ и признак конца строки ‘\n’.
Потом мы копируем в output саму input. Следующая операция заменяет символ ‘\0’ нашим символом, а следующий(пустой) — ‘\0’.
Подытожим:
- Переменная массива — это указатель на его первый элемент.
- С указателем можно работать также как и с массивом, использую квадратные скобки. Предварительно нужно выделить ему память с помощью функции malloc.
- Строки в С представляют собой массивы символов(char), длинна массива равна длине строки +1, в конец записывается признак конца строки, символ ‘\0’. Для их использования, необходимо создавать массивы или использовать указатели.
- После того, как указатель стал не нужным, необходимо освободить выделенную ему память с помощью функции free, а самому указателю присвоить NULL. Это обезопасит ваш код!
- Для работы со строками в С есть специальные функции. Например strcmp — сравнивает строки, в случае совпадения возвращает ноль.
- Оператор sizeof возвращает размер переменной/типа в байтах. Он не умеет вычислять размер динамической памяти, поэтому не используйте с указателями!
Теперь немного о С++. Что бы выделить память указателю, достаточно написать:
char* myString = new char[40];
Указателю будет выделена память размером в 40 символов. Для очистки:
delete(myString);
Подробнее читайте в гугле, там есть особенности связанные с классами(ООП). Чистый С не имеет классов, перегруженных функций и прочих вкусностей С++.
Спрашивайте свои ответы. Пишите пожелания/просьбы. И не забывайте сообщать мне об орфографических ошибках :D!
Удачи.
Сделаю вывод сразу по двум частям. Спасибо за старания. Но… Очень сухо, мало объяснений, одни примеры, и некоторые из них неудачные, и только запутывают. Самое лучшее, что я читал на эту тему и что мне самому помогло разобраться в указателях — это статья Andrew Peace «A Beginner’s Guide to Pointers» — кратко доходчиво и ничего лишнего.
Спасибо за грамотную критику. Попытаюсь сделать лучше.
spasibo! no mojete napisat’ po bol’she for example 😀
Окей, скоро напишу статью «Указатели в примерах!».
Вот код с массивами из примера выше:
#include
void main()
{
int array1[2];
array1[0] = 0;
array1[1] = 1;
int* pointer1 = NULL;
pointer1 = array1;
++pointer1;
printf("%irn", *pointer1);
}
Вопрос : pointer1 = array1; pointer1 присваевается адрес array1,например адрес array1 равен 0x5A3?А в строке(++pointer;) префиксное увеличение даст 0x5A4 или передвинет на следущий элемент массива,но тогда следущий элемент array1[0] после array1,а не array1[1].
В компиляторе все работает,получается где-то я ошибься…
Дело в том, что сама переменная массива (то есть без индексных скобок «[]») является указателем на первый элемент массива. Если ее сделать:
int *pointer1;
int array1[5];
а потом
*pointer1 = array1;
То по сути, теперь pointer1 тот же массив. То есть он указывает на первый элемент массива array1. Если его инкрементировать, он сдвинет адрес и будет указывать на следующий элемент массива. Что и случается в этом примере.
Просто все дело в том, что массив это указатель на первый элемент набора элементов.
#include
#include
using namespace std;
void main()
{
int array1[3];
array1[0] = 0;
array1[1] = 1;
array1[2] = 2;
// вывожу элементы массива
cout << array1[0] << endl; //0
cout << array1[1] << endl; //1
cout << array1[2] << endl; //2
pointer1 = array1;
cout << array1 << endl; // hex(т.к. указатель адрес в памяти)
cout << pointer1 << endl; // hex(т.к. указатель адрес в памяти)
cout << ++pointer1 << endl; // hex+4байта(как раз размер типа int равен 4 байта,я так понял в зависимости от типа среда расчитывает,где находиться следущий элемент массива)
printf("%irn", *pointer1); /* а вот тут уже array[1],я так догадываюсь, что вместо %i подставился номер элемента массива*/
cin.get();
}
Спасибо за помощь)))
P.S жду новых статей на тему С++
Пожалуйста.
По поводу вопроса о printf. Почитай-те как эта функция выводит в консоль данные. %i — это директива для printf, которая говорит, что элемент будет типа integer.
Учту (про статьи о С++).
Скоро будет продолжение уроков по с++???
А есть пожелания? 🙂
Если будет время и придумаю тему — напишу.
Как происходит работа памяти во время выполнения функций и
классов.
#include
using namespace std;
int result(int variable);
void main ()
{
int x = 10;
result(x);
cout << x;
cin.get ();
cin.get ();
}
int result(int variable)
{
cout << variable*2 << endl;
int x;
x = 30;
return x;
}
Например,значение переменной копируется в функцию при передачи ее как аргумента, поэтому при переприсвоении переменной внутри функции она во внешнем окружении не меняется,вот такие ньюансы хотелось бы понять.
Я понял. Кстати, интересная тема. Попробую что-то написать новое.
Интересует ли программирование на С\С++ в Linux? Назревает проект, по ходу дела может получиться пару статей написать.
К сожалению уровень моих знаний С++ пока низкий, но идея С/C++в linux очень даже интересная, можно еще и в windows с использованием MFC и других библиотек.
Прочитал обе статьи про указатели, автору огромная благодарность!!! Всё описано простым и доступным языком, примеры, всё в тему!!! Проштудировал большое количество статей, эта самая лучшая! Продолжай в том же духе!!!
Спасибо :), рад был помочь.
Классно,спс,статья многое объясняет
Пожалуйста. Рад, что смог помочь.
«Чтобы» и «итак» пишется слитно; на пунктуацию внимания не обращала: это отдельная тема. Отличная статья, кстати!
С ошибками у меня плохо.
Спасибо. Хорошая статья. Продолжай к динамическому выделению памяти
Рад что понравилось! Возможно вернусь к этому.
понравились иллюстрации выделения памяти. Сразу стало понятно.
Там где выводим строку, какую нужно подключить библиотеку?
Пардон за долгий ответ, так сказать.
Думаю вам уже не актуально, но зацените мой блог на английском. Ссылка в шапке.