많은 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 조건문의 코드 블록을 살펴보겠습니다.
- 단일 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 이후의 명시적 코드 블록으로 확장될 수 있습니다.
- 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 명시적 코드 블록에서 사용됩니다.