Указатели и массивы
Связь между указателями и массивами
Имя массива в языке Си является указателем на его первый элемент. Это фундаментальная связь между двумя концепциями.
int numbers[5] = {10, 20, 30, 40, 50};
int *ptr = numbers; // Эквивалентно: int *ptr = &numbers[0];
Эквивалентность записей
Доступ к элементам
- Одинаковые способы доступа
- Адреса элементов
#include <stdio.h>
int main() {
int data[4] = {100, 200, 300, 400};
int *ptr = data;
printf("=== ЭКВИВАЛЕНТНЫЕ СПОСОБЫ ДОСТУПА ===\n");
// Все эти записи дают одинаковый результат:
printf("data[0] = %d\n", data[0]); // Обычная индексация
printf("*(data + 0) = %d\n", *(data + 0)); // Указатель + смещение
printf("ptr[0] = %d\n", ptr[0]); // Индексация указателя
printf("*ptr = %d\n", *ptr); // Разыменование указателя
printf("\nВторой элемент:\n");
printf("data[1] = %d\n", data[1]);
printf("*(ptr + 1) = %d\n", *(ptr + 1));
printf("ptr[1] = %d\n", ptr[1]);
return 0;
}
int array[3] = {10, 20, 30};
printf("Адреса элементов массива:\n");
printf("&array[0] = %p\n", &array[0]);
printf("array = %p\n", array);
printf("array + 0 = %p\n", array + 0);
printf("&array[1] = %p\n", &array[1]);
printf("array + 1 = %p\n", array + 1);
printf("&array[2] = %p\n", &array[2]);
printf("array + 2 = %p\n", array + 2);
Обход массива указателем
Последовательный доступ
#include <stdio.h>
int main() {
int temperatures[7] = {18, 22, 25, 19, 16, 28, 24};
int *current = temperatures;
printf("Температуры за неделю:\n");
for (int day = 0; day < 7; day++) {
printf("День %d: %d°C\n", day + 1, *current);
current++; // Переходим к следующему элементу
}
return 0;
}
Обход с арифметикой указателей
- Через арифметику
- Через инкремент
#include <stdio.h>
int main() {
int scores[5] = {85, 92, 78, 95, 88};
int *ptr = scores;
printf("Оценки студентов:\n");
for (int i = 0; i < 5; i++) {
printf("Студент %d: %d баллов\n", i + 1, *(ptr + i));
}
return 0;
}
float prices[4] = {19.99, 34.50, 12.75, 45.20};
float *pricePtr = prices;
printf("Цены товаров:\n");
for (int i = 0; i < 4; i++) {
printf("Товар %d: %.2f руб.\n", i + 1, *pricePtr);
pricePtr++;
}
Передача массивов в функции
Массив как параметр
#include <stdio.h>
void printArray(int *arr, int size) {
printf("Элементы массива: ");
for (int i = 0; i < size; i++) {
printf("%d ", *(arr + i));
}
printf("\n");
}
int main() {
int numbers[6] = {1, 2, 3, 4, 5, 6};
printArray(numbers, 6); // Передаем массив как указатель
return 0;
}
Модификация массива через указатель
#include <stdio.h>
void doubleValues(int *arr, int size) {
for (int i = 0; i < size; i++) {
*(arr + i) *= 2; // Удваиваем каждый элемент
}
}
int main() {
int values[4] = {5, 10, 15, 20};
printf("До изменения: ");
for (int i = 0; i < 4; i++) {
printf("%d ", values[i]);
}
printf("\n");
doubleValues(values, 4); // Изменяем массив
printf("После изменения: ");
for (int i = 0; i < 4; i++) {
printf("%d ", values[i]);
}
printf("\n");
return 0;
}
Границы массива и указатели
Безопасная работа с границами
#include <stdio.h>
int main() {
int data[5] = {10, 20, 30, 40, 50};
int *start = data;
int *end = data + 5; // Указывает ЗА последний элемент
int *current = start;
printf("Безопасный обход:\n");
while (current < end) {
int index = current - start;
printf("Индекс %d: значение %d\n", index, *current);
current++;
}
printf("Обход завершен безопасно\n");
return 0;
}
Многомерные массивы и указатели
Двумерные массивы
- Основы 2D массивов
- Доступ по строкам
#include <stdio.h>
int main() {
int matrix[3][4] = {
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12}
};
int *ptr = &matrix[0][0]; // Указатель на первый элемент
printf("Обход 2D массива через указатель:\n");
for (int i = 0; i < 12; i++) { // 3 × 4 = 12 элементов
printf("%2d ", *(ptr + i));
if ((i + 1) % 4 == 0) { // Переход на новую строку каждые 4 элемента
printf("\n");
}
}
return 0;
}
int table[2][3] = {
{10, 20, 30},
{40, 50, 60}
};
// Указатель на строку
int *firstRow = table[0]; // Указывает на первую строку
int *secondRow = table[1]; // Указывает на вторую строку
printf("Первая строка: ");
for (int i = 0; i < 3; i++) {
printf("%d ", *(firstRow + i));
}
printf("\n");
printf("Вторая строка: ");
for (int i = 0; i < 3; i++) {
printf("%d ", *(secondRow + i));
}
printf("\n");
Динамическое перемещение по массиву
Поиск с указателями
#include <stdio.h>
int main() {
int studentIds[8] = {1001, 1005, 1012, 1008, 1003, 1015, 1020, 1007};
int size = 8;
int searchId = 1008;
int *start = studentIds;
int *end = studentIds + size;
int *found = NULL;
printf("Поиск студента ID: %d\n", searchId);
for (int *current = start; current < end; current++) {
printf("Проверяем: %d\n", *current);
if (*current == searchId) {
found = current;
break;
}
}
if (found != NULL) {
int position = found - start;
printf("✅ Студент найден на позиции %d\n", position);
} else {
printf("❌ Студент не найден\n");
}
return 0;
}
Сортировка через указатели
#include <stdio.h>
void bubbleSort(int *arr, int size) {
for (int i = 0; i < size - 1; i++) {
for (int j = 0; j < size - 1 - i; j++) {
// Сравниваем соседние элементы
if (*(arr + j) > *(arr + j + 1)) {
// Меняем местами через указатели
int temp = *(arr + j);
*(arr + j) = *(arr + j + 1);
*(arr + j + 1) = temp;
}
}
}
}
int main() {
int numbers[6] = {64, 34, 25, 12, 22, 11};
int size = 6;
printf("До сортировки: ");
for (int i = 0; i < size; i++) {
printf("%d ", numbers[i]);
}
printf("\n");
bubbleSort(numbers, size);
printf("После сортировки: ");
for (int i = 0; i < size; i++) {
printf("%d ", numbers[i]);
}
printf("\n");
return 0;
}
Важные особенности
Размер массива и указателя
#include <stdio.h>
int main() {
int array[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
int *ptr = array;
printf("Размер массива: %zu байт\n", sizeof(array)); // 40 байт (10 × 4)
printf("Размер указателя: %zu байт\n", sizeof(ptr)); // 8 байт (на 64-бит системе)
printf("Количество элементов в массиве: %zu\n", sizeof(array) / sizeof(array[0]));
// ❌ Через указатель размер массива получить нельзя!
// printf("Элементов через ptr: %zu\n", sizeof(ptr) / sizeof(ptr[0])); // Неправильно!
return 0;
}
Важное отличие
Имя массива и указатель на массив ведут себя по-разному:
sizeof(array)возвращает размер всего массиваsizeof(ptr)возвращает размер самого указателя- При передаче в функцию массив становится указателем
Указатели на указатели и массивы
Массив указателей
#include <stdio.h>
int main() {
int a = 10, b = 20, c = 30;
int *pointers[3] = {&a, &b, &c}; // Массив указателей
printf("Массив указателей:\n");
for (int i = 0; i < 3; i++) {
printf("pointers[%d] указывает на: %d\n", i, *pointers[i]);
}
// Изменяем значения через массив указателей
*pointers[0] = 100;
*pointers[1] = 200;
*pointers[2] = 300;
printf("\nПосле изменения:\n");
printf("a = %d, b = %d, c = %d\n", a, b, c);
return 0;
}
Ключевые принципы
- Имя массива = указатель на первый элемент
- array[i] = *(array + i)
- &array[i] = array + i
- Указатель можно использовать как массив
- Массив можно обрабатывать как указатель
Указатели и массивы тесно связаны — понимание этой связи ключ к эффективной работе с данными в языке Си.