简体   繁体   中英

Iterating over pointer array C++

I am new to C++ but I have an experience in C. Now I am trying to iterate over an array of objects but I am getting segmentation fault because it runs out of the range of the array.

I have a class ZombieHorede which keeps a pointer to an array of zombies.

#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;
};

The constructor of this class takes a number N and initializes the array of N zombies. Like this.

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++;
        }
}

Now I want to iterate over zombies and call a function on each of them... like this...

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

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

And the announce function gives segmentation fault cuz i goes beyond the range of the array with this main:

#include "ZombieHorde.hpp"

int     main()
{
        ZombieHorde     horde(4);

        horde.announce();
}

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

ATTENTION: Note that this is a 42 school exercise and I am not allowed to use STL of C++. I should allocate zombies at once in the constructor.

As i read the given headline to your question, I thought you really have an "pointer array" and iterate over it. But you coded an array of objects, which is a different thing. OK, so lets start creating a real pointer array...

Your problem is while (&this->zombies[i]) . This will test if the result is true or false, if you have pointers, they will be implicit converted to bool, as this we expect a nullptr at the end of the array.

Maybe you change the allocation and initializing of your array to:

    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();
    }    



BTW: No need to write this in this context!

But you should start to write C++ instead of "C with classes"!


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.

One way to resolve this would be to have a data member called zombieSize that stores the value of N as shown below in the modified snippets:

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++;
        }
}

I feel like the actual problem you are facing is the difference between C and C++.

The C++ way of doing this, is to use the vast standard library, in this case a vector:

#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]);
        }
}

NOTE: If your C++ standard (and compiler) is new enough (c++ 17 is enough, not sure about earlier ones you can instead use a range based for:

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();
        }
}

There is another thing I find important when switching to C++: Classes usually are not simply structs of data but usually their data combines to some entity, that satisfies some rules. These rules manifest usually by forbidding certain combinations of member data and are normally called "class invariants".

For example here it is probably nonsense to have a zombie without a type or a name. I like to design my classes in such a way that after construction they fulfill their rules. If you don't do that you have to check everytime you use a zombie, whether it has already called its init functions set_name and set_type.

Instead you could make the zombies have const members name and type and only provide a constructor that provides these:

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) {}

The horde must then call the appropriate constructor:

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.
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM