Dammit, why didn’t I notice there was a second page that already covered that!? :smack:
Sorry if this is not quite on-topic.
Many years ago I was enrolled in an undergraduate Operating Systems course for which we had to learn i386 assembly language. By this time I already had a lot of experience programming the 6502, the 6809, the Z80, and the M68000, so learning the i386 was straightforward. For other students in the class though, it was clearly a challenge.
For one particular assignment, we had to solve some problem (I forget what) in i386 assembly code. On the day we turned in our program listings the professor re-distributed our submissions randomly among the class, so that we could go through a kind of peer review process. I’m not sure why he did this, and curiously he never did it a second time — but anyway the task was to “grade” your classmate’s work, writing your own comments and suggestions on it in red ink, and then hand this back in for the next class. Fortunately for me, as you’re about to see, the student grading was non-binding.
On the next class we handed in our graded versions of each other’s work. I don’t remember anything about the program I reviewed. It must have been decent enough. Then finally on the next class after that, we received our own work back, now marked with both the professor’s commentary as well as commentary from one of our anonymous peers.
The professor gave me a near perfect score, but the student dinged me for writing inefficient code. What was my “error”?
At the time, I was fond of writing my assembly language subroutines like this:
SOME_ROUTINE:
; [ Block-comment documentation of the routine goes here. ]
[ Routine body goes here, end-of-line comments as needed. ]
RET
You can see the flaw now I’m sure. The guy dinged me of course because I put a big comment after the routine’s opening label. His remark was that at runtime, whenever my routine was called, the computer would be wasting time processing the comment.
Ig… oooog… grrrr… mmmmmph… RARRRRH!
First off, if efficiency is an issue, I’d be careful with the switches.
Regarding your goto quandries, the first example was answered the same way I would have, using returns. This one I’d handle with a variable:
otherstuff = 0;
switch (x)
{
case 0:
//do some stuff
otherstuff = 1;
break;
case 1:
// do some stuff that's different than case 0
otherstuff = 1;
break;
//5 other cases here
}
if (otherstuff==1)
{
//do otherstuff;
}
Forgive any syntax errors; I haven’t written in C in forever.
Hmm… don’t you also run into trouble with the effect of break and continue statements? You could still easily simulate a while loop with a for loop, but to do the opposite would require a goto.
As to your second point, I agree with you. But the OP actually suggests using a for loop to do linked list traversal, as well, which I don’t think counts as a “counting situation.”
I guess it’s a matter of programming practice, but my own style generally dictates that dynamic data structures get traversed with while loops, while arrays and the like (as well as standard counting loops) get traversed with a for. In general, when the increment statement (not the loop test) of the loop is complex (read: more than i++ or i += 3), I prefer a while loop.
I once knew a programmer who put halts after evey one of his jumps (assembly code don’t you know) “just in case”.
Of course, this was when microprocessors were new enough that a little skepticism was in order. Still, you have to make a few assumptions.
I agree with you. There are instances where surperflous code is just that.
Well, yeah, especially on a processor with a deep pipeline. But I can only rewrite so much code to be more efficient. Believe me, I’m trying to get rid of the switches. Your way makes sense, although it does add another conditional branch (but again, I can’t complain about that much, since there’s already a big-asses switch statement in the routine).
Miltan, personally, I don’t care how complex the incrementor or the test is. If you have an initializer, an incrementor, and a test, then you should be using the looping statement that’s designed to deal with an initializer, and incrementor, and a test, and not the looping statement that just tests.
Now hang on a second. What’s to determine if the code I have in a loop is an “incrementor”? What about searching a binary tree? It’s not amazingly different from traversing a linked list. I could certainly write it as a nice compact for loop:
for (TreePtr *p = root; (p != NULL) && (p->d != value);
p = (p->d < value)?p->left:p->right);
But I find that it makes more sense as a while loop:
TreePtr *p = root;
while ((p != NULL) && (p->d != value)) {
if (p->d < value) {
p = p->left;
} else {
p = p->right;
}
}
Now, obviously this is a slightly contrived example, but my general point is that one person’s “incrementor” is another person’s “too complex to put into a for-loop.” And a lot of times, a “proper” use of a while loop just hides the incrementor anyway, such as your queue.get() example. I’m not trying to start a holy war, or anything, just trying to argue that different coding styles call for different loop constructs.
That’s the sad part. I do make the specification explicit. I’m always careful to specify exactly what a function should do. Then I try to provide an example or two to help illustrate the usage. Same applies for assignments I give – my specs are always as precise and complete as possible.
At one of the places I worked, there was a consultant who would alway move spaces to a character field immediately before moving in the desired string. When I tried to tell her this was unnecessary, she said she just wanted to be sure the variable was cleared. :rolleyes: She was the one who gave me the technical interview for the position.
Just curious – what’s the scenario you are envisioning here? Do you mean converting a for-loop that uses a continue into a while-loop with the same functionality would need a goto? Or was it something else you were envisioning? Example?
If the former:
for (initial condition; test-condition; iterator-statement)
{
// some stuff
if (some-condition)
continue;
// possibly some other stuff
}
just becomes:
initial condition
while (test-condition)
{
// some stuff
if (some-condition)
{
iterator-statment // (since would occur after 'continue' in for-loop above)
continue;
}
// possibly some other stuff
iterator-statement
}
Don’t see a need for “goto” here. Or perhaps you were thinking of something else? If so, could you give an example?
Not that I would do it with a while, instead – just saying that C/C++ loops are just basic flowcharts and not implemented with rigid rules on when you have to use a specific type. Even though there are conventions on which ones are best to use when. For me, I’ll pick a for-loop for situations when the for-header best encapsulates the exact information about the control of the loop. But it doesn’t preclude the possibility of writing an equivalent loop as a while or a do-while. (again, except for the scope issue of a variable like “count” declared in a for-header, like this):
for (int count = 0; count < 10; count++)
{
}
Monstre:
Ahh, I see. For some reason, I wasn’t thinking about it like that. I was thinking of keeping the iterator statement at the end, instead of putting it before all continues. This would then involve replacing any continue with a “goto iteratorstatement.” Your way definitely makes sense. Of course, in the end, using the goto statements would essentially mimic what the compiled assembly code would do, anyway (optimizations notwithstanding)
(only read the OP; I’m sure others have made this comment as well):
If you’ve achieved a position where you review the code of others, you really shouldn’t be worrying about this. Using while’s is a perfectly valid method. When the code hits the processor, it’s likely to be identical to a for. In some situations, reasonable people could find using a while more readable than a for, even if a for were possible. When you make this sort of comment as part of your review, you diminish the value of your review, and more importantly, you diminish your respect among your team.
Now, if you just say aloud every once in a while, “I sure prefer a nice for when a for is called for,” that’s a different beast. Everyone has their own pet-peeves; lord knows I have mine. But if you “correct” every while you see, you’re really doing yourself (and your team) a disservice, and in the long run, you’ll be taken less seriously than if you concentrate on real issues.
Only when printing out tables of data would our teacher encourage us to do:
for (i = 0; i < 10; i++)
{
//do stuff
}
Otherwise, it is:
i = 0;
for
{
i++;
//do stuff
}
while (i < 10);
Fun being a high school student.
Or a Do While loop. kicks self
Hey, it is a testament to my teacher, not me, okay? :smack:
A for-while loop… heh. Better get yourself a new teacher, kushiel.
Which is why I didn’t fucking say it in the review, I said it in this-here lame-assed pit thread. I will also say, however, there there are pretty damned few places where spending three lines of code to spell out the while loop is “easier to read” then one line of a for loop.
MilTan, point taken. The ? operator has got to be the least-intuitive operator used in a production OS, (I still, occasionally, have to look up which side of the : is “true”) and if you can’t do the incrementor without it, you should be in a while loop, for readability’s sake.
-lv
catsix I think you picked just about the worst possible example to illustrate your case. NEVER assume a number will always be positive and NEVER assume that % 2 numbers will always return a 0 or a 1.
The really crappy thing about software is that one tiny change somewhere in your program will cause a different function 2000 lines away to unexpectedly break. What if you decided to run it in hex to save speed and changed the %2 to %16 and missed one condition? What if your directly pushing a return value through and forgot to check if it returns -1 for error? Program specs change, and 10 seconds writing “useless” guard conditions now can save many hours of heartache.
For example, if their code was:
char * message;
if (x == 0)
message = "you typed 0
";
else if (x == 1)
message = "you typed 1
";
return message;
if x is neither 1 or 0, your going to return a pointer to random garbage which might be chucked through other convoluted processes which completely obscure the original bug. It might eventually end up as something so completely unreleated like your mouse pointer becoming corrupted and you would have no idea why this had happened. Far better to be safe than sorry.
I only wish I could have used loops in my last project. I had to write it in straight SQLPLUS, so I had to have three passes through the data in separate inline views, to gather subtotals at three levels. Because of the way the table relationships I couldn’t use the canned COMPUTE function.
You might want to go back through this thread and read the rest of my posts before you continue jumping in my shit for that. There are no 'what if’s to that example and there are no ‘program specs change’ to something that gets a thirty second mention in a two hour lecture.
I guess you also missed it when I said that they’re not wasting ten seconds, they’re wasting hours doing useless shit in their programs. Instead of writing
if(i >= 5)
{
//do stuff
}
they write
switch(i)
case 5:
case 6:
case 7:
…
case 99:
case 100: //do something
Then they’re asking me for extensions because they spent two weeks coding 3000 fucking case statements.
And now we’ve just gotten silly… anything that attempts to use modulo arithmetic to avoid a goto is going to make the code 30 times less legible, and far more likely to lead to errors (due to future code maintainers having no idea what in heck your modulo arithmetic is doing) than just having a goto in the first place.
And sticking the whole loop inside a function and then doing an if off of the function result does add at least one additional branch (the if).