构建动态分配的类对象数组

Building a dynamically allocated array of class Objects

本文关键字:对象 数组 动态分配 构建      更新时间:2023-10-16

首先,如果这个问题对你来说非常容易,我想提前道歉,但我只是一个初学者。

这个问题我已经被困了大约一个星期,它变得越来越荒谬,因为它不应该那么难,即使对于像我这样的完全初学者也是如此。

我正在编写一个程序,该程序从文本文件中读取有关收据的大量信息,例如姓名,总和,日期等,然后将其打印到屏幕上。很简单,对吧?好吧,我开始在我的两个类事务和事务列表中使用静态数组,它工作正常,我将文件的内容打印到屏幕上,只是一行接着一行。现在我需要使用动态数组来执行此操作。

文本文件中的每一行都包含一个日期、类型、名称、总和、朋友的数量和那些朋友的名字,这些朋友应该被读取并存储为动态数组 trans 中的 Transaction 类对象。这就是我难以理解的地方,无论我在这个问题上做了多少理论和谷歌搜索。我应该在哪里使用重载的辅助运算符,在哪里使用复制构造函数以及如何正确调用它们?我已经阅读了这些概念,但我仍然无法在我的程序中使用它们。这些问题现在就在我脑海中飞来飞去。

我已经将数组 friends 和 trans 更改为声明为指针,我知道这是正确的。然后我想用"new"为数组分配内存,但在这里我开始不确定我在哪里分配新的,在它们的类的构造器内部还是在需要它们的函数中?我意识到向量是很多问题的答案,但我应该告诉你,我还没有进入向量,所以我试图在没有向量的情况下解决这个问题。我意识到这可能有点倒退,但我应该能够构建动态分配的对象数组并将其打印出来,而无需我认为的向量。我听说它们更实用,但现在我必须在没有向量概念的情况下理解这个任务。我也读过浅拷贝和深拷贝之间的区别,我明白了理论,但我无法以某种方式实现它。(我知道我可能是智障)。这是我到目前为止得到的:

#include <iostream>
#include <string>
#include <fstream>
#include <cstdlib>
#include <iomanip>
using namespace std;
class Transaction
 {
  private:
   string date;
   string type;
   string name;
   double sum;
   int nr_friends;
   string *friends;
  public:
   Transaction();
   ~Transaction();
   Transaction &operator = ( const Transaction &t );
   string get_name();
   int get_no_friends();
   double get_sum();
   bool readOneTrans( istream &is );
   void writeOneTrans( ostream &os );
 };

class TransactionsList
 {
 private:
   Transaction *trans;
   int no_Trans;
 public:
   TransactionsList();
   ~TransactionsList();
   void read( istream & is );
   void print( ostream & os );
   void add( Transaction & t );
 };

int main()
{
    ifstream inFile("test.txt");
    Transaction t;
    TransactionsList tl;
   // t.readOneTrans(inFile);  // reading just one line works fine (when uncommented)
   // t.writeOneTrans(cout);  // printing works too just fine

    //tl.read(inFile); // here I want to read all contents of file
    //tl.print(cout); // and here print out them to the screen

return 0;
}

Transaction::Transaction()
    {
        date = "000000";
        type = "transp";
        name = "default";
        sum = 0.0;
        nr_friends = 0;
        friends = NULL;
    }
Transaction::~Transaction()
{
    delete [] friends;
}
Transaction &Transaction::operator = ( const Transaction &t )
{
        if ( this != &t )
        {
            delete[] friends;
            date = t.date;
            type = t.type;
            name = t.name;
            sum = t.sum;
            nr_friends = t.nr_friends;
            friends = new string[nr_friends];
            for ( int i = 0; i < nr_friends; i++ )
            {
                friends[i] = t.friends[i];
        }
    }

    return *this;
}
string Transaction::get_name()
    {
    return name;
}
double Transaction::get_sum()
    {
    return sum;
}
int Transaction::get_no_friends()
    {
        return nr_friends;
    }
bool Transaction::readOneTrans( istream &is )
    {
        is >> date >> type >> name >> sum >> nr_friends;
        friends = new string[nr_friends];
        for (int i = 0; i < nr_friends; i++)
            {
                is >> friends[i];
            }
        return is;
        return !is.eof();
    }
void Transaction::writeOneTrans( ostream &os )
    {
        os << left << setw(10) << date <<
        setw(10) << type << setw(10) << name
        << setw(10) << sum << setw(10)
        << nr_friends;
        for (int i = 0; i < nr_friends; i++)
            {
                os << left << setw(8) << friends[i];
            }
        os << endl;
    }

TransactionsList::TransactionsList()
{
        no_Trans = 1;
        trans = new Transaction[no_Trans];
}
TransactionsList::~TransactionsList()
{
    delete [] trans;
}
void TransactionsList::read( istream & is )
{
            Transaction t;
            while ( t.readOneTrans( is ))
                {
                    add( t );
                }
}
void TransactionsList::print( ostream & os )
{
    Transaction t;
    for (int i = 0; i < no_Trans; i++)
        {
            t = trans[i];
            t.writeOneTrans( os );
        }
    if (os == cout)
        {
            os << "nNumber of transactions: " << no_Trans << endl;
        }
}
void TransactionsList::add( Transaction & t )
{
   // each time I read a line from the file it is passed in as object t here
   // here I want to add this object t to the dynamic array trans somehow
   // and keep building the array with a new class object every time
    // Probably by overloading assignment operator somehow but how?
        trans[no_Trans] = t;
        no_Trans++;
 // i have no idea what to put here to make it work...
}

如您所见,我想做的是不断构建具有类 Transaction 的不同对象的动态数组 trans,每个实例代表我正在读取的文本文件中的不同行,以便我可以打印出文件中的所有行到屏幕上。输出行应如下所示:

  011216    food      John       300       2        Nathan   Julia

为了现在动态地执行此操作,我意识到我必须复制在方法"add"中传入的对象 t 的内容并将其添加到数组 trans 中,并且以某种方式不会丢失表示先前文本行的早期 t:s 的数据。这对我来说很容易做到,而数组是静态的,因为我刚刚分配了数组中的下一个元素 trans 等于当前对象 t(在 add 函数内)。这是我的 add 函数在静态数组中的样子:

 void TransactionsList::add( Transaction & t )
{
        trans[no_Trans] = t;
        no_Trans++;
}

显然,当您使用动态分配的内存时,这不起作用。我读了一些关于这个的理论,我知道一个人不能在运行时更改数组的大小,所以数组实际上必须被删除,然后分配为一个更大的数组,并使用深层复制复制旧内容,这不仅复制动态数组的内存地址,而且使用旧内容创建一个新数组。

如您所见,我读过很多理论,但并不真正理解它......谁能帮忙?我将非常感激,因为我一周没有学到任何东西,这真的杀死了我。我现在需要进步!

关于容器的一些提示:

  1. 不要使用using namespace std;(为什么?

  2. c++ 中的无符号整数大小通常表示为 <cstddef> std::size_t

  3. 熟悉三法则/三法则
  4. /四法则/五法则。

  5. 通常应用于此类类的一个非常有用的习语是:"资源获取是初始化(RAII)"。

底线:

  1. 在管理资源时,我们通常需要拥有

    • 析构函数
    • 复制构造函数
    • 移动构造函数
    • 复制赋值运算符
    • 移动赋值运算符
  2. 资源获取应仅在构造函数中进行。

    add等函数不应执行单独的资源获取,而应创建适当大小的临时资源并交换/移动内容。

构造动态分配数组的问题与构造对象本身的问题完全不同。

class TransactionList {
    Transaction *trans;
    size_t trans_size;
    size_t no_Trans;
public:
    TransactionList(size_t initial_size)
         : trans(new Transaction[initial_size]),
           trans_size(initial_size),
           no_Trans(0)
    {
    }
    ~TransactionList()
    {
          delete[] trans;
    }
    // ...
};

就是这样。现有的 add() 方法没有什么不同。它仍然以完全相同的方式工作,因为数组实际上只是指向数组中第一个元素的指针,这里仍然如此。

但是您确实需要弄清楚当no_Trans达到实际分配的trans_size时该怎么做。这将是你的家庭作业。

但是,您可能想要做的是将其更改为Transaction *对象的数组,并在将每个Transaction添加到数组时动态分配它。这将需要额外的工作。

(这个答案不需要额外的知识,只需要对代码进行一点点更改)

构造函数中的事情变得很奇怪:

no_Trans = 1;
trans = new Transaction[no_Trans];

人们通常会为未来的元素留下一些空间来添加:

max_Trans = 100;
no_Trans = 0;
trans = new Transaction[max_Trans];

add()

if (no_Trans >= max_Trans) { // no more space?
    // make a new array that is as twice big as the old one
    max_Trans = 2 * max_Trans;
    Transaction new_trans = new Transaction[max_Trans];
    // copy elements to the new array
    for (int i = 0; i < no_Trans; i++)
        new_trans[i] = trans[i]; 
    // delete the old one and start to use the new one
    delete[] trans;
    trans = new_trans;
}
trans[no_Trans] = t;
no_Trans++;

当然max_Trans也可以是 1,让它增长为 1、2、3、4......但这需要对每个添加操作进行new,这是低效的。