Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате
Форум программистов > C/C++: Для новичков > Проход по массиву объектов


Автор: aspirin2003 14.12.2009, 17:02
Здравствуйте!
Есть некоторый класс:
Код

class Videotape {
private:
    int nomer;
    char *nazvanie;
    char *regisser;
    char *kinokompaniya;
    int year;
    char *producer;
    char *annotation;
public:
    Videotape();
    ~Videotape();
    const int getNomer();
    const char * getNazvanie();
    const char * getRegisser();
    const char * getKinokompaniya();
    const int getYear();
    const char * getProducer();
    const char * getAnnotation();
    void parse(char *str);
};

И есть массив из объектов данного класса:
Код

int n = 7;
Videotape *tapes = new Videotape[n];
// далее массив заполняется данными

Как мне пройти по этому массиву, используя указатель на его первый элемент?
Т.е. сделать так, как работает, например, с массивом чисел типа double:
Код

    double *gfg = new double[3];
    gfg[0] = 1;
    gfg[1] = 2;
    gfg[2] = 3;
    gfg++;
    // Здесь gfg будет указывать на элемент, равный двум

Я пытаюсь делать так...
Код

tapes += sizeof(tapes[0]); // sizeof(tapes[0]) == 28

но тогда tapes указывает на какую-то чушь, а не на второй элемент

Автор: azesmcar 14.12.2009, 17:07
Цитата(aspirin2003 @  14.12.2009,  17:02 Найти цитируемый пост)
но тогда tapes указывает на какую-то чушь, а не на второй элемент 

также как и в случае с double. Почитай про арифметические операции с указателями.
Код

(tapes++)->parse("");


Добавлено через 1 минуту и 49 секунд
читай
http://www.viva64.com/terminology/Address_arithmetic_rus.html
Цитата

Согласно стандартам языка Си и Си++, при арифметике с указателями, результирующий адрес должен оставаться строго на границе единичного объекта массива (или следовать сразу за ним). Сложение или вычитание указателя сдвигает его на величину, кратную размеру того типа данных, на который он указывает. Пример. Пусть есть указатель на массив 4-байтных целых. Инкремент этого указателя приведет к увеличению его значения на 4 (размер элемента). Такой эффект часто используется для увеличения указателя для того, чтобы он указывал на следующий элемент в смежном массиве целых чисел.

Автор: aspirin2003 14.12.2009, 17:31
Пока не понял, почему работает такой вариант:
Код

int mynum = (tapes++)->getNomer(); 

и не работает такой:
Код

tapes++;
int mynum = tapes->getNomer(); 


Автор: djamshud 14.12.2009, 17:46
Про арифметику прочитали? Теперь читайте про постфиксный и префиксный инкремент.smile

Проблема в том, что types однажды вылазит из рамок массива или "tapes->getNomer()" обращается по левому адресу.

Автор: aspirin2003 14.12.2009, 19:54
Цитата(djamshud @ 14.12.2009,  17:46)
Про арифметику прочитали? Теперь читайте про постфиксный и префиксный инкремент.smile

Проблема в том, что types однажды вылазит из рамок массива или "tapes->getNomer()" обращается по левому адресу.

Про инкремент я в курсе, просто не увидел, что (tapes++)->getNomer() возвращает значение из первого элемента, а не из второго
Вопрос в том почему это не работает:
Код

tapes++;
int mynum = tapes->getNomer();

Я понимаю, что что-то где-то вылазит за рамки массива, но как сделать правильно тогда?

Автор: djamshud 14.12.2009, 19:58
Videotape *tmp=tapes++;
tmp->...

И вообще вы пытаетесь изменить изначально правильный вариант.

Автор: aspirin2003 15.12.2009, 09:39
Цитата(djamshud @ 14.12.2009,  19:58)
Videotape *tmp=tapes++;
tmp->...

И вообще вы пытаетесь изменить изначально правильный вариант.

Вы сами с инкрементом напутали,
Код

Videotape *tmp=tapes++;

в tmp будет указатель на первый элемент, а нужен на второй.
Вопрос по прежнему не решен...

Автор: azesmcar 15.12.2009, 09:44
Цитата(aspirin2003 @  15.12.2009,  09:39 Найти цитируемый пост)
Вопрос по прежнему не решен...

какой вопрос?

Цитата(aspirin2003 @  14.12.2009,  17:02 Найти цитируемый пост)
Как мне пройти по этому массиву, используя указатель на его первый элемент?

Цитата(azesmcar @  14.12.2009,  17:07 Найти цитируемый пост)
также как и в случае с double



Цитата(aspirin2003 @  14.12.2009,  17:31 Найти цитируемый пост)
Пока не понял, почему работает такой вариант:
int mynum = (tapes++)->getNomer(); 

и не работает такой:
tapes++;
int mynum = tapes->getNomer(); 


потому что это разные вещи. Почему
Код

int i = 0;
std::cout << i++ << std::endl;

выведет 0 а
Код

int i = 0;
i++;
std::cout << i << std::endl;

единицу? ответ был дан выше

Цитата(djamshud @  14.12.2009,  17:46 Найти цитируемый пост)
читайте про постфиксный и префиксный инкремент


Автор: aspirin2003 15.12.2009, 10:40
Зачем Вы мне про инкремент рассказываете? Я в курсе что это такое и как работает i++ и ++i
Вопрос - почему не работает
Код

tapes++;
int mynum = tapes->getNomer();

По идее после выполнения tapes++   tapes должен указывать на второй элемент, а указывает на какую-то фигню

Автор: djamshud 15.12.2009, 10:41
>Вы сами с инкрементом напутали,

Нет, все правильно. Приведенный мной пример идентичен (someptr++)->somefnc();

Добавлено @ 10:42
>По идее после выполнения tapes++   tapes должен указывать на второй элемент, а указывает на какую-то фигню 

Он и указывает на второй элемент. А там может не быть валидного объекта.

Автор: azesmcar 15.12.2009, 10:44
aspirin2003

Весь код покажи

Автор: aspirin2003 15.12.2009, 11:15
Не знаю, чем это поможет...
main.h
Код

#include <stdio.h>
#include <stdlib.h>
#include <conio.h>
#include <iostream>
#include <math.h>

class Videotape {
private:
    int nomer;
    char *nazvanie;
    char *regisser;
    char *kinokompaniya;
    int year;
    char *producer;
    char *annotation;
public:
    Videotape();
    ~Videotape();
    const int getNomer();
    const char * getNazvanie();
    const char * getRegisser();
    const char * getKinokompaniya();
    const int getYear();
    const char * getProducer();
    const char * getAnnotation();
    void parse(char *str);
};

main.cpp
Код

#include <stdio.h>
#include <stdlib.h>
#include <conio.h>
#include <iostream>
#include <fstream>
#include <math.h>
#include "main.h"

using namespace std;

int main(int argc, char *argv[])
{
    // По заданию число сортируемых элементов, т.е. строк в файле, заранее известно.
    // Будем хранить это значение в переменной n
    int n = 7;
    // Создаем новую переменную, которая будет хранить текущую прочитанную из файла строку
    // и выделяем под нее 1024 байта
    char *str = new char[1024];
    // Создаем указатель на массив объектов типа Videotape, и выделяем память под n элементов
    Videotape *tapes = new Videotape[n];
    // Создаем новый поток, который открывает файл на чтение
    ifstream file("data.txt", ios::in);
    // Если файла нет, или его не удалось открыть, выводим ошибку
    if (!file)
        cout << "Can't open file!\n";
    // Счетчик цикла
    int i = 0;
    // Цикл "пока в потоке есть данные"
    while(file.good())
    {
        // Читаем из потока одну строку и записываем ее в str.
        // Если в строке больше чем 1024 символа, она обрезается до 1024
        file.getline(str, 1024);
        tapes[i].parse(str);
    }
    //int y = sizeof(tapes[0]);
    //tapes += sizeof(tapes[0]);
    tapes++;
    int mynum = tmp->getNomer(); 

    //MergeSort(tapes, n);
    // Закрываем поток и освобождаем выделенные ресурсы
    file.close();

    getch();
    // Освобождаем память, выделенную под массив из n объектов
    delete [] tapes;
    return 0;
}

Videotape::Videotape()
{
    // Выделяем память под поля класса
    nazvanie = new char[255];
    regisser = new char[50];
    kinokompaniya = new char[50];
    producer = new char[50];
    annotation = new char[255];
}

Videotape::~Videotape()
{
    // Освобождаем память
    delete nazvanie;
    delete regisser;
    delete kinokompaniya;
    delete producer;
    delete annotation;
}

void Videotape::parse(char *str)
{
    // Разбиваем переданную сюда строку по разделителю
    // и записываем полученные подстроки в соответствующие поля класса
    nomer = atoi(strtok(str, "::"));
    nazvanie = strtok(NULL, "::");
    regisser = strtok(NULL, "::");
    kinokompaniya = strtok(NULL, "::");
    year = atoi(strtok(NULL, "::"));
    producer = strtok(NULL, "::");
    annotation = strtok(NULL, "::");
}

const int Videotape::getNomer()
{
    return this->nomer;
}


Добавлено через 2 минуты и 56 секунд
Цитата(djamshud @ 15.12.2009,  10:41)
>Вы сами с инкрементом напутали,

Нет, все правильно. Приведенный мной пример идентичен (someptr++)->somefnc();


Videotape *tmp=tapes++;
tmp->...

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

Добавлено через 4 минуты и 38 секунд
Естесственно не 
Код

tapes++;
    int mynum = tmp->getNomer();

а 
Код

tapes++;
    int mynum = tapes->getNomer();

Автор: azesmcar 15.12.2009, 11:34
Цитата(aspirin2003 @  15.12.2009,  11:15 Найти цитируемый пост)
Не знаю, чем это поможет...

чем то да поможет.

вот ошибка
Код

    int i = 0;
    // Цикл "пока в потоке есть данные"
    while(file.good())
    {
        // Читаем из потока одну строку и записываем ее в str.
        // Если в строке больше чем 1024 символа, она обрезается до 1024
        file.getline(str, 1024);
        tapes[i].parse(str); // <----------- ТУТ
    }

ты все время заливаешь информацию в первый объект, ну а используешь второй.

Автор: xvr 15.12.2009, 11:34
В коде КУЧА ошибок (но не с tapes++)
Код


int main(int argc, char *argv[])
{

    char *str = new char[1024]; <<< *2

    // Счетчик цикла
    int i = 0;
    // Цикл "пока в потоке есть данные"
    while(file.good())
    {
        // Читаем из потока одну строку и записываем ее в str.
        // Если в строке больше чем 1024 символа, она обрезается до 1024
        file.getline(str, 1024);
        tapes[i].parse(str);
    }
!!! Баг №1 - i в цикле не инкрементируется - все данные будут сложенны в tapes[0]

    tapes++; // <<< *1

    // Освобождаем память, выделенную под массив из n объектов
    delete [] tapes;
!!! Баг №2 - Указатель tapes передвинут на 2й элемент массива, который вернул new. Делать к нему delete нельзя (см *1)
    return 0;
}

Videotape::Videotape()
{
    // Выделяем память под поля класса
    nazvanie = new char[255];
    regisser = new char[50];
    kinokompaniya = new char[50];
    producer = new char[50];
    annotation = new char[255];
}
!!! Баг №3 - Поле nomer не проинициализированно, а вы его пытаетесь печатать (после tapes++)

void Videotape::parse(char *str)
{
    // Разбиваем переданную сюда строку по разделителю
    // и записываем полученные подстроки в соответствующие поля класса
    nomer = atoi(strtok(str, "::"));
    nazvanie = strtok(NULL, "::");
    regisser = strtok(NULL, "::");
    kinokompaniya = strtok(NULL, "::");
    year = atoi(strtok(NULL, "::"));
    producer = strtok(NULL, "::");
    annotation = strtok(NULL, "::");
}
!!! Баг № 4 и 5 - Все поля, выделенные через new char[] переписанны значениями из strtok, которые в свою очередь указывают на один и тот же буфер, выделенные с помощью new char [] в main (*2).
Итого - memory leak + все экземпляры Videotape ссылаются на одну и ту же строку (которую многократно переписали в main)

Videotape::~Videotape()
{
    // Освобождаем память
    delete nazvanie;
    delete regisser;
    delete kinokompaniya;
    delete producer;
    delete annotation;
}
!!! Баг №6 (следствие бага №5) - делать delete для этих указателей нельзя - они все ссылаются на внутренности *2 из main


Автор: aspirin2003 15.12.2009, 11:37
Цитата(azesmcar @ 15.12.2009,  11:34)
Цитата(aspirin2003 @  15.12.2009,  11:15 Найти цитируемый пост)
Не знаю, чем это поможет...

чем то да поможет.

вот ошибка
Код

    int i = 0;
    // Цикл "пока в потоке есть данные"
    while(file.good())
    {
        // Читаем из потока одну строку и записываем ее в str.
        // Если в строке больше чем 1024 символа, она обрезается до 1024
        file.getline(str, 1024);
        tapes[i].parse(str); // <----------- ТУТ
    }

ты все время заливаешь информацию в первый объект, ну а используешь второй.

Точно блин, спасибо smile

Автор: aspirin2003 15.12.2009, 12:11
Цитата(xvr @ 15.12.2009,  11:34)
В коде КУЧА ошибок (но не с tapes++)
Код


int main(int argc, char *argv[])
{

    char *str = new char[1024]; <<< *2

    // Счетчик цикла
    int i = 0;
    // Цикл "пока в потоке есть данные"
    while(file.good())
    {
        // Читаем из потока одну строку и записываем ее в str.
        // Если в строке больше чем 1024 символа, она обрезается до 1024
        file.getline(str, 1024);
        tapes[i].parse(str);
    }
!!! Баг №1 - i в цикле не инкрементируется - все данные будут сложенны в tapes[0]

    tapes++; // <<< *1

    // Освобождаем память, выделенную под массив из n объектов
    delete [] tapes;
!!! Баг №2 - Указатель tapes передвинут на 2й элемент массива, который вернул new. Делать к нему delete нельзя (см *1)
    return 0;
}

Videotape::Videotape()
{
    // Выделяем память под поля класса
    nazvanie = new char[255];
    regisser = new char[50];
    kinokompaniya = new char[50];
    producer = new char[50];
    annotation = new char[255];
}
!!! Баг №3 - Поле nomer не проинициализированно, а вы его пытаетесь печатать (после tapes++)

void Videotape::parse(char *str)
{
    // Разбиваем переданную сюда строку по разделителю
    // и записываем полученные подстроки в соответствующие поля класса
    nomer = atoi(strtok(str, "::"));
    nazvanie = strtok(NULL, "::");
    regisser = strtok(NULL, "::");
    kinokompaniya = strtok(NULL, "::");
    year = atoi(strtok(NULL, "::"));
    producer = strtok(NULL, "::");
    annotation = strtok(NULL, "::");
}
!!! Баг № 4 и 5 - Все поля, выделенные через new char[] переписанны значениями из strtok, которые в свою очередь указывают на один и тот же буфер, выделенные с помощью new char [] в main (*2).
Итого - memory leak + все экземпляры Videotape ссылаются на одну и ту же строку (которую многократно переписали в main)

Videotape::~Videotape()
{
    // Освобождаем память
    delete nazvanie;
    delete regisser;
    delete kinokompaniya;
    delete producer;
    delete annotation;
}
!!! Баг №6 (следствие бага №5) - делать delete для этих указателей нельзя - они все ссылаются на внутренности *2 из main

Как тогда сделать правильно, подскажите, пожалуйста. Что еще нужно тут изменить?
Код

#include <stdio.h>
#include <stdlib.h>
#include <conio.h>
#include <iostream>
#include <fstream>
#include <math.h>
#include "main.h"

using namespace std;

int main(int argc, char *argv[])
{
    // По заданию число сортируемых элементов, т.е. строк в файле, заранее известно.
    // Будем хранить это значение в переменной n
    int n = 7;
    // Создаем новую переменную, которая будет хранить текущую прочитанную из файла строку
    // и выделяем под нее 1024 байта
    char *str = new char[1024];
    // Создаем указатель на массив объектов типа Videotape, и выделяем память под n элементов
    Videotape *tapes = new Videotape[n];
    // Создаем новый поток, который открывает файл на чтение
    ifstream file("data.txt", ios::in);
    // Если файла нет, или его не удалось открыть, выводим ошибку
    if (!file)
        cout << "Can't open file!\n";
    // Счетчик цикла
    int i = 0;
    // Цикл "пока в потоке есть данные"
    while(file.good())
    {
        // Читаем из потока одну строку и записываем ее в str.
        // Если в строке больше чем 1024 символа, она обрезается до 1024
        file.getline(str, 1024);
        tapes[i].parse(str);
        i++;
    }
    //int y = sizeof(tapes[0]);
    //tapes += sizeof(tapes[0]);
    //tapes++;
    //int mynum = tapes[0].getNomer(); 

    //MergeSort(tapes, n);
    // Закрываем поток и освобождаем выделенные ресурсы
    file.close();

    getch();
    // Освобождаем память, выделенную под массив из n объектов
    delete [] tapes;
    return 0;
}

Videotape::Videotape()
{
    nomer = 0;
    year = 0;
    // Выделяем память под поля класса
    nazvanie = NULL;//new char[255];
    regisser = NULL;//new char[50];
    kinokompaniya = NULL;//new char[50];
    producer = NULL;//new char[50];
    annotation = NULL;//new char[255];
}

Videotape::~Videotape()
{
    // Освобождаем память
    delete nazvanie;
    delete regisser;
    delete kinokompaniya;
    delete producer;
    delete annotation;
}

void Videotape::parse(char *str)
{
    // Разбиваем переданную сюда строку по разделителю
    // и записываем полученные подстроки в соответствующие поля класса
    nomer = atoi(strtok(str, "::"));
    nazvanie = strtok(NULL, "::");
    regisser = strtok(NULL, "::");
    kinokompaniya = strtok(NULL, "::");
    year = atoi(strtok(NULL, "::"));
    producer = strtok(NULL, "::");
    annotation = strtok(NULL, "::");
}

const int Videotape::getNomer()
{
    return this->nomer;
}

Автор: xvr 15.12.2009, 12:32
Цитата

Что еще нужно тут изменить?

Все ??? = strtok(NULL, "::"); поменять на strcpy(???,strtok(NULL, "::")); (а еще лучше на strncpy, что бы переполнения строк не произошло)

В Videotape::Videotape() для этих полей оставить new char[]

А еще лучше воспользоваться std::string

Автор: aspirin2003 15.12.2009, 15:38
Еще один вопрос почему если после этого куска кода
Код

  Videotape *tapes1 = new Videotape[n];
  for(int i=0; i<n; i++)
    tapes1[i]=tapes[i];

написать delete[n] tapes;  то в tapes1 тоже получается какая-то фигня? Вроде же копируются значения а не указатели

Автор: mes 15.12.2009, 16:29
Цитата(aspirin2003 @  15.12.2009,  14:38 Найти цитируемый пост)
 то в tapes1 тоже получается какая-то фигня? Вроде же копируются значения а не указатели 

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


Автор: bsa 16.12.2009, 01:10
aspirin2003, забудь о new/delete, используй std::vector, std::string и прочее из STL. Чтобы этим пользоваться нужно меньше книжек читать.  smile 

Автор: aspirin2003 16.12.2009, 14:42
Цитата(mes @ 15.12.2009,  16:29)
а Вы определили (правильный) конструктор копирования ? Т.к как если среди дата членов есть указатели, то по умолчанию они (указатели, а не то, на что они указывают) просто копируются,
в результате получается что разные объекты ссылаются на один участок памяти, и при удалении одного из объектов,  указатели второго считаются невалидными.

Именно так, спасибо!

Добавлено через 44 секунды
Цитата(bsa @ 16.12.2009,  01:10)
aspirin2003, забудь о new/delete, используй std::vector, std::string и прочее из STL. Чтобы этим пользоваться нужно меньше книжек читать.  smile

С STL все конечно гораздо проще но задание было сделать по возможности без STL.

Добавлено через 1 минуту и 51 секунду
Книжки читать полезнее smile

Powered by Invision Power Board (http://www.invisionboard.com)
© Invision Power Services (http://www.invisionpower.com)