C ++ Pointers / Arrays (я сейчас совершенно запутался и очень нуждаюсь в помощи) - PullRequest
0 голосов
/ 03 мая 2018

Так вот что я закончил, придумав для этого. В настоящее время я не могу понять, как поместить свою функцию reverseArray в основную ... Если я просто копирую и вставляю в нее код, он вылетает при каждом запуске ... Я не знаю, почему это приводит к сбою или что-нибудь. Спасибо всем, кто помог мне с этим.

using namespace std;

// Prototype for printArray goes here
void reverseArray(int*, int);
void printArray(int*, int);

int main()
{
    int size;   // size of the dynamically allocated array

                // Declare as needed for a dynamically allocated array of 
ints named "data".

            // Declare other variables as needed


            // Edit to display your own name
cout << "" << endl << endl;

// Prompt the user for the array size
cout << "Array size: ";
cin >> size;

// Add code to validate array size, so it is greater than one
while (size < 2)
{
    cout << "Array size must be greater than 1: ";
    cin >> size;
}

// Add code to dynamically allocate "data". Don't forget to release the memory before
// the program ends
int *data = new int[size],
    *p = data;
// Write a loop to fill the "data" array with random numbers from 1 - 100 (inclusive)
// This code must use POINTER NOTATION (no subscripting) to work with the array.
// Reminder: neither of these notations is acceptable here:
// data[n]  or *(data + n)
// Instead this code will use pointer incrementing/decrementing and dereferencing

for (int i = 0; i < size; i++, p++)
{
    *p = rand() % 100 + 1;
}
// Call function to print the original "data" array
cout << "\nOriginal array:\n" << endl;
printArray(data, size);
// Reset "data" to point to the beginning of the array 

// Add code to reverse the array. Use 2 pointers: one starts at the beginning of the array and
// moves forward, the other starts at its last element and works backward. Swap the values they
// point to.
// Reminder: neither of these notations is acceptable here:
// data[n]  or *(data + n)
// Instead this code will use pointer incrementing/decrementing and dereferencing
// For this, I made the function reverseArray instead of coding it in main.
reverseArray(data, size);
cout << endl;
cout << "\nReversed array:\n" << endl;
printArray(data, size);
cout << endl << endl;
// Finish up
delete[] data;
system("pause");
return 0;
}

// Function printArray() goes here. Print the array, 5 numbers per line, 
right-aligned
void printArray(int*p, int size)
{
    for (int i = 0; i < size; i++, p++)
    {
        cout << setw(5) << right << *p;
        if ((i + 1) % 5 == 0)
        {
            cout << endl;
        }
    }
}
// Function reverseArray() Reverses the array.
void reverseArray(int *data, int size)
{
    int *e = data + size - 1;      // Pointer at the end
    for (; e > data; data++, e--) // while end pointer (e)> start pointer, swap start w/ end
    {
        int arrayFlip = *data;
        *data = *e;
        *e = arrayFlip;
    }
}

Ответы [ 3 ]

0 голосов
/ 03 мая 2018

Перемещение указателя

data++;

и

data--;

в основном. Есть и другие вещи, которые вы могли бы сделать, но ваш инструктор попросил об увеличении и уменьшении.

Итак

for (int i = 0; i < size; i++)
{
    *data = rand() % 100 + 1;
    cout << setw(5) << right << *data;
    if ((i + 1) % 5 == 0)
    {
        cout << endl;
    }
}

становится

for (int i = 0; i < size; i++)
{
    *data = rand() % 100 + 1;
    cout << setw(5) << right << *data++; // change made here
    if ((i + 1) % 5 == 0)
    {
        cout << endl;
    }
}

Обратите внимание только на один data++, и он находится на втором использовании data. Вы должны быть в состоянии понять, почему.

Сброс указателя

Самым простым и очевидным является

int*reset = data; 

тогда вы можете data довольствоваться вашим сердцем, а когда вы хотите сбросить,

data = reset;

Таким образом, приведенные выше петли выглядят как

int*reset = data; 
for (int i = 0; i < size; i++)
{
    *data = rand() % 100 + 1;
    cout << setw(5) << right << *data++; // change made here
    if ((i + 1) % 5 == 0)
    {
        cout << endl;
    }
}
data = reset;

Но ... Вы также можете разделить свою логику на функции и воспользоваться возможностью передачи по значению

void fill(int * data,
          int size)
{
    for (int i = 0; i < size; i++)
    {
        *data = rand() % 100 + 1;
        cout << setw(5) << right << *data++; // change made here
        if ((i + 1) % 5 == 0)
        {
            cout << endl;
        }
    }
}

и связанная с ним часть main теперь выглядит как

data = new int[size];
// Write a loop to fill the "data" array with random numbers from 1 - 100 (inclusive)
// This code must use POINTER NOTATION (no subscripting) to work with the array.
// Reminder: neither of these notations is acceptable here:
// data[n]  or *(data + n)
// Instead this code will use pointer incrementing/decrementing and dereferencing
cout << "This is just the test to see if the pointer is successfully creating the array" << endl;
fill(data, size);
// Reset "data" to point to the beginning of the array

"Подожди минутку!" ты думаешь. "Как в имени Крома int * data передать по значению? Это <объяснительный удаленный> указатель!" Данные, указывающие на , передаются по ссылке, но сам указатель передается по значению. data в заполнении - копия data в main. Все data++ в fill происходит с копией, поэтому data в main все еще указывает туда, где вы ее оставили.

Сброс не требуется и Вы упростили обязанности main, выделив часть из них в их собственную простую и независимо проверяемую функцию. На мой взгляд, все как можно проще, меньше и глупее стоит своего веса в биткойнах.

0 голосов
/ 03 мая 2018

Вы можете мучить себя без всякой причины или бить себя головой о кирпичную стену (не волнуйтесь - мы все были там ... и у вас есть синяки, чтобы доказать это).

Сначала давайте начнем с любого выделенного блока памяти, скажем:

     int *a = new int[NELEM], ...

Что такое a? (указатель - да, но на что?) Это указатель на начальный адрес в блоке памяти размером NELEM * sizeof *a байт. Какой тип указателя это? (int). Сколько байт на int? (обычно 4).

Так почему указатель имеет тип int? (ну, он устанавливает размер шрифта, который управляет тем, как работает арифметика указателей при обращении к блоку памяти через этот указатель) То есть, поскольку тип указателя int, компилятор знает, что a + 1 равен a + 4-bytes, что позволяет вам для ссылки на следующее значение в вашем блоке памяти.

Хорошо, но я выделил память для a, каковы мои обязанности в отношении a? В любом написанном вами коде, который динамически распределяет память, у вас есть 2 обязанностей в отношении любого выделенного блока памяти: (1) всегда сохраняйте указатель на начальный адрес для блока памяти, так , (2) он может быть освобожден , когда он больше не нужен.

Что это значит для меня? Это означает, что если вы не можете просто увеличить a (например, a++) в области, где было объявлено a. Если вы это сделаете, вы потеряли свою ссылку на начальный адрес блока, и этот блок больше не может быть освобожден (это утечка памяти ).

Так что, если я не могу использовать какое-либо индексирование (например, a[i] или *(a + i)) и не могу увеличить свой указатель a - тогда каковы мои варианты? Использовать другой указатель ... , например,

    int *a = new int[NELEM],
        *p = a;
    ...
    std::cout << "array  : ";
    for (int i = 0; i < NELEM; i++, p++) {
        *p = rand() % 100 + 1;
        std::cout << std::setw(5) << *p;
    }
    std::cout << '\n';

Удовлетворены ли вы своими обязанностями в отношении блока памяти, выделенного для a? Конечно, a все еще указывает на начальный адрес блока, поэтому его можно освободить. Все, что вы сделали, это использовали второй указатель p и итерацию, используя p, оставив a без изменений.

Хмм .. Используя второй указатель .. Интересно, смогу ли я перевернуть массив, используя ту же схему Ага. В простейшей форме вы можете сделать что-то вроде:

 void rev (int *a, size_t size)
{
    int *e = a + size - 1;      /* end pointer */

    for (; e > a; a++, e--) {   /* while end > start, swap start, end */
        int tmp = *a;
        *a = *e;
        *e = tmp;
    }
}

Но подождите! Вы сказали, что не можете увеличить a без потери начального адреса для моего выделенного блока - как я могу free сделать это сейчас? (a в main() никогда не меняется, функция rev получает копию a и в пределах rev вы можете увеличивать / уменьшать или делать все, что захотите, до a в пределах границ блок памяти, потому что a в rev имеет свой собственный (и очень отличный) адрес от исходного указателя в main().

(в сторону ...) Вы могли бы объявить третий указатель в пределах rev, например,

    int *s = a,                 /* start pointer */
        *e = a + size - 1;      /* end pointer */

и затем использовал s вместо a в вашей итерации и подкачке, но в этом нет необходимости. Вы можете делать это таким образом, если вам более ясно, с каким указателем вы работаете. Это просто еще 8 байтов (или 4 на x86), поэтому дополнительное хранилище не проблема.

В целом, в коротком примере, вы можете сделать что-то похожее на следующее:

#include <iostream>
#include <iomanip>
#include <cstdlib>

#define NELEM 10

void rev (int *a, size_t size)
{
    int *e = a + size - 1;      /* end pointer */

    for (; e > a; a++, e--) {   /* while end > start, swap start, end */
        int tmp = *a;
        *a = *e;
        *e = tmp;
    }
}

int main (void) {

    int *a = new int[NELEM],
        *p = a;

    srand (20180502);

    std::cout << "array  : ";
    for (int i = 0; i < NELEM; i++, p++) {
        *p = rand() % 100 + 1;
        std::cout << std::setw(5) << *p;
    }
    std::cout << '\n';

    rev (a, NELEM);
    p = a;
    std::cout << "reverse: ";
    for (int i = 0; i < NELEM; i++, p++)
        std::cout << std::setw(5) << *p;
    std::cout << '\n';

    delete[] a;
}

Пример использования / Вывод

$ ./bin/array_reverse
array  :    11    6   78   93   25   71   82   58   97   68
reverse:    68   97   58   82   71   25   93   78    6   11

На все это уходит немного времени. У всех нас синяки на лбу от одной стены. Просто примиритесь с тем фактом, что указатель - это просто переменная, которая содержит адрес другого значения в качестве значения (например, оно указывает на то, где хранится что-то еще).

Понять, как type указателя влияет на арифметику указателя (и индексацию), например, сколько байтов добавляется с помощью p++ или for (i = 0; i < size; i++) p[i], и убедитесь, что вы точно знаете, куда указывает ваш указатель, и все должно начать становиться на свои места.

Если у вас возникнут проблемы с выяснением того, что происходит с указателем, вытащите лист бумаги размером 8,5 x 11 и карандаш № 2 и просто вытяните его - на каждой итерации заполняйте блок, где находится ваш указатель указывает и т.д .. - это действительно помогает. После того, как вы нарисовали достаточно диаграмм, сделали достаточно связанных списков, стеков и т. Д., Вам больше не понадобится бумага, как сейчас (она все равно понадобится - так что держите ее под рукой)


Реверс в main() с функцией

В ответ на ваш комментарий, когда вы смотрите на main(), у вас уже есть дополнительный указатель p, объявленный. Таким образом, вы можете просто использовать его в качестве указателя начала и добавить e из функции rev() в качестве указателя конца. Простая реализация будет:

int main (void) {

    int *a = new int[NELEM],
        *p = a,
        *e = a + NELEM - 1;;

    srand (20180502);

    std::cout << "array  : ";
    for (int i = 0; i < NELEM; i++, p++) {
        *p = rand() % 100 + 1;
        std::cout << std::setw(5) << *p;
    }
    std::cout << '\n';

    p = a;                      /* reset pointer */
    for (; e > p; p++, e--) {   /* reverse array */
        int tmp = *p;
        *p = *e;
        *e = tmp;    
    }
    p = a;                      /* reset pointer -- again */
    std::cout << "reverse: ";
    for (int i = 0; i < NELEM; i++, p++)
        std::cout << std::setw(5) << *p;
    std::cout << '\n';

    delete[] a;
}

(тот же вывод)

Посмотрите вещи и дайте мне знать, если у вас есть дополнительные вопросы.

0 голосов
/ 03 мая 2018

Следующие строки в main неверны.

*data = rand() % 100 + 1;
cout << setw(5) << right << *data;

Они просто устанавливают значение первого элемента массива и печатают тот же элемент.

Используйте data[i] вместо.

data[i] = rand() % 100 + 1;
cout << setw(5) << right << data[i];

Если вы должны использовать обозначение указателя, используйте *(data+i).

*(data+i) = rand() % 100 + 1;
cout << setw(5) << right << *(data+i);

Другой метод, который вы можете использовать, - использовать временную переменную-указатель только для перебора массива.

int* iter = data;
for (int i = 0; i < size; i++. ++iter)
{
    *iter = rand() % 100 + 1;
    cout << setw(5) << right << *iter;

    ...
}

Это гарантирует, что вы не потеряете исходный указатель, что необходимо для возможности освобождения памяти.

PS Могут быть и другие ошибки или нет, но я заметил вышеуказанную проблему после быстрого просмотра вашего кода.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...