LinuxQuestions.org

LinuxQuestions.org (/questions/)
-   Programming (https://www.linuxquestions.org/questions/programming-9/)
-   -   C/C++ do loops - good practice or bad? (https://www.linuxquestions.org/questions/programming-9/c-c-do-loops-good-practice-or-bad-4175413687/)

worzel1968 06-27-2012 11:53 AM

C/C++ do loops - good practice or bad?
 
I have been having some discussions on another forum about do loops.

I don't like them, and a lot guys in the beginners forum were having problems. So I was advising them not to use them, use while or for loops instead. However some of the senior members (with lots of posts)weren't agreeable. Then someone posted this:

Quote:

Bjarne Stroustrup wrote:
Quote:

In my experience, the do-statement is a source of errors and confusion. The reason is that its body is always executed once before the condition is evaluated. However, for the body to work correctly, something very much like the condition must hold even the first time through. More often then a would have guessed, I have found that condition not to hold as expected either when the program was first written and tested or later after the code preceding it has been modified. I also prefer the condition "up front where I can see it." Consequently, I tend to avoid do-statements.
I was wondering what the senior members on this forum thoughts were. I am not trying to start an inter forum war, so my posts on cplusplus beginners forum would be general and not mention names specifically.

The other thing I see beginners doing all the time is this:

Code:

while (Game != 'a' && Game != 'A' && Game != 'b' && Game != 'B' && Game != 'c' && Game != 'C')
                        {
                                cin.clear();
                                cin.ignore(1000, '\n');

                                cout << "\nInvalid input. Pick a function (a, b, c):  ";
                                cin >> Game; 
                        }

I think that use of != and && like this is really ugly.
So I have offered this as a clue to doing it better. Also using the toupper function to reduce the testing.
Code:

bool IsOperator(char symbol)
{
  switch (symbol)
  {
  case '+':
  case '-':
  case '*':
  case '/': return true;
  default:  return false;
  }
}

Finally most of this guys write C code along with cout cin and string, so I try to provide them with hints that is more of a C approach. It's understandable that they are C because they are doing school assignments.

I look forward to your replies

tronayne 06-27-2012 12:47 PM

Bad thing about do-while: it is executed at least once because the checking is done at the bottom of the loop (rather at the top as in for and while); can lead to all kinds of problems.

Nuff said.

johnsfine 06-27-2012 12:52 PM

I don't share many of Stroustrup's opinions on maintainable code. If the logic of the situation fits a do loop, I would certainly use a do loop. Occasionally, when performance really matters, I might twist the logic of the program to fit a do loop when it would not initially fit.

But most loops I encounter in real programming require processing both before and after the decision. Structured programming fanatics tend to write either:
Code:

BEFORE;
while (!DECISION) {
  AFTER;
  BEFORE;
}

or
Code:

bool flag = true;
while (flag) {
  BEFORE;
  flag = ! DECISION;
  if ( flag )
      AFTER;
}

I know from experience, constructs like that lead to misunderstandings and bugs. So most of my loops start with for (;;) which drives many structured programming fanatics crazy, but leads to more readable / maintainable code:
Code:

for (;;) {
  BEFORE;
  if ( DECISION )
      break;
  AFTER;
}

Quote:

Originally Posted by tronayne (Post 4713474)
Bad thing about do-while:

Bad programmers write bad code. Rules that try to guide them into writing mediocre code fail. Bad programmers still write bad code. But those rules can guide good programmers into writing mediocre code instead of good code.

Quote:

it is executed at least once because the checking is done at the bottom of the loop (rather at the top as in for and while); can lead to all kinds of problems.
Any competent programmer understands where the check is done based on the syntax of the loop. If that is likely to be other than where the check should have been done, the programmer isn't competent.

tronayne 06-27-2012 03:45 PM

Quote:

Any competent programmer understands where the check is done based on the syntax of the loop. If that is likely to be other than where the check should have been done, the programmer isn't competent.
Ah, yes -- but I was paraphrasing Dennis Ritchie (or, maybe, Brian Kernighan) in the do-while section of The C Programming Language, 2nd ed., pp. 63 where the example syntax appears:
Code:

do
    statement
while (expressio);

That is, the test is made after at least one pass through the body.

A perhaps useful "one-pass" may be the itoa function example presented on pp. 64 (left to the reader to explore).

Begin a fairly competent programmer -- and preferring straight-forward code that is designed to take advantage of the features of individual loop functions; e.g., while and for (where the test is at the top), personally I cannot recall ever having use do-while in any programming I've ever done simply to avoid complex testing to "get around" the design function of the loop.

Hope this helps some.

sundialsvcs 06-27-2012 10:39 PM

My suggestion is very simple: "whatever you do, make whatever you do stupendously clear."

I frankly don't care too much about how you write a loop, as long as I can instantly, "at a glance," and with 150% accuracy understand, not only what you intended to do, but that your logic is correct. I want to see comments. I want to see consistent spacing and easy visual readability. "Anything else is either human preference or fashion."

The Amazing Super-Optimizing Compiler™ will ultimately transform your source-code into "god-knows-what (and who-cares as long as it works and by-the-way it does)." So, the computer is really not the issue here. What's important is your fellow human beings ... not only your colleagues of today, but the blokes who just got back to the office after your funeral when you just got smooshed by an insane Windows programme... I mean, by a bread truck. ;)

worzel1968 06-27-2012 11:43 PM

Thanks for your replies. @johnsfine: Always a real education - thankyou. Mind you I am not sure whether I am ready to go to the lengths that you do.

My philosophy is to use while or for loops, unless a do loop is necessary. An example, if I am remembering correctly from K&R:

Code:

bool test = true;
do {
  //some code here
  // that
  // means
  // test cannot be set until down here
test = false;
}while (test==true);

Does anyone have thoughts on this code? I hate this logic as well, and would rather have a IsValidOption function with a switch inside it.

Code:

char option;
while (option != 'A' && option != 'B' && option != 'C'){
//get input

cout << "Bad option try again" << endl;
}

An even worse version is using a do loop.

The other thing is, I really don't like is single letters for variable names - even if it is just a loop counter. The problem is K&R do it all the time in their book. I would rather have a meaningful word or words as the name. Here is an example of what can happen:

I was helping someone who had a problem with a 2d array - she was using i and j for the array subscripts. I suggested that she do a find replace changing i to Row and j to Col. She almost immediately spotted the error which was [Row][Row], which should have been [Row][Col].

Finally another style question:

I like to use a very restricted Hungarian notation for my variable names. Before everyone goes Arrrrrrgh!!! #$@*# Hungarian notation - hear me out !!

Some examples:

Code:

int iDaysDateDifference = 0;          //Num of days between 2 dates +ve Date2 is later than Date1, -ve Date2 is before than Date1
unsigned short usDayOfYear = 0;      //Feb 01 is 32
class CPoint2D;                      //2Dimensional point class
double dDistance = 0.0;              //The distance between 2 CPoint2D
string strFirstName = "";            //A person's first name
const unsigned short usDaysInWeekConst = 7;      //should use #define for this
const double dEarthRadiusConst = //whatever it is. Better use of a const
double dScaleFactorArray[] = {1.0, 1.1, 1.2};
unsigned long long ullABigNumber =0;  //Don't use this one much
double m_dEast =0.0;                  // member variable in  CPoint2D class 
CPoint2D* pCentrePoint = new CPoint2D(1.0, 2.0);  //very important for pointer variables

I would have something similar for the other basic types.

I am hoping that you guys might approve of this because:

My convention is restricted to the basic types, and doesn't get carried away, which was a problem with some other notation styles;
Is well defined;
Hopefully pretty obvious.

Am interested to hear your opinions.

worzel1968 06-28-2012 12:00 AM

@sundialsvcs: Thanks for your post, I am always grateful for expert opinion.

Here is another angle on simplicity.

Eienstein once said:
Quote:

Keep it simple, simple as possible, but no simpler.
I find a lot of people trip up on the last one because they have something that is too simple for complexity of the situation that they have. However it should still be "simple as possible".

Note: My signature refers to time relativity, not necessarily to other things he said.

pan64 06-28-2012 02:19 AM

My :twocents:
I do not want to restrict myself, if the language was capable to do that way: why not use that? From the other hand I would suggest (for the beginners) to know all the language elements, all the possibilities. Otherwise they will not be able to use all the features and they may implement inefficient code.

So
Quote:

Bad thing about do-while: it is executed at least once because the checking is done at the bottom of the loop (rather at the top as in for and while); can lead to all kinds of problems.
No! good thing about do-while: it is executed at least once because the checking is done at the bottom of the loop. Use when your need it! Not knowing the language can lead to all kinds of problems.

Nominal Animal 06-28-2012 03:02 AM

I've never had any issues in C/C++ with do {} while (), while () {}, or for () {}, and I use all three all the time.

I'm just happy C/C++ does not have unless or if statements controlling execution of preceding statements. I also do not like OOXML, which exhibits the same kind of "logic" in its approach.

NevemTeve 06-28-2012 05:13 AM

Note: In non-trivial cases loops are often like this:

Code:

for (errc= 0; !errc ;) {
    errc= DoFirstStep ();
    if (errc) break/continue;

    errc= DoNextStep ();
    if (errc) break/continue;

    errc= DoLastStep ();
    if (errc) break/continue;
}

So it is actually neither pre-test nor post-test...

Edit: in more non-trivial cases:

Code:

volatile int interrupted= 0;
...

for (errc= 0, n= 0; !errc && n<nmax && !interrupted;) {
    errc= DoFirstStep ();
    if (errc) break/continue;

    errc= DoNextStep ();
    if (errc) break/continue;

    errc= DoLastStep ();
    if (errc) break/continue;

    ++n;
}


ntubski 06-28-2012 09:35 AM

Quote:

Originally Posted by worzel1968 (Post 4713815)
Does anyone have thoughts on this code? I hate this logic as well, and would rather have a IsValidOption function with a switch inside it.

Code:

char option;
while (option != 'A' && option != 'B' && option != 'C'){
//get input

cout << "Bad option try again" << endl;
}

An even worse version is using a do loop.

You have a bug in that code (because you used a while loop:p): it tests the value of option before initializing it.

johnsfine 06-28-2012 10:44 AM

Quote:

Originally Posted by worzel1968 (Post 4713815)
Does anyone have thoughts on this code?

1) It is a typical middle checked loop:
BEFORE: Get input
DECISION: Is input valid
AFTER: Report problem
As ntubski pointed out, you have treated a middle checked loop as if it were top checked, introducing the common nasty bug of depending on an uninitialized variable in a way that typically works during your testing and fails non reproducibly for the end user.

2) If you made it a middle checked loop and you cleaned it up to use a switch instead of a messy if, you hit the problem that it is hard to break out of a loop from inside a switch.
One could (as you suggest) bury the switch in a function. But usually (and in this case) it is cleaner to make the whole middle checked loop a function. Once the loop is its own function, the return instruction is the simple method to break out of the loop from inside a switch that is inside the loop.

3) I often make a loop into its own (inline) function simply to allow easily breaking out of the loop from inside a switch. But in this case, there is an even better reason to make the whole loop into a function. Getting a chunk of valid input is a logical primitive from the point of view of the procedure that uses that input. A loop to retry after invalid input is a distraction from the flow of that procedure.

Good modularization is key to good programming. A function to check whether input is valid might be a good modularization decision, but not if that is done to facilitate keeping the input retry loop in a larger function. The input retry loop should be its own function even if you also make the input validity check its own function.

worzel1968 06-29-2012 03:17 AM

ntubski & johnsfine

It's not my code and it's code I don't like, but I am wrong because i didn't post all of it (hence no initialisation), below is the example I should have posted. It shows the whole thing with several examples of the logic that I don't like:

johnsfine:
However I am guessing that it might not alter your advice (which I appreciate), and the code that I offered on the other forum was similar to what you were suggesting.

On the other forum there are lots of beginners posting code like this, and I guess that I was after some confirmation from experts, that this code is bad. Thanks to your advice I think that's what I have now, although I am not going to crow about it on the other forum.


Code:

#include <iostream>
#include <cstdlib>
#include <ctime>

using namespace std;

int main()
{
    srand (time(NULL));

    char Game;
    char Menu = 'Y';
    int num = 0;
    char PlayAgain = 'Y';

        do        {
            cout << "What would you like to use?\n"
                        "Guess the number (A)\n"
                        "Basic Calculator (B)\n"
                          "Print your text (C)\n";
            cin >> Game;

                        while (Game != 'a' && Game != 'A' && Game != 'b' && Game != 'B' && Game != 'c' && Game != 'C')
                        {
                                cin.clear();
                                cin.ignore(1000, '\n');

                                cout << "\nInvalid input. Pick a function (a, b, c):  ";
                                cin >> Game; 
                        }

                        if  (Game == 'a' || Game == 'A')
                        {
                                do
                                {
                                        cout << "Pick a number: (1-10)  ";
                                        cin >> num;

                                        while (num < 1 || num > 10 || cin.fail())
                                        {   
                                                cin.clear();
                                                cin.ignore(1000, '\n');

                                                cout << "\nInvalid input. Pick a number (1-10):  ";
                                                cin >> num; 
                                        }

                                        if (num == (rand() % 10 + 1))
                                                cout << "\nCongradulations, you guessed correct. \n";                 
                                        else
                                                cout << "\nYou guessed wrong.\n";

                                        cout << "\nWould you like to play again? (y/n)  ";
                                        cin >> PlayAgain;

                                        while (PlayAgain != 'y' && PlayAgain != 'Y' && PlayAgain != 'n' && PlayAgain != 'N')
                                        {
                                                cin.clear();
                                                cin.ignore(1000, '\n');
                                                cout << "\nInvalid input.  Would you like to play again? (y/n)  ";
                                                cin >> PlayAgain;
                                        }

                                }  while (PlayAgain == 'y' || PlayAgain == 'Y');
                                                   
                        }

                        if (Game == 'b' || Game == 'B')
                        {
                                // Game B stuff goes here
                        }

                        if (Game == 'c' || Game == 'C')
                        {
                                // Game C stuff goes here
                        }


                        cout << "\nThank you for playing\n"
                                        "\nWould you like to return to the main menu? (y/n)  ";
                        cin >> Menu;

                        while (Menu != 'y' && Menu != 'Y' && Menu != 'n' && Menu != 'N')
                        {
                                cin.clear();
                                cin.ignore(1000, '\n');
                                cout << "\nInvalid input.  Would you like to return to the main menu? (y/n)  ";
                                cin >> Menu;
                        }

                } while (Menu == 'y' || Menu == 'Y');

        cout << "Goodbye\n";
        return 0;
}


Sergei Steshenko 06-29-2012 06:37 AM

Quote:

Originally Posted by tronayne (Post 4713474)
Bad thing about do-while: it is executed at least once because the checking is done at the bottom of the loop (rather at the top as in for and while); can lead to all kinds of problems.

Nuff said.


If you know what you are doing, it's a good thing. I.e. sometimes loop body needs always to be executed at least once. For example, I work with measured data in acoustics, and conceptually it looks this way:

Code:

do{
  perform_acoustic_measurements(...);
  } while(there_are_problems_with_measured_data(...));

I can not know whether there are problems until I have measured data. In some programming languages 'until' is a keyword.

...

Managers who insist on 'for' loops can be worked around using the following idiom:

Code:

for(;;)
  {
  do_something(...);
  if(loop_exit_condition(...))
    {
    break;
    }
  }


Sergei Steshenko 06-29-2012 06:40 AM

Quote:

Originally Posted by sundialsvcs (Post 4713781)
...
The Amazing Super-Optimizing Compiler™ will ultimately transform your source-code into "god-knows-what (and who-cares as long as it works and by-the-way it does)." So, the computer is really not the issue here. ...

Alas, no. For example, ATLAS (math-atlas.sf.net) tries all kinds of code snippets in order to achieve good performance. Similar thing are done by FFTW when it calculates plans.


All times are GMT -5. The time now is 05:22 PM.