Вот та замечательная плюшка, из-за которой я использую указатели. Начнем с динамических массивов. Зачастую при решении какой-либо задачи возникает потребность в использовании массива неопределенного размера, то есть размер этот заранее неизвестен. Здесь нам на помощь приходят динамические массивы — память под них выделяется в процессе выполнения программы. Пример:
int size = -1;
// здесь происходят какие - то // действия, которые изменяют// значение переменной sizeint* dyn_arr = newint[size];
Что здесь происходит: мы объявляем указатель и инициализируем его началом массива, под который выделяется память оператором new на sizeэлементов. Следует заметить, что в этом случае мы можем использовать те же приемы в работе с указателями, что и с статическим массивом. Что следует из этого извлечь — если вам нужна какая — то структура (как массив, например), но ее размер вам заранее неизвестен, то просто сделайте объявление этой структуры, а проинициализируете ее уж позже. Более полный пример приведу чуть позже, а пока что — рассмотрим двойные указатели.
Что такое указатель на указатель? Это та же переменная, которая хранит адрес другого указателя „более низкого порядка“. Зачем он нужен? Для инициализации двумерного динамического массива, например:
constint size = 7;
// двумерный массив размером 7x7int** i_arr = newint*[size];
for(int i = 0; i != size; i++){
i_arr[i] = newint[size];
}
А тройной указатель? Трехмерный динамический массив. Неинтересно, скажите вы, так можно продолжать до бесконечности. Ну хорошо. Тогда давайте представим себе ситуацию, когда нам нужно разместить динамические объекты какого-нибудь класса MyClass в двумерном динамическом массиве. Как это выглядит (пример иллюстрирует исключительно использование указателей, приведенный в примере класс никакой смысловой нагрузки не несет):
class MyClass{
public:
int a;
public:
MyClass(int v){ this->a = v; };
~MyClass(){};
};
voidmain(){
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 и проинициализируем их элементы некоторыми значениями:
voidf1(int**, int);
voidmain(){
constint size = 4;
// объявление и выделение памяти // под другие указателиint** a = newint*[size];
int** b = newint*[size];
// выделение памяти под числовые значенияfor (int i = 0; i != size; i++){
a[i] = newint[size];
b[i] = newint[size];
// собственно инициализацияfor (int j = 0; j != size; j++){
a[i][j] = i * j + 1;
b[i][j] = i * j - 1;
}
}
}
voidf1(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 в качестве параметров функции:
voidf2(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.
voidmain(){
constint size = 4;
// объявление и выделение памяти // под другие указателиint** a = newint*[size];
int** b = newint*[size];
// выделение памяти под числовые значенияfor (int i = 0; i != size; i++){
a[i] = newint[size];
b[i] = newint[size];
// собственно инициализацияfor (int j = 0; j != size; j++){
a[i][j] = i * j + 1;
b[i][j] = i * j - 1;
}
}
// Здесь это сработает
a = b;
}
Однако, нам интересен случай, когда массивы обрабатываются в некоторой функции. Что первое приходит на ум? Передать указатели в качестве параметров нашей функции и там сделать то же самое: присвоить указателю a значение указателя b. То есть реализовать следующую функцию:
voidf3(int** a, int** b){
a = b;
}
Сработает ли она? Если мы внутри функции f3 вызовем функцию f1(a, 4), то увидим, что значения массива действительно поменялись. НО: если мы посмотрим содержимое массива a в main — то обнаружим обратное — ничего не изменилось. Так в чем же причина? Все предельно просто: в функции f3 мы работали не с самим указателем a, а с его локальной копией! Все изменения, которые произошли в функции f3 — затронули только локальную копию указателя, но никак не сам указатель a. Давайте посмотрим на следующий пример:
voidfalse_eqv(int, int);
voidmain(){
int a = 3, b = 5;
false_eqv(a, b);
// Поменялось значение a? // Конечно же, нет
}
false_eqv(int a, int b){
a = b;
}
Итак, я думаю, вы поняли, к чему я веду. Переменной a нельзя присвоить таким образом значение переменной b — ведь мы передавали их значения напрямую, а не по ссылке. То же самое и с указателями — используя их в качестве аргументов таким образом, мы заведомо лишаем их возможности изменения значения.
Вариант третий, или работа над ошибками по второму варианту:
voidf4(int***, int**);
voidmain(){
constint size = 4;
int** a = newint*[4];
int** b = newint*[4];
for (int i = 0; i != 4; i++){
a[i] = newint[4];
b[i] = newint[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);
}
voidf4(int*** a, int** b){
*a = b;
}
Таким образом, в main'е мы создаем указатель d на указатель a, и именно его передаем в качестве аргумента в функцию замены. Теперь,разыменовавd внутри f4 и приравняв ему значение указателя b, мы заменили значение настоящего указателя a, а не его локальной копии, на значение указателя b.
Кстати, а чего это мы создаем динамические объекты? Ну ладно размер массива не знали, а экземпляры классов мы зачем динамическими делали? Да потому что зачастую, созданный нами объекты свое — они генерились, порождали новые данные/объекты для дальнейшей работы, а теперь пришло им время...умереть [фу, как грубо] уйти со сцены. И как мы это сделаем? Просто:
delete(a);
delete(b);
// Вот и кончились наши двумерные массивыdelete(v);
// Вот и нет больше двумерного массива с динамическими объектамиdelete(dyn_array);
// Вот и удалился одномерный массив
На данной ноте я хотел бы закончить свое повествование. Если найдется хотя бы пара ребят, которым понравится стиль изложения материала, то я постараюсь продолжить… ой, да кого я обманываю, мне нужен инвайт и все на этом, дайте инвайт и вашим глазам больше не придется видеть это околесицу. Шучу, конечно. Ругайте, комментируйте.