Другое > Hard'n'Soft
Програмирование на C++
<< < (5/25) > >>
HoRRoR:
О боже мой, вот вы о чём. Ну это бред, извините, сравнивать вывод в поток и функцию. Почитайте про перегрузку операторов, прежде чем считать использование оператора за функцию. Конечно, можно заставить оператор вызывать функцию, но это уже другой вопрос.
gepar:
HoRRoR,ты меня изначально не понял по этому поводу, тут схожесть как в том случае когда друг увидев мой телефон сказал "у меня такой же, только серый и раскладушка"  :) Я не о том что си это низкоуровневый язык вроде asm'а или что они реализованы похоже как ты наверное подумал изначально.
HoRRoR:
Ты можешь нормальным языком изложить свои мысли? Так, чтобы я однозначно тебя понял?
Ты сравниваешь принципиально разные вещи, вот я и хочу узнать, что же ты в них общего нашёл.
gepar:
HoRRoR,наличие указателей всюду где только можно и вообще непривычная (для меня) конструкция многих функций. Примеры:
scanf ("%d%f",&x,&y)
printf("%s", message)
HoRRoR:
Где связь вышесказанного с:
1. Схожестью функций си и функций ассемблера?
2. Различием между функциями си и функциями си плюс плюс?
Для меня это сейчас как вырванная из контекста фраза.
А указатели только там, где они нужны. Тебе надо вникнуть в принципы работы, чтобы понять, что это такое и зачем они нужны.
gepar:
HoRRoR,я уже тебе написал что схожесть минимальная и она остаётся схожестью и это лично мне оно похоже. Я отлично понимаю что есть разница между asm кодом вида mov es, ax и кодом на си.

--- Цитата: HoRRoR ---2. Различием между функциями си и функциями си плюс плюс?
--- Конец цитаты ---
Нулевое если учитывать что можно использовать всё что осталось от си. И большое если использовать тот же самый вывод в поток вместо print, ловить ввод через опять же таки поток, а не через get. функции.

--- Цитата: HoRRoR ---Тебе надо вникнуть в принципы работы, чтобы понять, что это такое и зачем они нужны.
--- Конец цитаты ---
Я вник до уровня что они нужны для удобства и экономии памяти.
А вообще пустой диалог получается, я так понимаю ты изучил си и знаешь его хорошо и поэтому для тебя он удобен, мне же он не привычен и сравним с asm'ом, который опять же мне не удобен (оба одинаково неудобны и поэтому для меня явл. взаимозаменяемыми понятиями) , а тебе тоже хорошо знаком. Вот потому то тебе и сложно меня понять. Надеюсь хоть в этот раз я более-менее понятно выразился, предлагаю дальше уже не докапываться где же схожесть си и asm'а так как они могут быть похожи лишь внешне и лишь тем кто с ними мало знаком вроде меня  :)

Добавлено позже:
Кстати нашёл удобную кнопочку в Code::Blocks в одной из менюшек для добавления файлов в текущий проект (даже если проекта по сути то и нет), делается всё в 2 клика после того как открыт любой cpp файл, в том числе и любой файл от примера Дейтела.
HoRRoR:
Ладно, закроем эту тему, пока мой мозг не вытек в образовавшиеся трещины.


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

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

--- Цитата: HoRRoR ---с объектами классов через указатели на них
--- Конец цитаты ---
Ну я пробовал возвращать значения в виде указателя на элемент класса, но преимуществ не заметил да и Дейтел написал что это опасно и что это плохой стиль так как пользователь может подсунуть потом что угодно и программа из-за этого будет некорректно работать  :)

Кстати: а может ли функция возвращать сразу несколько значений? Мне с самого начала было интересно.

И ещё я тут Дейтела пример рассматриваю и вот у него некоторые строчки без комментов, прокомментируйте пожалуйста (это конструктор если что):

--- Код: ---Employee::Employee( const char * const first, const char * const last,  //W
   const Date &dateOfBirth, const Date &dateOfHire )                    //T
   : birthDate( dateOfBirth ),                                          //F
     hireDate( dateOfHire )                                             //?

--- Конец кода ---
Конкретно интересует первая строчка, это 2 константных указателя на константные данные, wright? Ну а 3я и 4я как я понимаю это инициализация уже переменных класса.
HoRRoR:

--- Цитата ---Ну они указывают на участок памяти, если присвоить им адрес переменной то будут указывать на ту же память где храниться переменная, так?
--- Конец цитаты ---
Всё верно, но мне кажется, что ты всё же не совсем понимаешь их предназначение.


--- Цитата ---Ну я пробовал возвращать значения в виде указателя на элемент класса, но преимуществ не заметил да и Дейтел написал что это опасно и что это плохой стиль так как пользователь может подсунуть потом что угодно и программа из-за этого будет некорректно работать
--- Конец цитаты ---
Глупости какие-то Дейтел говорит. В кривых руках всё опасно.
Как ещё организовывать передачу объектов класса, как не через указатели? Вариант, конечно, через ссылки, но это не всегда удобно. Создавать копии или временные экземпляры в большинстве случаев не имеет смысла.


--- Цитата ---Кстати: а может ли функция возвращать сразу несколько значений? Мне с самого начала было интересно.
--- Конец цитаты ---
Да, но через return только одно. Ты можешь передавать значения по указателям или ссылкам.

--- Код: ---void foo_ref(int &a1, int &a2)
{
  a1 = 1;
  a2 = 2;
}

// Более опасно, желательно проверять на NULL:
void foo_ptr(int *a1, int *a2)
{
  *a1 = 1;
  *a2 = 2;
}

--- Конец кода ---


--- Цитата ---Конкретно интересует первая строчка, это 2 константных указателя на константные данные, wright? Ну а 3я и 4я как я понимаю это инициализация уже переменных класса.

--- Конец цитаты ---
Всё верно.

--- Код: ---Employee::Employee(
  const char * const first, // Константный указатель на константный char (ни указатель, ни данные по нему менять нельзя)
const char * const last,  // Константный указатель на константный char
   const Date &dateOfBirth, const Date &dateOfHire )                    // Константные ссылки на переменные типа Date
   : birthDate( dateOfBirth ),                                          // birthDate = dateOfBirth
     hireDate( dateOfHire )                                             // hireDate = dateOfHire

--- Конец кода ---

Ссылка - это что-то вроде второго имени для существующей переменной. По сути это как бы автоматический указатель, который нельзя менять - он всегда указывает на переменную, для которой был создан и автоматически разадресовывается. Т.е. работать с ней можно как с переменной, на которую она указывает. Иногда имеет смысл передавать переменные через ссылки (например, когда указатели не совсем удобны) - тогда не создаются копии переменных. Ну, или когда необходимо менять переменную внутри вызываемой функции.
gepar:
HoRRoR, ага, с const понял, спасибо.
Нуждаюсь в хорошей статье по динамическим структурам (Дейтел писал только об обычных, а мне нужно сделать лабораторную по динамическим) может у кого завалялась ссылка, только на англ. не подойдёт информация. У меня ещё по ним пример есть в лабе, но он мне как-то не особо понятен.

--- Код: ---#include <iostream.h>
struct Node{
int d; //информационное поле
Node *next; //указатель на следующий
Node *prev; //указатель на предыдущий
};
/*описание прототипов функций */
Node * first(int d); //инициализация списка (создание первого эл-та)
void add(Node **tail, int d); //добавление в конец
Node * find(Node * const head, int i); //поиск по ключу элемента
bool remove(Node **head, Node **tail, int key); //удаление
Node * insert(Node * const head, Node **tail, int key, int d); //вставка элемента в список
//----
int main()
{
// Формирование первого элемента списка
Node *head = first(l);
// устанавливаем указатель на конец на первый элемент списка
Node *tail = head;
// Добавление в конец списка четырех элементов 2, 3, 4, и 5:
for (int i = 2; i<6; i++) add(&tail, i);
// Вставка элемента 200 после элемента 2:
insert(head, &tail, 2, 200);
// Удаление элемента 5:
if(!remove (&head, &tail, 5)) cout << "не найден";
// вывод списка на экран
Node *pv = head;
while (pv){
cout << pv->d << ' ';
pv = pv->next;
}
return 0;
}
//описание функций
// Формирование первого элемента (инициализация списка)
Node * first(int d)
{
Node *pv = new Node; //выделение памяти под первый элемент
pv->d = d; //заполнение информационной части
pv->next = 0; //формирование указателя на следующий (на конец)
pv->prev = 0; //формирование указателя на предыдущий (на конец)
return pv; //возврат указателя на голову (первый элемент)
}
// Добавление в конец списка
void add(Node **tail, int d)
{
Node *pv = new Node; //выделение памяти под создаваемый элемент
pv->d = d; //заполнение инф. поля
pv->next = 0; //формирование указателя на следующий (конец)
pv->prev = *tail; //формирование указателя на предыдущий
(*tail)->next = pv;
//изменение указателя на следующий предыдущего элемента
*tail = pv; //изменение указателя на хвост
}
// Поиск элемента по ключу
Node * find(Node * const head, int d)
{
Node *pv = head; //берем первый элемент списка
while (pv) //просматриваем список до конца
{
if(pv->d == d)break;
//если нашли элемент с соответствующим ключом, выходим из цикла
pv = pv->next; //переходим к следующему элементу
}
return pv; //возвращаем указатель на найденный элемент, //если искомый элемент не найден – возвращаемое значение pv=0
}
// Удаление элемента
bool remove(Node **head, Node **tail, int key)
{
if(Node *pkey = find(*head, key)){ // 1
if (pkey == *head){ // 2
*head = (*head)->next;
(*head)->prev =0;}
else
if (pkey == *tail){ // 3
*tail = (*tail)->prev;
(*tail)->next = 0;}
else{ // 4
(pkey->prev)->next = pkey->next;
(pkey->next)->prev = pkey->prev;}
6
delete pkey;
return true; // 5
}
return false; // 6
}
// Вставка элемента после заданного
Node * insert(Node * const head, Node **tail, int key, int d)
{
if(Node *pkey = find(head, key)) //если заданный элемент найден
{
Node *pv = new Node; //выделяем память под новый элемент
pv->d = d; //заполняем инф. поле
// 1 - устанавливаем связь нового узла с последующим:
pv->next = pkey->next;
// 2 - устанавливаем связь нового узла с предыдущим:
pv->prev = pkey;
// 3 - устанавливаем связь предыдущего узла с новым:
pkey->next = pv;
// 4 - устанавливаем связь последующего узла
//(не последнего !) с новым:
if( pkey != *tail) (pv->next)->prev = pv;
//иначе обновление указателя на конец списка,
//так как узел вставляется в конец:
else *tail = pv;
return pv; //возвращаем указатель на вновь созданный элемент
}
return 0;
}
--- Конец кода ---

Например мне непонятно что такое void add(Node **tail, int d); и прочие строки где есть по две звёздочки, что это? Не указатель на указатель на элемент же  :)
HoRRoR:
Впервые встречаю понятие "динамические структуры". То ли понятие неверное, то ли перевод кривой. Динамических структур в C++ быть не может в принципе, т.к. структура - это железно заданный порядок данных или нескольк заданных порядков (если присутствуют объединения или т.п.). Есть динамические объекты - это переменные типов этих структур, при том динамически создаваемые, но никак не динамические структуры.

В примере у тебя - двусвязный список, когда каждый элемент списка указывает на предыдущий и следующий элементы. Две звёздочки - это как раз указатель на указатель, потому как указатель - тоже тип данных. Три звёздочки - указатель на указатель на указатель. Всё предельно просто, если не заморачиваться. Для лучшей читаемости кода иногда создают новый тип через typedef, тогда не придётся писать много звёздочек.

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

--- Код: ---template <typename t, int base_hash>
ListItem<t, base_hash>* ListItem<t, base_hash>::AddToTail(t data)
{
    count++;
    if(next) return next->AddToTail(data);
    ListItem* item = new ListItem(data);
    next = item;
    item->prev = this;
    return item;
}

--- Конец кода ---
Тут в процессе участвует только указатель (this), но не указатель на указатель (зачем?). Здесь по цепочки просматривается весь список и находится последний элемент, к которому и прицепляется добавляемый элемент. Можно было избежать рекурсивного вызова функций, сделав цикл.
gepar:

--- Цитата: HoRRoR ---Я не вникал в код, но мне не совсем понятно, зачем в коде нужен указатель на указатель, потому как обычно вполне достаточно просто указателя на родительский (основной) элемент.
--- Конец цитаты ---
Значит всё же это именно указатель на указатель в примере, понятно.
gepar:
HoRRoR, а что делает в твоём коде операция стрелка?
HoRRoR:
Разадресация.
ptr->field === (*ptr).field.
gepar:
А как совершить вставку одной структуры в другую, но не в конец, а где-то по середине. Что-то не могу сообразить как делать потом сдвиг элементов в первой структуре.
HoRRoR:
Ничего не понял. Какой ещё сдвиг элементов?
Структура в структуре - дело элементарное. Ничем не отличается от переменной в структуре.


--- Код: ---struct st1
{
    int a;
    int b;
};

struct st2
{
    float c;
    st1  s;
    float d;
};

--- Конец кода ---

или


--- Код: ---struct st2
{
    float c;
    struct
    {
        int a;
        int b;
    } s;
    float d;
};

--- Конец кода ---
gepar:
HoRRoR, задание вообще полностью такое:
написать программу с функциями которая:
1)Вставляет в список P за последним вхождением элемента E все элементы списка P1, если E входит в P;
2)Удаляет из непустых слов списка P их первые буквы;
3)Добавляет в конец списка P инвертированный список P.

Тоесть в 1 я должен найти последнее вхождение этого E, пихнуть как-то туда P1, а то что после в списке P было изначально сдивинуть в конец чтобы оно было после всех элементов P1 как я понимаю. Иными словами:
Список P: 1 2 3 4 5 6 7 8 9;
Список P1: a b c d e f g;
Элемент E например 3, в итоге должно получиться что список P: 1 2 3 a b c d e f g 4 5 6 7 8 9.
Как подвинуть эти 4 5 6 7 8 9, я что-то не могу никак представить.
HoRRoR:
При чём тут структуры вообще?
А сдвиг - это элементарно (если речь идёт о массиве) - копируешь в цикле сдвигаемые элементы, начиная с последнего сдвигаемого, на позицию, увеличенную на значение сдвига.
Но у тебя двусвязный список, насколько я понимаю, поэтому тебе сдвигать ничего не надо, надо лишь переставить указатели. "3" у тебя должен начать указывать на "a", "а" обратно на "3", "g" на "4", "4" обратно на "g".

Схематично:
<- - связь назад
-> - связь вперёд
<--> - двусторонняя взаимная связь
Т.е., у тебя было два списка:

--- Код: ---(пусто) <- 1 <--> 2 <--> 3 <--> 4 <--> 5 <--> 6 <--> 7 <--> 8 <--> 9 -> (пусто)
(пусто) <- a <--> b <--> c <--> d <--> e <--> f <--> g -> (пусто)

--- Конец кода ---
Тебе надо сделать примерно так:

--- Код: ---(пусто) <- 1 <--> 2 <--> 3<-                                                  -> 4 <--> 5 <--> 6 <--> 7 <--> 8 <--> 9 -> (пусто)
                            \-> a <--> b <--> c <--> d <--> e <--> f <--> g -/

--- Конец кода ---
Т.е., по шагам:
1. Связь вперёд у "3" заменить на "а".
2. Связь назад у "а" заменить "3".
3. Связь вперёд у "g" заменить на "4".
4. Связь назад у "4" заменить на "g".
gepar:
HoRRoR,спасибо, задача сама прояснилась теперь, я изначально воспринимал эти  новые "динамические" элементы стуктуры немного иначе (больше как элементы массива нежели как куча указателей  :) ).
А можешь пожалуйста скопировать свой код лабораторной по двусвязным спискам полностью (если он у тебя по структурам и затрагивает создание/удаление элементов структуры, а не какие-то другие операции), подозреваю что он у тебя будет понятнее мне примера преподши что у меня сейчас есть. Жаль у Дейтела нет по структурам с динам. данными отдельного раздела, мне нравиться его стиль изложения и его код понимать мне не сложно.
HoRRoR:
У меня лабораторная не по спискам была, а с их использованием. Там используются шаблоны и прочие ужасы (по крайней мере, тебе так может показаться), да и использование списка ограничено парой операций. Лучше тебе так простенький пример состряпаю:

--- Код: ---#include <iostream>

struct ListItem
{
    std::string data;       // Хранимые в элементе списка данные
    ListItem *prev, *next;  // Указатели на предыдущий и следующий элементы

    // Конструктор
    ListItem(const std::string data):prev(NULL),next(NULL)
    {
        this->data = data;
    }

    // Возвращает последний элемент списка
    ListItem* getTail()
    {
        ListItem* item = this;
        while(item->next)
            item = item->next;
        return item;
    }

    // Добавляет в конец списка новый элемент
    ListItem* addToTail(const std::string data)
    {
        ListItem *item = new ListItem(data), *last = getTail();
        item->prev = last;
        last->next = item;
        return item;
    }

    // Находим последний элемент с указанным значением
    ListItem* findLastItem(const std::string data)
    {
        ListItem* item = this, *last = NULL;
        while(item->next)
        {
            if(item->data == data)
            {
                last = item;
            }
            item = item->next;
        }
        return last;

    }

    // Вставляем список после последнего элемента с указанным значением
    // Если элемент не найден - возвращаем false
    bool insertAfterLastItem(const std::string data, ListItem* list)
    {
        ListItem* last = findLastItem(data), *next_item, *prev_item, *list_last;
        if(!last) return false;


        next_item = last->next;
        list_last = list->getTail();


        // b1 <--> b2 <--> b3 <--> b4 -> завставляем указывать на a4
        list_last->next = next_item;

        // Если на месте a4 не NULL, то заставляем a4 указывать на b4
        // b1 <--> b2 <--> b3 <--> b4 <--> a4
        if(next_item)
            next_item->prev = list_last;

        // Заставляем b1 (prev) указывать на a3
        // a1 <--> a2 <--> a3 <--> a3 <- b1 <--> b2 <--> b3 <--> b4 <--> a4
        //                              \_____________________________/
        list->prev = last;

        // Заставляем a3 (next) указывать на b1
        // a1 <--> a2 <--> a3 <--> a3 <--> b1 <--> b2 <--> b3 <--> b4 <--> a4
        last->next = list;
        return true;
    }

    // Декструктор - уничтожаем по цепочке все элементы списка
    ~ListItem()
    {
        if(next) delete next;
    }
};

void print_list(ListItem* list)
{
    ListItem* item = list;
    while(item)
    {
        std::cout << item->data;
        item = item->next;
        if(item) std::cout << " <--> ";
    }
    std::cout << std::endl;
};

int main()
{
    ListItem *a = new ListItem("a1");
    a->addToTail("a2");
    a->addToTail("a3");
    a->addToTail("a3");
    a->addToTail("a4");
    std::cout << "List a: ";
    print_list(a);


    ListItem *b = new ListItem("b1");
    b->addToTail("b2");
    b->addToTail("b3");
    b->addToTail("b4");
    std::cout << "List b: ";
    print_list(b);

    a->insertAfterLastItem("a3", b);
    std::cout << "Merged: ";
    print_list(a);

    // Т.к. все элементы теперь в одном списке, уничтожаем этот список разом со всеми элементами
    delete a;

    return 0;
}


--- Конец кода ---
Навигация
Главная страница сообщений
Следующая страница
Предыдущая страница

Перейти к полной версии