Go 기본 13 - Go 언어 코드 블록 및 범위 이해

많은 Gophers가 바둑 퀴즈 게임을 좋아합니다
. 이 퀴즈는 모든 사람의 언어 이해 깊이를 테스트합니다. 답변자는 프로그램 출력 결과를 제공할 뿐만 아니라 왜 그러한 결과가 출력되는지 설명해야 합니다.

이 글에서는 먼저 바둑 퀴즈도 플레이할 예정입니다. 이 퀴즈의 코드는 다음과 같습니다.

func main() {
    
    
if a := 1; false {
    
    
} else if b := 2; false {
    
    
} else if c := 3; false {
    
    
} else {
    
    
println(a, b, c)
}
}

두 가지 답변 옵션이 있습니다.

A:1 2 3
B:无法通过编译

이 코드를 처음 봤을 때 내 첫인상은 선택 B인 것 같았습니다. 이 코드를 컴파일하고 실행할 수 있습니까? 컴파일러는 마지막 else 분기 문의 println(a, b, c) 문에 대해 "정의되지 않은 b"와 같은 오류를 보고합니까?

그런데 실제로 실행해 보니 옵션 A가 맞다는 걸 알았습니다. 그래서 나는 이 코드를 질문으로 분석하고 결론에 도달했습니다. Go 코드 블록과 범위 규칙을 깊이 이해해야만 이 코드가 "1 23"을 출력하는 실제 이유를 이해할 수 있습니다.

이번 글에서는 위의 예시를 중심으로 Go 코드 블록(code block)과 스코프(scope) 규칙을 이해하고, 이러한 규칙을 이해하는 것은 정확하고 가독성이 높은 코드를 작성하는 데 도움이 됩니다.

Go 코드 블록 및 범위 소개

Go 언어의 코드 블록은 한 쌍의 중괄호 안에 포함된 선언 및 명령문이며 코드 블록은 중첩을 지원합니다. 중괄호 쌍 사이에 명령문이 없으면 코드 블록을 빈 코드 블록이라고 합니다.

코드 블록은 코드 실행 흐름의 기본 단위로, 코드 실행 흐름은 항상 한 코드 블록에서 다른 코드 블록으로 점프합니다.

Go 언어에는 두 가지 유형의 코드 블록이 있는데, 하나는 함수의 함수 본문, for 루프의 루프 본문과 같이 코드에서 직관적으로 볼 수 있는 여러 개의 중괄호로 묶인 명시적인 코드 블록입니다. if 문의 특정 분기 등 :

func Foo() {
    
    
// 这里是显式代码块,包裹在函数的函数体内
// ...
for {
    
    
// 这里是显式代码块,包裹在for循环体内
// 该代码块也是嵌套在函数体显式代码块内部的代码块
// ...
}
if true {
    
    
// 这里是显式代码块,包裹在if语句的true分支内
// 该代码块也是嵌套在函数体显式代码块内部的代码块
// ...
}
}

다른 유형은 중괄호가 없는 암시적 코드 블록입니다. Go 사양은 다음 유형의 암시적 코드 블록을 정의합니다.

● 유니버스 코드 블록: 모든 Go 소스 코드는 암시적 코드 블록에 있으며, 이는 모든 Go 코드의 가장 바깥쪽 레이어에 있는 중괄호 쌍과 동일합니다.

● 패키지 코드 블록: 각 패키지에는 패키지의 모든 Go 소스 코드가 포함된 패키지 코드 블록이 있습니다.

● 파일 코드 블록: 각 파일에는 파일의 모든 Go 소스 코드가 포함된 파일 코드 블록이 있습니다.

● 각 if, for 및 switch 문은 자체 암시적 코드 블록에 있는 것으로 간주됩니다.

● switch 또는 select 문의 각 절은 암시적 코드 블록으로 간주됩니다.
Go 식별자의 범위는 코드 블록을 기반으로 정의되며 범위 지정 규칙은 식별자가 유효한 코드 블록을 설명합니다. 다음은 식별자 범위 지정 규칙입니다.

● 미리 정의된 식별자인 make, new, cap, len 등의 범위는 유니버스 블록입니다.

● 최상위 수준(함수 외부)에 선언된 상수, 유형, 변수 또는 함수(메서드는 아님)에 해당하는 식별자의 범위는 패키지 코드 블록입니다. 예를 들어 패키지 수준 변수와 패키지 수준 상수의 식별자 범위는 패키지 코드 블록입니다.

● Go 소스 파일에서 가져온 패키지 이름의 범위는 파일 코드 블록입니다.

● 메소드 수신자(수신자), 함수 매개변수 또는 반환 값 변수에 해당하는 식별자의 범위는 함수 본문(명시적 코드 블록)이지만 함수 본문의 중괄호로 명시적으로 래핑되지는 않습니다.

● 함수 내에서 선언된 상수 또는 변수에 해당하는 식별자의 범위는 상수 또는 변수 선언문의 끝에서 시작하여 가장 안쪽 포함 블록의 끝에서 끝납니다.

● 함수 내부에 선언된 유형 식별자의 범위는 유형 정의의 식별자에서 시작하여 가장 안쪽 포함 블록의 끝에서 끝납니다. 아래 코드 예제를 참조하세요.

func Foo() {
    
    
{
    
     // 代码块1
 // 代码块1是包含类型bar标识符的最里面的那个包含代码块
type bar struct {
    
    } // 类型标识符bar的作用域始于此
{
    
     // 代码块2
// 代码块2是包含变量a标识符的最里面的那个包含代码块
a := 5 // a的作用域始于此
{
    
    
//...
}
// a的作用域止于此
}
// 类型标识符bar的作用域止于此
}
}

코드 블록과 범위 지정 규칙은 코드 분석을 위한 기초를 제공합니다.

if 조건부 제어문의 코드 블록


본 글의 시작 부분에 나오는 바둑 퀴즈를 분석하기 위해서는 if 조건 제어문의 코드 블록 배포 규칙에 대한 전반적인 이해가 필요합니다 . 다음으로 세 가지 유형의 if 조건문의 코드 블록을 살펴보겠습니다.

  1. 단일 if 유형
    가장 일반적인 if 문 유형은 단일 if 유형입니다.
if SimpleStmt; Expression {
    
    
...
}

코드 블록 규칙에 따르면 if 문 자체는 암시적 코드 블록에 있으므로 단일 if 유형 제어 문에는
암시적 코드 블록과 명시적 코드 블록이라는 두 개의 코드 블록이 있습니다. 이해를 돕기 위해 위 코드에 대해 등가 변환을 수행
하고 코드 블록의 시작점과 끝점을 추가합니다.

{
    
     // 隐式代码块开始
SimpleStmt
if Expression {
    
     // 显式代码块开始
...
} // 显式代码块结束
} // 隐式代码块结束

if 뒤에 중괄호 쌍으로 감싼 명시적 코드 블록이 SimpleStmt가 위치한 암시적 코드 블록 내에 중첩되어 있음을 알 수 있습니다. 이것이 SimpleStmt의 짧은 변수 선언 형식을 사용하여 정의된 변수가 if 문의 명시적 코드에 포함될 수 있는 이유입니다. .블록에 사용됩니다.

예를 들어 다시 설명해 보겠습니다.

func Foo() {
    
    
if a := 1; true {
    
    
fmt.Println(a)
}
}
等价变换为:
func Foo() {
    
    
{
    
    
a := 1
if true {
    
    
fmt.Println(a)
}
}
}

동등하게 변환된 코드에서는 위의 Go 식별자 범위 규칙의 함수 본문에 있는 변수 식별자 범위에 대한 설명에 따라 변수 a의 범위를 if 내의 명시적 코드 블록으로 확장할 수 있으므로 if에서 적법합니다. 명시적인 코드 블록 내에서 a를 사용하세요.
2. if { } else { } 유형
if {} else {} 유형 제어문의 코드 블록 분포를 살펴보겠습니다.

if Simplestmt; Expression {
    
    
...
} else {
    
    
...
}
分析逻辑同上面的if型:对上面的伪代码进行一个等价变换并给出代码块起始点和结
束点的标注。
{
    
     // 隐式代码块开始
SimpleStmt
if Expression {
    
     // 显式代码块1开始
...
// 显式代码块1结束
} else {
    
     // 显式代码块2开始
...
} // 显式代码块2结束
} // 隐式代码块结束

if {} else {} 유형 제어문에는 세 개의 코드 블록이 있는 것을 볼 수 있습니다. 단일 if 유형의 두 코드 블록 외에도 else에 의해 도입된 명시적 코드 블록(명시적 코드 블록 2)도 있습니다. 다시 한 번 예를 사용하여 설명합니다.

func Foo() {
    
    
if a,b := 1, 2; false{
    
    
fmt.Println(a)
} else {
    
    
fmt.Println(b)
}
}
等价变换为:
func Foo() {
    
    
{
    
    
a, b := 1, 2
if false {
    
    
fmt.Println(a)
} else {
    
    
fmt.Println(b)
}
}
}

SimpleStmt에 선언된 변수의 범위는 else 이후의 명시적 코드 블록으로 확장될 수 있습니다.

  1. if {} else if {} else {} 유형
    마지막으로 가장 복잡한 if {} else if {} else {} 유형을 살펴보겠습니다. 이는
    Go 퀴즈 코드의 시작 부분에 사용되는 if 제어문의 유형 이기도 합니다. 이 기사:
if SimpleStmt1; Expression1 {
    
    
...
} else if SimpleStmt2; Expression2 {
    
    
...
} else {
    
    
...
}

위의 의사코드에 대해 등가 변환을 수행하고 코드 블록의 시작점과 끝점을 표시하며 결과는 다음과 같습니다
.

{
    
     // 隐式代码块1开始
SimpleStmt1
if Expression1 {
    
     // 显式代码块1开始
...
} else {
    
     // 显式代码块1结束;显式代码块2开始
{
    
     // 隐式代码块2开始
SimpleStmt2
if Expression2 {
    
     // 显式代码块3开始
...
} else {
    
     // 显式代码块3结束;显式代码块4开始
...
} // 显式代码块4结束
} // 隐式代码块2结束
} // 显式代码块2结束
} // 隐式代码块1结束

이 유형에서는 총 2개의 암시적 코드 블록과 4개의 명시적 코드 블록을 식별했습니다.
위의 규칙과 분석을 바탕으로 이 글의 시작 부분에 있는 바둑 퀴즈를 살펴보겠습니다.

package main
func main() {
    
    
if a := 1; false {
    
    
} else if b := 2; false {
    
    
} else if c := 3; false {
    
    
} else {
    
    
println(a, b, c)
}
}

if {} else if {} else {} 유형 제어문을 적용한 것입니다. 우리의 분석 아이디어에 따르면
이 코드는 동등하게 변환될 수 있습니다.

func main() {
    
    
{
    
    
a := 1
if false {
    
    
 } else {
    
    
{
    
    
b := 2
if false {
    
    
} else {
    
    
{
    
    
c := 3
if false {
    
    
} else {
    
    
println(a, b, c)
}
}
}
}
}
}
}

확장된 코드는 모든 것을 한눈에 명확하게 해줍니다. 세 가지 변수 a, b, c는 서로 다른 수준의 암시적 코드 블록에 위치하며 , 이 세 변수의 범위에 따라 변수 a, b, c는 모두
가장 깊은 else 명시적 코드 블록에서 사용됩니다.

추천

출처blog.csdn.net/hai411741962/article/details/132759719