рефераты конспекты курсовые дипломные лекции шпоры

Реферат Курсовая Конспект

Структура программы на языке Си. Этапы выполнения программы

Структура программы на языке Си. Этапы выполнения программы - раздел Информатика, Структура Программы На Язык...

Структура программы на языке Си. Этапы выполнения программы

Алфавит языка Си

- прописные и строчные буквы латинского алфавита, а также знак подчеркивания (код ASCII 95); - арабские цифры от 0 до 9; - специальные символы:

Лексемы

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

- идентификаторы;

- ключевые (зарезервированные) слова;

- знаки операций;

- константы;

- разделители (скобки, точка, запятая, пробельные символы).

Границы лексем определяются другими лексемами, такими, как разделители или знаки операций, а также комментариями.

 

Идентификаторы и ключевые слова

Длина иденти­фикатора определяется реализацией (версией) транслятора Cи и редактора связей (компоновщика). Современная тенденция - снятие… При именовании объектов следует придерживаться общепринятых соглашений: - ID переменной обычно пишется строчными буквами, например index (для сравнения: Index – это ID типа или функции, а…

Знаки операций

Знак операции – это один или более символов, определяющих действие над операндами (например, ">=", означающий условие "больше или равно). Внутри знака операции пробелы не допускаются. Операции делятся на унарные, бинарные и тернарные операции, по количеству участвующих в них операндов (1, 2 или 3).

Литералы (константы)

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

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

Комментарии

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

В Си комментарии ограничиваются парами символов /* и */, а в С++ был введен вариант комментария, который начинается символами // и заканчивается символом перехода на новую строку.

 

Общая структура программы на языке Си.

Общая структура программы на языке Си имеет вид:   <директивы препроцессора>

Функциональная и модульная декомпозиции

Один из основных приемов - разбивка алгоритма на отдельные функции и/или модули, используя функциональную и/или модульную декомпозиции… Функциональная декомпозиция - метод разбивки большой программы на отдельные… Алгоритм декомпозиции можно представить следующим образом:

Этапы обработки программы.

Компилятор - программа, осуществляющая перевод программ с языка высокого уровня (приближенного к человеку) на язык более низкого уровня (близкий к… Лишь вторая специальная программа - редактор связей (Linker) - создает… Большинство сред программирования по команде "Запуск программы" ("Run") автоматически выполняют…

Роль препроцессора.

Препроцессорные директивы начинаются с символа #, за которым следует наименование директивы, указывающее текущую операцию препроцессора. Препроцессор решает ряд задач по предварительной обработке программы, основной… #include <имя_файла.h>

Ошибки

Ошибки, допускаемые при написании программ, разделяют на синтаксические и логические.

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

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

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

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

Причина семантической ошибки - неправильное понимание смысла (сема­нти­ки) операторов языка.

Программа, содержащая логическую ошибку, может быть запущена. Однако она либо выдает неверный результат, либо даже завершается "аварийно" из-за попытки выполнить недопустимую операцию (например, деление на 0) - в таком случае выдается сообщение об ошибке времени выполнения. Поиск места в программе, содержащего логическую ошибку, является непростой задачей; он носит название отладки программы.


Переменные и константы. Типы данных

 

Для программиста на языке Си память компьютера представляется как набор ячеек, каждая из которых называется переменной, или константой, в зависимости от того, меняется ее значение в процессе работы или нет. Каждая переменная имеет имя (идентификатор, ID). Константа может иметь или не иметь имени.

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

 

Основные типы данных

Тип данных определяет: •внутреннее представление данных в оперативной памяти; •совокупность значений (диапазон), которые могут принимать данные этого типа;

Константы в программах

- самоопределенные арифметические, символьные и строковые данные; - идентификаторы массивов и функций; - элементы перечислений.

Целочисленные константы

Десятичные константы - последовательность цифр 0...9, первая из которых не должна быть 0. Например, 22 и 273 - обычные целые константы, если нужно… Существует система обозначений для восьмеричных и шестнадца­те­­ри­чных… Восьмеричные константы - последовательность цифр от 0 до 7, первая из которых должна быть 0, например: 020 =…

Константы вещественного типа

1) с фиксированной десятичной точкой, формат записи: ±n.m, где n, m - целая и дробная части числа; 2) с плавающей десятичной точкой (экспоненциальная форма): ±n.mE±p, где n, m -… Примеры констант с фиксированной и плавающей точками:

Символьные константы

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

Строковые константы

'0','1','2','3','4','','A','B','C','D','E','F','' Примеры строковых констант: "Система", "nt Аргумент n", "Состояние "WAIT""

Рассмотренные выше константы не имеют имен. Но иногда одну и ту же постоянную (т.е. не меняющуюся в ходе работы программы) величину приходится использовать в тексте программы многократно. Тогда может быть удобно назвать ее именем, чтобы в дальнейшем обращаться к ней по этому имени. Значение же ее будет указываться лишь в одном месте - при ее объявлении, и для его изменения необходимо лишь единственное исправление в тексте программы.

Объявление такой именованной константы пишется так же, как и объявление переменной, но перед ее типом указывается слово const :

 

const double pi=3.14159

 

Далее ее можно использовать в выражениях, указывая ее имя, например:

 

double x=2*pi;

 

Таким образом, именованная константа выглядит, как переменная, но ее значение нельзя менять в процессе работы программы. Зато ее можно использовать там, где разрешается использовать только константы. В языке С++ в сложных программах, разбитых на модули, употребление именованных констант часто считается предпочтительнее, чем директива #define.


Обзор операций

 

Операции, выражения

Операции языка Си предназначены для управления данными (более 40). Для их использования необходимо знать:

- синтаксис;

- приоритеты (15 уровней);

- порядок выполнения.

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

Операции, применяемые к одному операнду, - унарные, к двум операндам – бинарные, есть операция с тремя операндами - тернарная. Операции выполняются в соответствии с приоритетами. Для изменения порядка выполнения операций используются круглые скобки.

Большинство операций выполняются слева направо, например, a+b+c ® (a+b)+c. Исключение: унарные операции, операции присваивания и условная операция (?:) - справа налево.

Полный список операций в соответствии с их приоритетом приводится ниже, в § 4.7.

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

 

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

Арифметические операции - бинарные. Перечень арифметических операций и их обозначений:

+ - сложение;

- - вычитание (либо унарная операция - изменение знака);

/ - деление (для int операндов - с отбрасыванием остатка);

* - умножение;

% - остаток от деления целочисленных операндов, со знаком первого операнда (деление по модулю).

Операндами традиционных арифметических операций (+ - * /) могут быть константы, переменные, элементы массивов, любые арифметические выражения.

Порядок выполнения операций:

- выражения в круглых скобках;

- функции (стандартные математические, функции пользователя);

- * / выполняются слева направо;

- + ─ слева направо.

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

Унарные операции + и – обладают самым высоким приоритетом, определены только для целых и вещественных операндов, «+» носит только информационный характер, «–» меняет знак значения операнда на противоположный (не адресная операция).

Таким образом, так как операции *, /, % обладают высшим приоритетом над операциями +, -, при записи сложных выражений нужно использовать общеприня­тые математические правила:

x+y*z-a/b Û x+(y*z)-(a/b)

 

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

== - равно или эквивалентно;

!= - не равно;

< - меньше;

<= - меньше либо равно;

> - больше;

>= - больше либо равно.

Пары символов соответствующих операций разделять нельзя.

Общий вид операций отношений:

<выражение1> <знак_операции> <выражение2>

Общие правила:

- операндами могут быть любые базовые (скалярные) типы;

- значения операндов перед сравнением преобразуются к одному типу;

- результат операции отношения - целое значение 1, если отношение истинно, или 0 в противном случае. Следовательно, операция отношения может использоваться в любых арифметических выражениях.

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

Перечень логических операций в порядке убывания относительного приоритета и их обозначения:

! - отрицание (логическое НЕТ);

&& - коньюнкция (логическое И);

|| - дизьюнкция (логическое ИЛИ).

Общий вид операции отрицания:

!<выражение>

Общий вид операций коньюнкции и дизьюнкции

<выражение1> <знак_операции> <выражение2>

Например:

y>0 && x==7 ® истина, если 1-е и 2-е выражения истинны;

e>0 || x==7 ® истина, если хотя бы одно выражение истинно.

Ненулевое значение операнда трактуется как "истина", а нулевое - "ложь".

Например:

!0 ® 1

!5 ® 0

x=10; y=10;

!((x==y)>0) ® 0

 

Пример правильной записи двойного неравенства:

0<x<100 → (0<x)&&(x<100)

 

Особенность операций коньюнкции и дизьюнкции – экономное последовательное вычисление выражений-операндов:

<выражение1> <операция><выражение2>,

- если выражение1 операции коньюнкция ложно, то результат операции - ноль и выражение2 может не вычисляться;

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

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

scanf("%d",&i) && test1(i) && test2(i) ® нулевой результат одной из функций может привести к игнорированию вызова остальных;

search1(x) || search2(x) || search3(x) ® только ненулевой результат одной из функций может привести к игнорированию вызова остальных.

Действительно ли "ненужные" функции не будут вызываться - зависит от настроек компилятора.

 

Операции (продолжение).

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

Операнд_1 = Операнд_2 Операндом_1 может быть только переменная. Этот (левый) операнд операции… Операция присваивания может быть как отдельным оператором (тогда после нее ставится знак ; ), так и частью другого…

Сокращенная запись операции присваивания

В языке Си используются два вида сокращенной записи операции присваивания:

1) вместо записи:

v = v # e;

где # – любая арифметическая или битовая операция, рекомендуется использовать запись v #= e;

Например,

i = i + 2; « i += 2; (+= – без пробелов);

2) вместо записи:

x = x # 1;

где # означает + либо - , x – переменная одного из целочисленных типов (или переменная-указатель), рекомендуется использовать запись:

 

##x; – префиксную, или x##; – постфиксную.

 

Если эти операции используются в отдельном виде, то различий между постфиксной и префиксной формами нет. Если же они используются в выражении, то в префиксной форме (##x), сначала значение x изменится на 1, а затем будет использовано в выражении; в постфиксной форме (x##) – сначала значение используется в выражении, а затем изменяется на 1. (Операции над указателями будут рассмотрены позже.)

 

 

Рекомендации использования сокращений обоснованы возможностью оптимизации программы (ускорение в работе программы), т.к. схема выражения вида v #= e соответствует схеме выполнения многих машинных команд типа "регистр-память".

 
 

 

Преобразование типов операндов

Бинарных операций

Типы операндов преобразуются к общему типу в порядке увеличения их "допустимого диапазона значений". Поэтому неявные преобразования всегда…   short, char ® int ® unsigned ® long ® double

Преобразование типов при присваивании.

Если объявлены: float x; int i; то как x=i; так и i=x; приводят к преобразованиям. При этом float преобразуется в int отбрасыванием дробной части. … Тип double преобразуется во float округлением. Длинное целое преобразуется в более короткое целое и переменные типа char посредством отбрасывания лишних битов более…

Операция явного приведения типа

Вид записи операции: (тип) выражение; Ее результат - значение выражения, преобразованное к заданному типу… Операция приведения типа вынуждает компилятор выполнить указанное преобразование, но ответственность за последствия…

Стандартная библиотека языка Си

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

Стандартные математические функции

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

Потоковый ввод-вывод

Для ввода-вывода в консольных приложениях в языке С++ используются два объекта класса iostream: cin (класс istream), cout (класс ostream) и две… Формат записи операций извлечения из потока >> (ввод с клавиатуры) и… cout << выражение ;

Консольные функции вывода данных на экран

Для вывода информации на экран монитора (дисплей) в языке Си служат функции printf() и puts(). Формат функции форматного вывода на экран: printf( управляющая строка , список объектов вывода);

Консольные функции ввода информации

scanf (управляющая строка , список адресов объектов ввода); Список адресов объектов ввода представляет собой имена переменных, разделенные… В управляющей строке (заключенной в кавычки) указывается список спецификаторов форматов. Каждый спецификатор формата…

Ввод-вывод в оконных приложениях.

Для ввода-вывода в оконных приложениях C++ Builder'a используются различные компоненты (Memo, Edit, Label и др.) Например, ввод-вывод в Edit осуществляется обычно через операцию присваивания:

 

Edit1->Text=IntToStr(k);

X:=StrToFloat(Edit2->Text);

 

а вывод в Memo - через метод Add() :

 

Memo1->Lines->Add("x="+FloatToStrF(x, ffFixed, 15, 7));

 

При этом обычно используются функции преобразования типов C++ Builder'a:

 

Функция Направление преобразования Пример
StrToInt() String à int k=StrToInt (Edit1->Text);
IntToStr() int à String Edit1->Text= IntToStr(k);
StrToFloat() String à double x= StrToFloat (Edit1->Text);
FloatToStr() double à String Edit1->Text= FloatToStr(x);
FloatToStrF() double à String Edit1->Text=FloatToStrF(x, ffFixed, 15, 8);

 

В функции FloatToStrF(), в отличие от функции FloatToStr(), задается максимальное общее количество цифр в числе (которое поэтому лучше задавать побольше, например, 15) и количество цифр после запятой. Второй параметр может принимать лишь одно из нескольких допустимых значений, имеющих смысл, подобный спецификаторам формата printf() (ffFixed - %f, ffExponent - %e, ffGeneral - %g и т.п.).

В русской версии Windows в функциях StrToFloat(), FloatToStr(), FloatToStrF() дробная часть числа отделяется от целой запятой; а в английской версии Windows - точкой.

 

Советы по программированию

2. Старайтесь давать переменным ID (имена), отражающие их назначение. 3. При вводе данных с клавиатуры выводите на экран пояснения: что нужно… 4. При составлении выражений учитывайте приоритет используемых операций.

Операторы языка С.

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

Простые операторы:

- пустой оператор «;» ;

- оператор присваивания - выполнение операций присваивания;

- оператор вызова функции - выполнение операции вызова функции.

 

Примеры простых операторов:

 

;

b=3;

a=b+7;

printf("%lfn", a);

 

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

 


 

Составление разветвляющихся алгоритмов

 

Условные операторы

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

В языке Си имеется две формы условного оператора: простая и полная. Синтаксис простой формы:

if (выражение) оператор;

выражение – логическое или арифметическое выражение, вычисляемое перед проверкой, и, если выражение истинно (не равно нулю), то выполняется оператор, иначе он игнорируется; оператор – любой оператор языка Си. Если в случае истинности выражения необходимо выполнить несколько операторов (более одного), их необходимо заключить в фигурные скобки. (Группа операторов, заключенная в фигурные скобки, называется составным оператором или блоком).

 

 
 

Примеры записи:

 

if (x > 0) x = 0;

if (i != 1) j++, s = 1; – используем операцию «запятая»;

if (i != 1)

{

j++; s = 1; – последовательность операций (блок);

}

if (getch() != 27) k = 0; – если нажата любая клавиша, кроме “Esc”.

if (!x) exit (1); или так : if (x == 0) exit(1);

if ( i>0 && i<n) k++; – если нужно проверить несколько условий, то их объединяют знаками логических операций.

 

Синтаксис полной формы условного оператора:

if (выражение) оператор 1 ;

else оператор 2 ;

Если выражение не равно нулю (истина), то выполняется оператор 1, иначе – оператор 2. Операторы 1 и 2 могут быть простыми или составными (блоками).

Наличие символа «;» перед словом else в языке Си обязательно.

 
 

 

Примеры записи:

 

if (x > 0) j = k+10;

else m = i+10;

if ( x > 0 && k !=0 )

{

j = x/k;

x += 10;

}

else m = k*i + 10;

 

Операторы 1 и 2 могут быть любыми операторами, в том числе и условными. Тогда, если есть вложенная последовательность операторов if else, то слово else связывается с ближайшим к ней предыдущим if, не содержащим ветвь else. Например:

if (n > 0)

if(a > b) z = a;

else z = b;

Здесь ветвь else связана со вторым if (a > b). Если же необходимо связать слово else с внешним if, то используются операторные скобки:

if (n > 0) {

if (a > b) z = a;

}

else z = b;

В следующей цепочке операторов if – else – if выражения просматриваются последовательно:

if (выражение 1) оператор 1;

else

if (выражение 2) оператор 2;

else

if (выражение 3) оператор 3;

else оператор4 ;

Если какое-то выражение оказывается истинным, то выполняется относящийся к нему оператор и этим вся цепочка заканчивается. Каждый оператор может быть либо отдельным оператором, либо группой операторов в фигурных скобках. Оператор 4 будет выполняться только тогда, когда ни одно из проверяемых условий не выполняется. Иногда при этом не нужно предпринимать никаких явных действий, тогда последний else может быть опущен, или его можно использовать для контроля, чтобы зафиксировать "невозможное" условие (своеобразная экономия на проверке условий).

Пример:

if (x < 0) printf("n X отрицательное n");

else if(x==0) printf ("n X равно нулю n");

else prinf("n X положительное n");

Замечание. Наиболее распространенной ошибкой при создании условных операторов является использование в выражении операции присваивания «=» вместо операции сравнения на равенство операндов «==» (два знака равно). Например, в следующем операторе синтаксической ошибки нет:

if (x = 5) a++;

но значение а будет увеличено на единицу независимо от значения переменной х, т.к. результатом операции присваивания х = 5 в круглых скобках является значение 5¹0 – истина.

 

Оператор выбора альтернатив (переключатель)

Оператор switch (переключатель) предназначен для разветвления процесса вычислений на несколько направлений.

Общий вид оператора:

switch ( выражение )

{

caseконстанта1: список операторов 1

caseконстанта2: список операторов 2

...

caseконстантаN: список операторов N

default: список операторов N+1 – необязательная ветвь;

}

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

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

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

В случае несовпадения значения выражения ни с одной из констант выбора происходит переход на метку default, либо, при ее отсутствии, к оператору, следующему за оператором switch.

Управляющий оператор break (разрыв) выполняет выход из оператора switch. Если в некоторой ветви выход из переключателя явно не указан, то после ее выполнения начнут последовательно выполняться последующие ветви. Поэтому, если по совпадению с каждой константой должна быть выполнена одна и только одна ветвь, схема оператора switch следующая:

switch (выражение)

{

case константа1: операторы 1; break;

case константа2: операторы 2; break;

...

case константаN: операторы N; break;

default: операторы (N+1);

}

 

Структурная схема рассмотренной конструкции (с использованием оператора break) приведена на рисунке.

 

 
 

Пример оператора switch с использованием оператора break:

void main(void)

{

int i = 2;

switch(i)

{

case 1: puts ( "Случай 1. "); break;

case 2: puts ( "Случай 2. "); break;

case 3: puts ( "Случай 3. "); break;

default: puts ( "Случай default. "); break;

}

}

 

Для того чтобы выйти из оператора switch в любом месте использовали оператор break, поэтому результатом данной программы будет:

Случай 2.

 

 
 

Пример оператора switch без использования оператора break (схема общего вида такой конструкции приведена на рисунке):

 

void main()

{

int i = 2;

switch(i)

{

case 1: puts ( "Случай 1. ");

case 2: puts ( "Случай 2. ");

case 3: puts ( "Случай 3. ");

default: puts ( "Случай default. ");

}

}

 

Так как оператор разрыва отсутствует, результат в данном случае:

 

Случай 2.

Случай 3.

Случай default.

 

 

Пример реализации простейшего калькулятора на четыре действия с контролем правильности ввода символа нужной операции. Ввод данных осуществляется следующим образом: операнд 1, символ нужной операции, операнд 2. Вывод – а, символ операции, b, = , вычисленное значение.

 

#include <stdio.h>

void main(void)

{

double a, b, c;

char s;

m1: fflush(stdin); // Очистка буфера ввода stdin

printf("n Введите операнд 1, символ операции, операнд 2:");

scanf("%lf%c%lf", &a, &s, &b);

switch(s) {

case '+': c = a+b; break;

case '–': c = a–b; break;

case '*': c = a*b; break;

case '/': c = a/b; break;

default: printf("n Ошибка, повторите ввод!”); goto m1;

}

printf("n a %c b = %lf", s, c);

printf("n Продолжим? (Y/y) ");

s = getch();

if ( (s=='Y') || (s=='y') ) goto m1;

printf("n Good bye! ");

}

 

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

Введите операнд 1, символ операции, операнд 2: 2.4+3.6

На экран будет выведен результат и дальнейший диалог:

a + b = 6.000000

Продолжим? (Y/y)

Введя символ y (Y), вернемся в начало функции и на экране вновь:

Введите операнд 1, символ операции, операнд 2:

Если ошибочно ввести – 2r3 ,появятся следующие сообщения:

Ошибка, повторите ввод!

Введите операнд 1, символ операции, операнд 2:

2 * 3

a*b = 6.000000

Continue? (Y/y)

Нажимаем любую клавишу, кроме y или Y, получим сообщение

Good bye!

Программа закончена.

 

7.3. Условная операция «? :»

Если одно и то же выражение (или переменная) вычисляется по-разному в зависимости от некоторого условия, вместо оператора if можно использовать более короткую запись - условную операцию. Эта операция – тернарная, т.е. в ней участвуют три операнда. Формат написания условной операции следующий:

Выражение1 ? выражение2: выражение3;

если выражение 1 (условие) отлично от нуля (истинно), то результатом операции является значение выражения 2, в противном случае – значение выражения 3. Каждый раз вычисляется только одно из выражений 2 или 3.

На рисунке приведена схема вычисления результата, которая аналогична схеме полного оператора if (см. рисунок):

 

 

Рассмотрим участок программы для нахождения максимального значения z из двух чисел a и b, используя оператор if и условную операцию.

1. Запишем оператор if :

if (a > b) z = a;

else z = b;

2. Используя условную операцию:

z = a > b ? a : b;

Условную операцию можно использовать так же, как и любое другое выражение. Если выражения 2 и 3 имеют разные типы, то тип результата определяется по правилам преобразования. Например, если f имеет тип double, а nint, то результатом операции:

(n > 0) ? f : n;

по правилам преобразования типов будет double, независимо от того, положительно n или нет.

 

 


Составление циклических алгоритмов

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

Оператор с предусловием while

Цикл с предусловием реализует структурную схему, приведенную на рис. 8.1а и имеет вид:

while (выражение)

код цикла;

Выражение определяет условие повторения кода цикла, представленного простым или составным оператором.

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

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

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


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

 

 

Рис. 8.1

 

Цикл завершается, если условие его продолжения не выполняется. Возможно принудительное завершение, как текущей итерации, так и цикла в целом.

Для этого используют оператор continue – переход к следующей итерации цикла и break – выход из цикла.

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

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

int count = 0;

char ch;

scanf("%c", &ch);

while ( ch != n') {

count++;

scanf("%c", &ch);

}

8.2. Оператор цикла с постусловием do – while

Цикл с постусловием реализует структурную схему, приведенную на рис. 8.1 б.

Общий вид записи такой конструкции:

do

код цикла;

while (выражение);

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

Здесь сначала выполняется код цикла, после чего проверяется, надо ли его выполнять еще раз.

Следующая программа будет вас приветствовать до тех пор, пока будем вводить символ Y или y (Yes). После введения любого другого символа цикл завершит свою работу.

 

#include <stdio.h>

void main(void)

{

char answer;

do {

puts(" Hello! => ");

scanf(“%c”, &answer);

fflush(stdin);

}

while ((answer=='y')||(answer=='Y'));

}

 

Результат выполнения программы:

Hello! => Y

Hello! => y

Hello! => d

Оператор цикла с предусловием и коррекцией for

Цикл реализует структурную схему, приведенную на рис. 8.2 (такой цикл еще называют циклом с параметром).


 

Рис. 8.2

 

Общий вид оператора:

for (выражение 1; выражение 2; выражение 3)

код цикла;

где выражение 1 – инициализация счетчика (параметр цикла);

выражение 2 – условие продолжения счета;

выражение 3 – коррекция счетчика.

Инициализация используется для присвоения счетчику (параметру цикла) начального значения.

Выражение 2 определяет условие выполнения цикла. Как и в предыдущих случаях, если его результат не нулевой («истина») то цикл выполняется.

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

Выражения 1,2 и 3 могут отсутствовать (пустые выражения), но символы «;» опускать нельзя.

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

sum = 0;

for ( i = 1; i<=N; i++) sum+=i;

Заметим, что в выражении 1 переменную-счетчик можно декларировать:

for (int i = 1; i<=N; i++)

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

Цикл for эквивалентен последовательности инструкций:

выражение 1;

while (выражение 2) {

...

выражение 3;

}

а оператор

for (; выражение 2; )

код цикла;

эквивалентен оператору

while (выражение 2)

код цикла;

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

for ( ; ; ) код цикла;

эквивалентен оператору

while (1) код цикла;

В циклической структуре for может использоваться операция «запятая». Она позволяет включать в его выражения несколько операторов. Тогда рассмотренный пример суммирования первых N натуральных чисел можно записать в следующем виде:

for ( sum = 0 , i = 1; i<=N; sum+= i , i++) ;

Оператор for имеет следующие возможности:

– можно вести подсчет с помощью символов, а не только чисел:

for (ch = 'a'; ch <= 'z'; ch++) ... ;

– можно проверить выполнение некоторого произвольного условия:

for (n = 0; s[i] >= '0' && s[i] < '9'; i++) ... ;

или:

for (n = 1; n*n*n <= 216; n++) ... ;

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

num=0;

for(printf(" вводить числа по порядку! n"); num!=6;)

scanf("%d", & num);

printf(" последнее число – это то, что нужно. n");

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

Переменные, входящие в выражения 2 и 3 можно изменять при выполнении кода цикла, например, значения k и delta:

for (n = 1; n < 10*k; n += delta) k=delta--;

Использование условных выражений позволяет во многих случаях значительно упростить программу, например:

for (i = 0; i<n; i++)

printf("%6d%c", a[i], (i%10==0 || i==n–1) ? 'n' : ’ ‘);

В этом цикле печатаются n элементов массива а по 10 в строке, разделяя каждый столбец одним пробелом и заканчивая каждую строку (включая последнюю) одним символом перевода строки. Символ перевода строки записывается поле каждого десятого и n-го элементов. За всеми остальными – пробел.

Наиболее часто встречающиеся ошибки при создании циклов – это использование в коде цикла неинициализированных переменных и неверная запись условия выхода из цикла.

Чтобы избежать ошибок, нужно стараться:

– проверить, всем ли переменным, встречающимся в правой части операторов присваивания в коде цикла, присвоены до этого начальные значения (а также, возможно ли выполнение других операторов);

– проверить, изменяется ли в цикле хотя бы одна переменная, входящая в условие выхода из цикла;

– предусмотреть аварийный выход из цикла по достижению некоторого количества итераций;

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

 

Вложенные циклы.

  Пример. Для X, меняющегося от a до b с шагом h, вычислить:

Операторы передачи управления.

 

Формально к операторам передачи управления относятся:

– оператор безусловного перехода goto;

– оператор перехода к следующему шагу (итерации) цикла continue;

– выход из цикла, либо из оператора switchbreak;

– оператор возврата из функции return.

 

Оператор безусловного перехода goto

В языке Си предусмотрен оператор goto, общий вид которого:

goto метка ;

Он предназначен для передачи управления оператору, помеченному указанной меткой. Метка представляет собой идентификатор с символом «двоеточие» после него, например, пустой помеченный меткой m1 оператор:

m1: ;

 

Циклы и переключатели можно вкладывать вдруг в друга, и наиболее характерный оправданный случай использования оператора goto – выполнение прерывания (организация выхода) из вложенных циклов. Например, при обнаружении грубых смысловых ошибок необходимо выйти из двух (или более) вложенных циклов (где нельзя использовать непосредственно оператор break, т.к. он прерывает только самый внутренний цикл):

for (...)

for (...) {

...

if (ошибка) goto error;

}

...

error: операторы для устранения ошибки;

 

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

Не рекомендуется, однако, совершать с помощью оператора goto переходы внутрь цикла из-за его пределов:

 
 


goto HHH;

do{

i++;

HHH:

}while(i<5);

 

а также делать переходы, обходящие объявление переменных:

 
 


goto YYY;

int k;

YYY:

 

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

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

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

 

Операторы continue, break и return

В определенных случаях вместо оператора goto удобнее использовать операторы continue или break.

Оператор continue может использоваться во всех типах циклов (но не в операторе-переключателе switch). Наличие оператора continue вызывает пропуск "оставшейся" части итерации и переход к началу следующей, т.е. досрочное завершение текущего шага и переход к следующему шагу.

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

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

Оператор break производит досрочный выход из цикла или оператора-переключателя switch, к которым он принадлежит, и передает управление первому оператору, следующему за текущим оператором. Т.е. break обеспечивает переход в точку кода программы, находящуюся за оператором, внутри которого он (break) находится.

Оператор return производит выход из текущей функции и будет рассмотрен позже.

 

Советы по программированию

2. Если в какой-либо ветви вычислений условного оператора или в цикле требуется выполнить два (и более) оператора, то они при помощи фигурных скобок… 3. Проверка вещественных величин на равенство друг другу может дать неверный…  

Массивы

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

Одномерные массивы

тип ID_массива [размер] = {список начальных значений}; тип – тип элементов массива; размер – количество элементов в массиве.

Примеры алгоритмов, использующих одномерные массивы.

А) Простейший пример

Задача 1. Ввести с клавиатуры массив, а затем вывести только его положительные элементы:

 

#include<iostream.h>

#include<conio.h>

#include<stdio.h>

void main()

{

int a[100],n,i;

M: cout<<"Vvedite n<=100:";

cin>>n;

if(n>100) goto M;

cout<<"Vvedite massiv:n";

for (i=0; i<n; i++)

cin>>a[i];

 

cout<<"Polozhitelnye elementy: ";

for (i=0; i<n; i++)

if (a[i]>0)

cout<<a[i]<<" ";

cout<<endl;

 

getch();

}

 

 

Б) Нахождение суммы, произведения, количества

  #include<iostream.h> #include<conio.h>

Г) Проверка условия, относящегося к массиву.

Задача 11. Проверить, все ли элементы массива меньше 5.

 

Для этого достаточно подсчитать количество элементов,не меньших 5:

 

for (i=s=0; i<n; i++)

if (a[i]>=5)

s++;

if (s)

cout<<"Ne vse elementy men'she 5 !";

Заметим, что s++ можно заменить на s=1, т.к. конкретное количество элементов здесь несущественно.

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

 

Д) Удаление и вставка в массивах

  Поскольку полное количество элементов в массиве задано в его объявлении,…   1

Е) Обмен местами

  При обмене, чтобы не потерять одно из значений, потребуется дополнительная…  

З) Сортировка массива

  Для этой задачи придумано множество различных алгоритмов. Один из них -…  

И) Поиск совпадений

  for(max=i=0; i<n; i++){ s=1; // s - число повторений i-го элемента

Многомерные массивы

Декларация многомерного массива имеет следующий формат: тип ID [размер1] [размер2]…[размерN] = { {список начальных значений},

Примеры алгоритмов, использующих двумерные массивы.

А) Простейшие примеры

Задача 1. Ввести матрицу и увеличить все ее элементы на единицу.

 

#include<iostream.h>

#include<conio.h>

#include<stdio.h>

void main() {

int a[10][10],n,m,i,j;

// Ввод матрицы

cout<<"Vvedite n,m <=10:";

cin>>n>>m;

cout<<"Vvedite massiv:n";

for (i=0; i<n; i++)

for (j=0; j<m; j++)

cin>>a[i][j];

// Увеличение на 1

for (i=0; i<n; i++)

for (j=0; j<m; j++)

a[i][j]++;

// Вывод матрицы

puts("Result:");

for (i=0; i<n; i++)

for (j=0; j<m; j++)

printf("%3d%c", a[i][j], j==m-1? 'n' : ' ');

 

getch();

}

 

Задача 2. Найти в матрице наибольший элемент и его позицию.

 

max=a[0][0];

im=jm=0;

for (i=0; i<n; i++)

for (j=0; j<m; j++)

if (a[i][j]>max) {

max=a[i][j];

im=i;

jm=j;

}

printf("Max element a[%d][%d]=%dn", im, jm, max);

 

Задача 3. Переписать матрицу в одномерный массив.

 

int b[100];

for (i=k=0; i<n; i++)

for (j=0; j<m; j++)

b[k++]=a[i][j];

 

После выполнения этого участка программы k=n*m -количество элементов в полученном массиве.

 

 

Б) Диагонали квадратной матрицы

Квадратной называется матрица, у которой число строк равно числу столбцов. Ее главной диагональю называется диагональный ряд элементов, идущий из…  

В) Работа со строками и столбцами

В применении же ко всей матрице это обычно требует дополнительного внешнего цикла (циклов).   Задача 5. Поменять местами первую и последнюю строки матрицы.

Компонента StringGrid

   

Размещение данных и программ в памяти ПЭВМ

Общие понятия.

Минимальная адресуемая ячейка (согласно стандарту IBM), с точки зрения программиста, состоит из 8 двоичных позиций (т.е. в каждую двоичную позицию… Таким образом, в одной ячейке из 8 двоичных разрядов помещается объем… Для помещения данных в такие ячейки производится их запись с помощью нулей и единиц (кодирование). При этом любые…

Стандартная часть таблицы символов (ASCII)

КС С КС С КС С КС С КС С КС С КС С КС С
    @ P ` p
! A Q a q
" B R b r
# C S c s
$ D T d t
§ % E U e u
& F V f v
' G W g w
( H X h x
) I Y i y
* : J Z j z
+ ; K [ k {
, < L l |
- = M ] m }
. > N ^ n ~
/ ? O _ o

 

Некоторые из вышеперечисленных символов имеют особый смысл. Так, например, символ с кодом 9 обозначает символ горизонтальной табуляции, символ с кодом 10 – символ перевода строки, символ с кодом 13 – символ возврата каретки.

Дополнительная часть таблицы символов

КС С КС С КС С КС С КС С КС С КС С КС С
А Р а р Ё
Б С б с ё
В Т в т Є
Г У г у є
Д Ф д ф Ї
Е Х е х ї
Ж Ц ж ц Ў
З Ч з ч ў
И Ш и ш °
Й Щ й щ
К Ъ к ъ ·
Л Ы л ы
М Ь м ь
Н Э н э ¤
О Ю о ю
П Я п я  

 

В таблицах обозначение КС означает "код символа", а С – "символ".

 

Тип char рассматри­вается компиля­то­ром как "целочисленный", поэтому возможно использование signed char (по умолчанию) - коды символов от -128 до +127 и unsigned char - коды символов от 0 до 255. При этом коды символов первой половины кодовой таблицы для signed char и unsigned char совпадают. Коды символов второй половины кодовой таблицы для signed char являются отрицательными, а для unsigned char - положительными, от 128 до 255 (см. выше кодировку целых отрицательных чисел).

Таким образом, одни и те же значения типа char могут рассматриваться и как символы, и как числа (знаковые либо беззнаковые); при этом конкретный смысл значения определяется операцией, которая над ним совершается.

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

 

Пример 1. Проверить, является ли символ цифрой. Если да, то присвоить переменной целого типа значение этой цифры.

 

char c; int i;

...

if (c>='0' && c<='9')

i=c-'0'; // Равносильно i=c-48;

 

Пример 2. Занести в переменную случайно выбранную большую букву латинского алфавита.

 

#include<stdlib.h>

...

char c;

randomize(); // Инициализация счетчика случайных чисел

...

c=random(26)+'A'; // 26 - число латинских букв в алфавите

 

Напомним, что символьная константа – это символ, заключенный в одинарные кавычки: 'A', 'х'.

 

Операция sizeof

sizeof(параметр); где: «параметр» – тип или идентификатор объекта (не ID функции). Если указан идентификатор сложного объекта (массив, структура, объединение), то получаем размер всего сложного…

Побитовые логические операции. Операции над битами

В СИ предусмотрен набор операций для работы с отдельными битами слов. Эти операции нельзя применять к переменным вещественного типа (float, double).

Перечень операций над битами и их обозначения:

~ - инвертирование (унарная операция);

& - побитовое И - конъюнкция;

| - побитовое включающее ИЛИ - дизъюнкция;

^ - побитовое исключающее ИЛИ - сложение по модулю 2;

>> - сдвиг вправо;

<< - сдвиг влево.

Пары символов (>>,<<) разделять нельзя.

Общий вид операции инвертирования:

~<выражение>

Остальные операции над битами имеют вид:

<выражение1> <знак_операции> <выражение2>

Операндами операций над битами могут быть только выражения, приводимые к целому типу. Операнды представляются в двоичной форме, и операции выполняются поразрядно, т.е. над каждым битом (для операций &, |, ^ - над каждой парой соответствующих бит, взятых от первого и второго операндов) (знаковый разряд особо не выделяется).

Операция ~ означает замену значения бита на противоположное (т.е 0 на 1 , а 1 на 0).

Операция & дает 1, если оба участвующих в ней бита равны 1; иначе дает 0.

Операция | дает 1, если хотя бы один из участвующих в ней битов равен 1; иначе дает 0.

Операция ^ дает 1, если один из участвующих в ней битов равен 1, а другой 0; иначе (т.е. если оба бита одинаковы) дает 0.

 

Примеры:

 

~0xF0 Û 0x0F

0xFF & 0x0F Û 0x0F

0xF0 | 0x11 Û 0xF1

0xF4 ^ 0xF5 Û 0x01

 

Операция & часто используется для маскирования (выделения) некоторого множества битов. Например, оператор w = n & 0177 передает в w семь младших битов n, полагая остальные равными нулю.

Необходимо отличать побитовые операции & и | от логических операций && и ||, которые подразумевают вычисление значения истинности. Если x=1, y=2, то x & y равно нулю, а x && y равно 1.

0x81<<1 Û 0x02

0x81>>1 Û 0x40

 

Унарная операция (~) дает дополнение к целому. Это означает, что каждый бит со значением 1 получает значение 0 и наоборот. Эта операция обычно оказывается полезной в выражениях типа:

X & (~)077,

где последние 6 битов X маскируются нулем. Это выражение не зависит от длины слова и поэтому предпочтительнее, чем, например:

X & 0177700,

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

 

Операции сдвига << и >> осуществляют соответственно сдвиг вправо (влево) своего левого операнда, на число битовых позиций, задаваемых правым операндом. Таким образом, x<<2 сдвигает x влево на две позиции, заполняя освобождающиеся биты нулями, что эквивалентно умножению на 4.

Операции сдвига выполняются для всех разрядов с потерей выходящих за границы битов. Если выражение1 имеет тип unsigned, то при сдвиге вправо освобождающиеся разряды гарантированно заполняются нулями (логический сдвиг). Выражения типа signed могут, но не обязательно, сдвигаться вправо с копированием знакового разряда (арифметический сдвиг). При сдвиге влево освобождающиеся разряды всегда заполняются нулями. Выражение2 не должно быть отрицательно.

Операции сдвига вправо на k разрядов весьма эффективны для деления, а сдвиг влево - для умножения целых чисел на 2 в степени k:

x<<1 « x*2

x>>1 « x/2

x<<3 « x*8

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

 

Двуместные операции над битами (&, |, ^, <<, >>) могут использоваться в сокращенных формах записи операции присваивания:

int i,j,k;

. . .

i |= j « i = i | j - включение в поле i единиц из поля j;

i &= 0xFF « i = i & 0xFF - выделение в поле i единиц по маске поля 0x00FF;

k ^= j - выделение в поле k отличающихся разрядов в полях k и j;

i ^= i - обнуление всех разрядов поля i .

 

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

В математическом смысле операнды логических операций над битами можно рассматривать как отображение некоторых множеств с размерностью не более разрядности операнда на значения {0,1}.

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

~ - дополнение; | - объединение; & - пересечение.

Простейшее применение - проверка нечетности целого числа:

int i;

...

if (i &1) printf(" Значение i нечетно!");

 

Комбинирование операций над битами с арифметическими операциями часто позволяет упростить выражения. Например, получение размера области в блоках размером 16 байт для размещения объекта размером x байт:

(x + 15)>>4

Другие возможности оперирования над битами:

- использование структур с битовыми полями;

- доступ к битам как разрядам арифметических данных.

 

Кодирование программы.

Команда размещается в комбинированной ячейке следующим образом. Первый байт содержит код операции (КОП) (например + или – или *), которую необходимо…  

Регистры

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

Строки как нуль-терминированные массивы char.

А) Основные понятия.

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

Нулевой байт – это байт, каждый бит которого равен нулю, при этом для нулевого байта определена символьная константа ´´ (признак окончания строки, или нуль-терминатор). По нулевому байту функции, работающие со строками, определяют место окончания строки. Если они читают строку, то воспринимают ее только до первого нуль-терминатора; если они создают строку, то записывают нуль-терминатор в ее конец.

Поэтому, если строка должна содержать максимум k символов, то в описании массива необходимо указать k+1 элемент. Например, char a[7]; - означает, что строка может содержать от 0 до 6 символов, а один байт будет занимать нуль-терминатор.

Строковая константа – это набор символов, заключенных в двойные кавычки, например: "Работа со строками". Такие константы хранятся именно как массивы типа char. В конце строковой константы явно указывать символ ´´ не нужно (он будет добавлен автоматически в ходе компиляции).

Строковые константы можно использовать при инициализации массивов:

сhar S[]="Работа со строками";

В этом примере размер массива не указан, поэтому он будет определен автоматически (19 элементов).

 

Б) Ввод-вывод строк - массивов char.

scanf() (см. тему "Функции ввода-вывода"; спецификатор ввода %s; символ «&» перед именем массива типа char указывать не надо).Значения… gets(char *S)(запись"char *S" означает, что на ее место нужно… Обе функции автоматически ставят в конец строки нулевой байт.

В) Поэлементная работа со строками.

Пример поэлементной работы со строкой - массивом char : Задача 1. В строке заменить все пробелы на символы подчеркивания.  

Г) Копирование, сцепление (конкатенация), сравнение, поиск строк - массивов char.

Описание прототипов стандартных функций работы со строками - массивами char находится в файле string.h.

Рассмотрим наиболее часто используемые функции (перед именем функции указывается тип ее результата, в скобках - количество и типы параметров).

 

Функция int strlen(char *S) возвращает длину строки (количество символов в строке), при этом завершающий нулевой байт не учитывается.

Пример:

 

char S1[]="Минск!", S2[90]="БГУИР-Ура!";

printf("%d, %d .", strlen(S1), strlen(S2));

 

Результат выполнения данного участка программы:

6 , 10 .

 

Функция strcpy(char *S1, char *S2) - копирует содержимое строки S2 в строку S1. Она заменяет операцию присваивания, непригодную для массивов в языке С.

Пример:

 

char s[50], t[30];

strcpy(t, "Hello!");

strcpy(s,t);

 

Функция strcat(char *S1, char *S2) - присоединяет строку S2 к концу строки S1 и помещает ее в массив, где находилась строка S1, при этом строка S2 не изменяется. Нулевой байт, который завершал строку S1, заменяется первым символом строки S2. (Такая операция называется также конкатенацией или сцеплением строк).

Пример:

 

char a[20], b[10];

strcpy(a, "Hello,");

strcpy(b, "world!");

strcat(a,b);

puts(a);

 

Результат выполнения данного участка программы:

Hello,world!

 

Функция int strcmp(char *S1, char *S2) сравнивает строки S1 и S2 . Результат функции - целое число, меньшее 0, если значение S1 предшествует значению S2 в "алфавитном" порядке; большее 0, если S2 предшествует S1; равное 0, если строки равны, т.е. содержат одно и то же число одинаковых символов.

Под "алфавитным порядком" здесь понимается порядок возрастания кодов символов (см. тему "Кодирование символов", кодовые таблицы). Как можно видеть из этих таблиц, этот порядок совпадает с алфавитным для латинских букв одинакового регистра, а также для русских букв одинакового регистра (кроме буквы 'ё'). Если первые символы S1 и S2 совпадают, то учитываются следующие символы и т.д., по обычным алфавитным правилам.

Функция int stricmp(char *S1, char *S2) делает то же, что и strcmp, но нечувствительна к регистру букв (например, 'D' и 'd' для нее - одинаковые символы).

Пример:

 

char a[20], b[10];

gets(a);

gets(b);

if (!strcmp(a,b))

puts("a==b!");

else

if (strcmp(a,b)<0)

puts("a<b!");

else puts("b<a!");

 

Для a="Москва", b="Могилев" результат будет:

b<a!

 

Функции:

- strncpy(char *S1, char *S2, int max);

- strncat(char *S1, char *S2, int max);

- int strncmp(char *S1, char *S2, int max);

- int strnicmp(char *S1, char *S2, int max);

делают то же, что и соответствующие им функции strcpy, strcat, strcmp, stricmp, но, если длина строки S2 превышает max, то они используют только первые max ее символов. В этом случае нуль-терминатор в конце полученной строки может не быть поставлен автоматически.

 

Существует также функция для поиска заданной подстроки в заданной строке (подстрокой называется подряд идущая часть строки):

char * strstr(char *S1, char *S2);

Здесь S1 - строка, в которой будет проводиться поиск; S2 - искомая подстрока. Результатом функции является указатель (см. тему "Указатели") на первое (считая слева направо) местоположение подстроки S2 в строке S1, либо NULL, если S2 не встречается в S1.

Д) Перевод строк - массивов char в числа и наоборот.

- целое: int atoi(char *S); - длинное целое: long atol(char *S); - действительное: double atof(char *S);

Е) Примеры работы со строками - массивами char.

Задача 2. Удалить символ "с" из строки s каждый раз, когда он встречается.

 

int i,j;

for( i=j=0; s[i]; i++)

if( s[i]!=c)

s[j++]=s[i];

s[j]='';

 

Задача 3. Если строка начинается со слова "song" (неважно, в каком регистре), добавить в начало и конец ее восклицательные знаки.

 

char s[50],t[52];

int i;

...

if (!strnicmp(s, "song", 4)) {

strcpy(t,"!");

strcat(t, s);

strcat(t, "!");

strcpy(s,t);

}

Если s было равно "Song 1", то оно станет равно "!Song 1!".

Другой вариант решения этой же задачи:

 

if (!strnicmp(s, "song", 4)) {

sprintf(t,"!%s!",s);

strcpy(s,t);

}

 

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

 

n=strlen(s);

for (i=0; i<n; i++)

if (s[i]=='.'){

 

// Выделение целой части вещественного числа

for (j=i-1; j>=0; j--)

if (s[j]<'0' || s[j]>'9') break;

//(Вначале была проверка j>=0, т.к. иначе s[j] недопустимо)

 

// Выделение дробной части вещественного числа

for (k=i+1; s[k]>='0' && s[k]<='9'; k++);

//(Незачем проверять на k<n - при k=n s[k]=0, а 0 <'0')

 

if (k==i+1 && j==i-1) //Если ни целой, ни дробной части

continue; // то переходим к следующей итерации for(i)

 

for (j++, p=0; j<k; j++,p++)

t[p]=s[j]; // Копируем в t подстроку от j+1 до k-1

t[p]=0; // Ставим нуль-терминатор

puts(t);

i=k; // Смещаем i за конец подстроки

}

 

Русификация консольных приложений.

функцию CharToOem(char *S1, char *S2) для преобразования символов из кодировки ANSI в кодировку ASCII (S1 - исходная строка, S2 - результат); функцию OemToChar(char *S1, char *S2) для обратного преобразования (S1 -… Эти функции находятся в библиотеке windows.h. Приведем пример их использования.

Строки как переменные типа AnsiString.

А) Основные понятия.

В оконных компонентах и функциях C++ Builder'а основным строковым типом является тип AnsiString, который обозначается также просто как String (первая буква большая! Тип string (с малой буквы) - это уже другой похожий тип, из библиотеки STL).

Работа с типом String чаще всего удобнее, чем с массивом char. Однако скорость выполнения операций при работе с большими объемами данных может быть ниже.

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

Отдельные символы, входящие в строку типа String, имеют тип char. К ним можно обращаться так же, как к элементам массива, но нумерация начинается с единицы:

 

String V="Привет!";

char c,d;

c=V[1]; // c='П'

d=V[7]; // c='!'

 

Текст в переменной типа String не обязательно заканчивается нуль-терминатором. Более того, попытка обращения к несуществующему символу строки (например, в вышеприведенном образце V[0], или V[8]) может вызвать ошибку (в то время как выход за границы массива не проверяется). Поэтому изменять длину строки (например, добавить в нее новый символ) нужно не присваиванием значений отдельным символам, а другими способами, описанными ниже.

 

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

 

String s=Edit1->Text;

Edit2->Text=s;

Memo1->Lines->Add(s);

 

 

Б) Действия над типом String.

  1) Присваивание: S1=S2;  

В) Примеры решения задач.

Задача 5. В Edit1 заменить все пробелы на символы подчеркивания.

 

String S=Edit1->Text;

for (int i=1; i<=S.Length(); i++)

if (S[i]==' ') S[i]='_';

Edit1->Text=S;

 

Задача 6. Заменить в строке все слова "NO" на "YES" (заменять только целые слова):

 

s=" "+s+" "; // иначе 1-е и последнее слова не узнает

while(i=s.Pos(" NO ")) { //Пока Pos!=0, i присв. Pos

s.Delete(i,4);

s.Insert(" YES ",i);

}

s=s.SubString(2,s.Length()-2);//теперь удаляем пробелы

 

 

Задача 7. Заменить в строке все слова "NO" (без учета регистра) на "YES" (большими буквами); заменять только целые слова:

 

s=" "+s+" "; // иначе 1-е и последнее слова не узнает

while(i=AnsiUpperCase(s).Pos(" NO ")) {

s.Delete(i,4);

s.Insert(" YES ",i);

}

s=s.SubString(2,s.Length()-2);//теперь удаляем пробелы

 

 

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

 

String s,t,r,w[90];

int i,j,n=0,k;

s=Edit1->Text+" ";

for (i=1; i<=s.Length(); i++)

if (s[i]!=' ')

t+=s[i];

else

if (t!="") {

w[n++]=t;

t="";

}

 

for (k=i=0; i<n; i++)

if (AnsiUpperCase(w[i])< AnsiUpperCase(w[k])) k=i;

w[k]=AnsiUpperCase(w[k]);

 

for (i=n-1; i>=0; i--)

r+=w[i]+(i==0? "" : " " );

Edit1->Text=r;

 

 

Преобразования строковых типов.

А) Преобразование из массива char в String и наоборот.

  char c[]="Привет!"; String s=c;

Б) Преобразование из String в простую переменную типа char.

  String s='*'; char c=s[1];

Тип String в консольных приложениях.

В консольном приложении использование типа String требует подключения библиотек (достаточно библиотеки vcl):

 

#include<vcl.h>

 

(Эта строчка может быть добавлена автоматически).

Для консольного ввода-вывода String должен быть преобразован в массив char и/или наоборот (см. выше). В частности, ввод с консоли лучше производить вначале во вспомогательный массив char, а уже оттуда (если нужно) - в String.

 

Задача 9. Во введенной с консоли строке заменить все пробелы на символы подчеркивания, используя тип String.

 

#include<vcl.h>

#include<stdio.h>

#include<conio.h>

 

void main(){

String t;

int i;

 

puts("Vvedite stroku s probelami:");

char s[100];

gets(s);

t=s;

 

for(i=1; i<=t.Length(); i++)

if (t[i]==' ') t[i]='_';

 

puts(t.c_str());

 

getch();

}

 


Функции пользователя и классы памяти.

Сущность и предназначение функций.

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

Определение и вызов функции.

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

 

Тип_результата имя_функции ( список параметров )

код функции return выражение; }

Прототип функции.

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

Область видимости.

В сложных программах ограничение этой области помогает избежать путаницы, вызванной использованием одинаковых имен в разных местах для разных целей.… Основное правило видимости в языке Си: объект, объявленный внутри блока… Область действия локальных данных – от точки декларации (объявления) до конца функции (блока), в которой произведено…

Классы памяти объектов в языке Cи.

Класс памяти определяет время жизни объекта и место его размещения в памяти (относительно границ сегмента памяти, выделенного для программы в целом).

Существует 4 класса памяти: static(статический),extern(внешний), auto(автоматический),register(регистровый).

Объекты, имеющие класс памяти static, существуют (занимают место в памяти) в течение всего времени работы программы. Их место в памяти определяется на этапе компиляции. К ним по умолчанию относятся глобальные переменные.

Объекты, имеющие класс памяти auto, существуют (занимают место в памяти) лишь во время выполнения блока, в котором объявлены. К ним по умолчанию относятся локальные переменные. Память для них выделяется при входе в блок в пределах определенной области, называемой программным стеком. После выхода из блока занимаемая ими память освобождается и может быть использована для других автоматических переменных. Это позволяет экономить память, а также осуществлять рекурсию (при вызове функцией самой себя создается новая копия всех ее автоматических переменных). Но это приводит к тому, что при повторном входе в блок значение, ранее присвоенное переменной, теряется.

Кроме того, общий размер стека должен быть задан на этапе компиляции и, таким образом, ограничен; в большинстве компиляторов по умолчанию (если не менять настроек) он равен всего лишь 1 мегабайт.

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

Класс памятиextern похож наstatic,но означает, что объект объявлен позже в этом или другом файле (см. ниже). Он может применяться к глобальным переменным.

Класс памяти register похож на auto; но он рекомендует компилятору разместить (если возможно) переменную не в стеке, а непосредственно в регистрах процессора. Регистровая память позволяет увеличить быстродействие программы, но к размещаемым в ней объектам в языке Си (но не С++) не применима операция получения адреса «&».

Обычно класс памяти переменных определяется по умолчанию (static либо auto). Но программист имеет возможность поменять класс памяти объекта, указав требуемый класс при объявлении перед его типом (в пределах допустимого по смыслу - например, глобальная переменная не может быть auto или register).

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

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

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

 

Инициализация объектов классов памяти static и extern происходит один раз при запуске программы. Если инициализация не указана, объект обязательно инициализируется нулем (или пустым значением - для нечисловых переменных).

Инициализация объектов классов памяти auto или register происходит каждый раз при входе в блок, где они объявлены. Если инициализация не указана, объект не инициализируется; его начальное значение тогда непредсказуемо. Исключение составляют переменные некоторых типов (например, String тогда инициализируется пустым значением).

 

Пример 1:

#include <stdio.h>

void f1(int);

void main(void)

{

f1(5);

}

void f1(int i)

{

int m=0;

puts(" n m p ");

while (i--) {

static int n = 0;

int p = 0;

printf(“ %d %d %d n”, n++ , m++ , p++);

}

}

 

В результате выполнения программы получим:

n m p

0 0 0

1 1 0

2 2 0

3 3 0

4 4 0

 

Статическая переменная n будет создана в сегменте данных ОЗУ и проинициализируется нулем только один раз при первом выполнении оператора, содержащего ее определение, т.е. при первом вызове функции f1. Автоматическая переменная m инициализируется при каждом входе в функцию. Автоматическая переменная р инициализируется при каждом входе в блок цикла.

Задача 3. Условие задачи 2, но в начале каждой строки из звездочек выводить ее порядковый номер.

Функция st() примет вид:

 

void st(void){

static int k;

cout<<"n"<<++k<<") ********************************n ";

}

 

Результат работы программы (подчеркнуто число, введенное человеком):

 

Vvedite x :

3

1) ********************************

sin=0.14112

2) ********************************

cos=-0.989992

3) ********************************

tg=-0.142547

4) ********************************

ctg=-7.015253

 

 

Разбиение программы на модули.

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

Структуры и объединения

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

 

Понятие структуры

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

Декларация структурного типа данных

struct ID структурного типа { описание полей };

Объявление структурных переменных

Способ 1. В любом месте программы для декларации структурных переменных, массивов, функций и т.д., используется объявленный в шаблоне структурный… struct Stud_type student; - структурная переменная; Stud_type Stud[100]; - массив структур;

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

ID_структуры .ID_поля   Пример обращения для объявленного ранее шаблона:

Операции со структурой как единым целым

Для структур одинакового типа допустимы следующие операции:

1) Присваивание: s1=s2;

2) Сравнение на равенство и неравенство: s1==s2 либо s1!=s2

3) Подстановка в качестве аргумента на место параметра функции:

int Fun(Stud_type W);

...

int d=Fun(S1);

 

Вложенные структуры

Например, в структуре person, содержащей сведения - Ф.И.О., дата рождения, можно сделать дату рождения внутренней структурой date по отношению к… struct date { int day, month, year;

Массивы структур

struct person spisok[100]; // spisok - массив структур Или можно записать так: struct person {

Размещение структурных переменных в памяти

Выравнивание (align) означает, что компилятор выбирает адреса переменных (в т.ч. полей структуры) так, чтобы они были кратны некоторой величине. Эта…  

Битовые поля

Битовые поля содержат заданное количество бит, необязательно кратное байту, но не превосходящее количество бит в типе int (в С++ Builder'е - 32).… Объявление знакового битового поля имеет вид: int a:размер;

Объединения

Объединенный тип данных декларируется подобно структурному типу: union ID_объединения { описание полей

Генерация псевдослучайных чисел.

К числу функций, обеспечивающих генерацию псевдослучайных чисел, относятся random() и randomize(). Для их работы необходимо подключить заголовочный… Собственно генерацию выполняет функция random. При обращении к ней в скобках… Если нам нужно случайное целое число в другом диапазоне, то достаточно прибавить к значению random() начало требуемого…

Типы файлов.

  Примеры текстовых файлов (знак * означает любое имя файла) : Текст (*.txt)

Открытие файла

FILE*указатель на файл; FILE - идентификатор структурного типа, описанный в стандартной библиотеке…  

Закрытие файла

Для закрытия нескольких файлов введена функция, объявленная следующим образом: void fcloseall(void); Если требуется изменить режим доступа к файлу, то для этого сначала необходимо… FILE* freopen(char *имя_файла, char *режим, FILE *указатель_файла);

Запись - чтение информации

- операции посимвольного ввода-вывода; - операции построчного и форматированного ввода-вывода; - операции ввода-вывода по блокам.

А) Посимвольный ввод-вывод

(Хотя ch формально имеет тип int, при успешной работе он преобразуется в (либо из) unsigned char. Но при ошибке эти функции возвращают стандартное…

Б) Построчный и форматированный ввод-вывод

В функциях построчного ввода-вывода происходит пере­нос из файла, или в файл строк символов.     char * fgets (char *S, int m, FILE *f) - чтение из файла f в массив S строки текста, длиной…

В) Блоковый ввод-вывод

Недостатком подобного способа сохранения является то, что полученный файл не будет иметь текстовый формат, и читать из него данные в правильном виде… Блоковый ввод-вывод осуществляется следующими функциями: int … В функциях блокового ввода-вывода количество байт задается несколько сложным образом: вводится термин «блок». Это –…

Г) Проверка достижения конца файла

Вместо вышеописанных способов определения «невозможности дальнейшего чтения», для проверки достижения конца файла можно пользоваться функцией feof() :

 

intfeof(FILE *f) ;

 

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

 

Можно также использовать описанную ниже функцию filelength().

 

Д) Сброс буфера файла

  int fflush(FILE *stream); Она вызывает завершение всех действий, использующих буфер, после чего он становится пустым.

Текстовые файлы

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

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

Эти потоки имеют тип FILE * , и их удобство в том, что их можно перенаправлять функцией freopen() , например:   freopen("results.txt", "w", stdout);

Бинарные файлы

Пример: Сохранить, а затем прочесть из файла значения целых переменных i и j .   int i,j;

Дополнительные полезные функции

Функция fseek может многократно ускорять и упрощать работу с файлами, т.к. она позволяет начать чтение/запись в файле сразу с требуемого места.… При расчете смещения для fseek может пригодиться операция sizeof.  

Простейший пример создания собственной базы данных

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

Пример программы работы с файлом структур:

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

 

#include<stdio.h>

#include<conio.h>

#include<iostream.h>

 

struct Sved {

char Fam[30];

float S_Bal;

} zap,zapt;

char Spis[]="Sp.dat";

FILE *F_zap;

FILE* Open_file(char *, char *);

void main (void) {

int i, j, kodR, nom, size = sizeof(Sved);

while(1) {

puts("Sozdanie - 1nProsmotr - 2nDobavlenie - 3nVyhod - 0");

switch(kodR = getch())

{

case '1': case '3':

if(kodR==1) F_zap = Open_file (Spis,"w+");

else F_zap = Open_file (Spis,"a+");

while(2) {

cout << "n Fam "; cin >> zap.Fam;

if((zap.Fam[0])=='0') break;

cout << "n Srednij ball: ";

cin >> zap.S_Bal;

fwrite(&zap,size,1,F_zap);

}

fclose(F_zap);

break;

case '2': F_zap = Open_file (Spis,"r+"); nom=1;

while(2) {

if(!fread(&zap,size, 1, F_zap)) break;

printf(" %2d: %20s %5.2fn", nom++, zap.Fam, zap.S_Bal);

}

fclose(F_zap);

break;

case '0': return; // exit(0);

} // Конец While(1)

} // Конец switch

} // Конец программы

 

// Нижеприведенная функция служит для того же, что и fopen(), но при

// неудаче выдает на экран ошибку

FILE* Open_file(char *file, char *kod)

{

FILE *f;

if(!(f = fopen(file, kod)))

{

puts("File isn't created!");

getch();

exit(1);

}

else return f;

}


Указатели

Определение указателей

При обработке декларации любой переменной, например double x=1.5; компилятор выделяет для переменной участок памяти, размер которого определяется ее… Разработчик программы на языке Си имеет возможность определить собственные… Итак, указатель – это переменная, которая может содержать адрес некоторого объекта. Простейшая декларация указателя…

Связь указателей и массивов.

Пусть объявлен массив a из 5 целочисленных элементов:   int a[5];

Операции над указателями (косвенная адресация)

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

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

p+i

p-i

p+=i

p-=i

p++

p--

где: p - указатель, i - целочисленное выражение.

 

Допускается также вычитание указателей:

p1-p2

где p1, p2 - указатели. Результатом вычитания является целое число.

 

Чтобы программист не был вынужден при этом каждый раз учитывать размер элемента, в языке Си принято правило: все арифметические операции с указателями выполняются в единицах памяти того типа объекта, на который ссылается этот указатель. Иными словами, операция p++ означает реальное увеличение p не на единицу, а на sizeof(*p) ; при этом p как раз будет указывать на следующий элемент массива. Аналогично, выражение p+i означает в действительности p+ i*sizeof(*p) , т.е. смещение на i элементов.

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

 

int a[5], *q=a; // i указывает на a[0]

double *d=(double*)q;

// Теперь d=q (не считая разницы в типах)

q++; d++;

// теперь d>q, т.к. хранимый в d адрес

// увеличился на 8, а хранимый в q - на 4

q++; // а теперь снова d=q, и равно &a[2]

 

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

В силу сказанного выше, адрес i-го элемента массива A всегда можно записывать и как &A[i], и как A+i . Итак, для массивов запись A[i] и *(A+i) эквивалентна. Для удобства операций с указателями, в языке С введено такое же правило записи и для них:

p[i] равносильно *(p+i)

где p - указатель, i - целочисленное выражение.

Иными словами, для обращения к i-му (считая от места, куда указывает p) элементу массива вместо записи *(p+i) можно писать короче: p[i]. Соответственно, и для указателей, и для массивов запись *p эквивалентна p[0]

Пример:

int a[5], *q=a; // Инициализация q: q указывает на a[0]

//(Здесь * перед q означает объявление его типа(указатель), а не разадресацию)

q++;

*q=40; // Означает a[1]=40;

q[3]=70; // Означает a[4]=70;

q[-1]=22; // Означает a[0]=22;

 

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

 

char s[]="Hello, world!";

cout<<s+7; // Будет выведен текст: world!

 

 

Операции над указателями (косвенная адресация)

При сравнении указателей могут использоваться отношения любого вида (">", ">=", "<", "<=", "==", "!="). Наиболее важными видами проверок являются отношения равенства или неравенства. Остальные отношения порядка имеют смысл только для указателей на последовательно размещенные объекты (элементы одного массива).

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

Любой указатель можно сравнивать со значением NULL, которое означает недействительный адрес. Значение NULL можно присваивать указателю как признак пустого указателя. NULL заменяется препроцессором на выражение (void *)0.

 

Массивы указателей.

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

int *b[10], y;

Теперь каждому из элементов массива можно присвоить адрес целочи­сленной переменной y, например: b[1]=&y;

Чтобы теперь найти значение переменной y через данный элемент массива а, необходимо записать *b[1].

 

Указатели на указатели.

int y=5; int *p1=&y; int **pp1=&p1;

Указатели как параметры функций.

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

Указатели на структуры

  struct Point{ int x,y;

Ссылка

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

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

Ссылка декларируется следующим образом:

type &ID = инициализатор;

Инициализатор - это идентификатор объекта, на который в дальнейшем будет указывать ссылка. Пример:

int a = 8;

int &r = a;

Ссылка стала "псевдонимом" объекта, указанного в качестве инициализатора. В данном примере, одинаковыми будут следующие действия:

a++;

r++;

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

void zam(int &x, int &y)

{

int t = x;

x = y;

y = t;

}

 

Участок программы с обращением к данной функции:

 

void zam (int&, int&);

void main (void)

{

int a=2, b=3;

printf(" a = %d , b = %dn", a, b);

zam (a, b);

printf(" a = %d , b = %dn", a, b);

}

 

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

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

Рассмотрим методику работы с указателями на функции. 1. Как и любой объект языка Си, указатель на функции необходимо декларировать.… тип (*переменная-указатель)(список параметров);

Переменная-указатель (список аргументов);

После таких действий кроме стандартного обращения к функции:

ID_функции(список аргументов);

появляется еще два способа вызова функции:

(*переменная-указатель)(список аргументов);

или

Переменная-указатель (список аргументов);

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

Например, в теле вышеприведенной функции FunOut вызов функции по указателю может выглядеть так:

 

double zz=p_f('@', 3.14159);

 

В вышеприведенном примере тогда будет вызвана функция f1, причем C будет присвоено значение - символ @ , а D - 3.14159 .


Работа с динамической памятью

 

Динамическое выделение и освобождение памяти.

Динамическое (т.е. осуществляемое в процессе работы программы) выделение памяти означает: 1) поиск свободного (т.е. не занятого пока никаким объектом) участка памяти… 2) объявление его занятым (чтобы никакая последующая операция выделения памяти не назначила его другому объекту);

Создание одномерного динамического массива.

  Формат операции new для массивов: указатель = new тип [количество] ;

Создание двуxмерного динамического массива.

Наиболее удобный способ - это представить двумерный массив как массив из массивов, т.е каждую строку матрицы - как одномерный массив. При этом под…   Пример. Создать динамическую матрицу размера n1 х n2. Присвоить каждому ее элементу значение, равное сумме номеров его…

Операция typedef

typedef тип новое_имя ; Введенный таким образом новый тип используется аналогично стандартным типам;…  

Отладка и пошаговое выполнение программы

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

– Конец работы –

Используемые теги: структура, программы, языке, этапы, выполнения, программы0.103

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

Что будем делать с полученным материалом:

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

Еще рефераты, курсовые, дипломные работы на эту тему:

Организационный этап выполнения курсовой работы 2.1 Примерная тематика курсовой работы . 3 Основной этап выполнения курсовой работы 3.1.1 Назначение и место ученого предмета дисциплины
стр Введение... Введение Реформирование национальной системы высшего образования связанное с введением нового перечня специальностей общегосударственного классификатора...

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

Понятие литературный язык. Место литературного языка среди других форм существования языка
Литературный язык это язык государственных и культурных учреждений школьного обучения радио и телевидения науки публицистики художественной... Современный литературный язык многофункционален Он используется в различных... Основные сферы использования литературного языка телевидение и кино наука и образование печать и радио...

Разработка алгоритмов и программ выполнения операций над последовательными и связанными представлениями структур данных
Дуги орграфов образуют неупорядоченные списки. Орграфы задаются неупорядоченными списками смежных вершин - номеров вершин, в которые ведут ребра из… Особенности представления данных Последовательное представление данных… Таким образом, для каждого графа должно вводится в общей сложности N нолей.

Билет №1 Линейные программы. Структура программ Паскаль
Билет... Способы изображения алгоритмов... Алгоритм заранее заданное точное предписание возможному ис полнителю совершить определ нную последовательность...

Два объекта истории русского языка: живой язык диалектный и литературный язык
Новые общественные функции приобретает русский язык по мере сложения новой исторической общности советского народа он становится межнациональным... Современный период... Горшкова Хабургаев ИГРЯ...

Ля начала мы разберёмся с отладкой твоих программ. Отладка – пошаговое выполнение операций программы. В этом режиме, каждая строчка кода
Глава Дополнительная информация... Тестирование и отладка...

А.Л. Лось Пособие включает разделы: языкознание как наука о языке, сущность и структура языка, лексикология, фонетика, грамматика, письменность и графические
Предисловие... Настоящее пособие написано в соответствии с программой по курсу Введение в... Автор стремился достаточно полно изложить материал курса осветить наиболее существенные проблемы стоящие перед...

Рабочая программа учебной дисциплины Основная образовательная программа
ВЛАДИВОСТОКСКИЙ ГОСУДАРСТВЕННЫЙ УНИВЕРСИТЕТ ЭКОНОМИКИ И СЕРВИСА... ИНСТИТУТ ПРАВА И УПРАВЛЕНИЯ... КАФЕДРА МЕНЕДЖМЕНТА...

0.041
Хотите получать на электронную почту самые свежие новости?
Education Insider Sample
Подпишитесь на Нашу рассылку
Наша политика приватности обеспечивает 100% безопасность и анонимность Ваших E-Mail
Реклама
Соответствующий теме материал
  • Похожее
  • По категориям
  • По работам