Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате
Форум программистов > C/C++: Для новичков > Указатели, строки, классы и пр.


Автор: ManiaK 9.8.2005, 14:35
 ВВЕДЕНИЕ

    В этой статье я бы хотел научить людей, недавно занявшихся изучением Си++, сразу трём вещам: работать со строками (в более общем виде - с указателями), писать свои классы и перегружать некоторые операторы в этих классах, которые иногда заметно упрощают жизнь, но для новичков представляются чем-то загадочным и непонятным. Мы напишем свой строковый класс и, таким образом, охватим сразу три эти интересные темы. В конце мы посмотрим, что уже есть в стандартной библиотеке Си++ и сравним с тем, что сделами мы.

    Эта статья не учит Си++ - предполагается, что читатель уже знаком с основной частью синтаксиса Си++, но не имеет опыта в программировании на нём или этот опыт незначителен. Проще говоря, статья ориентирована на новичков. Я буду по ходу дела объяснять некоторые важные моменты, на которые часто не обращают внимания новички, но все мои рассуждения будут уже содержать в себе базовые термины, такие как класс, метод, функция, переменная и прочее. Все эти понятия должны быть понятны читателю до прочтения данной статьи иначе оно не будет иметь никакого смысла. И ещё: все примеры в этой статье - не готовые решения, а лишь демострация идей; поэтому не стоит копировать их и пытаться компилировать без каких либо изменений - идеи надо просто усвоить.

    Ну, готовы?..

    ЧАСТЬ 1. СИ СТРОКИ ИЛИ НЕМНОГО ТЕОРИИ.

    Наверное треть всех вопросов, задаваемых новичками на различных форумах интернета, посвящённых Си или Си++, так или иначе касаются строк. Кстати, в названии параграфа я не ошибся - именно Си строки. Почему? потому что в Си++ есть классы и использование указателей бессмысленно (Си является практически идеальным подмножеством Си++ и потому почти всё, что справедливо для Си, справедливо и для Си++). В сущности, все проблемы со строками сводятся к проблемам указателей. Если человек не понимает, как работают указатели и что они из себя представляют, он никогда не поймёт, чем char отличается от char*. Если ещё более углубиться в проблему, то можно увидеть, что непонимание указателей связано зачастую с непонимаением работы самого компьютера. Действительно, а знаете ли вы как ваши программы выполняются компьютером? В Си, как и в Си++, этот вопрос очень важен, так как эти языки очень тесно взаимодействуют с аппаратным уровнем.

    Обычно ваша программа (бинарный объектный код, понятный процессору без трансляции) загружается в оперативную память и там исполняется. То есть, предположим, что ваша программа занимает 512 байт. Загрузчик программы определяет, куда лучше всего впихнуть её; предположим, он выбрал адрес 0x1000. Тогда последний байт вашей программы будет по адресу 0x1200 (0x1000 + 512). Некоторые преполагают, что в том же месте хранятся все переменные программы. Спешу вас разуверить, это не так. В сегменте кода (так правильно называется эта часть оперативной памяти) хранятся только машинные инструкции самой программы и (не обязательно) константные объекты. Проще говоря, только то, что не меняется в процессе выполнения программы. При попытке из вашей же программы поменять код ("поработать над собой") хорошая операционная система даст оплеуху и выкинет из списка задач, почистив за вами память. Где же хранятся переменные?..

Необходимо сначала разделить переменные на два рода - "динамические" и стековые. В чём разница? во-первых, в скорости выделения памяти под них: под стековые переменные память выделяется с гиганской скоростью и ещё быстрее высвобождается, в то время как при создании динамической переменной система ещё подумает: а где взять эту память?.. Данный вопрос очень важен, часто программисты "обманывают" компилятор, заключая динамический объект в статическую оболочку.

Второе достоинство стековых переменных в том, что они автоматически удаляются, коль скоро выполнение программы выходит за облась действия данной переменной. Иначе говоря, программисту не приходится следить за тем, когда отдавать память обратно системе - компилятор делает это за него сам. Но за всё приходится платить в нашем мире: при использовании стековых указателей приходится следить, чтобы в их личную жизнь никто не вмешивался и не брал указатели на них, иначе может получиться казус: переменной уже сто лет как нет, а указатель на неё до сих пор существует. Ничего не подозревающий клиент пытается обратиться по этому указателю к объекту и получает по шее от операционной системы - упс...

Что же такое динамические переменные? - это переменные, которые можно создавать и удалять в процессе работы программы. Например, нам нужно написать программу, которая будет читать файлы, указанные пользователем и как-то обрабатывать их. Для этого нам нужно записывать прочитанные данные в какой-то массив, причём мы заранее не можем сказать, какого размера должен быть этот массив. Как быть? использовать динамическую переменную.

В теории всё просто. При непосредственном выполнении ваша программа выясняет размер файла и запрашивает у операционной системы необходимый объём памяти. Но как нам "дадут" эту память? Просто! Операционная система выделяет в оперативной памяти область, размер которой равен запрошенному, и даёт вам адрес первого байта этой области (далее просто - адрес области) - этакий жетончик, по которому вы можете получить свои данные. Помимо этого она заносит в свои списки вашу программу, адрес области и её размер. Теперь вы полноправный владелец данной области памяти! Смотрите, как выглядит последний абзац в Си-коде:

Код

    int *i = alloc(1000);// Хотим блок памяти в тысячу байт


    В Си++ есть ещё вариант:

Код

    int *i = new int[1000];// Здесь сложнее - блок памяти, размером в тысячу int'ов


    В обоих примерах переменная i - статическая, а область памяти, адрес которой эта переменная содержит, - динамическая. Сложно? только первое время, на самом деле всё логично. Можно даже обнаглеть и написать такое:

Код

    unsigned long ic = (unsigned long)alloc(1000);// Ха-ха, в Си такое прокатывает
    unsigned long icpp = reinterpret_cast<unsigned long>(new int[1000]);// А так - в Си++


    В Си++ значительно более уважительно относятся к типам объектов и на любые преобразования, подобные этим, грустно улыбаются; от себя хочу добавить, что подобными фокусами по возможности лучше не заниматься. Размер unsigned long гарантированно совпадает с размером указателя. С указателями пользователь может выполнять некоторые арифметические операции; рассмотрим на примере:

Код

    int *p = new int;// Если скобок нет - объект один
    int *p2 = p;// Присвоение адреса из одного указателя другому
    p2 += 10;// Сдвиг указателя на десять позиций
    p2 = 1001;// Ошибка! слева тип - указатель, справа - числовая константа!


    Почему предпоследняя строчка работает, а последняя нет, хотя и в той и другой справа - числа? Есть одна маленькая хитрость. В предпоследней операции при сдвиге литера 10 имеет не числовой тип, а специальный тип ptr_diff. Это тип разницы между двумя адресами; переменные этого типа можно прибавлять/вычитать из указателей, но их нельзя присваивать. Иногда этот тип обозначают как difference_typе.

    Да, для любителей поглумиться над компилятором скажу сразу: операции умножения/деления над указателями запрещены.

    Наконец мы выделили блок нужного размера и записали туда прочитанные из файла данные, обработали их. Теперь нужно читать следующий файл, вероятно, другого размера. Что делать со "старым" блоком? Есть два варианта:

    1. Использовать функцию операционной системы для увеличения размера блока (обычно - realloc).
    2. Удалить старый блок ("вернуть" его системе) и запросить новый нужного размера.

    В Си это выглядит примерно так:

Код

    free(p);// Возвращаем память системе
    // p теперь указывает на нулевой адрес; гарантируется, что там ничего нет
    p = alloc(500);// Берём блок по-меньше


    В Си++ это может выглядеть ещё и так:

Код

    delete [] p2;// Квадратные скобки указывают на то, что удаляемый объект - массив
    // если их нет, считается, что объект одиночный
    p2 = new int[125];// Во так


    При выделении памяти оператором new под массив обычно помечается также и размер выделенного массива. Тогда при освобождении памяти оператором delete [] программа будет знать, сколько памяти вернуть системе. Вот такие ситуации не желательны:

Код

    int* pi = new int[12];
    delete pi;// Результат не определён, зависит от конкретного компилятора или ОС


    Лучше не делать этого. Чаще всего разработчики компиляторов перестраховываются и разницы между удалением массива и удалением одиночного объекта нет (и то - при использовании встроенных типов). Но по закону подлости, если сделать такую пакость, как в последнем примере, через несколько месяцев (лет?) вашу программу перекомпилируют на другом компиляторе или перегрузят операторы delete и.. лучше просто следовать известному в России принципу "заплати налоги - спи спокойно" под налогом подразумевая дополнительную проверку кода на такие вот тонкости.

    И последнее замечание по указателям: в Си динамически созданные объекты сами не удаляются. То есть, если вы создали какую-то переменную через alloc-функцию или new, она будет висеть в RAM до самого завершения работы программы. Такие маленькие недоработки называют утечками памяти и иногда они выливаются в полное исчерпание ресурсов системы и её зависание (в том случае если программа работает достаточно продолжительное время и регулярно "теряет" память).

    Что ж, я считаю указатели мы в общих чертах разобрали - можно переходить к тому, с чего начали - к строкам.

    Первое и самое главное, что вам необходимо понять - это что в Си++, равно как и в Си, нет типа данных "строка". Поняли? ну я и не сомневался, разберём по-подробней...

    Си - самый логичный с точки зрения архитектуры ЭВМ язык программирования. Практически всё, что можно сделать на ассемблере, можно сделать и на Си. Не верите? Смотрите:

Код

    unsigned char* pch = (unsigned char*)0xBC00;
    memcpy(pch, _T("Hello, World!"), 26);


    Знаете, что сделают эти две строчки, если пустить их сразу после инициализации Биос? Выведут вам на экран строку "Hello, World!". Мы можем откомпилировать этот код, записать в первый сектор дискеты и загрузиться с неё. Здорово? Если попытаться проделать тоже под ОС, очень вероятно, что ваша программа просто "грохнется", т.к. любая уважающая себя ОС не даёт кому ни попадя лазить в системную область RAM.

    В Си++ всё выглядит почти также. Отличие только в том, что преобразовывать числовую константу в указатель приходится через reinterpret_cast. Ну не любят этого плюсники! Разумеется, всё это сказано только для общего развития, подобные вещи принято и правильно писать на Ассемблере, так как только в нём можно наиболее эффективно работать с аппаратурой напрямую. На данном примере вы должны были уже понять, что строка в Си/Си++ - это последовательность кодов из заданной таблицы символов. Не правда ли логично? Загляните в текстовый файл hex-редактором и вы увидите нечто вроде этого:

    
Цитата
cf ff f2 b8 f0 ea e0 20 e7 e0 20 eb fe e1 ee e7
    ed e0 f2 e5 eb fc ed ee f1 f2 fc 21 3a 2d 29 20
    d1 e8 2b 2b 20 f0 f3 eb e8 f2 21 20 cd e8 ea ee
    ec f3 20 ed e5 20 e3 ee e2 ee f0 e8 21 3a 29 29


    Проще говоря, вы увидите набор кодов символов. Точно также строки хранятся в Си/Си++ переменных. Придумайте, как можно текст представить иначе и очень может быть, что вам дадут нобелевскую премию. Шутка!..

    Любая строка в Си - это массив символов. Всё остальное вытекает из этого постулата.

Код

    char str[] = {'S', 't', 'r', 'i', 'n', 'g'};// Статический массив (статическая строка)
    str[3] = 'o';// Нормально. Теперь str содержит "Strong"
    // Внимание - ВАЖНО!
    char* pStr = "Constant String";// Указатель на константную (!!!) строку!
    pStr[3] = 'A';// Ошибка! результат зависит от реализации...
    char* pStr2 = new char[15];// Вот так нужно создавать динамические строки
    strcpy(pStr2, "Dinamic String");// После этого pStr2 будет указывать на
    // динамическую область памяти, содержащую строку "Dinamic String"


    Как известно, строки в Си заканчиваются нулевым символом (символом, имеющим код 0). Любая строка, взятая в двойные кавычки по определению заканчивается этим символом и потому размер "Dinamic String" не 14, а 15. Это нужно для того, чтобы программа, использующая эту строку, знала, где она кончается. Можно, конечно, для каждой строки таскать свою переменную, в которой содержится число символов, но это очень неудобно в тех случаях, когда приходится оперировать десятками различных строк. В Pascal'е другой принцип - первый символ строки содержит не код первого символа, а число символов в строке. Но тогда для стандартного байтного набора символов максимально возможной длинной строки будет 255 символов, а этого бывает мало.

    Рассмотрим ещё пару примеров работы со строками в Си стиле и закончим эту главу.

Код

    char* pStr = new char[255];// Берём с запасом

    strcpy(pStr, "Тестовая строка");

    strcat(pStr, " #1");// Добавление к строке, pStr = pStr + " #1"
    strncpy(pStr, "String", 3);// Копирование n символов, pStr = "Str"
    strncat(pStr, "ings", 3);// Добавление n символов, pStr = pStr + "ing"
    size_t i = strlen(pStr);// Получение длины строки, i = 6
    strcmp(pStr, "string");// Сравнение строк, возвратит не 0 - разные строки
    stricmp(pStr, "string");// Сравнение без учёта регистра, возвратит 0
    // Сравнение первых n символов, также возвратит 0
    strncmp(pStr, "Strings", 6);
    char* pc = strchr(pStr, 'i');// Поиск первого вхождения, pc = pStr+3 = "ing"
    strcpy(pStr, "development");

    // Аналогично функции:
    strrchr// Поиск последнего вхождения
    strstr// Поиск первого вхождения строки
    strpbrk// Поиск первого вхождения одного из символов
    strspn// Число символов до любого символа из строки
    strcspn// Число символов до любого символа не из строки


Ну вот, на этом с Си строками можно закончить. Переходим к Си++. 

ЧАСТЬ 2. ПРОЦЕДУРНОЕ ПРОГРАММИРОВАНИЕ. ФУНКЦИИ, СТРУКТУРЫ, МАССИВЫ.

Вспоминая себя во времена моего изучения Си++, я часто удивляюсь, насколько логично проходило это изучение. На первых порах я брал чужие программы и пытался их модифицировать; классическая схема: сам пока ничего не можешь, но что-то творить хочется. Вскоре мне это, разумеется, надоело, я залез в книги и начал пробовать писать самостоятельно. Но как я писал - это заслуживает отдельного внимания! Я думаю, многие так начинали свой путь программиста...

Я не использовал классов. Я писал на Си++ лишь формально: в действительности же мои программы представляли собой набор функций - то, что даёт уже язык Си! Этот факт можно считать доказательством того, что начинать обучение Си++ следует не с классов, а с возможностей, составляющих фактически язык Си. Итак, процедурное программирование.

Для чего нужны программы? - вот самый простой по формулировке и самый сложный по своему смыслу вопрос программирования. Отвлечёмся от метафизических прений и опустимся на землю. Компьютеры необходимы для выполнения различных операций без участия человека. Когда-то, на заре информационных технологий, существовали машины, которые были созданны для определённых операций, причём функциональность таких машин определялась их строением. Например: строение лопаты определяет её назначение - копку, строение чайника - хранение вязких веществ и т.д. Конечно, с дуру можно в чайник засунуть голову, а лопатой попробовать писать вместо ручки.. если у вас возникают подобные мысли, то, как ни странно, программирование для вас идеально подходит! Почему - выяснится позже.

Итак, строение машин долгое время определяло их функции. Однако, за человеком давно замечена одна глупость: он всё пытается унифицировать. Иными словами, в один прекрасный день захотелось одному человеку объединить лопату и чайник в один предмет, причём так, чтобы можно было переключать их функциональность. Нажал кнопочку - чайник, нажал другую - лопата. Удобно!..

Так появились компьютеры. Да, это были, по сути своей, первые компьютеры. Устройство, которое следило за нажатием ваших кнопочек и переключением функций, фактически есть первая программа. Долго радовался человек на своё изобретение, однако и этому однажды пришёл конец. "Лучшее - враг хорошего" - говорят, и не зря...

Захотелось однажды человеку сделать так, чтобы можно было произвольно, в любой момент времени выбрать, как будет реагировать их машина, на нажатие той или иной кнопки. Что появилось? Правильно - перфокарты!.. Такие машины были уже полноценными компьютерами, потому как обладали не только постоянной памятью (коей являлась архитектура), но и переменной - перфокартами. Извините за столько затянувшееся бла-бла-бла, но теперь, мне кажется, вопросов вроде "что есть программа" должно не возникать - а это очень важно!..

Теперь посмотрим более современно. В любом компьютере существует исполнительное устройство (и часто - не одно), которое может выполнять фиксированный набор каких-то простых операций. Разберём это на примере нашего вездесущего чайника. Предположим, у нашей машины "автоматического чайника" есть следующий набор элементарных операций: открыть крышку, залить воду, включить нагреватель, выключить нагреватель, заорать "идиот, опять включил - и заснул!!!". Нам нужно составить несколько программ: кипичение воды без криков при окончании работы и с ними. Очевидно, что обе программы будут содержать блоки с одинаковыми наборами операций (открыть крышку, залить воду, включиться, выключиться) и только в конце второй программы будет приписано включение ора на всю ивановскую. "И что?" - скажем мы, "Не обломаемся продублировать эти одинаковые блоки!". Подождите, я же не договорил: у нас памяти на перфокарте только на шесть элементарных операций. Что делать будете?

Вот тут в дело вступают процедуры: мы обзываем наши первые четыре операции как "кипячение чайника" и прописываем отдельно, а в двух наших программах пишем только вызов этого блока: "закипятить чайник", "поорать". Функции отличаются от процедур только тем, что возвращают результат своих действий; например, после выполнения функции "закипятить чайник", можно куда-то записать, сколько времени происходило кипячение. Также, и функции и процедуры могут принимать параметры, например: "закипятить чайник водой с начальной температурой 20 градусов" - здесь параметром будет начальная температура; от начальных параметров, мы видим, будет меняться и возвращаемое значение - чем выше начальная температура, тем меньше времени потребуется на кипячение. А если мы пишем просто "закипятить чайник", то фактически мы предоставляем выбор начальной тепературы самой функции кипячения, мы не передаём параметра. Если функция способна сама выбрать это начальное значение при необходимости, то такой параметр называют параметром по умолчанию. Кстати, само разграничение понятий "процедура" и "функция" довольно условно и никто вас не имеет права щёлкнуть по носу, если вы вдруг употребить одно понятие взамен другого (хотя принято по возможности пользоваться вторым).

Программирование, основанное на функциях и процедурах и называют процедурным. Однако довольно часто в это понятие включают ещё кое-что. О том, что такое переменная и указатель говорилось в предыдущей статье, так что не имеет смысла повторяться. Переменные предоставляют возможность хранить информацию, но предоставляют очень примитивно: нам доступны лишь целые числа, строки и дробные числа. А как мы сможем описать параметры чайника? Для этого нам потребуется несколько переменных, но передавать их все отдельно в функции неудобно. Да и создавать придётся для каждого экземпляра чайника сразу несколько переменных.. но опять эти люди!..

Придумали обзывать набор переменных одним именем и предоставлять возможность создавать сразу несколько переменных по заранее сформированному шаблону. Такой шаблон назвали структурой. Структура подобна штампу, который заметно облегчает жизнь бухгалтерам и юристам: им не приходится каждый раз писать, им необходимо просто припечатать штамп к листку и на последнем сразу появится нужная надпись, да ещё и в рамочке! Структуры - продвинутые штампы, они содержат в себе нечто, что можно изменять (некоторые штампы тоже содержат пустые поля, куда можно что-нибудь вписать). Это заранее заранее "непропечатанное" нечто и есть переменные, как вы должны были уже понять. Вы указываете только тип и набор переменных. Вот пример:
Код

struct Chaynik
{
    char model[255];// Модель
    int gab_x, gab_y, gab_z;// Габаритные размеры
    // Бла-бла-бла...
};

Теперь мы не будем отдельно создавать переменные "модель", "габаритные размеры" и пр. для каждого чайника. Вместо этого мы напишем:
Код

struct Chaynik ch1;// "Штампуем".. :-)

Переменная ch1 уже содержит в себе переменные model, gab_x и пр. Нам нужно только использовать специальный оператор, чтобы обратиться к ним:
Код

strcpy(ch1.model, "Bosch CH3000");
ch1.gab_x = 90, ch1.gab_y = 90, ch1.gab_z = 200;

Структуры также могут быть указываемы; и тогда используется другой оператор для доступа к содержимому структуры:
Код

struct Chaynik* pch1 = malloc(sizeof(struct Chaynik));
pch1->gab_x = 90, cph1->gab_y = 90, pch1->gab_z = 200;

struct Chaynik практически полноценный тип, такой же как int, double и char. Он не умеет только того, что не определено явно. К примеру, компилятор не знает, как ему прибавить к чайнику что-либо. Поэтому данная операция в процедурном программировании существовать не может:
Код

ch1 += 5;
ch1 += *pch1;// Тоже самое - каким образом это сделать?

Задавая себе вопрос "как?" можно довольно точно определить, будет ли компилятор воспринимать вашу запись или нет, хотя иногда бывают неожиданности. В любом случае знайте: если после компиляции вашей гениальной программы компилятор покатывается со смеху, указывая на строчку в вашей программе, где производится какая-либо операция над структурой - значит компилятор такую операцию не понимает (заметим, что над тем, что они что-то не понимают, смеются только люди довольно глупые, но это не значит, что разработчики вашего компилятора одни из них).

Опустимся теперь на следующий уровень: разберём детали. Впервую очередь в процедурном программировании необходимо выяснить, каким образом происходит передача переменных в функции. Передача стандартных, "одиночных" переменных  и указателей должна быть понятна и так, а вот как обстоит дело с массивами и структурами?..

Человеческий ум убог и ничего принципиально нового придумать не может. Вместо этого он решает текущие проблемы уже изобретёнными инструментами, подстраивая их (тот самый случай, когда чайник используют вместо лопаты). Передача массивов и структур происходит банальной передачей указателя на них. Смотрите:
Код

int func(int* a, char* b)
{
    // ...
}

int func1(int a[], struct Chaynik b)
{
    // ...
}

int func2(int a[13], double b)
{
    // ...
}

Первые две функции с точки зрения ассемблера будут совершенно идеинтичным, различными у них будут только имена. Почему? Потому, что всё, что больше размера указателя (sizeof(int*)) обычно передаётся посредством указателя: сначала создаётся копия b, а потом в функцию отправляется только указатель на эту копию. int a[] и int* в Си же вообще идеинтичны, потому копия a создаваться не будет: вместо этого просто произойдёт передача указателя на первый элемент a. Казалось бы: где логика? На самом деле логика есть: когда мы объединили несколько переменных структурой в одну, мы тем самым сказали компилятору, чтобы он распространил на эту группу переменных теже правила, что действуют на обычные переменные (насколько это возможно, конечно). Одиночные переменные передаются "по значению" (то есть, передаётся не сам объект, а его копия - значение). Разделение же на int[] и int* в Си и Си++ чисто лексическое, компилятор интерпретирует и то однозначно за исключением случая создания переменной: тут указание константы в квадратных скобках явно свидетельствует об том, что будет создан статический массив. Также, было введено ещё одно удобное ограничение; взгляните на func2 - параметр a здесь будет передаваться точно также, как и в func и func1, но компилятор должен будет проверить, что вы передаёте ему массив именно из 13 элементов (разумеется, проверить это на этапе компиляции можно лишь при передаче статического объекта). Обычно правда компилятор языка Си разрешает нарушение этого правила и максимум - погрозит вам пальчиком при передаче указателя или статического объекта иного размера. В Си++ всё может быть значительно строже.

Вообще, передача параметров не устанавливается чётко ни стандартом Cи ни Си++: поведение может быть целиком и полностью определяемо компилятором. Но если вы не программируете микроконтроллеры или какие-нибудь экзотичные процессоры, вы вряд ли встретите серьёзные отступления от описанных правил.


 ! 
archimed7592
Любые просьбы решить задачку, помочь разобраться в проблеме и прочий оффтоп в этой теме будут неумолимо удаляться.
Для решения своих проблем создавайте отедльную тему.

Автор: 509harbore 21.9.2005, 20:24
ManiaK Объясните мне разницу между указателеми и ссылками. smile
Насколько я понял из прочтения вашей статьи. Что указатель это переменная, которая содержит адрес другой переменой.

Автор: Гость_ManiaK 21.9.2005, 23:12
Цитата(509harbore @ 21.9.2005, 20:24)
Насколько я понял из прочтения вашей статьи. Что указатель это переменная, которая содержит адрес другой переменой.

Именно так.

Цитата(509harbore @ 21.9.2005, 20:24)
ManiaK Объясните мне разницу между указателеми и ссылками.

Ссылка - это просто синоним для переменной.

Код

int a = 13;
int& sa = a;
sa = 15;// Тоже самое, что и a = 15


В ассемблерном коде (после компиляции) ссылок не существует. Это - абстракция, реально никакой памяти ссылки не занимают. Для чего они нужны? самый простой и в тоже время наиболее употребительный пример - передача объекта в функцию не копированием, а ссылкой (то есть передача самого объекта):

Код

void func(int i)
{
    i = 15;
}

void main(void)
{
    int a = 13;
    func(a);// a в этом примере не изменяется!!!
}


В данном примере при передаче а в функцию func сначала создаётся копия a и она уже отдаётся на растерзание, в то время как сам исходный объект не меняется. А вот так можно явно указать компилятору, что нужно использовать сам объект (точнее - ссылку, хотя это одно и тоже), а не его копию:
Код

void func(int& i)
{
    i = 15;// Вот теперь a примет значение 15
}

}

Автор: maxim1000 22.9.2005, 00:56
ссылка ближе к указателю, чем к пременной
в общем-то именно так она и реализована на уровне компилятора
просто везде, где она используется, компилятор дорисовывает звездочку smile
Код

int x;
int &y=x;
y++;
x=y;

при компиляции будет сначала переделано в такое:
Код

int x;
int *z=&x;
(*z)++;
x=(*z);

поэтому, когда мы пишем
Код

y=x;

на самом деле получается
Код

(*z)=x;

получается, что изменить значение адреса невозможно

единственное, чем отличается ссылка от указателя - инициализация:
Код

int &y=x;

означает занесение адреса x в y
и это навсегда (пока не выйдем из этого блока)...

Автор: 509harbore 22.9.2005, 20:32
ManiaK ,maxim1000 ,благодарю вас за столь хорошие отвтеты. smile
А для чего служит необходимость разыменования указателей? smile

Автор: Void 22.9.2005, 20:55
Цитата(509harbore @ 22.9.2005, 22:32)
А для чего служит необходимость разыменования указателей

Для того, чтобы получить доступ к объекту, на который они указывают. А зачем они тогда нужны были бы? smile

Автор: BlHol 3.5.2006, 12:37
Маленький вопрос:

Вот это понятно:

Код

TComponent *Obj;


Указатель на объект класса.


А вот это как понимать?:

Код

(TComponent *)Sender;



Заранее благодарен.
С уважением. 

Автор: ManiaK 30.5.2006, 16:49
BlHol, абсолютно никакой разницы нет. 

Автор: champion 22.6.2006, 17:37
Я правильно понял, что указатель это переменная, которая ссылается на адрес, по которому находится содержание переменной,  другой переменной?  И каков смысл применения указателей? Приведите пожалуйста в пример код, из которого было бы видно, где их используют. 
 В статье я увидел int *i=alloc(1000); что есть alloc?
 И все остальное из этой кухни, что приводится в пример? 

Автор: slava72 26.6.2006, 12:11
2ManiaK: продолжения похоже так и не дождутся новички ))

Тем не менее тема, почему не стоит пользоваться C-style строками в С++ ИМХО очень важна.
Итак попытка продолжения - разрешается пользоватся без ограничений, в т.ч. копировать, редактировать, etc. (например вставить после редактирвания в начальную статью).

Строки в стиле С++

В большинстве языков программирования строки включены в список базовых типов языка. Над ними определен список операций, таких как сравнение, конкатенация и т.д.

В С у нас есть только базовый набор функций над участками памяти, исходя из предположения,
что строка - это набор символов (char или wchar_t), заканчивающийся нулевым символом ('\0').

Основные недостатки этого подхода:
Мы должны заранее распределять память (неважно каким образом), достаточную для помещения в нее строки (+1 символ) при всех манипуляциях над ней.
Если память ваделяется динамически - не забыть ее вернуть системе.
Использовать в коде strcpy(a,b) вместо a+b, strcmp(a,b) < 0  вместо a < b и т.д., что затрудняет читабельность программы (и время написания ;)

Для избавления от этих недостатков в C++ введен стандартный класс std::string, с которым можно работать почти также как с обычным типом (например int), и не задумываться о распределении памяти - все за вас сделает компилятор.
Код

  typedef std::string MyStr; // для любителей

  MyStr str_list[100]; // обявляем массив строк
  MyStr prefix = "эта строка содержит\""; // объявляем строку и инициализируем ее
  MyStr suffix = "\";\n";
  str_list[0] = "А это 1-я строка массива";
  
  if(str_list[0] < suffix) { ... } // операция сравнения 2-х строк
  str_list[1] = prefix + str_list[0]; // конкатенация (объединение) 2-х строк
  str_list[1] += suffix; // то же самое, что str_list[1] = str_list[1] + suffix;
  ...
  str_list[99] = "the last";
  for(int i = 0; str_list[i] != "the last" ; ++i) {
    ...
  }
  const char* pc1 = str_list[3].c_str(); // получаем неизменяемую С-строку
  const char* pc2 = str_list[5].c_str();
  if((stricmp(pc1, pc2) == 0) {// проверяем равенство строк без учета регистра
  }

....
bool Find_stri(
  std::string dets&,      // изменяемый аргумент, прередается по ссылке
  const std::string src1,  // не изменяемый аргумент, прередается по ссылке
  std::string src2         // передача по значению - так не стоит делать
)
  


определено множество стандартных функция для работы со строками, при желании можно писать свои, но здесь есть одна тонкость - передача параметров.

Хотя внешне обект строка похож на обычный int, тем не менее для каждого нового объекта распределяется память, вызываются конструкторы/деструкторы и т.д.

Поэтому при написании своих функций в списоке аргументов желательно:
- Для входных параметров объявлять аргумент как const string param&
- для выходных (изменяемых) - как string param&

Использование строк в качестве char* (C-строк) - можно для функций, который _не_ модифицируют
строки (с использованием c_str() см. код).
Если же требуется буфер обмена с изменяемой памятью - его придется создавать самостоятельно,
но зато потом можно будет присваивать строке.

Зы: навеяно http://forum.vingrad.ru/index.php?showtopic=101829 

Автор: ManiaK 24.7.2006, 10:01
Цитата(champion @  22.6.2006,  17:37 Найти цитируемый пост)
В статье я увидел int *i=alloc(1000); что есть alloc?

Читаем внимательней, коментарии не зря ставил:
Код

int *i = alloc(1000);// [b]Хотим блок памяти в тысячу байт[/b]

Т.е.: данной функцией мы можем "попросить" у операционной системы дать нам память.

Цитата(champion @  22.6.2006,  17:37 Найти цитируемый пост)
Я правильно понял, что указатель это переменная, которая ссылается на адрес, по которому находится содержание переменной,  другой переменной?

Да. Причём не обязательно другой переменной. У вас в программе может существовать указатель на память, которой не соответствует ни одна переменная. Каким образом мы получим доступ к этой памяти? Только с помощью указателя на неё. Это ответ на следующий вопрос:
Цитата(champion @  22.6.2006,  17:37 Найти цитируемый пост)
И каков смысл применения указателей?


Цитата(champion @  22.6.2006,  17:37 Найти цитируемый пост)
Приведите пожалуйста в пример код, из которого было бы видно, где их используют. 

Пожалуйста! - 
Код

char* i = alloc(1000); // Получаем 1000 байт
FILE* file = fopen("myfile.txt", "rb");// Получаем указатель на данные файла
fread(i, 1000, 1, file);// Читаем из файла, на который указывает file 1 блок данных размером в 1000 байт


[оффтоп]
Подписался на тему, теперь буду отвечать во-время smile
[/оффтоп]

Добавлено @ 10:11 
Цитата(BlHol @  3.5.2006,  12:37 Найти цитируемый пост)
А вот это как понимать?:

Пардон, если вот так:

Код

int* Sender = alloc(1000);
TComponent * p = (TComponent *)Sender;


То это т.н. преобразование типов. Название страшное, но, как обычно, идея элементарная: в первой строке мы создаём переменную-указатель типом int, во второй строке - типом TComponent. Си++ отличается от Си тем, что довольно строго относится ко взаимодействиям объектов разного типа. Если бы мы написали вот так вторую строку:
Код

TComponent * p = Sender;

То компилятор заявил бы нам, мол, парень, очко протри: у тебя слева помидор, а справа огурец: ты их потом не перепутаешь? А то может у тебя алергия на помидоры, а ты съешь указатель на огурец, который на самом деле указывает на помидор.

Когда мы пишем перед объектом "(Type*)", мы фактически говорим компилятору: думай шо хошь, но тип этого объекта - указатель на Type. У меня всё под контролем, мол... 

Автор: NightmareZ 2.11.2006, 13:28
Цитата(Lokky @  31.8.2006,  00:51 Найти цитируемый пост)
1. enum (пречисления);

Код

enum MyEnum
{
   One, Two, Three
};

Таким образом определяем тип данных MyEnum, который может принимать три значения: OneTwo и Three.
При этом OneTwo и Three - это целочисленные константы.
Далее MyEnum можно использовать, например, так:
Код

MyEnum hello = One;

switch (hello)
{
  case One:
    cout << "One";
  break;
  case Two:
    cout << "Two";
  break;
  case Three:
    cout << "Three";
}


Цитата(Lokky @  31.8.2006,  00:51 Найти цитируемый пост)
2. struct (структуры) и чем они отличаются от enum;

Структуры же - это совокупность переменных, объединённых под одним именем. Если мы определим структуру вот так:
Код

struct MyStruct
{
  int One, Two, Three;
};

то сможем создавать переменные, содержащие в себе три значения (OneTwo и Three).
Обращаться к отдельным переменным внутри структуры нужно через точку:
Код

MyStruct hello;

hello.One = 10;


Цитата(Lokky @  31.8.2006,  00:51 Найти цитируемый пост)
3. union (объединения).

Объединения позволяют хранить переменные разного типа в одной области памяти.
Код

union MyUnion
{
    struct {unsigned char One, Two;} Four;
    unsigned short int Three;
};

MyUnion hello;
Three = 256;

Если unsigned short int занимает два байта, а unsigned char - один, то, после данного присваивания, переменная One будет содержать ноль, а Two - единицу.

Автор: Frekenbok 10.11.2006, 06:05
Со всеми этими обсуждениями вторая глава как-то осталась незамеченной. Или всем все понятно? Мне вот не все.
ManiaK, батенька, да вы философ! (я имею в виду чайники)  smile 
Вот мои вопросы:
  •  В структуре поля хранятся в соседних ячейках памяти наподобие массивов?
  •  
    Цитата(ManiaK @  7.11.2006,  15:29 Найти цитируемый пост)
    int func(int* a, char* b)

    Почему char* b? Потому что первое поле структуры типа char? 
    Почему тогда...
    Цитата(ManiaK @  7.11.2006,  15:29 Найти цитируемый пост)
    int func2(int a[13], double b)

    Или здесь во второй параметр передается уже не структура?
  •  Объясни, плиз, поподробнее разницу между "точками" и "стрелками" для доступа к полям классов (кстати, только к полям или к методам тоже?). В Delphi вот точка да и все. А здесь как? Или можно просто ссылочку полезную дать...



Автор: archimed7592 10.11.2006, 07:21
Цитата(Frekenbok @  10.11.2006,  07:05 Найти цитируемый пост)
 В структуре поля хранятся в соседних ячейках памяти наподобие массивов?
да в соседних ячейках, но только при выполнении определенных условий. точнее говоря, в общем случае между полями структуры могут быть "дырки"(маленькие - 1-7 байт).
но это не массивы. массив - это N элементов одного заданного типа. структура же может иметь много полей и все могут быть разных типов, в том числе и массивами. структура - это как мусорный контейнер (совсем не в плохом слысле, просто в голову больше ниче не приходит), а массив - как корзина для яиц.
кстати, помимо того, что структура может содержать массивы ещё не редко встречаются массивы из структур.

Цитата(Frekenbok @  10.11.2006,  07:05 Найти цитируемый пост)
Почему char* b? Потому что первое поле структуры типа char? 
вступление: передача параметров как правило производится через регистры (размер регистра 4 байта). если регистров не хватает, то передача параметров производится через стек.
ещё одно вступление: указатель - это почти в любом случае 4 байта - просто номер первого (нулевого) байта в памяти (для int - это номер младшего байта).
ответ: нет. ManiaK сказал, что с точки зрения ассемблера - это одинаковые ф-ции (лучше было сказать сигнатуры)...ф-ции то разные, но вот способ их вызова одинаковый. т. е. способ их вызова при хорошо сложившихся звёздах будет одинаковый. т. е., когда мы вызываем любую из этих ф-ций происходит следующее: первый аргумент кладётся в регистр1, второй - в регистр2. и в том и в другом случае второй аргумент - это указатель. в первом случае - на char, во втором - на Chaynik. но! с точки зрения ассемблера разницы между указателем на char и указателем на Chaynik нету. в первом случае всё понятно, а вот во втором: почему указатель, мы ж структуру передаем? вот собственно говоря та мысль, которую пытался донести ManiaK: при передаче малых объектов (до 4 байт) они передаются как есть т. е. их копия лежит в регистре, а для больших объектов делается следующее: создается копия на стеке и в регистре передается уже указатель, а не сам объект.

Цитата(Frekenbok @  10.11.2006,  07:05 Найти цитируемый пост)
Или здесь во второй параметр передается уже не структура?
во второй параметр передается не структура, но некий объект, размер которого превышает 4 байта (8 байт) и потому разницы между сигнатурами всех трех ф-ций не будет - в первой укзатель передается явно, в друих двух компилятор делает так, что ты не замечаешь как делается копия и неявно передается указатель на неё.

Цитата(Frekenbok @  10.11.2006,  07:05 Найти цитируемый пост)
Объясни, плиз, поподробнее разницу между "точками" и "стрелками" для доступа к полям классов. В Delphi вот точка да и все.
просто в делфи нет указателей на классы smile в делфи class - это и есть указатель на object (есть там такой, но про него мало кто знает smile) т. е. в делфи на уровне типов принимается решение каким образом получать доступ к объекту. в си это немного иначе. в си компилятор "ленится для твоего же блага" и не принимает решений на уровне типов. полагаю, что ты усвоил, что указатель - это номер байта в памяти, и чтобы получить эту самую память (на которую указывает указатель) нужно его прежде разыменовать: 
Код
int x = 5;
int *px = &x;
int y = *px; //<< звездочка перед px разыменовывает указатель и получает само значение x
дык вот. стрелочка - это просто упрощение синтаксиса: стрелочка - это два в одном
1. разыменовать
2. взять поле.
т. е.
Код
struct S
{
    int x;
    int y;
};

S s;

s.x = 0;
s.y = 0;

S *ps = &s;

ps->x = 5; // изменяем s.x - так проще
(*ps).y = 10; // изменяем s.y - так тоже просто, но смотрится не оч. красиво...

int result = s.x + s.y; // result = 15


Цитата(Frekenbok @  10.11.2006,  07:05 Найти цитируемый пост)
(кстати, только к полям или к методам тоже?)
к методам тоже. к любым членам.

Автор: Frekenbok 12.11.2006, 08:51
archimed7592, еще раз, для особо одаренных...  smile 

Цитата(archimed7592 @  10.11.2006,  07:21 Найти цитируемый пост)
int x = 5;
int *px = &x;
int y = *px; //<< звездочка перед px разыменовывает указатель и получает само значение x


Насколько я понимаю, указатель должен указывать на уже существующий объект (переменную и т.п.) или на NULL. Возьмем для примера класс TButton (С++ Builder). Я получаю доступ к полям и методам кнопки через указатель? А "где" сам объект? Компилятор создает объект и указатель на него, а я работаю только с указателем?

Автор: Daevaorn 12.11.2006, 10:48
Цитата(Frekenbok @  12.11.2006,  09:51 Найти цитируемый пост)
Насколько я понимаю, указатель должен указывать на уже существующий объект (переменную и т.п.) или на NULL.

Указаетль указывает на область памяти. Куда именно ему всё равно. Просто если там не будет объекта - будут проблемы
Цитата(Frekenbok @  12.11.2006,  09:51 Найти цитируемый пост)
Я получаю доступ к полям и методам кнопки через указатель?

Да
Цитата(Frekenbok @  12.11.2006,  09:51 Найти цитируемый пост)
А "где" сам объект? 

Находится по адресу накоторой указывает указатель
Цитата(Frekenbok @  12.11.2006,  09:51 Найти цитируемый пост)
Компилятор создает объект и указатель на него

Создает поле в классе формы с типом "указатель на объект кнопка"
Цитата(Frekenbok @  12.11.2006,  09:51 Найти цитируемый пост)
а я работаю только с указателем? 

Да

Автор: ManiaK 14.11.2006, 15:00
Цитата(Frekenbok @  12.11.2006,  08:51 Найти цитируемый пост)
Компилятор создает объект и указатель на него, а я работаю только с указателем?


Нифига подобного. В Си/Си++ компилятор вообще ничего сам не делает. ТЫ создаёшь указатель, ТЫ, если хочешь, создаёшь объект, ТЫ выбираешь с кем тебе работать - с указателем или с объектом. И, наконец, ТЫ выбираешь когда твой объект должен будет удалён из памяти. Ни один из этих пунктов не будет за тебя выполнен компилятором, если ты не создашь объект сам, а попытаешься обратиться к нему по указателю, то результат может быть самым неожиданным (в зависимости от того, что находится в данный момент времени в качестве значения указателя).

Автор: archimed7592 14.11.2006, 23:52
ManiaK, ты забыл сказать, что это касается динамических объектов. статические же компилятор переодически создает и уничтажает "без твоего ведома"...один из примеров - при передаче объекта в ф-цию компилятор вызывает конструктор копирования, по завершению ф-ции, вызывает деструктор...

Автор: Frekenbok 15.11.2006, 13:14
Цитата(ManiaK @  14.11.2006,  15:00 Найти цитируемый пост)
ТЫ создаёшь указатель, ТЫ, если хочешь, создаёшь объект,

ManiaK,  ты имеешь ввиду, если Я кидаю компонент на форму (Builder), то Я и создаю объект и указатель? 


Автор: archimed7592 15.11.2006, 21:37
угу. в codebehind сможешь найти что-то вроде Label1 = new Label1 (...); хотя, я совсем не исключаю, что у билдера со своими замутами и __published не будет такой строки, но по сути объект именно создается...

Автор: Frekenbok 16.11.2006, 12:36
как передать в функцию указатель на строку в качестве параметра-переменной (чтобы в функции он передвинулся и в main это было видно, но не глобальный указатель)? сама функция возвращает число (позиция указателя). 

Автор: archimed7592 16.11.2006, 12:58
Код

int func (char *&ptr)
{
ptr += strlen (ptr) > 5 ? 5 : strlen (ptr);
}

main ()
{
char *p = "abcdefgh", p2 = "abc";
func (p); func (p2);
printf ("%s\n%s\n", p, p2);
}

Автор: ManiaK 17.11.2006, 09:49
C языке Си придётся пользоваться указателем на указатель.

Добавлено @ 09:56 
Цитата(Frekenbok @  15.11.2006,  13:14 Найти цитируемый пост)
ManiaK,  ты имеешь ввиду, если Я кидаю компонент на форму (Builder), то Я и создаю объект и указатель? 

Я вообще про формы ничего не говорил. Если ты кидаешь компонент на форму - то, что там произойдёт зависит от компилятора, на уровне языка у тебя ещё ничего не будет. Я имел ввиду вот что:

Код

int* pi;// ТЫ создаёшь указатель
pi = new int;// ТЫ создаёшь объект
delete pi;// ТЫ удаляешь объект


Компилятор за тебя ни то, ни другое, ни третье делать не будет.

Код

int* pi;// создали указатель
*pi = 10;// Фигу! объекта-то ещё нет!


Вот что я, собственно, хотел сказать. Если есть какие-то непонимания в понятиях объект/указатель в Си/Си++ - то это уже вопрос к первой статье, по которой, покуда все молчат, я посчитал, всё понятно.

Цитата(archimed7592 @  14.11.2006,  23:52 Найти цитируемый пост)
ты забыл сказать, что это касается динамических объектов.

Да, конечно. Для статических нам нужно только изъявить желание и мы тут же получим объект и нам не нужно будет следить за тем, чтобы память им занимаемая где-нибудь не "улетела". Но это всё вроде было уже описано в первой статье. Если что-то непонятно - я поправлю. Тока скажите.

Автор: Frekenbok 18.11.2006, 05:07
Цитата(archimed7592 @  16.11.2006,  12:58 Найти цитируемый пост)
int func (char *&ptr)

archimed7592, спасибо, функция у меня есть. Интересовала именно передача параметра char *&ptr. Как-то еще не приходилось этим сочетанием пользоваться smile

Автор: archimed7592 18.11.2006, 16:12
Цитата(Frekenbok @  18.11.2006,  06:07 Найти цитируемый пост)
 Интересовала именно передача параметра char *&ptr
вот тебе и передача...оттуда же
Цитата(archimed7592 @  16.11.2006,  13:58 Найти цитируемый пост)
char *p = "abcdefgh";
func (p);


Автор: drLans 2.12.2006, 15:56
Люди поясните, плз, в чем отличие динамического выделения памяти от обычного задания того же массива в тексте программы?

Автор: Partizan 2.12.2006, 17:15
Цитата(drLans @ 2.12.2006,  15:56)
Люди поясните, плз, в чем отличие динамического выделения памяти от обычного задания того же массива в тексте программы?

разные области памяти задействуются...
при объявлении массив в тексте программы в compile-time память под него выделяется в стэке...
динамическая память выделяется из кучи...

Автор: nickless 2.12.2006, 17:35
Цитата(drLans @ 2.12.2006,  14:56)
Люди поясните, плз, в чем отличие динамического выделения памяти от обычного задания того же массива в тексте программы?

А еще динамически выделенную память нужно не забывать освобождать 

Автор: drLans 2.12.2006, 17:54
Partizan
так стек по идее быстрее.  smile 


Daevaorn
не могу пока.  smile 
Ибо не вдавался в особенности (и главное смысл) использования new/delete.

Я не понимаю, что может быть лучше этого:
Код

int massiv[] = {1,2,3};

Автор: Daevaorn 2.12.2006, 18:12
Цитата(drLans @  2.12.2006,  18:54 Найти цитируемый пост)
так стек по идее быстрее.  

Гораздо.
Цитата(drLans @  2.12.2006,  18:54 Найти цитируемый пост)
Я не понимаю, что может быть лучше этого:

А когда не знаешь зарание количество элементов? Тут то и пригождается динамическое выделение.

Автор: maxim1000 2.12.2006, 19:46
в общем смысле динамическое использование памяти нужно для контроля времени жизни объекта
при использовании стека если объект1 создан раньше объекта2, то уничтожен он будет позже
именно из этого ограничения исходит скорость работы - функции выделения освобождения памяти зачастую сводятся к одной команде процессора, а проблемы поиска и фрагментации не возникают в принципе
(ну ещё на скорость влияет то, что чаще всего стек находится в кеше, а значит не нужно его ниоткуда доставать, но это уже зависит от платформы)

если вернуться к C++, то ещё одно достоинство (которое уже упоминали) - автоматическое удаление выделенного объекта при выходе из блока, так что забыть это сделать невозможно

лично я стараюсь по минимуму использовать динамическую память и другое выделение ресурсов, которые потом нужно явно освобождать, только когда без этого сложно обойтись (как уже привели пример с неизвестным количеством элементов) или это сильно повышает эффективность (когда она критична), но даже в этих случаях (особенно когда программа сложная) не помешает упаковать управление этими ресурсами в какой-нибудь класс, который будет следить за ними (для всяких массивов чаще всего подходит std::vector)...

Автор: UnrealMan 2.12.2006, 20:39
Цитата(drLans @  2.12.2006,  15:56 Найти цитируемый пост)
Люди поясните, плз, в чем отличие динамического выделения памяти от обычного задания того же массива в тексте программы? 

А под статически объявленный массив память тоже может выделяться динамически (если он объявлен как член класса и память под объект такого класса выделяется динамически).

По поводу отличий ничего говорить не буду, ибо вопрос – суть боян. В И-нете полно литературы – читай и просвещайся. Всё равно по-нормальному научиться чему-то без книг, задавая подобные вопросы на форумах, вряд ли получится.

http://kak-tot.narod.ru/ru/otd-bs2e/p3p2p6.htm

Цитата(maxim1000 @  2.12.2006,  19:46 Найти цитируемый пост)
при использовании стека если объект1 создан раньше объекта2, то уничтожен он будет позже

Необязательно. Из-за оптимизации это правило может быть нарушено, например:

Код
struct A
{
    A() { static int i; cout << "A()  a" << (n = ++i) << endl; }
    ~A() { cout << "~A() a" << n << endl; }
    int n;
};

A func()
{
    A a1;
    A a2;
    return a2;
}

int main()
{
    A a = func();
}

вывод (MinGW-3.4.4):

Код
A()  a1
A()  a2
~A() a1
~A() a2

– т.е. объект a2 функции func может быть создан прямо на месте объекта a в функции main и уничтожен только по завершении main.

Автор: ali01 24.8.2007, 04:52
А где же про классы? есть вопрос если в классе создаются динамические переменные, в деструкторе надо вызывать delete?

Автор: bsa 24.8.2007, 09:35
Цитата(ali01 @ 24.8.2007,  04:52)
А где же про классы? есть вопрос если в классе создаются динамические переменные, в деструкторе надо вызывать delete?

Если где-то сделан new, то обязательно должен быть выполнен соответствующий delete. Иначе никак. И не важно где, в классах, структурах или функциях.

Автор: ali01 24.8.2007, 21:24
Деструктор сам этого не делает? А когда автоматически создаешь класс например в VC там создается пустой конструктор, я так понял надо самому там прописывать delete?

Автор: Daevaorn 24.8.2007, 21:47
Цитата(ali01 @  24.8.2007,  22:24 Найти цитируемый пост)
Деструктор сам этого не делает? 

нет. delete он не вызывает для динамических объектов.
Цитата(ali01 @  24.8.2007,  22:24 Найти цитируемый пост)
А когда автоматически создаешь класс например в VC там создается пустой конструктор, я так понял надо самому там прописывать delete? 

по логике скорей всего не в конструктор, а в деструктор.

Автор: ali01 24.8.2007, 23:51
Да, запутался. Я понял, спасибо. Раньше думал деструктор сам все сделаетsmile

Автор: ali01 2.9.2007, 03:29
Цитата(bsa @ 24.8.2007,  09:35)
Цитата(ali01 @ 24.8.2007,  04:52)
А где же про классы? есть вопрос если в классе создаются динамические переменные, в деструкторе надо вызывать delete?

Если где-то сделан new, то обязательно должен быть выполнен соответствующий delete. Иначе никак. И не важно где, в классах, структурах или функциях.

Просматривал код wxFormBuilder, пример конструктора: 
Код

MainFrame::MainFrame(wxWindow *parent, int id)
: wxFrame(parent,id,wxT("wxFormBuilder v.0.1"),wxDefaultPosition,wxSize(1000,800))
{

        wxIcon ico;
        ico.CopyFromBitmap( AppBitmaps::GetBitmap(wxT("app")));
        SetIcon( ico );

        wxString date(wxT(__DATE__));
        wxString time(wxT(__TIME__));
        SetTitle(wxT("wxFormBuilder (Build on ") + date +wxT(" - ")+ time + wxT(")"));

        SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_BTNFACE));

        wxMenu *menuFile = new wxMenu;
        menuFile->Append(ID_NEW_PRJ, wxT("&New"), wxT("create an empty project"));
  
[skip]

        wxMenu *menuEdit = new wxMenu;
        menuEdit->Append(ID_UNDO, wxT("&Undo \tCtrl+Z"), wxT("Undo changes"));

[skip]

        wxMenu *menuHelp = new wxMenu;
        menuHelp->Append(ID_ABOUT, wxT("&About...\tF1"), wxT("Show about dialog"));


        // now append the freshly created menu to the menu bar...
        wxMenuBar *menuBar = new wxMenuBar();
        menuBar->Append(menuFile, wxT("&File"));
        menuBar->Append(menuEdit, wxT("&Edit"));
        menuBar->Append(menuHelp, wxT("&Help"));

        // ... and attach this menu bar to the frame
        SetMenuBar(menuBar);
        wxBoxSizer *top_sizer = new wxBoxSizer(wxVERTICAL);

        ///////////////

        wxSplitterWindow *v_splitter = new wxSplitterWindow(this,-1,wxDefaultPosition,wxDefaultSize, wxSP_3DSASH | wxSP_LIVE_UPDATE);
        wxPanel *left = new wxPanel(v_splitter,-1);//,wxDefaultPosition, wxDefaultSize,wxSIMPLE_BORDER);
        wxBoxSizer *left_sizer = new wxBoxSizer(wxVERTICAL);

        wxPanel *right = new wxPanel(v_splitter,-1);
        v_splitter->SplitVertically(left,right,300);

        wxSplitterWindow *h_splitter = new wxSplitterWindow(left,-1,wxDefaultPosition,wxDefaultSize, wxSP_3D | wxSP_LIVE_UPDATE);//wxSP_BORDER);

        wxPanel *tree_panel = new wxPanel(h_splitter,-1);
        Title *tree_title = new Title(tree_panel,wxT("Object Tree"));

[skip]

        toolbar->Realize();

        SetSizer(top_sizer);
//      top_sizer->SetSizeHints(this);
        SetAutoLayout(true);
        Layout();
        Fit();

        //SetSize(wxSize(1000,800));
        RestorePosition(wxT("mainframe"));
        //Centre();
        Refresh();

        // añadimos el manejador de las teclas rápidas de la aplicación
        // realmente este es el sitio donde hacerlo ?????
        //m_objTree->AddCustomKeysHandler(new CustomKeysEvtHandler(data));
        AppData()->AddHandler( this->GetEventHandler() );
};



и деструктора:
Код


MainFrame::~MainFrame()
{/*
 #ifdef __WXFB_DEBUG__
 wxLog::SetActiveTarget(m_old_log);
 m_log->GetFrame()->Destroy();
 #endif //__WXFB_DEBUG__
 */

        // Eliminamos los observadores, ya que si quedara algún evento por procesar
        // se produciría un error de acceso no válido debido a que los observadores
        // ya estarían destruidos

        AppData()->RemoveHandler( this->GetEventHandler() );
}


Там вообще нет delete. Или может я что-то не понял? 

Автор: archimed7592 2.9.2007, 03:55
ali01, посмотри деструкторы базовых классов. Там(чуть ли не на самом верху иерархии) должны быть примерно следующие строки:
Код
for (SomeContainer::iterator i = children.begin(), end = children.end(); i != end; ++i)
    delete *i;

Во всяком случае в известных мне тулкитах(Qt, VCL) все компоненты хранятся и удаляются единообразно(т.о. позволяя не заботиться о MM... хорошо это или плохо - вопрос другой темы).

Автор: archimed7592 28.12.2007, 15:34

M
archimed7592
Оффтопик и личная переписка удалены.
Одна конструктивная беседа выделена в тему расширение динамического массива.

Спасибо zkv за то, что подчистил тему :).

Автор: anatox91 27.1.2008, 19:06
ManiaK, большое спасибо за интересную статью и своеобразный стиль изложения  smile 

Тема разделена: http://forum.vingrad.ru/index.php?show_type=forum&showtopic=194329.

Автор: Annuta 27.8.2008, 15:04
У меня такой вопрос - есть ли разница между этими двумя объявлениями. Какое из них более рационально  ?
TMinuit *min = new TMinuit(6);    
TMinuit min(6);

Автор: bsa 27.8.2008, 15:19
Цитата(Annuta @ 27.8.2008,  15:04)
У меня такой вопрос - есть ли разница между этими двумя объявлениями. Какое из них более рационально  ?
TMinuit *min = new TMinuit(6);    
TMinuit min(6);

Второе.
Если есть желание обсудить этот вопрос, то лучше создать отдельную тему

Автор: XeS 30.8.2008, 01:12
Маленький вопросик по работе с указателями

Код

int* p[10];


это как я понимаю массив из 10 указателей, т.е я с ним работаю так

Код

p[0] = new int;
p[1] = new int;
delete p[0];
delete p[1];


а конструкция такого вида

Код

int (*p)[10]


это указатель на массив из 10 int

я с ним работаю так

Код

p = new int;
p[0] = 1;
p[1] = 2;
delete p;


я прав????

Автор: J0ker 14.10.2008, 03:11
Цитата(XeS @  30.8.2008,  01:12 Найти цитируемый пост)
я прав???? 

нет
можно так:
Код

int (*p)[10];
p = reinterpret_cast<int(*)[10]>(new int[10]);
p[0] = 1;
p[1] = 2;
delete[] p;


технически int* и int(*)[n] - разные типы, и данная запись несколько бессмысленна... ну разве что вам надо передавать p в функцию принимающую конкретно int(*)[n]
но той-же функциональностью обладает следующий код:
Код

int *p = new int[10];
p[0] = 1;
p[1] = 2;
delete[] p;

Автор: UnrealMan 16.10.2008, 13:44
Цитата(J0ker @  14.10.2008,  04:11 Найти цитируемый пост)
можно так

Проще так:

Код
p = new int[1][10];

Автор: boombasta 30.10.2008, 12:51
привет всем!

что-то немогу найти ответов на 2 моих вопроса:
1. че отличается 
Код
CoreInterface* _core;

от
Код
CoreInterface *_core;


2. что здесь написано:
Код
getObject = (PluginInterface*(*)(CoreInterface*)) lib->resolve("getObject");

что за конструкция такая PluginInterface*(*)??

3. и здесь 
Код
PluginInterface* (*getObject)(CoreInterface*);

тут типа обьявление переменной getObject но не понятно что за (CoreInterface*)?

З.Ы. PluginInterface и CoreInterface - это классы

Автор: bsa 30.10.2008, 16:31
1. ничем не отличается.
2 и 3. Это тип указателя на функцию, которая принимает указатель на CoreInterface и возвращает указатель на PluginInterface. Соответственно, PluginInterface* (*getObject)(CoreInterface*) - это указатель на функцию.

Я, например, всегда делаю так, чтобы не вводить никого в замешательство:
Код
typedef PluginInterface* (*GetObjectProcPtr)(CoreInterface*); //создаем новый тип "указатель на функцию"
GetObjectProcPtr getObject; //определяем указатель этого типа

Автор: boombasta 30.10.2008, 17:30
Код
PluginInterface* (*getObject)(CoreInterface*);    
getObject = (PluginInterface*(*)(CoreInterface*)) lib->resolve("getObject");

со строкой 1 понятно (спсб bsa ;)) обьявляется указательна функцию, которая в качестве аргумента принимает указатель на CoreInterface, а возвращает указатель на PluginInterface.

а что происходит в строке 2?
насколько я понимаю lib->resolve("getObject") возвражает ссылку на функцию?
а что такое вот это: (PluginInterface*(*)(CoreInterface*)) ? - это жжет мне мозг  smile  как-то это можно переписать по проще - пусть больше строк, но как-то понятнее smile (или хотябы просто по-другому, может так будет понятнее smile)

дольше по тексту getObject юзается как обычная функция:
PluginInterface* getObject(CoreInterface*);

Автор: bsa 30.10.2008, 18:43
Цитата(boombasta @ 30.10.2008,  17:30)
а что такое вот это: (PluginInterface*(*)(CoreInterface*)) ? - это жжет мне мозг  smile

Это преобразование к типу "указатель на функцию" - PluginInterface* (*)(CoreInterface*).
Что делать для того, чтобы было понятно я показал:
Код
typedef PluginInterface* (*GetObjectProcPtr)(CoreInterface*); //создаем новый тип "указатель на функцию"
GetObjectProcPtr getObject; //определяем указатель этого типа
getObject = (GetObjectProcPtr) lib->resolve("getObject");


Вообще-то обобщенно тип "указатель на функцию" выглядит так: <return_type> (*)(<arg1_type>, <arg2_type>, ...)
Сам указатель (переменная типа "указатель на функцию") определяется так: <return_type> (*<var_name>)(<arg1_type>, <arg2_type>, ...)

P.S.: почитай книжку для начинающих

Автор: Goliaf777 2.12.2008, 17:14
Вопрос.Почему нужно  using namespace std если в приводимых кодах на форуме пишется std::cout.Объясните ЧаЙнИгУ!!))) smile 

Автор: bsa 2.12.2008, 20:42
Цитата(Goliaf777 @ 2.12.2008,  17:14)
Вопрос.Почему нужно  using namespace std если в приводимых кодах на форуме пишется std::cout.Объясните ЧаЙнИгУ!!))) smile

по-хорошему, надо писать std::cout, std::cin, std::cerr, std::endl... Но если тебе лень и пишешь маленькую программу, которую не собираешься в дальнейшем улучшать, то можно написать после всех инклюдов using namespace std, и это избавит тебя от необходимости писать приставку std::. Если у тебя нет желания писать std, но не хочется глобально раскрывать стандартное пространство имен, то можешь использовать using namespace std внутри функции (точнее, внутри почти любых операторных скобок). Как вариант, можно раскрыть только необходимые элементы пространства имен:
Код
using std::cout;
using std::endl;
После этого можно использовать cout и endl без указания std::.

Автор: Goliaf777 2.12.2008, 21:22
Огромное спасибо!!! smile 

Автор: XeS 20.4.2009, 23:57
Помогите разобраться, почему не меняеться адрес переменной, допустим есть такой код:

Код

void f(int* b)
{
      int* i = new int;
      
      *i = 321;

      b = i;
}

int main()
{
       int* a = new int;
       
       *a = 123;
      
       f(a);

       printf("%a", *a);

}


на экране будет 123, а не 321, а если я перепешу код вот так то все будет норм:

Код

void f(int** b)
{
      int* i = new int;
      
      *i = 321;

      *b = i;
}

int main()
{
       int* a = new int;
       
       *a = 123;
      
       f(&a);

       printf("%a", *a);

}


с чем это связано и почему? smile

и второй вопрос:

пусть у меня есть переменные такого типа:

int*** a;
int* b;

что бы привести b к типу a, я должен сделать такое:

int*** a;
int** c;
int* b;

c = &b;
a = &c;

можно ли это сделать по другому, что-бы меньше писать? smile

Автор: bsa 21.4.2009, 13:12
XeS
Код
void f(int* b) //b - копия это указателя, который ты передаешь в качестве аргумента
{
      int* i = new int;
      *i = 321;
      b = i;
}
Чтобы можно было изменить аргумент, то нужно сделать b ссылкой:
Код
void f(int* &b) //только для С++!
{
      int* i = new int;
      *i = 321;
      b = i;
}
или так:
Код
void f(int* *b) //вызывать надо так: int *a = new int; f(&a);
{
      int* i = new int;
      *i = 321;
      *b = i;
}
Цитата(XeS)
пусть у меня есть переменные такого типа:

int*** a;
int* b;

что бы привести b к типу a...
По хорошему, не нужно приводить b к типу a! Но если так неймется, то только тем способом, что сам ты и написал. Только это не приведением "типов". Потому что фактически ты не передаешь значение b. Ты передаешь указатель на переменную, которая кодержит указатель на b;
Обычно, такие конструкции можно встретить в качестве параметров функций (когда функция может изменить значение аргумента) или для определения многомерных динамических массивов.

Автор: master123 1.5.2009, 20:08
Спс... Мне помогло
________________________________
http://linecinema.ru/

Автор: XAKERs89 26.8.2009, 20:52
можно ли создать массив структур с помощью указателей?
Код

struct date *y=new struct date[3];
 

Автор: andrew_121 26.8.2009, 21:01
XAKERs89, Ссылок? Или указателей?
Уточните вопрос.

Автор: XAKERs89 26.8.2009, 21:12
оЙ..сори запутался (((  указателей

Автор: andrew_121 26.8.2009, 21:30
Код

struct date **y=new struct date*[3];


Автор: ПанкПрогаммист 25.9.2009, 13:42
Нефига не понял)) сушствует книга с++ для чаиников??  smile 

Автор: bsa 25.9.2009, 13:48
ПанкПрогаммист, попробуй тут посмотреть: http://forum.vingrad.ru/forum/topic-269794/kw-faq-c++.html

Автор: Axsandr 28.3.2010, 18:25
Цитата(ПанкПрогаммист @ 25.9.2009,  13:42)
Нефига не понял)) сушствует книга с++ для чаиников??  smile

язык программирования С++ Бьерн Страуструп 
В принципе очень доступно все написано....


_____________________
Для тех, кто интересуется http://ru.ikonfx.com есть выгодные условия торговли.

Автор: EgoBrain 26.6.2012, 12:07
Цитата(maxim1000 @ 22.9.2005,  00:56)
ссылка ближе к указателю, чем к пременной
в общем-то именно так она и реализована на уровне компилятора
просто везде, где она используется, компилятор дорисовывает звездочку smile
...

А что происходит при использовании ссылки на сложные типы данных, например структуры?
Компилятор меняют ссылку на указатель, и вместо оператора "." вставляет оператор "->"?

Автор: baldina 26.6.2012, 12:46
ничего он не меняет. maxim1000 имел в виду семантику, связанную с реализацией, но не саму реализацию.
впрочем, если считать, что в ассемблерном виде может (и скорее всего будет) использоваться косвенная адресация, то можно и так сказать

Автор: kolesnle 3.6.2013, 18:32
Цитата(EgoBrain @  26.6.2012,  12:07 Найти цитируемый пост)
Компилятор меняют ссылку на указатель, и вместо оператора "." вставляет оператор "->"? 

Код

(*pos).y ...

Автор: rvpxbnkx 18.7.2013, 16:44

Поясните пожалуйста. Программа на си. Я так понимаю массив это указатель на первый элемент + число элементов. В примере char  *dic[][40] получается трехмерный массив. Потом он приобразуется с p = (char **)dic;
Вот я и не понимаю почему трехмерный массив станет 2-х мерным. И еще Не совсем понятно чем такая конструкция char (*p)[10]; отличается от char *s[]

Код
/* Простой словарь. */
#include <stdio.h>
#include <string.h>
#include <ctype.h>

/* список слов и их значений */
char  *dic[][40] = {
  "атлас", "Том географических и/или топографических карт.",
  "автомобиль", "Моторизоравонное средство передвижения.",
  "телефон", "Средство связи.",
  "самолет", "Летающая машина.",
  "", ""  /* нули, завершающие список */
};

int main(void)
{
  char word[80], ch;
  char **p;

  do {
    puts("\nВведите слово: ");
    scanf("%s", word);

    p = (char **)dic;

    /* поиск слова в словаре и вывод его значения */
    do {
      if(!strcmp(*p, word)) {
        puts("Значение:");
        puts(*(p+1));
        break;
      }
      if(!strcmp(*p, word)) break;
      p = p + 2;  /* продвижение по списку */
    } while(*p);
    if(!*p) puts("Слово в словаре отсутствует.");
    printf("Будете еще вводить? (y/n): ");
    scanf(" %c%*c", &ch);
  } while(toupper(ch) != 'N');

  return 0;
}

Автор: bsa 29.7.2013, 17:52
char *dic[][40] массив массивов по 40 указателей на char. char ** - это указатель на указатель на char. Т.е. когда идет это преобразование ты отказываешься от одного измерения.
Обычно, подобные вещи делают так: const char *dic[] = { ... };

А вообще, почитай про указатели и ссылки еще http://forum.vingrad.ru/index.php?show_type=forum&showtopic=269794&kw=faq-c++.

Автор: eses 30.7.2013, 13:27
Модератор: Сообщение скрыто.

Автор: gendalf7771 22.11.2013, 01:45
Привет всем.
Есть незамысловатый код:
Код

int main()
{
   ... //здесь создал разносортные указатели и выделил им память
   ...
   if(...)//проблема, надо закрыть приложение
   {
      ... //надо освободить память
      return 1;
   }
   ...
   if(...)//проблема, надо закрыть приложение
   {
      ... //надо освободить память
      return 1;
   }
   ...
   if(...)//проблема, надо закрыть приложение
   {
      ... //надо освободить память
      return 1;
   }
   ...
   ... //освобождаю память для всех указателей
   return 0;
}


Как бы так покомпактнее сделать, чтобы в этих if'ах не копипастить освобождение памяти для всех указателей? Я пытался с запретным goto прыгать в конец программы сразу к освобождению, да не заладилось чегой-то...
И если в этих условиях вообще о таком не заикаться, то будет ли утечка памяти?

Автор: baldina 22.11.2013, 01:52
в main() уже ничего не надо освобождать, ОС освободит.
ну а вообще - сделайте локальный объект, освобождайте в деструкторе. при возврате из функции деструктор вызовется автоматически.

Автор: gendalf7771 22.11.2013, 02:03
baldina, то есть если, скажем, у меня в программе большущие динамические массивы char наклёпаны, то в рамках main() можно в принципе забить на delete?

И вопрос вдогонку. Код:

Код

char* anyFunc(...)
{
   ...
   char *szTemp = new *char[iSize];
   strcpy(szTemp, szTemp2);
   return szTemp;
}


Вот бывает иногда параметров у функции итак навалом, ну не хочу я szTemp передавать среди прочих, а на выходе функции массив получить надо. Есть ли адекватный способ получить желаемый массив с нормальный освобождением памяти?

Автор: bsa 22.11.2013, 10:25
gendalf7771, если ты можешь использовать возможности C++11, то рекомендую использовать std::unique_ptr:
Код
std::unique_ptr<MyClass> ptr1(new MyClass);
std::unique_ptr<int> ptr2(new int[100]);
Если ты ограничен только C++03 или описанный выше подход не нравится, то тогда можно использовать std::vector и std::auto_ptr (только очень внимательно прочитай документацию на этот класс).

Автор: baldina 22.11.2013, 10:26
delete в main() дань хорошему стилю, если не сделать, отладчик будет показывать утечки памяти.
но т.к. приложение завершается, и ОС освободит память, занятую приложением, с этой точки зрения этот delete ничего не дает.

Цитата(gendalf7771 @  22.11.2013,  02:03 Найти цитируемый пост)
адекватный способ получить желаемый массив с нормальный освобождением памяти

возвращать объект. например, http://www.cplusplus.com/reference/memory/unique_ptr/

Автор: gendalf7771 22.11.2013, 20:59
Большое спасибо за ответы!

Автор: TarasProger 12.8.2015, 15:54
Цитата(ManiaK @  9.8.2005,  14:35 Найти цитируемый пост)
Как известно, строки в Си заканчиваются нулевым символом (символом, имеющим код 0). Любая строка, взятая в двойные кавычки по определению заканчивается этим символом и потому размер "Dinamic String" не 14, а 15. Это нужно для того, чтобы программа, использующая эту строку, знала, где она кончается. Можно, конечно, для каждой строки таскать свою переменную, в которой содержится число символов, но это очень неудобно в тех случаях, когда приходится оперировать десятками различных строк. В Pascal'е другой принцип - первый символ строки содержит не код первого символа, а число символов в строке. Но тогда для стандартного байтного набора символов максимально возможной длинной строки будет 255 символов, а этого бывает мало.
Количество символов можно хранить в двух первых байтах, и в четырёх, и в восьми, и даже в sizeof(char*) первых байт, чтоб уж точно хватило на любую возможную строку. Проблема здесь другая:
1. Если на одной платформе будет количество символов в 4-х первых байтах, а на другой - в восьми, и такая строка будет как есть сохранена в бинарный файл, или передана по сети, то код первого символа точно прочитается не правильно, а может быть не правильно интерпретируется и количество символов.
2. Если на одной платформе один порядкок байт, а на другой - совсем другой, то не правильно интерпретируется количество символов.
Терминальный же ноль в этом плане универсален. И что неудобного в структуре 
Код
struct 
{
 int count;
 char *p;
};
?

Добавлено через 7 минут и 40 секунд
Цитата(ManiaK @  9.8.2005,  14:35 Найти цитируемый пост)
   Си - самый логичный с точки зрения архитектуры ЭВМ язык программирования. Практически всё, что можно сделать на ассемблере, можно сделать и на Си. Не верите? Смотрите:

Выделить всёкод C++
1:
2:
    
    unsigned char* pch = (unsigned char*)0xBC00;
    memcpy(pch, _T("Hello, World!"), 26);

Вот только вызов функции, возможно на языке ассемблера написанной, - не показатель. А попробуйте помимо функций и асм-вставок вызхвать программное прерываение, или присвоить значение регистру процессора. Или соверешить переход в зависимости не от результата сравнения, а от флага результата арифметической операции, например, в случае переполнения. На c нельзя делать вообще ничего, что можно делать на языке ассемблера. А так то свой memcpy можно написать и для паскаля, и для бейсика, и для любого другого языка, допускающего побочные эффекты подпрограмм, даже стекового.

Добавлено через 14 минут и 50 секунд
Цитата(ManiaK @  9.8.2005,  14:35 Найти цитируемый пост)
Разумеется, всё это сказано только для общего развития, подобные вещи принято и правильно писать на Ассемблере, так как только в нём можно наиболее эффективно работать с аппаратурой напрямую.
И это на фоне утверждения о том, что на c якобы можно делать всё тоже самое. Эффектичность чистого c кода, кстати, обязана быть максимально возможной, это было главное требование ТЗ на язык. Максимальная эффктивность кода + возможность делать всё тоже самое, что и на языке ассемблера = на ассемблере нельзя ничего делать эффективнее, в лучшем случае столько же эффективно, но не эффективнее. Но почему то c нуждается в поддержке вставок на языке ассемблера.

Автор: TarasProger 12.8.2015, 16:15
Цитата(ManiaK @  9.8.2005,  14:35 Найти цитируемый пост)
Вспоминая себя во времена моего изучения Си++, я часто удивляюсь, насколько логично проходило это изучение. На первых порах я брал чужие программы и пытался их модифицировать; классическая схема: сам пока ничего не можешь, но что-то творить хочется. Вскоре мне это, разумеется, надоело, я залез в книги и начал пробовать писать самостоятельно. Но как я писал - это заслуживает отдельного внимания! Я думаю, многие так начинали свой путь программиста...
Я первые месяца полтора не то что изменить что либо в чужой программе, а даже понять в них хоть что то не мог, а мог как раз только писать сам. Потом со знанием алгоритмов и их классов пришло и умение читать чужие программы. Умение же что то менять в чужих программах пришло только лет через 7 с умением абстрагироваться от конркретного стиля, а раньше я мог только прочитать, полностью переписать и тогда уже дополнять.

Автор: TarasProger 14.8.2015, 09:41
Цитата(ManiaK @  9.8.2005,  14:35 Найти цитируемый пост)
тут указание константы в квадратных скобках явно свидетельствует об том, что будет создан статический массив.
Хочу отметить, что "статический массив" здесь - не синоним "статическая переменная, являющаяся массивом", хотя массив - тоже переменая. Массивы бывают:
1. Константные, в этом случае и количество элементов массива - константа, и сами элементы - константы.
2. Статические, в этом случае количество элементов массива - константа, но сами элементы - переменные. Весь массив при этом может лежать на стеке, если это не противорчит ни стандарту языка, ни спецификации конкретного диалекта, тогда при слишком большой глубине рекурсии рекурсивной функции с локальным массивом может происходить переполнение. В программах на Object Pascal я данное явление наблюдал дважды.
3. Динамические, тогда и количество элементов - переменная, и сами элементы - переменные.
4. Разреженные, тогда количество фактически хранимых элементов может быть меньше диапазона индексов.
5. Логические, каждый такой массив есть массив, номинально состоящий из всех элементов разреженного массива, количество элементов логического массива соответствует диапазону элементов разреженного массива. В принципе можно считать, что логический массив есть представление интерфейса разреженного массива.
6. Физические, это вообще не обязательно массивы, физический массив есть контейнер, содержащий все фактичеки хранимые элементы разреженного массива. Можно считать, что физический массив есть контейнер, на котором реализован разреженный массив, но без собственно реализации операций, отвечающих за разрежение. Физический массив может быть стаческим, или динамическим массивом, линейным или кольцевым списком (но не очередью, или стеком), массивом линейных или кольцевых списков (но не очередей, или стеков), деревом массивом деревьев, линейным или кольцевым списком (но не очередей, или стеков) деревьев деревом массивов, деревом линейных или кольцевых списков (но не очередей, или стеков) и много чем ещё, сколь угодно экзотическим. Но это обязательно контейнер и он обязательно допускает неразрушающее произвольное чтение элемента.
Очевидно, что разреженные и логические массивы явяются динамическими. Только константный массив целиком - константа, любой другой массив целиком - переменная. Но хотя массив - тоже переменная, но особая. В оличие от скалярных переменных, имеющих одно значение каждая, и струкутр, содержащих каэжая несколько переменных, чьи значения складываются в комплексное, но всёж одно значение всей структуры, массив вообще не имеет единого значения, весь массив имеет как раз только множество значений всех своих элементов и эти значения не складываются в нечто цельное. Это не сложная переменная, а перменная-контейнер. И термин "статический" в случае массива имеет иное значение, относящееся не к памяти, в которой он хранится, а к количеству элементов.

Автор: TarasProger 14.8.2015, 09:59
Цитата(ManiaK @  9.8.2005,  14:35 Найти цитируемый пост)
Итак, строение машин долгое время определяло их функции. Однако, за человеком давно замечена одна глупость: он всё пытается унифицировать. Иными словами, в один прекрасный день захотелось одному человеку объединить лопату и чайник в один предмет, причём так, чтобы можно было переключать их функциональность. Нажал кнопочку - чайник, нажал другую - лопата. Удобно!..

Так появились компьютеры. Да, это были, по сути своей, первые компьютеры. Устройство, которое следило за нажатием ваших кнопочек и переключением функций, фактически есть первая программа. Долго радовался человек на своё изобретение, однако и этому однажды пришёл конец. "Лучшее - враг хорошего" - говорят, и не зря...
Computer от английского to compute - вычислять. Значит вычислитель. А реагирование на кнопки - это ещё не вычисление, первый копьютер - это вообще арифмометр, второй - калькулятор.

Автор: TarasProger 14.8.2015, 13:23
Цитата(ManiaK @  9.8.2005,  14:35 Найти цитируемый пост)
Кстати, само разграничение понятий "процедура" и "функция" довольно условно и никто вас не имеет права щёлкнуть по носу, если вы вдруг употребить одно понятие взамен другого (хотя принято по возможности пользоваться вторым).
Процелура и функция различаются способом возврата значения. Если через имя самой подпрограммы - это функция, иначе - процедура. 
Код
val('3.14', a);
 - вызов процедуры, 
Код
a:=val('3.14');
 - вызов функции. Процедура может возращать значение через глобальную переменную, через модифицируемый параметр, через память, адрес которой передан в параметре-указтеле. Но её собственное имя не может быть указано идентификатором того опенда другого оператора, в котором ожидается результат работы процедуры. А имя функции в этом месте указать можно. На низком уровне нет функций, а есть только процедуры, просто некоторые из них умеют оставлять результаты своей работы в строго определённых местах согласно соглашению о вызовах. Это может быть регистр ax, вершина стека на момент сразу после возврата из функции, место в памяти сразу перед точкой входа в функцию, или что угодно ещё, прописанное в соглашении о вызовах, которым пользуется компилятор языка более высокого уровня. На высоком уровне могут быть и токо функции, как на c/c++, и только процедуры, и оба вида подпрограмм. На object pascal грань между ними вроде бы вообще стёрта: можно функцию вызвать как процедуру, возвращающую результата через первый параметр со стандартным именем Result. Если нет процедур, но есть функции, то вместо процедур в языке релаизованы фунции, формально возвращающие специциальные значения без данных. Если нет функций, но есть процедуры, то надо придётся, как на языке ассемблера, обходиться процедурами и изобретать способы возврата результатов их работы. Если поддерживаются оба вида подпрограмм, то и оба синтаксиса. Поэтому в зависимости от языка может быть принят любой из двух терминов, или все три.

Автор: TarasProger 14.8.2015, 14:37
Цитата(Гость_ManiaK @  21.9.2005,  23:12 Найти цитируемый пост)
Насколько я понял из прочтения вашей статьи. Что указатель это переменная, которая содержит адрес другой переменой. Именно так.
Не всегда так. Во-первых указатель может содержать и адрес функции. А во-вторых указатель может быть и констатой. Указатель - это величина, содержащая адрес чего то другого. Это может быть:
1. Переменная, хранящая адрес другой переменной. Возможно другого указателя.
2. Константа, хранящая адрес переменной. Возможно другого указателя.
3. Переменная, хранящая адрес константы. Возможно другого указателя.
4. Константа, хранящая адерес другой константы. Возможно другого указателя.
5. Переменная, хранящая адрес функции.
6. Константа, хранящая адрес функции.
Указатель, хранящий адрес другого указателя, называется кратным указателем. Указатель, хранящий адерс другого указателя, в котором уже харнится адрес не указателья, называется двойным указателем. Указатель, хранящий адерс другого указателя, в котором опять хрантся адрес другого указателя, в котором уже харнится адрес не указателья, называется тройным указателем. Кратный - собирательное название. Кратность указателя - это количество указателей, включая данный, адреса которых нужны, чтоб по цепочке добраться до не указателя. Здесь: 
Код
int ****p;
 p - указатель на int кратности 4, то есть указатель на указатель на указатель на указатель на int.

Добавлено через 11 минут и 12 секунд
Цитата(Гость_ManiaK @  21.9.2005,  23:12 Найти цитируемый пост)
В ассемблерном коде (после компиляции) ссылок не существует. Это - абстракция, реально никакой памяти ссылки не занимают. Для чего они нужны? самый простой и в тоже время наиболее употребительный пример - передача объекта в функцию не копированием, а ссылкой (то есть передача самого объекта):
Вот как раз параметры ссылочных типов - это на самом деле такие константные указатели, которые не надо разыменовывать. Ни сайзоф, ни взятие адреса этого не покажут, так как настоящая, но временная и безымянная ссылка получается при разыменовании указателья, а параметр ссылочного типа при каждом использовании разыменуется по умолчанию. Но информацию о том, где переменная лежит, надо ведь как то в функцию передать и другого способа кроме указателя для этого на низком уровне нет.

Добавлено через 14 минут и 47 секунд
Цитата(maxim1000 @  22.9.2005,  00:56 Найти цитируемый пост)
ссылка ближе к указателю, чем к пременной
в общем-то именно так она и реализована на уровне компилятора
просто везде, где она используется, компилятор дорисовывает звездочку smile
Код
int x;
int &y=x;
y++;
x=y;
Эта ссылка ближе как раз к переменной, такая ссылка - это просто дублирующая запись с тем же адресом в таблице переменных компилятора. Ничего общего с указателем, хранящим адрес на этапе исполнения, она не имеет.

Автор: TarasProger 14.8.2015, 14:55
Цитата(ManiaK @  30.5.2006,  16:49 Найти цитируемый пост)
BlHol, абсолютно никакой разницы нет.  
Есть, причём, огромная. Декларация указателя и приведение к указательному типу.

Добавлено через 14 минут и 29 секунд
Цитата(ManiaK @  24.7.2006,  10:01 Найти цитируемый пост)
Да. Причём не обязательно другой переменной. У вас в программе может существовать указатель на память, которой не соответствует ни одна переменная. Каким образом мы получим доступ к этой памяти? Только с помощью указателя на неё. Это ответ на следующий вопрос:
Не путайте безымянные переменные с другими сущностями. 
Код
void f()
{
 int a[20];
 int *p;
 p=new int;// В этой строке значением p станованится адрес динимаческой безымянной переменной.
 delete p;
 p=&(a[5]);// В этой строке значением p станованится адрес безымянной переменной, но не динамической, а элемента другой именованной автоматической переменной.
}
. Переменная существует в обоих случаях, но у неё нет собственного имени. В одном случае переменная создана динамически и получить доступ к ней можно только по указателю, в другом безымеянна только конкретная переменная, но весь массив имеет имя, является обычной автоматической переменной и лежит на стеке, а доступ к её элементу можно получить двояко:
1. По указателю.
2. Вычислив адрес элемента по адерсу всего массива и индексу элемента.
Но если 
Код
 const double a=3.14;
 const double *p;
 p=&a;
, то по адресу в p нет переменной вообще. К величине по этому адресу можно получить доступ по имени самой величины, но ни какая переменная там не лежит, а лежит там константа. А в этом: 
Код
typedef void (*pf)(double);
int main ()
{
 pf p;
 p=f;
 случае p хранит адрес функции.

Powered by Invision Power Board (http://www.invisionboard.com)
© Invision Power Services (http://www.invisionpower.com)