The code you have posted above is fine, technically. I suspect there is a bit of misunderstanding with the arrays.
All arrays are simply a contiguous list of homogeneous elements, such as your Student.
In C and C++, an array is a bit of an odd data type. Arrays have the following information stored with them:
• address of first element
• element type
• number of elements
Arrays
cannot be assigned. So passing an array by
value causes it to decay to a simple pointer, which leaves you with only the following information:
• address of (first) element
• element type
That is, pointers do not have size information. It does not matter if you use [] or not, the following are identical:
1 2
|
void f( int xs[] );
void f( int * xs );
|
This is why modifying the elements of an argument array (pointer!) modify the elements of the source array — because they are the same exact thing.
It is also why you need an additional argument to pass the available number of elements that can be modified:
1 2 3 4
|
void f( int xs[], int n )
{
// I can do anything with any xs[z] as long as 0 ≤ z < n.
}
|
If you wish to add elements to an array, you are still constrained by the
available number of elements in the array. Hence, you will see one of the two:
1 2 3 4 5
|
int f( int xs[], int n ) // common
{
...
return z; // z is number of elements USED in the available space in the array, 0 ≤ z < n.
}
|
1 2 3 4 5
|
void f( int xs[], int& n ) // rare
{
...
n = z; // update n to the new number of elements USED in the available space in the array
}
|
The second method is less common because it leads to accidental abuses where the caller forgets the amount of space AVAILABLE in the array. To wit, use the first method.
Now, C++ has introduced the concept of a
reference. But again, arrays are
special. You can pass a reference to the array
address, which is the same as before, but it also allows you to query information about length.
1 2 3
|
template <std::size_t N>
int g( int (&xs)[ N ] )
{
|
This is great, except you should be aware of the bloat this can cause. Use it to query an array’s length and pass it along to the non-reference version:
1 2 3 4 5
|
template <std::size_t N>
int g( int (&xs)[ N ] )
{
return f( xs, N );
}
|
Beyond the bloat issue and the convenience to the caller, the function is otherwise
identical: you got a reference to the pointer to the first element in the array; modifying the elements modifies the source array.
It is convenient for an actual
array type, but not for dynamically-allocated arrays, which just give you a pointer to begin with.
1 2 3 4 5 6 7 8
|
constexpr int XsAvailable = 10;
int xs[ XsAvailable ];
int XsUsed = f( xs, XsAvailable );
//or
int XsUsed = g( xs );
|
1 2 3 4 5 6 7 8 9 10 11
|
constexpr int XsAvailable = 10;
int* xs = new int[ XsAvailable ];
int XsUsed = f( xs, XsAvailable );
// int XsUsed = g( xs ); // ← doesn’t work; xs is not an “array type”
// (it is a pointer to a dynamically-allocated array)
...
delete [] xs;
|
The Debugger isn’t trying to trick you. All elements of an array are indexed via the array’s pointer. That is,
xs[7]
is syntactical sugar for
*(xs + 7)
which gives you a true reference (alias) to the 8th element of the array.
So your
Student accesses really are accesses through a
Student*.
Hope this helps.