programing

고투 사용을 피하고 중첩 루프를 효율적으로 끊는 방법

css3 2023. 9. 27. 18:03

고투 사용을 피하고 중첩 루프를 효율적으로 끊는 방법

는 합니다를 하는 것이 싶습니다.gotoC/C++을 하는 .

그러나 다음과 같은 코드를 감안할 때

for (i = 0; i < N; ++i) 
{
    for (j = 0; j < N; j++) 
    {
        for (k = 0; k < N; ++k) 
        {
            ...
            if (condition)
                goto out;
            ...
        }
    }
}
out:
    ...

하면 를 하지 않고 을 할 수 .goto내 가 입니다를 하는 것과 을 할 수 입니다.condition를 들어, 루프가 어,에서,나 AFAIK Go는입니다로 합니다.jmp할 수 가장 이것이 제가 생각할 수 있는 가장 효율적인 방법입니다.

그 외에 좋은 관행으로 여겨지는 것이 있습니까?고토를 사용하는 것이 나쁜 관행으로 간주된다고 말하면 제가 틀린 건가요?만약 그렇다면, 사용하는 것이 좋은 경우 중 하나가 될까요?

감사해요.

(imo) 최고의 non-goto 버전은 다음과 같습니다.

void calculateStuff()
{
    // Please use better names than this.
    doSomeStuff();
    doLoopyStuff();
    doMoreStuff();
}

void doLoopyStuff()
{
    for (i = 0; i < N; ++i) 
    {
        for (j = 0; j < N; j++) 
        {
            for (k = 0; k < N; ++k) 
            {
                /* do something */

                if (/*condition*/)
                    return; // Intuitive control flow without goto

                /* do something */
            }
        }
    }
}

기능을 짧게 유지하고 코드를 읽을 수 있도록(나보다 기능 이름을 잘 지정한 경우), 의존도를 낮게 유지하는 데 도움이 되므로 이를 분할하는 것도 좋은 방법일 것입니다.

만약 네가 그렇게 깊은 고리를 가지고 있고, 너는 반드시 끊어져야만 한다면, 나는 그렇게 생각합니다.goto최선의 해결책입니다.에는 (C가닌)닌이 .break(N)하나 이상의 루프에서 발생하는 문입니다.C가 없는 이유는 A보다 더 나쁘기 때문입니다.goto: 이것이 무엇을 하는지 알아내려면 중첩 루프를 세어야 하는데, 나중에 누군가가 와서 단절 횟수를 조정해야 한다는 것을 알아차리지 못하고 중첩 수준을 추가하거나 제거할 때 취약합니다.

.gotos눈살을 찌푸리다A를 사용.goto이것은 좋은 해결책이 아닙니다. 그것은 단지 몇 가지 악 중에서 가장 적은 것입니다.

대부분의 경우, 깊은 둥지가 있는 고리에서 벗어나야 하는 이유는 무언가를 찾고 있기 때문입니다.이 경우 (그리고 다른 여러 의견과 답변이 제시한 것처럼), 저는 중첩 루프를 고유 기능으로 이동하는 것을 선호합니다. 경우에는우 a.return내부 루프를 벗어나면 작업을 매우 깨끗하게 수행할 수 있습니다.

(함수는 항상 중간이 아니라 마지막에 되돌아와야 한다고 말하는 사람들이 있습니다.이러한 사람들은 쉬운 기능 이탈 해결책은 따라서 무효이며 검색이 자신의 기능으로 분리된 경우에도 동일한 어색한 내부 이탈 기술을 사용하도록 강요할 것입니다.개인적으로, 저는 그 사람들이 틀렸다고 생각하지만, 독자 분의 마일리지는 다를 수 있습니다.)

만약 당신이 a를 사용하지 않겠다고 고집한다면,goto그리고 하고 각 것을 할 수 한다면, , 추가적인 부울 제어 변수를 유지하고 각 중첩 루프의 제어 조건에서 중복 테스트하는 것과 같은 작업을 수행할 수 있습니다. 하지만 이는 성가시고 엉망진창일 뿐입니다.(이것은 내가 말한 가장 큰 악 중의 하나입니다.goto보다 작습니다.)

그런 것 같습니다.gotoC++ Core 가이드라인에 따라 예외적으로 사용하는 경우 중 하나입니다.

그러나 아마도 고려해야 할 또 다른 해결책은 IIFE 람다일 것입니다.제 생각에 이것은 별도의 기능을 선언하는 것보다 약간 더 우아합니다!

[&] {
    for (int i = 0; i < N; ++i)
        for (int j = 0; j < N; j++)
            for (int k = 0; k < N; ++k)
                if (condition)
                    return;
}();

이 제안에 대해 레딧의 존 맥파인애플에게 감사드립니다!

이 경우에는 사용을 피하고 싶지 않을 것입니다.goto.

일반적으로 의 사용goto피하되, 이 규칙에는 예외가 있고, 당신의 경우도 그 중 하나의 좋은 예입니다.

다음과 같은 대안을 살펴보겠습니다.

for (i = 0; i < N; ++i) {
    for (j = 0; j < N; j++) {
        for (k = 0; k < N; ++k) {
            ...
            if (condition)
                break;
            ...
        }
        if (condition)
            break;
    }
    if (condition)
        break;
}

또는:

int flag = 0
for (i = 0; (i < N) && !flag; ++i) {
    for (j = 0; (j < N) && !flag; j++) {
        for (k = 0; (k < N) && !flag; ++k) {
            ...
            if (condition) {
                flag = 1
                break;
            ...
        }
    }
}

이 중도 수 없습니다.goto

A 를 .goto뒤로 이동하지 않고 앞으로 이동하는 경우에만 허용되는 것으로 간주되며, 코드를 더 잘 읽고 이해할 수 있습니다.

하는 것은.goto양방향으로 점프하거나 변수 초기화를 우회할 수 있는 범위로 점프하는 것은 좋지 않습니다.

의 .goto:

int x;
scanf("%d", &x);
if (x==4) goto bad_jump;

{
    int y=9;

// jumping here skips the initialization of y
bad_jump:

    printf("y=%d\n", y);
}

++ 컴파일러가 하므로 C. goto다의 를 뛰어 .y. 그러나 C 컴파일러는 이것을 컴파일할 것이고 위의 코드는 인쇄를 시도할 때 정의되지 않은 동작을 호출할 것입니다.y입니다인 .goto일어나다.

의 적절한 의 또 예.goto오류 처리 중입니다.

void f()
{
    char *p1 = malloc(10);
    if (!p1) {
        goto end1;
    }
    char *p2 = malloc(10);
    if (!p2) {
        goto end2;
    }
    char *p3 = malloc(10);
    if (!p3) {
        goto end3;
    }
    // do something with p1, p2, and p3

end3:
    free(p3);
end2:
    free(p2);
end1:
    free(p1);
}

이렇게 하면 함수가 끝날 때 모든 정리가 수행됩니다.이를 대안과 비교합니다.

void f()
{
    char *p1 = malloc(10);
    if (!p1) {
        return;
    }
    char *p2 = malloc(10);
    if (!p2) {
        free(p1);
        return;
    }
    char *p3 = malloc(10);
    if (!p3) {
        free(p2);
        free(p1);
        return;
    }
    // do something with p1, p2, and p3

    free(p3);
    free(p2);
    free(p1);
}

정리가 여러 곳에서 이루어지는 경우.나중에 정리해야 할 리소스를 더 추가하는 경우 이러한 모든 위치에 정리와 이전에 얻은 리소스에 대한 정리를 추가해야 합니다.

위의 예는 C++보다 C와 더 관련이 있는데 후자의 경우 수동 정리를 피하기 위해 적절한 디스트럭터와 스마트 포인터가 있는 클래스를 사용할 수 있기 때문입니다.

Lambdas를 사용하면 로컬 스코프를 만들 수 있습니다.

[&]{
  for (i = 0; i < N; ++i) 
  {
    for (j = 0; j < N; j++) 
    {
      for (k = 0; k < N; ++k) 
      {
        ...
        if (condition)
          return;
        ...
      }
    }
  }
}();

해당 범위를 벗어나는 기능도 필요한 경우:

if (auto r = [&]()->boost::optional<RetType>{
  for (i = 0; i < N; ++i) 
  {
    for (j = 0; j < N; j++) 
    {
      for (k = 0; k < N; ++k) 
      {
        ...
        if (condition)
          return {};
        ...
      }
    }
  }
}()) {
  return *r;
}

곳에.{}아니면boost::nullopt는 "브레이크"이며 값을 반환하면 엔클로저 스코프의 값이 반환됩니다.

또 다른 접근 방식은 다음과 같습니다.

for( auto idx : cube( {0,N}, {0,N}, {0,N} ) {
  auto i = std::get<0>(idx);
  auto j = std::get<1>(idx);
  auto k = std::get<2>(idx);
}

여기서 우리는 모든 3차원에 걸쳐 반복 가능한 고리를 생성하고 그것을 1개의 깊은 중첩 고리로 만듭니다. 이제break잘 통합니다.다를 써야 .cube.

에서 이것은

for( auto[i,j,k] : cube( {0,N}, {0,N}, {0,N} ) ) {
}

좋은 일이네요.

반응성이 뛰어나야 하는 애플리케이션에서는 1차 제어 흐름 수준에서 큰 3차원 범위를 반복하는 것은 종종 좋지 않은 생각입니다.스레드를 사용할 수 있지만 스레드가 너무 오래 실행되는 문제가 발생합니다.그리고 제가 사용해 본 대부분의 3차원 대형 반복은 하위 작업 스레드를 자체적으로 사용함으로써 이점을 얻을 수 있습니다.

이를 위해 액세스하는 데이터의 종류에 따라 작업을 분류한 다음 반복을 예약하는 작업에 작업을 전달하고자 할 것입니다.

auto work = do_per_voxel( volume,
  [&]( auto&& voxel ) {
    // do work on the voxel
    if (condition)
      return Worker::abort;
    else
      return Worker::success;
  }
);

이 로 .do_per_voxel기능.

do_per_voxel한 네이키드 x엘 단위 실행 시 평면 평면 단위 작성한 다음, 풀 로 전송하고,x를 이 될 입니다.라, vox엘 단위 작업을 스캔라인 단위 작업(또는 실행 시 평면/스캔라인의 크기에 따라 평면 단위 작업까지 가능)으로 다시 작성한 다음, 스레드 풀 관리 작업 스케줄러에 차례로 파견하고, 결과 작업 핸들을 스티치하고, 미래와 같은 작업을 반환하는 시스템입니다.work작업이 완료되면 대기하거나 계속 트리거로 사용할 수 있습니다.

가끔은 고토를 이용할 때도 있습니다.또는 서브루프에 대한 함수를 수동으로 잘라냅니다.아니면 깃발을 사용해서 깊은 재귀를 break니다.또는 3층 루프 전체를 자체 기능으로 사용합니다.또는 모나드 라이브러리를 사용하여 루프 연산자를 구성합니다.아니면 예외(!)를 던져 잡을 수도 있습니다.

의 거의 모든 질문에 대한 답은 "그것은 달라요" 입니다.문제의 범위와 사용 가능한 기법의 수가 많고, 문제의 세부 사항이 해결책의 세부 사항을 바꿉니다.

대안 - 1

다음과 같은 작업을 수행할 수 있습니다.

  1. 합니다.bool의 변수isOkay = true
  2. for루프 조건, 추가 조건 추가isOkay == true
  3. 지정 /을(를) 설정합니다.isOkay = false.

그러면 루프가 정지됩니다.abool변수는 가끔 도움이 되겠지만요

bool isOkay = true;
for (int i = 0; isOkay && i < N; ++i)
{
    for (int j = 0; isOkay && j < N; j++)
    {
        for (int k = 0; isOkay && k < N; ++k)
        {
            // some code
            if (/*your condition*/)
                 isOkay = false;
        }
     }
}

대안 - 2

번째로,의 루프 이 함수 안에 은 로, , 입니다.return사용자 정의 조건이 충족될 때마다 결과를 제공합니다.

bool loop_fun(/* pass the array and other arguments */)
{
    for (int i = 0; i < N ; ++i)
    {
        for (int j = 0; j < N ; j++)
        {
            for (int k = 0; k < N ; ++k)
            {
                // some code
                if (/* your condition*/)
                    return false;
            }
        }
    }
    return true;
}

for loops를 함수로 분할합니다.이제 각 루프가 실제로 무엇을 하고 있는지 볼 수 있기 때문에 상황을 훨씬 쉽게 이해할 수 있습니다.

bool doHerpDerp() {
    for (i = 0; i < N; ++i) 
    {
        if (!doDerp())
            return false;
    }
    return true;
}

bool doDerp() {
    for (int i=0; i<X; ++i) {
        if (!doHerp())
            return false;
    }
    return true;
}

bool doHerp() {
    if (shouldSkip)
        return false;
    return true;
}

그 외에 좋은 관행으로 여겨지는 것이 있습니까?고토를 사용하는 것이 나쁜 관행으로 간주된다고 말하면 제가 틀린 건가요?

goto오용되거나 남용될 수 있지만, 당신의 예에서는 두 가지 중 어떤 것도 보이지 않습니다. 것은 다로 하게 표현됩니다.goto label_out_of_the_loop;.

많은 것을 사용하는 것은 나쁜 관행입니다.goto가 는 .goto그 자체가 당신의 코드를 나쁘게 만듭니다.당신이 코드를 따라 하기 어렵게 뛰어다니는 것이 나쁜 것입니다.그러나 중첩 루프에서 한 번의 점프가 필요한 경우에는 정확히 해당 용도로 만들어진 도구를 사용하지 않는 것이 좋습니다.

허공으로 꾸며낸 비유를 사용하는 방법:여러분이 과거에는 벽에 못을 박는 것이 유행이었던 세상에 살고 있다고 상상해 보세요.최근에는 스크루 드라이버를 사용하여 벽에 나사를 뚫는 것이 더 유행이 되었고 해머는 완전히 유행에 뒤떨어졌습니다.이제 벽에 못을 박아야 한다는 것을 생각해 보세요.그렇게 하기 위해서 망치를 사용하는 것을 자제해서는 안 되지만, 나사 대신에 정말로 벽에 못이 필요한지 자문해 보는 것이 좋을 것입니다.

명확하지 를 대비하여 (하여) 가합니다.goto하여 깊은 을 모두 수 다 ;)

한 가지 가능한 방법은 상태를 나타내는 변수에 부울 값을 할당하는 것입니다.이 상태는 나중에 코드의 다른 용도로 "IF" 조건문을 사용하여 테스트할 수 있습니다.

비주얼 스튜디오 2017의 릴리스 모드에서 두 옵션을 모두 컴파일하는 효율성에 대한 당신의 코멘트는 정확히 동일한 어셈블리를 생성합니다.

for (int i = 0; i < 5; ++i)
{
    for (int j = 0; j < 5; ++j)
    {
        for (int k = 0; k < 5; ++k)
        {
            if (i == 1 && j == 2 && k == 3) {
                goto end;

            }
        }
    }
}
end:;

깃발을 들고.

bool done = false;
for (int i = 0; i < 5; ++i)
{
    for (int j = 0; j < 5; ++j)
    {
        for (int k = 0; k < 5; ++k)
        {
            if (i == 1 && j == 2 && k == 3) {
                done = true;
                break;
            }
        }
        if (done) break;
    }
    if (done) break;
}

둘 다 생산합니다.

xor         edx,edx  
xor         ecx,ecx  
xor         eax,eax  
cmp         edx,1  
jne         main+15h (0C11015h)  
cmp         ecx,2  
jne         main+15h (0C11015h)  
cmp         eax,3  
je          main+27h (0C11027h)  
inc         eax  
cmp         eax,5  
jl          main+6h (0C11006h)  
inc         ecx  
cmp         ecx,5  
jl          main+4h (0C11004h)  
inc         edx  
cmp         edx,5  
jl          main+2h (0C11002h)    

그래서 이득이 없습니다.현대식 c++ 컴파일러를 사용하는 경우 또 다른 옵션은 람다로 래핑하는 것입니다.

[](){
for (int i = 0; i < 5; ++i)
{
    for (int j = 0; j < 5; ++j)
    {
        for (int k = 0; k < 5; ++k)
        {
            if (i == 1 && j == 2 && k == 3) {
                return;
            }
        }

    }
}
}();

다시 이것은 정확히 같은 조립물을 만들어냅니다.개인적으로 나는 당신의 예에서 고토를 사용하는 것은 충분히 받아들일 만하다고 생각합니다.다른 사람에게 무슨 일이 일어나고 있는지는 분명하며, 더 간결한 코드를 만듭니다.거의 틀림없이 람다도 마찬가지로 간결합니다.

특정한

IMO, 이 구체적인 예에서, 루프 간의 공통 기능에 주목하는 것이 중요하다고 생각합니다.의 예가 (이제 당신의 예가 여기서 반드시 말 그대로는 아니라는 것을 압니다). 하지만 잠시만 참아주세요) 각 루프가 반복될 때마다N다음과 같이 코드를 재구성할 수 있습니다.

int max_iterations = N * N * N;
for (int i = 0; i < max_iterations; i++)
{
    /* do stuff, like the following for example */
    *(some_ptr + i) = 0; // as opposed to *(some_3D_ptr + i*X + j*Y + Z) = 0;
    // some_arr[i] = 0; // as oppose to some_3D_arr[i][j][k] = 0;
}

자, 중요한 것은, 모든 루프는, 어쨌든, if-goto 패러다임을 위한 통사당일 뿐이라는 것을 기억하는 것이 중요합니다.함수가 결과를 반환하도록 해야 한다는 다른 사람들의 의견에 동의하지만, 그렇지 않을 수도 있는 위와 같은 예를 보여주고 싶었습니다.물론 위 내용을 코드 리뷰로 표시하고 싶지만, 위 내용을 고투로 대체했다면 잘못된 방향으로 가는 단계라고 생각합니다. (참고 - 원하는 데이터 유형에 안정적으로 맞출 수 있는지 확인하십시오.)

장군

자, 일반적인 답으로서, 루프의 출구 조건은 문제의 게시물처럼 매번 같지 않을 수 있습니다.일반적으로 필요 없는 작업을 루프(다중화 등)에서 최대한 많이 꺼내야 하지만 컴파일러는 점점 더 스마트해지고 있지만 효율적이고 읽을 수 있는 코드를 대신할 수는 없습니다.

/* matrix_length: int of m*n (row-major order) */
int num_squared = num * num;
for (int i = 0; i < matrix_length; i++)
{
    some_matrix[i] *= num_squared; // some_matrix is a pointer to an array of ints of size matrix_length
}

글을 *= num * num는 더 ).().따라서 위의 기능을 수행하는 이중 또는 삼중으로 중첩된 루프는 코드뿐만 아니라 IMO가 당신의 부분에 깨끗하고 효율적인 코드를 작성하는 데에도 도움이 될 것입니다.에서,에서를 수 .*(some_3D_ptr + i*X + j*Y + Z) = 0;가 를(를) 할 수 믿습니까?i*X그리고.j*Y?

bool check_threshold(int *some_matrix, int max_value)
{
    for (int i = 0; i < rows; i++)
    {
        int i_row = i*cols; // no longer computed inside j loop unnecessarily.
        for (int j = 0; j < cols; j++)
        {
            if (some_matrix[i_row + j] > max_value) return true;
        }
    }
    return false;
}

헉! 왜 우리는 STL에서 제공하는 수업이나 부스트 같은 도서관을 이용하지 않는 거지?(여기서 낮은 수준/높은 성능 코드를 수행해야 합니다.)복잡해서 3D 버전도 못 썼어요.비록 우리가 손으로 최적화된 무언가를 가지고 있지만, 당신의 컴파일러가 허락한다면 #pragma unroll이나 비슷한 전처리기 힌트를 사용하는 것이 더 나을 수 있습니다.

결론

일반적으로 사용할 수 있는 추상화 수준이 높을수록 좋습니다. 하지만 정수로 구성된 1차원 행 주 순서 행렬을 2차원 배열로 얼라이징하면 코드 흐름을 이해/확장하기가 더 어려워진다고 하면 그럴 가치가 있습니까?마찬가지로, 그것은 또한 어떤 것을 그것만의 기능으로 만들기 위한 지표가 될 수도 있습니다.이러한 예들을 고려할 때, 여러분이 각기 다른 장소에서 다른 패러다임이 요구되고, 그것을 알아내는 것이 프로그래머로서의 여러분의 일이라는 것을 알 수 있기를 바랍니다.위의 내용에 열광하지 말고, 그 내용이 무엇을 의미하는지, 어떻게 사용하는지, 그리고 언제 그들이 요청을 받는지, 그리고 가장 중요한 것은 코드베이스를 사용하는 다른 사람들도 자신이 무엇인지 알고, 그것에 대해 거리낌이 없도록 해야 합니다.행운을 빕니다.

bool meetCondition = false;
for (i = 0; i < N && !meetCondition; ++i) 
{
    for (j = 0; j < N && !meetCondition; j++) 
    {
        for (k = 0; k < N && !meetCondition; ++k) 
        {
            ...
            if (condition)
                meetCondition = true;
            ...
        }
    }
}

코드를 리팩터링할 수 있는 방법을 알려주는 훌륭한 답변들이 이미 몇 개 있으므로 반복하지 않겠습니다. 더 는 그것이 ( 한 만약 가 해당 된 적이 도 있습니다. 문제는 그것이 우아하지 않은지 여부입니다. (좋아요, 한 가지 개선점을 제안하겠습니다: 도우미 함수가 한 함수의 본문에서만 사용되도록 의도된 적이 있다면, 이를 선언함으로써 옵티마이저를 도와줄 수도 있습니다.static inline상처받을 수 없습니다.그러나 이전의 답변에 따르면 람다를 사용할 때 현대 컴파일러는 그러한 힌트를 필요로 하지 않습니다.)

저는 질문의 틀에 조금 도전해 보겠습니다.다 금기시하는 goto이라고 생각합니다 이것은 원래의 목적을 상실한 것이라고 생각합니다.고투는 유해한 으로 간주됩니다"라고 때, 그가 Edsger Dijkstra가 되며"때,다의 "절제되지 : 은"용.go to현재 프로그램 상태와 어떤 조건이 현재 참이어야 하는지에 대해 (그가 선호하는) 재귀적 함수 호출 또는 반복 루프(그가 수락한)의 제어 흐름과 비교하여 공식적으로 추론하기가 너무 어렵습니다.그는 이렇게 결론지었습니다.

go to지금 상태에서 진술하는 것은 너무 원시적이고, 프로그램을 엉망으로 만드는 것은 너무 지나친 초대입니다.그 사용을 억제하는 것으로 간주되는 조항들을 이해하고 인정할 수 있습니다.저는 언급된 조항들이 모든 필요를 충족시킬 것이라는 의미에서 완전하다고 주장하지는 않지만, 어떤 조항이 제안되든 (낙태 조항과 같은) 그것들은 도움이 되고 관리 가능한 방식으로 과정을 묘사하기 위해 프로그래머 독립적인 좌표 체계가 유지될 수 있다는 요건을 충족해야 합니다.

C 용도를 교배하는 으로 간주되는 조항"이 C 은 "" 입니다.break꼬리표 훨씬 더 제한된 구문은 다음과 같습니다같은 일 수 .break 2 continue;중첩 루프의 두 수준에서 분리하여 해당 수준을 포함하는 루프 상단에서 재개합니다.은 C 지 C입니다에 않는 입니다.breakDijkstra가 하고 싶었던 일: 프로그래머들이 머릿속으로 추적할 수 있는 프로그램 상태에 대한 간결한 설명을 정의하거나 정적 분석기가 다루기 쉽다고 생각할 것입니다.

goto이와 같은 구조물은 단순히 레이블로 이름을 바꾼 것입니다.남은 문제는 컴파일러와 프로그래머가 당신이 이 방법으로만 사용할 것이라는 것을 반드시 모를 필요는 없다는 것입니다.

상태가입니다.goto Dijkstra다와 해 볼 수 있습니다.// We have done foo to every element, or encountered condition and stopped.그것은 인간의 문제를 완화할 것이고, 정적 분석기는 잘 작동할 것입니다.

가장 하는 입니다.return그 기능에서.

의 것입니다.goto예를 들어, 하지만 당신이 또 다른 것을 가지는 것을 피할 수 있는 거대한 이점을 가지고 있습니다.goto토론.

단순 의사 코드:

bool function (void)
{
  bool result = something;


  for (i = 0; i < N; ++i) 
    for (j = 0; j < N; j++) 
      for (k = 0; k < N; ++k) 
        if (condition)
          return something_else;
  ...
  return result;
}

입니다에서 할 수 입니다.bool완전히enum두 가지 이상의 시나리오를 접하게 되면.다로는 할수goto읽기 쉬운 방법으로 라벨을 입니다.여러 개의 고토와 여러 개의 라벨을 사용하기 시작하는 순간은 스파게티 코딩을 수용하는 순간입니다.네, 아래쪽으로 가지치기만 해도 읽고 유지하는 것이 예쁘지 않습니다.

특히 루프에 대해 3개의 중첩된 경우 코드를 여러 함수로 분할해야 한다는 것을 나타내는 것일 수 있습니다. 그러면 이 전체 논의는 관련이 없을 수도 있습니다.

언급URL : https://stackoverflow.com/questions/50530113/how-to-avoid-use-of-goto-and-break-nested-loops-efficiently