Архив рубрики: Уроки

Занятие №20. Некоторые особенности цикла for. Оператор последовательного вычисления.

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

Кроме того, мы помним, что после цикла всегда стоит один оператор, но если нам необходимо в теле цикла выполнить несколько действий, то мы можем использовать составной оператор {}. Все что заключено в фигурные скобки, будет считаться за один единственный оператор.
Если вы еще не забыли, а это не мудрено с моей-то частотой выпуска занятий, то последнем уроке мы изучали двойные массивы. Одной из типичных задач является вывод массива размерности [N][M] на экран в виде таблицы (или матрицы) в N-строк и M столбцов. Для этой задачи очень крайне удобно использовать два вложенных цикла for. Например:
Листинг 1.
#include <stdio.h>
#include <stdlib.h>
#define N 10
#define M 8

int main(void){
    int arr[N][M];
   
    for (int i=0;i<N;i++)
        for (int j=0; j<M; j++)
            arr[i][j]=rand();
   
    for (int i=0;i<N;i++)
        for (int j=0; j<M; j++)
            printf("%d\t",arr[i][j]);
   
return (0);
}

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

#include <stdio.h>
#include <stdlib.h>
#define N 10
#define M 8

int main(void){
    int arr[N][M];
   
    for (int i=0;i<N;i++)
        for (int j=0; j<M; j++)
            arr[i][j]=rand();
   
    for (int i=0;i<N;i++){
        for (int j=0; j<M; j++)
            printf("%d\t",arr[i][j]);
        printf("\n");
    }
return (0);
}
Вот так уже лучше. Так, наша программа будет выводить все так, как нужно. Вот, посмотрите на следующую картинку.
Рис.1. Результат работы программы.

А теперь поговорим о красоте. Этот вот дополнительный перенос строки , он как шило в заднице. Из-за него одного пришлось добавить составной оператор. Некрасиво получилось. Есть ли способ этого избежать. Да, есть!
Я вам раньше не говорил, а теперь скажу. В заголовке цикла for на месте инициализации счетчика и на месте изменения счетчика, могут стоять не одна, а сразу несколько инструкций. Чтобы их туда записать нужно использовать еще один оператор, оператор последовательного выполнения – ,.  Да-да, просто запятая. 
Как это может помочь нам. Да вот как. Смотрите, мы добавляем перенос, на каждой новой итерации внешнего цикла, вот и добавим наш printf в блок изменение счетчика.
Листинг 3.
#include <stdio.h>
#include <stdlib.h>
#define N 10
#define M 8

int main(void){
    int arr[N][M];
   
    for (int i=0;i<N;i++)
        for (int j=0; j<M; j++)
            arr[i][j]=rand();
   
    for (int i=0;i<N;i++,printf("\n"))
        for (int j=0; j<M; j++)
            printf("%d\t",arr[i][j]);
       

return (0);
}

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

Рассмотрим один показательный пример. Используем для вывода двойного массива на экран  один цикл for. Это будет выглядеть например так:
Листинг 4:
#include <stdio.h>
#include <stdlib.h>
#define N 10
#define M 8

int main(void){
    int arr[N][M];
   
    for (int i=0;i<N;i++)
        for (int j=0; j<M; j++)
            arr[i][j]=rand();
   
    for (int i=0, j=0 ;i<N; j++){
        printf("%d\t",arr[i][j]);
        if (M-j==1){
           printf("\n");
           i++;
           j=-1; 
        }
    }  

return (0);
}
А на мониторе снова никаких изменений. ))
Рис.2. Результат работы программы.
Но такие штуки лучше не проворачивать, так как это может сильно запутать код. Я не буду подробно комментировать этот цикл, попробуйте разобраться самостоятельно, как он работает. Возьмите листочек и пошагово выполните его, будто бы вы компьютер.

UPD(от 2.02.14): Я говорил же вам, что этот код может запутать любого. Я и сам попался в эту ловушку. Записанный ранее код, был ошибочен. Стараниями внимательного читателя он исправлен, и теперь работает так, как ему и подобает. =))

Я не помню, рассказывал я о именованных константах, которые использую в своих примерах в этом уроке. На всякий случай расскажу еще раз.
Для объявления именованных констант служит директива #define. Её синтаксис таков
#define имя значение

Важно! Не нужно ставить в конце точку с запятой. А так же между именем и значением знак присвоения =.

Как работает эта инструкция, рассмотрим на нашем примере. Перед тем как компилятор начнет обрабатывать программу, он пройдется по всему коду и заменит все места, где встречается N или M и вместо них подставит в код их значения: 10 и 8 соответственно.  Причем компилятору вообще по барабану, что на что мы заменяем. С учетом этой особенности, некоторые используют при написании код «Боярский язык». Ну т.е. пишут код на обычном русском языке.  Например вот так.
Листинг 5.

#include <stdio.h>
#include <stdlib.h>

#define N 10
#define M 8
#define целое int
#define присвоить =


целое main(void){
    целое arr[N][M];
   
    for (целое i присвоить 0;i<N;i++)
        for (целое j присвоить 0; j<M; j++)
            arr[i][j] присвоить rand();
   
    for (целое i присвоить 0, j присвоить 0 ;i<M,j<N;i++){
        printf("%d\t",arr[i][j]);
        if (M-i==1){
           printf("\n");
           j++;
           i присвоить 0; 
        }
    }
return (0);
}

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

На сегодня всё. Удачи вам и красивого кода. ))

Занятие 19. Двумерные массивы.


Добрый день друзья.
Сегодня затронем новую тему. Двумерные массивы. Или их еще называют матрицы.   

Прочитайте улучшенную версию этого урока "Двумерные массивы".

В новой версии:

  • Ещё более доступное объяснение
  • Дополнительные материалы
  • 10 задач на программирование с автоматической проверкой решения

Что такое двумерный массив?

Двумерный массив это прямоугольная таблица с определенным количеством строк и столбиков.  Каждая строка и каждый столбик имеют свой порядковый номер. На рисунке изображен двумерный массив, который имеет 6 строк и 7 столбцов.
Рис.1. Двумерный массив. Общий вид.

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

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

Следующий естественный вопрос:

Как работать с двумерным массивом?

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

Листинг 19.1
int arr [2][4] = {{1,2,4,29},{3,4,6,1}};

Записанный выше код создает массив с двумя строчками и четырьмя столбцами, и присваивает каждому элементу определенные значения. Получится вот такой массив.
Рис.3. Двумерный массив инициализированный при объявлении


Кроме того, мы можем задать только некоторые элементы массива, тогда остальные будут заполнены нулями. Например:

Листинг 19.2
int arr [2][4] = {{1,2,4},{3,4}};

При таком объявлении мы получим массив, который изображен на следующем рисунке.
Рис.4. Двумерный массив, инициализированный не полностью.


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

Как работать с отдельным элементом массива.

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

Рис.5. Обращение к элементу двумерного массива.

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

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

Листинг19.3
#include <stdio.h> 
int main (){ 
      int arr [2][4] = {{1,2,4},{3,4}}; 
      for (int i=0; i<2; i++){ // 1 цикл 
            for (int j=0; j<4; j++) // 2 цикл 
                  printf("%d\t",arr[i][j]); 
            printf("\n"); 
      } 
return 0; 
}  
Данная программа, выводит последовательно все элементы массива. Результат её работы, представлен на следующем рисунке.
Рис.6. Двумерный массив. Поэлементный вывод на экран.

Как  работает вложенный цикл, вам уже должно быть понятно.
Сначала переменной i присваивается значение нуль, проверяется условие 0<2, и так как оно выполняется, программа начинает выполнять тело первого цикла. В теле первого цикла программа опять попадает в цикл, теперь уже второй. Переменной j присваивается значение 0 и проверяется условие 0<4. Оно истинно, поэтому выполняется тело второго цикла. Оно состоит из одной инструкции вывода на экран элемента arr[i][j]. Так как на данном шаге у нас i=0 j=0, то выводится значение элемент из нулевой строки и нулевого столбика. В нашем примере это элемент число 1. Тело второго цикла закончилось, происходит увеличение j на единицу j=1.
Проверка условия 1<4. Выполнение тела второго цикла: вывод на экран элемента arr[0][1] в нашем случае это 2. И так далее …
Используя вложенные циклы, мы можем и заполнять двумерные массивы.  

Зачем нужны двумерные массивы?

Двумерные массивы используются для хранения данных одинакового типа и одинаковой структуры. Приведу пример. В одном из практических заданий к прошлым урокам,  необходимо было составить программу, которая хранила оценки ученика по разным предметам. Для их хранения в памяти компьютера мы использовали несколько одномерных массивов. Это было не очень удобно. Для этих же целей, мы могли использовать двумерный массив. Например, нам нужно хранить оценки ученика по русскому, математике, физике и информатике, за целый месяц. Для этого мы создадим массив с 4 строками и 30 столбиками (или наоборот, 30 строк и 4 столбика, кому как больше нравится), и в каждую отдельную строчку будем записывать оценки по какому либо предмету. Это гораздо удобнее и практичнее.
Например, рассмотрим такую задачку. Пусть нам нужно было бы найти среднее арифметическое всех оценок по всем предметам.  Если бы у нас было четыре одномерных массива, то нам пришлось бы писать четыре цикла, в каждом из них считать среднее арифметическое оценок по одному предмету, потом  находить среднее арифметическое этих четырех чисел. Если оценки у нас хранятся в двумерном массиве, нам достаточно будет просто пройтись по всем элементам массива, используя два вложенных цикла for, и найти среднее арифметическое.
Но это еще не всё. Представьте, что вы хотите добавить один предмет и снова посчитать среднюю арифметическую оценку для всех предметов.   Сколько мороки будет, если вы храните оценки в одномерных массивах. Вам придется кучу изменений вносить, дописывать еще один цикл (а вдруг добавилось 7 предметов?). Потом еще среднее искать между этими цифрами.
Другое дело если вы храните оценки в двумерном массиве. Всего лишь изменить условие в одном из циклов. Поменять одно число, представляете как удобно?


Напишите в комментариях пожалуйста, понятен ли вам этот пример, или лучше его подробно расписать?

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

Практическое задание.

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

Готовое решение пользователя с ником "Дмитрий". За проявленное упорство и трудолюбие, и как первый выполнивший правильно практическое задание, Дмитрий награждается печенькой:

Занятие 19. Двумерные массивы.


Добрый день друзья.
Сегодня затронем новую тему. Двумерные массивы. Или их еще называют матрицы.   

Прочитайте улучшенную версию этого урока "Двумерные массивы".

В новой версии:

  • Ещё более доступное объяснение
  • Дополнительные материалы
  • 10 задач на программирование с автоматической проверкой решения

Что такое двумерный массив?

Двумерный массив это прямоугольная таблица с определенным количеством строк и столбиков.  Каждая строка и каждый столбик имеют свой порядковый номер. На рисунке изображен двумерный массив, который имеет 6 строк и 7 столбцов.
Рис.1. Двумерный массив. Общий вид.

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

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

Следующий естественный вопрос:

Как работать с двумерным массивом?

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

Листинг 19.1
int arr [2][4] = {{1,2,4,29},{3,4,6,1}};

Записанный выше код создает массив с двумя строчками и четырьмя столбцами, и присваивает каждому элементу определенные значения. Получится вот такой массив.
Рис.3. Двумерный массив инициализированный при объявлении


Кроме того, мы можем задать только некоторые элементы массива, тогда остальные будут заполнены нулями. Например:

Листинг 19.2
int arr [2][4] = {{1,2,4},{3,4}};

При таком объявлении мы получим массив, который изображен на следующем рисунке.
Рис.4. Двумерный массив, инициализированный не полностью.


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

Как работать с отдельным элементом массива.

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

Рис.5. Обращение к элементу двумерного массива.

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

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

Листинг19.3
#include <stdio.h> 
int main (){ 
      int arr [2][4] = {{1,2,4},{3,4}}; 
      for (int i=0; i<2; i++){ // 1 цикл 
            for (int j=0; j<4; j++) // 2 цикл 
                  printf("%d\t",arr[i][j]); 
            printf("\n"); 
      } 
return 0; 
}  
Данная программа, выводит последовательно все элементы массива. Результат её работы, представлен на следующем рисунке.
Рис.6. Двумерный массив. Поэлементный вывод на экран.

Как  работает вложенный цикл, вам уже должно быть понятно.
Сначала переменной i присваивается значение нуль, проверяется условие 0<2, и так как оно выполняется, программа начинает выполнять тело первого цикла. В теле первого цикла программа опять попадает в цикл, теперь уже второй. Переменной j присваивается значение 0 и проверяется условие 0<4. Оно истинно, поэтому выполняется тело второго цикла. Оно состоит из одной инструкции вывода на экран элемента arr[i][j]. Так как на данном шаге у нас i=0 j=0, то выводится значение элемент из нулевой строки и нулевого столбика. В нашем примере это элемент число 1. Тело второго цикла закончилось, происходит увеличение j на единицу j=1.
Проверка условия 1<4. Выполнение тела второго цикла: вывод на экран элемента arr[0][1] в нашем случае это 2. И так далее …
Используя вложенные циклы, мы можем и заполнять двумерные массивы.  

Зачем нужны двумерные массивы?

Двумерные массивы используются для хранения данных одинакового типа и одинаковой структуры. Приведу пример. В одном из практических заданий к прошлым урокам,  необходимо было составить программу, которая хранила оценки ученика по разным предметам. Для их хранения в памяти компьютера мы использовали несколько одномерных массивов. Это было не очень удобно. Для этих же целей, мы могли использовать двумерный массив. Например, нам нужно хранить оценки ученика по русскому, математике, физике и информатике, за целый месяц. Для этого мы создадим массив с 4 строками и 30 столбиками (или наоборот, 30 строк и 4 столбика, кому как больше нравится), и в каждую отдельную строчку будем записывать оценки по какому либо предмету. Это гораздо удобнее и практичнее.
Например, рассмотрим такую задачку. Пусть нам нужно было бы найти среднее арифметическое всех оценок по всем предметам.  Если бы у нас было четыре одномерных массива, то нам пришлось бы писать четыре цикла, в каждом из них считать среднее арифметическое оценок по одному предмету, потом  находить среднее арифметическое этих четырех чисел. Если оценки у нас хранятся в двумерном массиве, нам достаточно будет просто пройтись по всем элементам массива, используя два вложенных цикла for, и найти среднее арифметическое.
Но это еще не всё. Представьте, что вы хотите добавить один предмет и снова посчитать среднюю арифметическую оценку для всех предметов.   Сколько мороки будет, если вы храните оценки в одномерных массивах. Вам придется кучу изменений вносить, дописывать еще один цикл (а вдруг добавилось 7 предметов?). Потом еще среднее искать между этими цифрами.
Другое дело если вы храните оценки в двумерном массиве. Всего лишь изменить условие в одном из циклов. Поменять одно число, представляете как удобно?


Напишите в комментариях пожалуйста, понятен ли вам этот пример, или лучше его подробно расписать?

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

Практическое задание.

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

Готовое решение пользователя с ником "Дмитрий". За проявленное упорство и трудолюбие, и как первый выполнивший правильно практическое задание, Дмитрий награждается печенькой:

Занятие 18. Передача одномерных массивов в функцию. Возвращение массива из функции.

Добрый вечер друзья. Продолжаем изучать работу с массивами и указателями.

Сегодня научимся передавать массив в функцию и возвращать массив из функции.

Прочитайте улучшенную версию этого урока "Передача аргументов в функцию".

В новой версии:

  • Ещё более доступное объяснение
  • Дополнительные материалы
  • 12 задач на программирование с автоматической проверкой решения


Итак, начнем с первого пункта. Пусть нам необходимо передать массив в функцию и там его обработать. Ну допустим вывести его элементы на экран. Тут возможны два варианта.
1. У нас статический массив.
2. У нас динамический массив.

В зависимости от этого и нужно плясать.

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

Для определенности будем передавать массив символов. Напишем функцию, которая принимает строку и выводит её на экран.

Листинг 18.1


void print_arr(char a[], int n){
      for (int i=0; i<n; i++)
            printf("%c",a[i]);
      printf("\n");
}


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

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

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

Листинг 18.2


#include <stdio.h>
#include <string.h>
void print_arr(char a[], int n){
      for (int i=0; i<n; i++)
            printf("%c",a[i]);
      printf("\n");

}
int main(){
      char arr[]="kirill";
      int t=strlen(arr);
      print_arr(arr,t);
      return 0;
}


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

Второй случай. Передача в функцию динамического массива.

Давайте решим такую задачу. имеется текстовый файл input.txt. Пусть в нем в первой строчке записано натуральное число N не превосходящее 100. А в следующих N строках записаны некоторые вещественные числа. Пусть мы хотим посчитать среднее арифметическое всех этих чисел и вывести.

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

И так напишем требуемую программу.

Листинг 18.3
#include <stdio.h>
void sa_arr(float *a, int n){
      double summ=0;
      for (int i=0; i<n; i++)
            summ+=a[i];
      printf("srednee arifmeticheskoe: %.2f\n", summ/n);
}

int main(){
      freopen("input.txt","r",stdin);
      int N;
      scanf("%d",&N);
      float *arr = new (float [N]);
      for (int i=0; i<N; i++)
            scanf("%f", &arr[i]);
      sa_arr(arr,N);
      delete []arr;
      return 0;
}


Обратите внимание на аргументы функции. Как видите, если мы передаем динамический массив, то нам нужно явно указывать на то, что функция принимает указатель на первый элемент массива. В случае одномерного массива это не критично, и вы можете объявить аргументы как и в прошлый раз, т.е.
void sa_arr(float a[], int n)
но так делать не стоит. Приучайтесь сразу передавать динамические массивы таким образом.

Теперь вернемся к нашим отличиям а так же второму вопросу.

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

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

Листинг 18.4


#include <stdio.h>
void sa_arr(float *a, int n){
      double summ=0;
      for (int i=0; i<n; i++)
            summ+=a[i];
      printf("srednee arifmeticheskoe: %.2f\n", summ/n);
      //изменим какой-нибудь элемент массива внтури функции
      a[2]=43.2;
} 


int main(){
      freopen("D:\\input1.txt","r",stdin);
      int N;
      scanf("%d",&N);
      float *arr = new (float [N]);
      for (int i=0; i<N; i++)
            scanf("%f", &arr[i]);
      //выводим массив
      for (int i=0; i<N; i++)
            printf("%f\t",arr[i]);
      printf("\n");
      sa_arr(arr,N);    
      // снова выводим массив на экран, после вызова функции
      // внутри которой, мы изменили один его элемент
      for (int i=0; i<N; i++)
            printf("%f\t",arr[i]);
      printf("\n");
      delete []arr;
      return 0; 
}

Результат работы программы на следующем рисунке.


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

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


Задание для практической работы:

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

2. Напишите функцию, которая ищет максимальный элемент в массиве.

3. Напишите функцию, которая принимает два массива одинаковой длинны и поэлементно перемножает их. Результат работы она записывает в элементы первого массива. Т.е. буквально она должна вернуть результат свой работы в первом массиве.
В программе, НЕ В ФУНКЦИИ, выведите результат на экран.

Если мы передаем массивы
[2,5,6]
[1,4,2]

то в результате своей работы, программа должна вывести
2 20 12

Занятие 18. Передача одномерных массивов в функцию. Возвращение массива из функции.

Добрый вечер друзья. Продолжаем изучать работу с массивами и указателями.

Сегодня научимся передавать массив в функцию и возвращать массив из функции.

Прочитайте улучшенную версию этого урока "Передача аргументов в функцию".

В новой версии:

  • Ещё более доступное объяснение
  • Дополнительные материалы
  • 12 задач на программирование с автоматической проверкой решения


Итак, начнем с первого пункта. Пусть нам необходимо передать массив в функцию и там его обработать. Ну допустим вывести его элементы на экран. Тут возможны два варианта.
1. У нас статический массив.
2. У нас динамический массив.

В зависимости от этого и нужно плясать.

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

Для определенности будем передавать массив символов. Напишем функцию, которая принимает строку и выводит её на экран.

Листинг 18.1


void print_arr(char a[], int n){
      for (int i=0; i<n; i++)
            printf("%c",a[i]);
      printf("\n");
}


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

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

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

Листинг 18.2


#include <stdio.h>
#include <string.h>
void print_arr(char a[], int n){
      for (int i=0; i<n; i++)
            printf("%c",a[i]);
      printf("\n");

}
int main(){
      char arr[]="kirill";
      int t=strlen(arr);
      print_arr(arr,t);
      return 0;
}


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

Второй случай. Передача в функцию динамического массива.

Давайте решим такую задачу. имеется текстовый файл input.txt. Пусть в нем в первой строчке записано натуральное число N не превосходящее 100. А в следующих N строках записаны некоторые вещественные числа. Пусть мы хотим посчитать среднее арифметическое всех этих чисел и вывести.

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

И так напишем требуемую программу.

Листинг 18.3
#include <stdio.h>
void sa_arr(float *a, int n){
      double summ=0;
      for (int i=0; i<n; i++)
            summ+=a[i];
      printf("srednee arifmeticheskoe: %.2f\n", summ/n);
}

int main(){
      freopen("input.txt","r",stdin);
      int N;
      scanf("%d",&N);
      float *arr = new (float [N]);
      for (int i=0; i<N; i++)
            scanf("%f", &arr[i]);
      sa_arr(arr,N);
      delete []arr;
      return 0;
}


Обратите внимание на аргументы функции. Как видите, если мы передаем динамический массив, то нам нужно явно указывать на то, что функция принимает указатель на первый элемент массива. В случае одномерного массива это не критично, и вы можете объявить аргументы как и в прошлый раз, т.е.
void sa_arr(float a[], int n)
но так делать не стоит. Приучайтесь сразу передавать динамические массивы таким образом.

Теперь вернемся к нашим отличиям а так же второму вопросу.

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

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

Листинг 18.4


#include <stdio.h>
void sa_arr(float *a, int n){
      double summ=0;
      for (int i=0; i<n; i++)
            summ+=a[i];
      printf("srednee arifmeticheskoe: %.2f\n", summ/n);
      //изменим какой-нибудь элемент массива внтури функции
      a[2]=43.2;
} 


int main(){
      freopen("D:\\input1.txt","r",stdin);
      int N;
      scanf("%d",&N);
      float *arr = new (float [N]);
      for (int i=0; i<N; i++)
            scanf("%f", &arr[i]);
      //выводим массив
      for (int i=0; i<N; i++)
            printf("%f\t",arr[i]);
      printf("\n");
      sa_arr(arr,N);    
      // снова выводим массив на экран, после вызова функции
      // внутри которой, мы изменили один его элемент
      for (int i=0; i<N; i++)
            printf("%f\t",arr[i]);
      printf("\n");
      delete []arr;
      return 0; 
}

Результат работы программы на следующем рисунке.


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

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


Задание для практической работы:

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

2. Напишите функцию, которая ищет максимальный элемент в массиве.

3. Напишите функцию, которая принимает два массива одинаковой длинны и поэлементно перемножает их. Результат работы она записывает в элементы первого массива. Т.е. буквально она должна вернуть результат свой работы в первом массиве.
В программе, НЕ В ФУНКЦИИ, выведите результат на экран.

Если мы передаем массивы
[2,5,6]
[1,4,2]

то в результате своей работы, программа должна вывести
2 20 12

Урок 17. Создание динамического массива. Указатели и практическое применение.


Добрый день друзья. Сегодня у нас особый урок. Во-первых, он будет более практичный и небольшой по объему, во-вторых, он посвящен ответам на вопросы, которые мне задали в нашей группе вконтакте, и в-третьих, он будет уже использовать некоторые возможности С++. Это осознанный шаг, и я думаю, во многом, он облегчит жизнь и вам и мне. Приступим.
Начну с последнего вопроса. Как создать динамический массив. Допустим, мы пишем программу, которая вычисляет среднее значение введенных чисел. Но мы же не знаем, сколько чисел собирается ввести пользователь. Естественно мы могли бы организовать цикл и просто сохранять все веденные числа в одну переменную, а потом лишь поделить на количество введенных элементов. Но что делать, если мы хотим все эти введенные числа использовать в дальнейшем. Или хотим посчитать среднее лишь по некоторым из этих чисел. Тут-то нам на помощь и придут динамические массивы. Т.е. массивы, длину которых задает не в коде программы. На самом деле это достаточно просто.
Примерно, это выглядело бы так:
Листинг17.1
#include <stdio.h>

int main(){

      int N;

      printf("Vvedite kolichestvo dannih\N");

      scanf("%d",&N);

      int arr[N];


      return 0;

}

ВНИМАНИЕ! В современных компиляторах языка Си этот код будет работать! В Си такая возможность предполагается новым стандартом.

Но если мы сделаем так, то наша программа не скомпилируется даже. Получим ошибку, что при объявлении массива, нужно константное выражение.  Естественно, мы могли бы создать массив на 100 элементов и не париться об этом, но мы пойдем другим путём, более оптимальным.
Как я уже писал в прошлом уроке, память можно представить себе в виде последовательных ячеек с адресами. Так вот, наша программа, может работать с этой памятью почти напрямую. Т.е. мы можем выделить себе в свободной памяти некоторый кусочек и в нем работать.
Это называется выделение памяти «в куче». Для того, чтобы выделить себе некоторую область в памяти, необходимо использовать команду new(). Смотрите, как это работает.
Листинг17.2
#include <stdio.h>



int main(){

      int *num = new (int);

      *num=4;

      printf("%d \n",*num);

      return 0;

}


Результат работы это программы, представлен на следующем рисунке.

Рис.1 Выделение памяти под переменную типа int.
Команда new() выделяет необходимое количество памяти, под тип объекта, который указан в скобках. Ну т.е. в нашем случае, мы попросили выделить для нас память под одну переменную типа int. Данная команда возвращает указатель на выделенный фрагмент памяти. Поэтому мы в принципе и сохраняем её в соответствующую переменную.
С одним числом разобрались, но мы же хотели целый массив таких чисел. Да без проблем, просто укажем, что это должен быть массив из нужного нам количества переменных.
Листинг 17.3
#include <stdio.h>



int main(){

      int N; 
      printf("Vvedite kolichestvo dannih\n");

      scanf("%d",&N);

      int *arr = new (int [N]); 
      return 0;

}


Вот в таком виде наша программа уже скомпилируется и будет делать именно то, что нам нужно. Пользователь введет 20 и она создаст массив из 20 элементов типа int. Введет пять - будет массив из пяти элементов. Удобно, не правда ли? Не надо расходовать лишнюю память, создавая массив из 100 элементов из которых только пять первых будут использоваться.
А самое классно знаете что? То, что работать с этим массивом можно прямо точно так же как если бы мы создали его самостоятельно. Ну т.е. нам не потребуется добавлять звездочки и т.д. Вот, например, в следующей программе мы присваиваем вновь созданному динамическому массиву из двух элементов некоторые значения.
Листинг 17.4
#include <stdio.h>



int main(){

      int N;

      printf("Vvedite kolichestvo dannih\n");

      scanf("%d",&N);

      int *arr = new (int [N]);

      arr[0]=1;

      scanf("%d", &arr[1]);

      printf("%d %d\n", arr[0],arr[1]);

      return 0;

}

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

Как видите, все как и прежде. Это действительно удобно. Теперь немножко отвлечемся. Команда new() это команда языка С++, в чистом Си её нет. Там есть некоторый её аналог команда malloc. Но она менее удобная, чем new. Поэтому пользуйтесь.
Я думаю, всех учили убирать за собой, когда навели беспорядок. В этом плане память, как мама. Не любит, когда не убрано. Поэтому есть и еще одна команда, которая позволяет убрать за собой. Ну т.е. освободить память, которую мы заняли. Она называется delete(). Логично, да?
Передаете ей в качестве аргумента, то что вы выделили и она самостоятельно наведет уборку. Это нужно делать всегда, когда вы выделяли память. Конечно, если вы не сделаете так, ничего особенно страшного не случится. Но представьте ситуацию, что каждая программа, которую вы запускаете, сохраняет в оперативной памяти вашего компьютера какие-то данные и потом не удаляет их. Не порядок, не так ли? Или пришел к вам гость, сходил в туалет и не смыл за собой.  Ну вот и вы не сорите за собой. Я вот, кстати намусорил сейчас в вашем компьютере немного, если вы уже запускали программы из примеров. =))
Покажу на примере первой программы, как это делается.
Листинг17.5
#include <stdio.h>



int main(){

      int *num = new (int);

      *num=4;

      printf("%d \n",*num);

      delete num;

      return 0;

}

Если высвобождаете память, в которой хранился массив, то необходимо указать это.
Листинг 17.6
delete []arr;

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

Урок 17. Создание динамического массива. Указатели и практическое применение.


Добрый день друзья. Сегодня у нас особый урок. Во-первых, он будет более практичный и небольшой по объему, во-вторых, он посвящен ответам на вопросы, которые мне задали в нашей группе вконтакте, и в-третьих, он будет уже использовать некоторые возможности С++. Это осознанный шаг, и я думаю, во многом, он облегчит жизнь и вам и мне. Приступим.
Начну с последнего вопроса. Как создать динамический массив. Допустим, мы пишем программу, которая вычисляет среднее значение введенных чисел. Но мы же не знаем, сколько чисел собирается ввести пользователь. Естественно мы могли бы организовать цикл и просто сохранять все веденные числа в одну переменную, а потом лишь поделить на количество введенных элементов. Но что делать, если мы хотим все эти введенные числа использовать в дальнейшем. Или хотим посчитать среднее лишь по некоторым из этих чисел. Тут-то нам на помощь и придут динамические массивы. Т.е. массивы, длину которых задает не в коде программы. На самом деле это достаточно просто.
Примерно, это выглядело бы так:
Листинг17.1
#include <stdio.h>

int main(){

      int N;

      printf("Vvedite kolichestvo dannih\N");

      scanf("%d",&N);

      int arr[N];


      return 0;

}

ВНИМАНИЕ! В современных компиляторах языка Си этот код будет работать! В Си такая возможность предполагается новым стандартом.

Но если мы сделаем так, то наша программа не скомпилируется даже. Получим ошибку, что при объявлении массива, нужно константное выражение.  Естественно, мы могли бы создать массив на 100 элементов и не париться об этом, но мы пойдем другим путём, более оптимальным.
Как я уже писал в прошлом уроке, память можно представить себе в виде последовательных ячеек с адресами. Так вот, наша программа, может работать с этой памятью почти напрямую. Т.е. мы можем выделить себе в свободной памяти некоторый кусочек и в нем работать.
Это называется выделение памяти «в куче». Для того, чтобы выделить себе некоторую область в памяти, необходимо использовать команду new(). Смотрите, как это работает.
Листинг17.2
#include <stdio.h>



int main(){

      int *num = new (int);

      *num=4;

      printf("%d \n",*num);

      return 0;

}


Результат работы это программы, представлен на следующем рисунке.

Рис.1 Выделение памяти под переменную типа int.
Команда new() выделяет необходимое количество памяти, под тип объекта, который указан в скобках. Ну т.е. в нашем случае, мы попросили выделить для нас память под одну переменную типа int. Данная команда возвращает указатель на выделенный фрагмент памяти. Поэтому мы в принципе и сохраняем её в соответствующую переменную.
С одним числом разобрались, но мы же хотели целый массив таких чисел. Да без проблем, просто укажем, что это должен быть массив из нужного нам количества переменных.
Листинг 17.3
#include <stdio.h>



int main(){

      int N; 
      printf("Vvedite kolichestvo dannih\n");

      scanf("%d",&N);

      int *arr = new (int [N]); 
      return 0;

}


Вот в таком виде наша программа уже скомпилируется и будет делать именно то, что нам нужно. Пользователь введет 20 и она создаст массив из 20 элементов типа int. Введет пять - будет массив из пяти элементов. Удобно, не правда ли? Не надо расходовать лишнюю память, создавая массив из 100 элементов из которых только пять первых будут использоваться.
А самое классно знаете что? То, что работать с этим массивом можно прямо точно так же как если бы мы создали его самостоятельно. Ну т.е. нам не потребуется добавлять звездочки и т.д. Вот, например, в следующей программе мы присваиваем вновь созданному динамическому массиву из двух элементов некоторые значения.
Листинг 17.4
#include <stdio.h>



int main(){

      int N;

      printf("Vvedite kolichestvo dannih\n");

      scanf("%d",&N);

      int *arr = new (int [N]);

      arr[0]=1;

      scanf("%d", &arr[1]);

      printf("%d %d\n", arr[0],arr[1]);

      return 0;

}

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

Как видите, все как и прежде. Это действительно удобно. Теперь немножко отвлечемся. Команда new() это команда языка С++, в чистом Си её нет. Там есть некоторый её аналог команда malloc. Но она менее удобная, чем new. Поэтому пользуйтесь.
Я думаю, всех учили убирать за собой, когда навели беспорядок. В этом плане память, как мама. Не любит, когда не убрано. Поэтому есть и еще одна команда, которая позволяет убрать за собой. Ну т.е. освободить память, которую мы заняли. Она называется delete(). Логично, да?
Передаете ей в качестве аргумента, то что вы выделили и она самостоятельно наведет уборку. Это нужно делать всегда, когда вы выделяли память. Конечно, если вы не сделаете так, ничего особенно страшного не случится. Но представьте ситуацию, что каждая программа, которую вы запускаете, сохраняет в оперативной памяти вашего компьютера какие-то данные и потом не удаляет их. Не порядок, не так ли? Или пришел к вам гость, сходил в туалет и не смыл за собой.  Ну вот и вы не сорите за собой. Я вот, кстати намусорил сейчас в вашем компьютере немного, если вы уже запускали программы из примеров. =))
Покажу на примере первой программы, как это делается.
Листинг17.5
#include <stdio.h>



int main(){

      int *num = new (int);

      *num=4;

      printf("%d \n",*num);

      delete num;

      return 0;

}

Если высвобождаете память, в которой хранился массив, то необходимо указать это.
Листинг 17.6
delete []arr;

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

Занятие 16. Указатели.


Здравствуйте друзья.
Сегодня у нас важная и сложная тема. Возможно, самая сложная тема за все время. Указатели.

Прочитайте более простую версию этого урока "Указатели".

В новой версии:

  • Ещё более доступное объяснение
  • Дополнительные материалы
  • 10 задач на программирование с автоматической проверкой решения


Прежде вспомним основы шестнадцатеричной системы счисления.

Любое число в 16-тиричной системе счисления записывается с помощью символов 0,1,2,3,4,5,6,7,8,9,A,B,C,D,E,F. Шестнадцатеричной она называется, потому что в ней для записи различных чисел используются 16 основных цифр. Например, в привычной нами десятичной системе счисления используется десять основных цифр 0,1,2,3,4,5,6,7,8 и 9. 

Первые десять цифр в 16-тиричной системе счисления такие же, как и в десятичной системе, а вот для записи следующих шести используются буквы латинского алфавита. 
A=10
B=11
C=12
D=13
E=14
F=15.

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

Пусть у нас есть число 2D5. И мы хотим узнать, сколько это в нашей десятичной системе счисления.
Для этого сначала разделим число на цифры:
2 D5
Теперь заменим все буквы, на их числовые обозначения:
2 13 5

Пронумеруем эти цифры справа налево начиная с нуля.

2   13   5
2         1      0

Умножим каждую из цифр, на 16 в степени, соответствующей порядковому номеру. И сложим все это между собой.
Рис.1 Перевод шестнадцатеричного числа в десятичную систему счисления
В результате получим 725. Это и есть число 2D5 в десятичной системе счисления.

Перевод числа из десятичной в шестнадцатеричную систему счисления. 

Давайте переведем число 725 обратно в шестнадцатеричную систему счисления. В результате у нас должно получится 2D5.
Рис.2 Перевод из 10-ой в 16-ричную

Для перевода, нам необходимо поделить с остатком число 725 на 16 в столбик. Получим 45 и остаток 5. 

Теперь делим 45 на 16 с остатком. Получим 2 и остаток 13. Оба числа меньше 16 значит на этом можно закончить деление.

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

Как вы уже успели заметить, мы получили то, что и ожидали 2D5.






А теперь посмотрите на следующее число 235. Оно может быть как числом в десятичной, так и в шестнадцатеричной системе счисления. А кстати, чему будет равно число 235 в шестнадцатеричной системе счисления  при переводе в десятичную систему? Переведите самостоятельно, посмотрите сколь велико отличие.
Нужно как-то уметь отличать. Для этого используются специальные обозначения. Одним из способов является запись в которой шестнадцатеричному числу предшествует пара символов «0х». Т.е. запись 0х235 говорит нам, что 235 это число, записанное в шестнадцатеричной системе счисления.

Переменные и их адреса.

Как отмечалось во втором уроке, каждая переменная хранится в памяти. Естественно у каждой переменной в памяти есть свой адрес, по которому она записана. Мы уже даже использовали адреса переменных, когда пользовались функцией scanf().
Чтобы получить адрес переменной нам необходимо перед её именем написать символ “&”.

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

Листинг16.1
#include <stdio.h>

int main (){

printf ("razmer peremennoi tipa char %d\n", sizeof(char));

printf ("razmer peremennoi tipa int %d\n", sizeof(int));

printf ("razmer peremennoi tipa float %d\n", sizeof(float));

printf ("razmer peremennoi tipa double %d\n", sizeof(double));

return(0);

}

Результат её работы:
Рис 4. Программа иллюстрирующая работу sizeof()

У вас данные цифры могут быть другими кстати. Так как стандартом языка не оговаривается какой тип сколько должен занимать в памяти. Оговариваются только из соотношения. Например, размер double не должен быть меньше чем размер float.

То есть, если я объявляю в программе переменную типа int, то под нее в памяти выделяется 4 байта (ячейки).

Так как мы уже умеем получать адрес переменной, то давайте посмотрим на него. Для того, чтобы вывести число в шестнадцатеричной системе существует специальный спецификатор “%x”. И для него есть модификатор “#” при его записи, шестнадцатеричное число выводится с символами «0х».

Листинг16.2
#include <stdio.h>

int main (){

      int a,b;

      printf ("adres peremennoi a %#x\n", &a);

      printf ("adres peremennoi b %#x\n", &b);

return(0);

}

Рис.5 Адреса переменных в памяти

Мы получили два адреса 0x12ff60 и 0x12ff56. По этим адресам в памяти записаны наши переменные a и b. При этом они занимают в памяти по 4 клетки подряд, так как это переменные целого типа и из рисунка 3 видно, что их размер 4 байта. Это выглядит примерно следующим образом.
Рис.6. Пример расположения переменных в памяти
Как вы уже заметили, переменные в память записываются не одна за другой, а в произвольном месте, лишь бы там было пусто и хватило места. Исключение составляют массивы. Они записываются в память последовательно. Посмотрите на результат работы следующей программы.

Листинг16.3
#include <stdio.h>

int main (){

      int a[3];

      printf ("adres peremennoi a[0] %#x\n", &a[0]);

      printf ("adres peremennoi a[1] %#x\n", &a[1]);

return(0);

}

Рис.7 Расположение массива в памяти

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

Адреса это хорошо, но что с ними делать? На кой чёрт они нам сдались?
Обо всем по порядку. 

Указатели. 

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

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

Объявление указателя.

Указатель, раз это переменная, должен как-то объявляться. Делается это почти что также как и обычно.
int * p_a;

Сначала указывается тип переменной, которая будет храниться в памяти, по адресу указателя. Далее следует специальный символ «*» (звездочка), которая и указывает на то, что мы собираемся объявить не просто переменную типа int, а указатель на переменную типа int. После звездочки пишут имя указателя. Ну и естественно заключительная точка с запятой.

В нашем примере, мы объявили  указатель с именем p_a, который будет указывать на переменную типа int. Кстати, обычно, чтобы не путать указатели с другими переменными, в их имена добавляют какой-нибудь отличительный знак. Например, я вот добавляю обычно “p_”.  Когда я вижу в своей программе переменную, имя которой начинается с этих символов, я точно знаю что это у меня указатель. Кроме того, если программа большая, я помимо этого указывают после «p» ещё и тип переменной для типа int  - i, для floatf, для char – с и так далее, получается что-то типа pi_a. Это  сразу говорит мне, что это указатель, который ссылается на переменную типа int.
  

Присвоение указателю адреса.

Давайте перепишем Листинг 16.2, используя указатели.

Листинг 16.4
#include <stdio.h>

int main (){

      int a,b,*pi_a, *pi_b;

      pi_a=&a;

      pi_b=&b;

      printf ("adres peremennoi a %#x\n", pi_a);

      printf ("adres peremennoi b %p\n", pi_b);

return(0);

}

Рис.8. Пример хранения адреса переменной в указателе.

Как видите, после объявления с указателем можно работать так же, как и с обычной переменной. Ему можно присвоить некоторый адрес, используя оператор «=».
И заметьте, для  вывода указателя можно использовать специальный спецификатор вывода «p».

Получение значения переменной.

Мы можем получить значение, которое хранится по адресу, записанному в указателе. Для этого используется оператор «*». Ага, снова эта пресловутая звездочка. Догадайтесь, куда она записывается? Ага, именно туда, перед именем указателя. Вот такие чудеса творятся иногда.  Надо посмотреть на примере.

Давайте немного изменим Листинг 16.4.  Добавим переменным значения и попробуем получить их, используя указатели.

Листинг16.5
#include <stdio.h>

int main (){

      int a=3, b=0, *pi_a;    //объявляем переменную a

//и указатель на переменную типа int

      pi_a=&a; // присваиваем указателю адресс переменной а

      *pi_a=b; // записываем в память, по адресу который хранится в указателе

                   // значение переменной b

      printf ("adres peremennoi a %#x\n", pi_a);

      printf ("znachenie po adresu zapisannomy v pi_a %d\n", *pi_a);

return(0);

}

Рис.9. Использование указателей для обращения к значениям переменных,на которые они ссылаются

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

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

Есть еще один специальный указатель. Он имеет свое особое название – нулевой указатель NULL. Нулевой указатель не ссылается никуда. Он используется, чтобы обнулять указатели. Посмотрите на следующую программу.

Листинг16.6
#include <stdio.h>

int main (){

      int a=3,*pi_a;

      pi_a=&a; // присваиваем указателю адресс переменной а

      printf ("adres peremennoi a %#x\n", pi_a);

      pi_a=NULL;

      printf ("adres peremennoi a %#x\n", pi_a);

return(0);

}

Рис.10 Нулевой указатель

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

И под конец урока. Не зря же мы с вами так долго паримся с этими указателями сегодня. Один небольшой пример. Помните занятие про функции? Или недавнюю небольшую головоломку в группе во Вконтакте.
Кто не помнит, вот вам картинка.

Рис.11 Простенькая головоломка. Что выведет представленная программа?
Нам известно, когда мы передаем переменные в функцию, то передаются не сами переменные, а их копии? Иногда нам это вовсе не нужно. Иногда удобно сделать так, чтобы значения все-таки изменялись. Для этого, нужно передавать в функцию не переменную, а указатель на неё.

Давайте перепишем программу с картинки, чтобы она работала так, как и предполагается.

Листинг 16.7
#include <stdio.h>

void obmen (int *pi_a, int*pi_b){

//принимаем указатели на переменные типа int

      int temp;

      temp=*pi_a;

      *pi_a=*pi_b;

      *pi_b=temp;

}

int main (){

      int x=5, y=10;

      obmen(&x,&y);

// ВНИМАНИЕ!передаем адреса, так как функция obmen принимаем указатели

      printf ("Posle x=%d y=%d\n",x,y);

return(0);

}

Рис.12 Пример работы программы с указателями
Как видите, теперь работает как надо. Если вы внимательно разобрались с началом урока, то проблем с этой программой возникнуть не должно. Если вопросы есть, задавайте в комментариях. Я постараюсь все вам разъяснить.

Подробный урок о том, зачем нужны указатели.

И это еще не все возможности указателей. Следующее занятие снова будет посвящено указателям. Их связью с массивами и строками.

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

Если вам понравился этот или другие уроки, расскажите пожалуйста о них, своим друзьям Вконтакте,Facebook,Google+, используя кнопки социальных сетей расположенные ниже.

Занятие 16. Указатели.


Здравствуйте друзья.
Сегодня у нас важная и сложная тема. Возможно, самая сложная тема за все время. Указатели.

Прочитайте более простую версию этого урока "Указатели".

В новой версии:

  • Ещё более доступное объяснение
  • Дополнительные материалы
  • 10 задач на программирование с автоматической проверкой решения


Прежде вспомним основы шестнадцатеричной системы счисления.

Любое число в 16-тиричной системе счисления записывается с помощью символов 0,1,2,3,4,5,6,7,8,9,A,B,C,D,E,F. Шестнадцатеричной она называется, потому что в ней для записи различных чисел используются 16 основных цифр. Например, в привычной нами десятичной системе счисления используется десять основных цифр 0,1,2,3,4,5,6,7,8 и 9. 

Первые десять цифр в 16-тиричной системе счисления такие же, как и в десятичной системе, а вот для записи следующих шести используются буквы латинского алфавита. 
A=10
B=11
C=12
D=13
E=14
F=15.

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

Пусть у нас есть число 2D5. И мы хотим узнать, сколько это в нашей десятичной системе счисления.
Для этого сначала разделим число на цифры:
2 D5
Теперь заменим все буквы, на их числовые обозначения:
2 13 5

Пронумеруем эти цифры справа налево начиная с нуля.

2   13   5
2         1      0

Умножим каждую из цифр, на 16 в степени, соответствующей порядковому номеру. И сложим все это между собой.
Рис.1 Перевод шестнадцатеричного числа в десятичную систему счисления
В результате получим 725. Это и есть число 2D5 в десятичной системе счисления.

Перевод числа из десятичной в шестнадцатеричную систему счисления. 

Давайте переведем число 725 обратно в шестнадцатеричную систему счисления. В результате у нас должно получится 2D5.
Рис.2 Перевод из 10-ой в 16-ричную

Для перевода, нам необходимо поделить с остатком число 725 на 16 в столбик. Получим 45 и остаток 5. 

Теперь делим 45 на 16 с остатком. Получим 2 и остаток 13. Оба числа меньше 16 значит на этом можно закончить деление.

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

Как вы уже успели заметить, мы получили то, что и ожидали 2D5.






А теперь посмотрите на следующее число 235. Оно может быть как числом в десятичной, так и в шестнадцатеричной системе счисления. А кстати, чему будет равно число 235 в шестнадцатеричной системе счисления  при переводе в десятичную систему? Переведите самостоятельно, посмотрите сколь велико отличие.
Нужно как-то уметь отличать. Для этого используются специальные обозначения. Одним из способов является запись в которой шестнадцатеричному числу предшествует пара символов «0х». Т.е. запись 0х235 говорит нам, что 235 это число, записанное в шестнадцатеричной системе счисления.

Переменные и их адреса.

Как отмечалось во втором уроке, каждая переменная хранится в памяти. Естественно у каждой переменной в памяти есть свой адрес, по которому она записана. Мы уже даже использовали адреса переменных, когда пользовались функцией scanf().
Чтобы получить адрес переменной нам необходимо перед её именем написать символ “&”.

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

Листинг16.1
#include <stdio.h>

int main (){

printf ("razmer peremennoi tipa char %d\n", sizeof(char));

printf ("razmer peremennoi tipa int %d\n", sizeof(int));

printf ("razmer peremennoi tipa float %d\n", sizeof(float));

printf ("razmer peremennoi tipa double %d\n", sizeof(double));

return(0);

}

Результат её работы:
Рис 4. Программа иллюстрирующая работу sizeof()

У вас данные цифры могут быть другими кстати. Так как стандартом языка не оговаривается какой тип сколько должен занимать в памяти. Оговариваются только из соотношения. Например, размер double не должен быть меньше чем размер float.

То есть, если я объявляю в программе переменную типа int, то под нее в памяти выделяется 4 байта (ячейки).

Так как мы уже умеем получать адрес переменной, то давайте посмотрим на него. Для того, чтобы вывести число в шестнадцатеричной системе существует специальный спецификатор “%x”. И для него есть модификатор “#” при его записи, шестнадцатеричное число выводится с символами «0х».

Листинг16.2
#include <stdio.h>

int main (){

      int a,b;

      printf ("adres peremennoi a %#x\n", &a);

      printf ("adres peremennoi b %#x\n", &b);

return(0);

}

Рис.5 Адреса переменных в памяти

Мы получили два адреса 0x12ff60 и 0x12ff56. По этим адресам в памяти записаны наши переменные a и b. При этом они занимают в памяти по 4 клетки подряд, так как это переменные целого типа и из рисунка 3 видно, что их размер 4 байта. Это выглядит примерно следующим образом.
Рис.6. Пример расположения переменных в памяти
Как вы уже заметили, переменные в память записываются не одна за другой, а в произвольном месте, лишь бы там было пусто и хватило места. Исключение составляют массивы. Они записываются в память последовательно. Посмотрите на результат работы следующей программы.

Листинг16.3
#include <stdio.h>

int main (){

      int a[3];

      printf ("adres peremennoi a[0] %#x\n", &a[0]);

      printf ("adres peremennoi a[1] %#x\n", &a[1]);

return(0);

}

Рис.7 Расположение массива в памяти

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

Адреса это хорошо, но что с ними делать? На кой чёрт они нам сдались?
Обо всем по порядку. 

Указатели. 

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

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

Объявление указателя.

Указатель, раз это переменная, должен как-то объявляться. Делается это почти что также как и обычно.
int * p_a;

Сначала указывается тип переменной, которая будет храниться в памяти, по адресу указателя. Далее следует специальный символ «*» (звездочка), которая и указывает на то, что мы собираемся объявить не просто переменную типа int, а указатель на переменную типа int. После звездочки пишут имя указателя. Ну и естественно заключительная точка с запятой.

В нашем примере, мы объявили  указатель с именем p_a, который будет указывать на переменную типа int. Кстати, обычно, чтобы не путать указатели с другими переменными, в их имена добавляют какой-нибудь отличительный знак. Например, я вот добавляю обычно “p_”.  Когда я вижу в своей программе переменную, имя которой начинается с этих символов, я точно знаю что это у меня указатель. Кроме того, если программа большая, я помимо этого указывают после «p» ещё и тип переменной для типа int  - i, для floatf, для char – с и так далее, получается что-то типа pi_a. Это  сразу говорит мне, что это указатель, который ссылается на переменную типа int.
  

Присвоение указателю адреса.

Давайте перепишем Листинг 16.2, используя указатели.

Листинг 16.4
#include <stdio.h>

int main (){

      int a,b,*pi_a, *pi_b;

      pi_a=&a;

      pi_b=&b;

      printf ("adres peremennoi a %#x\n", pi_a);

      printf ("adres peremennoi b %p\n", pi_b);

return(0);

}

Рис.8. Пример хранения адреса переменной в указателе.

Как видите, после объявления с указателем можно работать так же, как и с обычной переменной. Ему можно присвоить некоторый адрес, используя оператор «=».
И заметьте, для  вывода указателя можно использовать специальный спецификатор вывода «p».

Получение значения переменной.

Мы можем получить значение, которое хранится по адресу, записанному в указателе. Для этого используется оператор «*». Ага, снова эта пресловутая звездочка. Догадайтесь, куда она записывается? Ага, именно туда, перед именем указателя. Вот такие чудеса творятся иногда.  Надо посмотреть на примере.

Давайте немного изменим Листинг 16.4.  Добавим переменным значения и попробуем получить их, используя указатели.

Листинг16.5
#include <stdio.h>

int main (){

      int a=3, b=0, *pi_a;    //объявляем переменную a

//и указатель на переменную типа int

      pi_a=&a; // присваиваем указателю адресс переменной а

      *pi_a=b; // записываем в память, по адресу который хранится в указателе

                   // значение переменной b

      printf ("adres peremennoi a %#x\n", pi_a);

      printf ("znachenie po adresu zapisannomy v pi_a %d\n", *pi_a);

return(0);

}

Рис.9. Использование указателей для обращения к значениям переменных,на которые они ссылаются

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

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

Есть еще один специальный указатель. Он имеет свое особое название – нулевой указатель NULL. Нулевой указатель не ссылается никуда. Он используется, чтобы обнулять указатели. Посмотрите на следующую программу.

Листинг16.6
#include <stdio.h>

int main (){

      int a=3,*pi_a;

      pi_a=&a; // присваиваем указателю адресс переменной а

      printf ("adres peremennoi a %#x\n", pi_a);

      pi_a=NULL;

      printf ("adres peremennoi a %#x\n", pi_a);

return(0);

}

Рис.10 Нулевой указатель

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

И под конец урока. Не зря же мы с вами так долго паримся с этими указателями сегодня. Один небольшой пример. Помните занятие про функции? Или недавнюю небольшую головоломку в группе во Вконтакте.
Кто не помнит, вот вам картинка.

Рис.11 Простенькая головоломка. Что выведет представленная программа?
Нам известно, когда мы передаем переменные в функцию, то передаются не сами переменные, а их копии? Иногда нам это вовсе не нужно. Иногда удобно сделать так, чтобы значения все-таки изменялись. Для этого, нужно передавать в функцию не переменную, а указатель на неё.

Давайте перепишем программу с картинки, чтобы она работала так, как и предполагается.

Листинг 16.7
#include <stdio.h>

void obmen (int *pi_a, int*pi_b){

//принимаем указатели на переменные типа int

      int temp;

      temp=*pi_a;

      *pi_a=*pi_b;

      *pi_b=temp;

}

int main (){

      int x=5, y=10;

      obmen(&x,&y);

// ВНИМАНИЕ!передаем адреса, так как функция obmen принимаем указатели

      printf ("Posle x=%d y=%d\n",x,y);

return(0);

}

Рис.12 Пример работы программы с указателями
Как видите, теперь работает как надо. Если вы внимательно разобрались с началом урока, то проблем с этой программой возникнуть не должно. Если вопросы есть, задавайте в комментариях. Я постараюсь все вам разъяснить.

Подробный урок о том, зачем нужны указатели.

И это еще не все возможности указателей. Следующее занятие снова будет посвящено указателям. Их связью с массивами и строками.

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

Если вам понравился этот или другие уроки, расскажите пожалуйста о них, своим друзьям Вконтакте,Facebook,Google+, используя кнопки социальных сетей расположенные ниже.

Урок 15. Некоторые стандартные функции работы со строками.


Всем доброго дня.
Продолжаем изучать строки. Сегодня у нас праздник. Почему? Да просто мы познакомимся с новым заголовочным файлом – string.h. Нетрудно сообразить, что в нём описываются стандартные функции, предназначенные для работы со строками.  Естественно не следует забывать, что для использования этих функций необходимо подключить этот заголовочный файл. Я надеюсь вы не забыли как это делается.

Прочитайте улучшенную версию этого урока "Стандартные функции работы со строками".

В новой версии:

  • Ещё более доступное объяснение
  • Дополнительные материалы
  • 8 задач на программирование с автоматической проверкой решения


Функция strlen().

В практическом задании к прошлому занятию, я просил вас написать  программу, которая вычисляет длину строки без учета нулевого элемента.  Длина строки - это один из самых важных параметров, характеризующих любую строку, и необходимый при работе со строками.  Для определения длины строки в заголовочном файле string.h описана функция strlen().
Данная функция имеет простой синтаксис. Она принимает один параметр строку символов, и возвращает в результате своей работы целое число - длину этой строки.
Листинг15.1
#include <stdio.h> 
#include <string.h> 
int main(void) { 
      char str[]="Hello world"
      int n=strlen(str); 
      printf("Dlina stroki %d simvolov\n",n); 
return(0);
}
Рис.1 Программа, иллюстрирующая работу функции strlen().

Как видите ничего неожиданного, функция вернула в результате своей работы число 11.
Теперь, на основании знаний, полученных на прошлом занятии, объясните результат работы следующей программы.
Листинг15.2
#include <stdio.h> 
#include <string.h> 
int main(void) { 
      char str[100]; 
      fgets(str,100,stdin); 
      int n=strlen(str); 
      printf("Dlina stroki %d simvolov\n",n);
return(0); 
}

Рис.2. Иллюстрация работы программы Листинг 15.2
Почему, когда мы ввели Hello world и нажали Enter, наша функция вернула нам число 12, а не 11 как в прошлом случае?

Функции сравнения строк.

Еще одной задачей, предложенной для решения в прошлом занятии, было написание программы, которая посимвольно сравнивает между собой две строки.  Для выполнения этой операции, тоже существует готовая функция –strcmp().
Данная функция принимает в качестве аргументов две строки, которые необходимо сравнить. В результате своей работы, если строки одинаковые, он возвращает нуль, и если разные, то либо целое число положительное или отрицательное. Сравнение идет по коду символов в таблице ASCII.
Рассмотрим пример.
Листинг15.3
#include <stdio.h> 
#include <string.h> 
int main(void) { 
       char  str1[] = "hello world",
             str2[] = "hello world"
             str3[] = "hello World"
       int n12 = strcmp(str1,str2); 
       int n13 = strcmp(str1,str3); 
       int n31 = strcmp(str3,str1); 
       printf(" %s i %s %d\n", str1, str2,n12); 
       printf(" %s i %s %d\n", str1, str3,n13); 
       printf(" %s i %s %d\n", str3, str1,n31); 
       printf("%c/%d\n",str1[6],str1[6]); 
       printf("%c/%d\n",str3[6],str3[6]);
return(0); 
}
Результат работы данной программы:
Рис.3. Сравнение строк функцией strcmp()

 
Строки str1 и str2 одинаковые и потому, при их сравнении получилось 0. А вот строки str1 и str3 различаются, регистром символа ‘w’ поэтому результат их сравнения не равен нулю. Обратите внимание, что в одном случае он равен единице, а в другом минус единице. Почему так получается, мы сейчас разберемся.
Строки сравниваются посимвольно, использую соответствие между символом и его кодом в таблице ASCII.  Ниже сравнения, в нашей программе указаны коды символов ‘w’ и ‘W’.  Как видите, код символа ‘w’ –  119, больше чем код символа ‘W’ – 87. Значит и строка str1 больше чем строка str3.

Основное правило:  Если, в функции strcmp() строка записанная в первом аргументе, больше чем строка во втором аргументе, то функция возвращает положительное число.  Если меньше – отрицательное.
Есть и еще одна функция, предназначенная для сравнения строк strncmp(). Её отличие лишь в том, что добавляется третий параметр – целое число, указывающее, сколько символов с начала строки необходимо сравнить.
Немного преобразуем предыдущую программу
Листинг 15.4
#include <stdio.h> 
#include <string.h> 
int main(void) { 
       char  str1[] = "hello world"
             str2[] = "hello World"
       int n1 = strncmp(str1,str2,6); 
       int n2 = strncmp(str1,str2,7); 
       printf("%s i %s %d\n", str1, str2,n1); 
       printf("%s i %s %d\n", str1, str2,n2);
return(0);
}

Рис.4. Сравнение частей строк, с помощью функции strncmp().
Как видите, когда мы сравнили первые шесть символов двух строк, то получили 0, так как они совпадают. Если же мы сравниваем первые семь символов, то первая строка больше второй, и в ответе получается положительное целое число. Причем в данном случае, это разность между кодами соответствующих символов (119-87 = 32). Пользоваться этим обстоятельством нужно очень осторожно. Не факт, что в других средах разработки, эти функции реализованы точно таким же образом. Т.е. может быть, что где-то strcmp будет тоже возвращать  32, а не 1, как в нашем примере. А где-то возможно strncmp не будет возвращать 32. Как это реализовано в вашей среде программирования проверьте самостоятельно.   

Функции изменения регистра строки.

Как вы помните, в задаче к прошлому уроку требовалось написать функцию, которая переводит все символы строки в нижний регистр. Для этой задачи тоже есть готовая функция – strlwr().  Ко всему прочему имеется и функция, которая переводит все символы в верхний регистр – strupr().
Данные функции принимают один параметр – строку, которую необходимо перевести в тот или иной регистр. Рассмотрим пример их использования.
Листинг 15.5
#include <stdio.h> 
#include <string.h> 
int main(void) { 
      char str[] = "HeLLo WoRlD"
      printf ("%s\n",str); 
      strlwr(str); //переводим строку в нижний регистр
      printf ("%s\n",str); 
      strupr(str); //переводим строку в верхний регистр
      printf ("%s\n",str);
return(0);
}
Результат работы программы
Рис.5. Изменение регистра строки функциями strupr() и strlwr().

Функции объединения строк.

В файле string.h описаны две функции для объединения строк.
Первая strcat() принимает две строки, и приписывает вторую в конец первой. Т.е. результат склеивания двух строк окажется в той, что записана в первом аргументе. Причем нулевой символ ‘\0’ добавляется автоматически в конец новой, получившейся строки.  Небольшой иллюстрирующий пример:
Листинг15.6
#include <stdio.h> 
#include <string.h> 
int main(void) { 
      char  str1[50] = "hello"
            str2[]="world"
      strcat(str1," "); //приклеиваем к str1 пробел получаем
                        //в str1 строку "hello "
      strcat(str1,str2);//приклеиваем к str1 строку str2 
      printf("%s\n", str1); //выводим str1
return(0); 
}
Рис.6. Склеивание двух строк функцией strcat().

Вторая функция strncat() отличается от предыдущей, наличием еще одного аргумента, означающего, какое количество символов с начала второй строки следует приклеить к первой строке. Немного переделаем наш предыдущий пример.
Листинг 15.7
#include <stdio.h> 
#include <string.h> 
int main(void) { 
      char  str1[50] = "hello"
            str2[]="world"
      strcat(str1," "); //приклеиваем к str1 пробел получаем                              //в str1 строку "hello "
      strncat(str1,str2,3); //приклеиваем к str1 3 первых                                     //символа из str2

      printf("%s\n", str1); //выводим str1

return(0);
}

Ну и соответственно результат её работы.
Рис.7. Использование функции strncat().


Ничего удивительного. Все работает именно так, как и ожидалось.
При работе с этими двумя функциями стоит внимательно следить за тем, чтобы в строке, к которой мы приклеиваем, хватило места, иначе произойдет ошибка переполнения. С ней мы уже сталкивались с прошлом занятии. Вот попробуйте выполнить следующую программу.
 Листинг 15.8
#include <stdio.h> 
#include <string.h> 
int main(void) { 
      char  str1[10] = "hello"
            str2[]=" world"
      strcat(str1,str2); 
      printf("%s\n", str1); 
      return(0);
}

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

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


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