Показаны сообщения с ярлыком указатели. Показать все сообщения
Показаны сообщения с ярлыком указатели. Показать все сообщения

суббота, 2 октября 2010 г.

4. Допускается ли передавать объекты в качестве параметров, и какими способами? А возвращать как результат?

Так же, как и переменные основных типов - по ссылке(1) и по значению(2).
1. С передачей по ссылке все в принципе ясно: передали ссылку и продолжаем работать с тем же объектом как ни в чем не бывало. Но мне не очень нравится такой способ. Если в коде мы видим:

int x = 1;
f(x);

То мы не можем предугадать, как передан x и может ли измениться его значение в теле функции, так как определение может быть void f(int a) или void f(int &a). Та же неопределенность и при передаче объекта.
Отмечу, что возможности передачи по ссылке не было в C.

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

void f(A* a){/* разыменовываем: *a */}

A a;
f(&a);

При передаче объекта по значению будет передано содержимое объекта

void f(A a){/* разыменовываем: *a */}

A a;
f(a);

Будет вызван конструктор копирования.

Возврат результата производится аналогичными способами. Однако важно проследить за временем жизни объекта, иначе можно вернуть ссылку или указатель на уже несуществующий объект. При передаче по значению будет вызван конструктор копирования.

понедельник, 14 сентября 2009 г.

char* и строковые литералы

Изучаю дальше.

char c = 'x';
char* pc = &c;

Все понятно, pc – указатель на переменную char. И тут внезапно! Оказывается можно так:

char* pc = ”Hello”;
cout << pc //Hello;

pc же вроде должна хранить адрес! Страуструп сказал, что так можно типа потому, что в языке C строковые литералы (это оказывается так по-научному называется) хранились именно так, через char*. А C++ должен поддерживать C. И Страуструпу тут все понятно и незачем останавливаться на этом подробно. Жаль я не он, и мне ничего не понятно. Мне не нравится, что char* ведет двойную жизнь. Сейчас будем разбираться.

Попробую посмотреть адрес, и разыменовать pc.

cout << pc << (long)pc << *pc; //Hello 4464640 H

Ага, pc выдает Hello, а когда я преобразовал ее к long, то она все-таки вернула адрес.
Отлично, *pc выдает символ H. Т.е. pc по-прежнему работает как указатель на char.
Интересно, где остальные символы слова.

Итак, я взял pc (а он все-таки хранит адрес, но при удобной возможности прикидывается строкой), прибавил к хранящемуся (слово было выговорено с только нескольких попыток) там адресу 1. В ячейке с адресом pc лежит ‘H’, очевидно предположить, что в следующей ячейке лежит 'e'. Чтобы узнать адрес следующей ячейки, нужно просто прибавить 1 (так как ячейка char равна байту). А чтобы извлечь ее значение, нужно сделать разыменование:

cout << *pc << *(pc+1) << *(pc+2) << *(pc+3) << *(pc+4); //Hello

Сейчас нарисую, память после:
char* pc = ”Hello”;
char* pc2 = ”World”;


Так получилось, что слова идут друг за другом. В следующий раз такого может не быть. Для этого случая верно выражение pc + 6 == pc2. Еще можно вытащить слово “World” не используя pc2

cout << *(pc+6) << *(pc+7) << *(pc+8) << *(pc+9) << *(pc+10); //World

Разумеется, в другой раз это может не прокатить.
Определить конец слова помогает ‘\0’. Если бы я был составителем задачника, то добавил задачу печати слова, используя *(pc+i) в цикле, пока не встретится ‘\0’.

А еще минздрав предупреждает: строковые литералы изменять нельзя. *(pc) = 'z'; вывалится с ошибкой во время выполнения.

Вообще говоря это устаревший способ, лучше использовать char c[] = “Hello”. Потом об этом напишу.

воскресенье, 13 сентября 2009 г.

Указатели

В двух словах. 

int i = 7;
int* pi = &i; //Указатель на int. Проинициализирован адресом i.
int** ppi = &pi //Указатель на int*, т.е. указатель на указатель на int. Проинициализирован адресом pi.

Когда-то я не понимал, что физически, указатель - это переменная хранящая адрес. Вот и все. Взглянем на структуру памяти после выполнения вышеприведенного кода.

Прямоугольники символизируют ячейки памяти. Правее указаны их адреса. Разумеется адреса могут быть другими, при следующем запуске. Стрелки добавлены исключильно для наглядности. 

Переменная i может хранить любое значение типа int. pi - адрес любой переменной int. ppi - адрес любой переменной типа int*(например адрес pi).

Все это легко проверяется. В моем примере:

cout << i << (long)&i; //7 2293572
cout << pi << (long)&pi; //2293572 2293568
cout << ppi << (long)&ppi; //22968 2293564
cout << *pi << **ppi << (long)*ppi; //7 7 2293572

(long) - преобразование перед выводом. Без него адреса выводятся в шеснадцатеричном виде. * - операция разыменования. То есть *pi возвращает значение переменной, находящуюся по адресу pi. *ppi == pi == &i

Был еще сложный для меня момент: указатели на int и на int* (и вообще все указатели) так похожи, раз хранят только адрес. Почему же нельзя сделать просто указатель на переменную любого типа. Вообще говоря можно. Но в таком случае можно легко ошибиться. Например случайно указать на переменную int, и думать про себя, что это объект. А благодаря типизации, компилятор будет следить за этим, а среда разработки будет помогать автозавершением кода для более сложных типов.

Уф, пока писал и проверял, сам все понял :)