Применение указателей в c


Содержание

Обозначение указателей в C++

Здравствуйте. Есть ли какие-нибудь существенные различия между такими определениями указателя:

Также хотелось бы выяснить, выигрывает ли в быстродействии определение типа «указатель на указатель». Стоит ли его использовать, или это является плохим стилем? Какую альтернативу можно применить, если лучше стараться избегать такого:

7 ответов 7

В первом случае с точки зрения поведения разницы нет. Где ставить ‘*’ в этом случае — это сугубо вопрос вкуса и используемого coding standard .

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

Если, конечно, хочется пообсуждать, то делать это нужно, имея N ассемблерных листингов gcc, cl, clang и icc с разными уровнями оптимизаций, хотя у меня есть сильное подозрение, что он будет одинаков для большинства случаев.

  • Поскольку использование многоуровневой адресации ( int ***ptr_ptr_ptrvar ) очень сильно вредит читаемости кода, делать это следует только в случаях, когда без этого действительно не обойтись (в 99% случаев это — bad code smell ).

Если вам нужно, чтобы значение переменной var изменялось в функции, передавайте ее по указателю [C99] или иногда, если удобнее, то по ссылке [ C++03 ]. Некоторые стандарты явно запрещают передачу переменной с семантикой out по неконстантной ссылке и требуют для этого использования указателя.

Существенных отличий нет, скорее дело вкуса. Однако следует обратить внимание на следующий момент — несмотря на то, что указатель относится к типу (т.е. тип переменной : указатель на int, или указатель на char), запись вида :

вовсе не будет означать, что у Вас описаны 3 указателя на int, — это 1 указатель и 2 переменных типа int.
Использование многоуровневой адресации возможно, однако уровни глубже второго (указатель на указатель) применяются крайне редко, да и IMXO, запутывают программу.

не различается — это всё указатели на целое.

Одинарные, двойные, тройные и т.д. указатели физически ничем не отличаются — это просто адреса в памяти. Использовать указатели вообще и тем более двойные-тройные нужно только там где они действительно нужны.

тоже годится. Количество пробелов влияет только на размер исходного файла.

Указатель на указатель обычно используют для передаче данных в функцию с целью изменения функцией самого указателя.

@Asen, искусственное «накручивание» адресов применять не надо.

По поводу быстородействия — точно не ускорит.

Я бы сказал, что семантически правильной является запись:

Хотя вроде смысл обоих записей один и тот же, а именно а указатель на целое, но попытка написания:

Не означает, что все три a, b и с являются указателями на целое. Семантически именно это и написано! Типа int* — нет! А запись:

Не вызывает никакой иллюзии. Здесь четко и явно видно что указателем является только а.

    Записи int* a; и int *b; эквивалентны.

Разница появляется в том, что когда нужно в одну строчку объявить несколько указателей. Например:

int* a, b, c; // a — указатель, b и с — просто целочисленные переменные int* d,*e,*f; // здесь определено три указателя: d, e, f

В случае с применением конструкция typedef и #define получается интереснее.

#define P_INT int* typedef int* PINT; P_INT d, e, f; // указатель ТОЛЬКО d. PINT a, b, c; // все три являются указателями

Внимание, тут явная ошибка, которую стоит обходить :-) И это еще один повод не пользоваться define .

Указатель на указатель — это хорошо. По сути мы экономим память (указатель занимает мало места), вместо того, чтобы хранить сами копии объектов целиком, но ценой дополнительного времени на разыменование указателя. С точки зрения использования — вижу смысл использовать такую конструкцию для организации многомерных указателей или изменения значения указателя в ф-циях. А вот разумное применение для указателей на указатели на указатели уже сложно придумать.

Указатели в C++ — урок 7

При выполнении любой программы, все необходимые для ее работы данные должныбыть загружены в оперативную память компьютера. Для обращения к переменным, находящимся в памяти, используются специальные адреса, которые записываются в шестнадцатеричном виде, например 0x100 или 0x200 .

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

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

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

Можете себе представить, если бы небезызвестная Battlefield 3 использовала такой метод работы с данными? В таком случае, самым заядлым геймерам пришлось бы перезагружать свои высоконагруженные системы кнопкой reset после нескольких секунд работы игры.

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

Естественно, все это занимает какое-то место в оперативной памяти компьютера. Если не уничтожать неиспользуемые объекты, очень скоро они заполнят весь объем ресурсов ПК.

По этим причинам, в большинстве языков, в том числе и C/C++, имеется понятие указателя. Указатель — это переменная, хранящая в себе адрес ячейки оперативной памяти, например 0x100.

Мы можем обращаться, например к массиву данных через указатель, который будет содержать адрес начала диапазона ячеек памяти, хранящих этот массив.

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

Ниже приведен конкретный пример обращения к переменным через указатель и напрямую.

Пример использования статических переменных

Пример использования динамических переменных

Синтаксис первого примера вам уже должен быть знаком. Мы объявляем/инициализируем статичные переменные a и b , после чего выполняем различные операции напрямую с ними.

Во втором примере мы оперируем динамическими переменными посредством указателей. Рассмотрим общий синтаксис указателей в C++.

Выделение памяти осуществляется с помощью оператора new и имеет вид: тип_данных *имя_указателя = new тип_данных; , например int *a = new int; . После удачного выполнения такой операции, в оперативной памяти компьютера происходит выделение диапазона ячеек, необходимого для хранения переменной типа int .

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

Инициализация значения, находящегося по адресу указателя выполняется схожим образом, только в конце ставятся круглые скобки с нужным значением: тип данных *имя_указателя = new тип_данных(значение) . В нашем примере это int *b = new int(5) .

Для того, чтобы получить адрес в памяти, на который ссылается указатель, используется имя переменной-указателя с префиксом & . перед ним (не путать со знаком ссылки в C++).

Например, чтобы вывести на экран адрес ячейки памяти, на который ссылается указатель b во втором примере, мы пишем cout . В моей системе, я получил значение 0x1aba030 . У вас оно может быть другим, потому что адреса в оперативной памяти распределяются таким образом, чтобы максимально уменьшить фрагментацию. Поскольку, в любой системе список запущенных процессов, а также объем и разрядность памяти могут отличаться, система сама распределяет данные для обеспечения минимальной фрагментации.

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

Во втором примере мы выводим на экран значение, которое находится в ячейке памяти (у меня это 0x1aba030 ): cout . В этом случае необходимо использовать знак * .

Чтобы изменить значение, находящееся по адресу, на который ссылается указатель, нужно также использовать звездочку, например, как во втором примере — *b = *a + *b; .

  • Когда мы оперируем данными, то используем знак *
  • Когда мы оперируем адресами, то используем знак &

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

Для того, чтобы освободить память, выделенную оператором new, используется оператор delete.

Пример освобождения памяти

При использовании оператора delete для указателя, знак * не используется.

Указатели и ссылки в C и C++

Немного о памяти

Память можно представить по-разному.

Объяснение для военных на примере взвода. Есть взвод солдат. Численность — 30 человек. Построены в одну шеренгу. Если отдать им команду рассчитаться, у кажого в этой шеренге будет свой уникальный номер. Обязательно у каждого будет и обязательно уникальный. Этот взвод — доступная нам память. Всего нам здесь выделено для работы 30 ячеек. Можно использовать меньше. Больше — нельзя. К каждой ячейке можно обратиться и быть уверенным, что обратился именно к ней. Любому солдату можно дать что-то в руки. Например, цветы. То есть поместить по адресу данные.

Объяснение для Маленького Принца. Здравствуй, Маленький Принц. Представим, что твоему барашку стало одиноко. И ты попросил нарисовать ему друзей. Ты выделил для барашков целую планету (точнее, астероид) по соседству. Эта планета — доступная память. Вся она уставлена коробочками, в которых будут жить барашки. Чтобы не запутаться, все коробочки пронумерованы. Коробочки — это ячейки памяти. Барашек в коробочке — это данные. Допустим, что попался какой-то особо упитанный барашек. Ему понадобится две коробочки. Или даже больше. Барашек — неделимая структура (для нас с тобой, Маленький Принц, это точно так), а коробочки идут подряд. Нет ничего проще. Мы вынимает стенки между двумя рядом стоящими коробочками и кладем туда барашка. Места в коробочке не очень много. И барашек не может свободно развернуться. Поэтому мы всегда знаем, где его голова, а где хвост. И если нам что-то нужно будет сказать барашку, мы обратимся к той коробочке, где у него голова.

Объяснение для хулиганов. Есть забор. Забор из досок. Забор — доступная память. Доска — ячейка памяти. Забор длинный. И чтобы потом похвастаться друзьям, где ты сделал надпись, надо как-то обозначить место. Я знаю, о уважаемый хулиган, что ты нашел бы что-то поинтереснее, чем нумеровать каждую доску. Но в программировании не такие выдумщики. Поэтому доски просто пронумерованы. Возможно, твоя надпись поместится на одну доску. Например, %знак футбольного клуба%. Тогда ты просто скажешь номер и друзья увидят серьезность твоего отношения к футболу. А возможно, что одной доски не хватит. Ничего, главное, чтобы хватило забора. Пиши подряд. Просто потом скажи, с какой доски читать. А что если не подряд? Бывает и не подряд. Например, ты хочешь признаться Маше в любви. Ты назначаешь ей встречу под доской номер 40. Если все пройдет хорошо, ты возьмешь Машу и поведешь ее к доске 10, где заранее написал «Хулиган + Маша = любовь». Если что-то пошло не так, ты поведешь Машу к доске 60, на которой написано все нехорошее, что ты думаешь о Маше. Примерно так выглядит условный переход. То есть оба его исхода помещаются в память заранее. На каком-то этапе вычисляется условие. Если условие выполнилось — переходим к одному месту памяти и начинаем идти дальше подряд. Если условие не выполнилось — переходим к другому месту, с другими инструкциями. И тоже продолжаем выполнять их подряд. Инструкции всегда выполняются одна за другой, если только не встретился переход (с условием или без условия). Ну, или что-то поломалось.

Модель взаимодействия программы с памятью компьютера может быть разной. Будем считать, что для каждой программы выделяется своя обособленная область памяти. Даже если запущены два экземпляра одной программы — память у них будет разная.

Илон Маск рекомендует:  Как кнопку сделать ссылкой

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


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

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

Раз уж мы договорились, что минимальная адресуемая величина — байт, то всю доступную программе память можно представить в виде последовательности байтов.

Система в компьютере двоичная (хотя есть и тернарные машины). В 1 байте 8 бит. Английское bit означает binary digit, то есть двоичный разряд. Получается, что байт может принимать числовые значения от 0 до 2 в 8 степени без единицы. То есть от 0 до 255. Если представлять числа в шестнадцатеричной системе, то от 0x00 до 0xFF.

Представим область памяти.

0x01 0x02 0x03 0x04
0x05 0x06 0x07 0x08
0x09 0x0A 0x0B 0x0C
0x0D 0x0E 0x0F 0x10

В ней лежат числа от 1 до 16. Направление обхода обычно задается слева направо и сверху вниз. Помните, что никакой таблицы на самом деле нет (почти как ложки в Матрице). Она нужна человеку для удобства восприятия. Каждая такая ячейка описывается двумя величинами: значением и адресом. В приведенной таблице значение и адрес совпадают.

Понятие указателя

Указатель — это переменная. Такая же, как и любая другая. Со своими «можно» и со своими «нельзя». У нее есть свое значение и свой адрес в памяти.

Значение переменной-указателя — адрес другой переменной. Адрес переменной-указателя свой и независимый.

Переменная-указатель (далее будем говорить просто — указатель) объявляется также, как и любые другие переменные, но после имени типа ставится звездочка.

Здесь объявляется переменная pointerToInteger. Ее тип — указатель на переменную типа int.

Как следует писать звездочку относительно типа и имени переменной? Встречаются, например, такие формы записи, и все они имеют право на существование:

Аргументы за первую форму. Чтобы объявить переменную следует указать ее тип, а затем имя. Звездочка является частью типа, а не частью имени. Это также подтверждается тем, что при привидении типов пишется тип со звездочкой, а не тип отдельно. Следовательно, должна писаться слитно с типом. Минус в том, что при объявлении нескольких переменных после объявления int*, только первая из них будет указателем, а вторая будет просто переменной типа int. Не объявляйте несколько указателей в одной строчке. Это не очень хороший стиль.

Аргументы за вторую форму. Есть люди, которым нравится «когда код дышит» Они ставят пробел до скобок и после скобок. И здесь тоже ставят. Возможно, это просто такой компромисс.

Аргументы за третью форму. Если писать так, то с объявлением нескольких указателей в одной строчке проблем быть не должно (хотя это все равно плохой тон). Некоторая идеология нарушается. Но этот стиль — самый распространенный, так как точно видно, что переменная — указатель.

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

Адрес переменной и значение переменной по адресу

Рассмотрим две переменные: целочисленную переменную x и указатель на целочисленную переменную.

Чтобы получить адрес переменной, нужно перед ее именем написать амперсанд.

Данная конструкция будет выполняться справа налево. Сначала с помощью оператора &, примененного к переменной x, будет получен адрес x. Затем адрес x будет сохранен в указателе p.

Есть и обратная операция. Чтобы получить значение переменной по ее адресу, следует написать звездочку перед именем указателя.

Такая операция в русском языке называется не слишком благозвучным словом «разыменование». В английском — dereference.

В данном примере с помощью оператора * мы получим то значение, которое находится в памяти по адресу p. Затем мы сохраним его в переменную y. В итоге получится, что значения x и y совпадают.

Все это несложно увидеть на экране.

В указанном примере значение x и y будут одинаковы. А также адрес x и значение p.

Адресная арифметика

К указателям можно прибавлять числа. Из указателей можно вычитать числа. На основе этого сделана адресация в массиве. Этот код показывает несколько важных вещей.

Первая строка простая и понятая. Объявлен массив и заполнен числами от 1 до 5.

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

Но если вы хотите избежать неоднозначности, пишите явно. Таким образом в p лежит адрес начала массива. А конструкция *p даст 1.

Третья строчка увеличивает значение p. Но не просто на 1, а на 1 * sizeof(int). Пусть в данной системе int занимает 4 байта. После увеличения p на 1, p указывает не на следующий байт, а на первый байт из следующей четверки байтов. Программисту не нужно думать в данном случае о размере типа.

С вычитанием ситуация такая же.

Последний важный момент этого кода в том, как преобразуется обращение к элементу массива. Имя массива — это указатель на его начало. Точка отсчета. Индекс, переданный в квадратных скобках, — смещение относительно начала массива.

Конструкция array[i] будет преобразована компилятором к *(array + i). К начальному адресу массива будет прибавлено число с учетом размерности типа данных. А затем будет взято значение по вычисленному адресу. Обратите внимание, что никто не запрещает написать и так i[array]. Ведь конструкция будет преобразована к виду.

С указателем можно складывать число, представленное переменной или целочисленной константой. Вычесть можно не только число, но и указатель из указателя. Это бывает полезно. А вот сложить два указателя, умножить или разделить указатель на число или на другой указатель — нельзя.

С указателями разных типов нельзя обходиться легкомысленно.

С точки зрения языка C все корректно. А вот в C++ будет ошибка, потому что типы указателей не совпадают.

Вот такая конструкция будет принята C++.

Применение указателей

Обычно функция возвращает одно значение. А как вернуть больше одного? Рассмотрим код функции, которая меняет местами две переменные.

Пусть есть переменные x и y с некоторыми значениями. Если выполнить функцию, передав в нее x и y, окажется, что никакого обмена не произошло. И это правильно.

При вызове этой функции в стеке будут сохранены значения x и y. Далее a и b получат значения x и y. Будет выполнена перестановка. Затем функция завершится и значения x и y будут восстановлены из стека. Все по-честному.

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

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

Теперь в функцию передаются адреса. И работа ведется относительно переданных адресов.

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

Если функция должна менять значение переменной, нужно передавать ей адрес этой переменной.

У тех, кто только начинает программировать на C, есть одна распространенная ошибка. При вводе с клавиатуры с помощью функции scanf() они передают значение переменной, а не ее адрес. А ведь scanf() должна менять значение переменной.

Еще один важный случай, когда указатели крайне полезны — это передача большого объема данных.

Пусть нам нужно передать в функцию целое число типа int. Таким образом мы передаем в функцию sizeof(int) байт. Обычно это 4 байта (размер будет зависеть от архитектуры компьютера и компилятора). 4 байта — не так много. 4 байта уйдут в стек. Потому что имеет место передача по значению.

Теперь нам нужно передать 10 таких переменных. Это уже 40 байт. Тоже невелика задача.

Вообразим себя проектировщиками Большого Адронного Коллайдера. Вы отвечаете за безопасность системы. Именно вас окружают люди с недобрыми взглядами и факелами. Нужно показать им на модели, что конца света не будет. Для этого нужно передать в функцию collaiderModel(), скажем, 1 Гб данных. Представляете, сколько информации будет сохранено в стек? А скорее всего программа не даст вам стек такого объема без специальных манипуляций.

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

Указатели — это мощный инструмент. Указатели эффективны и быстры, но не слишком безопасны. Потому как вся ответственность за их использования ложится на разработчика. Разработчик — человек. А человеку свойственно ошибаться.

В большинстве компиляторов C и С++ неинициализированные локальные переменные имеют случайное значение. Глобальные обнуляются.

Если мы захотим разыменовать указатель и присвоить ему значение, скорее всего, будет ошибка.

Неинициализированный указатель p хранит случайный адрес. Мы честно можем попытаться получить значение по этому адресу и что-то туда записать. Но совсем не факт, что нам можно что-то делать с памятью по этому адресу.

Указатели можно и нужно обнулять. Для этого есть специальное значение NULL.

Это запись больше соответствует стилю C. В C++ обычно можно инициализировать указатель нулем.

Ловкость рук и никакого мошенничества. На самом деле, если изучить библиотечные файлы языка, можно найти определение для NULL.

Для C NULL — это нуль, приведенный к указателю на void. Для C++ все немного не так. Стандарт говорит: «The macro NULL is an implementation-defined C++ null pointer constant in this International Standard. Possible definitions include 0 and 0L, but not (void*)0». То есть это просто 0 или 0, приведенный к long.

Предлагаю вам такую задачку. Папа Карло дал Буратино 5 яблок. Злой Карабас Барабас отобрал 3 яблока. Сколько яблок осталось у Буратино?

Ответ: неизвестно. Так как нигде не сказано, сколько яблок у Буратино было изначально.

Мораль: обнуляйте переменные.

Ссылки

В языке C++ появился новый механизм работы с переменными — ссылки. Функция swap() была хороша, только не слишком удобно применять разыменование. С помощью ссылок функция swap() может выглядеть аккуратнее.

А вызов функции тогда будет уже без взятия адреса переменных.


Конструкция double& объявляет ссылку на переменную типа double. При таком объявлении функции в стек будут положены не значения переменных, а их адреса.

Ссылка — это указатель, с которым можно работать, как с обычной переменной.

Ссылка не может быть равна NULL. Указатель может. Ссылка не может быть непроинициализирована. Указатель может.

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

Использование ссылок и указателей — это очень широкая тема. Описание основ на этом закончим.

За мысли и замечания спасибо Юрию Борисову, @vkirkizh, @vitpetrov.

Указатели и ссылки

В данном уроке мы рассмотрим два связанных понятия: указатели (pointers) и ссылки (references). Указатели позволяют работать с памятью напрямую. Это одна из отличительных черт C/C++. Указатели присутствуют в немногих языках, так как их использование считается небезопасным и может легко привести к многочисленным ошибкам. На этой мажорной ноте давайте начнём рассмотрение указателей.

Указатели достаточно сложная тема и их назначение мы сможем полностью понять только в следующих уроках, когда познакомимся с более сложной концепцией C++ — функциями.

Память и указатели в C++

Когда операционная система запускает программу на выполнение, она выделяет ей участок памяти, который называется куча (heap). У каждой переменной, которую вы создаёте, есть адрес в куче. C++ позволяет получить этот адрес — это просто порядковый номер. В шестнадцатеричной системе счисления он может выглядеть вот так:

На сегодняшний день адреса занимают 64 бита — 8 байт. Мы будем подробно разбирать разные архитектуры позже.

При создании переменных программа берёт какой-нибудь кусок в куче и присваивает ему имя (имя переменной). Когда вы в программе используете это имя, программа смотрит какому адресу принадлежит это имя.

Указатель — это переменная, которая содержит адрес в памяти. Указатели имеют свой синтаксис:

p — имя указателя (стандартный идентификатор С++). Звёздочка после типа говорит, что это именно указатель, а не простая переменная. Всё выражение int* читается как: указатель на тип int. При объявлении указателя он содержит случайный адрес, который может использоваться другими частями программы. Поэтому важно инициализировать указатели:

nullptr — специальное значение, которое позволяет определить содержит ли указатель правильный адрес. Поэтому мы можем использовать nullptr в проверке условия:

Типы переменных и указателей должны совпадать:

В последней строке компилятор выдаст ошибку. В будущем мы узнаем как в указателе на один тип хранить переменные разных типов — это очень мощный инструмент.

Илон Маск рекомендует:  Шаблон сайта - Закат HTML, CSS, JS, 5 страниц

Оператор получения адреса & (address-of operator)

& перед именем переменной возвращает её адрес. Это унарный оператор получения адреса (address-of operator). В результате указатель p1 будет хранить адрес переменной v1. Если мы выведем содержимое указателя в консоль, то увидим адрес — это адрес первого байта переменной v1.

Получение значения — разыменование указателя (косвенный доступ)

Мы уже знаем как создать указатель и как поместить в него адрес какой-нибудь переменной, теперь пора узнать как получить значение в памяти на которую указывает указатель. Такая операция называется разыменованием (dereferencing) или косвенным обращением(indirection operator). В коде для разыменования используется звёздочка — *.

Указатели в C++

Указатели широко используются в C++. В чем то, именно их наличие сделало этот язык более удобным для системного программирования. Но стоит отметить, что это одна из наиболее сложных для освоения возможностей C++. Идея работы с указателями состоит в том, что пользователь работает с адресом ячейки памяти и имеет возможность динамически создавать и уничтожать переменные.

Как правило, при обработке оператора объявления переменной тип имя_переменной; компилятор автоматически выделяет память под переменную имя_переменной в соответствии с указаннымтипом:

Доступ к объявленной переменной осуществляется по ее имени. При этом все обращения к переменной меняются на адрес ячейки памяти, в которой хранится ее значение:

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

Доступ к значению переменной можно получить иным способом — определить собственные переменные для хранения адресов памяти. Такие переменные называют указателями. С помощью указателей можно обрабатывать массивы, строки и структуры, создавать новые переменные в процессе выполнения программы, передавать адреса фактических параметров.

Указатель — это переменная, значением которой является адрес памяти, по которому хранится объект определенного типа (другая переменная). Например, если C это переменная типа char, а P — указатель на C, значит в P находится адрес, по которому в памяти компьютера хранится значение переменной C.

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

Указатели в C/C++ для новичков (Часть 2)

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

  • Что общего у массивов и указателей;
  • Как выделять память;
  • Что из себя представляют строки;

Кому интересно, прошу под спойлер!

И так. Для начала узнаем, что же такое массив. Вот небольшой код:

Странный код, правда? Создается массив. Потом мы его пытаемся вывести на экран как указатель(wtf?). Мало того, получаем значение массива, как будто он указатель. Бред? А вот и нет! Массив и есть указатель. Почти. Дело в том, что написав:

Мы скомандовали компьютеру: «выделить последовательно(один блок за одним) память, для трех элементов типа int, указатель на первый элемент поместить в переменную array«. Теперь попробуем наоборот, работать с указателем как с массивом:

С функциями malloc и free мы разберемся чуть пожже, просто запомните что они работают с памятью(выделяет malloc, free — освобождает). Поскольку указатель указывает(да да, я знаю) на область памяти, то этой области не обязательно должна быть присвоена переменная. То есть, можно как в нашем примере выделить память для указателя, и работать с ней.

Примечание: переменные и массивы при объявлении получают память в стеке, при выделении памяти вручную, блоки создаются в куче. Гугл ваш друг.

Как вы уже знаете, переменная массива тоже указатель(на первый элемент), так что нам мешает создать указатель, выделить блок памяти и присвоить ему значение первого элемента? А ничего. Функция malloc выделает указанный размер памяти в байтах и возвращает указатель на его первый элемент. Поскольку она универсальная, то возвращает указатель типа void*, его нужно явно преобразовать в тип int*. Для того, что бы вычислить необходимое количество байт я использовал оператор sizeof, он возвращает размер переменной или типа. Функция free освобождает память, выделенную под указатель.

Примечание: sizeof вычисляется на этапе компиляции, и если ее применять в указателям(то есть пытаться вычислить динамическую память) она всегда будет возвращать размер указателя.

Важно: всегда освобождайте выделенную память! Думаю все слышали о такой вещи, как «утечка памяти»? Это и есть блоки памяти, которую не освободили. То есть, создался указатель, ему выделился блок памяти(назовем его блок1). Потом указателю выделили другой блок памяти(блок2). В результате этого, блок1 попрежнему воспринимается операционной системой как используемый, но доступа к нему уже нет(указателя нет, ничего на него не указывает). Освободится он лишь в случае закрытия программы или перезагрузки компьютера. Поэтому следуйте правилу: «Использовал указатель? — Освободи память! Присвой указателю NULL!».

Все очень запутанно, поэтому я попробую графически вам показать. Память состоит из бит. 8 бит = 1 байт. Это думаю все знают. Операционная система работает с памятью, как с блоками байт. Не будем заморачиваться размерами, а представим себе что int и int* занимают одинаковый размер блока в памяти.

Примечание: на всех компьютерах реализация размеров int разная. То есть для x86 — один размер, для x64 — другой.

Указатели в языке Си

Тема указателей довольно сложна и мне часто присылают вопросы по ней. В этой статье мы подробно разберем тему указателей.

Рекомендуемая работа с памятью

Перед тем, как разбираться с указателями, сначала посмотрим, как лучше работать с памятью в Си.

Рекомендуемый способ — это прямо описать ту область памяти, которую вы хотите использовать.

В этом примере мы задали целое число, массив и вещественное число. То есть мы явно дали имя элементу данных.

Если мы теперь будем использовать этот элемент данных, то достаточно указать его имя.

Почему рекомендуется делать именно так? Три причины:

Причина 1. Соблюдение принципа изоляции кода

В языке Си данные в функцию передаются по значению. Эти значит, что копия входных параметров размещается на стеке. После выхода из функции эти данные уничтожаются. Поэтому программист уверен, что никакого влияния на остальную программу эти данные не окажут.

Причина 2. Простой вызов в отладчике

Просто добавьте имя элемента данных и вы можете легко наблюдать, что с ним происходит во время отладки.

Причина 3. Самодокументируемый код

Если мы дадим объектам данных осмысленные имена, то код будет хорошо читаться. Например:

Но так как язык Си универсален и есть много способов писать программы, то есть и такой способ как указатели.

Что такое указатели?

Указатель — это переменная, которая содержит адрес некоторого объекта в памяти.

Для работы с указателями используются два оператора:
& — получить адрес переменной (&x — адрес переменной x)

* — получить значение переменной по адресу (*px — значение по адресу px)

Рассмотрим участок памяти. Предположим, что по адресу 54100 размещена символьная переменная char x;

При заведения указателя мы сразу говорим компилятору, что мы завели указатель на объект типа char. Чтобы не было путаницы в именах рекомендуется указатель начинать с символа «p».

Важный момент. Когда комплятор выделяет память под «char x», то выделяется один байт, потому что x — это символ, то есть это однобайтовая переменная. Но когда компилятор выделяет память под «char *px», то выделяется обычно 4 байта, так как адрес (в 32-х битовой системе) занимает 4 байта.

*px — читается как «взять значение по адресу, xранящемуся в px»

Теперь нам нужно записать:


  • в переменную x некоторое значение,
  • a в указатель px — адрес этого значения.

Для этого мы пишем следующие строки:

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

Но тут нужно быть осторожным. Если в указателе лежит адрес, который не выделен программе, то эта ситуация называется висячий указатель.

Предположим, что px — это висячий указатель. Действия с самим указателем px могут быть любыми, программа не пострадает. Но если мы выполним действие *px над памятью, которая не была выделена программе, то операционная система прекратит действие программы и напишет что-то вроде: «Программа выполнила недопустимую операцию и будет закрыта».

Преимущество указателей

Преимущество указателей в том, что они позволяют передать в функцию значение по ссылке. Слово «ссылка» означает, что мы не передаем значение, а ссылаемся на адрес этого значения. В этом случае можно внутри функции изменять значение элемента данных. Хотя указатель и будет уничтожен после выхода из функции, но мы изменили значение памяти по указателю и это изменение значения сохранится после выход из функции.

Именно поэтому их часто используют.

Недостатки указателей

Главные недостатки указателей:

1. Нарушение принципов изоляции кода

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

2. Отвлечение внимание на детали реализации

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

3. Плохая читаемость кода

Прямое использование переменной является самоочевидной вещью. Если мы видим x++, то сразу понимаем, что происходит, а вот если мы видим (*px)++ или *px++, то чтобы понять процесс, нужно вдумываться.

Сравним два варианта кода. Код с переменными:

и код с указателями

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

Указатели лучше вообще не использовать.

Конечно же, на это последует вопрос: «А как тогда изменять значения внутри функции?»

Этот код поменяет значения любых элементов массива.

Если же речь идет именно о переменных, то правильный ответ такой: если вам нужно изменять внешние значения переменных внутри функции — у вас неверно спроектирована программа.

В правильно спроектированной программе есть три вида элемента данных:

  • Элементы общего назначения — этих данных немного и они должны быть глобальными для всей программы. (например, идентификатор открытой базы данных)
  • Элементы модуля — сюда рекомендуется выносить все данные, которые нужны модулю, чтобы не раздувать передаваемые параметры (например, идентификаторы окон).
  • Временные данные — они должны быть описаны внутри функций и удаляться при выходе.

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

Ответы на вопросы

Вопрос

Мы сначала передали адрес a в функцию AddFive, затем создали указатель int px(но почему именно в аргументе функции?), далее значение по адресу указателя увеличили на 5. Но тут непонятно, разве так будет работать? То есть, нужно сначала адрес присвоить указателю, как Вы показывали ранее в статье. Получится вот так:

Ответ

В аргументах функции ничего не создается. В аргументах указываются типы, чтобы компилятор мог проверить их при вызове функции. Адрес указателю px не надо присваивать внутри функции, так как этот адрес уже передан в качестве аргумента при вызове функции. То есть во время работы функции указатель px уже указывает на нужный адрес.

Вопрос

Я проверил код в CodeBlocks. Если мы укажем, например, вот так:

то возникнет ошибка: «px undeclared». Т.е. как видите указатель px не объявлен. А чтобы всё работало мы одновременно, c одной стороны, объявляем указатель в аргументе функции AddFive, а с другой стороны, записываем в указатель адрес a. Поэтому, непонятно почему Вы считаете, что ничего там не создаётся. Ведь память под указатель выделилась, так? И как раз, так как мы создали указатель, пусть и в аргументе функции, программа и работает.

Ответ

На этапе компиляции программы при проверке аргументов функции компилятор ничего не создает, а только проверяет соответствие типов аргументов в описании функции и при ее вызове. В данном примере в описании функции нет аргументов, а при вызове передается адрес — это первая ошибка в данном фрагменте. Вторая ошибка заключается в том, что идет обращение к переменной px, но она не объявлена, поэтому компилятор пишет, что «px undeclared», то есть «переменная px не объявлена».

Когда ошибки будут устранены и программа будет запущена, то в момент вызова AddFive(&a) произойдет следующее:

  1. Программа считает адрес переменной a и передаст управление функции AddFive.
  2. Аргументы функции (в данном случае адрес) будут размещены на стеке функции (это временное хранилище данных).
  3. Во время выполнения функции данные будут взяты со стека и обработаны.
  4. После выхода из функции стек будет очищен.

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

Илон Маск рекомендует:  Как собрать дешевый компьютер для дома и офиса

Применение указателей в c

Язык C# также поддерживает указатели (pointer), однако несколько ограниченно. Ограниченность заключается в том, что применение указателей не поощряют, поскольку справедливо считается, что это может повлиять на надежность как кода, так и среды выполнения в целом.

Указатель — это всего лишь обычная переменная, содержащая в себе адрес памяти, в которой содержится переменная какого-либо типа (адрес переменной). Другое ограничение C# — указатели могут быть объявлены только для удержания адреса переменной и массива. В отличие от ссылочных типов (reference types), типы указателей (pointer types) не отслеживаются механизмом сбора мусора по умолчанию (default garbage collection). По той же самой причине указателям не разрешено указывать на ссылочный тип (reference type) или даже на тип структуры, которая содержит в себе ссылочный тип. Можно сказать, что указатели могут указывать только на неуправляемые типы (unmanaged types), которые включают в себя все базовые типы данных (basic data types), типы перечисления (enum types), другие типы указателей и структуры, которые содержат только неуправляемые типы.

[Объявление указателя]

Основная форма объявления переменной указателя следующая:

Здесь звездочка * обозначает оператор разыменования (de-reference operator). Например, строка

объявляет переменную указателя ptr, которая может держать в себе адрес переменной типа int. Обратный оператор генерирования ссылки (reference operator, &) может использоваться для получения адреса переменной. Например, у нас есть переменная

Оператор &x даст нам адрес переменной x, который мы можем присвоить переменной указателя.

Здесь мы рассмотрели обычное простое использование указателей, применяемое на языках C и C++. На языке C# все происходит похожим образом, однако есть некоторые отличия.

[Небезопасный код (Unsafe Code)]

Правилами языка C# определено, что операторы могут выполняться либо в безопасном (safe), либо в небезопасном (unsafe) контексте. Операторы, процедуры и функции, помеченные как небезопасные, запускаются вне области управления памятью с помощью сборщика мусора (Garbage Collector). Помните, что любой код C#, использующий указатели, требует для выполнения небезопасный контекст.

Для того, чтобы пометить небезопасный контекст (т. е. код), используется ключевое слово unsafe. Мы можем использовать unsafe двумя различными способами. Ключевое слово unsafe может использоваться как модификатор метода, свойства, конструктора, и т. д. Например:

Кроме того, ключевое слово unsafe может также использоваться, чтобы пометить группу операторов как небезопасную:

[Прикрепление объекта (Pinning an Object)]

Сборщик мусора C# может переместить объекты в памяти в соответствии с алгоритмом процесса уборки мусора. Язык C# предоставляет специальное ключевое слово fixed, чтобы указать сборщику мусора не перемещать объект. Это означает, что позиция переменной в памяти в памяти фиксируется, чтобы на нее мог ссылаться указатель. На C# это называется прикреплением (pinning).

Функционал оператора fixed обычно реализован путем генерации таблиц, описывающих для сборщика мусора, какие объекты в каких областях выполняемого кода должны оставаться фиксированными. Таким образом, пока процесс сбора мусора не встречает во время выполнения операторов fixed, потери ресурсов на них оказываются весьма незначительными. Однако, когда сборщик мусора встречает fixed, то фиксированные объекты могут привести к образованию фрагментации кучи (heap). Т. е. в куче могут появиться неиспользуемые «дыры». Следовательно, объекты должны использовать fixed только тогда, когда это абсолютно необходимо, и только на самый малый, насколько это возможно, промежуток времени выполнения кода.

[Указатели и методы (Pointers & Methods)]

Указатели могут быть переданы в метод как аргументы. Методы также могут возвратить указатель. Пример:

[Указатели и преобразования типа (Pointers & Conversions)]

Типы указателей в C# не наследуются от объекта, и нет существующих преобразований между типами указателя и объектами. Это означает, что boxing и un-boxing не поддерживается указателями. Однако C# поддерживает преобразования между различными типами указателей, типами указателей (pointer types) и целочисленными типами (integral types).

C# поддерживает и неявные (implicit), и явные (explicit) преобразования указателя в небезопасном контексте. Имеются неявные преобразования типа:

1. Из типа указателя на любой тип к типу указателя на тип void *.
2. Из типа null к любому другому типу указателя.

Оператор преобразования типа cast operator () необходим для любых явных преобразований типа. Имеются явные преобразования типа:

1. Из любого типа указателя на любой другой тип указателя.
2. Из типов sbyte, byte, short, ushort, int, uint, long, ulong к любому другому типу указателя.
3. Из любого типа указателя к типам sbyte, byte, short, ushort, int, uint, long, ulong.

[Арифметика указателей (Pointer Arithmetic)]

В небезопасном контексте операторы ++ и — могут быть приложены к переменной указателя всех типов, за исключением типа void *. Таким образом, для каждого типа указателя T* следующие операторы будут неявно перегружены (implicitly overloaded).


T* operator ++ (T *x);
T* operator — (T *x);

Оператор ++ добавляет sizeof(T) к адресу, содержащемуся в переменной указателя, и оператор — вычитает sizeof(T) из этого адреса для переменной указателя на тип T*.

In an un-safe context a constant can be added or subtracted from a pointer variable. Similarly a pointer variable can be subtracted from another pointer variable. But it is not possible to add two pointer variables in C#.

В небезопасном контексте операторы ==, !=, , = могут также использоваться со значениями указателей на все типы. Умножение и деление переменной указателя на константу или другую переменную-указатель не поддерживается в C#.

[Выделение памяти стека (Stack Allocation)]

В небезопасном контексте локальные определения переменных могут включать инициализатор выделения стека (stack allocation initialiser), который выделяет память из стека вызовов (call stack).

Оператор stackalloc T[E] требует T как необрабатываемый (unmanaged) тип и E как выражение типа int. Вышеуказанная конструкция выделяет E * sizeof(T) байт из стека и генерирует указатель типа T* на новый выделенный блок. Если E отрицательно, то выбрасывается исключение System.OverFlowException. Если недостаточно памяти, то срабатывает исключение System.StackOverflowException.

Содержимое только что выделенной памяти является неопределенным. Нет способа неявного освобождения памяти, выделенной через stackalloc. Вместо этого весь блок памяти стека автоматически освобождается после возврата из функции.

[Указатели и массивы (Pointers & Arrays)]

В C# может быть получен доступ к элементам массива при помощи использованием нотаций указателя.

[Указатели и структуры (Pointers & Structures)]

Все структуры C# имеют тип переменной. На структуру тоже может быть задан указатель только в том случае, если структура в качестве своих полей содержит только типы в виде значения. Пример:

[Выводы]

1. Функции или процедуры C#, если они используют указатели, должны иметь атрибут unsafe.

2. Проект C#, в котором есть небезопасный (unsafe) код, должен иметь соответствующее разрешение в настройках (соответствует опции компилятора /unsafe).

3. Получение указателя от объекта возможно только в том случае, если он определен с атрибутом fixed.

Указатели (C++) Pointers (C++)

Указатели объявляются с помощью следующей последовательности. Pointers are declared using the following sequence.

[storage-class-specifiers] [cv-qualifiers] type-specifiers [ms-modifier] declarator ; [storage-class-specifiers] [cv-qualifiers] type-specifiers [ms-modifier] declarator ;

где можно использовать любой допустимый декларатор указателя для декларатор. where any valid pointer declarator may be used for declarator. Синтаксис простого декларатора указателя следующий: The syntax for a simple pointer declarator is as follows:

Спецификаторы объявления: The declaration specifiers:

Необязательный спецификатор класса хранения. An optional storage class specifier. Дополнительные сведения см. в разделе спецификаторы. For more information, see Specifiers.

Необязательный const или volatile ключевое слово, относящееся к типу объекта, на который нужно указывать. An optional const or volatile keyword applying to the type of the object to be pointed to.

Спецификатор типа: имя типа, представляющее тип объекта, на который нужно указывать. The type specifier: the name of a type representing the type of the object to be pointed to.

Декларатор: The declarator:

Необязательный модификатор, используемый в системах Microsoft. An optional Microsoft-specific modifier. Дополнительные сведения см. в разделе модификаторы, используемые Microsoft. For more information, see Microsoft-Specific Modifiers.

* Оператор. The * operator.

Необязательный const или volatile ключевое слово, применение к самому указателю. An optional const or volatile keyword applying to the pointer itself.

Идентификатор. The identifier.

Необязательный инициализатор. An optional initializer.

Декларатор для указателя на функцию выглядит следующим образом: The declarator for a pointer to function looks like this:

(* [ cv квалификаторы] идентификатор ) ( список аргументов ) [ cv и(или)] [ спецификация исключений] [ = выражение] ; (* [cv-qualifiers] >

Для массива указателей синтаксис следующий: For an array of pointers, the syntax looks like this:

* identifier [ [constant-expression] ] * identifier [ [constant-expression] ]

Несколько деклараторов и их инициализаторы могут присутствовать вместе в одном объявлении в разделенном запятыми списке после спецификатора объявления. Multiple declarators and their initializers may appear together in a single declaration in a comma separated list following the declaration specifier.

Простой пример объявления указателя: A simple example of a pointer declaration is:

Указывает, что предыдущего объявления pch указывает на объект типа char. The preceding declaration specifies that pch points to an object of type char.

Более сложный пример: A more complex example is

Указывает, что предыдущего объявления ptr — это постоянный указатель на объект типа без знака int со статической длительностью хранения. The preceding declaration specifies that ptr is a constant pointer to an object of type unsigned int with static storage duration.

В следующем примере показываются объявление и инициализация нескольких указателей. The next example shows how multiple pointers are declared and initialized:

В приведенном выше примере указатели p и q указывают на объекты типа int и инициализируются с адресами i и j соответственно. In the preceding example, pointers p and q both point to objects of type int and are initialized to the addresses of i and j respectively. Спецификатор класса хранения статический относится к обоим указателям. The storage class specifier static applies to both pointers.

Пример Example

Пример Example

В другом примере показывается использование указателей в структурах данных; в данном случае — в связанном списке. Another example illustrates the use of pointers in data structures; in this case, a linked list.

Указатель в языке Си

Указатель — переменная, содержащая адрес объекта. Указатель не несет информации о содержимом объекта, а содержит сведения о том, где размещен объект.

Указатели широко используются в программировании на языке Си.
Указатели часто используются при работе с массивами.

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

Каждая переменная в памяти имеет свой адрес — номер первой ячейки, где она расположена, а также свое значение. Указатель — это тоже переменная, которая размещается в памяти. Она тоже имеет адрес, а ее значение является адресом некоторой другой переменной. Переменная, объявленная как указатель, занимает 4 байта в оперативной памяти (в случае 32-битной версии компилятора).

Указатель, как и любая переменная, должен быть объявлен.
Общая форма объявления указателя

Тип указателя — это тип переменной, адрес которой он содержит.

Для работы с указателями в Си определены две операции:

  • операция * (звездочка) — позволяет получить значение объекта по его адресу — определяет значение переменной, которое содержится по адресу, содержащемуся в указателе;
  • операция & (амперсанд) — позволяет определить адрес переменной.

Для указанного примера обращение к одним и тем же значениям переменной и адреса представлено в таблице

Переменная Указатель
Адрес &c p
Значение c *p

Расположение в памяти переменной a и указателя b:

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

Комментариев к записи: 32

/Рабочий стол/CodeC$ gcc pointers.c -o pointers.exe pointers.c: In function ‘main’: pointers.c:12:62: warning: format ‘%x’ expects argument of type ‘unsigned int’, but argument 2 has type ‘int *’ [-Wformat=] printf(«\n Адрес переменной a равен %x шестн.», &a);

%ls pointers.c:14:66: warning: format ‘%x’ expects argument of type ‘unsigned int’, but argument 2 has type ‘int *’ [-Wformat=] printf(«\n Значение указателя b равно %x шестн.», b);

^ %ls pointers.c:15:85: warning: format ‘%x’ expects argument of type ‘unsigned int’, but argument 2 has type ‘int **’ [-Wformat=] ntf(«\n Адрес расположения указателя b равен %x шестн.», &b);

#include
#include
#include

void helloWorld (GtkWidget *wid, GtkWidget *win)
<
GtkW >NULL ;

dialog = gtk_message_dialog_new (GTK_WINDOW (win), GTK_DIALOG_MODAL,
GTK_MESSAGE_INFO, GTK_BUTTONS_CLOSE, sqlite3_libversion());
gtk_window_set_position (GTK_WINDOW (dialog), GTK_WIN_POS_CENTER);
gtk_dialog_run (GTK_DIALOG (dialog));
gtk_widget_destroy (dialog);
>

int main ( int argc, char *argv[])
<
GtkW >NULL ;
GtkW >NULL ;
GtkW >NULL ;

/* Initialize GTK+ */
g_log_set_handler ( «Gtk» , G_LOG_LEVEL_WARNING, (GLogFunc) gtk_false, NULL );
gtk_init (&argc, &argv);
g_log_set_handler ( «Gtk» , G_LOG_LEVEL_WARNING, g_log_default_handler, NULL );

/* Create the main window */
win = gtk_window_new (GTK_WINDOW_TOPLEVEL);
gtk_container_set_border_width (GTK_CONTAINER (win), 8);
gtk_window_set_default_size (GTK_WINDOW (win), 400, 200);
gtk_window_set_title (GTK_WINDOW (win), «Hello World» );
gtk_window_set_position (GTK_WINDOW (win), GTK_WIN_POS_CENTER);
gtk_widget_realize (win);
g_signal_connect (win, «destroy» , gtk_main_quit, NULL );

/* Create a vertical box with buttons */
vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6);
gtk_container_add (GTK_CONTAINER (win), vbox);

button = gtk_button_new_from_stock (GTK_STOCK_DIALOG_INFO);
g_signal_connect (G_OBJECT (button), «clicked» , G_CALLBACK (helloWorld), (gpointer) win);
gtk_box_pack_start (GTK_BOX (vbox), button, TRUE, TRUE, 0);

button = gtk_button_new_from_stock (GTK_STOCK_CLOSE);
g_signal_connect (button, «clicked» , gtk_main_quit, NULL );
gtk_box_pack_start (GTK_BOX (vbox), button, TRUE, TRUE, 0);

/* Enter the main loop */
gtk_widget_show_all (win);
gtk_main ();
return 0;
>

Понравилась статья? Поделиться с друзьями:
Кодинг, CSS и SQL