суббота, 26 сентября 2009 г.
Константы
const int a = 1;
int b;
cout << &a << &b; //У меня, при запусках, b всегда имеет адрес меньше(на 4 байта), чем a.
int* p = (&b+1); //Это адрес константы a
cout << *p //1
*p = 2; //Меняем
cout << a << endl << *p; //1 2
Вроде поменяли, однако a осталось прежним, но по ее адресу лежит число 2. Дело в том, что компилятор просто заменяет все вхождения a на 1, и обычно для этого не выделяет память. Но когда мы написали &a, компилятор все-таки выделил память для нее и записал туда значение. Но все-равно перед компиляцией заменил в коде a на 1. Память вроде бы пропадает впустую, ее даже можно изменить.
понедельник, 21 сентября 2009 г.
reinterpret_cast
long a = 2829155;
cout << reinterpret_cast< char* >(&a);
Выведется строка «c++». Не отдам руку на отсечение, что у всех она будет именно такой, это требует дополнительного исследования.В первой строке записали четырехбайтовое число. Затем взяли адрес переменной и нагло наврали компилятору, что этот адрес совсем не указатель на long. Это указатель на char. Как компилятор интерпретирует указатель на char? Как строку.
Почему именно «с++»? 2829155 == 0x002B2B63. То есть число 2829155 хранится в виде последовательности байт: 0x63(==99), 0x2B(==43), 0x2B(==43), 0x0(==0). Если посмотреть таблицу символов, то убедимся, что 99 это символ «c», а 43 – «+». Байт с нулем интерпретируется как конец строки.
Совсем нетрудно написать программу обратного преобразования, из строки в число
Я не смогу толково объяснить все тонкости reinterpret_cast, для этого есть учебники. Однако, точно могу сказать, что он запросто превращает указатель одного типа в указатель другого. А это влечет совершенно другую интерпретацию байтов памяти, в чем можно убедиться выше. Об опасностях нетрудно догадаться. Если в примере взять число побольше, то 4-й байт уже не будет равен нулю. Значит, приведенная к char* строка будет продолжаться до первого нуля, но уже неизвестно где он будет.
С классами тоже интересно.
class A {
public:
int i;
};
void main(){
int b = 1;
cout << (*reinterpret_cast< A* >(&b)).i; /*1*/
}
Тут говорится, что по адресу &b вовсе не число, а переменная класса A. Нужно ли говорить насколько это все опасно.
пятница, 18 сентября 2009 г.
Массивы
var z : array[1..3] of Integer;
то переменная z будет массивом и все тут. Словно обертка над более низкоуровневыми типами. Хотя, может быть, это не совсем верно. Массив в C++:
int z[3];
В памяти отводится место(непрерывное) для трех int, а переменная z - указатель на первый элемент. Квадратные скобки просто делают удобнее работу с указателями. А раз так, то для получения значения, можно писать не только z[i], а еще и *(z+i).
z[i] == *(z+i)
&z[i] == z+i
Иными словами: Берем адрес первого элемента массива (адрес это z). Следующий элемент находится на sizeof(int) байт (у меня 4) дальше. Но количество байт нам знать не нужно, так как компилятор в курсе, что z - указатель на int, поэтому z+1 указывает на sizeof(int) дальше чем z, т.е на следующий элемент массива (на z[1]). Аналогично и z+i.
Еще пример
int* p = z+1;
int* p = &z[1];
//Обе записи эквивалентны.
cout << p[2]; //p - не массив, просто квадратные скобки смещают указатель и сразу разыменовывают.
В этом примере p[1]==z[2].
четверг, 17 сентября 2009 г.
cout << (long)&"Hello" << (long)&"World"; //4464640 4464646 (опять эти числа, почему всегда они)
Любопытно, если записать одинаковые слова:
cout << (long)&"Hello" << (long)&"Hello"; //4464640 4464640
то можно увидеть одинаковые адреса. Видимо компилятор произвел оптимизацию.
понедельник, 14 сентября 2009 г.
char* и строковые литералы
воскресенье, 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)π //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, и думать про себя, что это объект. А благодаря типизации, компилятор будет следить за этим, а среда разработки будет помогать автозавершением кода для более сложных типов.
Уф, пока писал и проверял, сам все понял :)
Перечислимые типы
С этими типами посложнее, чем в Delphi. Самый простой пример:
enum colors{RED, GREEN, BLUE};
RED, GREEN, BLUE - константы, равные числам 0, 1, 2 соответственно. Их значения можно задавать самостоятельно:
enum colors{RED=0, GREEN=1, BLUE=9}; //Должны быть целыми
Использование достаточно очевидно:
colors x = GREEN; //z == 1
colors z = 9; //Ошибка компиляции.
colors z = colors(9); //А вот так все хорошо. z == BLUE
if (z = BLUE) {/* */};
Любопытно (по крайней мере для меня), что переменной z можно присвоить другое значение кроме этих трех:
colors z = 2; //Как мы уже выяснили - это ошибка компиляции
colors z = colors(2); // Так можно. z == 2
Интересно, в каких пределах можно задавать значения? У меня не сошлось со Страуструпом.
Он говорит, что выделяется непрерывный диапазон, в зависимости от минимального и максимального значения. Диапазон определяется минимальным количеством бит, требуемым для представления перечисления. Пример оттуда:
enum e2{a=3, b=9} //диапазон 0:15
Поэтому, по его словам, e2 f2 = e2(20) не определено.
Границы диапазонов связаны со степенями двойки, но я не буду переписывать точное правило. Размер sizeof не может превышать sizeof(int).
Не знаю почему, но как я не крутил, sizeof всегда был равен 4. По всей вероятности sizeof(enum) = sizeof(int).
Вывод, который я сделал: Перечислимый тип - это фактически тот же int, но с личными именами некоторых чисел
суббота, 12 сентября 2009 г.
Консольный сапер
Для тренировки сделал сапера. Пишем столбец и строку, а в ответ получаем количество мин вокруг.
четверг, 10 сентября 2009 г.
Страуструп. Размеры целочисленных типов.
Я был в курсе, что книга для продолжающих. Во 2-й главе я занервничал. В 3-й начал паниковать. Эти главы с высоты птичьего полета охватывали процентов 70 из того, что я узнал из предыдущей книги. Что же будет дальше. Но все закончилось хэппи эндом, это был просто обзор возможностей языка :)
Наконец-то я понял в каких пределах могут отличаться фундаментальные типы в зависимосимости от платформы. Авторы любят предупреждать: "Внимание, размер типов может отличаться, обязательно это помните". А откуда я знаю какой размер может принять, скажем, тип int на другой платформе. 2 байта? 4?. А может, например 2 бита? Могу ли я быть уверен, что число 9000 влезет в int?
Но тут конкретно сказано: независимо от погоды типу char гарантируется 8 бит, для short - 16, long - 32. Хм, где же int. Cудя по sizeof(short) <= sizeof(int) <= sizeof(long) ему дадут 16 бит точно. У меня на 32-битной Висте и компилятором minGW для них выдало: 2, 4, 4. Ну да ладно, просто учту на будущее.
среда, 9 сентября 2009 г.
C++
Так получилось, что я плохо знаю C++. Скажем так, чуть лучше уровня "Hello, World!". Если я хочу(а я хочу) писать прикладные программы, то просто обязан постигнуть его.
Это осознание пришло около 2-х недель назад. Я начал с поиска подходящей книги. В рунете первым делом вспоминают книгу Брюса Эккеля "Философия Java" (Thinking in Java). Вот и отлично, тем более она была куплена мною около полугода назад и аккуратно положена на полку. Я часто смотрел на ее красивую оранжевую обложку. Обложка мне нравилась. Вероятно потому, что оранжевый цвет считается самым антидепрессивным :) Иногда я прочитывал введение, через месяц забывал, и снова прочитывал. Начало введения было очень вдохновляющим, но к концу оно меня утомляло.
Но однажды я сел и стал читать. И за пару недель почти полностью ее прочитал. Мне понравилось, но новичку в программировании не рекомендую. Автор предполагает(и подчеркивает), что читатель знаком с языком C, и разъясняет только новое, появившееся в C++.
Честно говоря, мне приходилось читать ее очень медленно, вдаваясь в каждый пример, вдумываясь в каждое предложение. Понравилось, что почти нет "разжевывания" материала. Если не понимал, просто возвращался назад и врубался.
Стиль интересный, хочу перечитать. Но только после Страуструпа "Язык программирования С++", в котором я прочитал кучу введений и предисловий, а также главу "от редакции русского издания". Ходят легенды, что каждый программист должен ее прочитать.
вторник, 8 сентября 2009 г.
Итак, надо вспомнить, что я вообще знаю.
Первым в списке QBasic - на нем написаны моя первая, а также вагон других программ. Интересно, что самым трудным для меня был цикл for, сложнее чем do-loop с массивами. Но я был тогда в 5-м классе. Не могу не вспомнить одну из самых крупных, созданную в 8 классе - тетрис с 29 различными фигурками (в каждой от одного до пяти квадратиков). Впрочем с возможностью выбора набора.
В университете начал учить Pascal. Долго не мог врубиться в указатели, а особенно в реализацию списков с помощью них. Хотя у меня тогда не было интернета.
Там же изучил HTML, CSS, но средненько. Очень поверхностно ASP, 1С, PHP
Курсовики и диплом писал на Delphi и достаточно хорошо усвоил принципы ООП.
Универ дал не очень много практики, зато теперь я относительно хорошо подкован в теории :)
На работе продолжил писать на Delphi, поэтому сам язык знаю достаточно хорошо. Но для меня остаются тайной дельфовые вкладки компонентов с названиями типа SOAP, Corba и т. п.
На среднем уровне владею SQL, PL/SQL
Не буду упоминать то, с чем мало работал.
Наверняка о чем-нибудь забыл написать.
Самая большая дыра в моем образовании это плохое знание C++. Про это в другом топике.