Привет, Хабр!

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

GNU Scientific Library (GSL)

GNU Scientific Library — это свободно распространяемая библиотека для выполнения научных вычислений.

Линейная алгебра

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

Библиотека предоставляет функции для разложения матриц (LU, QR, Cholesky) и решения линейных систем уравнений.

#include <gsl/gsl_matrix.h>
#include <gsl/gsl_linalg.h>

int main() {
    gsl_matrix *m = gsl_matrix_alloc(3, 3);
    gsl_vector *b = gsl_vector_alloc(3);
    gsl_vector *x = gsl_vector_alloc(3);

    // заполнение матрицы и вектора
    gsl_matrix_set(m, 0, 0, 1.0);
    gsl_matrix_set(m, 0, 1, 2.0);
    gsl_matrix_set(m, 0, 2, 3.0);
    gsl_vector_set(b, 0, 1.0);

    gsl_linalg_HH_solve(m, b, x);

    gsl_matrix_free(m);
    gsl_vector_free(b);
    gsl_vector_free(x);

    return 0;
}

Численное интегрирование

Существуют разные методы интегрирования, такие как адаптивные методы, методы Гаусса-Кронрода и интеграцию на бесконечных интервалах.

#include <gsl/gsl_integration.h>

double f(double x, void *params) {
    return x*x;
}

int main() {
    gsl_integration_workspace *w = gsl_integration_workspace_alloc(1000);
    double result, error;
    gsl_function F;
    F.function = &f;
    gsl_integration_qags(&F, 0, 1, 0, 1e-7, 1000, w, &result, &error);
    gsl_integration_workspace_free(w);
    return 0;
}

Функции хороши для вычисления определенных интегралов и обработки функций с особенностями и сингулярностями.

Генерация случайных чисел

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

#include <gsl/gsl_rng.h>
#include <gsl/gsl_randist.h>

int main() {
    gsl_rng *r = gsl_rng_alloc(gsl_rng_default);
    double x = gsl_ran_gaussian(r, 1.0);
    gsl_rng_free(r);
    return 0;
}

Специальные функции

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

#include <gsl/gsl_sf_bessel.h>

int main() {
    double x = 5.0;
    double y = gsl_sf_bessel_J0(x);
    printf("J0(%g) = %.18e\n", x, y);
    return 0;
}

Статистика и анализ данных

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

#include <gsl/gsl_statistics.h>

int main() {
    double data[] = {17.0, 15.0, 23.0, 7.0, 9.0, 13.0};
    double mean = gsl_stats_mean(data, 1, 6);
    double variance = gsl_stats_variance(data, 1, 6);
    printf("Mean: %g, Variance: %g\n", mean, variance);
    return 0;
}

Пример применения

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

#include <stdio.h>
#include <gsl/gsl_integration.h>
#include <gsl/gsl_matrix.h>
#include <gsl/gsl_linalg.h>
#include <gsl/gsl_statistics.h>

double my_function(double x, void *params) {
    return x*x;
}

int main() {
    // численное интегрирование
    gsl_integration_workspace *workspace = gsl_integration_workspace_alloc(1000);
    gsl_function F;
    F.function = &my_function;
    double result, error;
    gsl_integration_qags(&F, 0, 1, 0, 1e-7, 1000, workspace, &result, &error);
    printf("Integral result: %g, error: %g\n", result, error);
    gsl_integration_workspace_free(workspace);

    // решение системы линейных уравнений
    gsl_matrix *m = gsl_matrix_alloc(2, 2);
    gsl_vector *b = gsl_vector_alloc(2);
    gsl_vector *x = gsl_vector_alloc(2);

    gsl_matrix_set(m, 0, 0, 2.0);
    gsl_matrix_set(m, 0, 1, 1.0);
    gsl_matrix_set(m, 1, 0, 1.0);
    gsl_matrix_set(m, 1, 1, 3.0);
    gsl_vector_set(b, 0, 1.0);
    gsl_vector_set(b, 1, 2.0);

    gsl_linalg_HH_solve(m, b, x);

    printf("Solution: x0 = %g, x1 = %g\n", gsl_vector_get(x, 0), gsl_vector_get(x, 1));

    gsl_matrix_free(m);
    gsl_vector_free(b);
    gsl_vector_free(x);

    // статистический анализ
    double data[] = {17.0, 15.0, 23.0, 7.0, 9.0, 13.0};
    double mean = gsl_stats_mean(data, 1, 6);
    double variance = gsl_stats_variance(data, 1, 6);
    printf("Mean: %g, Variance: %g\n", mean, variance);

    return 0;
}

Как можно увидеть, GSL — очень мощный инструмент. Подробнее с библиотекой можно ознакомиться здесь.

SQLite

SQLite — это легковесная, встроенная, самодостаточная и безсерверная библиотека, реализующая SQL-диск-движо

Архитектура SQLite

  • Токенизатор: Преобразует SQL-запросы в токены для их дальнейшей обработки. Эта стадия важна для правильной интерпретации SQL-команд.

  • Парсер: Использует Lemon парсер для создания синтаксического дерева запросов.

  • Генератор кода: Создает байт-код для выполнения SQL-запросов. Этот байт-код затем интерпретируется VM SQLite.

  • Виртуальная машина: Выполняет байт-код, обеспечивая выполнение SQL-команд. Виртуальная машина реализована в файле vdbe.c и управляется через API, такие как sqlite3_step().

Основные возможности

  • Создание и управление БД: SQLite позволяет создавать базы данных, таблицы и индексы с помощью стандартных SQL-команд.

  • SQL-запросы: Поддержка полного набора SQL-команд, включая SELECT, INSERT, UPDATE, DELETE и JOIN.

  • Встроенные функции: SQLite предоставляет множество встроенных функций, таких как abs(), length(), datetime(), coalesce() и многие другие.

  • Триггеры и представления: Поддержка триггеров и представлений для выполнения автоматических операций и упрощения сложных запросов.

  • Транзакции: Поддержка атомарных транзакций с возможностью отката ROLLBACK и фиксации COMMIT.

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

Пример использования

#include <stdio.h>
#include <sqlite3.h>

int main() {
    sqlite3 *db;
    char *err_msg = 0;

    // открытие/создание БД
    int rc = sqlite3_open("test.db", &db);
    if (rc != SQLITE_OK) {
        fprintf(stderr, "Cannot open database: %s\n", sqlite3_errmsg(db));
        return rc;
    }

    // создание таблицы
    char *sql = "CREATE TABLE IF NOT EXISTS Friends(Id INT, Name TEXT);"
                "INSERT INTO Friends VALUES(1, 'Tom');"
                "INSERT INTO Friends VALUES(2, 'Rebecca');"
                "INSERT INTO Friends VALUES(3, 'Jim');";

    rc = sqlite3_exec(db, sql, 0, 0, &err_msg);
    if (rc != SQLITE_OK) {
        fprintf(stderr, "SQL error: %s\n", err_msg);
        sqlite3_free(err_msg);
        sqlite3_close(db);
        return rc;
    }

    // чтение данных
    sql = "SELECT * FROM Friends";
    sqlite3_stmt *stmt;
    rc = sqlite3_prepare_v2(db, sql, -1, &stmt, 0);
    if (rc != SQLITE_OK) {
        fprintf(stderr, "Failed to fetch data: %s\n", sqlite3_errmsg(db));
        sqlite3_close(db);
        return rc;
    }

    while (sqlite3_step(stmt) == SQLITE_ROW) {
        printf("%s: %s\n", sqlite3_column_text(stmt, 0), sqlite3_column_text(stmt, 1));
    }

    // завершение работы с базой данных
    sqlite3_finalize(stmt);
    sqlite3_close(db);

    return 0;
}

Подробнее про библиотеку можно посмотреть здесь.

SDL

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

Основные функции SDL

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

Для корректного завершения работы и освобождения ресурсов используется функция SDL_Quit:

if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO) != 0) {
    printf("SDL_Init Error: %s\n", SDL_GetError());
    return 1;
}
// завершение работы
SDL_Quit();

SDL позволяет создавать окна с помощью функции SDL_CreateWindow, которая принимает параметры для установки заголовка окна, его позиции и размеров. Для рендеринга используется функция SDL_CreateRenderer, которая создает контекст рендера для заданного окна:

SDL_Window *win = SDL_CreateWindow("Hello SDL", 100, 100, 640, 480, SDL_WINDOW_SHOWN);
if (win == NULL) {
    printf("SDL_CreateWindow Error: %s\n", SDL_GetError());
    SDL_Quit();
    return 1;
}

SDL_Renderer *ren = SDL_CreateRenderer(win, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC);
if (ren == NULL) {
    SDL_DestroyWindow(win);
    printf("SDL_CreateRenderer Error: %s\n", SDL_GetError());
    SDL_Quit();
    return 1;
}

Функция SDL_PollEvent используется для извлечения событий из очереди:

SDL_Event e;
while (SDL_PollEvent(&e)) {
    if (e.type == SDL_QUIT) {
        break;
    }
}

Для работы с изображениями SDL предоставляет функции загрузки текстур и их рендеринга на экран. Функция SDL_CreateTextureFromSurface создает текстуру из загруженного изображения, а SDL_RenderCopy используется для её отображения:

SDL_Surface *bmp = SDL_LoadBMP("image.bmp");
if (bmp == NULL) {
    printf("SDL_LoadBMP Error: %s\n", SDL_GetError());
    SDL_DestroyRenderer(ren);
    SDL_DestroyWindow(win);
    SDL_Quit();
    return 1;
}

SDL_Texture *tex = SDL_CreateTextureFromSurface(ren, bmp);
SDL_FreeSurface(bmp);
if (tex == NULL) {
    printf("SDL_CreateTextureFromSurface Error: %s\n", SDL_GetError());
    SDL_DestroyRenderer(ren);
    SDL_DestroyWindow(win);
    SDL_Quit();
    return 1;
}

SDL_RenderClear(ren);
SDL_RenderCopy(ren, tex, NULL, NULL);
SDL_RenderPresent(ren);

Пример использования

Рассмотрим простой пример создания окна, рендеринга изображения и обработки событий:

#include <SDL.h>
#include <stdio.h>

int main(int argc, char **argv) {
    if (SDL_Init(SDL_INIT_VIDEO) != 0) {
        printf("SDL_Init Error: %s\n", SDL_GetError());
        return 1;
    }

    SDL_Window *win = SDL_CreateWindow("Hello SDL", 100, 100, 640, 480, SDL_WINDOW_SHOWN);
    if (win == NULL) {
        printf("SDL_CreateWindow Error: %s\n", SDL_GetError());
        SDL_Quit();
        return 1;
    }

    SDL_Renderer *ren = SDL_CreateRenderer(win, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC);
    if (ren == NULL) {
        SDL_DestroyWindow(win);
        printf("SDL_CreateRenderer Error: %s\n", SDL_GetError());
        SDL_Quit();
        return 1;
    }

    SDL_Surface *bmp = SDL_LoadBMP("image.bmp");
    if (bmp == NULL) {
        printf("SDL_LoadBMP Error: %s\n", SDL_GetError());
        SDL_DestroyRenderer(ren);
        SDL_DestroyWindow(win);
        SDL_Quit();
        return 1;
    }

    SDL_Texture *tex = SDL_CreateTextureFromSurface(ren, bmp);
    SDL_FreeSurface(bmp);
    if (tex == NULL) {
        printf("SDL_CreateTextureFromSurface Error: %s\n", SDL_GetError());
        SDL_DestroyRenderer(ren);
        SDL_DestroyWindow(win);
        SDL_Quit();
        return 1;
    }

    SDL_RenderClear(ren);
    SDL_RenderCopy(ren, tex, NULL, NULL);
    SDL_RenderPresent(ren);

    SDL_Event e;
    while (1) {
        if (SDL_PollEvent(&e) && e.type == SDL_QUIT) {
            break;
        }
    }

    SDL_DestroyTexture(tex);
    SDL_DestroyRenderer(ren);
    SDL_DestroyWindow(win);
    SDL_Quit();
    return 0;
}

Код создает окно, загружает изображение, отображает его и обрабатывает событие закрытия окна.

Подробнее про библиотеку


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