Cat

Сравнение array C и list Python

В этой статье я продолжу сравнивать языки Python и C. Сравню, чем отличается array (массив) из С от list (список) из Python. Также сравню циклы while из этих двух языков и сделаю ими обход массива и списка.

Сравнение Python и С Arduinum628 20 Июнь 2024 Просмотров: 223

Всем доброго дня! У меня был небольшой перерыв в написании статей, так как навалилось много дел. Заканчивал стажировку, параллельно занимаясь поиском работы и дописывая свой сайт-блог, весна и хорошая погода - всё это сделало своё дело. Сейчас я возобновляю свою работу над статьями и хочу выпускать их примерно раз в две недели. Также в ближайшем будущем я хочу запустить свой сайт-блог и дублировать свои статьи там.

Итак, продолжим. В предыдущей статье "Сравнение интерпретатора Python и компилятора C" я затрагивал вскользь темы array и list, а также циклов while. Поэтому в данной статье я сравню array (массив) из языка C и list (список) из языка Python, обход которых я сделаю на цикле while. Давненько я не писал статьи и даже очень соскучился по этому процессу. Так что приступаю к продолжению этой интересной и захватывающей рубрики.

Сравнение array и list

Для начала начнём со знакомого многим питонистам list (список) из языка Python. Список - это неограниченная по размеру упорядоченная коллекция произвольных объектов. Для начала давайте создадим список в Python.

list_example = [1, 2, 3, 4, 5]
print(list_example)

Я создал список и назвал его list_example так как он не будет привязан к конкретным данным или назначению. Давайте запустим в IDE и посмотрим что он выдаст в эмуляторе terminal на моём Debian. Я перешёл с Kubuntu на Debian 12, но различий между двумя этими системами для демонстрации кода не будет. При нажатии на кнопку run python file в IDE VS Code видим следующий вывод.

В terminal

[1, 2, 3, 4, 5]

Функция print из строки print(list_example) печатает нам содержимое списка [1, 2, 3, 4, 5], в котором находятся числа. Прежде чем перейти к написанию кода с аналогичным функционалом на C давайте разберёмся что такое array.

Массив, или array, в языке C - это структура данных, которая позволяет хранить последовательности элементов одного типа. Это определение уже намекает нам на то, что в массиве на языке C не может быть разных типов данных. Чуть позже я объясню почему, а сейчас создадим массив, похожий на список, что я писал выше на Python.

#include <stdio.h>
#include <string.h>
int main(){
    int array_example[] = {1, 2, 3, 4, 5};
    char buffer[100];
    sprintf(
        buffer,
        "[%d, %d, %d, %d, %d]",
        array_example[0],
        array_example[1],
        array_example[2],
        array_example[3],
        array_example[4]
    );
    printf("%s\n", buffer);
    return 0;
}

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

В terminal

$ gcc ./array_c.c -o array_c
$ ./array_c
[1, 2, 3, 4, 5]

После компиляции и запуска данного кода мы видим тот же результат, что и в коде на Python в эмуляторе terminal [1, 2, 3, 4, 5]. Давайте разберёмся, что мы вообще такое написали на С. Строка #include <string.h> включает заголовочный файл для компилятора, который нужен для работы со строками. В нашем случае заголовочный файл нужен, чтобы использовать функцию sprintf для формирования строки. Из предыдущей статьи вы помните, что строка int array_axample[] = {1, 2, 3, 4, 5}; создаст массив целых чисел. Прошу обратить внимание, что вы не можете класть туда любые типы данных как это делается в языке Python! Компилятору явно указывается, что в массиве у нас тип данных int - целое число. Давайте взглянем на примере, что будет если положить в массив int тип данных char или символ. При форматировании укажем %c спецификатор формата для char. Для этого давайте слегка поправим код.

#include <stdio.h>
#include <string.h>
int main(){
    int array_example[] = {1, 2, 3, 4, 'r'};
    char buffer[100];
    sprintf(
        buffer,
        "[%d, %d, %d, %d, %c]",
        array_example[0],
        array_example[1],
        array_example[2],
        array_example[3],
        array_example[4]
    );
    printf("%s\n", buffer);
    printf("размер int %d\n", sizeof(int));
    printf("размер char %d\n", sizeof(char));
    printf("размер char в массиве int %d\n", sizeof(array_axample[4]));
    return 0;
}

Ещё раз компилируем и запустим код в terminal.

$ gcc ./array_c.c -o array_c
$ ./array_c
[1, 2, 3, 4, r]
размер int 4
размер char 1
размер char в массиве int 4

Запустив код, мы видим что в [1, 2, 3, 4, r] есть символ char, который добавился каким-то образом в массив int. Как же это произошло если в C строгая типизация? Дело в том что массиве char символ будет храниться как int. Это означает что и занимать место он будет так же как и int тип. Из нашего примера мы видим что размер char символа у меня на пк 1 байт, а размер int 4 байт. Так же мы видим что размер char в массиве int типа 4 байт, а не 1. Это означает что мы в 4 раза будем тратить больше места на один символ. Это довольно не экономно с точки зрения ресурсов пк. Поэтому я вам не рекомендую этот подход. Перепишем код обратно на тот что был и снова перекомпилируем. Просто повторите предыдущий код, что был до изменений, и заново запустите команду компиляции. Всё должно работать как раньше.

Давайте разберём оставшиеся строки кода. Строка char buffer[100]; создаёт массив char из 99 символов и 1 символ конца строки \0. Это нужно для хранения символов строки char. Цифра 100 на самом деле не количество символов, а размер массива в байтах. Размер 100 байт в нашем случае очень избыточен, поэтому рассчитаем точный размер 5 * 1 + 1 = 6. Количество символов умножаем на вес одного в байтах и прибавляем 1 байт для символа конца строки. Квадратные скобки и запятые в нашем буфере не хранятся. Таким образом мы экономим 94 байта на компьютере. Программы на C требуют, чтобы вы следили за размером, который занимают ваши данные. Немного непривычно после языка Python, не так ли? На самом деле в Python тоже нужно думать о размере данных, но из-за того что он динамический и много прощает, следить за этим гораздо сложнее. Далее в функцию sprintf мы кладём наш буфер для хранения char, форматируем строку "[%d, %d, %d, %d, %d]" и для каждого спецификатора формата %d берём каждое число по индексу из массива. Далее функция sprintf заполнила наш buffer данными char в результате чего получился массив символов, последовательность которых образует строку. Ну и в конце выводим нашу строку в terminal (эмулятор терминала). В Python функция print сама выводила нам содержимое списка, а в C нам пришлось для этого форматировать строку. Возможности языка C и возможности Python разные и иногда в C просто нет простого решения. Давайте попробуем на Python написать близкий внешне вариант. Для этого перепишем наш Python код. Напишу два варианта: более старый и более новый и быстрый.

from typing import List
list_example: List[int] = [1, 2, 3, 4, 5]
str_list = '[%d, %d, %d, %d, %d]' % (list_example[0], list_example[1], \
    list_example[2], list_example[3], list_example[4])
print(str_list)

Первый вариант использует оператор %, который более родственный языку C. Обратите внимание: я использую аннотацию типов List[int], показывая, что у нас должен быть тип данных list с числами int типа внутри. Это работает на уровне соглашения между разработчиками и не влияет на саму работу кода. И если туда добавить другой тип данных естественно ничего не сломается. Результатом в переменной str_list будет отформатированная оператором % строка [1, 2, 3, 4, 5], которую мы потом выводим в terminal с помощью функции print.

from typing import List
list_example: List[int] = [1, 2, 3, 4, 5]
str_list = f'[{list_example[0]}, {list_example[1]}, {list_example[2]}, '\
    f'{list_example[3]}, {list_example[4]}]'
print(str_list)

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

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

from typing import List
list_example: List[int] = [1, 2, 3, 4, 5]
i = 0
while i <= list_example.index(list_example[-1]):
    print(list_example[i])
    i += 1

У меня получился довольно простой и элегантный вариант в версии Python. Когда я пишу на Python, я не пытаюсь усложнить задачу, а наоборот - стараюсь пользоваться его средствами для упрощения себе работы. В данном случае я использую функцию index для удобства. У нас есть переменная i для индекса, по которому нужно брать элементы из списка list_example. Строка while i <= list_example.index(list_example[-1]): говорит нам о том, что мы не выйдем из цикла while пока значение переменной i не будет равно последнему индексу списка. Далее выводим данные на экран в строке print(list_example[i]). После чего прибавляем на единицу переменную индекса i . Давайте запустим наш код в VS Code и взглянем на результат.

1
2
3
4
5

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

#include <stdio.h>
int main(){
    int array_example[] = {1, 2, 3, 4, 5};
    int i = 0;
    int i_end = sizeof(array_example) / sizeof(array_example[0]) - 1;
    while (i <= i_end){
        printf("%d\n", array_example[i]);
        ++i;
    }
    return 0;
}

Давайте взглянем на наш C код и на отличия в работе его логики. У нас имеется переменная для индекса int i = 0;. Строка получения длины списка вам уже известна из предыдущей главы и единственное здесь отличие - мы отнимаем единицу. Это нужно для того, чтобы получить последний индекс списка. Строка int i_end = sizeof(array_example) / sizeof(array_example[0]) - 1; успешно получит число 4. Далее идёт условие работы цикла: while i <= i_end работает аналогично примеру из Python. Строка printf("%d\n", array_example[i]); выведет число из списка в terminal. В конце ++i; мы прибавляем единицу к переменной индекса i. Вот тут хочу остановиться поподробнее. Дело в том, что есть два варианта: ++i и i++. Первый вариант ++i это префиксная форма и она сначала увеличивает значение, а потом возвращает его. Второй вид i++ это суффиксная форма, которая сначала вернёт текущее значение, а потом увеличит его. Будьте внимательны при использовании, у них будут разные результаты и может быть ошибка в логике программы.
Теперь давайте скомпилируем и запустим программу.

$ gcc array_c.c -o array_c
$ ./array_c
1
2
3
4
5

Всё хорошо, программа отработала как нужно и вывела нам каждое число из массива.

Бонус

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

arr = array('i', [0]*5)
arr[0] = 1
arr[1] = 2
arr[2] = 3
arr[3] = 4
arr[4] = 5
print(arr)
print(arr[0])

Первая строка arr = array('i', [0]*5) создаёт аналог массива (по функциональности) и заполняет его пятью элементами нулями, i указывает, что это массив с данными int типа. Далее, как и в обычном list, мы присваиваем значение по индексу arr[0] = 1 и т.д. Давайте запустим код и убедимся, что всё работает.

array('i', [1, 2, 3, 4, 5])
1

Мы видим, что у нас создался объект array, который заполнился данными. Далее мы выводим элемент цифру 1 по индексу 0. Согласитесь, не очень удобно заполнять нулями, а потом присваивать по каждому индексу значения? Мы можем сразу заполнить массив нужными значениями, уменьшив этим количество строк кода. Заодно давайте добавим новый элемент в массив, а так же добавим другой тип данных.

arr = array('i', [1, 2, 3, 4, 5])
print(arr)
arr.append(6)
print(arr)
arr.append('number')
print(arr)

Запустим наш новый код в terminal.

array('i', [1, 2, 3, 4, 5])
array('i', [1, 2, 3, 4, 5, 6])
Traceback (most recent call last):
  File "/home/arduinum628/Документы/Helper_for_programmer/Articles/Code/Код из статьи array C list Python/list_python.py", line 43, in <module>
    arr.append('number')
TypeError: 'str' object cannot be interpreted as an integer

При запуске нашего кода мы видим, что у нас создался такой же массив как и в примере выше. Далее мы добавили новый элемент с помощью метода append. Потому что размер array динамический в данной библиотеке. Обратите внимание что в языке C массив имеет фиксированный размер и выйти за его размеры у вас не получиться. Таким образом мы ограничиваем ресурсы нашего ПК в языке C. В Array мы получили фиксированную типизацию для массива указав i (int). Поэтому мы получили ошибку при добавлении в него типа данных str строки number. Также возможности встроенной библиотеки array позволяют удалять элемент по индексу del arr[0].

Заключение

  1. Узнали, чем отличается array (массив) в C от list (список) в Python;
  2. Научились работать с bufer для хранения символов типа данных char для строк;
  3. Посмотрели как работает цикл while на обоих языках;
  4. Затронули тему аннотации типов в Python;
  5. Бонусом посмотрели встроенный в Python модуль array, который может создавать подобие массива;

Автор

  • 21 июня 2024 г. 14:00

    Статья была отредактирована для исправления некоторых неточностей.

  • Реклама