Глава – 5
Введение в программирование на C
Введение
«C» — один из самых популярных компьютерных языков в современном компьютерном мире. Язык программирования C был спроектирован и разработан Брайаном Керниганом и Деннисом Ритчи в Bell Research Labs в 1972 году.
'C' - это язык, специально созданный для того, чтобы позволить программисту получить доступ почти ко всем внутренним устройствам машины - регистрам, слотам ввода-вывода и абсолютным адресам. В то же время "C" позволяет обрабатывать столько данных и модульность запрограммированного текста, сколько необходимо для организованного и своевременного создания очень сложных мультипрограммных проектов.
Хотя изначально этот язык предназначался для работы в UNIX, существует большой интерес к его работе в операционной системе MS-DOS на IBM PC и совместимых устройствах. Это отличный язык для этой среды из-за простоты выражения, компактности кода и широкого диапазона применимости.
Кроме того, из-за простоты и легкости написания компилятора C это обычно первый язык высокого уровня, доступный на любом новом компьютере, включая микрокомпьютеры, миникомпьютеры и мейнфреймы.
Зачем использовать C в программировании восстановления данных
В современном мире компьютерного программирования доступно множество языков высокого уровня. Эти языки хороши многими функциями, подходящими для большинства задач программирования. Тем не менее, есть несколько причин, по которым C является предпочтительным выбором для программистов, которые хотят программировать для восстановления данных, системного программирования, программирования устройств или аппаратного программирования:
- C — популярный язык, предпочитаемый профессиональными программистами. В результате доступно большое количество компиляторов C и полезных аксессуаров.
- C — переносимый язык. Программа на C, написанная для одной компьютерной системы, может быть скомпилирована и запущена в другой системе практически без изменений. Переносимость улучшена стандартом ANSI для C, набором правил для компиляторов C.
- C позволяет широко использовать модули в программировании. Код C можно писать в виде подпрограмм, называемых функциями. Эти функции можно повторно использовать в других приложениях или программах. Вам не нужно прилагать дополнительные усилия при программировании нового приложения, чтобы создать тот же модуль, который вы разработали ранее в программировании другого приложения.
Вы можете использовать эту функцию в новой программе без каких-либо изменений или с небольшими изменениями. В случае программирования восстановления данных вы найдете это качество очень полезным, когда вам нужно запустить одни и те же функции несколько раз в разных приложениях разных программ.
- C – мощный и гибкий язык. Именно поэтому C используется для таких разнообразных проектов, как операционные системы, текстовые процессоры, графика, электронные таблицы и даже компиляторы для других языков.
- C — это язык нескольких слов, содержащий всего несколько терминов, называемых ключевыми словами, которые служат основой, на которой строится функциональность языка. Эти ключевые слова, также называемые зарезервированными словами, делают его более мощным и расширяют область программирования, а также дают программисту возможность выполнять любой тип программирования на C.
Позвольте мне предположить, что вы ничего не знаете о C
Я предполагаю, что вы ничего не знаете о программировании на C, а также не имеете представления о программировании. Я начну с самых основных концепций C и перейду к высокому уровню программирования на C, включая обычно пугающие концепции указателей, структур и динамического распределения.
Чтобы полностью понять эти концепции, вам потребуется немало времени и усилий, потому что их не так легко понять, но они являются очень мощными инструментами.
Программирование на C является огромным преимуществом в тех областях, где вам может понадобиться использовать язык ассемблера, но вы предпочитаете, чтобы программа была простой в написании и легкой в обслуживании. В таких случаях экономия времени при кодировании C может быть огромной.
Несмотря на то, что язык C хорошо зарекомендовал себя при переносе программ из одной реализации в другую, существуют различия в компиляторах, которые вы обнаружите каждый раз, когда пытаетесь использовать другой компилятор.
Большинство различий становится очевидным при использовании нестандартных расширений, таких как вызовы DOS BIOS при использовании MS-DOS, но даже эти различия можно свести к минимуму путем тщательного выбора программных конструкций.
Когда стало очевидно, что язык программирования C становится очень популярным языком, доступным на самых разных компьютерах, группа заинтересованных лиц собралась, чтобы предложить стандартный набор правил для использования языка программирования C.
Группа представляла все секторы индустрии программного обеспечения, и после многих встреч и множества предварительных проектов они, наконец, написали приемлемый стандарт для языка C. Он был принят Американским национальным институтом стандартов (ANSI), и Международной организацией по стандартизации (ISO).
Он не навязывается какой-либо группе или пользователю, но, поскольку он так широко принят, для любого автора компилятора было бы экономическим самоубийством отказаться соответствовать стандарту.
Программы, написанные в этой книге, в первую очередь предназначены для использования на IBM-PC или совместимом компьютере, но могут использоваться с любым компилятором стандарта ANSI, поскольку он очень близко соответствует стандарту ANSI.
Начнем
Прежде чем вы сможете что-то делать на любом языке и начать программировать, вы должны знать, как назвать идентификатор. Идентификатор используется для любой переменной, функции, определения данных и т. д. В языке программирования C идентификатор представляет собой комбинацию буквенно-цифровых символов, первая из которых является буквой алфавита или символом подчеркивания, а остальные — любой буквой алфавита. алфавит, любую цифру или подчеркивание.
При именовании идентификаторов необходимо помнить о двух правилах.
- Регистр букв имеет значение. C — язык, чувствительный к регистру. Это означает, что Recovery отличается от восстановления, а RECOVERY отличается от обоих, упомянутых ранее.
- В соответствии со стандартом ANSI-C можно использовать не менее 31 значащего символа, и компилятор, соответствующий стандарту ANSI-C, будет считать их значащими. Если используется более 31, все символы после 31-го могут игнорироваться любым данным компилятором.
Ключевые слова
Есть 32 слова, определенных как ключевые слова в C. Они имеют предопределенное использование и не могут использоваться для каких-либо других целей в программе C. Они используются компилятором в качестве помощи при компиляции программы. Они всегда пишутся строчными буквами. Полный список следующий:
auto |
break |
case |
char |
const |
continue |
default |
do |
double |
else |
enum |
extern |
float |
for |
goto |
if |
int |
long |
register |
return |
short |
signed |
sizeof |
static |
struct |
switch |
typedef |
union |
unsigned |
void |
volatile |
while |
Здесь мы видим магию C. Замечательный набор всего из 32 ключевых слов дает широкое применение в различных приложениях. Любая компьютерная программа имеет два объекта для рассмотрения: данные и программу. Они сильно зависят друг от друга, и тщательное планирование обоих приводит к хорошо спланированной и хорошо написанной программе.
Давайте начнем с простой программы на C:
/* Первая программа для изучения C */
#include <stdio.h>
void main()
{
printf("This is a C program\n"); // printing a message
}
Хотя программа очень проста, стоит отметить несколько моментов. Разберем приведенную выше программу. Все, что находится внутри /* и */, считается комментарием и будет игнорироваться компилятором. Вы не должны включать комментарии в другие комментарии, поэтому что-то вроде этого не допускается:
/* это /* комментарий */ внутри комментария, что неверно */
Существует также способ документирования, работающий внутри строки. Используя //, мы можем добавить небольшую документацию в эту строку.
Каждая программа на C содержит функцию main. Это начальная точка программы. Каждая функция должна возвращать значение. В этой программе функция main не возвращает никакого значения, поэтому мы написали void main. Мы могли бы также написать эту программу как:
/* Первая программа для изучения C */
#include <stdio.h>
main()
{
printf("This is a C program\n"); // printing a message
return 0;
}
Обе программы одинаковы и выполняют одну и ту же задачу. В результате обе программы выведут на экран следующий вывод:
Это программа на C
#include<stdio.h> позволяет программе взаимодействовать с экраном, клавиатурой и файловой системой вашего компьютера. Вы найдете его в начале почти каждой программы на C.
main() объявляет начало функции, а две фигурные скобки показывают начало и конец функции. Фигурные скобки в C используются для группировки операторов, как в функции, так и в теле цикла. Такая группировка называется составным оператором или блоком.
printf("Это программа на C\n"); печатает слова на экране. Текст для печати заключается в двойные кавычки. Символ \n в конце текста сообщает программе, что необходимо напечатать новую строку как часть вывода. Функция printf() используется для отображения вывода на мониторе.
Большинство программ на C написаны строчными буквами. Обычно вы найдете заглавные буквы, используемые в определениях препроцессора, которые будут обсуждаться позже, или внутри кавычек как части символьных строк.
Компиляция программы
Пусть наша программа называется CPROG.C. Чтобы ввести и скомпилировать программу C, выполните следующие действия:
- Сделайте активным каталог ваших программ на C и запустите редактор. Для этого можно использовать любой текстовый редактор, но большинство компиляторов C, таких как Turbo C++ от Borland, имеют интегрированную среду разработки (IDE), которая позволяет вам вводить, компилировать и связывать ваши программы в одном удобном месте.
- Напишите и сохраните исходный код. Вы должны назвать файл CPROG.C.
- Скомпилируйте и слинкуйте CPROG.C. Выполните соответствующую команду, указанную в руководствах вашего компилятора. Вы должны получить сообщение об отсутствии ошибок или предупреждений.
- Проверьте сообщения компилятора. Если вы не получаете ошибок или предупреждений, все должно быть в порядке. Если при наборе программы есть какая-либо ошибка, компилятор поймает ее и выведет сообщение об ошибке. Исправьте ошибку, указанную в сообщении об ошибке.
- Теперь ваша первая программа на C должна быть скомпилирована и готова к запуску. Если вы отобразите список каталогов всех файлов с именем CPROG, вы получите четыре файла с разными расширениями, описанными ниже:
- CPROG.C, файл исходного кода
- CPROG.BAK, файл резервной копии исходного файла, созданного вами с помощью редактора
- CPROG.OBJ, содержит объектный код для CPROG.C
- CPROG.EXE, исполняемая программа, созданная при компиляции и компоновке CPROG.C
- Чтобы выполнить или запустить CPROG.EXE, просто введите cprog. На экране отображается сообщение Это программа на языке C.
Теперь давайте рассмотрим следующую программу:
/* Первая программа для изучения C */ // 1
// 2
#include <stdio.h> // 3
// 4
main() // 5
{
// 6
printf("This is a C program\n"); // 7
// 8
return 0; // 9
} // 10
При компиляции этой программы компилятор отображает сообщение, подобное следующему:
cprog.c(8): Ошибка: `;' ожидается
давайте разобьем это сообщение об ошибке на части. cprog.c — это имя файла, в котором была обнаружена ошибка. (8) — номер строки, в которой обнаружена ошибка. Ошибка: `;' ожидается описание ошибки.
Это довольно информативное сообщение, говорящее о том, что в строке 8 CPROG.C компилятор ожидал найти точку с запятой, но не нашел. Однако вы знаете, что на самом деле точка с запятой была опущена в строке 7, поэтому есть несоответствие.
Почему компилятор сообщает об ошибке в строке 8, когда на самом деле точка с запятой была опущена в строке 7. Ответ заключается в том, что C не заботится о таких вещах, как разрывы между строками. Точку с запятой, стоящую после оператора printf(), можно было бы поместить на следующей строке, хотя на практике это было бы плохим программированием.
Только после обнаружения следующей команды (return) в строке 8 компилятор уверен, что точка с запятой отсутствует. Поэтому компилятор сообщает, что ошибка в строке 8.
Возможны различные типы ошибок. Давайте обсудим связывание сообщений об ошибках. Ошибки компоновщика относительно редки и обычно возникают из-за неправильного написания имени библиотечной функции C. В этом случае вы получите сообщение Ошибка: неопределенные символы: сообщение об ошибке, за которым следует имя с ошибкой. Как только вы исправите орфографию, проблема должна исчезнуть.
Печать чисел
Давайте посмотрим на следующий пример:
// Как напечатать числа //
#include<stdio.h>
void main()
{
int num = 10;
printf(“ The Number Is %d”, num);
}
Вывод программы будет отображаться на экране следующим образом:
Число равно 10
Знак % используется для обозначения вывода многих различных типов переменных. Символ, следующий за знаком %, — это d, который сигнализирует процедуре вывода, чтобы получить десятичное значение и вывести его.
Использование переменных
В C переменная должна быть объявлена до того, как ее можно будет использовать. Переменные могут быть объявлены в начале любого блока кода, но большинство из них находятся в начале каждой функции. Большинство локальных переменных создаются при вызове функции и уничтожаются по возвращении из этой функции.
Чтобы использовать переменные в ваших программах на C, вы должны знать следующие правила при присвоении имени переменным в C:
- Имя может содержать буквы, цифры и символ подчеркивания (_).
- Первым символом имени должна быть буква. Подчеркивание также является разрешенным первым символом, но его использование не рекомендуется.
- C чувствителен к регистру, поэтому имя переменной num отличается от Num.
- Ключевые слова C нельзя использовать в качестве имен переменных. Ключевое слово — это слово, которое является частью языка C.
Следующий список содержит несколько примеров допустимых и недопустимых имен переменных C:
Variable Name |
Legal or Not |
Num |
Legal |
Ttpt2_t2p |
Legal |
Tt pt |
Illegal: Space is not allowed |
_1990_tax |
Legal but not advised |
Jack_phone# |
Illegal: Contains the illegal character # |
Case |
Illegal: Is a C keyword |
1book |
Illegal: First character is a digit |
Первое, что бросается в глаза, — это первая строка тела main():
целое число = 10;
Эта строка определяет переменную с именем 'num' типа int и инициализирует ее значением 10. Это также можно было бы записать так:
int num; /* define uninitialized variable 'num' */
/* and after all variable definitions: */
num = 10; /* assigns value 10 to variable 'num' */
Переменные могут быть определены в начале блока (между фигурными скобками {и}), обычно в начале тела функции, но также могут быть в начале блока другого типа.
Переменные, определенные в начале блока, по умолчанию имеют статус «авто». Это означает, что они существуют только во время выполнения блока. Когда начнется выполнение функции, переменные будут созданы, но их содержимое будет неопределенным. Когда функция вернется, переменные будут уничтожены. Определение также можно было бы записать так:
авто целочисленное число = 10;
Поскольку определение с ключевым словом auto или без него полностью эквивалентно, ключевое слово auto, очевидно, довольно избыточно.
Однако иногда это не то, что вам нужно. Предположим, вы хотите, чтобы функция подсчитывала, сколько раз она вызывается. Если бы переменная уничтожалась каждый раз, когда функция возвращается, это было бы невозможно.
Следовательно, переменной можно присвоить так называемую статическую продолжительность, что означает, что она останется неизменной в течение всего выполнения программы. Например:
статическое целое число = 10;
Это инициализирует переменную num значением 10 в начале выполнения программы. С этого момента значение останется нетронутым; переменная не будет повторно инициализирована, если функция вызывается несколько раз.
Иногда недостаточно, чтобы переменная была доступна только из одной функции, или может быть неудобно передавать значение через параметр всем другим функциям, которым это необходимо.
Но если вам нужен доступ к переменной из всех функций во всем исходном файле, это также можно сделать с помощью ключевого слова static, но поместив определение вне всех функций. Например:
#include <stdio.h>
static int num = 10; /* will be accessible from entire source file */
int main(void)
{
printf("The Number Is: %d\n", num);
return 0;
}
А также бывают случаи, когда переменная должна быть доступна из всей программы, которая может состоять из нескольких исходных файлов. Это называется глобальной переменной, и ее следует избегать, когда она не требуется.
Это также делается путем размещения определения вне всех функций, но без использования ключевого слова static:
#include <stdio.h>
int num = 10; /* will be accessible from entire program! */
int main(void)
{
printf("The Number Is: %d\n", num);
return 0;
}
Существует также ключевое слово extern, которое используется для доступа к глобальным переменным в других модулях. Есть также несколько квалификаторов, которые вы можете добавить к определениям переменных. Наиболее важным из них является const. Переменная, определенная как const, не может быть изменена.
Есть еще два модификатора, которые используются реже. Модификатор volatile и register. Модификатор volatile требует, чтобы компилятор фактически обращался к переменной каждый раз, когда она считывается. Он может не оптимизировать переменную, поместив ее в регистр или около того. Это в основном используется для многопоточности и обработки прерываний и т. д.
Модификатор регистра запрашивает компилятор оптимизировать переменную в регистр. Это возможно только с автоматическими переменными, и во многих случаях компилятор может лучше выбирать переменные для оптимизации в регистры, поэтому это ключевое слово устарело. Единственным прямым последствием создания регистра переменной является то, что его адрес не может быть взят.
Таблица переменных, приведенная на следующей странице, описывает класс хранения из пяти типов классов хранения.
В таблице мы видим, что ключевое слово extern размещено в двух строках. Ключевое слово extern используется в функциях для объявления статической внешней переменной, которая определена в другом месте.
Типы числовых переменных
C предоставляет несколько различных типов числовых переменных, потому что разные числовые значения имеют разные требования к памяти. Эти числовые типы отличаются простотой выполнения над ними определенных математических операций.
Для хранения небольших целых чисел требуется меньше памяти, и ваш компьютер может очень быстро выполнять математические операции с такими числами. Большие целые числа и значения с плавающей запятой требуют больше места для хранения и больше времени для математических операций. Используя соответствующие типы переменных, вы гарантируете, что ваша программа работает максимально эффективно.
Числовые переменные C делятся на следующие две основные категории:
- Целочисленные переменные
- Переменные с плавающей запятой
В каждой из этих категорий есть два или более определенных типа переменных. В приведенной ниже таблице показан объем памяти в байтах, необходимый для хранения одной переменной каждого типа.
Тип char может быть эквивалентен как signed char, так и unsigned char, но это всегда отдельный тип от любого из них.
В C нет разницы между сохранением символов или соответствующих им числовых значений в переменной, поэтому также нет необходимости в функции для преобразования между символом и его числовым значением или наоборот. Для других целочисленных типов, если вы опустите подписанный или беззнаковый, значение по умолчанию будет подписанным, поэтому, например. int и signed int эквивалентны.
Тип int должен быть больше или равен типу short и меньше или равен типу long. Если вам просто нужно сохранить некоторые значения, которые не очень велики, часто рекомендуется использовать тип int; обычно это размер, с которым процессор может справиться проще всего и, следовательно, быстрее всего.
Для некоторых компиляторов значения double и long double эквивалентны. Это в сочетании с тем фактом, что большинство стандартных математических функций работают с типом double, является хорошей причиной всегда использовать тип double, если вам нужно работать с дробными числами.
Следующая таблица лучше описывает типы переменных:
Обычно используются специальные типы:
Variable Type |
Description |
size_t |
unsigned type used for storing the sizes of objects in bytes |
time_t |
used to store results of the time() function |
clock_t |
used to store results of the clock() function |
FILE |
used for accessing a stream (usually a file or device) |
ptrdiff_t |
signed type of the difference between 2 pointers |
div_t |
used to store results of the div() function |
ldiv_t |
used to store results of ldiv() function |
fpos_t |
used to hold file position information |
va_list |
used in variable argument handling |
wchar_t |
wide character type (used for extended character sets) |
sig_atomic_t |
used in signal handlers |
Jmp_buf |
used for non-local jumps |
Чтобы лучше понять эти переменные, давайте возьмем пример:
/* Программа для указания диапазона и размера переменной C в байтах */
#include <stdio.h>
int main()
{
int a; /* simple integer type */
long int b; /* long integer type */
short int c; /* short integer type */
unsigned int d; /* unsigned integer type */
char e; /* character type */
float f; /* floating point type */
double g; /* double precision floating point */
a = 1023;
b = 2222;
c = 123;
d = 1234;
e = 'X';
f = 3.14159;
g = 3.1415926535898;
printf( "\nA char is %d bytes", sizeof( char ));
printf( "\nAn int is %d bytes", sizeof( int ));
printf( "\nA short is %d bytes", sizeof( short ));
printf( "\nA long is %d bytes", sizeof( long ));
printf( "\nAn unsigned char is %d bytes",
sizeof( unsigned char ));
printf( "\nAn unsigned int is %d bytes",
sizeof( unsigned int ));
printf( "\nAn unsigned short is %d bytes",
sizeof( unsigned short ));
printf( "\nAn unsigned long is %d bytes",
sizeof( unsigned long ));
printf( "\nA float is %d bytes", sizeof( float ));
printf( "\nA double is %d bytes\n", sizeof( double ));
printf("a = %d\n", a); /* decimal output */
printf("a = %o\n", a); /* octal output */
printf("a = %x\n", a); /* hexadecimal output */
printf("b = %ld\n", b); /* decimal long output */
printf("c = %d\n", c); /* decimal short output */
printf("d = %u\n", d); /* unsigned output */
printf("e = %c\n", e); /* character output */
printf("f = %f\n", f); /* floating output */
printf("g = %f\n", g); /* double float output */
printf("\n");
printf("a = %d\n", a); /* simple int output */
printf("a = %7d\n", a); /* use a field width of 7 */
printf("a = %-7d\n", a); /* left justify in
field of 7 */
c = 5;
d = 8;
printf("a = %*d\n", c, a); /* use a field width of 5*/
printf("a = %*d\n", d, a); /* use a field width of 8 */
printf("\n");
printf("f = %f\n", f); /* simple float output */
printf("f = %12f\n", f); /* use field width of 12 */
printf("f = %12.3f\n", f); /* use 3 decimal places */
printf("f = %12.5f\n", f); /* use 5 decimal places */
printf("f = %-12.5f\n", f); /* left justify in field */
return 0;
}
Результат программы после выполнения будет отображаться как:
A char is 1 bytes
An int is 2 bytes
A short is 2 bytes
A long is 4 bytes
An unsigned char is 1 bytes
An unsigned int is 2 bytes
An unsigned short is 2 bytes
An unsigned long is 4 bytes
A float is 4 bytes
A double is 8 bytes
a = 1023
a = 1777
a = 3ff
b = 2222
c = 123
d = 1234
e = X
f = 3.141590
g = 3.141593
a = 1023
a = 1023
a = 1023
a = 1023
a = 1023
f = 3.141590
f = 3.141590
f = 3.142
f = 3.14159
f = 3.14159 |
Прежде чем использовать переменную в программе на C, ее необходимо объявить. Объявление переменной сообщает компилятору имя и тип переменной и дополнительно инициализирует переменную определенным значением.
Если ваша программа попытается использовать необъявленную переменную, компилятор выдаст сообщение об ошибке. Объявление переменной имеет следующую форму:
имя_типа_имя_переменной;
typename указывает тип переменной и должен быть одним из ключевых слов. varname — это имя переменной. Вы можете объявить несколько переменных одного типа в одной строке, разделив имена переменных запятыми:
int count, number, start; /* three integer variables */
float percent, total; /* two float variables */
Ключевое слово typedef
Ключевое слово typedef используется для создания нового имени для существующего типа данных. По сути, typedef создает синоним. Например, оператор
typedef целое число;
здесь мы видим, что typedef создает целое число как синоним для целого числа. Затем вы можете использовать целое число для определения переменных типа int, как в этом примере:
целочисленное количество;
Поэтому typedef не создает новый тип данных, а только позволяет вам использовать другое имя для предопределенного типа данных.
Инициализация числовых переменных
При объявлении любой переменной компилятор получает указание выделить место для хранения этой переменной. Однако значение, хранящееся в этом пространстве, значение переменной, не определено. Это может быть ноль, а может быть какой-то случайный «мусор». стоимость. Прежде чем использовать переменную, вы всегда должны инициализировать ее известным значением. Возьмем этот пример:
int count; /* Set aside storage space for count */
count = 0; /* Store 0 in count */
В этом операторе используется знак равенства (=), который является оператором присваивания C. Вы также можете инициализировать переменную при ее объявлении. Для этого после имени переменной в операторе объявления следует поставить знак равенства и желаемое начальное значение:
int count = 0;
double rate = 0.01, complexity = 28.5;
Будьте осторожны, чтобы не инициализировать переменную со значением, выходящим за пределы допустимого диапазона. Вот два примера инициализации вне допустимого диапазона:
int amount = 100000;
unsigned int length = -2500;
Компилятор C не отлавливает такие ошибки. Ваша программа может компилироваться и компоноваться, но вы можете получить неожиданные результаты при запуске программы.
Давайте возьмем следующий пример для расчета общего количества секторов на диске:
// Модель программы для расчета секторов на диске //
#include<stdio.h>
#define SECTOR_PER_SIDE 63
#define SIDE_PER_CYLINDER 254
void main()
{
int cylinder=0;
clrscr();
printf("Enter The No. of Cylinders in the Disk \n\n\t");
scanf("%d",&cylinder); // Get the value from the user //
printf("\n\n\t Total Number of Sectors in the disk = %ld", (long)SECTOR_PER_SIDE*SIDE_PER_CYLINDER* cylinder);
getch();
}
Вывод программы следующий:
Enter The No. of Cylinders in the Disk
1024
Total Number of Sectors in the disk = 16386048
В этом примере мы видим три новых вещи, которые нужно изучить. #define используется для использования в программе символических констант или, в некоторых случаях, для экономии времени за счет определения длинных слов маленькими символами.
Здесь мы определили количество секторов на сторону, равное 63, как SECTOR_PER_SIDE, чтобы облегчить понимание программы. Тот же случай верен для #define SIDE_PER_CYLINDER 254. Для получения ввода от пользователя используется функция scanf().
Здесь мы получаем количество цилиндров от пользователя. * используется для умножения двух или более значений, как показано в примере.
Функция
getch() в основном получает ввод одного символа с клавиатуры. Набрав getch(); здесь мы останавливаем экран до тех пор, пока не будет нажата какая-либо клавиша на клавиатуре.
Операторы
Оператор — это символ, который предписывает C выполнить некоторую операцию или действие над одним или несколькими операндами. Операнд — это то, над чем действует оператор. В C все операнды являются выражениями. Операторы языка C относятся к следующим четырем категориям:
- Оператор присваивания
- Математические операторы
- Операторы отношения
- Логические операторы
Оператор присваивания
Оператор присваивания — это знак равенства (=). Использование знака равенства в программировании отличается от его использования в обычных математических алгебраических отношениях. Если вы пишете
х = у;
В программе на C это не означает, что "x равно y". Вместо этого это означает «присвоить значение y значению x». В операторе присваивания C правая часть может быть любым выражением, а левая часть должна быть именем переменной. Таким образом, форма выглядит следующим образом:
переменная = выражение;
Во время выполнения вычисляется выражение, и полученное значение присваивается переменной.
Математические операторы
Математические операторы C выполняют математические операции, такие как сложение и вычитание. C имеет два унарных математических оператора и пять бинарных математических операторов. Унарные математические операторы названы так потому, что они принимают один операнд. C имеет два унарных математических оператора.
Операторы инкремента и декремента можно использовать только с переменными, но не с константами. Выполняемая операция заключается в прибавлении единицы к операнду или вычитании единицы из него. Другими словами, операторы ++x; и --у; являются эквивалентами этих утверждений:
x = x + 1;
y = y - 1;
бинарные математические операторы принимают два операнда. Первые четыре бинарных оператора, которые включают в себя общие математические операции, встречающиеся в калькуляторе (+, -, *, /), вам знакомы. Пятый оператор Modulus возвращает остаток от деления первого операнда на второй операнд. Например, 11 по модулю 4 равно 3 (11 делится на 4, два раза и 3 остается).
Реляционные операторы
Операторы отношения C используются для сравнения выражений. Выражение, содержащее оператор отношения, оценивается как истинное (1) или ложное (0). C имеет шесть операторов отношения.
Логические операторы
Логические операторы C позволяют объединять два или более реляционных выражения в одно выражение, которое дает либо истинное, либо ложное значение. Логические операторы оцениваются как истинные или ложные, в зависимости от истинного или ложного значения их операндов.
Если x — целочисленная переменная, выражения, использующие логические операторы, могут быть записаны следующим образом:
(x > 1) && (x < 5)
(x >= 2) && (x <= 4)
Operator |
Symbol |
Description |
Example |
Assignment operators |
equal |
= |
assign the value of y to x |
x = y |
Mathematical operators |
Increment |
++ |
Increments the operand by one |
++x, x++ |
Decrement |
-- |
Decrements the operand by one |
--x, x-- |
Addition |
+ |
Adds two operands |
x + y |
Subtraction |
- |
Subtracts the second operand from the first |
x - y |
Multiplication |
* |
Multiplies two operands |
x * y |
Division |
/ |
Divides the first operand by the second operand |
x / y |
Modulus |
% |
Gives the remainder when the first operand is divided by the second operand |
x % y |
Relational operators |
Equal |
= = |
Equality |
x = = y |
Greater than |
> |
Greater than |
x > y |
Less than |
< |
Less than |
x < y |
Greater than or equal to |
>= |
Greater than or equal to |
x >= y |
Less than or equal to |
<= |
Less than or equal to |
x <= y |
Not equal |
!= |
Not equal to |
x != y |
Logical operators |
AND |
&& |
True (1) only if both exp1 and exp2 are true; false (0) otherwise |
exp1 && exp2 |
OR |
|| |
True (1) if either exp1 or exp2 is true; false (0) only if both are false |
exp1 || exp2 |
NOT |
! |
False (0) if exp1 is true; true (1) if exp1 is false |
!exp1 |
Что нужно помнить о логических выражениях
x * = y |
is same as |
x = x * y |
y - = z + 1 |
is same as |
y = y - z + 1 |
a / = b |
is same as |
a = a / b |
x + = y / 8 |
is same as |
x = x + y / 8 |
y % = 3 |
is same as |
y = y % 3 |
Оператор запятой
Запятая часто используется в C как простой знак препинания для разделения объявлений переменных, аргументов функций и т. д. В определенных ситуациях запятая действует как оператор.
Выражение можно сформировать, разделив два подвыражения запятой. Результат выглядит следующим образом:
- Оцениваются оба выражения, причем левое выражение оценивается первым.
- Все выражение оценивается как значение правильного выражения.
Например, следующий оператор присваивает значение b значению x, затем увеличивает a, а затем увеличивает b:
x = (a++, b++);
Приоритет оператора C (краткое описание операторов C)
Rank and Associativity |
Operators |
1(left to right) |
() [] -> . |
2(right to left) |
! ~ ++ -- * (indirection) & (address-of) (type)
sizeof + (unary) - (unary) |
3(left to right) |
* (multiplication) / % |
4(left to right) |
+ - |
5(left to right) |
<< >> |
6(left to right) |
< <= > >= |
7(left to right) |
= = != |
8(left to right) |
& (bitwise AND) |
9(left to right) |
^ |
10(left to right) |
| |
11(left to right) |
&& |
12(left to right) |
|| |
13(right to left) |
?: |
14(right to left) |
= += -= *= /= %= &= ^= |= <<= >>= |
15(left to right) |
, |
() is the function operator; [] is the array operator. |
|
Возьмем пример использования операторов:
/* Использование операторов */
int main()
{
int x = 0, y = 2, z = 1025;
float a = 0.0, b = 3.14159, c = -37.234;
/* incrementing */
x = x + 1; /* This increments x */
x++; /* This increments x */
++x; /* This increments x */
z = y++; /* z = 2, y = 3 */
z = ++y; /* z = 4, y = 4 */
/* decrementing */
y = y - 1; /* This decrements y */
y--; /* This decrements y */
--y; /* This decrements y */
y = 3;
z = y--; /* z = 3, y = 2 */
z = --y; /* z = 1, y = 1 */
/* arithmetic op */
a = a + 12; /* This adds 12 to a */
a += 12; /* This adds 12 more to a */
a *= 3.2; /* This multiplies a by 3.2 */
a -= b; /* This subtracts b from a */
a /= 10.0; /* This divides a by 10.0 */
/* conditional expression */
a = (b >= 3.0 ? 2.0 : 10.5 ); /* This expression */
if (b >= 3.0) /* And this expression */
a = 2.0; /* are identical, both */
else /* will cause the same */
a = 10.5; /* result. */
c = (a > b ? a : b); /* c will have the max of a or b */
c = (a > b ? b : a); /* c will have the min of a or b */
printf("x=%d, y=%d, z= %d\n", x, y, z);
printf("a=%f, b=%f, c= %f", a, b, c);
return 0;
}
и результат этой программы будет отображаться на экране как:
x=3, y=1, z=1
a=2.000000, b=3.141590, c=2.000000
Еще немного о printf() и Scanf()
Рассмотрим следующие два оператора printf.
printf(“\t %d\n”, num);
printf(“%5.2f”, fract);
в первом операторе printf \t запрашивает смещение табуляции на экране, аргумент %d сообщает компилятору, что значение num должно быть напечатано как десятичное целое число. \n заставляет новый вывод начинаться с новой строки.
Во втором операторе printf %5.2f сообщает компилятору, что вывод должен быть в формате с плавающей запятой, с пятью знаками во всех числах и двумя знаками справа от десятичной точки. Подробнее о символе обратной косой черты показано в следующей таблице:
Constant |
Meaning |
‘\a’ |
Audible alert (bell) |
‘\b’ |
Backspace |
‘\f’ |
Form feed |
‘\n’ |
New line |
‘\r’ |
Carriage return |
‘\t’ |
Horizontal tab |
‘\v’ |
Vertical tab |
‘\’’ |
Single quote |
‘\”’ |
Double quote |
‘\?’ |
Question mark |
‘\\’ |
Backslash |
‘\0’ |
Null |
Рассмотрим следующий оператор scanf
scanf("%d", &num);
Данные с клавиатуры принимаются функцией scanf. В приведенном выше формате & (амперсанд) перед каждым именем переменной является оператором, указывающим адрес имени переменной.
При этом выполнение останавливается и ожидает ввода значения переменной num. Когда введено целочисленное значение и нажата клавиша возврата, компьютер переходит к следующему оператору. Коды форматов scanf и printf перечислены в следующей таблице:
Code |
Reads... |
%c |
Single character |
%d |
Decimal integer |
%e |
Floating point value |
%f |
Floating point value |
%g |
Floating point value |
%h |
Short integer |
%i |
Decimal, hexadecimal or octal integer |
%o |
Octal integer |
%s |
String |
%u |
Unsigned decimal integer |
%x |
Hexadecimal integer |
Управляющие операторы
Программа состоит из ряда операторов, которые обычно выполняются последовательно. Программы могут быть намного более мощными, если мы сможем управлять порядком выполнения операторов.
Заявления делятся на три основных типа:
- Присваивание, при котором значения, обычно результаты вычислений, сохраняются в переменных.
- Ввод/вывод, данные считываются или распечатываются.
- Контроль, программа принимает решение о том, что делать дальше.
В этом разделе обсуждается использование операторов управления в C. Мы покажем, как их можно использовать для написания мощных программ:
- Повтор важных разделов программы.
- Выбор между необязательными разделами программы.
Оператор if else
Это используется, чтобы решить, делать ли что-то в определенный момент или выбрать между двумя вариантами действий.
Следующий тест определяет, сдал ли учащийся экзамен с проходным баллом 45.
if (result >= 45)
printf("Pass\n");
else
printf("Fail\n");
It is possible to use the if part without the else.
if (temperature < 0)
print("Frozen\n");
Каждая версия состоит из теста, заключенного в квадратные скобки, следующего за if. Если тест верен, то выполняется следующее утверждение. Если оно ложно, то выполняется утверждение, следующее за else, если оно присутствует. После этого остальная часть программы продолжается как обычно.
Если мы хотим, чтобы после if или else было несколько операторов, они должны быть сгруппированы между фигурными скобками. Такая группировка называется составным оператором или блоком.
if (result >= 45)
{ printf("Passed\n");
printf("Congratulations\n");
}
else
{ printf("Failed\n");
printf("Better Luck Next Time\n");
}
Иногда мы хотим принять многостороннее решение, основанное на нескольких условиях. Наиболее общий способ сделать это — использовать вариант else if в операторе if.
Это работает путем каскадирования нескольких сравнений. Как только один из них дает истинный результат, выполняется следующий оператор или блок, и дальнейшие сравнения не выполняются. В следующем примере мы выставляем оценки в зависимости от результата экзамена.
if (result <=100 && result >= 75)
printf("Passed: Grade A\n");
else if (result >= 60)
printf("Passed: Grade B\n");
else if (result >= 45)
printf("Passed: Grade C\n");
else
printf("Failed\n");
В этом примере все сравнения проверяют одну переменную с именем result. В других случаях каждый тест может включать другую переменную или некоторую комбинацию тестов. Один и тот же шаблон можно использовать с большим или меньшим количеством else if, а финал else может быть опущен.
Программист должен разработать правильную структуру для каждой задачи программирования. Чтобы лучше понять использование if else, давайте посмотрим на пример
#include <stdio.h>
int main()
{
int num;
for(num = 0 ; num < 10 ; num = num + 1)
{
if (num == 2)
printf("num is now equal to %d\n", num);
if (num < 5)
printf("num is now %d, which is less than 5\n", num);
else
printf("num is now %d, which is greater than 4\n", num);
} /* end of for loop */
return 0;
}
Результат программы
num is now 0, which is less than 5
num is now 1, which is less than 5
num is now equal to 2
num is now 2, which is less than 5
num is now 3, which is less than 5
num is now 4, which is less than 5
num is now 5, which is greater than 4
num is now 6, which is greater than 4
num is now 7, which is greater than 4
num is now 8, which is greater than 4
num is now 9, which is greater than 4
Заявление о переключении
Это еще одна форма многостороннего решения. Он хорошо структурирован, но может использоваться только в определенных случаях, когда;
- Проверяется только одна переменная, все ответвления должны зависеть от значения этой переменной. Переменная должна быть целочисленного типа. (целое, длинное, короткое или символьное).
- Каждое возможное значение переменной может управлять одной ветвью. Окончательная ветвь по умолчанию может использоваться для перехвата всех неуказанных случаев.
Приведенный ниже пример прояснит ситуацию. Это функция, которая преобразует целое число в расплывчатое описание. Это полезно, когда мы заинтересованы в измерении величины только тогда, когда она очень мала.
estimate(number)
int number;
/* Estimate a number as none, one, two, several, many */
{ switch(number) {
case 0 :
printf("None\n");
break;
case 1 :
printf("One\n");
break;
case 2 :
printf("Two\n");
break;
case 3 :
case 4 :
case 5 :
printf("Several\n");
break;
default :
printf("Many\n");
break;
}
}
Каждый интересный случай указан с соответствующим действием. Оператор break предотвращает выполнение любых дальнейших операторов, выходя из переключателя. Так как случай 3 и случай 4 не имеют следующего разрыва, они продолжают разрешать одно и то же действие для нескольких значений числа.
Обе конструкции if и switch позволяют программисту сделать выбор из множества возможных действий. Давайте посмотрим пример:
#include <stdio.h>
int main()
{
int num;
for (num = 3 ; num < 13 ; num = num + 1)
{
switch (num)
{
case 3 :
printf("The value is three\n");
break;
case 4 :
printf("The value is four\n");
break;
case 5 :
case 6 :
case 7 :
case 8 :
printf("The value is between 5 and 8\n");
break;
case 11 :
printf("The value is eleven\n");
break;
default :
printf("It is one of the undefined values\n");
break;
} /* end of switch */
} /* end of for loop */
return 0;
}
Вывод программы будет
The value is three
The value is four
The value is between 5 and 8
The value is between 5 and 8
The value is between 5 and 8
The value is between 5 and 8
It is one of the undefined values
It is one of the undefined values
The value is eleven
It is one of the undefined values
Заявление о перерыве
Мы уже встретили перерыв в обсуждении оператора switch. Он используется для выхода из цикла или переключателя, передачи управления первому оператору вне цикла или переключателя.
В циклах break можно использовать для принудительного выхода из цикла или для реализации цикла с проверкой на выход в середине тела цикла. Разрыв внутри цикла всегда должен быть защищен оператором if, который предоставляет тест для контроля условия выхода.
Заявление о продолжении
Это похоже на break, но встречается реже. Он работает только внутри циклов, где его результатом является принудительный немедленный переход к оператору управления циклом.
- В цикле while перейдите к оператору test.
- В цикле do while перейдите к оператору test.
- В цикле for перейдите к тесту и выполните итерацию.
Как и прерывание, продолжение должно быть защищено оператором if. Вы вряд ли будете использовать его очень часто. Чтобы лучше понять использование break и continue, давайте рассмотрим следующую программу:
#include <stdio.h>
int main()
{
int value;
for(value = 5 ; value < 15 ; value = value + 1)
{
if (value == 8)
break;
printf("In the break loop, value is now %d\n", value);
}
for(value = 5 ; value < 15 ; value = value + 1)
{
if (value == 8)
continue;
printf("In the continue loop, value is now %d\n", value);
}
return 0;
}
Вывод программы будет следующим:
In the break loop, value is now 5
In the break loop, value is now 6
In the break loop, value is now 7
In the continue loop, value is now 5
In the continue loop, value is now 6
In the continue loop, value is now 7
In the continue loop, value is now 9
In the continue loop, value is now 10
In the continue loop, value is now 11
In the continue loop, value is now 12
In the continue loop, value is now 13
In the continue loop, value is now 14
Циклы
Другим основным типом оператора управления является цикл. Циклы позволяют повторять оператор или блок операторов. Компьютеры очень хорошо умеют много раз повторять простые задачи. Цикл - это способ C достичь этого.
C позволяет выбрать один из трех типов циклов: while, do-while и for.
- Цикл while продолжает повторять действие до тех пор, пока соответствующий тест не вернет false. Это полезно, когда программист не знает заранее, сколько раз будет пройден цикл.
- Циклы do while аналогичны, но проверка выполняется после выполнения тела цикла. Это гарантирует, что тело цикла будет выполнено хотя бы один раз.
- Часто используется цикл for, обычно когда цикл проходит определенное количество раз. Он очень гибкий, и начинающим программистам следует позаботиться о том, чтобы не злоупотреблять предоставляемыми им возможностями.
Цикл while
Цикл while повторяет оператор до тех пор, пока проверка в верхней части не окажется ложной. В качестве примера, вот функция для возврата длины строки. Помните, что строка представлена как массив символов, оканчивающийся нулевым символом '\0'.
int string_length(char string[])
{ int i = 0;
while (string[i] != '\0')
i++;
return(i);
}
Строка передается функции в качестве аргумента. Размер массива не указан, функция будет работать для строки любого размера.
Цикл while используется для просмотра символов в строке по одному, пока не будет найден нулевой символ. Затем цикл завершается и возвращается индекс нуля.
Пока символ не нулевой, индекс увеличивается, и тест повторяется. Мы углубимся в массивы позже. Давайте посмотрим на пример цикла while:
#include <stdio.h>
int main()
{
int count;
count = 0;
while (count < 6)
{
printf("The value of count is %d\n", count);
count = count + 1;
}
return 0;
}
и результат отображается следующим образом:
The value of count is 0
The value of count is 1
The value of count is 2
The value of count is 3
The value of count is 4
The value of count is 5
Цикл do while
Это очень похоже на цикл while, за исключением того, что проверка выполняется в конце тела цикла. Это гарантирует, что цикл будет выполнен хотя бы один раз перед продолжением.
Такая установка часто используется, когда данные должны быть прочитаны. Затем тест проверяет данные и возвращается к повторному чтению, если они были неприемлемыми.
do
{
printf("Enter 1 for yes, 0 for no :");
scanf("%d", &input_value);
} while (input_value != 1 && input_value != 0)
To better understand the do while loop let us see the following example:
#include <stdio.h>
int main()
{
int i;
i = 0;
do
{
printf("The value of i is now %d\n", i);
i = i + 1;
} while (i < 5);
return 0;
}
Результат работы программы отображается следующим образом:
The value of i is now 0
The value of i is now 1
The value of i is now 2
The value of i is now 3
The value of i is now 4
Цикл for
Цикл for хорошо работает, когда количество итераций цикла известно до входа в цикл. Начало цикла состоит из трех частей, разделенных точкой с запятой.
- Первый запускается перед входом в цикл. Обычно это инициализация переменной цикла.
- Второй — тестовый, цикл завершается, когда он возвращает false.
- Третий оператор должен выполняться каждый раз, когда завершается тело цикла. Обычно это приращение счетчика циклов.
Примером является функция, которая вычисляет среднее значение чисел, хранящихся в массиве. В качестве аргументов функция принимает массив и количество элементов.
float average(float array[], int count)
{
float total = 0.0;
int i;
for(i = 0; i < count; i++)
total += array[i];
return(total / count);
}
Цикл for гарантирует, что правильное количество элементов массива суммируется перед вычислением среднего значения.
Три оператора в начале цикла for обычно выполняют только одну операцию, однако любой из них можно оставить пустым. Пустой первый или последний оператор будет означать отсутствие инициализации или выполнения приращения. Пустой оператор сравнения всегда будет считаться истинным. Это приведет к тому, что цикл будет работать бесконечно, если только он не будет прерван каким-либо другим способом. Это может быть оператор return или break.
Также можно втиснуть несколько утверждений в первую или третью позицию, разделяя их запятыми. Это позволяет использовать цикл с более чем одной управляющей переменной. Пример ниже иллюстрирует определение такого цикла с переменными hi и lo, начинающимися со 100 и 0 соответственно и сходящимися.
Цикл for позволяет использовать в нем различные сокращения. Обратите внимание на следующее выражение, в этом выражении один цикл содержит два цикла for. Здесь hi-- совпадает с hi = hi - 1, а lo++ совпадает с lo = lo + 1,
for(hi = 100, lo = 0; hi >= lo; hi--, lo++)
Цикл for чрезвычайно гибок и позволяет просто и быстро задавать многие типы поведения программы. Давайте посмотрим на пример цикла for
#include <stdio.h>
int main()
{
int index;
for(index = 0 ; index < 6 ; index = index + 1)
printf("The value of the index is %d\n", index);
return 0;
}
Результат программы отображается следующим образом:
The value of the index is 0
The value of the index is 1
The value of the index is 2
The value of the index is 3
The value of the index is 4
The value of the index is 5
Оператор goto
C имеет оператор goto, который позволяет делать неструктурированные переходы. Чтобы использовать оператор goto, вы просто используете зарезервированное слово goto, за которым следует символическое имя, к которому вы хотите перейти. Затем имя помещается в любом месте программы, за которым следует двоеточие. Вы можете переходить практически в любое место внутри функции, но вам не разрешается переходить в цикл, хотя вы можете выходить из цикла.
Эта конкретная программа действительно беспорядочна, но это хороший пример того, почему разработчики программного обеспечения стараются максимально исключить использование оператора goto. Единственное место в этой программе, где разумно использовать goto, это то место, где программа выходит из трех вложенных циклов за один переход. В этом случае было бы довольно запутанно настраивать переменную и последовательно выходить из каждого из трех вложенных циклов, но один оператор goto позволяет очень быстро выйти из всех трех.
Некоторые люди говорят, что оператор goto никогда не должен использоваться ни при каких обстоятельствах, но это узкое мышление. Если есть место, где goto будет явно выполнять более аккуратный поток управления, чем какая-либо другая конструкция, не стесняйтесь использовать его, однако, как и в остальной части программы на вашем мониторе. Давайте посмотрим на пример:
#include <stdio.h>
int main()
{
int dog, cat, pig;
goto real_start;
some_where:
printf("This is another line of the mess.\n");
goto stop_it;
/* the following section is the only section with a useable goto */
real_start:
for(dog = 1 ; dog < 6 ; dog = dog + 1)
{
for(cat = 1 ; cat < 6 ; cat = cat + 1)
{
for(pig = 1 ; pig < 4 ; pig = pig + 1)
{
printf("Dog = %d Cat = %d Pig = %d\n", dog, cat, pig);
if ((dog + cat + pig) > 8 ) goto enough;
}
}
}
enough: printf("Those are enough animals for now.\n");
/* this is the end of the section with a useable goto statement */
printf("\nThis is the first line of the code.\n");
goto there;
where:
printf("This is the third line of the code.\n");
goto some_where;
there:
printf("This is the second line of the code.\n");
goto where;
stop_it:
printf("This is the last line of this mess.\n");
return 0;
}
Давайте посмотрим на отображаемые результаты
Dog = 1 Cat = 1 Pig = 1
Dog = 1 Cat = 1 Pig = 2
Dog = 1 Cat = 1 Pig = 3
Dog = 1 Cat = 2 Pig = 1
Dog = 1 Cat = 2 Pig = 2
Dog = 1 Cat = 2 Pig = 3
Dog = 1 Cat = 3 Pig = 1
Dog = 1 Cat = 3 Pig = 2
Dog = 1 Cat = 3 Pig = 3
Dog = 1 Cat = 4 Pig = 1
Dog = 1 Cat = 4 Pig = 2
Dog = 1 Cat = 4 Pig = 3
Dog = 1 Cat = 5 Pig = 1
Dog = 1 Cat = 5 Pig = 2
Dog = 1 Cat = 5 Pig = 3
Those are enough animals for now.
This is the first line of the code.
This is the second line of the code.
This is the third line of the code.
This is another line of the mess.
This is the last line of this mess.
Указатели
Иногда нам нужно знать, где в памяти находится переменная. Указатель содержит адрес переменной, имеющей определенное значение. При объявлении указателя звездочка ставится непосредственно перед именем указателя. .
Адрес области памяти, в которой хранится переменная, можно узнать, поместив амперсанд перед именем переменной.
int num; /* Normal integer variable */
int *numPtr; /* Pointer to an integer variable */
В следующем примере печатается значение переменной и адрес в памяти этой переменной.
printf("The value %d is stored at address %X\n", num, &num);
Чтобы присвоить адрес переменной num указателю numPtr, вы назначаете адрес переменной num, как в приведенном ниже примере:
numPtr = #
Чтобы узнать, что хранится по адресу, на который указывает numPtr, необходимо разыменовать переменную. Разыменование достигается с помощью звездочки, с которой был объявлен указатель.
printf("The value %d is stored at address %X\n", *numPtr, numPtr);
Все переменные в программе находятся в памяти. Операторы, приведенные ниже, требуют, чтобы компилятор зарезервировал 4 байта памяти на 32-разрядном компьютере для переменной x с плавающей запятой, а затем поместил в нее значение 6,5.
float x;
x = 6.5;
Так как адрес расположения в памяти любой переменной получается размещением оператора & перед его именем, поэтому &x — это адрес x. C позволяет нам пойти еще дальше и определить переменную, называемую указателем, которая содержит адреса других переменных. Скорее можно сказать, что указатель указывает на другую переменную. Например:
float x;
float* px;
x = 6.5;
px = &x;
определяет px как указатель на объекты типа float и устанавливает его равным адресу x. Таким образом, *px относится к значению x:
Разберем следующие утверждения:
int var_x;
int* ptrX;
var_x = 6;
ptrX = &var_x;
*ptrX = 12;
printf("value of x : %d", var_x);
Первая строка заставляет компилятор зарезервировать место в памяти для целого числа. Вторая строка указывает компилятору зарезервировать место для хранения указателя.
Указатель — это место хранения адреса. Третья строка должна напоминать вам операторы scanf. Адрес "&" Оператор указывает компилятору перейти к месту хранения var_x, а затем передать адрес места хранения ptrX.
Звездочка * перед переменной говорит компилятору разыменовать указатель и перейти к памяти. Затем вы можете выполнять назначения переменной, хранящейся в этом месте. Вы можете сослаться на переменную и получить доступ к ее данным через указатель. Давайте посмотрим на пример указателей:
/* illustration of pointer use */
#include <stdio.h>
int main()
{
int index, *pt1, *pt2;
index = 39; /* any numerical value */
pt1 = &index; /* the address of index */
pt2 = pt1;
printf("The value is %d %d %d\n", index, *pt1, *pt2);
*pt1 = 13; /* this changes the value of index */
printf("The value is %d %d %d\n", index, *pt1, *pt2);
return 0;
}
Вывод программы будет отображаться следующим образом:
The value is 39 39 39
The value is 13 13 13
Давайте посмотрим на другой пример, чтобы лучше понять использование указателей:
#include <stdio.h>
#include <string.h>
int main()
{
char strg[40], *there, one, two;
int *pt, list[100], index;
strcpy(strg, "This is a character string.");
/* the function strcpy() is to copy one string to another. we’ll read about strcpy() function in String Section later */
one = strg[0]; /* one and two are identical */
two = *strg;
printf("The first output is %c %c\n", one, two);
one = strg[8]; /* one and two are identical */
two = *(strg+8);
printf("The second output is %c %c\n", one, two);
there = strg+10; /* strg+10 is identical to &strg[10] */
printf("The third output is %c\n", strg[10]);
printf("The fourth output is %c\n", *there);
for (index = 0 ; index < 100 ; index++)
list[index] = index + 100;
pt = list + 27;
printf("The fifth output is %d\n", list[27]);
printf("The sixth output is %d\n", *pt);
return 0;
}
Вывод программы будет таким:
The first output is T T
The second output is a a
The third output is c
The fourth output is c
The fifth output is 127
The sixth output is 127
Массивы
Массив – это набор переменных одного типа. Отдельные элементы массива идентифицируются целочисленным индексом. В C индекс начинается с нуля и всегда записывается в квадратных скобках.
Мы уже встречали одномерные массивы, которые объявляются подобным образом
целые результаты[20];
Массивы могут иметь больше измерений, и в этом случае они могут быть объявлены как
int results_2d[20][5];
int results_3d[20][5][3];
Каждый индекс имеет свой собственный набор квадратных скобок. Массив объявляется в основной функции, обычно содержит сведения о размерах. Вместо массива можно использовать другой тип, называемый указателем. Это означает, что размеры не фиксируются сразу, но пространство может быть выделено по мере необходимости. Это продвинутая техника, которая требуется только в некоторых специализированных программах.
В качестве примера, вот простая функция для сложения всех целых чисел в одномерный массив.
int add_array(int array[], int size)
{
int i;
int total = 0;
for(i = 0; i < size; i++)
total += array[i];
return(total);
}
Следующая программа создаст строку, получит доступ к некоторым данным в ней и распечатает ее. Получите к нему доступ снова, используя указатели, а затем распечатайте строку. Он должен напечатать «Привет!» и «012345678» на разных строках. Посмотрим код программы:
#include <stdio.h>
#define STR_LENGTH 10
void main()
{
char Str[STR_LENGTH];
char* pStr;
int i;
Str[0] = 'H';
Str[1] = 'i';
Str[2] = '!';
Str[3] = '\0'; // special end string character NULL
printf("The string in Str is : %s\n", Str);
pStr = &Str[0];
for (i = 0; i < STR_LENGTH; i++)
{
*pStr = '0'+i;
pStr++;
}
Str[STR_LENGTH-1] = '\0';
printf("The string in Str is : %s\n", Str);
}
[] (квадратные скобки) используются для объявления массива. Строка программы char Str[STR_LENGTH]; объявляет массив из десяти символов. Это десять отдельных персонажей, которые все в памяти собраны в одно место. Все они могут быть доступны через имя нашей переменной Str вместе с [n], где n – номер элемента.
Говоря о массиве, всегда следует помнить, что когда C объявляет массив из десяти элементов, доступ к которым вы можете получить, нумеруется от 0 до 9. Доступ к первому элементу соответствует доступу к 0-му элементу. Поэтому в случае массивов всегда отсчитывайте от 0 до размера массива - 1.
Далее обратите внимание, что мы ставим буквы "Привет!" в массив, но затем мы вставили '\0', вам, вероятно, интересно, что это такое. "\0" означает NULL и представляет собой конец строки. Все строки символов должны заканчиваться этим специальным символом '\0'. Если они этого не сделают, а затем кто-то вызовет printf для строки, тогда printf запустится в ячейке памяти вашей строки и продолжит печать, сообщая, что встречает '\ 0', и, таким образом, вы получите кучу мусора в конце вашей строки. Поэтому убедитесь, что ваши строки правильно завершены.
Массивы символов
Строковая константа, например
"Я строка"
— это массив символов. Внутри C он представлен символами ASCII в строке, т. е. «I», пробелом, «a», «m»,… или приведенной выше строкой, и завершается специальным нулевым символом «\0», чтобы программы могли найти конец строки.
Строковые константы часто используются для того, чтобы сделать вывод кода понятным с помощью printf:
printf("Hello, world\n");
printf("The value of a is: %f\n", a);
Строковые константы могут быть связаны с переменными. C предоставляет переменную символьного типа, которая может содержать один символ (1 байт) за раз. Строка символов хранится в массиве символьных типов, по одному символу ASCII на место.
Никогда не забывайте, что, поскольку строки обычно заканчиваются нулевым символом «\0», нам требуется одно дополнительное место для хранения в массиве.
C не предоставляет никакого оператора, который одновременно манипулирует целыми строками. Работа со строками осуществляется либо с помощью указателей, либо с помощью специальных подпрограмм, доступных в стандартной библиотеке строк string.h.
Использовать указатели на символы относительно просто, поскольку имя массива — это просто указатель на его первый элемент. Рассмотрим программу, приведенную ниже:
#include<stdio.h>
void main()
{
char text_1[100], text_2[100], text_3[100];
char *ta, *tb;
int i;
/* set message to be an arrray */
/* of characters; initialize it */
/* to the constant string "..." */
/* let the compiler decide on */
/* its size by using [] */
char message[] = "Hello, I am a string; what are
you?";
printf("Original message: %s\n", message);
/* copy the message to text_1 */
i=0;
while ( (text_1[i] = message[i]) != '\0' )
i++;
printf("Text_1: %s\n", text_1);
/* use explicit pointer arithmetic */
ta=message;
tb=text_2;
while ( ( *tb++ = *ta++ ) != '\0' )
;
printf("Text_2: %s\n", text_2);
}
Вывод программы будет следующим:
Original message: Hello, I am a string; what are you?
Text_1: Hello, I am a string; what are you?
Text_2: Hello, I am a string; what are you?
The standard “string” library contains many useful functions to manipulate strings, which we will learn in the string section later.
Доступ к элементам
Чтобы получить доступ к отдельному элементу массива, номер индекса следует за именем переменной в квадратных скобках. Затем эту переменную можно рассматривать как любую другую переменную в C. В следующем примере значение присваивается первому элементу массива.
х[0] = 16;
В следующем примере печатается значение третьего элемента массива.
printf("%d\n", x[2]);
В следующем примере функция scanf используется для чтения значения с клавиатуры в последний элемент массива из десяти элементов.
scanf("%d", &x[9]);
Инициализация элементов массива
Массивы можно инициализировать, как и любые другие переменные, путем присваивания. Поскольку массив содержит более одного значения, отдельные значения помещаются в фигурные скобки и разделяются запятыми. В следующем примере десятимерный массив инициализируется первыми десятью значениями таблицы умножения на три.
int x[10] = {3, 6, 9, 12, 15, 18, 21, 24, 27, 30};
Это позволяет не назначать значения по отдельности, как в следующем примере.
int x[10];
x[0] = 3;
x[1] = 6;
x[2] = 9;
x[3] = 12;
x[4] = 15;
x[5] = 18;
x[6] = 21;
x[7] = 24;
x[8] = 27;
x[9] = 30;
Перебор массива
Поскольку массив индексируется последовательно, мы можем использовать цикл for для отображения всех значений массива. В следующем примере отображаются все значения массива:
#include <stdio.h>
int main()
{
int x[10];
int counter;
/* Randomise the random number generator */
srand((unsigned)time(NULL));
/* Assign random values to the variable */
for (counter=0; counter<10; counter++)
x[counter] = rand();
/* Display the contents of the array */
for (counter=0; counter<10; counter++)
printf("element %d has the value %d\n", counter, x[counter]);
return 0;
}
хотя вывод каждый раз будет печатать разные значения, результат будет отображаться примерно так:
element 0 has the value 17132
element 1 has the value 24904
element 2 has the value 13466
element 3 has the value 3147
element 4 has the value 22006
element 5 has the value 10397
element 6 has the value 28114
element 7 has the value 19817
element 8 has the value 27430
element 9 has the value 22136
Многомерные массивы
Массив может иметь более одного измерения. Разрешение массиву иметь более одного измерения обеспечивает большую гибкость. Например, электронные таблицы строятся на двумерном массиве; массив для строк и массив для столбцов.
В следующем примере используется двумерный массив с двумя строками, каждая из которых содержит пять столбцов:
#include <stdio.h>
int main()
{
/* Declare a 2 x 5 multidimensional array */
int x[2][5] = { {1, 2, 3, 4, 5},
{2, 4, 6, 8, 10} };
int row, column;
/* Display the rows */
for (row=0; row<2; row++)
{
/* Display the columns */
for (column=0; column<5; column++)
printf("%d\t", x[row][column]);
putchar('\n');
}
return 0;
}
Вывод этой программы будет отображаться следующим образом:
1 2 3 4 5
2 4 6 8 10
Строки
Строка – это группа символов, обычно букв алфавита. Используется для форматирования печатного дисплея таким образом, чтобы он выглядел красиво, имел осмысленные имена и заголовки и был эстетически приятным для вас и людей, использующих вывод вашей программы.
На самом деле вы уже использовали строки в примерах из предыдущих разделов. Но это не полное введение строк. В программировании существует множество возможных случаев, когда использование форматированных строк помогает программисту избежать слишком многих сложностей в программе и, конечно же, слишком большого количества ошибок.
Полное определение строки — это последовательность данных символьного типа, заканчивающаяся нулевым символом ("\0").
Когда C собирается каким-либо образом использовать строку данных, чтобы сравнить ее с другой строкой, вывести ее, скопировать в другую строку и т. д., функции настроены на то, для чего они призваны. пока не будет обнаружен нуль.
В C нет базового типа данных для строки. Вместо этого; строки в C реализованы как массив символов. Например, чтобы сохранить имя, вы можете объявить достаточно большой массив символов для хранения имени, а затем использовать соответствующие библиотечные функции для управления именем.
В следующем примере на экране отображается строка, введенная пользователем:
#include <stdio.h>
int main()
{
char name[80]; /* Create a character array
called name */
printf("Enter your name: ");
gets(name);
printf("The name you entered was %s\n", name);
return 0;
}
Выполнение программы будет:
Enter your name: Tarun Tyagi
The name you entered was Tarun Tyagi
Некоторые общие строковые функции
Стандартная библиотека string.h содержит множество полезных функций для работы со строками. Здесь приведены примеры некоторых наиболее полезных функций.
Функция strlen
Функция strlen используется для определения длины строки. Давайте изучим использование strlen на примере:
#include <stdio.h>
#include <string.h>
int main()
{
char name[80];
int length;
printf("Enter your name: ");
gets(name);
length = strlen(name);
printf("Your name has %d characters\n", length);
return 0;
}
И выполнение программы будет следующим:
Enter your name: Tarun Subhash Tyagi
Your name has 19 characters
Enter your name: Preeti Tarun
Your name has 12 characters
Функция strcpy
Функция strcpy используется для копирования одной строки в другую. Давайте изучим использование этой функции на примере:
#include <stdio.h>
#include <string.h>
int main()
{
char first[80];
char second[80];
printf("Enter first string: ");
gets(first);
printf("Enter second string: ");
gets(second);
printf("first: %s, and second: %s Before strcpy()\n "
, first, second);
strcpy(second, first);
printf("first: %s, and second: %s After strcpy()\n",
first, second);
return 0;
}
и вывод программы будет таким:
Enter first string: Tarun
Enter second string: Tyagi
first: Tarun, and second: Tyagi Before strcpy()
first: Tarun, and second: Tarun After strcpy()
Функция strcmp
Функция strcmp используется для сравнения двух строк. Имя переменной массива указывает на базовый адрес этого массива. Поэтому, если мы попытаемся сравнить две строки, используя следующее, мы будем сравнивать два адреса, которые, очевидно, никогда не будут одинаковыми, поскольку невозможно хранить два значения в одном и том же месте.
if (first == second) /* Никогда нельзя сравнивать строки */
В следующем примере функция strcmp используется для сравнения двух строк:
#include <string.h>
int main()
{
char first[80], second[80];
int t;
for(t=1;t<=2;t++)
{
printf("\nEnter a string: ");
gets(first);
printf("Enter another string: ");
gets(second);
if (strcmp(first, second) == 0)
puts("The two strings are equal");
else
puts("The two strings are not equal");
}
return 0;
}
И выполнение программы будет следующим:
Enter a string: Tarun
Enter another string: tarun
The two strings are not equal
Enter a string: Tarun
Enter another string: Tarun
The two strings are equal
Функция strcat
Функция strcat используется для соединения одной строки с другой. Давайте посмотрим, как? С помощью примера:
#include <string.h>
int main()
{
char first[80], second[80];
printf("Enter a string: ");
gets(first);
printf("Enter another string: ");
gets(second);
strcat(first, second);
printf("The two strings joined together: %s\n",
first);
return 0;
}
И выполнение программы будет следующим:
Enter a string: Data
Enter another string: Recovery
The two strings joined together: DataRecovery
Функция strtok
Функция strtok используется для поиска следующей лексемы в строке. Маркер определяется списком возможных разделителей.
В следующем примере считывается строка текста из файла и определяется слово с помощью разделителей, пробела, табуляции и новой строки. Затем каждое слово отображается на отдельной строке:
#include <stdio.h>
#include <string.h>
int main()
{
FILE *in;
char line[80];
char *delimiters = " \t\n";
char *token;
if ((in = fopen("C:\\text.txt", "r")) == NULL)
{
puts("Unable to open the input file");
return 0;
}
/* Read each line one at a time */
while(!feof(in))
{
/* Get one line */
fgets(line, 80, in);
if (!feof(in))
{
/* Break the line up into words */
token = strtok(line, delimiters);
while (token != NULL)
{
puts(token);
/* Get the next word */
token = strtok(NULL, delimiters);
}
}
}
fclose(in);
return 0;
}
Эта программа выше, в = fopen("C:\\text.txt", "r"), открывает и существующий файл C:\\text.txt. Если файл не существует по указанному пути или по какой-либо причине файл не может быть открыт, на экране отображается сообщение об ошибке.
Рассмотрите следующий пример, в котором используются некоторые из этих функций:
#include <stdio.h>
#include <string.h>
void main()
{
char line[100], *sub_text;
/* initialize string */
strcpy(line,"hello, I am a string;");
printf("Line: %s\n", line);
/* add to end of string */
strcat(line," what are you?");
printf("Line: %s\n", line);
/* find length of string */
/* strlen brings back */
/* length as type size_t */
printf("Length of line: %d\n", (int)strlen(line));
/* find occurence of substrings */
if ( (sub_text = strchr ( line, 'W' ) )!= NULL )
printf("String starting with \"W\" ->%s\n",
sub_text);
if ( ( sub_text = strchr ( line, 'w' ) )!= NULL )
printf("String starting with \"w\" ->%s\n",
sub_text);
if ( ( sub_text = strchr ( sub_text, 'u' ) )!= NULL )
printf("String starting with \"w\" ->%s\n",
sub_text);
}
Вывод программы будет отображаться следующим образом:
Line: hello, I am a string;
Line: hello, I am a string; what are you?
Length of line: 35
String starting with "w" ->what are you?
String starting with "w" ->u?
Функции
Наилучший способ разработки и поддержки большой программы состоит в том, чтобы собрать ее из более мелких частей, каждой из которых легче управлять (метод, иногда называемый "разделяй и властвуй"). Функции позволяют программисту разделить программу на модули.
Функции позволяют разбивать сложные программы на небольшие блоки, каждый из которых легче писать, читать и поддерживать. Мы уже встречались с функцией main и использовали printf из стандартной библиотеки. Конечно, мы можем создавать свои собственные функции и заголовочные файлы. Функция имеет следующий вид:
return-type function-name ( argument list if necessary )
{
local-declarations;
statements ;
return return-value;
}
Если тип возврата опущен, C по умолчанию принимает значение int. Возвращаемое значение должно быть объявленного типа. Все переменные, объявленные внутри функций, называются локальными переменными, поскольку они известны только в той функции, для которой они были определены.
Некоторые функции имеют список параметров, который обеспечивает метод связи между функцией и модулем, вызвавшим функцию. Параметры также являются локальными переменными в том смысле, что они недоступны вне функции. Все рассмотренные программы имеют функцию main.
Функция может просто выполнять задачу, не возвращая никакого значения, и в этом случае она имеет следующий вид:
void function-name ( argument list if necessary )
{
local-declarations ;
statements;
}
Аргументы всегда передаются по значению в вызовах функций C. Это означает, что подпрограммам передаются локальные копии значений аргументов. Любые изменения, внесенные в аргументы внутри функции, применяются только к локальным копиям аргументов.
Чтобы изменить или определить аргумент в списке аргументов, этот аргумент должен быть передан как адрес. Вы используете обычные переменные, если функция не изменяет значения этих аргументов. Вы ДОЛЖНЫ использовать указатели, если функция изменяет значения этих аргументов.
Давайте учиться на примерах:
#include <stdio.h>
void exchange ( int *a, int *b )
{
int temp;
temp = *a;
*a = *b;
*b = temp;
printf(" From function exchange: ");
printf("a = %d, b = %d\n", *a, *b);
}
void main()
{
int a, b;
a = 5;
b = 7;
printf("From main: a = %d, b = %d\n", a, b);
exchange(&a, &b);
printf("Back in main: ");
printf("a = %d, b = %d\n", a, b);
}
И вывод этой программы будет отображаться следующим образом:
From main: a = 5, b = 7
From function exchange: a = 7, b = 5
Back in main: a = 7, b = 5
Давайте посмотрим на другой пример. В следующем примере используется функция Square, которая записывает квадрат чисел от 1 до 10.
#include <stdio.h>
int square(int x); /* Function prototype */
int main()
{
int counter;
for (counter=1; counter<=10; counter++)
printf("Square of %d is %d\n", counter, square(counter));
return 0;
}
/* Define the function 'square' */
int square(int x)
{
return x * x;
}
Вывод этой программы будет отображаться следующим образом:
Square of 1 is 1
Square of 2 is 4
Square of 3 is 9
Square of 4 is 16
Square of 5 is 25
Square of 6 is 36
Square of 7 is 49
Square of 8 is 64
Square of 9 is 81
Square of 10 is 100
Квадрат прототипа функции объявляет функцию, которая принимает целочисленный параметр и возвращает целое число. Когда компилятор достигает квадрата вызова функции в основной программе, он может сравнить вызов функции с определением функции.
Когда программа достигает строки, вызывающей квадрат функции, она переходит к функции и выполняет ее, прежде чем возобновить свой путь через основную программу. Программы, которые не имеют возвращаемого типа, должны быть объявлены с использованием void. Таким образом, параметры функции могут быть переданы по значению или переданы по ссылке.
Рекурсивная функция — это функция, которая вызывает сама себя. И этот процесс называется рекурсией.
Функции передачи по значению
Параметры квадратной функции в предыдущем примере передаются по значению. Это означает, что в функцию была передана только копия переменной. Любые изменения значения не будут отражены обратно в вызывающую функцию.
В следующем примере используется передача по значению и изменяется значение переданного параметра, что не влияет на вызывающую функцию. Функция count_down была объявлена пустой, так как не имеет возвращаемого типа.
#include <stdio.h>
void count_down(int x);
int main()
{
int counter;
for (counter=1; counter<=10; counter++)
count_down(counter);
return 0;
}
void count_down(int x)
{
int counter;
for (counter = x; counter > 0; counter--)
{
printf("%d ", x);
x--;
}
putchar('\n');
}
Вывод программы будет отображаться следующим образом:
1
2 1
3 2 1
4 3 2 1
5 4 3 2 1
6 5 4 3 2 1
7 6 5 4 3 2 1
8 7 6 5 4 3 2 1
9 8 7 6 5 4 3 2 1
10 9 8 7 6 5 4 3 2 1
Давайте посмотрим еще один пример C Pass By Value, чтобы лучше понять его. В следующем примере число от 1 до 30 000, введенное пользователем, преобразуется в слова.
#include <stdio.h>
void do_units(int num);
void do_tens(int num);
void do_teens(int num);
int main()
{
int num, residue;
do
{
printf("Enter a number between 1 and 30,000: ");
scanf("%d", &num);
} while (num < 1 || num > 30000);
residue = num;
printf("%d in words = ", num);
do_tens(residue/1000);
if (num >= 1000)
printf("thousand ");
residue %= 1000;
do_units(residue/100);
if (residue >= 100)
{
printf("hundred ");
}
if (num > 100 && num%100 > 0)
printf("and ");
residue %=100;
do_tens(residue);
putchar('\n');
return 0;
}
void do_units(int num)
{
switch(num)
{
case 1:
printf("one ");
break;
case 2:
printf("two ");
break;
case 3:
printf("three ");
break;
case 4:
printf("four ");
break;
case 5:
printf("five ");
break;
case 6:
printf("six ");
break;
case 7:
printf("seven ");
break;
case 8:
printf("eight ");
break;
case 9:
printf("nine ");
}
}
void do_tens(int num)
{
switch(num/10)
{
case 1:
do_teens(num);
break;
case 2:
printf("twenty ");
break;
case 3:
printf("thirty ");
break;
case 4:
printf("forty ");
break;
case 5:
printf("fifty ");
break;
case 6:
printf("sixty ");
break;
case 7:
printf("seventy ");
break;
case 8:
printf("eighty ");
break;
case 9:
printf("ninety ");
}
if (num/10 != 1)
do_units(num%10);
}
void do_teens(int num)
{
switch(num)
{
case 10:
printf("ten ");
break;
case 11:
printf("eleven ");
break;
case 12:
printf("twelve ");
break;
case 13:
printf("thirteen ");
break;
case 14:
printf("fourteen ");
break;
case 15:
printf("fifteen ");
break;
case 16:
printf("sixteen ");
break;
case 17:
printf("seventeen ");
break;
case 18:
printf("eighteen ");
break;
case 19:
printf("nineteen ");
}
}
и вывод программы будет следующим:
Enter a number between 1 and 30,000: 12345
12345 in words = twelve thousand three hundred and forty five
Вызов по ссылке
Чтобы вызвать функцию по ссылке, вместо передачи самой переменной передайте адрес переменной. Адрес переменной можно получить с помощью оператора & оператор. Далее вызывается функция подкачки, передающая адреса переменных вместо фактических значений.
swap(&x, &y);
Разыменование
Проблема, которая у нас есть сейчас, заключается в том, что функции swap был передан адрес, а не переменная, поэтому нам нужно разыменовать переменные, чтобы мы смотрели на фактические значения, а не на адреса переменных, чтобы поменять местами их.
Разыменование достигается в C с помощью нотации указателя (*). Проще говоря, это означает размещение * перед каждой переменной перед ее использованием, чтобы он ссылался на значение переменной, а не на ее адрес. Следующая программа иллюстрирует передачу по ссылке для замены двух значений.
#include <stdio.h>
void swap(int *x, int *y);
int main()
{
int x=6, y=10;
printf("Before the function swap, x = %d and y =
%d\n\n", x, y);
swap(&x, &y);
printf("After the function swap, x = %d and y =
%d\n\n", x, y);
return 0;
}
void swap(int *x, int *y)
{
int temp = *x;
*x = *y;
*y = temp;
}
Давайте посмотрим на результат работы программы:
Перед функцией swap x = 6 и y = 10
После замены функций x = 10 и y = 6
Функции могут быть рекурсивными, то есть функция может вызывать сама себя. Каждый вызов самого себя требует, чтобы текущее состояние функции было помещено в стек. Важно помнить об этом факте, так как легко создать переполнение стека, т. е. в стеке закончилось место для размещения каких-либо данных.
В следующем примере вычисляется факториал числа с использованием рекурсии. Факториал — это число, умноженное на любое другое целое число, стоящее ниже него, вплоть до 1. Например, факториал числа 6 равен:
Факториал 6 = 6 * 5 * 4 * 3 * 2 * 1
Следовательно, факториал 6 равен 720. Из приведенного выше примера видно, что факториал 6 = 6 * факториал 5. Точно так же факториал 5 = 5 * факториал 4 и т. д.
Ниже приводится общее правило вычисления факториала.
факториал(n) = n * факториал(n-1)
Приведенное выше правило перестает действовать, когда n = 1, так как факториал 1 равен 1. Попробуем лучше понять его на примере:
#include <stdio.h>
long int factorial(int num);
int main()
{
int num;
long int f;
printf("Enter a number: ");
scanf("%d", &num);
f = factorial(num);
printf("factorial of %d is %ld\n", num, f);
return 0;
}
long int factorial(int num)
{
if (num == 1)
return 1;
else
return num * factorial(num-1);
}
Давайте посмотрим на результат выполнения этой программы:
Enter a number: 7
factorial of 7 is 5040
Выделение памяти в C
Компилятор C имеет библиотеку распределения памяти, определенную в malloc.h. Память резервируется с помощью функции malloc и возвращает указатель на адрес. Он принимает один параметр — размер требуемой памяти в байтах.
В следующем примере выделяется место для строки "hello world".
ptr = (char *)malloc(strlen("Привет, мир") + 1);
Дополнительный байт необходим для учета символа завершения строки '\0'. (char *) называется приведением и заставляет возвращаемый тип быть char *.
Поскольку типы данных имеют разные размеры, а malloc возвращает пространство в байтах, в целях переносимости рекомендуется использовать оператор sizeof при указании выделяемого размера.
В следующем примере строка считывается в буфер массива символов, затем выделяется требуемый объем памяти и копируется в переменную с именем "ptr".
#include <string.h>
#include <malloc.h>
int main()
{
char *ptr, buffer[80];
printf("Enter a string: ");
gets(buffer);
ptr = (char *)malloc((strlen(buffer) + 1) *
sizeof(char));
strcpy(ptr, buffer);
printf("You entered: %s\n", ptr);
return 0;
}
Вывод программы будет следующим:
Enter a string: India is the best
You entered: India is the best
Перераспределение памяти
Возможно, во время программирования много раз вы захотите перераспределить память. Это делается с помощью функции realloc. Функция realloc принимает два параметра: базовый адрес памяти, размер которой вы хотите изменить, и объем пространства, которое вы хотите зарезервировать, и возвращает указатель на базовый адрес.
Предположим, что мы зарезервировали место для указателя с именем msg и хотим перераспределить пространство до размера, который он уже занимает, плюс длина другой строки, тогда мы могли бы использовать следующее.
msg = (char *)realloc(msg, (strlen(msg) + strlen(buffer) + 1)*sizeof(char));
Следующая программа иллюстрирует использование malloc, realloc и free. Пользователь вводит серию строк, которые соединяются вместе. Программа прекращает чтение строк при вводе пустой строки.
#include <string.h>
#include <malloc.h>
int main()
{
char buffer[80], *msg;
int firstTime=0;
do
{
printf("\nEnter a sentence: ");
gets(buffer);
if (!firstTime)
{
msg = (char *)malloc((strlen(buffer) + 1) *
sizeof(char));
strcpy(msg, buffer);
firstTime = 1;
}
else
{
msg = (char *)realloc(msg, (strlen(msg) +
strlen(buffer) + 1) * sizeof(char));
strcat(msg, buffer);
}
puts(msg);
} while(strcmp(buffer, ""));
free(msg);
return 0;
}
Вывод программы будет следующим:
Enter a sentence: Once upon a time
Once upon a time
Enter a sentence: there was a king
Once upon a timethere was a king
Enter a sentence: the king was
Once upon a timethere was a kingthe king was
Enter a sentence:
Once upon a timethere was a kingthe king was
Освобождение памяти
Когда вы закончите с выделенной памятью, вы никогда не должны забывать освобождать память, так как это освободит ресурсы и повысит скорость. Чтобы освободить выделенную память, используйте функцию free.
free(ptr);
Структуры
Помимо базовых типов данных, C имеет структурный механизм, который позволяет группировать элементы данных, связанные друг с другом, под общим именем. Это обычно называется определяемым пользователем типом.
Ключевое слово struct запускает определение структуры, а тег дает уникальное имя структуре. Типы данных и имена переменных, добавленные в структуру, являются членами структуры. Результатом является шаблон структуры, который можно использовать в качестве спецификатора типа. Ниже представлена структура с тегом месяца.
struct month
{
char name[10];
char abbrev[4];
int days;
};
Тип структуры обычно определяется в начале файла с помощью оператора typedef. typedef определяет и называет новый тип, позволяя использовать его во всей программе. typedef обычно появляется сразу после операторов #define и #include в файле.
Ключевое слово typedef может использоваться для определения слова, относящегося к структуре, вместо указания ключевого слова struct с именем структуры. Обычно название typedef пишется заглавными буквами. Вот примеры определения структуры.
typedef struct {
char name[64];
char course[128];
int age;
int year;
} student;
Это определяет новый тип студенческих переменных, которые могут быть объявлены следующим образом.
студент st_rec;
Обратите внимание, как это похоже на объявление int или float. Имя переменной — st_rec, в ней есть элементы с именем, курсом, возрастом и годом. По аналогии,
typedef struct element
{
char data;
struct element *next;
} STACKELEMENT;
A variable of the user defined type struct element may now be declared as follows.
STACKELEMENT *stack;
Рассмотрим следующую структуру:
struct student
{
char *name;
int grade;
};
Указатель на структуру student может быть определен следующим образом.
struct student *hnc;
When accessing a pointer to a structure, the member pointer operator, -> is used instead of the dot operator. To add a grade to a structure,
s.grade = 50;
Вы можете присвоить оценку структуре следующим образом.
s->grade = 50;
Как и в случае с базовыми типами данных, если вы хотите, чтобы изменения, сделанные функцией в переданных параметрах, были постоянными, вы должны передавать по ссылке (передавать адрес). Механизм точно такой же, как и у базовых типов данных. Передайте адрес и обратитесь к переменной, используя нотацию указателя.
Определив структуру, вы можете объявить ее экземпляр и присвоить значения членам, используя запись через точку. Следующий пример иллюстрирует использование структуры месяца.
#include <stdio.h>
#include <string.h>
struct month
{
char name[10];
char abbreviation[4];
int days;
};
int main()
{
struct month m;
strcpy(m.name, "January");
strcpy(m.abbreviation, "Jan");
m.days = 31;
printf("%s is abbreviated as %s and has %d days\n", m.name, m.abbreviation, m.days);
return 0;
}
Вывод программы будет следующим:
January is abbreviated as Jan and has 31 days
Все компиляторы ANSI C позволяют вам назначать одну структуру другой, выполняя копирование по элементам. Если бы у нас были месячные структуры с именами m1 и m2, мы могли бы присвоить значения от m1 до m2 следующим образом:
- Структура с указателями.
- Структура инициализируется.
- Передача структуры в функцию.
- Указатели и структуры.
Структуры с указателями в C
Хранение строк в массиве фиксированного размера является неэффективным использованием памяти. Более эффективным подходом было бы использование указателей. Указатели используются в структурах точно так же, как они используются в определениях обычных указателей. Давайте посмотрим пример:
#include <string.h>
#include <malloc.h>
struct month
{
char *name;
char *abbreviation;
int days;
};
int main()
{
struct month m;
m.name = (char *)malloc((strlen("January")+1) *
sizeof(char));
strcpy(m.name, "January");
m.abbreviation = (char *)malloc((strlen("Jan")+1) *
sizeof(char));
strcpy(m.abbreviation, "Jan");
m.days = 31;
printf("%s is abbreviated as %s and has %d days\n",
m.name, m.abbreviation, m.days);
return 0;
}
Вывод программы будет следующим:
Январь обозначается аббревиатурой Ян и состоит из 31 дня.
Инициализаторы структуры в C
Чтобы предоставить набор начальных значений для структуры, в оператор объявления можно добавить инициализаторы. Поскольку месяцы начинаются с 1, а массивы в C начинаются с нуля, в следующем примере использовался дополнительный элемент в нулевой позиции, называемый мусором.
#include <stdio.h>
#include <string.h>
struct month
{
char *name;
char *abbreviation;
int days;
} month_details[] =
{
"Junk", "Junk", 0,
"January", "Jan", 31,
"February", "Feb", 28,
"March", "Mar", 31,
"April", "Apr", 30,
"May", "May", 31,
"June", "Jun", 30,
"July", "Jul", 31,
"August", "Aug", 31,
"September", "Sep", 30,
"October", "Oct", 31,
"November", "Nov", 30,
"December", "Dec", 31
};
int main()
{
int counter;
for (counter=1; counter<=12; counter++)
printf("%s is abbreviated as %s and has %d days\n",
month_details[counter].name,
month_details[counter].abbreviation,
month_details[counter].days);
return 0;
}
И вывод будет отображаться следующим образом:
January is abbreviated as Jan and has 31 days
February is abbreviated as Feb and has 28 days
March is abbreviated as Mar and has 31 days
April is abbreviated as Apr and has 30 days
May is abbreviated as May and has 31 days
June is abbreviated as Jun and has 30 days
July is abbreviated as Jul and has 31 days
August is abbreviated as Aug and has 31 days
September is abbreviated as Sep and has 30 days
October is abbreviated as Oct and has 31 days
November is abbreviated as Nov and has 30 days
December is abbreviated as Dec and has 31 days
Передача структур функциям в C
Структуры могут быть переданы в качестве параметра функции, как и любой из основных типов данных. В следующем примере используется структура с именем date, которая передается в функцию isLeapYear, чтобы определить, является ли год високосным.
Обычно вы передаете только значение дня, но вся структура передается, чтобы проиллюстрировать передачу структур функциям.
#include <stdio.h>
#include <string.h>
struct month
{
char *name;
char *abbreviation;
int days;
} month_details[] =
{
"Junk", "Junk", 0,
"January", "Jan", 31,
"February", "Feb", 28,
"March", "Mar", 31,
"April", "Apr", 30,
"May", "May", 31,
"June", "Jun", 30,
"July", "Jul", 31,
"August", "Aug", 31,
"September", "Sep", 30,
"October", "Oct", 31,
"November", "Nov", 30,
"December", "Dec", 31
};
struct date
{
int day;
int month;
int year;
};
int isLeapYear(struct date d);
int main()
{
struct date d;
printf("Enter the date (eg: 11/11/1980): ");
scanf("%d/%d/%d", &d.day, &d.month, &d.year);
printf("The date %d %s %d is ", d.day,
month_details[d.month].name, d.year);
if (isLeapYear(d) == 0)
printf("not ");
puts("a leap year");
return 0;
}
int isLeapYear(struct date d)
{
if ((d.year % 4 == 0 && d.year % 100 != 0) ||
d.year % 400 == 0)
return 1;
return 0;
}
И Выполнение программы будет следующим:
Enter the date (eg: 11/11/1980): 9/12/1980
The date 9 December 1980 is a leap year
В следующем примере массив структур динамически выделяется для хранения имен и оценок учащихся. Затем оценки отображаются пользователю в порядке возрастания.
#include <string.h>
#include <malloc.h>
struct student
{
char *name;
int grade;
};
void swap(struct student *x, struct student *y);
int main()
{
struct student *group;
char buffer[80];
int spurious;
int inner, outer;
int counter, numStudents;
printf("How many students are there in the group: ");
scanf("%d", &numStudents);
group = (struct student *)malloc(numStudents *
sizeof(struct student));
for (counter=0; counter<numStudents; counter++)
{
spurious = getchar();
printf("Enter the name of the student: ");
gets(buffer);
group[counter].name = (char *)malloc((strlen(buffer)+1) * sizeof(char));
strcpy(group[counter].name, buffer);
printf("Enter grade: ");
scanf("%d", &group[counter].grade);
}
for (outer=0; outer<numStudents; outer++)
for (inner=0; inner<outer; inner++)
if (group[outer].grade <
group[inner].grade)
swap(&group[outer], &group[inner]);
puts("The group in ascending order of grades ...");
for (counter=0; counter<numStudents; counter++)
printf("%s achieved Grade %d \n”,
group[counter].name,
group[counter].grade);
return 0;
}
void swap(struct student *x, struct student *y)
{
struct student temp;
temp.name = (char *)malloc((strlen(x->name)+1) *
sizeof(char));
strcpy(temp.name, x->name);
temp.grade = x->grade;
x->grade = y->grade;
x->name = (char *)malloc((strlen(y->name)+1) *
sizeof(char));
strcpy(x->name, y->name);
y->grade = temp.grade;
y->name = (char *)malloc((strlen(temp.name)+1) *
sizeof(char));
strcpy(y->name, temp.name);
}
Выполнение вывода будет следующим:
How many students are there in the group: 4
Enter the name of the student: Anuraaj
Enter grade: 7
Enter the name of the student: Honey
Enter grade: 2
Enter the name of the student: Meetushi
Enter grade: 1
Enter the name of the student: Deepti
Enter grade: 4
The group in ascending order of grades ...
Meetushi achieved Grade 1
Honey achieved Grade 2
Deepti achieved Grade 4
Anuraaj achieved Grade 7
Союз
Объединение позволяет просматривать одни и те же данные с разными типами или использовать одни и те же данные с разными именами. Союзы похожи на структуры. Объединение объявляется и используется так же, как и структура.
Объединение отличается от структуры тем, что одновременно может использоваться только один из его членов. Причина этого проста. Все члены союза занимают одну и ту же область памяти. Они накладываются друг на друга.
Объединения определяются и объявляются так же, как и структуры. Единственная разница в объявлениях заключается в том, что вместо struct используется ключевое слово union. Чтобы определить простое объединение символьной переменной и целочисленной переменной, вы должны написать следующее:
union shared {
char c;
int i;
};
Это совместно используемое объединение может использоваться для создания экземпляров объединения, которое может содержать либо символьное значение c, либо целочисленное значение i. Это условие ИЛИ. В отличие от структуры, которая содержит оба значения, объединение может содержать только одно значение за раз.
Объединение может быть инициализировано при его объявлении. Потому что только один элемент может использоваться одновременно и только один может быть инициализирован. Во избежание путаницы можно инициализировать только первый член объединения. Следующий код показывает объявление и инициализацию экземпляра общего объединения:
объединение общих generic_variable = {`@'};
Обратите внимание, что объединение generic_variable было инициализировано так же, как и первый элемент структуры.
Отдельные элементы объединения можно использовать так же, как элементы структуры можно использовать с помощью оператора члена (.). Однако есть важная разница в доступе к членам союза.
Одновременно должен быть доступен только один член союза. Поскольку объединение хранит своих членов друг над другом, важно иметь доступ только к одному члену за раз.
The union Keyword
union tag {
union_member(s);
/* additional statements may go here */
}instance;
Ключевое слово union используется для объявления союзов. Объединение — это набор одной или нескольких переменных (union_members), сгруппированных под одним именем. Кроме того, каждый из этих членов объединения занимает одну и ту же область памяти.
Ключевое слово union определяет начало определения объединения. За ним следует тег, который представляет собой имя, данное объединению. За тегом следуют члены объединения, заключенные в фигурные скобки.
Также может быть определен экземпляр, фактическое объявление объединения. Если вы определяете структуру без экземпляра, это просто шаблон, который можно использовать позже в программе для объявления структур. Ниже приведен формат шаблона:
union tag {
union_member(s);
/* additional statements may go here */
};
Чтобы использовать шаблон, вы должны использовать следующий формат:
экземпляр тега объединения;
Чтобы использовать этот формат, вы должны предварительно объявить объединение с данным тегом.
/* Объявить шаблон объединения с именем tag */
union tag {
int num;
char alps;
}
/* Use the union template */
union tag mixed_variable;
/* Declare a union and instance together */
union generic_type_tag {
char c;
int i;
float f;
double d;
} generic;
/* Initialize a union. */
union date_tag {
char full_date[9];
struct part_date_tag {
char month[2];
char break_value1;
char day[2];
char break_value2;
char year[2];
} part_date;
}date = {"09/12/80"};
Давайте лучше поймем это с помощью примеров:
#include <stdio.h>
int main()
{
union
{
int value; /* This is the first part of the union */
struct
{
char first; /* These two values are the second part of it */
char second;
} half;
} number;
long index;
for (index = 12 ; index < 300000L ; index += 35231L)
{
number.value = index;
printf("%8x %6x %6x\n", number.value,
number.half.first,
number.half.second);
}
return 0;
}
И вывод программы будет отображаться следующим образом:
c c 0
89ab ffab ff89
134a 4a 13
9ce9 ffe9 ff9c
2688 ff88 26
b027 27 ffb0
39c6 ffc6 39
c365 65 ffc3
4d04 4 4d
Практическое использование объединения при восстановлении данных
Теперь давайте посмотрим на практическое использование union в программировании восстановления данных. Возьмем небольшой пример. Следующая программа представляет собой небольшую модель программы сканирования поврежденных секторов для дисковода гибких дисков (a: ), однако это не полная модель программного обеспечения для сканирования поврежденных секторов.
Давайте рассмотрим программу:
#include<dos.h>
#include<conio.h>
int main()
{
int rp, head, track, sector, status;
char *buf;
union REGS in, out;
struct SREGS s;
clrscr();
/* Reset the disk system to initialize to disk */
printf("\n Resetting the disk system....");
for(rp=0;rp<=2;rp++)
{
in.h.ah = 0;
in.h.dl = 0x00;
int86(0x13,&in,&out);
}
printf("\n\n\n Now Testing the Disk for Bad Sectors....");
/* scan for bad sectors */
for(track=0;track<=79;track++)
{
for(head=0;head<=1;head++)
{
for(sector=1;sector<=18;sector++)
{
in.h.ah = 0x04;
in.h.al = 1;
in.h.dl = 0x00;
in.h.ch = track;
in.h.dh = head;
in.h.cl = sector;
in.x.bx = FP_OFF(buf);
s.es = FP_SEG(buf);
int86x(0x13,&in,&out,&s);
if(out.x.cflag)
{
status=out.h.ah;
printf("\n track:%d Head:%d Sector:%d Status ==0x%X",track,head,sector,status);
}
}
}
}
printf("\n\n\nDone");
return 0;
}
Теперь посмотрим, как будет выглядеть его вывод, если на дискете есть битые сектора:
Сброс дисковой системы....
Теперь проверка диска на наличие поврежденных секторов....
track:0 Head:0 Sector:4 Status ==0xA
track:0 Head:0 Sector:5 Status ==0xA
track:1 Head:0 Sector:4 Status ==0xA
track:1 Head:0 Sector:5 Status ==0xA
track:1 Head:0 Sector:6 Status ==0xA
track:1 Head:0 Sector:7 Status ==0xA
track:1 Head:0 Sector:8 Status ==0xA
track:1 Head:0 Sector:11 Status ==0xA
track:1 Head:0 Sector:12 Status ==0xA
track:1 Head:0 Sector:13 Status ==0xA
track:1 Head:0 Sector:14 Status ==0xA
track:1 Head:0 Sector:15 Status ==0xA
track:1 Head:0 Sector:16 Status ==0xA
track:1 Head:0 Sector:17 Status ==0xA
track:1 Head:0 Sector:18 Status ==0xA
track:1 Head:1 Sector:5 Status ==0xA
track:1 Head:1 Sector:6 Status ==0xA
track:1 Head:1 Sector:7 Status ==0xA
track:1 Head:1 Sector:8 Status ==0xA
track:1 Head:1 Sector:9 Status ==0xA
track:1 Head:1 Sector:10 Status ==0xA
track:1 Head:1 Sector:11 Status ==0xA
track:1 Head:1 Sector:12 Status ==0xA
track:1 Head:1 Sector:13 Status ==0xA
track:1 Head:1 Sector:14 Status ==0xA
track:1 Head:1 Sector:15 Status ==0xA
track:1 Head:1 Sector:16 Status ==0xA
track:1 Head:1 Sector:17 Status ==0xA
track:1 Head:1 Sector:18 Status ==0xA
track:2 Head:0 Sector:4 Status ==0xA
track:2 Head:0 Sector:5 Status ==0xA
track:14 Head:0 Sector:6 Status ==0xA
Done
Может быть немного сложно понять функции и прерывания, используемые в этой программе для проверки диска на наличие поврежденных секторов, сброса системы диска и т. д., но вам не нужно беспокоиться, мы изучим все эти вещи в BIOS и программировании прерываний. разделы позже в следующих главах.
Обработка файлов в C
Доступ к файлу в C достигается путем связывания потока с файлом. C взаимодействует с файлами, используя новый тип данных, называемый файловым указателем. Этот тип определен в stdio.h и записывается как FILE *. Указатель файла с именем output_file объявляется в операторе вроде
FILE *output_file;
Файловые режимы функции fopen
Ваша программа должна открыть файл, прежде чем она сможет получить к нему доступ. Это делается с помощью функции fopen, которая возвращает нужный указатель файла. Если файл по какой-либо причине не может быть открыт, то будет возвращено значение NULL. Обычно вы будете использовать fopen следующим образом
if ((output_file = fopen("output_file", "w")) == NULL)
fprintf(stderr, "Cannot open %s\n",
"output_file");
fopen принимает два аргумента, оба являются строками, первый — это имя открываемого файла, второй — символ доступа, который обычно является одним из r, a или w и т. д. Файлы могут быть открыты в нескольких режимах, как показано в следующей таблице.
File Modes |
r |
Open a text file for reading. |
w |
Create a text file for writing. If the file exists, it is overwritten. |
a |
Open a text file in append mode. Text is added to the end of the file. |
rb |
Open a binary file for reading. |
wb |
Create a binary file for writing. If the file exists, it is overwritten. |
ab |
Open a binary file in append mode. Data is added to the end of the file. |
r+ |
Open a text file for reading and writing. |
w+ |
Create a text file for reading and writing. If the file exists, it is overwritten. |
a+ |
Open a text file for reading and writing at the end. |
r+b or rb+ |
Open binary file for reading and writing. |
w+b or wb+ |
Create a binary file for reading and writing. If the file exists, it is overwritten. |
a+b or ab+ |
Open a text file for reading and writing at the end. |
Режимы обновления используются с функциями fseek, fsetpos и перемотки. Функция fopen возвращает указатель на файл или NULL в случае ошибки.
В следующем примере файл tarun.txt открывается в режиме только для чтения. Хорошей практикой программирования является проверка существования файла.
if ((in = fopen("tarun.txt", "r")) == NULL)
{
puts("Unable to open the file");
return 0;
}
Закрытие файлов
Файлы закрываются с помощью функции fclose. Синтаксис следующий:
fclose(in);
Чтение файлов
Функция feof используется для проверки конца файла. Функции fgetc, fscanf и fgets используются для чтения данных из файла.
В следующем примере содержимое файла выводится на экран с использованием fgetc для чтения файла посимвольно.
#include <stdio.h>
int main()
{
FILE *in;
int key;
if ((in = fopen("tarun.txt", "r")) == NULL)
{
puts("Unable to open the file");
return 0;
}
while (!feof(in))
{
key = fgetc(in);
/* The last character read is the end of file marker so don't print it */
if (!feof(in))
putchar(key);
}
fclose(in);
return 0;
}
Функцию fscanf можно использовать для чтения различных типов данных из файла, как показано в следующем примере, при условии, что данные в файле имеют формат строки формата, используемый с fscanf.
fscanf(in, "%d/%d/%d", &день, &месяц, &год);
Функция fgets используется для чтения ряда символов из файла. stdin — это стандартный поток входных файлов, а для управления вводом можно использовать функцию fgets.
Запись в файлы
Данные могут быть записаны в файл с помощью fputc и fprintf. В следующем примере функции fgetc и fputc используются для создания копии текстового файла.
#include <stdio.h>
int main()
{
FILE *in, *out;
int key;
if ((in = fopen("tarun.txt", "r")) == NULL)
{
puts("Unable to open the file");
return 0;
}
out = fopen("copy.txt", "w");
while (!feof(in))
{
key = fgetc(in);
if (!feof(in))
fputc(key, out);
}
fclose(in);
fclose(out);
return 0;
}
Функцию fprintf можно использовать для записи отформатированных данных в файл.
fprintf(out, "Date: %02d/%02d/%02d\n",
day, month, year);
Аргументы командной строки с C
Определение ANSI C для объявления функции main():
int main() или int main(int argc, char **argv)
Вторая версия позволяет передавать аргументы из командной строки. Параметр argc является счетчиком аргументов и содержит количество параметров, переданных из командной строки. Параметр argv — это вектор аргумента, который представляет собой массив указателей на строки, представляющие фактически переданные параметры.
В следующем примере можно передать любое количество аргументов из командной строки и распечатать их. argv[0] — это фактическая программа. Программу необходимо запускать из командной строки.
#include <stdio.h>
int main(int argc, char **argv)
{
int counter;
puts("The arguments to the program are:");
for (counter=0; counter<argc; counter++)
puts(argv[counter]);
return 0;
}
If the program name was count.c, it could be called as follows from the command line.
count 3
or
count 7
or
count 192 etc.
В следующем примере процедуры обработки файлов используются для копирования текстового файла в новый файл. Например, аргумент командной строки можно назвать так:
txtcpy один.txt два.txt
#include <stdio.h>
int main(int argc, char **argv)
{
FILE *in, *out;
int key;
if (argc < 3)
{
puts("Usage: txtcpy source destination\n");
puts("The source must be an existing file");
puts("If the destination file exists, it will be
overwritten");
return 0;
}
if ((in = fopen(argv[1], "r")) == NULL)
{
puts("Unable to open the file to be copied");
return 0;
}
if ((out = fopen(argv[2], "w")) == NULL)
{
puts("Unable to open the output file");
return 0;
}
while (!feof(in))
{
key = fgetc(in);
if (!feof(in))
fputc(key, out);
}
fclose(in);
fclose(out);
return 0;
}
Побитовые манипуляторы
На аппаратном уровне данные представлены в виде двоичных чисел. Двоичное представление числа 59 — 111011. Бит 0 — это младший значащий бит, а в данном случае бит 5 — старший значащий бит.
Каждый набор битов рассчитывается как 2 в степени набора битов. Побитовые операторы позволяют вам манипулировать целочисленными переменными на битовом уровне. Ниже показано двоичное представление числа 59.
binary representation of the number 59 |
bit 5 4 3 2 1 0 |
2 power n 32 16 8 4 2 1 |
set 1 1 1 0 1 1 |
С помощью трех битов можно представить числа от 0 до 7. В следующей таблице показаны числа от 0 до 7 в их двоичной форме.
Binary Digits |
000 |
0 |
001 |
1 |
010 |
2 |
011 |
3 |
100 |
4 |
101 |
5 |
110 |
6 |
111 |
7 |
В следующей таблице перечислены побитовые операторы, которые можно использовать для работы с двоичными числами.
Binary Digits |
& |
Bitwise AND |
| |
Bitwise OR |
^ |
Bitwise Exclusive OR |
~ |
Bitwise Complement |
<< |
Bitwise Shift Left |
>> |
Bitwise Shift Right |
Побитовое И
Побитовое И имеет значение True, только если установлены оба бита. В следующем примере показан результат побитового И над числами 23 и 12.
10111 (23)
01100 (12) AND
____________________
00100 (result = 4) |
Вы можете использовать значение маски, чтобы проверить, были ли установлены определенные биты. Если бы мы хотели проверить, установлены ли биты 1 и 3, мы могли бы замаскировать число с помощью 10 (значение битов 1 и 3) и проверить результат по маске.
#include <stdio.h>
int main()
{
int num, mask = 10;
printf("Enter a number: ");
scanf("%d", &num);
if ((num & mask) == mask)
puts("Bits 1 and 3 are set");
else
puts("Bits 1 and 3 are not set");
return 0;
}
Побитовое ИЛИ
Побитовое ИЛИ выполняется, если установлен любой из битов. Ниже показан результат побитового ИЛИ над числами 23 и 12.
10111 (23)
01100 (12) OR
______________________
11111 (result = 31) |
Вы можете использовать маску, чтобы убедиться, что бит или биты были установлены. Следующий пример гарантирует, что бит 2 установлен.
#include <stdio.h>
int main()
{
int num, mask = 4;
printf("Enter a number: ");
scanf("%d", &num);
num |= mask;
printf("After ensuring bit 2 is set: %d\n", num);
return 0;
}
Побитовое исключающее ИЛИ
Побитовое исключающее ИЛИ имеет значение True, если установлен один из битов, но не оба. Ниже показан результат побитового исключающего ИЛИ для чисел 23 и 12.
10111 (23)
01100 (12) Exclusive OR (XOR)
_____________________________
11011 (result = 27) |
Эксклюзивное ИЛИ обладает некоторыми интересными свойствами. Если вы выполняете исключающее ИЛИ число само по себе, оно устанавливается равным нулю, поскольку нули останутся равными нулю, а единицы не могут быть установлены одновременно, поэтому они устанавливаются равными нулю.
В результате этого, если вы выполняете Исключающее ИЛИ число с другим числом, а затем Исключающее ИЛИ результат с другим числом снова, результатом является исходное число. Вы можете попробовать это с числами, использованными в приведенном выше примере.
23 XOR 12 = 27
27 XOR 12 = 23
27 XOR 23 = 12
Эту функцию можно использовать для шифрования. Следующая программа использует ключ шифрования 23, чтобы проиллюстрировать свойство числа, введенного пользователем.
#include <stdio.h>
int main()
{
int num, key = 23;
printf("Enter a number: ");
scanf("%d", &num);
num ^= key;
printf("Exclusive OR with %d gives %d\n", key, num);
num ^= key;
printf("Exclusive OR with %d gives %d\n", key, num);
return 0;
}
Побитовый комплимент
Побитовый комплимент — это оператор комплимента, который включает или выключает бит. Если он равен 1, он будет установлен в 0, если он равен 0, он будет установлен в 1.
#include <stdio.h>
int main()
{
int num = 0xFFFF;
printf("The compliment of %X is %X\n", num, ~num);
return 0;
}
Побитовый сдвиг влево
Оператор побитового сдвига влево сдвигает число влево. Старшие значащие биты теряются при перемещении числа влево, а освободившиеся младшие значащие биты равны нулю. Ниже показано двоичное представление числа 43.
0101011 (десятичное число 43)
Сдвигая биты влево, мы теряем старший бит (в данном случае ноль), и число дополняется нулем в младшем бите. Ниже приведено полученное число.
1010110 (десятичное число 86)
Побитовый сдвиг вправо
Оператор побитового сдвига вправо сдвигает число вправо. В освободившиеся старшие биты вводится ноль, а освободившиеся младшие биты теряются. Ниже показано двоичное представление числа 43.
0101011 (десятичное число 43)
Сдвигая биты вправо, мы теряем младший значащий бит (в данном случае единицу), а число дополняется нулем в старшем значащем бите. Ниже приведено полученное число.
0010101 (десятичное число 21)
Следующая программа использует побитовый сдвиг вправо и побитовое И для отображения числа в виде 16-битного двоичного числа. Число последовательно сдвигается вправо от 16 до нуля и объединяется побитовым И с 1, чтобы увидеть, установлен ли бит. Альтернативным методом может быть использование последовательных масок с оператором побитового ИЛИ.
#include <stdio.h>
int main()
{
int counter, num;
printf("Enter a number: ");
scanf("%d", &num);
printf("%d is binary: ", num);
for (counter=15; counter>=0; counter--)
printf("%d", (num >> counter) & 1);
putchar('\n');
return 0;
}
Функции для двоичных и десятичных преобразований
Следующие две функции предназначены для преобразования двоичных данных в десятичные и десятичных в двоичные. Приведенная ниже функция для преобразования десятичного числа в соответствующее двоичное число поддерживает до 32-битных двоичных чисел. Вы можете использовать эту или предоставленную ранее программу для преобразования в соответствии с вашими требованиями.
Функция преобразования десятичных чисел в двоичные:
void Decimal_to_Binary(void)
{
int input =0;
int i;
int count = 0;
int binary [32]; /* 32 Bit, MAXIMUM 32 elements */
printf ("Enter Decimal number to convert into
Binary :");
scanf ("%d", &input);
do
{
i = input%2; /* MOD 2 to get 1 or a 0*/
binary[count] = i; /* Load Elements into the Binary Array */
input = input/2; /* Divide input by 2 to decrement via binary */
count++; /* Count how many elements are needed*/
}while (input > 0);
/* Reverse and output binary digits */
printf ("Binary representation is: ");
do
{
printf ("%d", binary[count - 1]);
count--;
} while (count > 0);
printf ("\n");
}
Функция преобразования двоичного кода в десятичный:
Следующая функция предназначена для преобразования любого двоичного числа в соответствующее ему десятичное число:
void Binary_to_Decimal(void)
{
char binaryhold[512];
char *binary;
int i=0;
int dec = 0;
int z;
printf ("Please enter the Binary Digits.\n");
printf ("Binary digits are either 0 or 1 Only ");
printf ("Binary Entry : ");
binary = gets(binaryhold);
i=strlen(binary);
for (z=0; z<i; ++z)
{
dec=dec*2+(binary[z]=='1'? 1:0); /* if Binary[z] is
equal to 1,
then 1 else 0 */
}
printf ("\n");
printf ("Decimal value of %s is %d",
binary, dec);
printf ("\n");
}
Отладка и тестирование
Синтаксические ошибки
Синтаксис относится к грамматике, структуре и порядку элементов в выражении. Синтаксическая ошибка возникает, когда мы нарушаем правила, например забываем поставить в конце оператора точку с запятой. Когда вы скомпилируете программу, компилятор создаст список всех синтаксических ошибок, с которыми он может столкнуться.
Хороший компилятор выдаст список с описанием ошибки и может предоставить возможное решение. Исправление ошибок может привести к отображению дополнительных ошибок при перекомпиляции. Причина этого в том, что предыдущие ошибки изменили структуру программы, что означает, что дальнейшие ошибки были подавлены во время исходной компиляции.
Точно так же одна ошибка может привести к нескольким ошибкам. Попробуйте поставить точку с запятой в конце основной функции программы, которая компилируется и работает правильно. Когда вы перекомпилируете его, вы получите огромный список ошибок, а это всего лишь неуместная точка с запятой.
Помимо синтаксических ошибок, компиляторы также могут выдавать предупреждения. Предупреждение не является ошибкой, но может вызвать проблемы во время выполнения вашей программы. Например, присвоение числа с плавающей запятой двойной точности числу с плавающей запятой одинарной точности может привести к потере точности. Это не синтаксическая ошибка, но может привести к проблемам. В этом конкретном примере вы можете показать намерение, приведя переменную к соответствующему типу данных.
Рассмотрим следующий пример, где x — число с плавающей запятой одинарной точности, а y — число с плавающей запятой двойной точности. y явно приводится к типу с плавающей запятой во время присваивания, что устраняет любые предупреждения компилятора.
x = (float)y;
Логические ошибки
Логические ошибки возникают, когда есть ошибка в логике. Например, вы можете проверить, что число меньше 4 и больше 8. Это никогда не может быть правдой, но если оно синтаксически правильное, программа будет успешно скомпилирована. Рассмотрим следующий пример:
if (x < 4 && x > 8)
puts("Will never happen!");
Синтаксис правильный, поэтому программа скомпилируется, но инструкция puts никогда не будет напечатана, так как значение x не может быть меньше четырех и больше восьми одновременно.
Большинство логических ошибок обнаруживаются при первоначальном тестировании программы. Когда он ведет себя не так, как вы ожидали, вы более внимательно изучаете логические операторы и исправляете их. Это верно только для очевидных логических ошибок. Чем больше программа, тем больше путей будет через нее, тем сложнее будет проверить, что программа ведет себя так, как ожидается.
Тестирование
В процессе разработки программного обеспечения ошибки могут возникать на любом этапе разработки. Это связано с тем, что методы проверки на более ранних этапах разработки программного обеспечения выполняются вручную. Следовательно, код, разработанный во время кодирования, вероятно, будет содержать некоторые ошибки в требованиях и проектные ошибки в дополнение к ошибкам, допущенным во время кодирования. Во время тестирования тестируемая программа выполняется с набором тестовых случаев, а выходные данные программы для тестовых случаев оцениваются, чтобы определить, ожидается ли программирование.
Таким образом, тестирование — это процесс анализа элемента программного обеспечения для выявления различий между существующими и требуемыми условиями (т. е. ошибок) и для оценки характеристик элементов программного обеспечения. Итак, тестирование — это процесс анализа программы с целью поиска ошибок.
Некоторые принципы тестирования
- Тестирование не может показать отсутствие дефектов, только их наличие.
- Чем раньше допущена ошибка, тем она дороже.
- Чем позже обнаруживается ошибка, тем она дороже.
Теперь давайте обсудим некоторые методы тестирования:
Тестирование белого ящика
Тестирование методом белого ящика — это метод, при котором все пути прохождения программы проверяются со всеми возможными значениями. Этот подход требует некоторых знаний о том, как должна вести себя программа. Например, если ваша программа принимает целочисленное значение от 1 до 50, тест белого ящика проверит программу со всеми 50 значениями, чтобы убедиться, что она верна для каждого, а затем проверит все другие возможные значения, которые может принимать целое число, и проверит, что он вел себя так, как ожидалось. Учитывая количество элементов данных, которые может иметь типичная программа, возможные перестановки делают тестирование белого ящика чрезвычайно сложным для больших программ.
Тестирование методом «белого ящика» может быть применено к важным для безопасности функциям большой программы, а большая часть остальных тестируется с использованием тестирования «черного ящика», описанного ниже. Из-за большого количества перестановок тестирование методом «белого ящика» обычно выполняется с использованием тестового набора, в котором диапазоны значений быстро передаются в программу через специальную программу, регистрируя исключения ожидаемого поведения. Тестирование «белого ящика» иногда называют структурным, прозрачным или открытым тестированием.
Тестирование черного ящика
Тестирование черного ящика аналогично тестированию белого ящика, за исключением того, что тестируются не все возможные значения, а тестируются выбранные значения. В тестах этого типа тестировщик знает входные данные и ожидаемые результаты, но не обязательно знает, как программа их получила. Тестирование черного ящика иногда называют функциональным тестированием.
Тестовые наборы для тестирования черного ящика обычно разрабатываются, как только спецификация программы завершена. Тестовые примеры основаны на классах эквивалентности.
Классы эквивалентности
Для каждого входа класс эквивалентности определяет допустимое и недопустимое состояния. Обычно при определении классов эквивалентности следует планировать три сценария.
Если во входных данных указан диапазон или конкретное значение, будет определено одно допустимое состояние и два недопустимых состояния. Например, если число должно находиться в диапазоне от 1 до 20, допустимое состояние находится в диапазоне от 1 до 20, недопустимым будет значение меньше 1 и недопустимым состоянием больше 20.
Если входные данные исключают диапазон или конкретное значение, будет определено два допустимых состояния и одно недопустимое состояние. Например, если число не должно находиться в диапазоне от 1 до 20, допустимые состояния — меньше единицы и больше 20, а недопустимое состояние – от 1 до 20.
Если во входных данных указано логическое значение, будет только два состояния: одно допустимое и одно недействительное.
Анализ граничных значений
Анализ граничных значений рассматривает только значения на границе входных данных. Например, если число находится в диапазоне от 1 до 20, тестовыми примерами могут быть 1, 20, 0 и 21. Идея заключается в том, что если программа работает, как ожидается, с этими значениями, другие значения также будут работать. работают как положено.
В следующей таблице представлен обзор типичных границ, которые вы, возможно, захотите определить.
Testing Ranges |
Input type |
Test Values |
Range |
- x[lower_bound]-1
- x[lower_bound]
- x[upper_bound]
- x[upper_bound]+1
|
Boolean |
|
Разработка плана тестирования
Определите классы эквивалентности и для каждого класса определите границы. Определив границы класса, напишите список допустимых и недопустимых значений на границе и укажите ожидаемое поведение. Затем тестер может запустить программу с граничными значениями и указать, что произошло, когда граничное значение было проверено на соответствие требуемому результату.
Следующее может быть типичным планом тестирования, используемым для проверки введенного возраста, где допустимые значения находятся в диапазоне от 10 до 110.
Equivalence Class |
Valid |
Invalid |
Between 10 and 110 |
> 110 |
|
< 10 |
Определив наш класс эквивалентности, теперь мы можем разработать план тестирования для возраста.
Test Plan |
Value |
State |
Expected Result |
Actual Result |
10 |
Valid |
Continue execution to get name |
|
110 |
Valid |
Continue execution to get name |
|
9 |
Invalid |
Ask for age again |
|
111 |
Invalid |
Ask for age again |
|
«Фактический результат»; столбец оставлен пустым, так как он будет заполнен при тестировании. Если результат соответствует ожидаемому, столбец будет отмечен галочкой. Если нет, следует ввести комментарий, указывающий, что произошло.
Страница изменена: 15/03/2022