| Меню сайта |
|
 |
| Категории раздела |
|
 |
| Статистика |
Онлайн всего: 1 Гостей: 1 Пользователей: 0 |
 |
|
 | |  |
|
Указатели C++ для чайников Часть 2
3. Динамическое выделение памяти
Вот та замечательная плюшка, из-за которой я использую указатели. Начнем с динамических массивов. Зачастую при решении какой-либо задачи возникает потребность в использовании массива неопределенного размера, то есть размер этот заранее неизвестен. Здесь нам на помощь приходят динамические массивы — память под них выделяется в процессе выполнения программы. Пример:
int size = -1;
int* dyn_arr = new int[size];
Что здесь происходит: мы объявляем указатель и инициализируем его началом массива, под который выделяется память оператором new на sizeэлементов. Следует заметить, что в этом случае мы можем использовать те же приемы в работе с указателями, что и с статическим массивом. Что следует из этого извлечь — если вам нужна какая — то структура (как массив, например), но ее размер вам заранее неизвестен, то просто сделайте объявление этой структуры, а проинициализируете ее уж позже. Более полный пример приведу чуть позже, а пока что — рассмотрим двойные указатели.
Что такое указатель на указатель? Это та же переменная, которая хранит адрес другого указателя „более низкого порядка“. Зачем он нужен? Для инициализации двумерного динамического массива, например:
const int size = 7;
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, учитывая, что это должно произойти в некоторой функции, которая так или иначе занимается обработкой массивов. Цель: разобраться в способе передачи указателей для их дальнейшей модификации.
- Вариант первый. Передаем собственно указатели 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 станет одинаковым.
- Вариант второй. Заменить значение указателя: просто присвоить значение указателя 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);
}
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);
-
-
- На данной ноте я хотел бы закончить свое повествование. Если найдется хотя бы пара ребят, которым понравится стиль изложения материала, то я постараюсь продолжить… ой, да кого я обманываю, мне нужен инвайт и все на этом, дайте инвайт и вашим глазам больше не придется видеть это околесицу. Шучу, конечно. Ругайте, комментируйте.
http://habrahabr.ru/post/256443/
|
| Категория: Статьи о программировании | Добавил: TypicalUbuntu (23.11.2015)
|
| Просмотров: 766
| Рейтинг: 0.0/0 |
|
|
 | |  |
|
| Вход на сайт |
|
 |
| Поиск |
|
 |
|