Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате |
Форум программистов > C/C++: Для новичков > Указатели, строки, классы и пр. |
Автор: ManiaK 9.8.2005, 14:35 | ||||||||||||||||||||||||||||||||||||
ВВЕДЕНИЕ В этой статье я бы хотел научить людей, недавно занявшихся изучением Си++, сразу трём вещам: работать со строками (в более общем виде - с указателями), писать свои классы и перегружать некоторые операторы в этих классах, которые иногда заметно упрощают жизнь, но для новичков представляются чем-то загадочным и непонятным. Мы напишем свой строковый класс и, таким образом, охватим сразу три эти интересные темы. В конце мы посмотрим, что уже есть в стандартной библиотеке Си++ и сравним с тем, что сделами мы. Эта статья не учит Си++ - предполагается, что читатель уже знаком с основной частью синтаксиса Си++, но не имеет опыта в программировании на нём или этот опыт незначителен. Проще говоря, статья ориентирована на новичков. Я буду по ходу дела объяснять некоторые важные моменты, на которые часто не обращают внимания новички, но все мои рассуждения будут уже содержать в себе базовые термины, такие как класс, метод, функция, переменная и прочее. Все эти понятия должны быть понятны читателю до прочтения данной статьи иначе оно не будет иметь никакого смысла. И ещё: все примеры в этой статье - не готовые решения, а лишь демострация идей; поэтому не стоит копировать их и пытаться компилировать без каких либо изменений - идеи надо просто усвоить. Ну, готовы?.. ЧАСТЬ 1. СИ СТРОКИ ИЛИ НЕМНОГО ТЕОРИИ. Наверное треть всех вопросов, задаваемых новичками на различных форумах интернета, посвящённых Си или Си++, так или иначе касаются строк. Кстати, в названии параграфа я не ошибся - именно Си строки. Почему? потому что в Си++ есть классы и использование указателей бессмысленно (Си является практически идеальным подмножеством Си++ и потому почти всё, что справедливо для Си, справедливо и для Си++). В сущности, все проблемы со строками сводятся к проблемам указателей. Если человек не понимает, как работают указатели и что они из себя представляют, он никогда не поймёт, чем char отличается от char*. Если ещё более углубиться в проблему, то можно увидеть, что непонимание указателей связано зачастую с непонимаением работы самого компьютера. Действительно, а знаете ли вы как ваши программы выполняются компьютером? В Си, как и в Си++, этот вопрос очень важен, так как эти языки очень тесно взаимодействуют с аппаратным уровнем. Обычно ваша программа (бинарный объектный код, понятный процессору без трансляции) загружается в оперативную память и там исполняется. То есть, предположим, что ваша программа занимает 512 байт. Загрузчик программы определяет, куда лучше всего впихнуть её; предположим, он выбрал адрес 0x1000. Тогда последний байт вашей программы будет по адресу 0x1200 (0x1000 + 512). Некоторые преполагают, что в том же месте хранятся все переменные программы. Спешу вас разуверить, это не так. В сегменте кода (так правильно называется эта часть оперативной памяти) хранятся только машинные инструкции самой программы и (не обязательно) константные объекты. Проще говоря, только то, что не меняется в процессе выполнения программы. При попытке из вашей же программы поменять код ("поработать над собой") хорошая операционная система даст оплеуху и выкинет из списка задач, почистив за вами память. Где же хранятся переменные?.. Необходимо сначала разделить переменные на два рода - "динамические" и стековые. В чём разница? во-первых, в скорости выделения памяти под них: под стековые переменные память выделяется с гиганской скоростью и ещё быстрее высвобождается, в то время как при создании динамической переменной система ещё подумает: а где взять эту память?.. Данный вопрос очень важен, часто программисты "обманывают" компилятор, заключая динамический объект в статическую оболочку. Второе достоинство стековых переменных в том, что они автоматически удаляются, коль скоро выполнение программы выходит за облась действия данной переменной. Иначе говоря, программисту не приходится следить за тем, когда отдавать память обратно системе - компилятор делает это за него сам. Но за всё приходится платить в нашем мире: при использовании стековых указателей приходится следить, чтобы в их личную жизнь никто не вмешивался и не брал указатели на них, иначе может получиться казус: переменной уже сто лет как нет, а указатель на неё до сих пор существует. Ничего не подозревающий клиент пытается обратиться по этому указателю к объекту и получает по шее от операционной системы - упс... Что же такое динамические переменные? - это переменные, которые можно создавать и удалять в процессе работы программы. Например, нам нужно написать программу, которая будет читать файлы, указанные пользователем и как-то обрабатывать их. Для этого нам нужно записывать прочитанные данные в какой-то массив, причём мы заранее не можем сказать, какого размера должен быть этот массив. Как быть? использовать динамическую переменную. В теории всё просто. При непосредственном выполнении ваша программа выясняет размер файла и запрашивает у операционной системы необходимый объём памяти. Но как нам "дадут" эту память? Просто! Операционная система выделяет в оперативной памяти область, размер которой равен запрошенному, и даёт вам адрес первого байта этой области (далее просто - адрес области) - этакий жетончик, по которому вы можете получить свои данные. Помимо этого она заносит в свои списки вашу программу, адрес области и её размер. Теперь вы полноправный владелец данной области памяти! Смотрите, как выглядит последний абзац в Си-коде:
В Си++ есть ещё вариант:
В обоих примерах переменная i - статическая, а область памяти, адрес которой эта переменная содержит, - динамическая. Сложно? только первое время, на самом деле всё логично. Можно даже обнаглеть и написать такое:
В Си++ значительно более уважительно относятся к типам объектов и на любые преобразования, подобные этим, грустно улыбаются; от себя хочу добавить, что подобными фокусами по возможности лучше не заниматься. Размер unsigned long гарантированно совпадает с размером указателя. С указателями пользователь может выполнять некоторые арифметические операции; рассмотрим на примере:
Почему предпоследняя строчка работает, а последняя нет, хотя и в той и другой справа - числа? Есть одна маленькая хитрость. В предпоследней операции при сдвиге литера 10 имеет не числовой тип, а специальный тип ptr_diff. Это тип разницы между двумя адресами; переменные этого типа можно прибавлять/вычитать из указателей, но их нельзя присваивать. Иногда этот тип обозначают как difference_typе. Да, для любителей поглумиться над компилятором скажу сразу: операции умножения/деления над указателями запрещены. Наконец мы выделили блок нужного размера и записали туда прочитанные из файла данные, обработали их. Теперь нужно читать следующий файл, вероятно, другого размера. Что делать со "старым" блоком? Есть два варианта: 1. Использовать функцию операционной системы для увеличения размера блока (обычно - realloc). 2. Удалить старый блок ("вернуть" его системе) и запросить новый нужного размера. В Си это выглядит примерно так:
В Си++ это может выглядеть ещё и так:
При выделении памяти оператором new под массив обычно помечается также и размер выделенного массива. Тогда при освобождении памяти оператором delete [] программа будет знать, сколько памяти вернуть системе. Вот такие ситуации не желательны:
Лучше не делать этого. Чаще всего разработчики компиляторов перестраховываются и разницы между удалением массива и удалением одиночного объекта нет (и то - при использовании встроенных типов). Но по закону подлости, если сделать такую пакость, как в последнем примере, через несколько месяцев (лет?) вашу программу перекомпилируют на другом компиляторе или перегрузят операторы delete и.. лучше просто следовать известному в России принципу "заплати налоги - спи спокойно" под налогом подразумевая дополнительную проверку кода на такие вот тонкости. И последнее замечание по указателям: в Си динамически созданные объекты сами не удаляются. То есть, если вы создали какую-то переменную через alloc-функцию или new, она будет висеть в RAM до самого завершения работы программы. Такие маленькие недоработки называют утечками памяти и иногда они выливаются в полное исчерпание ресурсов системы и её зависание (в том случае если программа работает достаточно продолжительное время и регулярно "теряет" память). Что ж, я считаю указатели мы в общих чертах разобрали - можно переходить к тому, с чего начали - к строкам. Первое и самое главное, что вам необходимо понять - это что в Си++, равно как и в Си, нет типа данных "строка". Поняли? ну я и не сомневался, разберём по-подробней... Си - самый логичный с точки зрения архитектуры ЭВМ язык программирования. Практически всё, что можно сделать на ассемблере, можно сделать и на Си. Не верите? Смотрите:
Знаете, что сделают эти две строчки, если пустить их сразу после инициализации Биос? Выведут вам на экран строку "Hello, World!". Мы можем откомпилировать этот код, записать в первый сектор дискеты и загрузиться с неё. Здорово? Если попытаться проделать тоже под ОС, очень вероятно, что ваша программа просто "грохнется", т.к. любая уважающая себя ОС не даёт кому ни попадя лазить в системную область RAM. В Си++ всё выглядит почти также. Отличие только в том, что преобразовывать числовую константу в указатель приходится через reinterpret_cast. Ну не любят этого плюсники! Разумеется, всё это сказано только для общего развития, подобные вещи принято и правильно писать на Ассемблере, так как только в нём можно наиболее эффективно работать с аппаратурой напрямую. На данном примере вы должны были уже понять, что строка в Си/Си++ - это последовательность кодов из заданной таблицы символов. Не правда ли логично? Загляните в текстовый файл hex-редактором и вы увидите нечто вроде этого:
Проще говоря, вы увидите набор кодов символов. Точно также строки хранятся в Си/Си++ переменных. Придумайте, как можно текст представить иначе и очень может быть, что вам дадут нобелевскую премию. Шутка!.. Любая строка в Си - это массив символов. Всё остальное вытекает из этого постулата.
Как известно, строки в Си заканчиваются нулевым символом (символом, имеющим код 0). Любая строка, взятая в двойные кавычки по определению заканчивается этим символом и потому размер "Dinamic String" не 14, а 15. Это нужно для того, чтобы программа, использующая эту строку, знала, где она кончается. Можно, конечно, для каждой строки таскать свою переменную, в которой содержится число символов, но это очень неудобно в тех случаях, когда приходится оперировать десятками различных строк. В Pascal'е другой принцип - первый символ строки содержит не код первого символа, а число символов в строке. Но тогда для стандартного байтного набора символов максимально возможной длинной строки будет 255 символов, а этого бывает мало. Рассмотрим ещё пару примеров работы со строками в Си стиле и закончим эту главу.
Ну вот, на этом с Си строками можно закончить. Переходим к Си++. ЧАСТЬ 2. ПРОЦЕДУРНОЕ ПРОГРАММИРОВАНИЕ. ФУНКЦИИ, СТРУКТУРЫ, МАССИВЫ. Вспоминая себя во времена моего изучения Си++, я часто удивляюсь, насколько логично проходило это изучение. На первых порах я брал чужие программы и пытался их модифицировать; классическая схема: сам пока ничего не можешь, но что-то творить хочется. Вскоре мне это, разумеется, надоело, я залез в книги и начал пробовать писать самостоятельно. Но как я писал - это заслуживает отдельного внимания! Я думаю, многие так начинали свой путь программиста... Я не использовал классов. Я писал на Си++ лишь формально: в действительности же мои программы представляли собой набор функций - то, что даёт уже язык Си! Этот факт можно считать доказательством того, что начинать обучение Си++ следует не с классов, а с возможностей, составляющих фактически язык Си. Итак, процедурное программирование. Для чего нужны программы? - вот самый простой по формулировке и самый сложный по своему смыслу вопрос программирования. Отвлечёмся от метафизических прений и опустимся на землю. Компьютеры необходимы для выполнения различных операций без участия человека. Когда-то, на заре информационных технологий, существовали машины, которые были созданны для определённых операций, причём функциональность таких машин определялась их строением. Например: строение лопаты определяет её назначение - копку, строение чайника - хранение вязких веществ и т.д. Конечно, с дуру можно в чайник засунуть голову, а лопатой попробовать писать вместо ручки.. если у вас возникают подобные мысли, то, как ни странно, программирование для вас идеально подходит! Почему - выяснится позже. Итак, строение машин долгое время определяло их функции. Однако, за человеком давно замечена одна глупость: он всё пытается унифицировать. Иными словами, в один прекрасный день захотелось одному человеку объединить лопату и чайник в один предмет, причём так, чтобы можно было переключать их функциональность. Нажал кнопочку - чайник, нажал другую - лопата. Удобно!.. Так появились компьютеры. Да, это были, по сути своей, первые компьютеры. Устройство, которое следило за нажатием ваших кнопочек и переключением функций, фактически есть первая программа. Долго радовался человек на своё изобретение, однако и этому однажды пришёл конец. "Лучшее - враг хорошего" - говорят, и не зря... Захотелось однажды человеку сделать так, чтобы можно было произвольно, в любой момент времени выбрать, как будет реагировать их машина, на нажатие той или иной кнопки. Что появилось? Правильно - перфокарты!.. Такие машины были уже полноценными компьютерами, потому как обладали не только постоянной памятью (коей являлась архитектура), но и переменной - перфокартами. Извините за столько затянувшееся бла-бла-бла, но теперь, мне кажется, вопросов вроде "что есть программа" должно не возникать - а это очень важно!.. Теперь посмотрим более современно. В любом компьютере существует исполнительное устройство (и часто - не одно), которое может выполнять фиксированный набор каких-то простых операций. Разберём это на примере нашего вездесущего чайника. Предположим, у нашей машины "автоматического чайника" есть следующий набор элементарных операций: открыть крышку, залить воду, включить нагреватель, выключить нагреватель, заорать "идиот, опять включил - и заснул!!!". Нам нужно составить несколько программ: кипичение воды без криков при окончании работы и с ними. Очевидно, что обе программы будут содержать блоки с одинаковыми наборами операций (открыть крышку, залить воду, включиться, выключиться) и только в конце второй программы будет приписано включение ора на всю ивановскую. "И что?" - скажем мы, "Не обломаемся продублировать эти одинаковые блоки!". Подождите, я же не договорил: у нас памяти на перфокарте только на шесть элементарных операций. Что делать будете? Вот тут в дело вступают процедуры: мы обзываем наши первые четыре операции как "кипячение чайника" и прописываем отдельно, а в двух наших программах пишем только вызов этого блока: "закипятить чайник", "поорать". Функции отличаются от процедур только тем, что возвращают результат своих действий; например, после выполнения функции "закипятить чайник", можно куда-то записать, сколько времени происходило кипячение. Также, и функции и процедуры могут принимать параметры, например: "закипятить чайник водой с начальной температурой 20 градусов" - здесь параметром будет начальная температура; от начальных параметров, мы видим, будет меняться и возвращаемое значение - чем выше начальная температура, тем меньше времени потребуется на кипячение. А если мы пишем просто "закипятить чайник", то фактически мы предоставляем выбор начальной тепературы самой функции кипячения, мы не передаём параметра. Если функция способна сама выбрать это начальное значение при необходимости, то такой параметр называют параметром по умолчанию. Кстати, само разграничение понятий "процедура" и "функция" довольно условно и никто вас не имеет права щёлкнуть по носу, если вы вдруг употребить одно понятие взамен другого (хотя принято по возможности пользоваться вторым). Программирование, основанное на функциях и процедурах и называют процедурным. Однако довольно часто в это понятие включают ещё кое-что. О том, что такое переменная и указатель говорилось в предыдущей статье, так что не имеет смысла повторяться. Переменные предоставляют возможность хранить информацию, но предоставляют очень примитивно: нам доступны лишь целые числа, строки и дробные числа. А как мы сможем описать параметры чайника? Для этого нам потребуется несколько переменных, но передавать их все отдельно в функции неудобно. Да и создавать придётся для каждого экземпляра чайника сразу несколько переменных.. но опять эти люди!.. Придумали обзывать набор переменных одним именем и предоставлять возможность создавать сразу несколько переменных по заранее сформированному шаблону. Такой шаблон назвали структурой. Структура подобна штампу, который заметно облегчает жизнь бухгалтерам и юристам: им не приходится каждый раз писать, им необходимо просто припечатать штамп к листку и на последнем сразу появится нужная надпись, да ещё и в рамочке! Структуры - продвинутые штампы, они содержат в себе нечто, что можно изменять (некоторые штампы тоже содержат пустые поля, куда можно что-нибудь вписать). Это заранее заранее "непропечатанное" нечто и есть переменные, как вы должны были уже понять. Вы указываете только тип и набор переменных. Вот пример:
Теперь мы не будем отдельно создавать переменные "модель", "габаритные размеры" и пр. для каждого чайника. Вместо этого мы напишем:
Переменная ch1 уже содержит в себе переменные model, gab_x и пр. Нам нужно только использовать специальный оператор, чтобы обратиться к ним:
Структуры также могут быть указываемы; и тогда используется другой оператор для доступа к содержимому структуры:
struct Chaynik практически полноценный тип, такой же как int, double и char. Он не умеет только того, что не определено явно. К примеру, компилятор не знает, как ему прибавить к чайнику что-либо. Поэтому данная операция в процедурном программировании существовать не может:
Задавая себе вопрос "как?" можно довольно точно определить, будет ли компилятор воспринимать вашу запись или нет, хотя иногда бывают неожиданности. В любом случае знайте: если после компиляции вашей гениальной программы компилятор покатывается со смеху, указывая на строчку в вашей программе, где производится какая-либо операция над структурой - значит компилятор такую операцию не понимает (заметим, что над тем, что они что-то не понимают, смеются только люди довольно глупые, но это не значит, что разработчики вашего компилятора одни из них). Опустимся теперь на следующий уровень: разберём детали. Впервую очередь в процедурном программировании необходимо выяснить, каким образом происходит передача переменных в функции. Передача стандартных, "одиночных" переменных и указателей должна быть понятна и так, а вот как обстоит дело с массивами и структурами?.. Человеческий ум убог и ничего принципиально нового придумать не может. Вместо этого он решает текущие проблемы уже изобретёнными инструментами, подстраивая их (тот самый случай, когда чайник используют вместо лопаты). Передача массивов и структур происходит банальной передачей указателя на них. Смотрите:
Первые две функции с точки зрения ассемблера будут совершенно идеинтичным, различными у них будут только имена. Почему? Потому, что всё, что больше размера указателя (sizeof(int*)) обычно передаётся посредством указателя: сначала создаётся копия b, а потом в функцию отправляется только указатель на эту копию. int a[] и int* в Си же вообще идеинтичны, потому копия a создаваться не будет: вместо этого просто произойдёт передача указателя на первый элемент a. Казалось бы: где логика? На самом деле логика есть: когда мы объединили несколько переменных структурой в одну, мы тем самым сказали компилятору, чтобы он распространил на эту группу переменных теже правила, что действуют на обычные переменные (насколько это возможно, конечно). Одиночные переменные передаются "по значению" (то есть, передаётся не сам объект, а его копия - значение). Разделение же на int[] и int* в Си и Си++ чисто лексическое, компилятор интерпретирует и то однозначно за исключением случая создания переменной: тут указание константы в квадратных скобках явно свидетельствует об том, что будет создан статический массив. Также, было введено ещё одно удобное ограничение; взгляните на func2 - параметр a здесь будет передаваться точно также, как и в func и func1, но компилятор должен будет проверить, что вы передаёте ему массив именно из 13 элементов (разумеется, проверить это на этапе компиляции можно лишь при передаче статического объекта). Обычно правда компилятор языка Си разрешает нарушение этого правила и максимум - погрозит вам пальчиком при передаче указателя или статического объекта иного размера. В Си++ всё может быть значительно строже. Вообще, передача параметров не устанавливается чётко ни стандартом Cи ни Си++: поведение может быть целиком и полностью определяемо компилятором. Но если вы не программируете микроконтроллеры или какие-нибудь экзотичные процессоры, вы вряд ли встретите серьёзные отступления от описанных правил.
|
Автор: 509harbore 21.9.2005, 20:24 |
ManiaK Объясните мне разницу между указателеми и ссылками. ![]() Насколько я понял из прочтения вашей статьи. Что указатель это переменная, которая содержит адрес другой переменой. |
Автор: Гость_ManiaK 21.9.2005, 23:12 | ||||||||||
Именно так.
Ссылка - это просто синоним для переменной.
В ассемблерном коде (после компиляции) ссылок не существует. Это - абстракция, реально никакой памяти ссылки не занимают. Для чего они нужны? самый простой и в тоже время наиболее употребительный пример - передача объекта в функцию не копированием, а ссылкой (то есть передача самого объекта):
В данном примере при передаче а в функцию func сначала создаётся копия a и она уже отдаётся на растерзание, в то время как сам исходный объект не меняется. А вот так можно явно указать компилятору, что нужно использовать сам объект (точнее - ссылку, хотя это одно и тоже), а не его копию:
} |
Автор: maxim1000 22.9.2005, 00:56 | ||||||||||
ссылка ближе к указателю, чем к пременной в общем-то именно так она и реализована на уровне компилятора просто везде, где она используется, компилятор дорисовывает звездочку ![]()
при компиляции будет сначала переделано в такое:
поэтому, когда мы пишем
на самом деле получается
получается, что изменить значение адреса невозможно единственное, чем отличается ссылка от указателя - инициализация:
означает занесение адреса x в y и это навсегда (пока не выйдем из этого блока)... |
Автор: 509harbore 22.9.2005, 20:32 |
ManiaK ,maxim1000 ,благодарю вас за столь хорошие отвтеты. ![]() А для чего служит необходимость разыменования указателей? ![]() |
Автор: Void 22.9.2005, 20:55 | ||
Для того, чтобы получить доступ к объекту, на который они указывают. А зачем они тогда нужны были бы? ![]() |
Автор: BlHol 3.5.2006, 12:37 | ||||
Маленький вопрос: Вот это понятно:
Указатель на объект класса. А вот это как понимать?:
Заранее благодарен. С уважением. |
Автор: 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), и не задумываться о распределении памяти - все за вас сделает компилятор.
определено множество стандартных функция для работы со строками, при желании можно писать свои, но здесь есть одна тонкость - передача параметров. Хотя внешне обект строка похож на обычный int, тем не менее для каждого нового объекта распределяется память, вызываются конструкторы/деструкторы и т.д. Поэтому при написании своих функций в списоке аргументов желательно: - Для входных параметров объявлять аргумент как const string param& - для выходных (изменяемых) - как string param& Использование строк в качестве char* (C-строк) - можно для функций, который _не_ модифицируют строки (с использованием c_str() см. код). Если же требуется буфер обмена с изменяемой памятью - его придется создавать самостоятельно, но зато потом можно будет присваивать строке. Зы: навеяно http://forum.vingrad.ru/index.php?showtopic=101829 |
Автор: NightmareZ 2.11.2006, 13:28 | ||||||||||
Таким образом определяем тип данных MyEnum, который может принимать три значения: One, Two и Three. При этом One, Two и Three - это целочисленные константы. Далее MyEnum можно использовать, например, так:
Структуры же - это совокупность переменных, объединённых под одним именем. Если мы определим структуру вот так:
то сможем создавать переменные, содержащие в себе три значения (One, Two и Three). Обращаться к отдельным переменным внутри структуры нужно через точку:
Объединения позволяют хранить переменные разного типа в одной области памяти.
Если unsigned short int занимает два байта, а unsigned char - один, то, после данного присваивания, переменная One будет содержать ноль, а Two - единицу. |
Автор: Frekenbok 10.11.2006, 06:05 |
Со всеми этими обсуждениями вторая глава как-то осталась незамеченной. Или всем все понятно? Мне вот не все. ManiaK, батенька, да вы философ! (я имею в виду чайники) ![]() Вот мои вопросы:
|
Автор: archimed7592 10.11.2006, 07:21 | ||||||||
но это не массивы. массив - это N элементов одного заданного типа. структура же может иметь много полей и все могут быть разных типов, в том числе и массивами. структура - это как мусорный контейнер (совсем не в плохом слысле, просто в голову больше ниче не приходит), а массив - как корзина для яиц. кстати, помимо того, что структура может содержать массивы ещё не редко встречаются массивы из структур. вступление: передача параметров как правило производится через регистры (размер регистра 4 байта). если регистров не хватает, то передача параметров производится через стек. ещё одно вступление: указатель - это почти в любом случае 4 байта - просто номер первого (нулевого) байта в памяти (для int - это номер младшего байта). ответ: нет. ManiaK сказал, что с точки зрения ассемблера - это одинаковые ф-ции (лучше было сказать сигнатуры)...ф-ции то разные, но вот способ их вызова одинаковый. т. е. способ их вызова при хорошо сложившихся звёздах будет одинаковый. т. е., когда мы вызываем любую из этих ф-ций происходит следующее: первый аргумент кладётся в регистр1, второй - в регистр2. и в том и в другом случае второй аргумент - это указатель. в первом случае - на char, во втором - на Chaynik. но! с точки зрения ассемблера разницы между указателем на char и указателем на Chaynik нету. в первом случае всё понятно, а вот во втором: почему указатель, мы ж структуру передаем? вот собственно говоря та мысль, которую пытался донести ManiaK: при передаче малых объектов (до 4 байт) они передаются как есть т. е. их копия лежит в регистре, а для больших объектов делается следующее: создается копия на стеке и в регистре передается уже указатель, а не сам объект. во второй параметр передается не структура, но некий объект, размер которого превышает 4 байта (8 байт) и потому разницы между сигнатурами всех трех ф-ций не будет - в первой укзатель передается явно, в друих двух компилятор делает так, что ты не замечаешь как делается копия и неявно передается указатель на неё.
![]() ![]()
1. разыменовать 2. взять поле. т. е.
к методам тоже. к любым членам. |
Автор: Frekenbok 12.11.2006, 08:51 | ||
archimed7592, еще раз, для особо одаренных... ![]()
Насколько я понимаю, указатель должен указывать на уже существующий объект (переменную и т.п.) или на NULL. Возьмем для примера класс TButton (С++ Builder). Я получаю доступ к полям и методам кнопки через указатель? А "где" сам объект? Компилятор создает объект и указатель на него, а я работаю только с указателем? |
Автор: Daevaorn 12.11.2006, 10:48 | ||
Указаетль указывает на область памяти. Куда именно ему всё равно. Просто если там не будет объекта - будут проблемы Да Находится по адресу накоторой указывает указатель Создает поле в классе формы с типом "указатель на объект кнопка" Да |
Автор: ManiaK 14.11.2006, 15:00 | ||
Нифига подобного. В Си/Си++ компилятор вообще ничего сам не делает. ТЫ создаёшь указатель, ТЫ, если хочешь, создаёшь объект, ТЫ выбираешь с кем тебе работать - с указателем или с объектом. И, наконец, ТЫ выбираешь когда твой объект должен будет удалён из памяти. Ни один из этих пунктов не будет за тебя выполнен компилятором, если ты не создашь объект сам, а попытаешься обратиться к нему по указателю, то результат может быть самым неожиданным (в зависимости от того, что находится в данный момент времени в качестве значения указателя). |
Автор: archimed7592 14.11.2006, 23:52 |
ManiaK, ты забыл сказать, что это касается динамических объектов. статические же компилятор переодически создает и уничтажает "без твоего ведома"...один из примеров - при передаче объекта в ф-цию компилятор вызывает конструктор копирования, по завершению ф-ции, вызывает деструктор... |
Автор: Frekenbok 15.11.2006, 13:14 |
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 | ||
|
Автор: ManiaK 17.11.2006, 09:49 | ||||||
C языке Си придётся пользоваться указателем на указатель. Добавлено @ 09:56
Я вообще про формы ничего не говорил. Если ты кидаешь компонент на форму - то, что там произойдёт зависит от компилятора, на уровне языка у тебя ещё ничего не будет. Я имел ввиду вот что:
Компилятор за тебя ни то, ни другое, ни третье делать не будет.
Вот что я, собственно, хотел сказать. Если есть какие-то непонимания в понятиях объект/указатель в Си/Си++ - то это уже вопрос к первой статье, по которой, покуда все молчат, я посчитал, всё понятно. Да, конечно. Для статических нам нужно только изъявить желание и мы тут же получим объект и нам не нужно будет следить за тем, чтобы память им занимаемая где-нибудь не "улетела". Но это всё вроде было уже описано в первой статье. Если что-то непонятно - я поправлю. Тока скажите. |
Автор: Frekenbok 18.11.2006, 05:07 |
archimed7592, спасибо, функция у меня есть. Интересовала именно передача параметра char *&ptr. Как-то еще не приходилось этим сочетанием пользоваться ![]() |
Автор: archimed7592 18.11.2006, 16:12 |
вот тебе и передача...оттуда же |
Автор: drLans 2.12.2006, 15:56 |
Люди поясните, плз, в чем отличие динамического выделения памяти от обычного задания того же массива в тексте программы? |
Автор: Partizan 2.12.2006, 17:15 | ||
разные области памяти задействуются... при объявлении массив в тексте программы в compile-time память под него выделяется в стэке... динамическая память выделяется из кучи... |
Автор: nickless 2.12.2006, 17:35 | ||
А еще динамически выделенную память нужно не забывать освобождать |
Автор: drLans 2.12.2006, 17:54 | ||
Partizan, так стек по идее быстрее. ![]() Daevaorn, не могу пока. ![]() Ибо не вдавался в особенности (и главное смысл) использования new/delete. Я не понимаю, что может быть лучше этого:
|
Автор: Daevaorn 2.12.2006, 18:12 |
Гораздо. А когда не знаешь зарание количество элементов? Тут то и пригождается динамическое выделение. |
Автор: maxim1000 2.12.2006, 19:46 |
в общем смысле динамическое использование памяти нужно для контроля времени жизни объекта при использовании стека если объект1 создан раньше объекта2, то уничтожен он будет позже именно из этого ограничения исходит скорость работы - функции выделения освобождения памяти зачастую сводятся к одной команде процессора, а проблемы поиска и фрагментации не возникают в принципе (ну ещё на скорость влияет то, что чаще всего стек находится в кеше, а значит не нужно его ниоткуда доставать, но это уже зависит от платформы) если вернуться к C++, то ещё одно достоинство (которое уже упоминали) - автоматическое удаление выделенного объекта при выходе из блока, так что забыть это сделать невозможно лично я стараюсь по минимуму использовать динамическую память и другое выделение ресурсов, которые потом нужно явно освобождать, только когда без этого сложно обойтись (как уже привели пример с неизвестным количеством элементов) или это сильно повышает эффективность (когда она критична), но даже в этих случаях (особенно когда программа сложная) не помешает упаковать управление этими ресурсами в какой-нибудь класс, который будет следить за ними (для всяких массивов чаще всего подходит std::vector)... |
Автор: UnrealMan 2.12.2006, 20:39 | ||||||||
А под статически объявленный массив память тоже может выделяться динамически (если он объявлен как член класса и память под объект такого класса выделяется динамически). По поводу отличий ничего говорить не буду, ибо вопрос – суть боян. В И-нете полно литературы – читай и просвещайся. Всё равно по-нормальному научиться чему-то без книг, задавая подобные вопросы на форумах, вряд ли получится. http://kak-tot.narod.ru/ru/otd-bs2e/p3p2p6.htm
Необязательно. Из-за оптимизации это правило может быть нарушено, например:
вывод (MinGW-3.4.4):
– т.е. объект a2 функции func может быть создан прямо на месте объекта a в функции main и уничтожен только по завершении main. |
Автор: ali01 24.8.2007, 04:52 |
А где же про классы? есть вопрос если в классе создаются динамические переменные, в деструкторе надо вызывать delete? |
Автор: bsa 24.8.2007, 09:35 | ||
Если где-то сделан new, то обязательно должен быть выполнен соответствующий delete. Иначе никак. И не важно где, в классах, структурах или функциях. |
Автор: ali01 24.8.2007, 21:24 |
Деструктор сам этого не делает? А когда автоматически создаешь класс например в VC там создается пустой конструктор, я так понял надо самому там прописывать delete? |
Автор: Daevaorn 24.8.2007, 21:47 | ||
нет. delete он не вызывает для динамических объектов.
по логике скорей всего не в конструктор, а в деструктор. |
Автор: ali01 24.8.2007, 23:51 |
Да, запутался. Я понял, спасибо. Раньше думал деструктор сам все сделает![]() |
Автор: ali01 2.9.2007, 03:29 | ||||||||
Просматривал код wxFormBuilder, пример конструктора:
и деструктора:
Там вообще нет delete. Или может я что-то не понял? |
Автор: archimed7592 2.9.2007, 03:55 | ||
ali01, посмотри деструкторы базовых классов. Там(чуть ли не на самом верху иерархии) должны быть примерно следующие строки:
Во всяком случае в известных мне тулкитах(Qt, VCL) все компоненты хранятся и удаляются единообразно(т.о. позволяя не заботиться о MM... хорошо это или плохо - вопрос другой темы). |
Автор: archimed7592 28.12.2007, 15:34 | ||
|
Автор: anatox91 27.1.2008, 19:06 |
ManiaK, большое спасибо за интересную статью и своеобразный стиль изложения ![]() Тема разделена: 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 | ||
Второе. Если есть желание обсудить этот вопрос, то лучше создать отдельную тему |
Автор: XeS 30.8.2008, 01:12 | ||||||||
Маленький вопросик по работе с указателями
это как я понимаю массив из 10 указателей, т.е я с ним работаю так
а конструкция такого вида
это указатель на массив из 10 int я с ним работаю так
я прав???? |
Автор: J0ker 14.10.2008, 03:11 | ||||
нет можно так:
технически int* и int(*)[n] - разные типы, и данная запись несколько бессмысленна... ну разве что вам надо передавать p в функцию принимающую конкретно int(*)[n] но той-же функциональностью обладает следующий код:
|
Автор: UnrealMan 16.10.2008, 13:44 | ||
Проще так:
|
Автор: boombasta 30.10.2008, 12:51 | ||||||||
привет всем! что-то немогу найти ответов на 2 моих вопроса: 1. че отличается
от
2. что здесь написано:
что за конструкция такая PluginInterface*(*)?? 3. и здесь
тут типа обьявление переменной getObject но не понятно что за (CoreInterface*)? З.Ы. PluginInterface и CoreInterface - это классы |
Автор: bsa 30.10.2008, 16:31 | ||
1. ничем не отличается. 2 и 3. Это тип указателя на функцию, которая принимает указатель на CoreInterface и возвращает указатель на PluginInterface. Соответственно, PluginInterface* (*getObject)(CoreInterface*) - это указатель на функцию. Я, например, всегда делаю так, чтобы не вводить никого в замешательство:
|
Автор: boombasta 30.10.2008, 17:30 | ||
со строкой 1 понятно (спсб bsa ;)) обьявляется указательна функцию, которая в качестве аргумента принимает указатель на CoreInterface, а возвращает указатель на PluginInterface. а что происходит в строке 2? насколько я понимаю lib->resolve("getObject") возвражает ссылку на функцию? а что такое вот это: (PluginInterface*(*)(CoreInterface*)) ? - это жжет мне мозг ![]() ![]() ![]() дольше по тексту getObject юзается как обычная функция: PluginInterface* getObject(CoreInterface*); |
Автор: bsa 30.10.2008, 18:43 | ||||
Это преобразование к типу "указатель на функцию" - PluginInterface* (*)(CoreInterface*). Что делать для того, чтобы было понятно я показал:
Вообще-то обобщенно тип "указатель на функцию" выглядит так: <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.Объясните ЧаЙнИгУ!!))) ![]() |
Автор: bsa 2.12.2008, 20:42 | ||||
по-хорошему, надо писать std::cout, std::cin, std::cerr, std::endl... Но если тебе лень и пишешь маленькую программу, которую не собираешься в дальнейшем улучшать, то можно написать после всех инклюдов using namespace std, и это избавит тебя от необходимости писать приставку std::. Если у тебя нет желания писать std, но не хочется глобально раскрывать стандартное пространство имен, то можешь использовать using namespace std внутри функции (точнее, внутри почти любых операторных скобок). Как вариант, можно раскрыть только необходимые элементы пространства имен:
|
Автор: Goliaf777 2.12.2008, 21:22 |
Огромное спасибо!!! ![]() |
Автор: XeS 20.4.2009, 23:57 | ||||
Помогите разобраться, почему не меняеться адрес переменной, допустим есть такой код:
на экране будет 123, а не 321, а если я перепешу код вот так то все будет норм:
с чем это связано и почему? ![]() и второй вопрос: пусть у меня есть переменные такого типа: int*** a; int* b; что бы привести b к типу a, я должен сделать такое: int*** a; int** c; int* b; c = &b; a = &c; можно ли это сделать по другому, что-бы меньше писать? ![]() |
Автор: bsa 21.4.2009, 13:12 | ||||||||
XeS
Обычно, такие конструкции можно встретить в качестве параметров функций (когда функция может изменить значение аргумента) или для определения многомерных динамических массивов. |
Автор: master123 1.5.2009, 20:08 |
Спс... Мне помогло ________________________________ http://linecinema.ru/ |
Автор: XAKERs89 26.8.2009, 20:52 | ||
можно ли создать массив структур с помощью указателей?
|
Автор: andrew_121 26.8.2009, 21:01 |
XAKERs89, Ссылок? Или указателей? Уточните вопрос. |
Автор: XAKERs89 26.8.2009, 21:12 |
оЙ..сори запутался ((( указателей |
Автор: andrew_121 26.8.2009, 21:30 | ||
|
Автор: ПанкПрогаммист 25.9.2009, 13:42 |
Нефига не понял)) сушствует книга с++ для чаиников?? ![]() |
Автор: bsa 25.9.2009, 13:48 |
ПанкПрогаммист, попробуй тут посмотреть: http://forum.vingrad.ru/forum/topic-269794/kw-faq-c++.html |
Автор: Axsandr 28.3.2010, 18:25 | ||
язык программирования С++ Бьерн Страуструп В принципе очень доступно все написано.... _____________________ Для тех, кто интересуется http://ru.ikonfx.com есть выгодные условия торговли. |
Автор: EgoBrain 26.6.2012, 12:07 | ||
А что происходит при использовании ссылки на сложные типы данных, например структуры? Компилятор меняют ссылку на указатель, и вместо оператора "." вставляет оператор "->"? |
Автор: baldina 26.6.2012, 12:46 |
ничего он не меняет. maxim1000 имел в виду семантику, связанную с реализацией, но не саму реализацию. впрочем, если считать, что в ассемблерном виде может (и скорее всего будет) использоваться косвенная адресация, то можно и так сказать |
Автор: kolesnle 3.6.2013, 18:32 | ||||
|
Автор: rvpxbnkx 18.7.2013, 16:44 | ||
Поясните пожалуйста. Программа на си. Я так понимаю массив это указатель на первый элемент + число элементов. В примере char *dic[][40] получается трехмерный массив. Потом он приобразуется с p = (char **)dic; Вот я и не понимаю почему трехмерный массив станет 2-х мерным. И еще Не совсем понятно чем такая конструкция char (*p)[10]; отличается от char *s[]
|
Автор: 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 | ||
Привет всем. Есть незамысловатый код:
Как бы так покомпактнее сделать, чтобы в этих if'ах не копипастить освобождение памяти для всех указателей? Я пытался с запретным goto прыгать в конец программы сразу к освобождению, да не заладилось чегой-то... И если в этих условиях вообще о таком не заикаться, то будет ли утечка памяти? |
Автор: baldina 22.11.2013, 01:52 |
в main() уже ничего не надо освобождать, ОС освободит. ну а вообще - сделайте локальный объект, освобождайте в деструкторе. при возврате из функции деструктор вызовется автоматически. |
Автор: gendalf7771 22.11.2013, 02:03 | ||
baldina, то есть если, скажем, у меня в программе большущие динамические массивы char наклёпаны, то в рамках main() можно в принципе забить на delete? И вопрос вдогонку. Код:
Вот бывает иногда параметров у функции итак навалом, ну не хочу я szTemp передавать среди прочих, а на выходе функции массив получить надо. Есть ли адекватный способ получить желаемый массив с нормальный освобождением памяти? |
Автор: bsa 22.11.2013, 10:25 | ||
gendalf7771, если ты можешь использовать возможности C++11, то рекомендую использовать std::unique_ptr:
|
Автор: baldina 22.11.2013, 10:26 | ||
delete в main() дань хорошему стилю, если не сделать, отладчик будет показывать утечки памяти. но т.к. приложение завершается, и ОС освободит память, занятую приложением, с этой точки зрения этот delete ничего не дает.
возвращать объект. например, http://www.cplusplus.com/reference/memory/unique_ptr/ |
Автор: gendalf7771 22.11.2013, 20:59 |
Большое спасибо за ответы! |
Автор: TarasProger 12.8.2015, 15:54 | ||||||||
1. Если на одной платформе будет количество символов в 4-х первых байтах, а на другой - в восьми, и такая строка будет как есть сохранена в бинарный файл, или передана по сети, то код первого символа точно прочитается не правильно, а может быть не правильно интерпретируется и количество символов. 2. Если на одной платформе один порядкок байт, а на другой - совсем другой, то не правильно интерпретируется количество символов. Терминальный же ноль в этом плане универсален. И что неудобного в структуре
Добавлено через 7 минут и 40 секунд
Вот только вызов функции, возможно на языке ассемблера написанной, - не показатель. А попробуйте помимо функций и асм-вставок вызхвать программное прерываение, или присвоить значение регистру процессора. Или соверешить переход в зависимости не от результата сравнения, а от флага результата арифметической операции, например, в случае переполнения. На c нельзя делать вообще ничего, что можно делать на языке ассемблера. А так то свой memcpy можно написать и для паскаля, и для бейсика, и для любого другого языка, допускающего побочные эффекты подпрограмм, даже стекового. Добавлено через 14 минут и 50 секунд
|
Автор: TarasProger 12.8.2015, 16:15 | ||
|
Автор: TarasProger 14.8.2015, 09:41 | ||
1. Константные, в этом случае и количество элементов массива - константа, и сами элементы - константы. 2. Статические, в этом случае количество элементов массива - константа, но сами элементы - переменные. Весь массив при этом может лежать на стеке, если это не противорчит ни стандарту языка, ни спецификации конкретного диалекта, тогда при слишком большой глубине рекурсии рекурсивной функции с локальным массивом может происходить переполнение. В программах на Object Pascal я данное явление наблюдал дважды. 3. Динамические, тогда и количество элементов - переменная, и сами элементы - переменные. 4. Разреженные, тогда количество фактически хранимых элементов может быть меньше диапазона индексов. 5. Логические, каждый такой массив есть массив, номинально состоящий из всех элементов разреженного массива, количество элементов логического массива соответствует диапазону элементов разреженного массива. В принципе можно считать, что логический массив есть представление интерфейса разреженного массива. 6. Физические, это вообще не обязательно массивы, физический массив есть контейнер, содержащий все фактичеки хранимые элементы разреженного массива. Можно считать, что физический массив есть контейнер, на котором реализован разреженный массив, но без собственно реализации операций, отвечающих за разрежение. Физический массив может быть стаческим, или динамическим массивом, линейным или кольцевым списком (но не очередью, или стеком), массивом линейных или кольцевых списков (но не очередей, или стеков), деревом массивом деревьев, линейным или кольцевым списком (но не очередей, или стеков) деревьев деревом массивов, деревом линейных или кольцевых списков (но не очередей, или стеков) и много чем ещё, сколь угодно экзотическим. Но это обязательно контейнер и он обязательно допускает неразрушающее произвольное чтение элемента. Очевидно, что разреженные и логические массивы явяются динамическими. Только константный массив целиком - константа, любой другой массив целиком - переменная. Но хотя массив - тоже переменная, но особая. В оличие от скалярных переменных, имеющих одно значение каждая, и струкутр, содержащих каэжая несколько переменных, чьи значения складываются в комплексное, но всёж одно значение всей структуры, массив вообще не имеет единого значения, весь массив имеет как раз только множество значений всех своих элементов и эти значения не складываются в нечто цельное. Это не сложная переменная, а перменная-контейнер. И термин "статический" в случае массива имеет иное значение, относящееся не к памяти, в которой он хранится, а к количеству элементов. |
Автор: TarasProger 14.8.2015, 09:59 | ||
|
Автор: TarasProger 14.8.2015, 13:23 | ||||||
|
Автор: TarasProger 14.8.2015, 14:37 | ||||||||||
1. Переменная, хранящая адрес другой переменной. Возможно другого указателя. 2. Константа, хранящая адрес переменной. Возможно другого указателя. 3. Переменная, хранящая адрес константы. Возможно другого указателя. 4. Константа, хранящая адерес другой константы. Возможно другого указателя. 5. Переменная, хранящая адрес функции. 6. Константа, хранящая адрес функции. Указатель, хранящий адрес другого указателя, называется кратным указателем. Указатель, хранящий адерс другого указателя, в котором уже харнится адрес не указателья, называется двойным указателем. Указатель, хранящий адерс другого указателя, в котором опять хрантся адрес другого указателя, в котором уже харнится адрес не указателья, называется тройным указателем. Кратный - собирательное название. Кратность указателя - это количество указателей, включая данный, адреса которых нужны, чтоб по цепочке добраться до не указателя. Здесь:
Добавлено через 11 минут и 12 секунд
Добавлено через 14 минут и 47 секунд
|
Автор: TarasProger 14.8.2015, 14:55 | ||||||||
Есть, причём, огромная. Декларация указателя и приведение к указательному типу. Добавлено через 14 минут и 29 секунд
1. По указателю. 2. Вычислив адрес элемента по адерсу всего массива и индексу элемента. Но если
|