遍历指针数组 C++

Iterating over pointer array C++

我是 C++ 的新手,但我有 C 的经验。现在我试图遍历一个对象数组,但出现分段错误,因为它超出了数组的范围。

我有一个 class ZombieHorede,它保存了一个指向僵尸数组的指针。

#pragma once

#include "Zombie.hpp"

class ZombieHorde {
private:
        static std::string      namepool[7];
        static std::string      typepool[7];
        Zombie                          *zombies;
public:
        ZombieHorde(int N);
        ~ZombieHorde(void);

        void    announce(void) const;
};

这个class的构造函数接受一个数字N并初始化N个僵尸数组。像这样。

ZombieHorde::ZombieHorde(int N)
{
        int             i;

        i = 0;
        this->zombies = new Zombie[N];
        while (i < N)
        {
                this->zombies[i].set_name(this->namepool[rand() % 7]);
                this->zombies[i].set_type(this->typepool[rand() % 7]);
                i++;
        }
}

现在我想遍历僵尸并在每个僵尸上调用一个函数...像这样...

void    ZombieHorde::announce() const
{
        int     i;

        i = 0;
        while (&this->zombies[i])
        {
                this->zombies[i].announce();
                i++;
        }
}

并且 announce 函数给出了分段错误,因为 i 超出了这个 main 的数组范围:

#include "ZombieHorde.hpp"

int     main()
{
        ZombieHorde     horde(4);

        horde.announce();
}

现在的主要问题是如何遍历 zombies 数组以免出现分段错误。

注意:请注意,这是一个 42 的学校练习,不允许我使用 C++ 的 STL。我应该在构造函数中立即分配僵尸。

当我阅读您问题的给定标题时,我认为您确实有一个“指针数组”并对其进行迭代。但是你编码了一个 objects 的数组,这是另一回事。好的,让我们开始创建一个真正的指针数组...

您的问题是while (&this->zombies[i])。这将测试结果是真还是假,如果你有指针,它们将被隐式转换为 bool,因为我们期望数组末尾有一个 nullptr .

也许您将数组的分配和初始化更改为:

    Zombie **zombies = new Zombie*[N+1];

    for ( unsigned int idx=0; idx<N; idx++)
    {    
        zombies[idx] = new Zombie;
    }    
    zombies[N] = nullptr; // important to have the "end" mark if we later test for nullptr!

    // use it later maybe per index
    for ( unsigned int idx=0; idx<N; idx++) 
    {    
        zombies[idx]->announce();
    }    

    // or as you tried with pointer to nullptr compare
    // you can also use your While instead if you initialize
    // your running variable before outside the loop
    for ( Zombie** ptr= zombies; *ptr; ptr++)
    {    
        std::cout << ptr << std::endl;
        (*ptr)->announce();
    }    



顺便说一句:在这种情况下不需要写 this

但你应该开始写 C++ 而不是“C with 类”!


class Zombie
{
    private:
        std::string name;
        std::string type;

    public:
        // you should always use constructors to set content
        // of the class at the beginning instead later overwrite
        // values. Making things private and write accessor's to
        // all and everything is not the use case of encapsulation
        Zombie( const std::string& name_, const std::string& type_): name{name_},type{type_}{}
        void announce() const { std::cout << "Zombie " << name << ":" << type << std::endl; }
};

class ZombieHorde {
private:
        static std::string namepool[7];
        static std::string typepool[7];
        std::vector<Zombie> zombies;

public:
        ZombieHorde(const unsigned int N);
        ~ZombieHorde(){}

        void announce() const;
};

std::string ZombieHorde::namepool[7] = { "One","two","three","four","five","six","seven"};
std::string ZombieHorde::typepool[7] = { "red","blue","green","yellow","pink","mouve","black"};


ZombieHorde::ZombieHorde(const unsigned int N)
{
    for ( unsigned int idx=0; idx < N; idx++ )
    {    
        zombies.emplace_back( this->namepool[rand() % 7], this->typepool[rand() % 7]); 
    }    
}

void ZombieHorde::announce() const
{
    // iterate over containers is now simplified to:
    for ( const auto& zombie: zombies ) { zombie.announce(); }
}

int main()
{   
    ZombieHorde  horde(4);
    horde.announce();
}



question is how to iterate over the zombies array in order to not get a segmentation fault.

解决这个问题的一种方法是使用一个名为 zombieSize 数据成员 来存储 N 的值,如下面修改后的代码片段所示:

class ZombieHorde {
private:
        static std::string      namepool[7];
        static std::string      typepool[7];
        Zombie                          *zombies;
        std::size_t zombieSize; //data member that is used to store the value of N
public:
        ZombieHorde(int N);
        ~ZombieHorde(void);

        void    announce(void) const;
};

//use member initializer list to initialize zombieSize
ZombieHorde::ZombieHorde(int N): zombieSize(N)
{
        int             i;

        i = 0;
        this->zombies = new Zombie[zombieSize];//use data member zombieSize instead of N
        
        while (i < zombieSize)//use zombieSize instead of N
        {
                this->zombies[i].set_name(this->namepool[rand() % 7]);
                this->zombies[i].set_type(this->typepool[rand() % 7]);
                i++;
        }
        
}
void    ZombieHorde::announce() const
{
        int     i;

        i = 0;
        while (i < zombieSize)//use zombieSize instead of N
        {
                this->zombies[i].announce();
                i++;
        }
}

我觉得你面临的实际问题是 C 和 C++ 之间的区别。

执行此操作的 C++ 方法是使用庞大的标准库,在本例中为向量:

#pragma once

#include <vector>
#include "Zombie.hpp"

class ZombieHorde {
private:
        static std::vector<std::string>      namepool(7); // Maybe initialize it here if the names are known at compiletime!
// if not I think passing the name and and typelist as an argument to the constructor is cleaner than using a static value here.
        static std::vector<std::string> typepool(7);

        std::vector<Zombie> zombies;
public:
        ZombieHorde(int N);
      //  ~ZombieHorde(); as vector will take care of destruction you don't need to define or declare a destructor.
      // using void in parameter list is not very customary in c++ but allowed, iirc.
        void    announce() const; // same thing
};
ZombieHorde::ZombieHorde(int N): zombies(N) // <- initializer list. This constructs the data member zombies with N as an argument for the constructor.
// The vector class can take an int as argument and default constructs that many copies into the container.
{
 
for (size_t i = 0;i!= zombies.size();++i){
                this->zombies[i].set_name(this->namepool[rand() % 7]);
                this->zombies[i].set_type(this->typepool[rand() % 7]);
        }
}

注意:如果您的 C++ 标准(和编译器)足够新(C++ 17 就足够了,不确定较早的标准,您可以改为使用基于范围的范围:

ZombieHorde::ZombieHorde(int N): zombies(N) // <- initializer list
{
 
for (auto & zombie : zombies){
                zombie.set_name(this->namepool[rand() % 7]);
                zombie.set_type(this->typepool[rand() % 7]);
        }
}
void    ZombieHorde::announce() const
{

        for( auto const & zombie : zombies)
        {
                zombie.announce();
        }
}

在切换到 C++ 时,我发现另一件事很重要: 类 通常不是简单的数据结构,而是它们的数据通常组合成满足某些规则的某个实体。这些规则通常通过禁止成员数据的某些组合来体现,通常称为“class 不变量”。

例如这里有一个没有类型或名称的僵尸可能是无稽之谈。我喜欢以这样一种方式设计我的 classes,即在构建后它们满足它们的规则。 如果你不这样做,你必须在每次使用僵尸时检查它是否已经调用了它的初始化函数 set_name 和 set_type.

相反,您可以让僵尸拥有 const 成员名称和类型,并且只提供提供这些的构造函数:

class Zombie{
public:
Zombie(std::string const & name, std::string const & type);
private:
std::string const name;
std::string const type;
};

Zombie::Zombie(std::string const & _name, std::string const & _type): name(_name),type(_type) {}

部落必须调用适当的构造函数:

ZombieHorde::ZombieHorde(int N)
{
    for(int i = 0;i!=N;++i){
    zombies.emplace_back(namepool[rand() % 7],typepool[rand() % 7]);
// emplace_back is neat: It takes the same argument as the constructor of an array element.
    }
}