Язык Си. Первая программа. Основные типы данных. Проверка размера типа.

Знакомство с программированием на языке Си.

лекция № 1 - IDE. Первая программа. Основные типы данных. 



Все или почи все исходники по урокам расположены на github.com

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

Что(о чем) нужно почитать(изучить) после просмотра видео, а лучше перед видео:
1) Что такое язык программирования, в частности С и С++
2) IDE что это такое, какие они бывают
3) Что такое директива препроцессора в частности #include
4) Функция printf(), примеры использования для форматированного 
    вывода.
5) Что такое таблица ASCII
6) Что такое локаль
7) Что такое функция в языке программирования
8) Фундаментальные типы данных
9) Что такое бит, байт
10) Что такое система счисления в программировании
11) Что такое строка в С

При выборе IDE (Integrated Development Environment) предпочтение было отдано QT Creator + QT SDK. 
Причина проста - кроссплатформенность, бесплатность.
Даю советы по изучению программирования.

При создании проекта в QT Creator под Windows НЕ СОВЕТУЮ устанавливать в директорию в полном пути которой присутствуют не английские символы(вероятность того что будут проблемы очень высокая).
Поэтому создавайте проекты в специально созданной для этого директории, к примеру D:\dev\clng\educations
Следующий совет, в настройках редактора изменить символ табуляции на пробел(вместо табуляции 4 пробела или 2).

При выводе на экран с помощью функции printf(...); НЕ СОВЕТУЮ использовать не латиницу(нужно использовать только английские символы), ЕСЛИ не установлена локаль, если слово локаль неизвестно, значит пишем только английскими бувами.

 Хорошая справка по Си и С++ - http://en.cppreference.com/w/


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


Программа на языке Си состоит как правило из двух типов файлов, заголовочные файлы (файлы с расширением .h) и файлы реализации (файлы с расширением .c). Но расширения файлов не обязаны быть такими. Это сделано для некой стандартизации, чтобы программисты различали по файлам и их расширениям что в них находится. К примеру файлы с исходным кодом на PHP имеют расширение .php
Главное чтобы содержимое файлов соответствовало синтаксису и правилам языка.
Для языка Си и С++ есть такое понятие как единица трансляции.
Из единицы трансляции получается объектный файл, который содержит машинный код, то есть язык программирования понятный для человека переведенный в машинные коды(инструкции) понятные для компьютера. Из объектных файлов получается исполняемый файл, за то чтобы получился исполняемый файл отвечает компоновщик. Перед компиляцией программы, работает препроцессор, который готовит исходный код для компилятора. Препроцессор удаляет лишний код (к примеру комментарии), вместо вызовов макросов подставляет их содержимое, и т.д. то есть готовит все для "чистовой отделки" :-) . Обычно сколько файлов реализации столько и единиц трансляций(считай объектных файлов). К примеру мы можем иметь 2 файла реацизации - main.c и hello_world.c

main.c
int main()
{
  return 0;
}

hello_world.c
#include <stdio.h>
void hello()
{
  printf("Hello!\n");
}
В данном случае у нас 2 единицы трансляции, на выходе, после работы компилятора мы получим hello_world.o и main.o, а после работы компоновщика мы получим исполняемый файл. О том как посмотреть на исходный код после отработки препроцессора, читайте в другом блоге, в этом пока это пропустим. Чем меньше единиц трансляции тем быстрее собирается программа.
Совет: в заголовочных файлах не должно содержаться ничего лишнего(включенные на всякий случай другие заголовочные файлы, и т.д.)

Точкой входа для любой программы на языке Си и С++ является функция main() , то есть выполнение программы начинается именно с этой функции. Согласно стандарту Си, есть два прототипа этой функции
1) Без параметров           
    int main(void) { /* ... */ }
2) С двумя параметрами  
   int main(int argc, char *argv[]) { /* ... */ }
Во втором варианте имена параметров могут быть любыми, главное чтобы совпадал их тип.
Первый параметр содержит количество аргументов которые были переданы при вызове программы(когда мы вызываем консольные(терминальные) программы мы обычно передаем еще какие-то аргументы), например вызов программы в терминале - ipconfig с аргументом --help выдаст на экран терминала справку по использованию этой программы: user@user:~$ ipconfig --help
Второй параметр содержит массив этих агрументов.
При вызове программы без аргументов, первый параметр будет содержать значение 1, а второй, массив из одного элемента с полным путем к исполняемому файлу. Запустите программу с кодом ниже, и посмотрите результат.
#include <stdio.h>
int main(int argc, char *argv[])
{
  printf("%i - %s\n", argc, argv[argc-1]);
  return 0;
}
Типы данных.
_Bool Логический тип bool доступен начиная со стандарта C99. Для этого нужно подключить заголовочный файл  <stdbool.h>. bool является типом _Bool который является дополнением компилятора GCC. В стандарте говорится что размер объекта этого типа достаточно велик чтобы хранить значения 0 и 1, что соответствует макросам false и true, что по факту является целочисленным типом.
char Объекты с типом char хранят символы(ASCII), стандарт говорит что если в объекте этого типа хранится положительное значение, то это char объект. Если же отрицательные то это не char объекты, другими словами просто целочисленные значения со знаком минус.
Есть 5 целочисленных типов со знаком(то есть могут быть положительные и отрицательные значения) - signed charshort intintlong int и long long int.
Для каждого целочисленного типа со знаком есть беззнаковый тип(только положительные значения)  - unsigned charunsigned short intunsigned intunsigned long int и unsigned long long int.
Также есть 3 вещественных типа floatdouble и long double
Есть 3 типа для комплексной арифметики float _Complexdouble
_Complex
и  long double _Complex , но операции над мнимыми типа должна поддерживать платформа. 
Тип void означает неопределенный тип, или незаконченный. Допустимо объявлять только указатели этого типа.
Есть тип указатель. На каждый объект любого перечисленного выше типа можно объявить указатель, к примеру указатель на объект типа void
   void *pv;
Узнать размер типа можно вызвав стандартную функцию sizeof(T), где T это имя типа. Функция вернет размер в байтах.
К целочисленным и вещественным типа применимы все арифметические операции.

Правила арифметических преобразований типов:
Если тип одного из операндов является long double то другой операнд конвертируется в тип long double.
Иначе, если тип одного из операндов double то другой операнд конвертируется в тип double.
Иначе, если тип одного из операндов float то другой операнд конвертируется в тип float.
Иначе, если тип обеих операндов являются целочисленными то:
Если оба операнда имеют одинаковый тип, никаких конвертаций не требуется.
Иначе если тип обеих операндов является знаковым или беззнаковым, то операнд с меньшим размером типа конвертируется к типу второго операнда с большим типом. 
Иначе, если один операнд имеет беззнаковый тип который больше или равен по размеру типа второго операнда, то операнд со знаковым типом конвертируется в тип беззнаковый.
Иначе, если тип операнда со знаковым типом, все значения которого могут быть представлены типом второго операнда(беззнаковый тип), то операнд с беззнаковым типом конвертируется в знаковый тип.
Иначе, оба операнда конвертируются в беззнаковый тип.

Лимиты численных типов описаны в следующих заголовочных файлах
<limits.h>, <float.h>, <stdint.h>.

К примеру -
 #define SCHAR_MIN -127
означает что минимальное значение для signed char равно -127, а
#define SCHAR_MAX +127
означает что максимальное значение для signed char равно +127.

От компилятора к компилятору значения этих макросов могут меняться, но зачастую значения для всех популярных компиляторов совпадают.
Есть экзотические компиляторы у которых
sizeof(char) == sizeof(short) == 16 bits. Добровольно вы на такой компилятор и платформу не попадете :-) а если попадете, вы уже будете знать о том сколько бит занимает каждый отдельный тип данных.
К примеру если вам нужно чтобы ваш код собирался ТОЛЬКО на платформах у которых размер char равен 1 байт, short 2 байта и т.д. (вы можете сами задать какой размер), используйте следующий трюк:
#include <limits.h>
char ch_s_ch[ CHAR_BIT == 8 && sizeof(char)  == 1 ? 1 : -1 ];
char ch_s_sh[ CHAR_BIT == 8 && sizeof(short) == 2 ? 1 : -1 ];
char ch_s_i [ CHAR_BIT == 8 && sizeof(int)   == 4 ? 1 : -1 ];
В этом коде (лучше вставить перед функцией main) идет проверка на значение макроса CHAR_BIT который указывает сколько бит в байте, обычно это 8, но не всегда, к примеру для различных микроконтроллеров CHAR_BIT может быть равен 16 и sizeof(int) равен 1(микроконтроллеры TI 2000, TI MSP430, ...), возвращаемся к нашим проверкам, если будет несовпадение по размерам, будет попытка создать массив с длинной -1, что приведет к ошибке сборки.
Касательно макросов описывающих лимиты, чтобы не читать стандарт, можно почитать тут, про поддержку типов тут.

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

Даны не все ограничения(все ограничения описаны в стандарте ISO/IEC 9899:201x), а несколько примеров для понимания.
  • максимум 127 уровней вложенных блоков. ({{{ char a; }}} -  3 уровня)
  • максимум 127 аргументов переданных в функцию
  • максимум 4095 символов в строке кода 
  • и т. д.
На сегодня все! В будущем возможны дополнения!
Спасибо за внимание!
 

Комментарии