Список работ

СИ на 1 курсе

Содержание

Введение

В реферате рассмотрены базовые элементы языка СИ, в том числе, типы данных, операции и операторы СИ, средства ввода и вывода данных, способы организации структур данных, встроенные и пользовательские функции СИ. Все рассматриваемые элементы языка иллюстрируются примерами, которые запускались как СИ++ консоль-приложение Microsoft Visual Studio. Все примеры снабжены заголовочным файлом

#include "stdafx.h"

По умолчанию он содержит следующие определения:

#pragma once
#include "targetver.h"
#include <stdio.h>
#include <tchar.h>

В прил. П1 приведен пример решения типовой для первого курса задачи программирования, а в прил. П2 - перечень задач для самостоятельной проработки. Все эти задачи можно решить, опираясь на имеющиеся в реферате сведения.

1. Типы данных СИ

1.1. Базовые типы данных СИ

СИ имеет 4 базовых типа. Остальные типы данных в СИ образуются из базовых.

char - единичный байт; может содержать один символ (литеру).
int - целое число.
float - число с плавающей точкой одинарной точности.
double - число с плавающей точкой двойной точности.

В базовые типы СИ не входит строковый тип. В СИ отсутствует логический тип данных.
Примеры объявления типов данных рассмотрены ниже.

Замечание. Число байт, отводимых под переменную заданного типа, можно определить при помощи унарной операции sizeof(<тип>). Например, sizeof(int) определяет число байт, необходимое для представления int. Значение sizeof(char) всегда равно 1.

1.2. Квалификаторы

Квалификаторы служат для определения производных типов данных СИ. (Применение квалификаторов не единственный способ формирования типов данных.)

short, long - используются с int, например:

short int v1;
long int v2;

signed, unsigned (со знаком, без знака) применимы к int и char и любому целому типу, например:

signed int; // -32768 - 32767
unsigned int; // 0 - 65535

long double - предназначен для арифметики с плавающей точкой повышенной точности.

1.3. Преобразование типов данных в СИ

В СИ, как и в других языках, выполняются автоматические и явные преобразования типов данных. Преобразования выполняются автоматически, если в выражении присутствуют данные разных типов. Рассмотрение таких преобразований предоставляется для самостоятельной проработки. Для явного преобразования типов используется унарный оператор, называемый приведением. Конструкция вида

(имя-типа) выражение

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

Например, в СИ аргумент библиотечной функции sqrt (извлечение квадратного корня) должен иметь аргумент типа double. Поэтому, если n есть целое число, то при извлечении квадратного корня следует написать

sqrt((double) n).

При этом вырабатывается значение n типа double. Сама же переменная n тип не изменяет.

Замечание. Функции преобразования строка в int, строка в double и ряд других функций преобразования типа данных описаны в файле stdlib.h.

1.4. Истина и ложь в СИ

В СИ нет логического типа данных. Выражение в СИ считается истинным, если его значение отлично от 0. В противном случае значение выражения есть ложь.

Пример. Вывести на печать значения переменных a и b, если а делится на b без остатка.

#include "stdafx.h"
// viod означает, что функция не возвращает результата, поэтому оператор return не нужен
void main() {
 int a = 10, b = 5, c; // Объявление и инициализация переменных
 c = a % b; // Находим остаток от деления 'a' на 'b'
 if(c)
  printf_s("'a' is divided by 'b' with a remainder\n");
 else
  printf_s("a = %2d b = %2d\n", a, b);
}

Замечания:

  1. Текст, следующий за символами //, является комментарием.
  2. Операторы разделяются точкой с запятой.
  3. Все переменные должны быть объявлены.

В приведенном примере использованы следующие операции СИ:

= - операция присваивания;
% - операция определения остатка от деления.

Стандартная функция printf, выполняющая форматный вывод на экран. Последовательность '\n'в функции printf обеспечивает переход на новую строку экрана.

1.5. Пример

Найти суммарную площадь n равнобедренных треугольников, имеющих равное основание a. Длина боковой стороны i-го треугольника равна a + i.

#include "stdafx.h"
#include <math.h> // Математические функции sqrt, pow, ...
void main() {
 int n, i;
 float a, h, s = 0; // Инициализация при объявлении
 n = 20; a = 2.2f; // На одной строке может быть более одного оператора
 for (i = 1; i <= n; i++) {
  h = sqrt((a + i) * (a + i) - a * a / 4); // или h = sqrt(pow(a + i, 2) - pow(a / 2, 2));
  s += a * h / 2; // или s = s + a * h / 2;
 }
 printf_s("Total area s = %7.2f\n", s);
}

В примере наряду с прочими использованы стандартные функции СИ:

sqrt(x) - корень квадратный из x;
pow(a, b) - a в степени b - ab. Ошибка области определения: a = 0 и b <= 0 или a < 0 и b не целое.

2. Операции и операторы СИ

2.1. Операции СИ

2.1.1. Арифметические операции

+ - сложение;
- - вычитание;
* - умножение;
/ - деление;
% - остаток от деления.

Пример.

int a = 10, b = 4, c;
c = a % b; // Результат c = 2

Замечание. Для возведения a в степень b можно использовать функцию pow(a, b) библиотеки math.h.

2.1.2. Логические операции

Рассмотрим три логические операции СИ: отрицание, логическое или и логическое и. Для их обозначения в СИ используются знаки:

! - отрицание;
|| - логическое ИЛИ;
&& - логическое И.

Пример. Вывести на экран значения переменных a, b и с, если, во-первых, a < b или c < b и если, во-вторых, b = 0.

#include "stdafx.h"
void main(){
 int a = -3, b = 0, c = 2; // Объявление и инициализация переменных
 if((a < b || c < b) && b == 0) // Для приведенных данных условие выполнено
  printf_s("a = %2d b = %2d c = %2d\n", a, b, c);
 else
  printf_s("Condition is not fulfilled\n");
}

Приоритет операции и (&&) выше приоритета операции или (||), поэтому выражение (a < b || c < b) необходимо заключить в скобки. Запись

(a < b || c < b && b == 0)

эквивалентна следующей:

(a < b || (c < b && b == 0)).

2.1.3. Операции отношения

== - равно;
!= - не равно;
< - меньше;
<= - меньше равно;
> - больше;
>= - больше равно.

2.1.4. Операция присваивания

= - операция присваивания СИ (например, a = b;).

Поскольку присваивание является операцией, то допустимо множественное присваивание:

a = b = c = 2;

Пусть v - простая переменная; en - выражение, результатом которого является величина, тип которой совпадает с типом переменной v. Если операция присваивания имеет вид

v = v <арифметическая операция> en

то можно использовать более компактную форму для записи такой операции:

v <арифметическая операция>= en

Пример.

i = i + 2;
// или
i += 2;
k = k * a / c;
// или
k *= a / c;

2.1.5. Операции инкремента и декремента

Вызывают соответственно увеличение и уменьшение значения выражения на 1.

++ - операция инкремента;
-- - операция декремента.

Например, вместо i = i +1 можно записать i++ (суффикс) или ++i (префикс), а вместо i = i - 1 можно записать i-- или --i.

Пример.

int i = 3, j, k;
j = i++; // Результат i = 4; j = 3
k = --j; // Результат j = 2; k = 2

2.1.6. Арифметическое "Если"

Пусть EXPL - некоторое выражение, результатом которого является истина или ложь; v - переменная; expl, exp2 - выражения, типы результатов которых совпадают с типом переменной v.

v = EXPL ? exp1 : exp2; // если EXPL есть истина, то v = exp1, иначе v = exp2.

Пример.

z = (a > b) ? a : b; // если a > b, то z = a, иначе z = b
y = (x < 0)? -x : x; // Выражение для y = |x|

2.1.7. Приоритет операций СИ

При вычислении значения выражения операции выполняются слева направо порядке, определяемом их приоритетом. Приведем, разделяя пробелом, некоторые операции СИ, расположив их в порядке убывания приоритета:

2.2. Операторы СИ

Введем предварительно понятия идентификатора и выражения.

Идентификатор - это последовательность букв, цифр или подчеркивания, начинающаяся с буквы или подчеркивания, например, st, _step.

В СИ имеет значение регистр букв идентификатора: идентификаторы st, ST, St и sT есть не одно и то же.

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

Оператор выражения представляет собой выражение, заканчивающееся точкой с запятой (;), например:

a = b + c;

Выражение рассматривается как истина, если результат его вычисления не равен нулю, или как ложь - в противном случае.

Оператор с меткой - это оператор выражения, перед которым стоит идентификатор метки и двоеточие (:), например:

label1: a = b + c;

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

Пустой оператор - это просто точка с запятой (;). Используется, когда после метки или операторов while, do или for не должно следовать никакого оператора.

Следование - это нуль или более операторов, заключенных в фигурные скобки. Фигурные скобки могут быть опущены, если следование содержит нуль или один оператор.

Пример. В цикле ищется сумма отрицательных элементов целочисленного массива a. Цикл прерывается при обнаружении в массиве отрицательного элемента.

s = 0;
for(i = 0; i < n; i++)
 if(a[i] == 0)
  { fl = 1; break; } // Следование двух операторов
 else if(a[i] < 0)
  s += a[i];

Оператор объявления - содержит после имени типа идентификаторы переменных и констант. При объявлении возможна инициализация переменных.

int global, step;
char c;
char esc = '\\'; // Объявление и инициализация переменной
char line[80];

Для объявления константы используется квалификатор const:

const double e = 2.718281828845905; // Объявление костанты

Иной способ задание констансты - это употребление директивы #define.

Оператор break - прерывает выполнение оператора while, do, for или switch. Выполнение передается на оператор, следующий за прерванным циклом или переключателем switch.

Пример. Найти первое отрицательное число в массиве a[9].

int i;
float a[] = {1.1, -2.2, 3.3, -4.4, 5.5, -6.6, 7.7, -8.8, 9.9};
for(i = 0; i < 9; i++) if(a[i] < 0) break;
if(i < 9) // Сюда передается управление после выполнения оператора break
 printf_s("First negative number in array: %7.3f\n", a[i]);
else
 printf_s("No negative numbers found\n");

Оператор continue - передает управление на начало оператора (цикла) while, do или for.

Пример. Найти сумму положительных чисел массива a[9].

int s = 0, i, a[] = {1, -2, 3, -4, 5, -6, 7, -8, 9};
for(i = 0; i < 9; i++) {
 if(a[i] <= 0) continue; // Переход к следующему элементу массива a
 s += a[i];
}
printf_s("Array positive elements sum: s = %6d\n", s);

Возможны и иные варианты программирования последней задачи, например:

int s, i, a[] = {1, -2, 3, -4, 5, -6, 7, -8, 9};
for(s = i = 0; i < 9; i++) s += a[i ]> 0 ? a[i] : 0;
printf_s("Array positive elements sum: s = %6d\n", s);

Оператор return <выражение> прерывает выполнение текущей функции; результат функции равен значению выражения оператора return.
Выражение опускается, если функция имеет тип void.

Оператор goto <метка> передает управление оператору, помеченному меткой.

Пример. Найти первое отрицательное число в массиве a[9].

int i, a[] = {1, -2, 3, -4, 5, -6, 7, -8, 9};
for(i = 0; i < 9; i++) if(a[i] < 0) goto L1;
L1: // управление передается сюда
if(i < 9)
 printf_s("First negative number in array: %5d\n", a[i]);
else
 printf_s("No negative numbers found\n");

Условный оператор, или "ветвление"

if(выражение) {
 следование_1
}
else {
 следование_2
}

Оператор вычисляет выражение и, если оно истинно (не равно нулю), то выполняется следование_1, в противном случае выполняется следование_2. Конструкция else и следование_2 могут быть опущены.

Оператор switch - "переключатель"

switch(выражение) {
case константное выражение_1:
 следование_1
case константное выражение_2:
 следование_2
...
case константное выражение_N:
 следование_N
default:
 следование_N+1
}

Определяется результат выражения и выполняются операторы, которые следуют за константным выражением, имеющим то же значение, что и результат выражения. Если такое константное выражение отсутствует, то выполняется стоящее за default следование_N+1.
Элементами константного выражения могут быть только константы, определенные директивой #define. Значение константного выражения вычисляется при компиляции программы. Для прерывания оператора switch используется, как правило, оператор break (можно использовать, например, goto или return).
Допустимые типы выражения и константного выражения: int и char. Типы выражения и константного выражения должны совпадать. Совпадений константных выражений не допускается.

Пример. Имитация работы калькулятора.

switch(c) {
case '+': n = a + b; break;
case '-': n = a - b; break;
case '*': n = a * b; break;
case '/': n = a / b; break;
default: printf_s("Invalid operation\n");
}
// Сюда передается управление после выполнения любого оператора break

Программа имитации работы калькулятора с определеним констант посредством директивы #define:

#include "stdafx.h"
#include <conio.h> // Для >_getch
#define pls '+' // Объявляем define-константу
#define mns '-'
#define mltpl '*'
#define dvsn '/'
void main() {
 float n, a = 2.3, b = 4.6;
 char c;
 // Цикл прерывается после ввода символа, отличного от символа арифметической операции
 while(1) {
  puts("\n Enter operation (+, -, *, / or any to quit): ");
  c = _getch(); // Ввод символа арифметической операции
  switch(c) {
  case pls: n = a + b; break;
  case mns: n = a - b; break;
  case mltpl: n = a * b; break;
  case dvsn: n = a / b; break;
  default:
   printf_s("Invalid operation\n");
   return;
  }
  printf_s("n = %6.3f\n", n);
 }
}

Оператор цикла "пока"

while(выражение) {
 следование
}

Следование выполняется до тех пор, пока истинно выражение. Прерывание оператора цикла while может быть выполнено операторами break или return (последний обеспечивает также и выход из текущей функции). Также цикл while может быть прерван функцией exit, которая одновременно прерывает и выполнение программы. Пример цикла while рассмотрен ниже.

Оператор цикла "до"

do {
 следование
}
while(выражение)

Следование выполняется до тех пор, пока истинно выражение. Прерывание оператора цикла do может быть выполнено теми же операторами, что и цикла while. Пример цикла do рассмотрен ниже.

Оператор цикла for (цикл "с параметром")

for (следование_1; выражение_1; выражение_2) {
 следование_2
}

Следование_2 в цикле for выполняется до тех пор, пока истинно выражение_1.
Следование_1, как правило, выполняет инициализацию параметра цикла.
Выражение_1, как правило, контролирует верхнюю или нижнюю границы параметра цикла.
Выражение_2, как правило, определяет закон изменения параметра цикла.
Оператор_1 и оба выражения цикла for могут быть опущенными. Когда выражение_1 опущено, его значение считается истинным.
Прерывание оператора цикла for может быть выполнено теми же операторами, что и цикла while.

Пример. Выполнить табуляцию функции y = x * sinx на отрезке [0, π] с шагом π / 20.

#include "stdafx.h"
#include <math.h> // Для sin(x)
void main() {
 double pi, x, dx;
 pi = 2.0 * asin(1.0);
 dx = pi / 20;
 printf_s("y = x * sinx\n x y\n");
 for(x = 0; x <= pi; x += dx)
  printf_s("%7.3f %7.3f\n", x, x*sin(x));
}

Пример. Если в одномерном массиве нет нулевых элементов, то найти сумму его отрицательных элементов. Ввод элементов массива выполнить с клавиатуры.
Замечание. При вводе с клавиатуры разделителями являются символы пробела, табуляции или конца строки, например, при вводе 5-и элементов можно задать строку -2 -2 1 3 5. Допустим также ввод данных в столбик. В общем случае расположение данных произвольно: часть данных может быть расположена в столбик, часть - в строчку.

#include "stdafx.h"
#include <conio.h>
void main() {
 int n, i, fl = 1; // Инициализируем fl значением истина
 float a[120], s = 0; // Объявление массива и инициализация переменной s
 printf_s("Array length: ");
 scanf_s("%d", &n);
 printf_s("\nArray elements:\n");
 for(i = 0; i < n; i++) scanf_s("%f", &a[i]);
 // Контрольный вывод
 for(i = 0; i < n; i++) printf_s("%7.2f", a[i]);
 //
 // Цикл "с параметром"
 for(i = 0; i < n; i++)
  if(a[i] == 0) // Конструкция if - else - if
   {fl = 0; break;}
  else if(a[i] < 0)
   s += a[i]; // или s = s + a[i];
 if(!fl)
  printf_s("\nZero is found");
 else
  printf_s("\ns = %7.2f\n", s);
 //
 // Тот же результат, но с циклом "пока"
 s = 0.0;
 i = 0;
 while(i < n && fl) {
  if(a[i] == 0)
   fl = 0;
  else if(a[i] < 0)
   s += a[i];
  i++;
 }
 if(!fl)
  printf_s("\nZero is found");
 else
  printf_s("s = %7.2f\n",s);
 //
 // Тот же результат, но с циклом "до"
 s = 0.0;
 i = 0;
 do {
  if(a[i] == 0)
   fl = 0;
  else if(a[i] < 0)
   s += a[i];
  i++;
 }
 while(i < n && fl);
 if(!fl)
  printf_s("\nZero is found\n");
 else
  printf_s("s = %7.2f\n",s);
}

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

3. Указатели

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

Операторы, применяемые при работе с указателями:

& - унарный оператор определение адреса переменной;
* - унарный оператор раскрытия ссылки (позволяет получить значение по адресу, хранимому указателем).

Указатели объявляются при помощи символа *:

int j,*pj = &j; // Объявление указателя pj на int и его инициализация
int *ppj = &pj; // Объявление указателя на указатель на int
float (*pf)(int); // Указатель на функцию, принимающую параметр типа int и возвращающую значение типа float

Пример.

#include "stdafx.h"
void main() {
 int *p; // Объявляем указатель на int
 int a = 1, y, z[5] = {1, 2, 5, 8, 3};
 p = &a; // p содержит адрес переменной a: p указывает на a
 y = *p; // То же, что и y = a; тТеперь y = 1
 *p = 0; // Меняем значение по адресу p; в результате получим, что с = 0, однако y = 1
 printf_s("a = %3d, y = %3d\n", a, y); // Результат: с = 0, y = 1
 p = &z[2]; // Теперь p содержит адрес элемента z[2]: p указывает на z[2]
 y = *p; // В результате y = z[2] = 5; значение a не меняется (p теряет связь с a)
 printf_s("a = %3d, y = %3d\n", a, y); // Результат: с = 0, y = 5
}

При работе с функциями в СИ следует помнить, что аргументы функции, кроме указателей, передаются в функцию по значению. Передача аргумента по значению означает, что в функции образуется копия аргумента и преобразование копии не вызовет изменение самого аргумента. Чтобы передать изменения, необходимо в качестве аргумента функции в СИ использовать указатель.
Указателями в СИ являются имена массивов и строк. Последние в СИ являются одномерными массивами. Имя массива (строки) указывает на первый элемент массива (строки). Индекс первого элемента массива (строки) СИ равен 0.

4. Ввод-вывод в СИ

4.1. Ввод-вывод символов

Чтение введенного с клавиатуры символа можно выполнить функцией _getch или getchar. Первая функция объявлена в заголовочном файле conio.h, а вторая - в файле stdio.h.
Функция _getch возвращает код символа сразу после его ввода; пример употребления _getch см. выше.
Функция getchar после нажатия на Enter читает набранную последовательность символов.
Вывод символа на экран можно выполнить функциями _putch и putchar, принимающими код символа - переменную типа int. Первая функция объявлена в заголовочном файле conio.h, а вторая - в файле stdio.h.

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

#include "stdafx.h"
void main() {
 int c;
 // Ввод с клавиатуры; в условии вместо '\n' можно записать (int) '\n'
 while((c = getchar()) != '\n') putchar(c);
 // Введем 1a2b3c далее нажмем на Enter ('\n' - символ конца строки)
 // В цикле будет напечатано: 1a2b3c
 printf_s("\nDone\n");
}

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

while((c = getchar()) != '\n')

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

c = getchar()

вычисляется со значением, равным с.

4.2. Форматный вывод

Функция printf_s отображает выводимые данные на консоли. Определена следующим образом:

int printf_s(const char *_Formart, ...),

где char *_Formart - формат вывода (управляющая строка), последующие параметры - это выводимые на консоль значения.
Формат вывода, заключенная в двойные кавычки строка, в общем случае содержит:

Текст (обычные символы) формата вывода выводится без преобразований.
СП задает размер поля вывода, точность и способ вывода в заданном поле. Каждая СП вызывает преобразование и печать очередного аргумента функции printf_s. СП начинается символом % и заканчивается символом спецификатором. Между символом % и символом спецификатором располагается описание поля вывода и описание способа вывода.
Ниже приведены некоторые преобразования функции printf_s (приняты обозначения: w - ширина поля; n - точность; необязательные элементы СП заключены в таблице в квадратные скобки).

СпецификаторТипСП аргументаВид печати
d, iint%[w]d(i)Целое число
cint%[w]cОдиночный символ
schar *%[w]sСтрока символов
fdouble%[w].[n]fВещественное число
edouble%[w].[n]eЭкспоненциальное представление вещественного числа

Пример. Вывести на разных строках значения вещественных переменных x = -2.43 и y = 9.657.

float x = -2.43f, y = 9.567f;
printf_s(" x = %8.3f; \n y = %7.3f \n", x, y); // Используем \n для перехода на следующую строку
/* Результат:
x = -2.430;
y = 9.657
*/
printf_s(" x = %9.3e; \n y = %11.3e \n ",x,y);
/* Результат:
x = -2.430e+000;
y = 9.567e+000
*/

4.3. Форматный ввод

Форматный ввод обеспечивается в СИ функцией scanf_s которая выполняет те же преобразования, что и функция printf_s, но в обратном порядке. Описание функции приведено в файле stdio.h:

int scanf_s(const char *_Formart, ...)

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

СпецификаторТипСП аргументаВид печати
d, iint%dЦелое число
e или ffloat%e или fВещественное число; обязательно присутствие десятичной точки и/или экспоненциальной части

Пример.

#include "stdafx.h"
void main() {
 int a;
 float x, y;
 // Получить в результате ввода следующие значения:
 // a = 7
 // x = -5.43
 // y = 28.35e12
 //
 // Вводим с клавиатуры:
 // 7 -5.43 28.35e12
 scanf_s("%d%f%e", &a, &x, &y);
 // Печатаем введенные данные:
 printf_s("a = %2d, x = %6.2f, y = %10.2e\n", a, x, y);
 // Напечатает:
 // a = 7, x = -5.43, y = 2.83e+013
}

Примеры ввода-вывода символа, строки, одномерного и двумерного массива рассмотрены в других разделах.

4.4. Организация доступа к текстовому файлу

При работе с файлом можно придерживаться следующей схемы:

Указатель файла должен быть объявлен декларацией

FILE *<имя файлового указателя>; (например, *fp - сокращение от file pointer).

Указатель на файл получаем в результате вызова функции fopen_s, например:

fopen_s(&fp, name, mode);

name - строка, содержащая имя файла;
mode - строка, определяющая режим использования файла, например:
"r" - чтение файла (read); файл должен существовать;
"w" - запись в файл (write); файл создается при выполнении функции записи в файл;
"a" - добавление (append) в конец уже существующего файла.

Например:

fopen_s(&fp, "c:\\a.txt", "r"); // Обратите внимание на число слэшей (символов '\') в определении имени файла

или

char fn[30], md;
fn = "c:\\a.txt";
md = "r";
fopen_s(&fp, fn, md);

При наличии ошибки при открытии файла функция fopen_s заполняет fp значением NULL. Возможна более точная идентификация ошибки.

Файл закрывается функцией fclose, например:

fclose(fp);

4.5. Форматный ввод-вывод при работе с текстовым файлом

Форматный ввод данных выполняется функцией fscanf_s:

int fscanf_s(FILE *_File, const char *_Format, ...)

Форматный вывод данных в файл функцией fprintf_s:

int fprintf_s(FILE *_File, const char *_Format, ...).

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

Пример. Обеспечить ввод целочисленного массива из файла a.txt.

#include "stdafx.h"
void main() {
 int n, nf = 0, i, a[120]; // nf - число введенных из файла элементов
 FILE *fp;
 fopen_s(&fp, "a.txt", "r"); // Открываем файл a.txt для чтения
 // Пусть файл a.txt содержит следующие данные:
 // 1 3 -7 4 9 78 22
 if(fp == NULL) {
  printf_s("Open failed\n");
  return;
 }
 printf_s("Enter number elements to scan: ");
 scanf_s("%d", &n);
 // В общем случае следует при вводе следует проверять два условия: i < n && !feof(fp)
 for(i = 0; i < n && !feof(fp); nf++, i++)
  fscanf_s(fp, "%d", &a[i]); // Передаем адрес элемента массива
 fclose(fp);
 // Контрольный вывод введенного массива
 printf_s("nf = %3d\n", nf);
 for(i = 0; i < nf; i++) printf_s("%6d", a[i]); // Размер поля вывода - 6 символов
 printf_s("\n");
}

При вводе разделителями являются символы пробела, табуляции или конца строки. Следовательно, данные при вводе можно набивать и в строчку, и в столбик; часть данных может находиться в одной строке, а часть в другой.
В примере вместо оператора return для прекращения вычислений можно употребить функцию exit (описана в stdlib.h). Эта функция возвращает в среду, из которой была запущена программа, код завершения, передаваемый в качестве параметра функции. Переданный код может быть обработан средствами среды.

5. Функции и указатели на функции

5.1. Функции

В СИ определяются только функции (в Паскале, например, могут быть определены и процедуры). Функции разделяются на стандартные и пользовательские. Стандартные функции хранятся в в стандартных библиотеках СИ и описываются в заголовочных файлах stdio.h, string.h, math.h, stdlib.h и других. Пользовательские функции создаются программистом. Программа может состоять из отдельных функций, которые могут располагаться в одном или нескольких файлах.

Функция может иметь следующий вид:

[тип результата или void] имя_функции([<декларация аргументов>]) {
 раздел описаний переменных
 тело функции
 [return выражение;]
}

Если "тип результата" и void опущены, то по умолчанию функция имеет тип int. Если функция не возвращает результата, то оператор return выражение можно опустить, а функцию определить как void (тип функции не определен).
В СИ запрещается определять функции внутри других функций.
Связь между функциями осуществляется через аргументы, возвращаемые значения и внешние переменные.
Вызов функции осуществляется по имени. Для получения доступа к функции необходимо описать ее прототип.
Описание прототипа имеет слендующий вид:

[тип результата или void] имя функции([<описание типов аргументов>]).

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

Пример. Выполнить копирование строки sta в строку stb.

Вариант, когда нужно описание прототипа.

#include "stdafx.h"
void copy(char *, char *); // Описание прототипа функции copy
void main() {
 char sta[]="abcd", stb[5];
 copy(sta, stb); // Копируем строку sta в строку stb
 printf_s("stb = %s\n",stb);
}
// Копирует строку sta в строку stb
void copy(char sta[], char stb[]) {
 int i = 0;
 while((stb[i] = sta[i]) != '\0') i++;
}

Вариант, когда описание прототипа не нужно.

#include "stdafx.h"
void copy(char sta[], char stb[]) {
 int i = 0;
 while((stb[i] = sta[i]) != '\0') i++;
}
void main() {
 char sta[]="abcd", stb[5];
 copy(sta,stb);
 printf_s("stb = %s\n", stb);
}

Пример. В одномерном целочисленном массиве a найти минимальное значение, принадлежащее отрезку [cd].

#include "stdafx.h"
int fmin(int *, int, int, int); // Описание прототипа функции fmin
void main() {
 int a[120] = {5, 22, 33, 4, 11, 6, 7, 8, 9, 10, -11, 12};
 int c = 2, d = -9, n = 12, imin;
 // Ищем индекс минимального элемента массива на отрезке [c, d]
 // Если на заданном отрезке нет элементов массива, функция fmin возвращает -1
 imin = fmin(a, c, d, n);
 if(imin == -1)
  printf_s("No elements of array a are found in [c, d]\n");
 else
  printf_s("min = %3d\n", a[imin]);
}
int fmin(int a[], int c, int d, int n) {
 int i, ai, imin, min;
 min = d + 1;
 imin = -1;
 for(i = 0; i < n; i++) {
  ai = a[i];
  if(ai < c || ai > d) continue;
  if(ai < min) {
   min = ai;
   imin = i;
  }
 }
 return imin;
}

5.2. Указатели на функции

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

Пример. Написать функцию root поиска методом простых итераций корня уравнения

x = f(x)

на отрезке [a, b] с заданной точностью eps.
Далее, используя функцию root, найти на отрезке [0, 3] с точностью eps = 0.0001 корни следующих уравнений:

Решение уравнений x = f(x) методом простых итераций

Алгоритм метода простых итераций.

  1. Задать начальное приближение x0, например положить x0 = (a + b) / 2.
  2. Положить x = f(x0).
  3. Если |x - x0| > eps, то выполнить п. 4, иначе выполнить п. 5.
  4. x0 = x; x = f(x0); перейти к п. 3.
  5. Принять в качестве решения последнее значение переменной x.

Метод простых итераций иллюстрирует рис. 5.1.

Поиск решения уравнений x = f(x) методом простых итераций

Рис. 5.1. Графическая иллюстрация метода простых итераций

Условия сходимости метода простых итераций: |f '(x)| < 1 и f '(x) < 0.
Текст программы нахождения корней заданных функций (число итераций ограничено).

#include "stdafx.h"
#include <math.h> // Для математических функций
#include <stdlib.h> // Для функции exit
// Функции, возвращающие правые части заданных уравнений
double fx1(double x) { return 1.0 / (1.2 * tan(x) + sqrt(x + 1.0)); }
double fx2(double x) { return (exp(-x) - sqrt(exp(x)) + 3.7) / 3.0; }
// Ищет корень функции fx(x)
// double (*fx)(double) описывает указатель на функцию типа double с одним аргументом типа double
double root(double (*fx)(double), double a, double b, double eps) {
 int i, n = 100; // Предельное число итераций
 double x, x0 = (a + b) / 2.0;
 x = (*fx)(x0);
 i = 0; // Текущее число итераций
 while(fabs(x - x0) > eps && i <= n) {
  i++;
  x0 = x;
  x = (*fx)(x0);
 }
 if(i > n) {
  printf_s("To many iterations done\n");
  exit(1); // Прекращаем вычисления
 }
 else
  return x;
}
void main() {
 // p - указатель на функции, принимающую double и возвращающую double
 double (*p)(double) = fx1; // Установим указатель p на функцию fx1
 double r1, r2;
 r1 = root(*p, 0.0, 3.0, 1.0e-4);
 p = fx2; // Установим теперь указатель p на функцию fx2
 r2 = root(*p, 0.0, 3.0, 1.0e-4);
 printf_s("Function fx1 root: r1 = %7.4f\n", r1);
 printf_s("Function fx2 root: r2 = %7.4f\n", r2);
}

Иной вариант обращения к функции root:

void main() {
 double r1, r2;
 r1=root(*fx1, 0.0, 3.0, 1.0e-4);
 r2 = root(*fx2, 0.0, 3.0, 1.0e-4);
 printf_s("Function fx1 root: r1 = %7.4f\n", r1);
 printf_s("Function fx2 root: r2 = %7.4f\n", r2);
}

В примере использованы математические функции tan(x) - tgx; exp(x) - x; fabs(x) - модуль x (|x|). Функции описаны в math.h.

6. Массивы в СИ

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

6.1. Декларация одномерного массива

Декларация целочисленного одномерного массива a из 10 элементов

int a[10];

Декларация и инициализация одномерного массива

int a[10] = {1, 2, 3, 4, 5}; // Остальные элементы массива равны нулю

или

int a[] = {1, 2, -5, 9, 14, 6, 7, 8, 9, 10}; // Эта декларация определяет массив из десяти элементов

Приведенные декларации определяют массив из 10 объектов (элементов) с именами a[0], a[1], ..., a[9].
Запись a[i] отсылает нас к i-му элементу массива a.

6.2. Одномерный массив и указатели

Доступ к элементам массива можно обеспечить при помощи указателей.
Пусть pa есть указатель на int и определен так:

int *pa;

тогда в результате присваивания

pa = &a[0];

pa будет указывать на нулевой элемент массива a, иначе говоря, pa будет содержать адрес элемента a[0].

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

pa = a;

Присваивание

pa = &a[i];

размещает указатель на i-й элемент массива a. Тот же эффект имеет выполнение оператора

pa = a + i;

Это обусловлено тем, что имя массива в СИ есть указатель на начальный элемент массива.
Пусть pa указывает на i-й элемент массива. Тогда операторы

x = *pa;

и

x = a[i];

будут эквивалентны: в х будет установлено значение элемента а[i].

При этом pa + 1 будет указывать на элемент а[i + 1], pa + 2 будет указывать на элемент а[i + 2] и так далее.
Таким образом, если pa указывает на a[0], то *(pa + i) - это значение элемента a[i].

6.3. Форматный ввод-вывод одномерного массива

Ввод и вывод одномерного массива может быть выполнен при помощи указателей или в результате непосредственного обращения к i-му элементу массива.

#include "stdafx.h"
void main() {
 int i, a[5], *pa = a; // pa указывает на a[0] - начальный элемент массива а
 // Ввод массива
 // Нужно задать 5 целых чисел на одной или нескольких строках и затем нажать Enter
 for(i = 0; i < 5; i++) scanf_s("%d", pa + i);
 // Вывод массива
 printf_s("\nArray a: ");
 pa = a;
 for(i = 0; i < 5; i++) printf_s("%3d", *(pa+i));
 printf_s("\n");
}

или

#include "stdafx.h"
void main() {
 int i, a[5];
 // Ввод массива
 // Нужно задать 5 целых чисел на одной или нескольких строках и затем нажать Enter
 for(i = 0; i < 5; i++) scanf_s("%d", &a[i]);
 // Вывод массива
 printf_s("\nМассив a: ");
 for(i = 0; i < 5; i++) printf_s("%3d", a[i]);
 printf_s("\n");
}

6.4. Одномерный массив как параметр функции

Описание одномерного массива a в списке параметров функции может быть задано так:

int fun(int a[]);
int fun(int *a);

Описание прототипа функции, принимающей одномерный массив, может быть таким:

int fun(int a[]);
int fun(int *a);
int fun(int *);

Обращение к функции fun может быть таким:

y = fun(a);
y = fun(&a[0]);
y = fun(&a[2]);

В первых двух случаях функции передается адрес начала массива a. В последнем случае функции передается адрес третьего элемента массива, поэтому присваивание

x = a[0];

выполняемое в теле такой функции аналогично присваиванию

x = a[2];

выполненному в теле функции, вызвавшей функцию fun.

Пример. Иллюстрируются различные варианты обращения к элементам массива.

#include "stdafx.h"
void main() {
 int a[120] = {1, 2, -8}; // Остальные элементы массива есть 0
 int *pa = a, *pi;
 int i, x1, x2, x3, x4, x5;
 int fun(int *); // или fun(int a[])
 // pa указатель на a, поэтому, выполнив *pa = a, мы установим в pa адрес a[0]
 // pa = &a[0]; то же, что и *pa = a
 i = 2;
 pi = &a[i]; // pi указывает на элемент a[i]
 // Далее следует эквивалентные с точки зрения результата операторы;
 // в результате их выполнения получим: x1 = x2 = x3 = x4 = x5 = -8
 x1 = a[i];
 x2 = *(pa + i);
 x3 = *(a + i);
 x4 = *pi;
 x5 = pa[i];
 // x1 = x2 = x3 = x4 = x5 = -8
 printf_s("x1 = %3d; x2 = %3d; x3 = %3d; x4 = %3d; x5 = %3d\n", x1, x2, x3, x4, x5);
 printf_s("x0 = %3d\n", fun(a)); // Напечатает: 1
 for(i = 0; i < 4; i++)
  printf_s("x = %3d\n", fun(&a[i])); // Напечатает: 1 2 -8 0
 printf_s("\n");
}
int fun(int *a) // или fun(int a[])
{ return a[0]; }

6.5. Декларация двумерного массива

Декларация целочисленного двумерного массива a из 8 элементов, содержащего 2 стрки и 4 столбца

int a[2][4];

Декларация и инициализация двумерного массива

int a[2][4]={{1, -2, 3, -4}, {5, -6, 7, -8}};

или

int a[][4]={{1, -2, 3, -4}, {5, -6, 7, -8}};

Замечание. Наличие второй размерности обязательно.

Приведенные декларации определяют массив из 8 элементов с именами a[0][0], a[0][1], a[0][2], a[0][3], a[1][0], a[1][1], a[1][2], a[1][3]. Запись a[i][j] - отсылает нас к элементу j массива в строке i.

Двумерный массив в СИ - это одномерный массив, каждый элемент которого так же массив - строка двумерного массива.

6.6. Двумерный массив и указатели

Доступ к элементам двумерного массива при помощи указателей.

Пусть pa есть указатель на int и определен так:

int *pa;

тогда в результате присваивания

pa = &a[0][0];

или

pa = *a;

pa будет указывать на начальный элемент массива a, иначе говоря, pa будет содержать адрес элемента a[0][0].

Присваивание

pa = &a[i][j];

размещает указатель на элемент в строке i массива a. Тот же эффект мы имеет после выполнения оператора

pa = *a + i * n + j;

где n - число элементов в строке массива (в нашем примере n = 4).

Пусть pa указывает на j-й элемент в строке i массива a. Тогда операторы

x = *pa;

и

x=a[i][j];

будут эквивалентны. При этом pa + 1 будет указывать на следующий элемент массива а.

6.7. Форматный ввод-вывод двумерного массива

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

scanf_s("%d", &a[i][j]);

Примеры форматного ввода вывода двумерного массива.

#include "stdafx.h"
void main() {
 int i, j, a[2][4], *pa = *a; // pa указывает на начальный элемент массива a
 for(i = 0; i < 8; i++)
  scanf_s("%d", pa + i); // или scanf_s("%d", pa++);
 printf_s("\nArray a\n");
 pa = &a[0][0];
 // Вывод массива по строчкам
 for(i = 0; i < 8; i++) {
  printf_s("%3d", *(pa + i));
  if((i + 1) % 4 == 0) printf_s("\n"); // Переход на новую строку
 }
}

или

#include "stdafx.h"
void main() {
 int i, j, x, a[2][4];
 for(i = 0; i < 2; i++)
  for(j = 0; j < 4; j++) {
   scanf_s("%d", &x); // В СИ++ можно записать scanf_s("%d", &a[i][j]);
   a[i][j] = x;
  }
 printf_s("\nArray a\n");
 // Вывод массива по строчкам
 for(i = 0; i < 2; i++) {
  for(j = 0; j < 4; j++) printf_s("%3d", a[i][j]);
  printf_s("\n"); // Переход на новую строку
 }
}

6.8. Двумерный массив как параметр функции

Описание двумерного массива a из 2-х строк и 4-х столбцов в списке параметров функции может быть задано так:

int fun(int a[2][4]);
int fun(int a[][4]);
int fun(int (*a)[4]);

Последняя запись декларирует, что параметр функции есть указатель на массив из четырех значений типа int. Скобки (*a) необходимы, так как квадратные скобки имеют более высокий приоритет, чем * - операция раскрытия ссылки.

Замечание. Наличие второй размерности обязательно. В более общем случае (трех и более мерные массивы) можно при описании функции не указывать только первое измерение.

Описание прототипа функции, принимающей двумерный массив, может быть таким:

int fun(int a[2][4]);
int fun(int a[][4]);
int fun(int (*a)[4]);

Обращение к функции fun может быть следующим:

y = fun(a);

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

#include "stdafx.h"
void main() {
 int a[][4] = {{1, 2, - 8, -7}, {-1, -2, 8, 7}};
 int *pa = *a; // или *pa = &a[0][0];
 int i, j, x1, x2, x3, x4, x5, *pi;
 // Прототип функции fun
 int fun(int a[][4]); //или int fun(int (*)[4]);
 i = 1;
 j = 2;
 pi = &a[i][j]; // Адрес второго элемента второй строки массива
 // Далее следуют эквивалентные с точки зрения результата операторы;
 // в результате их выполнения получим: x1 = x2 = x3 = x4 = x5 = 8
 x1 = a[i][j];
 x2 = *pi;
 x3 = *(pa + i * 4 + j);
 x4 = *(*a + i * 4 + j);
 x5 = pa[i * 4 + j];
 // x1 = x2 = x3 = x4 = x5 = 8
 printf_s("x1 = %3d; x2 = %3d; x3 = %3d; x4 = %3d; x5 = %3d\n", x1, x2, x3, x4, x5);
 // Обращение к функции fun
 printf_s("x0 = %3d\n", fun(a)); // x0 = a[1][0] = -1
 for(j = 0; j < 4; j++)
  printf_s("%3d", a[i][j]); // Будет выведена вторая строка : -1 -2 8 7
 printf_s("\n");
 pa = *a + 4;
 for(j = 0; j < 4; j++)
  printf_s("%3d", *(pa + j)); // Будет выведена вторая строка : -1 -2 8 7
 printf_s("\n");
 for(j = 0; j < 4; j++)
  printf_s("%3d", pa[j]); // Будет выведена вторая строка : -1 -2 8 7
 printf_s("\n");
}
int fun(int a[][4]) // или int fun(int (*a)[4])
{ return a[1][0]; }

7. Строки в СИ

Строка в СИ - это последовательность символов, завершаемая пустым символом '\0'. Строка в СИ есть одномерный массив символов, доступ к элементам которого осуществляется так же, как и к элементам обычного одномерного массива.

7.1. Декларация строк

Декларация строки из 11 символов:

char st[11];

Декларация и инициализация строки из 11 символов:

char st[11] = "Это строка";

или

char st[] = "Это строка";

Приведенные декларации определяют массив из 11 символов с именами st[0], st[1], ..., st[10].
В этом массиве st[10] = '\0'. Запись st[i] - отсылает нас к i-му символу строки st.

7.2. Консольный ввод-вывод строк

Строки можно вводить, используя функции getchar и _getch чтения символов и функций gets_s и fgets ввода строк.
Вывод строки можно выполнить посимвольно (функции putchar и _putch), употребив функции puts и fputs или функции форматного вывода на консоль или в файл.

Пример посимвольного ввода-вывода строки.

#include "stdafx.h"
#include <conio.h> // Для _getch и _putch
void main() {
 int i = 0;
 char st[11];
 // Завершаем ввод нажатием на Enter ('\n' - символ конца строки)
 while((st[i] = getchar()) != '\n' && i < 10) i++;
 // В случае _getch вводим всю строку (10 символов)
 //while(i < 10) {st[i] = _getch(); i++;}
 st[i]='\0';
 printf_s("\nString st:\n");
 // Вывод putchar
 i = 0;
 while(st[i]) {putchar(st[i]); i++;}
 printf_s("\n");
 // Вывод printf_s
 printf_s("%s\n", st);
 // Вывод puts
 puts(st);
 // Вывод _putch
 i = 0;
 while(st[i]) {_putch(st[i]); i++;}
 printf_s("\n");
}

Вариант с использованием указателей.

#include "stdafx.h"
void main() {
 char st[11], *ps0, *ps = st; // ps указывает на начало строки, то есть на st[0]
 ps0 = ps; // (ps - ps0) - длина введенной строки
 // Завершаем ввод нажатием на Enter ('\n' - символ конца строки)
 while((*ps++ = getchar()) != '\n' && ps - ps0 < 11) ; // ; - пустой оператор
 *(ps - 1) = 0; // Символ конца строки (или *(ps - 1) ='\0';)
 printf_s("\nString st:\n");
 // Вывод начинаем с ps0
 while(*ps0++) putchar(*(ps0 - 1));
 printf_s("\n");
}

Пример форматного вывода строки.

#include "stdafx.h"
void main() {
 char st[11], *ps = st;
 // Ввод строки после нажатия на Enter
 gets_s(st);
 // Два варианта форматного вывода
 printf_s("\nString st: %12s\n", st);
 printf_s("\nString st: %12s\n", ps);
}

Замечание. Форматный ввод-вывод строк может быть выполнен и при работе с файлами.

7.3. Ввод-вывод строк при работе с файлом

Функция ввода строк из файла fgets в СИ описана следующим образом

char *fgets(char *_Buf, int _MaxCount, FILE *_File).

Функция читает строку ввода в массив символов _Buf. Число прочитанных символов не может превышать _MaxCount - 1. Считанная строка добавляется символом '\0'. В случае ошибки функция fgets возвращает NULL.
Функция fputs добавляет в файл строку (которая в общем случае может не заканчиваться символом '\n' - "новая строка").

int fputs(char *line, FILE *fp).

Эта функция возвращает EOF, если возникла ошибка, и нуль в противном случае.
Библиотечные функции fgets и fputs подобны функциям gets_s и puts. Отличаются они тем, что первые оперируют со стандартными устройствами ввода-вывода: клавиатура и монитор. Кроме того, gets_s исключает из строки последнюю литеру '\n', а puts ее добавляет.

Пример. Вывести на экран все строки текстового файла b.txt.

#include "stdafx.h"
#include <stdlib.h> // для вызова функции exit
void main() {
 char st[80];
 FILE *fp;
 fopen_s(&fp, "b.txt", "r"); // Открываем файл b.txt для чтения
 if(fp == NULL) {
  printf_s("\nOpen failed");
  exit(1); // Прекращаем вычисления
 }
 // Ввод (fgets) из файла и вывод (puts) на консоль
 while(fgets(st, 80, fp) != NULL) puts(st);
 // Если в файле 3 следующие строки:
 // Line one
 // Second line
 // Line 3
 // то на консоли получим:
 // Line one
 //
 // Second line
 //
 // Line 3
}

7.4. Пример обработки строк

Пример. Различные варианты копирования строки st1 в st2.

#include "stdafx.h"
void main() {
 char st1[] = "abcd", st2[8];
 void copy(char *, char *); // или void copy(char st2[], char st1[]);
 copy(st2, st1);
 printf_s("st2 = %s\n", st2);
}
// Копирует содержимое st1 в st2
void copy(char *st2, char *st1) { // или void copy(char st2[], char st1[]) {
 //
 // Вариант 1
 int i = 0;
 while(st1[i] != '\0') { // или просто: != 0
  st2[i] = st1[i]; i++;
 }
 st2[i] = '\0'; // Добавляем символ конца строки '\0'
 puts(st2);
 //
 // Вариант 2
 for(i = 0; (st2[i] = st1[i]) != '\0'; i++);
 puts(st2);
 //
 // Вариант 3
 //while((*st2 = *st1) != '\0') {
 // st2++;
 // st1++;
 //}
 //
 // Вариант 4
 //while((*st2++ = *st1++) != '\0');
 //
 // Вариант 5
 while(*st2++ = *st1++);
}

Замечание. При работе со строками употребляются стандартные библиотечные функции, описанные в string.h и stdlib.h.

8. Унарная операция sizeof

В некоторых режимах работы, например, при организации прямого ввода-вывода может возникнуть необходимость вычисления размера переменной или размера, отводимого под одну переменную базового или производного типа данных. Такую возможность в СИ предоставляет унарная операция sizeof.
Унарная операция sizeof возвращает целое значение типа size_t, равное размеру указанного объекта или типа в байтах. Операция выполняется на этапе компиляции программы. size_t - тип данных "беззнаковое целое", определенный в файле stddef.h. Использование sizeof:

sizeof <объект>;

или

sizeof(<имя типа>);

В качестве объектов могут выступать имена переменных.

В качестве имени типа может использоваться как имя базового типа (int, float, ...), так и с квалификатором или имя производного типа (short int, long double, struct exam, ...).

Пример. Определить число байт, отводимых компилятором под тип long double и целочисленный массив a из 120 элементов.

#include "stdafx.h"
void main() {
 size_t c, d;
 int a[120];
 c = sizeof(long double); // Ответ: с = 8
 d = sizeof a; // Ответ d: = 480
 printf_s("\nSize of long double: %2d", c);
 printf_s("\nSize of int array a[120]: %3d\n", d);
}

9. Функции прямого ввода и вывода в файл

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

Пример. Вывести n элементов целочисленного массива в файл c.txt, располагая в каждой строке по 10 чисел.

#include "stdafx.h"
void main() {
 int i, n = 34, a[120] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16};
 FILE *fp;
 fopen_s(&fp, "c.txt", "w");
 if(fp == NULL) {
  printf_s("Open failed\n");
  return;
 }
 for(i = 0; i < n; i++) {
  fprintf_s(fp, "%4d", a[i]);
  // Если (i + 1) % 10 == 0, тогда переходим на новую строку файла
  if((i + 1) % 10 == 0) fprintf_s(fp, "\n"); // % - операция определения остатка о деления
 }
}

Результат:

1 2 3 4 5 6 7 8 9 10
11 12 13 14 15 16 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0

Прямой вывод в файл, то есть вывод без преобразования данных, обеспечивает функция fwrite, а ввод из файла, сформированного этой функцией, выполняется функцией fread или fread_s. Описание функций находится в stdio.h:

size_t fwrite(const void *ptr, size_t size, size_t n, FILE *fp).
size_t __cdecl fwrite(const void *_Str, size_t _Size, size_t _Count, FILE *_File);
size_t __cdecl fread(void *_DstBuf, size_t _ElementSize, size_t _Count, FILE *_File);
size_t __cdecl fread_s(void *_DstBuf, size_t _DstSize, size_t _ElementSize, size_t _Count, FILE *_File);

Функция fwrite пишет из _Str в _File _Count объектов размера _Size и возвращает число записанных объектов, которое в случае ошибки меньше _Count. Для индикации состояния чтения следует использовать функцию ferror.
Функция fread_s читает из _File в _DstBuf размера _DstSize не более _Count объектов размера _ElementSize. Она возвращает количество прочитанных объектов, которое в случае ошибки может быть меньше заявленного. Для индикации состояния чтения можно использовать функции feof и ferror.
Функция fread отличается от fread_s отсутствием параметра _DstBuf.
Напомним, что size_t - тип данных беззнаковое целое, определенный в файле stddef.h. Значение типа size_t возвращается унарной операцией sizeof, которая возвращает целое значение, равное размеру указанного объекта или типа в байтах.

Замечание. При работе с функциями прямого ввода-вывода употребляются двоичные файлы.

Пример. Записать в двоичный файл a.dat 8 раз заданный целочисленный массив из 10 элементов. Из созданного файла ввести данные в целочисленный массив из 80 элементов.

#include "stdafx.h"
#include <stdlib.h> // Содержит прототип функции exit
void main() {
 int i, b[80], a[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
 size_t sta = sizeof(a); // Размер в байтах порции вывода и ввода
 FILE *fp;
 fopen_s(&fp, "a.dat", "wb"); // Открываем двоичный файл для записи
 if(fp == NULL) {
  printf_s("Open failed\n");
  exit(1);
 }
 for(i = 1; i <= 8; i++)
  fwrite(a, sta, 1, fp); // sta - число выводимых в файл за один раз байт
 fclose(fp);
 // Откроем созданный файл для чтения и выполним ввод данных
 fopen_s(&fp, "a.dat", "rb"); // Помним, что работаем с двоичным файлом
 fread_s(b, sizeof b, sta, 8, fp); // Читаем в массив b порцию размером в sta байт 8 раз
 // или
 //fread(b, sta, 8, fp); // Читаем в массив b порцию размером в sta байт 8 раз
 fclose(fp); // Выведем массив на консоль, располагая на строке 10 чисел
 printf_s("\nArray b\n");
 for(i = 0; i < 80; i++) {
  printf_s("%5d", b[i]);
  if((i + 1) % 10 == 0) printf_s("\n");
 }
}

Результат:

Array b
1 2 3 4 5 6 7 8 9 10
1 2 3 4 5 6 7 8 9 10
...
1 2 3 4 5 6 7 8 9 10

10. Функции feof и ferror

Многие функции при работе с файлами в случае ошибки или конца файла устанавливают индикаторы состояния файла. Примером такой функции является функция fread. В Эти индикаторы можно проверять и изменять, например, посредством функций feof, ferror и clearerr:

int feof(FILE *_File)

feof - возвращает не нулевое значение, если достигнут конец файла _File.

int ferror(FILE *_File)

ferror - возвращает не нулевое значение, если возникла ошибка при работе с файлом _File.

int clearerr(FILE *_File)

clearerr - очищает индикаторы конца файла и ошибки файла _File.

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

#include "stdafx.h"
#include <stdlib.h> // Содержит прототип функции exit
void main() {
 int b[120], a[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
 size_t sta = sizeof(a); // Размер в байтах порции вывода и ввода
 FILE *fp;
 fopen_s(&fp, "a.dat", "wb");
 if(fp == NULL) {
  printf_s("Open failed\n");
  exit(1);
 }
 // Ошибка: попытка чтения из пустого файла
 fread(b, sta, 1, fp);
 // Пример применения ferror
 printf_s("%5d\n", ferror(fp));
 if(ferror(fp)) {
  printf ("Read failed\n");
  exit(1);
 }
 fclose(fp);
}

Замечание. Пример использования функции feof можно посмотреть, например, в разд. 4.5.

11. Структуры

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

struct <имя структуры> {
 <тип1> <имя компоненты1 типа1>, <имя компоненты2 типа1>, ...;
 <тип2> <имя компоненты1 типа2>, <имя компоненты2 типа2>, ...;
 ...
 <типn> <имя компоненты1 типаn>, <имя компоненты2 типаn>,...;
} [<список переменных типа struct <имя структуры>];

В качестве примера рассмотрим формирование структуры struct exam, отражающей результаты экзаменационной сессии студента. Объявим также и переменные с12 и с6 типа struct exam, выполнив инициализацию переменной с6.

struct exam {
 char name[50]; // Фамилия, имя и отчество
 int m1, m2, m3, m4; // Оценки студента соответственно на 1-м, 2-м, 3-м и 4-м экзаменах
} c12, c6 = {"Алешин С. А.", 4, 4, 5, 5};

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

// Объявление структуры как типа
struct exam {
 char name[50];
 int m1, m2, m3, m4;
};
struct exam c12, c6 = {"Алешин С. А.", 4, 4, 5, 5};

Доступ к отдельному полю структуры выполняется одним из следующих способов:

где "." и -> - это операции доступа к полю структуры.

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

struct exam {
 char name[50];
 int m1, m2, m3, m4;
}
// Переменные типа struct exam
struct exam c12, c6 = {"Алешин С.А.". 4, 4, 5, 5}, *p = &c6;
// Переменная p является указателем на переменную c6
int v1;
v1 = c6.m1;
v1 = p->m1;
v1 = (*p).m1;
v1 = (&c6)->m1;

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

struct exam {
 char name[50];
 int m1, m2, m3, m4;
 float am;
};

где am - средняя оценка студента:

am = (m1+m2+m3+m4) / 4

Создадим, во-первых, программу, обеспечивающую запись данных в файл. Чтение данных из файла и их обработку выполним в другой программе.
В коде предполагается, что оценки разделяются одним пробелом.
Ввод данных будет продолжаться до тех пор, пока после сообщения Press Y to continue будет вводиться символ 'Y' или 'y'.

#include "stdafx.h"
#include <stdlib.h> // Содержит прототип функции exit
#include <conio.h> // Для вызова _getche
#include <ctype.h> // Для вызова toupper
void main() {
 size_t st;
 char fl = 'Y';
 char m[20], marks[20]; // Строка для ввода оценок
 struct exam {
  char name[50];
  int m1, m2, m3, m4;
  float am;
 };
 struct exam tmp, *t = &tmp;
 FILE *fp;
 // Открываем для записи двоичный файл
 fopen_s(&fp, "exam.dat", "wb");
 if(fp == NULL) {
  printf_s("Open failed\n");
  exit(1);
 }
 st = sizeof(struct exam);
 while(fl == 'Y') {
  printf_s("\nName: ");
  gets_s(t->name);
  printf_s("Marks: ");
  gets_s(marks);
  // Получаем из строки marks оценки, полагая, что они разделены одним пробелом
  m[0] = marks[0]; m[1] = '\0';
  t->m1 = atoi(m); // Преобразование строки в число
  m[0] = marks[2]; m[1] = '\0';
  t->m2 = atoi(m);
  m[0] = marks[4]; m[1] = '\0';
  t->m3 = atoi(m);
  m[0] = marks[6]; m[1] = '\0';
  t->m4 = atoi(m);
  // Контрольный вывод
  printf_s("Student: %s\n", t->name);
  printf_s("Marks: %3d%3d%3d%3d\n", t->m1, t->m2, t->m3, t->m4);
  t->am = (float)(t->m1 + t->m2 + t->m3 + t->m4) / 4.0f;
  fwrite(&tmp, st, 1 ,fp); // Добавляем запись в файл
  printf_s("Press Y to continue ");
  fl = toupper(_getche()); // Функция toupper переводит символ в верхний регистр
 }
 fclose(fp);
}

Введем следующие данные (показан также и контрольный вывод):

Name: Popov A. C.
Marks: 4 5 5 4
Student: Popov A. C.
Marks: 4 5 5 4
Press Y to continue y
Name: Pasick V. D.
Marks: 4 3 4 3
Student: Pasick V. D.
Marks: 4 3 4 3
Press Y to continue y
Name: Fedorov A. K.
Marks: 4 4 5 5
Student: Fedorov A. K.
Marks: 4 4 5 5
Press Y to continue y
Name: Sarnov D. E.
Marks: 2 3 3 3
Student: Sarnov D. E.
Marks: 2 3 3 3

Программа чтения и обработки двоичного файла exam.dat.

#include "stdafx.h"
#include <stdlib.h> // Содержит прототип функции exit
void main() {
 size_t st;
 float s = 0; // Средняя оценка группы
 int i, n = 0; // n - число студентов в группе
 struct exam {
  char name[50];
  int m1, m2, m3, m4;
  float am;
 };
 struct exam group[30], *t = group; // В массив group будут помещены записи файла exam.dat
 FILE *fp;
 // Открываем для записи двоичный файл
 fopen_s(&fp, "exam.dat", "rb");
 if(fp == NULL) {
  printf_s("Open failed");
  exit(1);
 }
 st = sizeof(struct exam);
 // Заполняем массив group и вычисляем среднюю оценку группы
 // Цикл завершается при обнаружении конца файла exam.dat
 while(1) {
  fread(t, st, 1, fp);
  if(feof(fp)) break; // Покидаем цикл, если достигнут конец файла
  s += t->m1 + t->m2 + t->m3 + t->m4;
  // Контрольный вывод
  printf_s("\nStudent: %s\nMarks: %3d%3d%3d%3d%5.2f", t->name, t->m1, t->m2, t->m3, t->m4, t->am);
  t++; // Устанавливаем указатель на следующий элемент массива group
  n++;
 }
 fclose(fp);
 s = s / (4 * n); // Средняя оценка группы
 printf_s("\nGroup average mark: %5.2f", s);
 t = group; // Перемещаем указатель на начало массива group
 printf_s("\nStudents list which average mark > group average mark");
 for(i = 0; i < n; i++, t++)
  if(t->am > s) printf_s("\n%-35s%5.2f", t->name, t->am);
 printf_s("\n");
}

Результат (показан и контрольный вывод):

Student: Popov A. C.
Marks: 4 5 5 4 4.50
Student: Pasick V. D.
Marks: 4 3 4 3 3.50
Student: Fedorov A. K.
Marks: 4 4 5 5 4.50
Student: Sarnov D. E.
Marks: 2 3 3 3 2.75
Group average mark: 3.81
Students list which average mark grater then group average mark
Popov A. C. 4.50
Fedorov A. K. 4.50

Приложение 1. Пример решения задачи с массивами

Даны три массива a, b и с. Найти массив, содержащий наименьшее число нулевых элементов.

#include "stdafx.h"
#include <stdlib.h>
int inar(FILE *, int *); // Заполняет массив данными из указанного файла
void outar(int *, int); // Печатает массив
int f0(int *, int); // Находит число нулевых элементов в массиве
void main() {
 int a[80], b[80], c[80], na, nb, nc, ka, kb, kc, km;
 FILE *fp;
 fopen_s(&fp, "abc.txt", "r");
 if(fp == NULL) {
  printf_s("\nOpen failed\n");
  exit(1);
 }
 // Ввод данных
 na = inar(fp, a);
 nb = inar(fp, b);
 nc = inar(fp, c);
 // Контрольный вывод
 outar(a, na);
 outar(b, nb);
 outar(c, nc);
 // Поиск числа нулевых элементов в массивах
 ka = f0(a, na);
 kb = f0(b, nb);
 kc = f0(c, nc);
 // km - минимальное из трех чисел ka, kb и kc
 km = ka < kb ? ka : kb;
 km = km < kc ? km : kc;
 // Вывод результата
 if(ka == 0 || kb == 0 || kc == 0)
  printf_s("\nNot all arrays has elements equal 0");
 else {
  if(km == ka) printf_s("\nArray a");
  if(km == kb) printf_s("\n Array b");
  if(km == kc) printf_s("\n Array c");
 }
 printf_s("\n");
}
// Заполняет массив данными из указанного файла
/*
 Файл abc.txt имеет следующую структуру:
 число элементов в массиве a
 перечень элементов массива a
 число элементов в массиве b
 перечень элементов массива b
 число элементов в массиве c
 перечень элементов массива c
*/
int inar(FILE *fp, int d[]) {
 int i, nd;
 fscanf_s(fp, "%d", &nd);
 for(i = 0; i < nd; i++) fscanf_s(fp, "%d", &d[i]);
 return nd;
}
// Печать массива
void outar(int d[],int nd) {
 int i;
 printf_s("\n n = %3d\n", nd);
 for(i = 0; i < nd; i++) printf_s("%3d", d[i]);
}
// Подсчет число нулевых элементов в массиве
int f0(int d[], int nd) {
 int i, kd = 0;
 for(i = 0; i < nd; i++)
  if(d[i] == 0) kd++;
 return kd;
}

Состав файла abc.txt:

7
1 2 3 4 0 0 0
6
1 2 0 0 5 6
8
1 2 3 0 0 6 7 8

Результат:

Array b
Array c

Приложение 2. Задачи

П.2.1. Вычисления

  1. Написать функцию вычисления определенного интеграла на отрезке [a, b] методом прямоугольников. Применить ее для вычисления следующих определенных интегралов:
    Интегралы, вычисляемые методом прямоугольника
  2. Сформировать массив сумм
    s1 = a1 + a2 + ... + a1+m
    s2 = a2 + a3 + ... + a2+m
    . . .
    sk = ak + ak+1 + ... + ak+m,
    где ai - элемент массива a, k и m - заданные числа, причем k+m меньше числа элементов в массиве a.
    Найти в полученном массиве три наименьших элемента.
  3. Вычислить произведение
    Произведение, которое следует вычислить
    где ai - элемент массива a.
    Поиск корня 5-й степени из положительного числа b с точностью ε осуществить, пользуясь последовательными приближениями
    Запись формулы последовательных приближений для вычисления корня 5-й степени
    Начальное приближение:
    Запись формул начальных приближений при вычисления корня 5-й степени
  4. Корень степени n из положительного числа x может быть вычислен по итерационной формуле
    Итерационная формула вычисления корня заданной степени
    c точностью ε и при y0 = x.
    Если в заданном массиве С нет отрицательных элементов, то сформировать матрицу из m строк по правилу:
    первая строка - массив С;
    вторая строка - корень квадратный из соответствующих элементов массива С;
    ...
    строка с номером m - корень степени m из соответствующих элементов массива С.

П.2.2. Матрицы

  1. Если на главной диагонали матрицы есть нулевой элемент, найти разность максимального и минимального элементов в первых трех строках матрицы, иначе - в трех последних.
  2. Переместить нулевые элементы каждого столбца двумерного массива в конец столбца. Вывести после преобразования первые m строк массива, не имеющие нулевых элементов.
  3. Из множества отрезков на плоскости сформировать подмножество отрезков, пересекающих линию y = a, и подмножество отрезков, пересекающих линию y = b. В найденных подмножествах найти отрезок наибольшей длины.
  4. Если целочисленная матрица является треугольной (элементы выше главной диагонали равны нулю), вычислить ее среднее арифметическое. Иначе сформировать массив из четных элементов матрицы.
  5. В квадратной целочисленной матрице поставить в каждом столбце на главную диагональ максимальный элемент столбца. Если все элементы главной диагонали полученной матрицы нечетны, определить сумму элементов до к-го столбца матрицы, иначе - после к-го столбца исходной матрицы.
  6. Если первая строка матрицы имеет максимальное количество отрицательных элементов, то проверить, как изменится среднее арифметическое матрицы, если заменить все отрицательные элементы их модулями.
  7. Если номер столбца прямоугольной матрицы с максимальной суммой элементов четен, сформировать матрицу из столбцов исходной до найденного столбца, иначе - после найденного.
  8. В заданной прямоугольной матрице переставить на первое место столбец с наименьшим количеством нулевых элементов, переставив все нули в конец этого столбца.

П.2.3. Строки

  1. Найти позицию n-го вхождения подстроки sub в строку st. Поиск реализовать в виде функции
    at(sub, st, n).
    Если подстрока не найдена вернуть -1.
    Написать основную программу ввода исходных данных и обращения к функции.
  2. Заменить в строке st все вхождения подстроки sub1 на подстроку sub2. Замену реализовать в виде функции
    stran(st, sub1, sub2).
    Например, функция stran("abcda", "a", "ff") возвращает строку "ffbcdff".
  3. Преобразовать в строке st первую буквы каждого слова в прописную, а остальных в строчные. Преобразование оформить в виде функции
    proper(st).
    Например, если
    st = "николаев николай сергеевич",
    то результатом работы функции proper должна быть строка
    "Николаев Николай Сергеевич".
  4. Выполнить подсчет числа вхождений подстроки sub в строку st. Подсчет реализовать в виде функции
    occurs(sub, st).
    Например, функция occurs("а", "база данных") вернет число 3.
  5. В текстовом файле каждая строка имеет вид
    Николаев-хор, Новиков-удовл, Шипачев-хор, Науменко-отл и так далее.
    Вывести фамилии отличников.
  6. В текстовом файле вывести строку, в которой находится слово максимальной длины.
    Указание. Разработать функцию для поиска в строке самого длинного слова.
  7. В каждой строке текстового файла подсчитать количество слов до первой запятой. Вывести строку, в которой это количество максимально.
    Указание. Разработать функцию для подсчета числа слов в строке до первой запятой.
  8. В каждой строке текстового файла найти слово, содержащее цифру '9' и заменить в нем, начиная с позиции p, k символов на подстроку sub. Распечатать найденное слово до и после замены.
    Указания.
  9. Строка st образуется путем повторения строки stt n раз. Строка sub помещается слева от строки st при вводе с клавиатуры символа 'l' и справа при вводе 'r'.
    Указания.
  10. В каждой строке текстового файла найти подстроку, расположенную между двумя последними запятыми. Найти длину наибольшей подстроки.
    Указание. Разработать функцию для выделения подстроки между двумя последними запятыми.
  11. В каждой строке текстового файла объединить в отдельную строку все слова, имеющие букву 'л'. Вывести в качестве результата список полученных строк.
    Указание. Для работы со строкой текстового файла написать функцию.
  12. Разработать функции
    padl(st, n, sub), padr(st, n, sub), padc(st, n, sub),
    возвращающие строку, состоящую из n повторений строки sub, в которой к левых (правых, центральных) символов заменено на строку st, где к - длина строки st. Например:
    padl("abcd", 8, "*") - abcd****
    padr("abcd", 8, "*") - ****abcd
    padc("abcd", 8, "*") - **abcd**
  13. Разработать функцию для поиска в строке слова с наибольшим вхождением буквы 'е'. Используя эту функцию, вывести строки текстового файла, в которых длина найденного слова более 8 символов.
  14. Разработать функцию для поиска в строке слова с наибольшим вхождением буквы 'е'. Используя эту функцию, удалить из каждой строки текстового файла слово с наибольшим вхождением буквы 'е'.
  15. Написать функцию перестановки всех слов строки, имеющих в начале букву 'р', в конец строки. Применить эту функцию для обработки всех строк заданного текстового файла.
  16. Написать функцию для перестановки в слове первого и последнего символов. Используя эту функцию, выполнить перестановку первого и последнего символов в каждом слове заданной строки.
  17. Вывести содержимое текстового файла в файл, ширина строки которого составляет 20 символов. При выводе размещать в каждой строке формируемого файла максимально возможное число слов. Перенос слов не допускается.

П.2.4. Структуры

  1. Структура записи последовательного файла сотрудников имеет вид:
    Заменить у сотрудника Новиков образование на "Высшее" и удалить из файла запись о сотруднике Никодимов.
  2. В последовательном файле, хранящем данные об имеющихся на складе товарах, структура записи имеет вид:
    Файл упорядочен по Наименованию. Подсчитать количество товара с заданным Наименованием. Расчет количества оформить в виде функции.
  3. В последовательном файле структура записи имеет вид:
    Записи расположены в порядке возрастания массы. Вывести наименования изделий, масса которых равна М1, М2 или М3 кг.
    Замечание. Число изделий с одинаковой массой в заданном файле может быть больше 1.
    Указание. Для поиска изделий использовать алгоритм бинарного поиска.

Литература

  1. Дьюхарст С., Старк К. Программирование на С++.-Киев: ДиаСофт, 1993. - 272с.
  2. Керниган Б., Ритчи Д. Язык программирования СИ.-М.: Финансы и статистика, 1992. - 272с.
  3. Лукас П. С++ под рукой. -Киев: ДиаСофт, 1993. - 176с.

Список работ

Рейтинг@Mail.ru