Cpp некоторые основные проблемы - PullRequest
1 голос
/ 25 мая 2010

Моя задача была следующей: Создайте класс Person с именем char * и возрастом. Реализуйте конструктор, используя динамическое распределение памяти для переменных, деструктор, функцию init и функцию friend. Затем преобразуйте этот класс в заголовочный файл и файл cpp и внедрите его в другую программу. Итак, вот мой класс Person:

#include <iostream>
using namespace std;

class Person {   
    char* name;
    int age;
public:   

    Person(){
        int size=0;
        cout << "Give length of char*" << endl;
        cin >> size;
        name = new char[size];      
        age = 0;
    }

    Person::~Person(){
        cout << "Destroying resources" << endl;
        delete [] name;
        delete take_age();
    }  

    friend void show(Person &p);

   int* take_age(){
       return &age;
   }

   char* take_name(){
         return name;      
   }

    void init(char* n, int a) {
        name = n;
        age = a;
    }
}; 

void show(Person *p){
    cout << "Name: " << p->take_name() << "," << "age: " << p->take_age() << endl; 
}

int main(void) {
    Person *p = new Person;  
    p->init("Mary", 25);

    show(p);

    system("PAUSE");
    return 0;
}

А теперь с заголовком / частью реализации:
мне нужно ввести конструктор в файлах заголовка / реализации? Если да - как?
- моя функция show () является дружественной функцией. Стоит ли это как-то учитывать?

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

Ответы [ 8 ]

5 голосов
/ 25 мая 2010

Решите многие из ваших проблем, переключившись с char * на std::string. Вы будете рады, что сделали.

Класс std::string отвечает за выделение памяти, освобождение и копирование.

Если это домашнее задание, убедите своего профессора использовать std::string для начинающих и сохраните char * для раздела по указателям. Также напомните своему профессору, что язык С ++ отличается от языка Си. Это одна из тех областей.

4 голосов
/ 25 мая 2010

Вам не нужно * при использовании delete или delete[]. Просто укажите для него переменную-указатель, например.

delete[] name;

Кроме того, ваш take_age участник заявляет, что он вернул int*, но вы действительно возвращаете int самого участника. Вам нужно взять адрес участника, используя &, если вы хотите это сделать. Как прокомментировал @Jerry, это , а не , что вы хотите сделать здесь.

3 голосов
/ 25 мая 2010

В типичном случае управление указателем и блоком динамически выделяемой памяти (например, именем в данном случае) является достаточной ответственностью для одного класса. Таким образом, Томас Мэттьюс прав: в этом случае вам действительно следует использовать строку. Если вы собираетесь справиться с этим самостоятельно, вам все равно следует разделить эту ответственность на собственный класс и встроить объект этого класса в свой объект Person. Во всяком случае, std::string уже пытается сделать слишком много; вам было бы лучше, если бы вы делали меньше, а не больше.

Ваши удаления должны точно соответствовать вашим распределениям. В этом случае единственное распределение:

    name = new char[size];      

поэтому единственное удаление должно быть:

    delete [] name;

Что касается функций friend, вам обычно требуется объявление друга внутри определения класса, но определение функции вне определения класса:

class Person { 
// ...
    friend void show(Person const &);
// ...
};

void show(Person const &p) { 
     // ...
}

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

Это показывает еще один момент: константность. Вы передавали параметр как ссылку на Персона. Если он не собирается изменять объект Person (в этом случае show() кажется неправильным выбором имени), он, вероятно, должен ссылаться на const-объект. Та же общая идея применима и к take_age() - поскольку она только извлекает значение, это должна быть функция const:

int take_age() const { return age; }

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

3 голосов
/ 25 мая 2010

Хотя некоторые на этом сайте, по-видимому, считают это вполне приемлемой, хорошей практикой (см. Может ли конструктор возвращать значение NULL? ), вам действительно следует воздерживаться от выполнения таких операций, как потоковые операции в конструкторе вашего объект. Выполните чтение этого потока снаружи и затем вызовите функцию с результатами.

Это, ИМХО, первый шаг, который вы должны сделать.

2 голосов
/ 25 мая 2010

Я думаю, вам следует изучить следующие фрагменты кода (например, что под ними, что здесь происходит и т. Д.) *

int * take_age(); // You should return plain `int` here, I assume


~Person(){
    cout << "Destroying resources" << endl;
    delete *[] name; // Do you understand why did you put `*` here?
    delete * take_age(); // Do you understand why did you write this? What behaviour you were trying to achieve?

И, собственно, так далее. Я думаю, что только когда вы закончите работу с basic, вы сможете перейти к вопросам разработки заголовков и функциям друзей.

1 голос
/ 25 мая 2010

Прежде всего, спасибо за попытку найти правильный способ реализации вашего класса, особенно после того, как уже пропустили ответ.

Из вашего описания вверху, я думаю, вы, возможно, неправильно поняли часть того, что просили для этого задания. Во-первых, моя интерпретация заключается в том, что установка значения имени и возраста должна осуществляться в функции init (), а не в конструкторе. Как уже упоминалось в нескольких других статьях, ваш конструктор должен просто инициализировать ваш класс в заведомо исправном состоянии. Например,

Person() {
    name = NULL;
    age = 0;
}

Затем в вашей функции инициализации вы можете назначить значения. Рассматривая исходную функцию init (), вероятно, следует упомянуть, что простое присвоение значения указателя (char *) другому указателю (char *) копирует только значение указателя, а не данные, которые он представляет. Таким образом, для присвоения значения имени вам нужно рассчитать размер буфера, который вам нужен, выделить буфер и самостоятельно скопировать данные. Основная функция init (), вероятно, будет выглядеть как

init(const char *n, int a) {
    // Calculate the required name length plus a NULL-terminator
    size_t nameLen = strlen(n) + 1;

    // Free any previous value.
    if (name != NULL) {
        delete[] name;
    }

    // Allocate the name buffer and copy the name into it.
    name = new char[nameLen];
    memcpy(name, n, nameLen);

    // Store the age.
    age = a;
}

Наконец, в вашем деструкторе вы освобождаете любые ресурсы, выделенные вашим классом, в данном случае это буфер имен.

~Person() {
    if (name != NULL) {
        delete[] name;
    }
}

Если у вас есть книга или что-то, связанное с вашим классом, вы можете просмотреть информацию об указателях. Они могут быть немного сложнее, но важно учиться. Я подозреваю, что именно поэтому проблема указана с использованием char * для строк, а не STL-класса.

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

Ключом при предоставлении определений членов класса в отдельном исходном файле является предоставление имени класса для правильного охвата функции (т. Е. Person: :). Таким образом, ваш заголовочный файл может содержать определение класса, например

// Header file (e.g., person.h)

class Person {
private:
    char *name;
    int age;

public:
    Person() { name = NULL; age = 0 };
    ~Person() { if (name != NULL) delete[] name; }

    void init(const char *n, int a);

    // Other method declarations and/or definitions
};

А потом в вашем исходном файле

// Source file (e.g., person.cpp)

void Person::init(const char *n, int a) {
    // ...
}

// Rest of method definitions

Исходные файлы, использующие ваш класс person, должны содержать только заголовочный файл с определением вашего класса.

0 голосов
/ 25 мая 2010

в дополнение к ранее опубликованным ответам, у меня есть два совета для вас:

  • не используйте «друг». некоторые здесь могут не соглашаться со мной, но «друг» больше не должен быть частью C ++, поскольку это противоречит тому, что означает ООП.
  • Называя ваши методы: избегайте называть ваши методы, такие как «take_name» или «take_age». условно, так как это геттеры, подумайте над тем, чтобы назвать их «getName» и «getAge». таким образом вы получите гораздо большее уважение от разработчиков.
0 голосов
/ 25 мая 2010

Я думаю, что ваша проблема с этой строкой: Друг пуст (Персона & р); Для чего это нужно.

мне нужно ввести конструктор в файлы заголовка / реализации? Конструктор может быть в файле .h или .cpp. Это не важно Обычно, если функция короткая, ее можно включить в файл .h. Все, что больше должно идти в .cpp.

Моя функция show () является дружественной функцией. Не уверен, что вы подразумеваете под этим. Функции друга существуют вне определения класса. Ваша функция шоу определена внутри класса, поэтому вам не нужно быть другом.

...