Глава 3. Массивы указателей, строки и многоуровневая адресация
Массивы указателей
Массив указателей — это массив, каждый элемент которого является указателем на другие данные.
int a = 10, b = 20, c = 30;
int *pointers[3] = {&a, &b, &c}; // Массив из 3 указателей
Массивы указателей на строки
- Массив строк
- Меню программы
#include <stdio.h>
int main() {
char *days[7] = {
"Понедельник",
"Вторник",
"Среда",
"Четверг",
"Пятница",
"Суббота",
"Воскресенье"
};
printf("Дни недели:\n");
for (int i = 0; i < 7; i++) {
printf("%d. %s\n", i + 1, days[i]);
}
// Доступ к отдельным символам
printf("\nПервая буква среды: '%c'\n", *days[2]);
printf("Вторая буква среды: '%c'\n", *(days[2] + 1));
return 0;
}
#include <stdio.h>
int main() {
char *menuOptions[5] = {
"Создать файл",
"Открыть файл",
"Сохранить файл",
"Настройки",
"Выход"
};
int choice = 2; // Выбираем пункт 2
printf("=== ГЛАВНОЕ МЕНЮ ===\n");
for (int i = 0; i < 5; i++) {
printf("%d. %s\n", i + 1, menuOptions[i]);
}
printf("\nВы выбрали: %s\n", menuOptions[choice - 1]);
printf("Длина выбранного пункта: %d символов\n",
(int)(menuOptions[choice - 1] + strlen(menuOptions[choice - 1]) - menuOptions[choice - 1]));
return 0;
}
// Простая функция для подсчета длины строки
int strlen(char *str) {
int len = 0;
while (str[len] != '\0') len++;
return len;
}
Указатели на указатели
Переменная, которая хранит адрес другого указателя.
Двойная адресация
#include <stdio.h>
int main() {
int value = 42;
int *ptr = &value; // Указатель на value
int **ptrToPtr = &ptr; // Указатель на указатель
printf("=== УРОВНИ АДРЕСАЦИИ ===\n");
printf("value = %d\n", value);
printf("*ptr = %d\n", *ptr);
printf("**ptrToPtr = %d\n", **ptrToPtr);
printf("\nАдреса:\n");
printf("&value = %p\n", &value);
printf("ptr = %p\n", ptr);
printf("&ptr = %p\n", &ptr);
printf("ptrToPtr = %p\n", ptrToPtr);
return 0;
}
Изменение через двойной указатель
- Изменение значения
- Изменение указателя
#include <stdio.h>
int main() {
int score = 85;
int *scorePtr = &score;
int **doublePtrScore = &scorePtr;
printf("Исходный счет: %d\n", score);
// Изменяем значение через двойной указатель
**doublePtrScore += 10;
printf("После изменения через **: %d\n", score); // 95
return 0;
}
int first = 100;
int second = 200;
int *ptr = &first;
int **ptrToPtr = &ptr;
printf("Сначала ptr указывает на: %d\n", *ptr); // 100
// Меняем сам указатель через двойной указатель
*ptrToPtr = &second;
printf("Теперь ptr указывает на: %d\n", *ptr); // 200
Практическое применение
Таблица строк
#include <stdio.h>
int main() {
char *employees[4] = {
"Иван Петров",
"Анна Сидорова",
"Петр Козлов",
"Елена Волкова"
};
char *departments[4] = {
"Разработка",
"Маркетинг",
"Продажи",
"HR"
};
printf("=== СПИСОК СОТРУДНИКОВ ===\n");
for (int i = 0; i < 4; i++) {
printf("%-15s - %s\n", employees[i], departments[i]);
}
// Поиск сотрудника
char *searchName = "Анна Сидорова";
for (int i = 0; i < 4; i++) {
char *currentName = employees[i];
char *searchPtr = searchName;
char *namePtr = currentName;
int match = 1;
// Простое сравнение строк
while (*searchPtr != '\0' && *namePtr != '\0') {
if (*searchPtr != *namePtr) {
match = 0;
break;
}
searchPtr++;
namePtr++;
}
if (match && *searchPtr == '\0' && *namePtr == '\0') {
printf("\n✅ %s работает в отделе: %s\n", searchName, departments[i]);
break;
}
}
return 0;
}
Многоуровневая структура данных
#include <stdio.h>
int main() {
// Двумерная структура через массивы указателей
char *subjects[3] = {"Математика", "Физика", "Информатика"};
char *grades[3] = {"Отлично", "Хорошо", "Отлично"};
// Массив указателей на массивы указателей
char **studentData[2] = {subjects, grades};
printf("=== АКАДЕМИЧЕСКИЕ ДАННЫЕ ===\n");
printf("Предметы и оценки:\n");
for (int i = 0; i < 3; i++) {
char *subject = studentData[0][i]; // subjects[i]
char *grade = studentData[1][i]; // grades[i]
printf("%-15s: %s\n", subject, grade);
}
return 0;
}
Динамическое управление строками
Переключение между строками
- Переключение языка
- Таблицы данных
#include <stdio.h>
int main() {
char *messagesRU[3] = {
"Добро пожаловать",
"Выберите действие",
"До свидания"
};
char *messagesEN[3] = {
"Welcome",
"Choose action",
"Goodbye"
};
int language = 0; // 0 = русский, 1 = английский
char **currentMessages = (language == 0) ? messagesRU : messagesEN;
printf("=== ИНТЕРФЕЙС ===\n");
for (int i = 0; i < 3; i++) {
printf("%s\n", currentMessages[i]);
}
// Переключаем язык
language = 1;
currentMessages = (language == 0) ? messagesRU : messagesEN;
printf("\n=== INTERFACE ===\n");
for (int i = 0; i < 3; i++) {
printf("%s\n", currentMessages[i]);
}
return 0;
}
#include <stdio.h>
int main() {
// Таблица информации о товарах
char *products[4] = {"Ноутбук", "Мышь", "Клавиатура", "Монитор"};
char *prices[4] = {"45000", "1500", "3000", "25000"};
char *categories[4] = {"Компьютеры", "Аксессуары", "Аксессуары", "Мониторы"};
char **productTable[3] = {products, prices, categories};
char *headers[3] = {"Товар", "Цена", "Категория"};
printf("=== КАТАЛОГ ТОВАРОВ ===\n");
printf("%-12s %-8s %-12s\n", headers[0], headers[1], headers[2]);
printf("------------------------------------\n");
for (int i = 0; i < 4; i++) {
printf("%-12s %-8s %-12s\n",
productTable[0][i], // products[i]
productTable[1][i], // prices[i]
productTable[2][i]); // categories[i]
}
return 0;
}
Функции с массивами указателей
Обработка списка строк
#include <stdio.h>
int findLongestString(char *strings[], int count) {
int maxLength = 0;
int maxIndex = 0;
for (int i = 0; i < count; i++) {
int length = 0;
char *ptr = strings[i];
// Подсчитываем длину строки
while (*ptr != '\0') {
length++;
ptr++;
}
if (length > maxLength) {
maxLength = length;
maxIndex = i;
}
}
return maxIndex;
}
int main() {
char *cities[5] = {
"Москва",
"Санкт-Петербург",
"Новосибирск",
"Нижний Новгород",
"Казань"
};
int longestIndex = findLongestString(cities, 5);
printf("Города:\n");
for (int i = 0; i < 5; i++) {
printf("%d. %s\n", i + 1, cities[i]);
}
printf("\nСамое длинное название: %s\n", cities[longestIndex]);
return 0;
}
Трехуровневая адресация
Указатель на указатель на указатель
#include <stdio.h>
int main() {
int data = 123;
int *level1 = &data; // Первый уровень
int **level2 = &level1; // Второй уровень
int ***level3 = &level2; // Третий уровень
printf("=== МНОГОУРОВНЕВАЯ АДРЕСАЦИЯ ===\n");
printf("data = %d\n", data);
printf("*level1 = %d\n", *level1);
printf("**level2 = %d\n", **level2);
printf("***level3 = %d\n", ***level3);
printf("\nАдреса:\n");
printf("&data = %p\n", &data);
printf("level1 = %p\n", level1);
printf("&level1 = %p\n", &level1);
printf("level2 = %p\n", level2);
printf("&level2 = %p\n", &level2);
printf("level3 = %p\n", level3);
// Изменение через тройной указатель
***level3 = 456;
printf("\nПосле изменения через ***: data = %d\n", data);
return 0;
}
Практический пример: База данных
Структурированные данные
#include <stdio.h>
int main() {
// База данных студентов
char *names[4] = {"Алексей", "Мария", "Дмитрий", "Анна"};
char *groups[4] = {"ИТ-101", "ИТ-102", "ИТ-101", "ИТ-103"};
char *specialties[4] = {"Программирование", "Дизайн", "Программирование", "Тестирование"};
// Массив указателей на массивы данных
char **database[3] = {names, groups, specialties};
char *fieldNames[3] = {"Имя", "Группа", "Специальность"};
printf("=== БАЗА ДАННЫХ СТУДЕНТОВ ===\n");
// Вывод заголовков
for (int field = 0; field < 3; field++) {
printf("%-15s ", fieldNames[field]);
}
printf("\n");
printf("-----------------------------------------------\n");
// Вывод данных студентов
for (int student = 0; student < 4; student++) {
for (int field = 0; field < 3; field++) {
printf("%-15s ", database[field][student]);
}
printf("\n");
}
// Поиск студентов определенной группы
char *targetGroup = "ИТ-101";
printf("\nСтуденты группы %s:\n", targetGroup);
for (int i = 0; i < 4; i++) {
char *studentGroup = database[1][i]; // groups[i]
char *studentName = database[0][i]; // names[i]
// Простое сравнение строк
char *g1 = studentGroup, *g2 = targetGroup;
int match = 1;
while (*g1 != '\0' && *g2 != '\0') {
if (*g1 != *g2) {
match = 0;
break;
}
g1++;
g2++;
}
if (match && *g1 == '\0' && *g2 == '\0') {
printf("- %s\n", studentName);
}
}
return 0;
}
Манипуляция указателями в массивах
Переназначение указателей
- Изменение направления указателей
- Динамический выбор
#include <stdio.h>
int main() {
char *status1 = "Активен";
char *status2 = "Неактивен";
char *status3 = "Заблокирован";
char *userStatuses[3] = {status1, status1, status2}; // Начальные статусы
char *userNames[3] = {"Алексей", "Мария", "Дмитрий"};
printf("Исходные статусы:\n");
for (int i = 0; i < 3; i++) {
printf("%s: %s\n", userNames[i], userStatuses[i]);
}
// Изменяем статус пользователя
userStatuses[1] = status3; // Мария становится заблокированной
printf("\nПосле изменения:\n");
for (int i = 0; i < 3; i++) {
printf("%s: %s\n", userNames[i], userStatuses[i]);
}
return 0;
}
#include <stdio.h>
int main() {
char *errorMessages[3] = {
"Файл не найден",
"Недостаточно прав доступа",
"Диск заполнен"
};
char *warningMessages[3] = {
"Низкий заряд батареи",
"Обновление доступно",
"Много открытых файлов"
};
int isError = 1; // 1 = ошибка, 0 = предупреждение
int messageCode = 0;
// Выбираем соответствующий массив сообщений
char **currentMessages = isError ? errorMessages : warningMessages;
char *messageType = isError ? "ОШИБКА" : "ПРЕДУПРЕЖДЕНИЕ";
printf("=== %s ===\n", messageType);
printf("Код %d: %s\n", messageCode, currentMessages[messageCode]);
return 0;
}
Функции с многоуровневыми указателями
Обработка таблицы строк
#include <stdio.h>
void printTable(char **rows, int rowCount, char **headers, int colCount) {
// Печатаем заголовки
for (int col = 0; col < colCount; col++) {
printf("%-12s ", headers[col]);
}
printf("\n");
// Печатаем разделитель
for (int col = 0; col < colCount; col++) {
printf("------------ ");
}
printf("\n");
// Печатаем строки данных
for (int row = 0; row < rowCount; row++) {
printf("%-12s ", rows[row]);
}
printf("\n");
}
int main() {
char *productNames[4] = {"Ноутбук", "Мышь", "Клавиатура", "Монитор"};
char *headers[4] = {"Товар", "Статус", "Количество", "Цена"};
printf("Вызов функции с массивом указателей:\n");
printTable(productNames, 4, headers, 4);
return 0;
}
Память и эффективность
Сравнение подходов
#include <stdio.h>
int main() {
// Подход 1: Двумерный массив символов
char countries2D[3][15] = {
"Россия",
"Германия",
"Франция"
};
// Подход 2: Массив указателей на строки
char *countriesPtr[3] = {
"Россия",
"Германия",
"Франция"
};
printf("=== СРАВНЕНИЕ ПАМЯТИ ===\n");
printf("Двумерный массив: %zu байт\n", sizeof(countries2D)); // 45 байт
printf("Массив указателей: %zu байт\n", sizeof(countriesPtr)); // 24 байта
printf("\nОба подхода работают одинаково:\n");
printf("2D массив: %s, %s, %s\n", countries2D[0], countries2D[1], countries2D[2]);
printf("Указатели: %s, %s, %s\n", countriesPtr[0], countriesPtr[1], countriesPtr[2]);
return 0;
}
Ключевые концепции
- Массив указателей — каждый элемент указывает на отдельные данные
- Указатель на указатель — двухуровневая адресация
- Многоуровневая адресация — цепочка указателей для сложных структур
- Эффективность памяти — массивы указателей экономят место для строк разной длины
Когда использовать
- Массивы указателей на строки — для списков текста разной длины
- Указатели на указатели — для изменения самих указателей в функциях
- Многоуровневую адресацию — для сложных иерархических структур данных
Многоуровневая адресация открывает возможности для создания гибких и эффективных структур данных.