C++/only 2 questions
Expert: Ralph McArdell - 3/12/2005
Question#include <iostream.h>
#include <string.h>
#include <conio.h>
class Animal{
protected:
char desc[20];
float height,weight;
public:
Animal(char[],float,float);
};
class Bird:public Animal{
private:
float wingspan;
public:
Bird(char[],float,float,float);
float getheig();
};
Animal::Animal(char de[],float heig,float weig){
strcpy(desc,de);
height = heig;
weight = weig;
}
Bird::Bird(char de[],float heig,float weig,float wingsp)
:Animal(de,heig,weig){
wingspan=wingsp;
}
Define a class called Animal
Desc : array of 20 characters
weight : float
Define a class Bird that inherits from Animal class above
Wingspan : float
The program that above is correct or not?
How can I write below (in C++)?
I only do not know how can I write (below)?
Thanks!
My program should be able to store up to 10 BirdRec records in an array called BirdsArray.
And has some operations(e.g Add new bird records into BirdArray)
AnswerFirst off, did you try to compile the code? Any mistakes in the use of the language would be picked up by the compiler - at least to the extent of the compiler's compliance with the ANSI/ISO C++ standard - some are better than others. In any case no matter whether the code is correct or not you still have to get it past the compiler you are using even if it is broken with regard to standard C++.
Anyhow the very first lines are incorrect - standard C++ has no such header files as iostream.h, string.h or conio.h. The first is the old name for iostream (no .h extension). string.h is the C header file for C string utility function declarations - so this is not totally incorrect, however C++ renames it to be cstring (again no .h extension). conio.h is a largely Microsoft invention - although I believe other compiler vendors such as Borland may also supply a version, at least on MS Windows platforms - so this header can only be used with compilers which support it and its contents may of course vary as it is non-standard.
The only reason for using iostream.h and string.h is if your code is compiled with an old compiler that does not support the new header files and the standard C++ library and I would urge you to locate a more up to date implementation such as the current GNU compiler release or one of the MS Windows compilers based on it such as Mingw and the Dev-C++ integrated development environment (see
http://www.bloodshed.net/download.html). Note these are free and often used by people learning C++.
The second point is that the only header file you require at the moment is the second one string.h or, in C++, cstring - for strcpy. I found all this out by running your code through a compiler and commenting out the header files until things broke.
However, having replaced the three header file includes with just one for cstring (or string.h) you code compiles.
Next, I must point out that the style you are using for your code layout is outdated, often used in books and magazine articles to save space and is generally considered to render the code unclear. Try doing something more like this - although this is only one possibility:
class Animal
{
protected:
char desc[20];
float height, weight;
public:
Animal(char[],float,float);
};
and
Animal::Animal(char de[],float heig,float weig)
{
strcpy(desc,de);
height = heig;
weight = weig;
}
Next you seem to understand the use of an initialiser list for initialising base classes using a constructor other than the default constructor but you do not extend this to the individual class members. The only exception is the call to strcpy (which I will return to later) to initialise desc. You could for example rework the Animal class constructor like so:
Animal::Animal(char de[],float heig,float weig)
: height( heig )
, weight( weig )
{
strcpy(desc,de);
}
Note this layout is my preferred style but you may prefer the more common ones such as:
Animal::Animal(char de[],float heig,float weig)
: height( heig ),
weight( weig )
{
strcpy(desc,de);
}
or
Animal::Animal(char de[],float heig,float weig)
: height( heig ), weight( weig )
{
strcpy(desc,de);
}
Now I notice you seem to like to bunch everything up and not give information just because it is optional - such as names for parameters in function declarations. Don't. Space your code out so it can be easily read by people - in most real life projects 80% of the time is spent maintaining the code - which means it needs to be read and understood to allow updates and fixes to be applied often years after it was originally written and by people other than the person who wrote it. Common practice then is in general to not place many member or object definitions on one line as in the case from class Animal:
float height, weight;
but to place them one per line like so:
float height;
float weight;
and not to leave out names of parameters in function declarations:
Animal(char[],float,float);
But to add them in:
Animal( char de[], float heig, float weig );
This in fact has another boon in that function declarations and definitions look similar, however of course for member functions you need to add the class qualifier to the function name. The boon is that you can copy and paste the declaration, remove the semi colon and add the class names qualifier if necessary and off you go. No need to type it all from scratch.
You are using arrays of char. Again this may be because your C++ implementation does not have the std::string class - again try and update your implementation if possible. Using std::string in place of arrays of char would simplify the code further as there is no need to copy std::string objects using special functions as is the case with C-strings and strcpy - they can be initialised as the other members.
Now to naming. You tend to contract names - in fact you have to change the names of parameters to the constructors so as not to clash with the member names. The worst example is getheig member function - why not getheight or get_height or GetHeight?
Now one popular convention is to prefix or postfix member names with a letter or character so they can be easily differentiated from non-members in member functions. Common examples are m or m_ prefixes for member or i prefix for instance member or an trailing underscore (prefixed underscores are reserved for use by C++ implementations). Here is the Animal class member height renamed using these conventions:
mHeight
m_height
iHeight
height_
Note that I add capitalisation if the prefix would obscure the name of the member. A side effect is that names of constructor parameters can if required now be the same as the member names they are to initialise less the prefix or postfix.
Now the next thing I notice is that Bird contains a member function getheig to presumably return the height of the bird. However height is an attribute of Animal, the base of Bird - so why can we not get the height of all types of Animals, not just Birds?
To put it another way: why does a sub-class get an attribute of its base class? Why is this getter not a member of the base class?
Which leads on to the next point. Why are sub classes allowed direct access to the base class data? Do they need it? If they do provide exactly the accessor (setters and getters) that are required and make them protected or public if anyone should be able to access the information.
Taking all the points made so far and making a couple of style choices your original code looks like this:
#include <string> // for C++ std::string class
class Animal
{
private:
std::string m_desc;
float m_height;
float m_weight;
public:
Animal( std::string const & desc, float height, float weight );
float get_height() const;
};
class Bird : public Animal
{
private:
float m_wingspan;
public:
Bird( std::string const & desc
, float height
, float weight
, float wingspan
);
};
Animal::Animal( std::string const & desc, float height, float weight )
: m_height( height )
, m_desc( desc )
, m_weight( weight )
{
}
Bird::Bird
( std::string const & desc
, float height
, float weight
, float wingspan
)
: Animal( desc, height, weight )
, m_wingspan( wingspan )
{
}
int main()
{
}
Of course your original code did compile - once I had tidied up the header files includes - and probably as far as I could see would work - the biggest gripe being the placement of getheig - so you can choose to mostly ignore what I have said so far. Also due to your use of tabs and tabs spacing varying from editor to editor some parts of the code listed above may not be arranged for you quite as I intended.
As to the Bird array - well I think I did in fact answer that for you in what I think was your previously question - sorry if it was someone else. The most obvious and most in keeping with the C with a bit of C++ style of the code is to use a built in array of 10 Bird objects:
int const MaximimBirdRecords(10);
// ...
Bird birdRecords[MaximimBirdRecords];
However to achieve this you need to provide the Bird class with a default constructor - that is one that can be called with no parameters, leaving the object in a default state.
Of course you could also use a std::vector, if it is available in your C++ implementation (if not again get something more up to date...):
#include <vector>
// ...
// Convenience alias for std::vector of Bird objects:
typedef std::vector<Bird> BirdVector;
// ...
BirdVector birdRecords;
Which will allow for more than 10 records as it automatically expands the size of the vector as more objects are added but you can always add code to check for the maximum allowed and stop any more being added - code you would need for the built in array case anyway to prevent overrunning the end of the array and possibly overwriting memory that you should not or causing the program to crash with a memory access violation due to the attempt.
Note that I am not going to go into great detail on std::vector here but leave that for you to look into if you are interested. As I stated before that in order to use a type as the element type of a std::vector class it must be a good citizen - which for the C++ standard library container types means it must be copyable - which in some cases requires the provision of a special constructor called a copy constructor - the compiler will generate one for a class if it does not provide one and this will do for many but not all cases. Likewise, it must be assignable ( element1 = element2 ), and again the compiler generated assignment operator ( operator= ) may not do for all cases. Finally it must be destroyable by a destructor - again the compiler will generate one for a class. In this case once again I think you are OK.
To add a new Bird to the array:
BirdVector birdRecords;
// ...
Bird a_bird("pideon", 10, 200, 0.5);
// makes copy of a_bird in birdRecords
birdRecords.push_back( a_bird );
or more briefly:
birdRecords.push_back( Bird("pideon", 10, 200, 0.5) );
Of course adding birds to the bird vector (or array) is an operation on the vector, not the Bird class itself. So it might be an idea to wrap the vector of birds in its own class and give it its own operations.
I would have thought that your tutor would should have given you the information you require for you to complete the task or the notes or books your are following would have explained what you need to know if you are on your own.
I do not have the time to teach you what you should have picked up from your studies - such as writing classes and member functions of those classes - you obviously have some idea what to do as you presented me with some plausible code. As you see just this little question has taken seven pages!