Легкая инициализация массивов указателей в C: советы и лучшие практики
Указатель — переменная, хранящая адрес другой переменной. Имя переменной-указателя должно начинаться с символа *. Как и в случае с обычной переменной, мы можем объявить «массив указателей», где каждый индекс массива хранит адрес переменной массива.
Как инициализировать массив указателей в C?
Переменная-указатель может быть инициализирована при объявлении путём присваивания ей адреса существующей переменной. Следующий фрагмент показывает, как инициализировать указатель —
int x = 10;
int *y = &x;
По умолчанию все переменные, включая переменные-указатели, принадлежат классу хранения «auto». Это означает, что переменная-указатель будет хранить непредсказуемый, мусорный, случайный адрес памяти, что может привести к неопределённому поведению и потенциальным рискам для программы, таким как ошибки сегментации. Поэтому её следует инициализировать NULL, если у нас нет конкретного значения для сохранения при объявлении.
int *ptr = NULL;
«Массив указателей» хранит адрес в каждом элементе. Тип массива должен совпадать с типом целевой переменной.
Инициализация массива указателей с ключевым словом static
Можно также использовать ключевое слово static для инициализации массива указателей, сохраняющего 0 в каждом индексе.
Пример
#include <stdio.h>
int main(){
static int *ptr[5];
for (int i = 0; i < 5; i++){
printf("ptr[%d] = %d\n", i, ptr[i]);
}
return 0;
}
Вывод
Запустите код и проверьте его вывод —
ptr[0]= 0
ptr[1]= 0
ptr[2]= 0
ptr[3]= 0
ptr[4]= 0
Инициализация массива целочисленных указателей
Здесь мы объявляем массив целочисленных указателей и сохраняем адреса трёх целочисленных переменных.
Пример
#include <stdio.h>
int main(){
int a = 10, b = 20, c = 30;
int *ptr[3] = {&a, &b, &c};
for (int i = 0; i < 3; i++){
printf("ptr[%d]: address: %d value: %d\n", i, ptr[i], *ptr[i]);
}
return 0;
}
Вывод
Запустите код и проверьте его вывод —
ptr[0]: address: 6422040 value: 10
ptr[1]: address: 6422036 value: 20
ptr[2]: address: 6422032 value: 30
Инициализация массива указателей прямыми адресами
Мы можем хранить адрес каждого элемента обычного массива в соответствующем элементе массива указателей.
Пример
#include <stdio.h>
int main(){
int arr[] = {10, 20, 30};
int *ptr[3] = {&arr[0], &arr[1], &arr[2]};
for (int i = 0; i < 3; i++){
printf("ptr[%d]: address: %d value: %d\n", i, ptr[i], *ptr[i]);
}
return 0;
}
Вывод
Запустите код и проверьте его вывод —
ptr[0]: address: 6422032 value: 10
ptr[1]: address: 6422036 value: 20
ptr[2]: address: 6422040 value: 30
Обход массива через его базовый адрес
Когда мы получаем базовый адрес массива (в данном случае &arr[0]), мы можем получить адреса его последующих элементов, зная, что указатель увеличивается на размер типа данных.
Таким образом, имея только базовый адрес (имя массива то же самое, что адрес 0-го элемента), мы можем обойти массив.
Пример 1
Посмотрите на следующий пример —
#include <stdio.h>
int main(){
int arr[] = {10, 20, 30};
int *ptr = arr;
for (int i = 0; i < 3; i++){
printf("ptr[%d]: address: %d value: %d\n", i, ptr+i, *(ptr+i));
}
return 0;
}
Вывод
Запустите код и проверьте его вывод —
ptr[0]: address: 6422020 value: 10
ptr[1]: address: 6422024 value: 20
ptr[2]: address: 6422028 value: 30
Пример 2: Обход двумерного массива с использованием массива указателей
В этом примере у нас есть двумерный массив. Адрес 0-го элемента каждой строки хранится в массиве указателей. При обходе адрес, сохранённый в каждом элементе массива указателей, указывающий на 0-й элемент соответствующей строки, увеличивается для получения значений в каждой строке.
#include <stdio.h>
int main(){
// 2d array
int arr[3][4] = {
{1, 2, 3, 4},
{5, 6, 7, 8},
};
int ROWS = 2, COLS = 4;
int i, j;
// pointer
int (*ptr)[4] = arr;
// print the element of the array via pointer ptr
for (i = 0; i < ROWS; i++) {
for (j = 0; j < COLS; j++) {
printf("%d ", *(ptr[i]+j));
}
printf("\n");
}
return 0;
}
Вывод
При запуске этот код выдаст следующий вывод —
1 2 3 4
5 6 7 8
Пример 3
Нам на самом деле не нужен массив указателей здесь, так как мы можем использовать имя этого двумерного массива как его базовый указатель и увеличивать его по строкам и столбцам для получения элементов в данном двумерном массиве —
#include <stdio.h>
int main(){
// 2d array
int arr[3][4] = {
{1, 2, 3, 4},
{5, 6, 7, 8},
};
int ROWS = 2, COLS = 4;
int i, j;
// pointer
int *ptr = arr;
// print the element of the array via pointer ptr
for (i = 0; i < ROWS; i++){
for (j = 0; j < COLS; j++){
printf("%d ", *(ptr + i * COLS + j));
}
printf("\n");
}
return 0;
}
Вывод
Вывод похож на предыдущий код —
1 2 3 4
5 6 7 8
Инициализация массива символьных указателей (строк)
В программировании на C строка — это массив типа данных char. Поскольку имя массива также представляет адрес его 0-го элемента, строку можно объявить как —
char arr[] = "Hello";
Используя указательную нотацию, строка присваивается указателю char как —
char *arr = "Hello";
Затем мы можем объявить массив указателей char для хранения нескольких строк следующим образом —
char *arr[3] = {"string1", "string2", "string3", ... };
Пример
В следующем примере массив указателей char используется для хранения названий языков программирования —
#include <stdio.h>
int main(){
char *langs[10] = {
"PYTHON", "JAVASCRIPT", "PHP", "NODE JS", "HTML",
"KOTLIN", "C++", "REACT JS", "RUST", "VBSCRIPT"
};
for(int i = 0; i < 10; i++)
printf("%s\n", langs[i]);
return 0;
}
Вывод
При запуске этот код выдаст следующий вывод —
PYTHON
JAVASCRIPT
PHP
NODE JS
HTML
KOTLIN
C++
REACT JS
RUST
VBSCRIPT
В этой программе langs — указатель на массив из 10 строк. Следовательно, если langs[0] указывает на адрес 5000, то langs + 1 будет указывать на адрес 5004, хранящий указатель на вторую строку.
Поэтому мы также можем использовать следующую вариацию цикла для печати массива строк —
for (int i = 0; i < 10; i++){
printf("%s\n", *(langs + i));
}
Инициализация динамического массива указателей
Можно использовать функцию malloc() для объявления и инициализации массива указателей динамическим способом.
Пример
Посмотрите на следующий пример —
#include <stdio.h>
int main(){
int *arr = (int *)malloc(sizeof(int) * 5);
for(int i = 0; i < 5; i++){
arr[i] = i;
}
for (int x = 0; x < 5; x++){
printf("%d %d\n", x, arr[x]);
}
return 0;
}
Вывод
При запуске этот код выдаст следующий вывод —
0 0
1 1
2 2
3 3
4 4
Вы можете даже запрашивать пользовательский ввод и присваивать значения элементам в массиве указателей —
for(i = 0; i < 5; i++){
scanf("%d", &x);
arr[i] = x;
}
Язык C
- Структуры против объединений в C: практическое руководство
- Типы данных в C: int, float, char и другие — полное руководство
- Управление потоком в C: операторы break и continue
- Понимание конструкторов в C++: конструкторы по умолчанию, параметризованные и копирования
- Классы хранения в C: область видимости, время жизни и производительность
- Многопоточность в C#: Повышение производительности и эффективности
- Освоение оператора break в C++
- Потоки в C++: cout, cin, cerr, clog — практическое руководство
- C#: обзор современного объектно-ориентированного языка от Microsoft
- Интерфейсы в C#: определение, пример и практическое применение