Структурный тип – это составной тип данных, каждая переменная которого объединяет несколько компонентов (полей) базовых или ранее определённых структур типов. Описание структурного типа в C начинается с ключевого слова struct
, за которым идёт имя нового типа и список полей в фигурных скобках:
struct point {
int x;
int y;
};
При описании самого типа память не выделяется – она резервируется только при объявлении переменной этого типа:
struct point pt; // здесь для полей x и y выделяется память;
Доступ к полям переменной осуществляется через оператор точки:
pt.x = 5;
pt.y = 7;
Компонентами структуры могут быть и другие структуры:
struct rect {
struct point pt1;
struct point pt2;
};
struct rect screen;
screen.pt2.x = 1337;
Интернет
В стандарте C99 и выше возможна инициализация при объявлении:
struct point pt = { .x = 0, .y = 0 };
Кроме того, поддерживаются агрегатные инициализаторы (например, {0}
для обнуления всех полей).
В C структуры при передаче в функции передаются по значению – происходит полная копия всех полей:
struct point makepoint(int x, int y);
struct point p = makepoint(3, 4);
Функция может возвращать структуру целиком:
struct point addpoint(struct point p1, struct point p2) {
p1.x += p2.x;
p1.y += p2.y;
return p1;
}
Интернет
В C++ инициализацию копированием может оптимизировать компилятор (RVO, copy elision), но в общем случае копирование больших структур влияет на производительность. Поэтому часто используют передачу по константной ссылке (const &
) для избежания лишнего копирования.
Для передачи или работы с большими структурами эффективнее использовать указатели. Объявление указателя на структуру:
struct point pt;
struct point *pp = &pt;
Доступ к полям через указатель возможен двумя способами:
// разыменование и обращение к полю
(*pp).x = 10;
// краткая запись через оператор "стрелка"
pp->y = 20;
Интернет
Путём арифметики указателей можно перемещаться по массиву структур:
struct point arr[5];
struct point *p = arr;
p += 2;
// теперь указывает на arr[2]
p->x = 7;
Массивы структур работают аналогично массивам базовых типов, только каждый элемент – это полноценная структура:
struct key {
char *word;
int count;
} keytab[100];
// массив из 100 структур key
Обход массива по индексам:
for (int i = 0; i < 100; i++) {
keytab[i].count++;
}
С помощью указателя:
struct key *kp;
for (kp = keytab; kp < keytab + 100; kp++) {
if (kp->count > 0) {
printf("%4d %s\n", kp->count, kp->word);
}
}
Интернет
В стандарте C99 введены гибкие массивы (flexible array members), позволяющие описывать структуры с полем-массивом переменной длины:
struct flex {
size_t n;
// гибкий массив
int data[];
};
Такие структуры обычно выделяются динамически с учётом требуемого размера массива.
Объединение (union
) – это составной тип данных, аналогичный структуре, но память под все его поля выделяется под самый «тяжёлый» (по размеру) компонент, и в каждый момент времени может храниться значение только одного из полей. Это позволяет экономить память, если разные представления данных никогда не используются одновременно. Синтаксис и обращение к полям полностью идентичны структурам:
union room {
// Целое число
int a1;
// Вещественное число
float a2;
};
union room kreslo;
kreslo.a1 = 5;
// теперь kreslo хранит в себе целое 5
Объединение может содержать внутри себя и структуры, и быть полем структуры:
struct student {
char *name;
int ID;
union {
int sovest;
float eda;
} ostatok;
} group[25];
group[3].ostatok.sovest = 10;
typedef
Поля битов позволяют задать точное количество бит, отводимых под каждое целое поле в структуре. Поля битов объявляются только внутри структур и обычно используют беззнаковые типы:
struct {
// 1 бит
unsigned int flag1 : 1;
// 2 бита (значения 0–3)
unsigned int flag2 : 2;
} komissiya;
Поля битов не могут быть массивами и не имеют адресов.
typedef
позволяет создавать псевдонимы для существующих типов:
// теперь "string" – синоним для "char*"
typedef char *string;
string s = "Hello";
Псевдоним используется везде, где можно было бы применить оригинальный тип.
Интернет
В препроцессоре C/C++ макрос – это правило текстовой подстановки, определяемое директивой #define
.
Объектные макросы:
#define PI 3.14159
При каждом упоминании PI
до компиляции подставляется 3.14159
.
Функциональные макросы:
#define SQR(x) ((x) * (x))
При встрече SQR(a+1)
препроцессор подставит ((a+1) * (a+1))
.
Макросы обрабатываются перед компиляцией, не имеют области видимости и не учитывают типы. Их следует использовать осторожно, чтобы избежать нежелательных эффектов от двойного вычисления аргументов и проблем с приоритетом операций.
Интернет
Подключение заголовочных файлов осуществляется директивой препроцессора #include
.
Примеры:
/* системный заголовок */
#include <stdio.h>
/* локальный файл */
#include "myheader.h"
Обычно в заголовочных файлах применяют защитные макрозаголовки (include guards), чтобы предотвратить повторное включение:
#ifndef HEADER_H
#define HEADER_H
// ...
#endif
Это гарантирует, что код внутри будет обработан только один раз, даже если файл включён несколько раз.
Интернет
В препроцессоре C/C++ условная компиляция позволяет включать или исключать участки кода в зависимости от заданных макросов. Используются директивы:
// если выражение истинно (не ноль)
#if выражение
/* код */
// иначе, если выражение2 истинно
#elif выражение2
/* код */
// иначе
#else
/* код */
#endif
// если MACRO определён
// (аналогично #if defined(MACRO))
#ifdef MACRO
/* код */
#endif
// если MACRO не определён
// (аналогично #if !defined(MACRO))
#ifndef MACRO
/* код */
#endif
Обычно применяется для портируемости (различные ОС, платформы), отладки (#ifdef DEBUG
), и защиты заголовков от многократного включения.
#ifndef HEADER_H
#define HEADER_H
// ...
#endif
math.h
и string.h
. Основные возможностиИнтернет
math.h
предоставляет функции для математических вычислений с плавающей точкой:
• Тригонометрические: sin
, cos
, tan
, asin
, acos
, atan
.
• Экспоненциальные и логарифмические: exp
, log
, log10
, pow
, sqrt
.
• Округление и абсолютная величина: ceil
, floor
, fabs
.
• Специальные: modf
, frexp
, ldexp
.
Каждый прототип начинается с double
, есть варианты для float
и long double
(sinf
, sinl
и т.д.).
string.h
содержит функции для работы со строками и блоками памяти:
• Копирование и конкатенация строк: strcpy
, strncpy
, strcat
, strncat
.
• Сравнение: strcmp
, strncmp
, strcoll
.
• Поиск: strchr
, strrchr
, strstr
.
• Длина: strlen
.
• Работа с памятью: memcpy
, memmove
, memset
, memcmp
.
ctype.h
и stdlib.h
. Основные возможностиИнтернет
ctype.h
предоставляет проверки и преобразования символов:
• Классификация: isalpha
, isdigit
, isalnum
, isspace
, ispunct
, isupper
, islower
.
• Преобразование регистра: toupper
, tolower
.
stdlib.h
охватывает утилиты общего назначения:
• Конвертация строк в числа: atoi
, atof
, strtol
, strtod
.
• Динамическое выделение памяти: malloc
, calloc
, realloc
, free
.
• Управление процессом: exit
, abort
.
• Поиск элементов: bsearch
, qsort
.
• Генерация псевдослучайных чисел: rand
, srand
.
Интернет
Открытие двоичного файла:
// чтение
FILE *f = fopen("data.bin", "rb");
// запись (с обнулением)
FILE *g = fopen("data.bin", "wb");
// чтение / запись без обнуления
FILE *h = fopen("data.bin", "r+b");
Последовательный доступ – чтение / запись блоками через fread
/ fwrite
, указатель смещается автоматически:
struct record rec;
while (fread(&rec, sizeof(rec), 1, f) == 1) {
// обработать rec
}
Произвольный доступ – использование fseek
/ ftell
/ rewind
, для перемещения по файлу:
// перейти к 10-му элементу:
fseek(f, 9 * sizeof(struct record), SEEK_SET);
fread(&rec, sizeof(rec), 1, f);
// получить текущую позицию
long pos = ftell(f);
// вернуться в начало
rewind(f);
Это позволяет эффективно обновлять отдельные записи без чтения всего файла.
В прототипе функции C++ можно указывать значения параметров по умолчанию. Если при вызове функции некоторые аргументы пропущены, компилятор подставит указанные в прототипе значения.
• Параметры со значениями по умолчанию должны идти после всех обязательных параметров.
• Если пропустить один аргумент, необходимо пропустить и все последующие параметры.
// Прототип с аргументами по умолчанию
void ShowMes(char *msg, int x = 0, int y = 0);
// Корректные вызовы
ShowMes("Привет");
// x=0, y=0
ShowMes("Координаты", 5);
// x=5, y=0
ShowMes("Координаты", 5, 7);
// x=5, y=7
// Неверный вызов (пропущен msg, но указан x):
ShowMes(10, 20); // Ошибка
Ссылка (reference
) в C++ – это псевдоним (алиас) для уже существующей переменной. Ссылка не занимает дополнительной памяти и после инициализации неизменна (нельзя «перепривязать» её к другой переменной).
• Объявление ссылки: int &r = original;
• При передаче параметров в функцию ссылка позволяет модифицировать оригинал без явного использования указателя, причём синтаксис вызова не отличается от передачи по значению.
int a = 5;
int &r = a;
// r – псевдоним для a
r += 10;
// теперь a == 15
void inc(int &x) { x++; }
inc(a);
// a изменится, как если
// бы мы передавали указатель
Интернет
В качестве возвращаемого типа функции ссылка позволяет реализовать «левыхзначный» вызов, например:
int arr[10];
int& elem(int i) { return arr[i]; }
elem(3) = 42;
// присваивает arr[3] значение 42
Ключевое слово inline
предписывает компилятору подставлять тело функции вместо обычного вызова, что сокращает накладные расходы на передачу управления, но может увеличить размер кода.
• Преимущества: уменьшение времени вызова, потенциальное ускорение.
• Недостатки: рост размера скомпилированного кода, ухудшение локальности инструкций при чрезмерном использовании.
• Компилятор может игнорировать inline
, если функция слишком сложна (содержит циклы, switch
, goto
, множественные return
и т.п.).
inline int Sqr(int x) {
return x * x;
}
Оператор ::
в C++ позволяет явно указать область видимости имени, например, обратиться к глобальной переменной, скрытой локальной, либо к статическому члену класса.
• Доступ к глобальной переменной внутри функции, где есть локальная с тем же именем ::name
• Доступ к статическому полю или методу класса: ClassName::staticMember
// глобальная
int value = 100;
void func() {
int value = 50;
// выводит 50 (локальная)
std::cout << value << "\n";
// выводит 100 (глобальная)
std::cout << ::value << "\n";
}
// Статический член класса
class Counter {
public:
static int count;
};
int Counter::count = 0;
void inc() {
Counter::count++;
}
Перегрузка функций (function overloading) в C++ означает возможность объявить несколько функций с одинаковым именем, но различающимся списком параметров (количеством и/или типами аргументов). Компилятор при вызове выбирает ту версию функции, чья сигнатура наилучшим образом соответствует переданным аргументам. При этом отличаться может только набор параметров – изменение только возвращаемого типа или только наличия ссылочного параметра не считается перегрузкой.
// принимает целое
void Showmes(int);
// принимает строку
void Showmes(char*);
При вызове Showmes(5)
используется первая версия, при Showmes("Hello")
– вторая.
Интернет
Перегрузка функций расширяет возможность реализации универсальных интерфейсов, но чрезмерное количество перегрузок может затруднить читаемость кода и отладку.
В отличие от C, где локальные переменные должны определяться в начале блока или функции, в C++ переменную можно объявить и инициализировать в любом месте кода, где это необходимо, что повышает гибкость и читаемость. Кроме того, модификатор const
в C++ делает переменную истинно константной, позволяя использовать её в местах, требующих compile-time константу (например, при определении размеров массивов статической длины). При описании переменных пользовательских типов (структур или классов) ключевое слово struct
опускать не нужно – достаточно написать имя класса или структуры напрямую:
// вместо struct Point p;
Point p;
const int N = 10;
// допустимо в C++
int arr[N];
Класс в C++ – это расширение структурного типа, в котором помимо полей (данных) могут определяться методы (функции-члены), а также спецификаторы доступа (public
, protected
, private
). Определение класса начинается с ключевого слова class
(или struct
, с отличием доступа по умолчанию) и включает в себя список членов:
class Point {
private:
int x, y;
public:
// конструктор
Point(int xx, int yy);
// деструктор
~Point();
// метод для чтения поля
int getX() const;
// метод для записи поля
void setX(int);
};
Если использовать struct
вместо class
, все члены по умолчанию будут public
.
Элементами класса в C++ являются:
• Данные-члены (поля) – могут быть простыми типами, объектами других классов, указателями или ссылками, но не самим классом.
• Функции-члены (методы) – либо определённые прямо в теле класса (в этом случае они автоматически считаются inline
), либо объявленные в классе и реализованные вне него с помощью операции разрешения видимости ClassName::method
.
• Специальные методы:
• Конструкторы (создают и инициализируют объект),
• Деструкторы (освобождают ресурсы при уничтожении объекта),
• Операции копирования / перемещения,
• Оператор присваивания.
• Статические члены – данные и методы, общие для всех объектов класса (существуют независимо от наличия экземпляров).
В зависимости от спецификатора доступа члены могут быть доступны из любой части программы (public
), только внутри класса и его наследников (protected
) или исключительно внутри самого класса (private
).
Обратиться к элементам данных и методам класса в C++ можно четырьмя способами:
• Через объект класса и оператор .
:
obj.method();
int v = obj.field;
• Через указатель на объект и оператор ->
:
ptr->method();
ptr->field = 5;
• Через имя класса и оператор разрешения видимости ::
(для доступа к статическим членам):
ClassName::staticField = 10;
ClassName::staticMethod();
• Без каких-либо приставок – только в теле метода самого класса (неявный доступ к своим членам):
void ClassName::foo() {
// эквивалент this->field
field = 3;
// вызов метода helper()
helper();
}
Конструктор – специальный метод с именем класса и без возвращаемого типа, автоматически вызывается при создании объекта. Если конструктор не определён, компилятор генерирует конструктор по умолчанию.
class MyClass {
public:
// конструктор по умолчанию
MyClass();
// перегружённый конструктор
MyClass(int a, int b);
};
MyClass::MyClass() {
/* инициализация */
}
MyClass::MyClass(int a, int b) : x(a), y(b) { }
Деструктор – метод с тем же именем, что и класс, но с префиксом ~
, вызывается при уничтожении объекта; не имеет параметров и возвращаемого значения:
class MyClass {
public:
// деструктор
~MyClass();
};
MyClass::~MyClass() {
/* очистка ресурсов */
}
Язык C++ позволяет задать для классов собственные реализации операторов. Сигнатура функции-перегрузчика имеет вид
ReturnType operatorX(Params);
где X
– знак операции (+
, -
, <<
и т.п.).
• Унарные операторы определяются с нулём параметров или одним параметром при внешней реализации.
• Бинарные – с одним параметром (если метод класса) или двумя (если свободная функция).
Пример перегрузки +
для класса Proverka
:
class Proverka {
int a, b;
public:
Proverka(int aa, int bb): a(aa), b(bb) {}
Proverka operator+(const Proverka &other) const {
return Proverka(a + other.a, b + other.b);
}
};
При этом приоритет и семантика операторов сохраняются, а перегружать можно только те операторы, в которых участвует хотя бы один операнд-объект пользовательского класса.
Интернет
В C++ для обработки ошибок и исключительных ситуаций используется механизм исключений:
• Генерация исключения через throw
:
if (x == 0)
throw std::runtime_error("Деление на ноль");
• Захват через try
/ catch
:
try {
dangerousOperation();
}
catch (const std::exception &e) {
std::cerr << "Ошибка: " << e.what() << std::endl;
}
• Можно объявлять несколько блоков catch
для разных типов исключений.
• Важный оператор finally
отсутствует, но аналогично завершающие действия можно выполнять в деструкторах локальных объектов (RAII).
• Для собственных типов исключений обычно наследуют от std::exception
или его потомков