动态分配内存给结构体

15

我正在上一门C++课程,需要为一个结构体动态分配内存。在课堂上我并没有学习过这部分内容,我们只在讲解类之前简略地介绍了 new 运算符。现在我需要:

“动态分配一个学生对象,并要求用户输入学生的名字、姓氏和A号(ID编号)。”

我的结构体定义如下:

struct Student
{
    string firstName, lastName, aNumber;
    double GPA;
};

我尝试了Student student1 = new Student;,但它不起作用,我不确定如何在结构中动态地实现这个。

8个回答

23

将您的定义更改为

struct Student 
{
    string firstName, lastName, aNumber;
    double GPA;
};

请注意,我已更改struct关键字的位置。

现在你需要使用Student* student1 = new Student来动态分配内存。

当你为一个结构体动态分配内存时,会得到一个指向该结构体的指针。

完成后,你还需要记得通过执行delete student1释放动态分配的内存。你可以使用std::shared_ptr自动管理动态分配的内存。


1
本来不是想在结构体前面加上“Student”的,但是使用了“Student*”修复了它,现在必须返回指针,所以谢谢:] - sircrisp
2
@sircrisp 这正是unique_ptr所做的事情。除了它会为你完成这些操作。不要使用裸指针,它们很糟糕。 - Etienne de Martel
新问题是,既然我有了指向结构体的指针,那么如何填写结构体成员,例如firstName、lastName。我尝试使用student.firstName,但它显示表达式必须具有类类型。虽然我以为我用Student* student = new Student;进行了声明。 - sircrisp
4
@sircrisp听起来你真的需要查阅你的课堂材料。或者一本好的C++书籍。你需要使用->通过指针访问成员。 - R. Martinho Fernandes
2
@sircrisp:从这个问题来看,这不是你学习的方式。 - sbi
显示剩余3条评论

8
这应该是你需要的内容:
std::unique_ptr<Student> x(new Student);

1
我有疑问。你怎么能这样说,当你甚至不知道他为什么在使用 new - James Kanze
@James:因为实际上几乎没有任何情况需要使用原始的 new,而不是立即构造您选择的智能指针? - Puppy
3
胡说八道。许多情况下都要看具体应用,但大多数指针应该是原始指针。事实上,你无法避免使用原始指针,因为“this”就是一个原始指针。unique_ptr具有非常特定的语义;当你需要这些语义时,它非常好用,但当你不需要时,使用它会是一个错误。由于我们不知道他为什么要动态分配内存(我怀疑实际上他根本不应该使用动态分配),所以我们不能对unique_ptr的适用性做任何假设。 - James Kanze
1
这就是我反对的自动化。没有通用规则;一切都取决于类型以及你正在做什么以及为什么要动态分配。(例如,我有很多情况下只是忽略从“new”返回的指针。话虽如此,我承认在许多情况下,“unique_ptr”或“auto_ptr”(如果不能保证超新编译器)是一个好的解决方案。) - James Kanze
你使用new是因为你想要一个新的对象,通常是响应某些外部刺激或输入。当然,新对象的构造函数会执行必要的操作,以使对象对应用程序的其余部分可见,但构造它的代码不一定需要立即访问它。这是一种常见的模式。 - James Kanze
显示剩余14条评论

5
动态分配一个学生对象,然后提示用户输入学生的名字、姓氏和A号(ID号)。这个任务要求你在更新学生信息之前拥有一个不完全初始化的Student对象。一般来说,这是非常糟糕的想法,因为即使只有可能存在一个未完全初始化的对象(例如,在这种情况下缺少适当的id值),使用该对象的代码也会变得更加复杂,因为它必须检查是否存在适当的id值。这种复杂性对于正确使用来说是必要的,而忽视这种复杂性会引发许多错误-不好的。
这就是为什么C++在扩展C时提供了非常强的耦合性,即在分配和初始化之间。使用C++的new表达式,您可以获得成功的分配和成功的完全初始化,否则两者都不会成功(如果失败会进行清理)。这才是问题应该更好地教授的内容!
因此,与其引用上面给出的问题,我将教您可接受的C++实践方法(尽管通常应避免使用new),这意味着回答以下修改后的问题:
提示用户输入学生的名字、姓氏和A号(ID号),然后动态分配一个具有这些值的Student对象。
好的,下面开始:
// The Dynamic Student, version 1.
// "Prompt the user for student’s first name, a last name, and A - number
// (ID), and then dynamically allocate a `Student` object with these values."

#include <assert.h>         // assert
#include <iostream>         // std::cout,std::endl
#include <string>           // std::string
#include <sstream>          // std::istringstream
#include <stdexcept>        // std::exception, std::runtime_error
#include <stdlib.h>         // EXIT_SUCCESS, EXIT_FAILURE

#define CPP_NO_COPYING_OF( Clazz )      \
    Clazz( Clazz const& );              \
    Clazz& operator=( Clazz const& )

namespace cpp {
    using namespace std;

    bool hopefully( bool const c ) { return c; }
    bool throwX( string const& s ) { throw runtime_error( s ); }

    string lineFromInput()
    {
        string result;
        getline( cin, result )
            || throwX( "lineFromInput: std::getline failed (EOF?)" );
        return result;
    }

    string lineFromInput( string const& prompt )
    {
        cout << prompt;
        return lineFromInput();
    }

    int intFromInput( string const& prompt )
    {
        istringstream   stream( lineFromInput( prompt ) );
        int             result;

        stream >> result
            || throwX( "intFromInput: input line was not a valid number spec" );
        return result;
    }
}  // namespace cpp

namespace blah {
    using namespace std;
    using namespace cpp;

    struct Student
    {
        CPP_NO_COPYING_OF( Student );

        int const       id;
        string const    firstName;
        string const    lastName;

        Student(
            int const       _id,
            string const    _firstName,
            string const    _lastName
            )
            : id( _id ), firstName( _firstName ), lastName( _lastName )
        {}
    };

    Student* studentFromInput()
    {
        cout << "It's -- the Dynamic Student program!" << endl;

        string const    firstName   = lineFromInput( "First name, please? " );
        hopefully( firstName != "" )
            || throwX( "Sorry, the first name can't be nothing." );

        string const    lastName    = lineFromInput( "Last name, please? " );
        hopefully( lastName != "" )
            || throwX( "Sorry, the last name can't be nothing." );

        int const       id          = intFromInput( "And the student id is...? " );
        hopefully( id > 0 )
            || throwX( "Sorry, the id can't be negative or zero." );

        return new Student( id, firstName, lastName );
    }
}  // namespace blah

void cppMain()
{
    using namespace blah;

    Student const* const    pStudent    = studentFromInput();

    try
    {
        // Use the student object, e.g.
        cout
            << "The student is "
            << pStudent->firstName << " " << pStudent->lastName
            << ", with id " << pStudent->id << "."
            << endl;
        // Then:
        delete pStudent;
    }
    catch( std::exception const& )
    {
        delete pStudent;
        throw;      // Rethrows the exception.
    }
}

int main()
{
    using namespace std;

    try
    {
        cppMain();
        return EXIT_SUCCESS;
    }
    catch( exception const& x )
    {
        cerr << "!" << x.what() << endl;
    }
    return EXIT_FAILURE;
}

对于每个执行的new表达式(分配和初始化),最好有一个相应的delete表达式的执行,清除并释放内存块以便可以重复使用。即使某些操作失败并引发异常,也应该尽可能执行delete表达式。因此需要使用trycatch

然而,像这样编码容易出错且冗长。

相反,在更典型的C++编程中,会使用智能指针,这是一种对象,它保存指针并提供指针操作(因此看起来像是一个指针),当指针不再使用时其析构函数将自动执行delete表达式以释放内存块。C++标准库有几个这样的智能指针类。通常情况下,应该使用最限制的智能指针,因为它具有最小的开销,并且很可能支持转换为更一般的智能指针,而相反则不太可能。

所以在这种情况下,您可以使用例如C++11的std::unique_ptr或如果您的编译器较老,则可以使用C++03的std::auto_ptr,两者都在<memory>头文件中:

// The Dynamic Student, version 2  --  using smart pointer.
// "Prompt the user for student’s first name, a last name, and A - number
// (ID), and then dynamically allocate a `Student` object with these values."

#include <assert.h>         // assert
#include <iostream>         // std::cout,std::endl
#include <memory>           // std::unique_ptr
#include <string>           // std::string
#include <sstream>          // std::istringstream
#include <stdexcept>        // std::exception, std::runtime_error
#include <stdlib.h>         // EXIT_SUCCESS, EXIT_FAILURE

#define CPP_NO_COPYING_OF( Clazz )      \
    Clazz( Clazz const& );              \
    Clazz& operator=( Clazz const& )

namespace cpp {
    using namespace std;

    bool hopefully( bool const c ) { return c; }
    bool throwX( string const& s ) { throw runtime_error( s ); }

    string lineFromInput()
    {
        string result;
        getline( cin, result )
            || throwX( "lineFromInput: std::getline failed (EOF?)" );
        return result;
    }

    string lineFromInput( string const& prompt )
    {
        cout << prompt;
        return lineFromInput();
    }

    int intFromInput( string const& prompt )
    {
        istringstream   stream( lineFromInput( prompt ) );
        int             result;

        stream >> result
            || throwX( "intFromInput: input line was not a valid number spec" );
        return result;
    }
}  // namespace cpp

namespace blah {
    using namespace std;
    using namespace cpp;

    struct Student
    {
        CPP_NO_COPYING_OF( Student );

        int const       id;
        string const    firstName;
        string const    lastName;

        Student(
            int const       _id,
            string const    _firstName,
            string const    _lastName
            )
            : id( _id ), firstName( _firstName ), lastName( _lastName )
        {}
    };

    unique_ptr<Student> studentFromInput()
    {
        cout << "It's -- the Dynamic Student program!" << endl;

        string const    firstName   = lineFromInput( "First name, please? " );
        hopefully( firstName != "" )
            || throwX( "Sorry, the first name can't be nothing." );

        string const    lastName    = lineFromInput( "Last name, please? " );
        hopefully( lastName != "" )
            || throwX( "Sorry, the last name can't be nothing." );

        int const       id          = intFromInput( "And the student id is...? " );
        hopefully( id > 0 )
            || throwX( "Sorry, the id can't be negative or zero." );

        return unique_ptr<Student>( new Student( id, firstName, lastName ) );
    }
}  // namespace blah

void cppMain()
{
    using namespace blah;

    unique_ptr<Student> const   pStudent    = studentFromInput();

    // Use the student object, e.g.
    cout
        << "The student is "
        << pStudent->firstName << " " << pStudent->lastName
        << ", with id " << pStudent->id << "."
        << endl;
}

int main()
{
    using namespace std;

    try
    {
        cppMain();
        return EXIT_SUCCESS;
    }
    catch( exception const& x )
    {
        cerr << "!" << x.what() << endl;
    }
    return EXIT_FAILURE;
}

除了分配动态内存的要求外,具有上述功能的程序可以不使用任何动态内存或智能指针来编写。函数studentFromInput只需通过值复制返回一个Student对象。这几乎是一个悖论,但现代C++非常依赖于复制,并且仍然产生相当快的程序!
当然,在底层有许多肮脏的技巧,以避免在机器代码中实际发生复制。

@匿名的点踩者:请解释一下你的点踩,这样其他人就可以从你的见解中受益,并且不会被你发现的错误误导。 - Cheers and hth. - Alf
@Alf:我给它点了踩,但我认为这是个错误。不太确定原因,但如果你能编辑你的回答,那么我可以取消踩。 - Puppy
@Cheersandhth.-Alf:哇,这是一个好答案,我喜欢它如何完成任务,但仍然不能作为家庭作业的答案提交。虽然不是我见过的最好的,但也很好,最好的是“这应该在x86上工作”int main(){asm {...}}”。 - r_ahlskog

3

new返回一个指向对象的指针...所以你会想要

struct Student* student1 = new Student;

1

new语句返回指向新实例的指针。因此,您需要将student1定义为指针。

struct Student * student1 = new Student;

1

你为什么要使用new?只需声明一个变量的实例:

Student student1;

鉴于Student的定义,它似乎没有身份, 而且肯定是可复制的,所以你可能永远不应该new它。

(我还会为其提供构造函数,以便在定义时正确地初始化。)


1

struct 在定义结构体的名称之前。

当您尝试使用 new Student 时,您看到了什么错误?为什么它不起作用?


0

如果你想理解语法的概念或者想要感受语法的味道......只需一直使用这个 struct student = new student 语法。 因为 struct student = new sizeof(student) 的意思是你只是将数据类型放入 sizeof() 函数中以确认大小。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接