Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using arrays of character strings: arrays of pointers - Are they like multidimensional arrays?

I've been reading C++ for dummies lately and either the title is a misnomer or they didn't count on me. On a section about utilizing arrays of pointers with characters strings they show a function on which I've been completely stumped and don't know where to turn.

char* int2month(int nMonth)
{
//check to see if value is in rang
if ((nMonth < 0) || (nMonth > 12))
    return "invalid";

//nMonth is valid - return the name of the month
char* pszMonths[] = {"invalid", "January", "February", "March", "April", "May", "June", 
                     "July", "August", "September", "October", "November", "December"};

return pszMonths[nMonth];
} 

First of (but not the main question), I don't understand why the return type is a pointer and how you can return pszMonths without it going out of scope. I've read about it in this book and online but I don't get it in this example.

The main question I have is "how does this work?!?!". I don't understand how you can create an array of pointers and actually initialize them. If I remember correctly you can't do this with numeric data types. Is each pointer in the "array of pointers" like an array itself, containing the individual characters which make up the words? This whole thing just boggles my mind.

August 20 - Since there seems to me some confusion by the people trying to help me at as to where my confusion actually stems from I'll try to explain it better. The section of code in particular I am concerned with is the following:

//nMonth is valid - return the name of the month
char* pszMonths[] = {"invalid", "January", "February", "March", "April", "May", "June", 
                 "July", "August", "September", "October", "November", "December"};

I thought that when you made a pointer you could only assign it to another predetermined value. I'm confused that what seems to be an array of pointers (going by the book here) initializes the month names. I did not think pointers could actually initialize values. Is the array dynamically allocating memory? Is "invalid" essentially equivalent to a "new char;" statement or something similar?

I'll try re-reading the posts in case they answered my questions but I just didn't understand the first time around.

like image 664
Greener Avatar asked Dec 28 '22 15:12

Greener


2 Answers

ok, let's take one line at a time.
 

char* int2month(int nMonth)

This line is most probably WRONG, because it says the function returns a pointer to a modifiable char (by convention this will be the first char element of an array). Instead it should say char const* or const char* as the result type. These two specifications mean exactly the same, namely a pointer to a char that you cannot modify.
 

{

This is just the opening brace of the function body. The function body ends at corresponding closing brace.
 

//check to see if value is in rang

This is a comment. It is ignored by the compiler.
 

if ((nMonth < 0) || (nMonth > 12))
    return "invalid";

Here the return statement is executed if and only if the condition in the if holds. The purpose is to deal in a predictable way with incorrect argument value. However, the checking is probably WRONG because it allows both values 0 and 12 as valid, which gives a total of 13 valid values, whereas a calendar year has only 12 months.

By the way, technically, for the return statement the specified return value is an array of 8 char elements, namely the 7 characters plus a nullbyte at the end. This array is implicitly converted to a pointer to its first element, which is called a type decay. This particular decay, from string literal to pointer to non-const char, is specially supported in C++98 and C++03 in order to be compatible with old C, but is invalid in the upcoming C++0x standard.

The book should not teach such ugly things; use const for the result type.


 

//nMonth is valid - return the name of the month
char* pszMonths[] = {"invalid", "January", "February", "March", "April", "May", "June", 
                     "July", "August", "September", "October", "November", "December"};

This array initialization again involves that decay. It's an array of pointers. Each pointer is initialized with a string literal, which type-wise is an array, and decays to pointer.

By the way, the "psz" prefix is a monstrosity called Hungarian Notation. It was invented for C programming, supporting the help system in Microsoft's Programmer's Workbench. In modern programming it serves no useful purpose but instead just akes the simplest code read like gibberish. You really don't want to adopt that.
 

return pszMonths[nMonth];

This indexing has formal Undefined Behavior, also known affectionately as just "UB", if nMonth is the value 12, since there is no array element at index 12. In practice you'll get some gibberish result.

EDIT: oh I didn't notice that the author has placed the month name "invalid" at the front, which makes for 13 array elements. how to obscure code... i didn't notice it because it's very bad and unexpected; the checking for "invalid" is done higher up in the function.


 

} 

And this is the closing brace of the function body.

Cheers & hth.,

like image 103
Cheers and hth. - Alf Avatar answered Mar 21 '23 09:03

Cheers and hth. - Alf


Perhaps a line-by-line explanation will help.

/* This function takes an int and returns the corresponding month
 0 returns invalid
 1 returns January
 2 returns February
 3 returns March
 ...
 12 returns December
*/
char* int2month(int nMonth)
{
// if nMonth is less than 0 or more than 12, it's an invalid number
if ((nMonth < 0) || (nMonth > 12))
    return "invalid";

// this line creates an array of char* (strings) and fills it with the names of the months
//
char* pszMonths[] = {"invalid",  // index 0
                     "January",  // index 1
                     "February", // index 2
                     "March",    // index 3
                     "April",    // index 4
                     "May",      // index 5
                     "June",     // index 6
                     "July",     // index 7
                     "August",   // index 8
                     "September",// index 9
                     "October",  // index 10
                     "November", // index 11
                     "December"  // index 12
                    };

// use nMonth to index the pszMonths array to return the appropriate month
// if nMonth is 1, returns January because pszMonths[1] is January
// if nMonth is 2, returns February because pszMonths[2] is February
// etc
return pszMonths[nMonth];
} 

First thing to get out of the way that you might not know is that a string literal in your program (stuff with double quotes around it) is really of the char* type1.

Second thing that you might not have realized is that indexing into an array of char*s (which is char* pszStrings[]) yields a char*, which is a string.

The reason why you can return something from local scope in this instance is because string literals are stored in the program at compile time and do not get destroyed. For instance, this is perfectly fine:

char* blah() { return "blah"; }

And it's almost like doing this2:

int blah() { return 5; }

Secondly, when you have an = {/* stuff */} after an array declaration, that's called an initializer list. If you leave off the size of the array like you're doing, the compiler figures out how big to make the array by how many elements are in the initializer list. So char* pszMonths[] means "an array of char*" and since you have "invalid", "January", "February", etc. in the initializer list and they are char*s1, you're just initializing your array of char*s with some char*s. And you misremembered about not being able to do this with numeric types, because you can do this with any type, numeric types and strings included.

1 It's not really a char*, it's a char const[x], and you cannot modify that memory like you could with a char*, but that's not important to you right now.

2 It's not really like that, but if it helps you to think of it that way, feel free until you get better at C++ and can handle the various subtleties without dying.

like image 38
Seth Carnegie Avatar answered Mar 21 '23 08:03

Seth Carnegie