views:

546

answers:

8

Beginners question, on loop efficiency. I've started programming in C++ (my first language) and have been using 'Principles and Practice Using C++' by Bjarne Stroustrup. I've been making my way through the earlier chapters and have just been introduced to the concept of loops.

The first exercise regarding loops asks of me the following: The character 'b' is char('a'+1), 'c' is char('a'+2), etc. Use a loop to write out a table of characters with their corresponding integer values:

a 97, b 98, ..., z 122

Although, I used uppercase, I created the following:

int number = 64; //integer value for @ sign, character before A
char letter = number;//converts integer to char value
int i = 0;

while (i<=25){
    cout << ++letter << "\t" << ++number << endl;
    ++i;
    }

Should I aim for only having 'i' be present in a loop or is it simply not possible when converting between types? I can't really think of any other way the above can be done apart from having the character value being converted to it's integer counterpart(i.e. opposite of current method) or simply not having the conversion at all and have letter store '@'.

+8  A: 

You should aim for clarity first and you try to micro-optimize instead. You could better rewrite that as a for loop:

const int offsetToA = 65;
const int numberOfCharacters = 26;
for( int i = 0; i < numberOfCharacters; ++i ) {
    const int characterValue = i + offsetToA;
    cout << static_cast<char>( characterValue  ) << characterValue << endl;
}

and you can convert between different types - that's called casting (the static_cast construct in the code above).

sharptooth
On the subject of clarity; Numeric constants are seldomly a good idea. I propose assigning `offsetToA` the value `'a'` (or `'A'`), which is implicitly converted to (the correct) `int`. This of course also requires that the first character printed is calculated as `offsetToA + 0`.
Magnus Hoff
+2  A: 

there is nothing particularly inefficient about the way you are doing it but it certainly is possible to just convert between chars and ints (a char is an integer type). this would mean you only need to store 1 counter rather than the 3 (i, letter + number) you curently have

also, for looping from a fixed start to end a 'for' loop is perhaps more idiomatic (though its possible you havent met this yet!)

jk
+2  A: 

That's not a bad way to do it, but you can do it with only one loop variable like this:

char letter = 65;

while(letter <= 65+25){
  printf("%c\t%d\n", letter, letter);
  ++letter;
}
3lectrologos
+17  A: 

Following on from jk you could even use the letter itself in the loop (letter <= 'z'). I'd also use a for loop but that's just me.

for( char letter = 'a'; letter <= 'z'; ++letter )
    std::cout << letter << "\t" << static_cast<int>( letter ) << std::endl;
Patrick
That's right: you actually iterate over the _letters_! +1 for programming as close as possible to the _intention_.
xtofl
But does C++ guarantee that `'a' + 1 == 'b'`? Also see my answer to this question for details.
Alok
@Alok: experience guarantees that 'a' + 1 == b. As soon as I work in an environment where it doesn't I'll write code that deals with it ;)
Patrick
Just a random rant: why not using char(i)? it is shorter, and also compatible with C. As far as I understand, in C this is a typecast, while in C++ this is calling a constructor of the "class" char. How far am I from the truth?
elcuco
@elcuco, stackoverflow is the font of all knowledge: http://stackoverflow.com/questions/28002/regular-cast-vs-staticcast-vs-dynamiccast (well, all important knowledge anyway)
Patrick
A: 

Incrementing three separate variables is probably a little confusing. Here's a possibility:

for (int i = 0; i != 26; ++i)
{
    int chr = 'a' + i;
    std::cout << static_cast<char>(chr) << ":\t" << chr << std::endl;
}

Note that using a for loop keeps all the logic of setting up, testing and incrementing the loop variable in one place.

James Hopkin
A: 

At this point, I wouldn't worry about micro-optimizations such as an efficient way to write a small loop like this. What you have allows a for loop to do the job nicely, but if you are more comfortable with while, you should use that. But I am not sure if that is your question.

I don't think you have understood the question properly. You are writing the code, knowing that 'A' is 65. The whole point of the exercise is to print the value of 'A' to 'Z' on your system, without knowing what value they have.

Now, to get an integer value for a character c, you can do: static_cast<int>(c). I believe that is what you're asking.

I haven't written any code because it should be more fun for you to do so.

Question for the experts: In C, I know that 'a'...'z' need not have continuous values (same for 'A'...'Z'). Is the same true for C++? I would think so, but then it seems highly unlikely that Stroustrup's book assumes that.

Alok
I can understand your point but it really is as basic as what I wrote, static_cast<int>(c) hasn't been introduced, the reader is never aware of such possibilities. He gave a before example of creating a list of numbers and their squares via a loop. It's more about just introducing the idea of a loop, not the content of a loop. if that makes sense.
Chris
Alok: no, I don't believe C++ guarantees that either. The source and execution character sets are defined in 2.2, which states that decimal digits are consecutive but doesn't mention letters. But the questioner states that it is in text which appears to me to be copied from the question in the book. So I think that for the purposes of the question, Bjarne is saying to assume lowercase letters are consecutive. As slackerbyname says, it's not a question about handling EDCBIC, nor about whether and how to introduce porting constraints to your code...
Steve Jessop
"to get an integer value for a character c, you can do: `static_cast<int>(c)`". Or `int i = c;`, which is what I'd expect.
Steve Jessop
+2  A: 

If you are concerned about the efficiency of your loop, I would urge you to try this:

Get this code compiled and running under an IDE, such as Visual Studio, and set a break point at the beginning. When you get there, switch to the disassembly view (instruction view) and start hitting the F11 (single-step) key, and keep a mental count of how many times you are hitting it.

You will see that it enters the loop, compares i against 25, and then starts doing the code for the cout line. That involves incrementing letter, and then going into the << routine for cout. It does a number of things in there, possibly going deeper into subroutines, etc., and finally comes back out, returning an object. Then it pushes "\t" as an argument and passes it to that object, and goes back in and does all the stuff it did before. Then it takes number, increments it, and passes it to the cout::<< routine that accepts an integer, calls a function to convert it to a string (which involves a loop), then does all the stuff it did before to loop that string into the output buffer and return.

Tired? You're not done yet. The endl has to be output, and when that happens, not only does it put "\n" in the buffer, but it calls the system routine to flush that buffer to the file or console where you are sending the I/O. You probably can't F11 into that, but rest assured it takes lots of cycles and doesn't return until the I/O is done.

By now, your F11-count should be in the vicinity of several thousand, more or less.

Finally, you come out and get to the ++i statement, which takes 1 or 2 instructions, and jumps back to the top of the loop to start the next iteration.

NOW, are you still worried about the efficiency of the loop?


There's an easier way to make this point, and it's just as instructive. Wrap an infinite loop around your entire code so it runs forever. While it's running, hit the "pause" button in the IDE, and look at the call stack. (This is called a "stackshot".) If you do this several times you get a good idea of how it spends time. Here's an example:

NTDLL! 7c90e514()
KERNEL32! 7c81cbfe()
KERNEL32! 7c81cc75()
KERNEL32! 7c81cc89()
MSVCRTD! 1021bed3()
MSVCRTD! 1021bd59()
MSVCRTD! 10218833()
MSVCRTD! 1023a500()
std::_Fputc() line 42 + 18 bytes
std::basic_filebuf<char,std::char_traits<char> >::overflow() line 108 + 25 bytes
std::basic_streambuf<char,std::char_traits<char> >::sputc() line 85 + 94 bytes
std::ostreambuf_iterator<char,std::char_traits<char> >::operator=() line 304 + 24 bytes
std::num_put<char,std::ostreambuf_iterator<char,std::char_traits<char> > >::_Putc() line 633 + 32 bytes
std::num_put<char,std::ostreambuf_iterator<char,std::char_traits<char> > >::_Iput() line 615 + 25 bytes
std::num_put<char,std::ostreambuf_iterator<char,std::char_traits<char> > >::do_put() line 481 + 71 bytes
std::num_put<char,std::ostreambuf_iterator<char,std::char_traits<char> > >::put() line 444 + 44 bytes
std::basic_ostream<char,std::char_traits<char> >::operator<<() line 115 + 114 bytes
main() line 43 + 96 bytes
mainCRTStartup() line 338 + 17 bytes

I did this a bunch of times, and not ONCE did it stop in the code for the outer i<=25 loop. So optimizing that loop is like someone's great metaphor: "getting a haircut to lose weight".

Mike Dunlavey
+1  A: 

Since no one else mentioned it: Having a fixed amount of iterations, this is also a candidate for post-condition iteration with do..while.

char letter = 'a';
do {
    std::cout << letter << "\t" << static_cast<int>( letter ) << std::endl;
} while ( ++letter <= 'z' );

However, as shown in Patrick's answer the for idiom is often shorter (in number of lines in this case).

Justin Johnson