Система типов C++
Концепция типа важна в C++. Каждая переменная, аргумент функции и возвращаемое значение функции должны иметь тип, чтобы их можно было скомпилировать. Кроме того, все выражения (включая литеральные значения) неявно задают тип компилятором перед их вычислением. Некоторые примеры типов включают встроенные типы, например int для хранения целых значений, double хранения значений с плавающей запятой или типов стандартной библиотеки, таких как класс std::basic_string для хранения текста. Вы можете создать собственный тип, определив или class struct . Тип указывает объем памяти, выделенной для переменной (или результата выражения). Тип также указывает типы значений, которые могут храниться, как компилятор интерпретирует битовые шаблоны в этих значениях, а также операции, которые можно выполнять с ними. Эта статья содержит неформальный обзор основных особенностей системы типов C++.
Терминология
Скалярный тип: тип, содержащий одно значение определенного диапазона. Скаляры включают арифметические типы (целочисленные или значения с плавающей запятой), элементы типа перечисления, типы указателей, типы указателей на члены и std::nullptr_t . Основные типы обычно скалярные.
Составной тип: тип, который не является скалярным типом. Составные типы включают типы массивов, типы функций, типы классов (или структуры), типы объединения, перечисления, ссылки и указатели на нестатические члены класса.
Переменная: символическое имя количества данных. Имя можно использовать для доступа к данным, которые он ссылается на область кода, в котором он определен. В C++ переменная часто используется для ссылки на экземпляры скалярных типов данных, а экземпляры других типов обычно называются объектами.
Объект. Для простоты и согласованности в этой статье используется объект терминов для ссылки на любой экземпляр класса или структуры. Если он используется в общем смысле, он включает все типы, даже скалярные переменные.
Тип POD (обычные старые данные): эта неформальная категория типов данных в C++ относится к типам, которые являются скалярными (см. раздел «Основные типы») или являются классами POD. Класс POD не имеет статических элементов данных, которые также не являются POD, и не имеет определяемых пользователем конструкторов, определяемых пользователем деструкторов или определяемых пользователем операторов назначения. Кроме того, класс POD не имеет виртуальных функций, базового класса и ни закрытых, ни защищенных нестатических данных-членов. Типы POD часто используются для внешнего обмена данными, например с модулем, написанным на языке С (в котором имеются только типы POD).
Указание типов переменных и функций
C++ — это строго типизированный язык и статически типизированный язык. Каждый объект имеет тип и этот тип никогда не изменяется. При объявлении переменной в коде необходимо явно указать его тип или использовать auto ключевое слово для указания компилятору выводить тип из инициализатора. При объявлении функции в коде необходимо указать тип возвращаемого значения и каждого аргумента. Используйте возвращаемый тип void значения, если значение не возвращается функцией. Исключением является использование шаблонов функций, которые позволяют использовать аргументы произвольных типов.
После первого объявления переменной его тип нельзя изменить в какой-то момент позже. Однако можно скопировать значение переменной или возвращаемое значение функции в другую переменную другого типа. Такие операции называются преобразованиями типов, которые иногда необходимы, но также являются потенциальными источниками потери данных или неправильности.
При объявлении переменной типа POD настоятельно рекомендуется инициализировать ее, то есть дать ему начальное значение. Пока переменная не инициализирована, она имеет «мусорное» значение, определяемое значениями битов, которые ранее были установлены в этом месте памяти. Это важный аспект C++ для запоминания, особенно если вы поступаете из другого языка, который обрабатывает инициализацию для вас. При объявлении переменной типа класса, отличного от POD, конструктор обрабатывает инициализацию.
В следующем примере показано несколько простых объявлений переменных с небольшим описанием для каждого объявления. В примере также показано, как компилятор использует сведения о типе, чтобы разрешить или запретить некоторые последующие операции с переменной.
int result = 0; // Declare and initialize an integer. double coefficient = 10.8; // Declare and initialize a floating // point value. auto name = "Lady G."; // Declare a variable and let compiler // deduce the type. auto address; // error. Compiler cannot deduce a type // without an intializing value. age = 12; // error. Variable declaration must // specify a type or use auto! result = "Kenny G."; // error. Can't assign text to an int. string result = "zero"; // error. Can't redefine a variable with // new type. int maxValue; // Not recommended! maxValue contains // garbage bits until it is initialized.
Базовые (встроенные) типы
В отличие от некоторых других языков, в C++ нет универсального базового типа, от которого наследуются все остальные типы. Язык включает множество фундаментальных типов, также известных как встроенные типы. Эти типы включают числовые типы, такие как int , double long , bool и char wchar_t типы для символов ASCII и ЮНИКОД соответственно. Большинство целочисленных фундаментальных типов (за исключением bool , double wchar_t и связанных типов) имеют unsigned версии, которые изменяют диапазон значений, которые переменная может хранить. Например, int 32-разрядное целое число со знаком может представлять значение от -2 147 483 648 до 2 147 483 647. Объект unsigned int , который также хранится как 32 бита, может хранить значение от 0 до 4 294 967 295. Общее количество возможных значений в каждом случае одинаково, отличается только диапазон.
Компилятор распознает эти встроенные типы и имеет встроенные правила, которые управляют операциями, которые можно выполнять с ними, и как их можно преобразовать в другие фундаментальные типы. Полный список встроенных типов и их размер и числовые ограничения см. в разделе «Встроенные типы».
На следующем рисунке показаны относительные размеры встроенных типов в реализации Microsoft C++:

В следующей таблице перечислены наиболее часто используемые основные типы и их размеры в реализации Microsoft C++:
| Тип | Size | Комментарии |
|---|---|---|
| int | 4 байта | Выбор по умолчанию для целочисленных значений. |
| double | 8 байт | Выбор по умолчанию для значений с плавающей запятой. |
| bool | 1 байт | Представляет значения, которые могут быть или true, или false. |
| char | 1 байт | Используйте для символов ASCII в старых строках в стиле C или в объектах std::string, которые никогда не будут преобразовываться в Юникод. |
| wchar_t | 2 байта | Представляет «расширенные» символы, которые могут быть представлены в формате Юникод (UTF-16 в Windows, в других операционных системах возможно другое представление). wchar_t — это тип символа, используемый в строках типа std::wstring . |
| unsigned char | 1 байт | C++ не имеет встроенного типа байтов. Используется unsigned char для представления байтового значения. |
| unsigned int | 4 байта | Вариант по умолчанию для битовых флагов. |
| long long | 8 байт | Представляет гораздо больший диапазон целых значений. |
Другие реализации C++ могут использовать различные размеры для определенных числовых типов. Дополнительные сведения о размерах и отношениях размера, необходимых стандарту C++, см. в разделе «Встроенные типы».
Тип void .
Тип void является специальным типом; нельзя объявить переменную типа, но можно объявить переменную типа void void * (указатель void на), которая иногда необходима при выделении необработанной (нетипизированной) памяти. Однако указатели на небезопасные void и их использование не рекомендуется использовать в современном C++. В объявлении функции возвращаемое значение означает, void что функция не возвращает значение; использование функции в качестве возвращаемого типа является общим и приемлемым. void Хотя язык C требует функций, имеющих нулевые параметры для объявления void в списке параметров, например, fn(void) эта практика не рекомендуется в современном языке C++; следует объявить fn() функцию без параметров. Дополнительные сведения см. в разделе «Преобразования типов» и «Безопасность типов».
const квалификатор типов
Любой встроенный или определяемый пользователем тип может быть квалифицирован ключевое слово const . Кроме того, функции-члены могут быть const квалифицированы и даже const перегружены. Значение const типа нельзя изменить после инициализации.
const double PI = 3.1415; PI = .75; //Error. Cannot modify const variable.
Квалификатор const широко используется в объявлениях функций и переменных и «константной правильности» является важной концепцией в C++; по сути это означает, что для const обеспечения гарантии во время компиляции значения не изменяются непреднамеренно. Дополнительные сведения см. в разделе const .
const Тип отличается от его const версии, const int например, является отдельным типом. int Оператор C++ const_cast можно использовать в тех редких случаях, когда необходимо удалить const-ness из переменной. Дополнительные сведения см. в разделе «Преобразования типов» и «Безопасность типов».
Строковые типы
Строго говоря, язык C++ не имеет встроенного типа строки; char и wchar_t сохранить одинарные символы — необходимо объявить массив этих типов, чтобы приблизить строку, добавив завершающее значение NULL (например, ASCII ‘\0’ ) в элемент массива за последним допустимым символом (также называемой строкой в стиле C). Строки в стиле C требовали написания гораздо большего объема кода или использования внешних библиотек служебных функций. Но в современном C++у нас есть типы std::string стандартной библиотеки (для 8-разрядных char строк символов типа) или std::wstring (для 16-разрядных wchar_t строк символов типа). Эти контейнеры стандартной библиотеки C++ можно рассматривать как собственные типы строк, так как они являются частью стандартных библиотек, включенных в любую соответствующую среду сборки C++. Используйте директиву #include , чтобы сделать эти типы доступными в программе. (Если вы используете MFC или ATL, класс также доступен, CString но не является частью стандарта C++.) Использование массивов символов, завершаемых значением NULL (строки в стиле C ранее упоминание) не рекомендуется использовать в современном C++.
Определяемые пользователем типы
При определении class , struct union или enum , эта конструкция используется в остальной части кода, как если бы это был фундаментальный тип. Он имеет известный размер в памяти, и в его отношении действуют определенные правила проверки во время компиляции и во время выполнения в течение срока использования программы. Основные различия между базовыми встроенными типами и пользовательскими типами указаны ниже:
- Компилятор не имеет встроенных сведений о пользовательском типе. Он узнает о типе при первом обнаружении определения во время процесса компиляции.
- Пользователь определяет, какие операции можно выполнять с типом и как его можно преобразовать в другие типы, задавая (через перегрузку) соответствующие операторы, либо в виде членов класса, либо в виде функций, не являющихся членами. Дополнительные сведения см. в разделе «Перегрузка функций»
Типы указателей
Как и в самых ранних версиях языка C, C++ продолжает объявлять переменную типа указателя с помощью специального декларатора (звездочка * ). Тип указателя хранит адрес расположения в памяти, в котором хранится фактическое значение данных. В современном C++эти типы указателей называются необработанными указателями, и они доступны в коде с помощью специальных операторов: * (звездочка) или -> (дефис с большей, чем часто называется стрелкой). Эта операция доступа к памяти называется деreferencing. Какой оператор вы используете, зависит от того, выполняется ли разыменовка указателя на скаляр или указатель на элемент в объекте.
Работа с типами указателя долгое время была одним из наиболее трудных и непонятных аспектов разработки программ на языках C и C++. В этом разделе описаны некоторые факты и методики, которые помогут вам использовать необработанные указатели. Однако в современном C++больше не требуется (или рекомендуется) использовать необработанные указатели для владения объектами вообще из-за эволюции интеллектуального указателя (рассматривается больше в конце этого раздела). Это по-прежнему полезно и безопасно использовать необработанные указатели для наблюдения объектов. Тем не менее, если их необходимо использовать для владения объектами, следует сделать это с осторожностью и тщательно учитывать, как объекты, принадлежащие им, создаются и уничтожаются.
Первое, что следует знать, заключается в том, что объявление необработанной переменной указателя выделяет достаточно памяти для хранения адреса: расположение памяти, которое указывает указатель, когда оно разоменовывается. Объявление указателя не выделяет память, необходимую для хранения значения данных. (Эта память также называется резервным хранилищем.) Другими словами, объявляя необработанную переменную указателя, вы создаете переменную адреса памяти, а не фактическую переменную данных. Если вы разыменуете переменную указателя, прежде чем убедиться, что она содержит допустимый адрес для резервного хранилища, это приводит к неопределенному поведению (обычно неустранимой ошибке) в программе. В следующем примере демонстрируется подобная ошибка:
int* pNumber; // Declare a pointer-to-int variable. *pNumber = 10; // error. Although this may compile, it is // a serious error. We are dereferencing an // uninitialized pointer variable with no // allocated memory to point to.
Пример разыменовывает тип указателя без выделения памяти для хранения фактических целочисленных данных или без выделенного допустимого адреса памяти. В следующем коде исправлены эти ошибки:
int number = 10; // Declare and initialize a local integer // variable for data backing store. int* pNumber = &number; // Declare and initialize a local integer // pointer variable to a valid memory // address to that backing store. . *pNumber = 41; // Dereference and store a new value in // the memory pointed to by // pNumber, the integer variable called // "number". Note "number" was changed, not // "pNumber".
В исправленном примере кода используется локальной память стека для создания резервного хранилища, на который указывает указатель pNumber . Базовый тип используется для простоты. На практике резервные хранилища для указателей чаще всего представляют собой определяемые пользователем типы, динамически выделенные в области памяти, называемой кучей (или свободное хранилище), с помощью new выражения ключевое слово (в программировании в стиле C используется старая malloc() функция библиотеки среды выполнения C). После выделения эти переменные обычно называются объектами, особенно если они основаны на определении класса. Память, выделенная с new помощью соответствующей delete инструкции (или, если вы использовали malloc() функцию для выделения, функция free() среды выполнения C).
Однако легко забыть удалить динамически выделенный объект, особенно в сложном коде, что приводит к ошибке ресурса, называемой утечкой памяти. По этой причине использование необработанных указателей не рекомендуется использовать в современном C++. Почти всегда лучше упаковать необработанный указатель в умный указатель, который автоматически освобождает память при вызове его деструктора. (То есть, когда код выходит из область для интеллектуального указателя.) Используя интеллектуальные указатели, вы практически устраняете целый класс ошибок в программах C++. В следующем примере предположим, что MyClass — это пользовательский тип, который имеет открытый метод DoSomeWork();
void someFunction() < unique_ptrpMc(new MyClass); pMc->DoSomeWork(); > // No memory leak. Out-of-scope automatically calls the destructor // for the unique_ptr, freeing the resource.
Дополнительные сведения о смарт-указателях см. в разделе «Умные указатели».
Дополнительные сведения о преобразованиях указателей см. в разделе «Преобразования типов» и «Безопасность типов».
Дополнительные сведения о указателях в целом см. в разделе «Указатели».
Типы данных Windows
В классическом программировании Win32 для C и C++большинство функций используют типдефы и #define макросы windows (определенные в windef.h ) для указания типов параметров и возвращаемых значений. Эти типы данных Windows в основном являются специальными именами (псевдонимами) для встроенных типов C/C++. Полный список этих определений typedefs и препроцессора см. в разделе «Типы данных Windows». Некоторые из этих типов, таких как HRESULT и LCID , являются полезными и описательными. Другие, например INT , не имеют специального значения и являются просто псевдонимами для основных типов C++. Прочие типы данных Windows имеют имена, которые сохранились с эпохи программирования на языке C для 16-разрядных процессоров, и не имеют смысла или значения на современном оборудовании или в современных операционных системах. Существуют также специальные типы данных, связанные с библиотекой среда выполнения Windows, перечисленные как среда выполнения Windows базовые типы данных. В современном C++общее руководство заключается в том, чтобы предпочесть базовые типы C++, если только тип Windows не сообщает некоторое дополнительное значение о том, как следует интерпретировать значение.
Дополнительные сведения
Дополнительные сведения о системе типов C++ см. в следующих статьях.
Типы значений
Описывает типы значений вместе с проблемами, связанными с их использованием.
Преобразования типов и безопасность типов
Описание типовых проблем преобразования типов и способов их избежать.
Добавление своего типа данных ( С++ )
Доброго времени суток, кто-нибудь подскажите что прочитать, чтобы понять, как реализовать свой тип ( речь не об ООП ). Пример простой. С C/C++ есть например ключевое слово int. И запись int a = 40; говорит компилятору: «Выдели 4 байта памяти, назови их «а» и занусь туда двоичное представление числа 40″ за счет чего я могу сделать такое слово ( не ООП ). type a = 40; «Выдели 8 байт (например 8) памяти, назови их «а» и засунь туда двоичное представление числа 40″ Проще говоря, кто знает где прочитать как писался Си или C++. Понимаю что это низкоуровневое и тд. Но я даже в таком ключе не могу найти информацию.
Отслеживать
32.1k 19 19 золотых знаков 79 79 серебряных знаков 106 106 бронзовых знаков
задан 19 ноя 2015 в 19:52
851 1 1 золотой знак 8 8 серебряных знаков 19 19 бронзовых знаков
Свои типы — структуры.
19 ноя 2015 в 20:06
Странный вопрос. Если речь не об ООП — то единственный путь править исходники C. Только какой смысл это делать, если в C представлены все типы данных, работа с которыми быстро и однозначно преобразуется в команды процессора. Все остальные типы строятся на основе этих базовых. Нет смысла что то расширять, ели это не будет поддержано процессором
19 ноя 2015 в 20:06
Почему в заголовке С++ , а в тегах и описании также С присутствует?
20 ноя 2015 в 9:05
Если вы решили писать собственный компилируемый язык, то начните с досконального изучения ассемблеров основных видов процессоров. Т.е. архитектуры intel x86, mips, arm. Думаю этих трех будет достаточно, для общего понимания ‘а как оно бывает’
20 ноя 2015 в 10:40
1 ответ 1
Сортировка: Сброс на вариант по умолчанию
Определенные пользователем типы — это только классы и объединения.
Поэтому если вы хотите иметь запись вида
type a = 40;
то вам следует type определить, например, как класс
struct type < type( int x = 0 ) : x( x ) <>int x; >;
Это возможно только в C++.
Чем отличается определенный пользователем тип от стандартных типов, предоставляемых языком? Это их поведение!
Язык C не позволяет пользователям определять собственные типы, то есть вводить новые типы с отличным от уже определенных в языке поведением. То есть вы не можете вводить собственные спецификаторы типов и/или менять поведение уже предопределенных в языке типов.
Имейте в виду, что, например, использование спецификатора typedef не определяет новый тип. Он лишь вводит алиас для существующих типов.
Вы сами в своем вопросе упомянули ключевое слово. Новые ключевые слова в язык вводятся только решением комитетов по стандарту языка C. Например, в С 99 было введено новое ключевое слово _Bool , обозначающее новый целочисленный тип.
А, например, до принятия стандарта C в языке не было ключевого слова void , которое предложил Страуструп.
Вы можете написать свое предложение по изменению стандарта C и послать его в комитет по стандартизации.
Если вам просто нужно иметь 8-байтовое целое число, то вы можете использовать типы long long int , long long unsigned int или типы, определенные в заголовке .
#2 — Переменные и типы данных в Си
Переменные являются важной частью любого языка программирования. В ходе урока мы познакомимся с написанием переменных, а также изучим различные типы данных. Также мы научимся выводить и получать данные от пользователя.
Видеоурок
Переменные невероятно важны, так как позволяют хранить информацию и использовать её в дальнейшем. Вначале может быть не совсем понятно зачем вообще что-то записывать в переменную, если можно просто оперировать значениями без них. Понимание переменных придет немного позже, когда мы начнем создавать более сложные программы и нам потребуется хранить информацию в каком-либо месте.
Типы переменных в языке Си указываются перед названием переменной. От конкретного типа данных будет зависеть содержимое переменной. Если мы укажем что переменная с типом данных для целых чисел int , то в неё мы не сможем записать строку или число с точкой.
В ходе программы мы можем записывать новые значения в переменную, но тип данных должен оставаться неизменным:
float some_value = 2.95; some_value = 6.9; // Записали новое значение
Рассмотрим все типы данных для создания переменных.
Целые числа
- short — предназначен для хранения целых чисел. Диапазон чисел от -32 768 до 32 767;
- unsigned short — предназначен для хранения целых положительных чисел. Диапазон чисел от 0 до 65 535;
- int — предназначен для хранения целых чисел. Диапазон чисел от -2 147 483 648 до 2 147 483 647;
- unsigned int — предназначен для хранения целых положительных чисел. Диапазон чисел от 0 до 4 294 967 295;
- long — предназначен для хранения целых чисел. Диапазон чисел от –9 223 372 036 854 775 808 до 9 223 372 036 854 775 807;
- unsigned long — предназначен для хранения целых положительных чисел. Диапазон чисел от 0 до 18 446 744 073 709 551 615.
Разница между типами заключается только в диапазоне чисел, который можно записать в переменную. Также не используйте большие типы данных по типу long, если число маленькое, так как чем больше диапазон, тем больше памяти требуется компьютеру на конкретную переменную.
Числа с точкой
- float — для создания чисел с плавающей точкой. Диапазон чисел от от -3.4*10 38 до 3.4*10 38 ;
- double — для создания чисел с плавающей точкой. Диапазон чисел от от ±4.9*10 -324 до ±1.8*10 308 .
Прочие типы данных
- bool — логический тип данных. Предназначен для записи истинного (true) или ложного (false) значения;
- char — тип данных для работы с символами. Позволяет поместить в одинарных кавычках какой-либо символ.
Для создания переменной необходимо указать тип данных и назвать её. Также можно сразу же добавить к ней значение:
int a = 0; // Добавление значения сразу float c; // Создание переменной без значения short b, y = 342; // Создание нескольких переменных
Для вызова переменной вы можете обратиться к ней по имени.
Получение данных
Для получения данных от пользователя необходимо воспользоваться функцией scanf() :
scanf("%d", &some_value);
Таким образом введенные пользователем данные будут помещены в переменную с названием «some_value».
Весь код будет доступен после подписки на проект!
Задание к уроку
Необходимо оформить подписку на проект, чтобы получить доступ ко всем домашним заданиям
Большое задание по курсу
Вам необходимо оформить подписку на сайте, чтобы иметь доступ ко всем большим заданиям. В задание входит методика решения, а также готовый проект с ответом к заданию.
PS: подобные задания доступны при подписке от 1 месяца
Структурный тип данных в языке Си
Примером структуры может послужить любой объект, для которого описывается ряд его характеристик, имеющих значение в данной программе. Например, для книг это может быть название, автор, количество страниц; для окружности — координаты центра, диаметр, цвет. На языке программирования C объявление вышеупомянутых структурных типов данных может выглядеть так:
struct book char title[50]; char author[30]; int pages; >; struct circle int x, y; float dia; char color[10]; >;
В данном случае мы как бы создаем новый тип данных, но еще не объявляем переменных этих типов. Обратите внимание на точку с запятой в конце объявлений.
Чаще переменные структур объявляются так:
struct circle a, b, c; struct book mybook;
Здесь объявляются три структуры типа circle и одна структура типа book . Можно объявлять типы структур и их переменные по-иному, но мы для избежания путаницы рассматривать другие способы не будем.
Каждая переменная типа circle содержит четыре элемента (или поля) — x , y , dia , color . Можно сказать, что они представляют собой вложенные переменные. Причем эти переменные разных типов. Таким образом переменная-структура позволяет объединить под одним именем ряд разнородных данных. Обычно это нужно для удобства обработки данных. Если нельзя было бы создавать структуры, то пришлось бы создавать множество независимых переменных или ряд массивов, явной взаимосвязи между которыми не было бы. Структуры же позволяют объединять взаимосвязанные данные. Это конечно еще не объектно-ориентированное программирование, но уже взгляд в его сторону.
Объявив переменную структурного типа, мы можем получить доступ к каждому ее элементу для присваивания, изменения или получения значения:
a.x = 10; a.dia = 2.35; printf("%.2f ", a.dia);
Значение элементов структуры можно сразу определять при объявлении переменной, что похоже на инициализацию массивов:
struct book lang_c = {"Language C", "Ritchi", 99};
Значение переменной-структуры можно присвоить переменной того же типа:
struct book { char *title, *author; int pages; }; struct book old, new; old.title = "GNU/Linux"; old.author = "people"; old.pages = 20213; new = old; new.pages += 2000; printf("%d, %d\n", old.pages, new.pages);
В примере данные переменной old присваиваются new . В итоге вторая структура содержит копию данных первой. То, что можно выполнять присваивание по отдельным полям должно быть понятно.
Структуры и функции
Структуры-переменные можно передавать в функции в качестве аргументов. Также функция может возвращать структуру. Структуры передаются по значению, как обычные переменные, а не по ссылке, как массивы.
Рассмотрим программу, в которой одна функция возвращает структуру, а другая — принимает структуру в качестве параметра:
#include #include struct circle { int x, y; float dia; char color[10]; }; struct circle new_circle(); void cross (struct circle); int main () { struct circle a; a = new_circle(); cross(a); } struct circle new_circle() { struct circle new; printf("Координаты: "); scanf("%d%d", &new.x, &new.y); printf("Диаметр: "); scanf("%f", &new.dia); printf("Цвет: "); scanf("%s", new.color); return new; } void cross (struct circle c) { double hyp; hyp = sqrt((double) c.x * c.x + (double) c.y * c.y); printf("Расстояние: %.2lf\n", hyp); if (hyp c.dia / 2) puts("Пересекает"); else puts("Не пересекает"); }
Примечание. При компиляции программы в GNU/Linux команда выглядит так: gcc program.c -lm . Это связано с использованием библиотеки с математическими функциями.
- Объявляется структура circle как глобальный тип данных. Таким образом любая, а не только main , функция может создавать переменные этого типа.
- Функция new_circle() возвращает структуру, а функция cross() принимает структуру по значению. Следует отметить, что можно создавать функции, которые как принимают (возможно, несколько структур) так и возвращают структуру.
- В функции new_circle создается переменная new типа struct circle , поля которой заполняются пользователем. Функция возвращает значение переменной new в функцию main , где это значение присваивается переменной a , которая также принадлежит типу sctruct circle .
- Функция cross определяет, пересекает ли круг начало координат. В ее теле вычисляется расстояние от центра круга до начала координат. Это расстояние является гипотенузой прямоугольного треугольника, длина катетов которого равна значениям x и у . Далее, если гипотенуза меньше радиуса, то круг пересекает начало координат, т.е. точку (0, 0).
- В функции main при вызове cross() данные, содержащиеся в переменной a , копируются и присваиваются переменной c .
Указатели и структуры
В отличие от массивов, структуры передаются в функции по значению. Это не всегда рационально, т.к. структуры могут быть достаточно большого размера, и копирование таких участков памяти может замедлять работу программы. Поэтому часто структуры в функцию передают по ссылке, при этом можно использовать как указатель, так и операцию получения адреса.
// переменная-структура struct book new; // указатель на структуру struct book *pnew; // передаем адрес reader(&new); pnew = &new; // передаем указатель reader(pnew);
Тогда функция reader должна иметь примерно такое объявление:
void reader(struct book *pb);
Возникает вопрос, как при использовании указателей обращаться к элементам структур? Во первых надо получить саму структуру, т.е. если pnew указатель, то сама структура будет *pnew . Далее можно уже обращаться к полям через точку: *pnew.title . Однако это выражение не верно, т.к. приоритет операции «точка» (обращение к полю) выше операции «звездочка» (получить значение по адресу). Таким образом приведенная запись сначала пытается получить значение поля title у указателя pnew , но у pnew нет такого поля. Проблема решается с помощью скобок, которые изменяют порядок выполнения операций: (*pnew).title . В таком случае сначала извлекается значение по адресу pnew , это значение является структурой. Затем происходит обращение к полю структуры.
В языке программирования C записи типа (*pnew).title часто заменяют на такие: pnew->title , что позволяет синтаксис языка. Когда в программе вы видите стрелку (тире и скобка) всегда помните, то, что написано до стрелки, — это указатель на структуру, а не переменная-структура.
Пример кода с использованием указателей:
#include struct circle { int x, y; float dia; }; void inversion (struct circle *); int main () { struct circle cir, *pc = ○ pc->x = 10; pc->y = 7; pc->dia = 6; inversion(pc); printf("x = %d, y = %d\n", cir.x, cir.y); } void inversion(struct circle *p) { p->x = -p->x; p->y = -p->y; }
Массивы структур
Обычно создание в программе одной переменной структурного типа не имеет особого смысла. Чаще структурами пользуются, когда необходимо описать множество похожих объектов, имеющих разные значения признаков. Значения каждого объекта следует объединить вместе (в структуру) и тем самым отделить от значений других объектов. Например, описание ряда книг или множества людей. Таким образом мы можем организовать массив, где каждый элемент представляет собой отдельную структуру, а все элементы принадлежат одному и тому же структурному типу.
Напишем программу для учета персональных компьютеров в организации. Каждая структура будет описывать определенные модели и содержать поле, в котором будет указано количество таких объектов. Поэтому при объявлении структурного типа данных следует описать такие поля как тип компьютера, модель процессора, количество.
Программа будет предоставлять возможность получать информацию о всех моделях и изменять количество компьютеров указанной пользователем модели. В программе будут определены две функции (помимо main ): для вывода всей информации и для изменения количества компьютеров.
#include #define N 4 struct computer { char *type; char *proc; int qty; }; void viewer (struct computer *); void changer (struct computer *); int main () { struct computer comps[N]= { "Desktop", "Core", 20, "Notebook", "Core", 5, "Desktop", "AMD", 10, "Notebook", "AMD", 2, }; viewer(comps); changer(comps); viewer(comps); } void viewer (struct computer *comp) { for (int i = 0; i N; i++, comp++) printf("%2d) %-8s - %-15s: %3d\n", i+1, comp->type, comp->proc, comp->qty); } void changer (struct computer *comp) { int i, differ; printf("Введите номер модели: "); scanf("%d", &i); i--; printf("На сколько уменьшить или увеличить: "); scanf("%d", &differ); (comp+i)->qty += differ; }
- Массив структур инициализируется при его объявлении.
- Функции viewer() и changer() принимают указатели на структуру computer .
- В теле viewer указатель инкрементируется в заголовке цикла; таким образом указывая на следующий элемент массива, т.е. на следующую структуру.
- В выражении (comp+i)->qty скобки необходимы, т.к оператор -> имеет более высокий приоритет. Скобки позволяют сначала получить указатель на i-ый элемент массива, а потом обратиться к его полю.
- Декрементирование i в функции changer связано с тем, что индексация начинается с нуля, а номера элементов массива, которые пользователь видит на экране, с единицы.
- Для того, чтобы уменьшить количество компьютеров, при запросе надо ввести отрицательное число.
Пример результата работы программы:
1) Desktop - Core : 20 2) Notebook - Core : 5 3) Desktop - AMD : 10 4) Notebook - AMD : 2 Введите номер модели: 3 На сколько уменьшить или увеличить: -2 1) Desktop - Core : 20 2) Notebook - Core : 5 3) Desktop - AMD : 8 4) Notebook - AMD : 2
Перепишите приведенную выше программу и дополните ее функцией, которая позволяет добавлять в массив новый элемент-структуру.
Курс с решением задач:
pdf-версия