Ваш "Дахах ...." момент:
for (int j = 0; j < n; i++)
Почему вы используете i++
в цикле j
?
Помимо опечатки, вы обычно хотите передать только m
и n
на InitializeTable
, объявить B
локально на InitializeTable
, выделить m
указатели, а затем n
символы и назначить начальный адрес для каждого распределения символов для последовательного указателя и возврата B
и присвоения возврата обратно в main()
. [1] Это предотвращает передачу адреса B
в качестве параметра и, таким образом, становление 3-звездочным программистом (не комплимент). Тем не менее, в упражнении есть образовательная цель.
Когда вы объявляете char **B;
в main()
, B
- это унифицированный указатель на указатель на символ . У него есть свой собственный адрес (указатель B
), но он нигде не указывает (на самом деле адрес, удерживаемый B
, является неопределенным и, скорее всего, каким бы он ни был по адресу B
на момент его объявления. Вы не можете используйте B
для любых других целей на этом этапе, кроме назначения адреса другого указателя char **
, который был правильно инициализирован.
Когда вы передаете адрес B
на InitializeTable
, например,
InitializeTable (&B, m, n);
и B
получает адрес, вы должны выделить m
указатели и назначить начальный адрес для указателей как значение , сохраняемое B
(не как адрес 3-звездочного указателя) , Для этого необходимо разыменовать B
в InitializeTable
. (точно так же, как вы объявляете int *a, b = 5;
, а затем a
указываете на b
с помощью a = &b
, чтобы изменить значение, на которое указывает b
, вы можете разыменовывать и разбирать, например, *b = 10;
) Пример:
void InitializeTable (char ***B, int m, int n)
{
*B = new char*[m];
Используя оператор new
, вы выделили хранилище для m
указателей (char*
) и присвоили начальный адрес указателю B
в main()
, *B
в InitializeTable
.
Теперь вам нужно выделить n
символов для каждого указателя и назначить начальный адрес для каждого блока каждому указателю. Однако, поскольку мы являемся 3-звездочными программистами и имеем один дополнительный уровень косвенности, вместо присвоения B[i]
, мы должны сначала разыменовать B
, но C ++ Precedence операторов вызывает []
для более тесного связывания (имеет более высокий приоритет), чем оператор разыменования '*'
, поэтому сначала необходимо заключить *B
в круглые скобки, например (*B)[i]
с:
for (int i = 0; i < m; i++) {
(*B)[i] = new char[n];
Теперь вы можете назначать пробелы в качестве символов для инициализации значений символов в (*B)[i]
, например,
for (int j = 0; j < n; j++)
(*B)[i][j] = ' ';
( примечание: все j
с в определении цикла)
Вот и все, что нужно сделать. В целом, вы можете сделать:
#include <iostream>
#include <string>
void InitializeTable (char ***B, int m, int n)
{
*B = new char*[m];
for (int i = 0; i < m; i++) {
(*B)[i] = new char[n];
for (int j = 0; j < n; j++)
(*B)[i][j] = ' ';
}
}
int main (void) {
std::string strX = "cats",
strY = "dogs";
//strX and strY are strings
int m = strX.length();
int n = strY.length();
//declare two dynamic 2-Dimensional array of variable length B is m X n
char **B;
InitializeTable (&B, m, n);
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++)
std::cout << " '" << B[i][j] << "'";
std::cout << '\n';
delete[] B[i]; /* free each block of characters */
}
delete[] B; /* free pointers */
}
(не забудьте освободить память, содержащую символы, а также выделенные вами указатели)
Пример использования / Вывод
$ ./bin/threestarc++
' ' ' ' ' ' ' '
' ' ' ' ' ' ' '
' ' ' ' ' ' ' '
' ' ' ' ' ' ' '
Использование памяти / проверка ошибок
В любом написанном вами коде, который динамически распределяет память, у вас есть 2 обязанностей относительно любого выделенного блока памяти: (1) всегда сохраняйте указатель на начальный адрес для блока памяти, так что, (2) он может быть освобожден , когда он больше не нужен.
Обязательно, чтобы вы использовали программу проверки ошибок памяти, чтобы гарантировать, что вы не пытаетесь получить доступ к памяти или писать за пределами / за пределами выделенного блока, пытаться прочитать или основать условный переход на неинициализированном значении и, наконец, , чтобы подтвердить, что вы освобождаете всю выделенную память.
Для Linux valgrind
- нормальный выбор. Для каждой платформы есть похожие проверки памяти. Все они просты в использовании, просто запустите вашу программу через него.
$ ./bin/threestarc++
'' '' '' ''
'' '' '' ''
'' '' '' ''
'' '' '' ''
$ valgrind ./bin/threestarc++
==784== Memcheck, a memory error detector
==784== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.
==784== Using Valgrind-3.12.0 and LibVEX; rerun with -h for copyright info
==784== Command: ./bin/threestarc++
==784==
' ' ' ' ' ' ' '
' ' ' ' ' ' ' '
' ' ' ' ' ' ' '
' ' ' ' ' ' ' '
==784==
==784== HEAP SUMMARY:
==784== in use at exit: 0 bytes in 0 blocks
==784== total heap usage: 8 allocs, 8 frees, 72,810 bytes allocated
==784==
==784== All heap blocks were freed -- no leaks are possible
==784==
==784== For counts of detected and suppressed errors, rerun with: -v
==784== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
Всегда подтверждайте, что вы освободили всю выделенную память и что ошибок памяти нет.
примечания:
1. На самом деле вы бы хотели объявить вектор вектора <char>
(например, std::vector<std::vector<char>>
в main()
и передать ссылку на InitializeTable
для инициализации, позволяя C ++ обрабатывать управление памятью длявы.