Форум программистов
 

Восстановите пароль или Зарегистрируйтесь на форуме, о проблемах и с заказом рекламы пишите сюда - alarforum@yandex.ru, проверяйте папку спам!

Вернуться   Форум программистов > C/C++ программирование > Общие вопросы C/C++
Регистрация

Восстановить пароль
Повторная активизация e-mail

Купить рекламу на форуме - 42 тыс руб за месяц

Ответ
 
Опции темы Поиск в этой теме
Старый 27.09.2014, 20:56   #1
evsign
Новичок
Джуниор
 
Регистрация: 27.09.2014
Сообщений: 6
По умолчанию Работа с динамической памятью. Помогите пожалуйста разобраться.

Здравствуйте.
Было дано вот такое задание:

Цитата:
В этой задаче вам необходимо написать функцию getline, которая читает строку из стандартного потока ввода cin. Конец строки достигается, если прочитан символ '\n' или поток ввода прочитан полностью. Если прочитан символ '\n', то сохранять его в строку не нужно. Не забудьте, что строка должна оканчиваться нулевым символом. Всю выделенную динамически память, кроме результирующей строки, необходимо освободить - будьте внимательны! Указатель возвращенный из getline будет освобожден с помощью delete[].

Замечания:
  1. выделяйте и освобождайте память в стиле C++,
  2. функция ничего не должна выводить (Sample Output в примере — это возвращаемое значении функции в формате длина возвращаемой строки:возвращаемая строка, длину в строке возвращать не нужно - эта информация для сведения).

На лекции было рассказано как выделять память в с и с++ стиле, и про ссылки с указателями.
Полученное задание меня обездвижило на минут 30)

Почитав коменты к заданию более менее стало понятно что делать.
Получился вот такой нерабочий код №1:
Код:
char *getline() {
    char ch = 0; //переменная для хранения вводимого символа
    char *ch2 = new char[90000]; //выделения памяти условно большого размера для записывания ввода
    int count = 0; //счётчик для определения нужного размера памяти
    while (cin.get(ch) && ch != '\n'){ //начало цикла для посимвольного ввода, который оборвётся когда увидит  \n и ничего не вернёт
        ch2[count] = ch; //переписывание в память вводимых символов
        count++; 
 
 
        if (ch == '\0'){ //когда закончен ввод приступаем ко второй фазе функции
            char *ch3 = new char[count-1]; //выделяем нужное кол-во памяти под символы
            count = 0; //сбрасываем счётчик
            while(ch2){ // начинаем переписывать символы в память нужного размера
                ch2++; //шагаем по указателю массива с символами
                count++; 
                ch3[count] = *ch2; //переписывание символов
            }
            ch3[count+1] = '\0'; //добавляем в конец символ окончания строки 
            delete [] ch2; //очищаем память
            return ch3; //возвращаем массив заполненный символами нужной длинны.
        }
 
 
    }
}
Но этот код в тесте выдавал runtime error.

В попытках всё переосмыслить был рождён другой нерабочий код №2:
Код:
char *getline() {
    char *ch = new char[90000]; //выделяем условно большой кусок памяти
    while (cin.get(*ch)){ //принимаем вводимую строку
        
        int count = 0; //создаём счётчик для подсчёта длинны строки
 
        while(*ch && *ch != '\n'){ //проходимся по строке через адрес и считаем её длинну
            ch++;
            count++;
        }
        ch; //сбрасываем адрес
        char *ch2 = new char[count]; //создаём массив нужного размера
        while(*ch && *ch != '\n'){ //опять проходимся по строке через адрес и сохраняем её значение в новый массив
            *ch2 = *ch;
            ch++;
            ch2++;
        }
        delete [] ch; //очищаем память
        ch2[count] = '\0'; //добавляем символ конца строки
        return ch2; //возвращаем строку в памяти нужного размера
 
 
 
    }
}
Этот код вообще выдаёт вот такую ошибку: *** Error in `./untitled': munmap_chunk(): invalid pointer: 0x00000000008f2011 ***

Предполагаю, что у меня где-то серьёзная поломка мозга и абсолютное непонимание задания...


Ещё не понятен момент, про работу while(cin.get(ch))
Каким образом это работает?
Вот я запускаю программу, программа ожидает ввода. Я ввожу некоторые символы и нажимаю ентер. Это получается, что в ch будет содержаться массив с этими символами и цикл запустится 1 раз? Или будет происходить цикл каждый раз когда я нажимаю на клаве символ? Или произойдёт срабатывание цикла столько раз, сколько я ввёл символов, т.е. в условие цикла отправится массив после нажатия ентер?

Коментарии, которые возможно помогут понять суть задания(хотя мне не особо помогли):

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

Забыл добавить:
Можно использовать только iostream.

Последний раз редактировалось evsign; 27.09.2014 в 21:00.
evsign вне форума Ответить с цитированием
Старый 27.09.2014, 21:13   #2
evsign
Новичок
Джуниор
 
Регистрация: 27.09.2014
Сообщений: 6
По умолчанию

Цитата:
Сообщение от evsign Посмотреть сообщение
Ещё не понятен момент, про работу while(cin.get(ch))
Каким образом это работает?
Вот я запускаю программу, программа ожидает ввода. Я ввожу некоторые символы и нажимаю ентер. Это получается, что в ch будет содержаться массив с этими символами и цикл запустится 1 раз? Или будет происходить цикл каждый раз когда я нажимаю на клаве символ? Или произойдёт срабатывание цикла столько раз, сколько я ввёл символов, т.е. в условие цикла отправится массив после нажатия ентер?
Опытным путём вроде понял как это работает)
После отправки ввода нажатием enter цикл выполняется столько раз, сколько было введено символов. Т.е. по итерации на каждый символ.
Только вот почему тогда цикл не завершается, когда я ввожу 0?

Чувствую, скоро будет рождён неработающий код №3 ))
evsign вне форума Ответить с цитированием
Старый 27.09.2014, 23:12   #3
evsign
Новичок
Джуниор
 
Регистрация: 27.09.2014
Сообщений: 6
По умолчанию

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

Получился пока ещё не работающий вот такой код
Код:
char *getline() {
	int n = 100; //размер буфера
	int count = 0;
	char *ch = new char[n]; //создания буфера
	while (cin.get(ch) && ch != '\n'){ //начало цикла для посимвольного ввода, который оборвётся когда увидит  \n и ничего не вернёт  
		if(++count == n){ //проверяем переполнение буфера
			n += 100; //увеличиваем размер буфера
			char *ch2 = new char[n]; //выделяем новый буфер
			while(ch){ //переписываем старый буфер в новый буфер
				*ch2 = *ch;
				ch++;
				ch2++;
			}
		}
		if (ch == '\0'){ //когда встречается конец строки
			delete [] ch; //удаляем старый буфер
			return ch2; //возвращаем новый буфер
		}  
	}  
}
Только вот неясен момент с созданием нового буфера, т.к. если к примеру вводимая строка превысит 200 байт, то код станет неработоспособным.
И правильно ли я переписываю старый буфер в новый?
evsign вне форума Ответить с цитированием
Старый 29.09.2014, 00:28   #4
evsign
Новичок
Джуниор
 
Регистрация: 27.09.2014
Сообщений: 6
По умолчанию

Уже не актуально, во всём разобрался)
evsign вне форума Ответить с цитированием
Старый 29.09.2014, 11:34   #5
rrrFer
Санитар
Старожил
 
Аватар для rrrFer
 
Регистрация: 04.10.2008
Сообщений: 2,577
По умолчанию

Цитата:
На другом форуме мне подсказали, что лучше не делать сразу большой буфер, а создавать новый по мере заполнения начального.
Не всегда это правильно.

Цитата:
Только вот неясен момент с созданием нового буфера, т.к. если к примеру вводимая строка превысит 200 байт, то код станет неработоспособным.
Почему?
rrrFer вне форума Ответить с цитированием
Старый 29.09.2014, 17:32   #6
evsign
Новичок
Джуниор
 
Регистрация: 27.09.2014
Сообщений: 6
По умолчанию

Цитата:
Сообщение от rrrFer Посмотреть сообщение
Не всегда это правильно.
Почему не всегда это правильно?) Так же вроде более бережно расходываться память будет. Да и размер максимальной строки, которая может быть подана на вход я не знаю)

Цитата:
Сообщение от rrrFer Посмотреть сообщение
Почему?
Эт я с начала не оч понял как работают указатели и вообще запутался в этой теме) Уже разобрался)

Получился вот такой, наконец-то рабочий код:
Код:
#include <iostream>
#include <cstring> 

using namespace std;


char *getline() {
	const size_t INITIAL_SIZE = 100; //размер буфера
	const size_t ALLOCATION_STEP = 10; //размер шага для увеличения буфера

	size_t current_size = INITIAL_SIZE; //текущий размер буфера
	char *buffer = new char[current_size]; // создание буфера

	size_t n = 0; //счётчик для контроля переполнения
	char c = 0; //переменная для считывания символов

	while(cin.get(c) && c != '\n'){ //запуск цикла считывания символов
		if (n + 1 > current_size){ //проверка переполнения буфера
			current_size += ALLOCATION_STEP; //увеличеваем размер буфера
			char *new_buffer = new char[current_size]; //создаём новый буфер увеличенного размера
			memcpy(new_buffer, buffer, n); //копируем в новый буфер данные из старого буфера
			delete [] buffer; //удаляем память по адресу buffer
			buffer = new_buffer; //назначаем переменной buffer адрес нового увеличенного адреса памяти
		}
		buffer[n++] = c; //заносим все символы в буфер
	}

	char *final_buffer = new char[n+1]; //выделяем память окончательного размера
	memcpy(final_buffer, buffer, n); //копируем в неё данные
	final_buffer[n] = '\0'; //вставляем в конце символ окончания строки
	delete [] buffer; //удаляем старый буфер
	return final_buffer; //возвращаем финальный
	
}
evsign вне форума Ответить с цитированием
Старый 29.09.2014, 17:48   #7
rrrFer
Санитар
Старожил
 
Аватар для rrrFer
 
Регистрация: 04.10.2008
Сообщений: 2,577
По умолчанию

Цитата:
Почему не всегда это правильно?) Так же вроде более бережно расходываться память будет. Да и размер максимальной строки, которая может быть подана на вход я не знаю)
Потому что я пишу НЕ ВСЕГДА, а ты пишешь по конкретный случай, когда ты не знаешь что-то там.

Обычно (да почти всегда) на длину строки есть ограничения. Ну например длина сообщения в протоколе ICQ имеет предел в районе 5000 символов. Если бы ты писал консольный клиент аськи - то разумно было бы сделать размер буфера в 5000 символов и считывать нужное количество символов через getline. Если внезапно юзер ввел больше 5000 символов ты, соблюдая протокол, должен все равно разбить его сообщение на 2 (или более) - поэтому юзаешь getline в цикле.

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

Ну и ваще, по заданию тебе сказали getline использовать, значит так и надо делать, а не велосипеды строить.
rrrFer вне форума Ответить с цитированием
Старый 29.09.2014, 17:51   #8
evsign
Новичок
Джуниор
 
Регистрация: 27.09.2014
Сообщений: 6
По умолчанию

Цитата:
Сообщение от rrrFer Посмотреть сообщение
Потому что я пишу НЕ ВСЕГДА, а ты пишешь по конкретный случай, когда ты не знаешь что-то там.

Обычно (да почти всегда) на длину строки есть ограничения. Ну например длина сообщения в протоколе ICQ имеет предел в районе 5000 символов. Если бы ты писал консольный клиент аськи - то разумно было бы сделать размер буфера в 5000 символов и считывать нужное количество символов через getline. Если внезапно юзер ввел больше 5000 символов ты, соблюдая протокол, должен все равно разбить его сообщение на 2 (или более) - поэтому юзаешь getline в цикле.


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

Ну и ваще, по заданию тебе сказали getline использовать, значит так и надо делать, а не велосипеды строить.
Понял) Спасибо.
А по заданию как раз и надо было сделать такой велосипед в образовательных целях) По идее вообще memcpy нельзя было даже юзать.
evsign вне форума Ответить с цитированием
Старый 29.09.2014, 18:04   #9
rrrFer
Санитар
Старожил
 
Аватар для rrrFer
 
Регистрация: 04.10.2008
Сообщений: 2,577
По умолчанию

Цитата:
Понял) Спасибо.
А по заданию как раз и надо было сделать такой велосипед в образовательных целях) По идее вообще memcpy нельзя было даже юзать.
Точно, я по диагонали читал задание. Все правильно у вас )
rrrFer вне форума Ответить с цитированием
Ответ


Купить рекламу на форуме - 42 тыс руб за месяц



Похожие темы
Тема Автор Раздел Ответов Последнее сообщение
Паскаль. Работа с динамической памятью. McFrey Помощь студентам 1 06.04.2012 16:56
Работа с динамической памятью через указатели. Загадочная ошибка. Hqw33 Общие вопросы C/C++ 2 28.03.2012 18:42
Работа с ListBox и Edit и динамической памятью Serebah Помощь студентам 5 19.11.2010 13:41
Помогите пожалуйста с динамической памятью и указателями Riddick2013 Помощь студентам 1 19.12.2007 14:41