[GOLANG] 02. GO 언어의 특징과 문법 간단 정리
0. Go의 특징
- 고수준 언어
- 정적 컴파일 언어
- 강 타입 언어
- GC(Garabage Collector) 有
일반적으로 백엔드 서버, 시스템 프로그래밍에 활용
구조체와 메서드 지원, 상속 지원 X, 인터페이스 제공, 익명 함수(함수 리터럴) 제공, GC 제공, 포인터 제공, 제네릭 X, 네임스페이스가 아닌 패키지 단위로 코드 분리
1. Go 코드 실행 방식
- 폴더 생성: 패키지는 폴더 단위로, 폴더명에 따라 관리됨
.go
파일 생성 및 작성- Go
모듈
생성
생성방법1
go mod init goproject/hello
- 빌드(OS 및 아키텍처 별 지정 가능)
1
GOOS=linux GOARCH=amd64 go build
- 실행
2. 문법
변수선언
1
2
3
4
var a int = 10
var b int
var c = 4 // 타입 생략 형태로 할당 연산자 우측의 타입으로 강제 지정됨
d := 4 // 일반적인 형태. type notation은 없어도 되지만, 강타입.
텍스트 입출력(C/JAVA와 비슷)
fmt
패키지 활용(표준 입출력 스트림을 쉽게. FIFO)
Printf(서식 문자열, 인수1, 인수2, …)
1
2
3
4
5
import "fmt"
fmt.Print() // 개행없이 원래 타입으로 출력
fmt.Println() // 개행하면서 원래 타입으로 출력
fmt.Printf() // 개행없이 원하는 포맷으로 출력 (%d, %f, %s, ...)
func Scan(a ..interface{}) (n int, err error)
func Scanf(format string, a …interface{}) (n int, err error) func Scanln(a …interface{}) (n int, err error)
1
2
3
4
5
import "fmt"
fmt.Scan() // 일반적인 입력 함수 -> 첫 번째 변수부터 맞지 않다면 err 출력
fmt.Scanln() // enter키 입력해 입력 종료
fmt.Scanf() // 포맷 스트링 활용
fflush 처럼 입력할 때 에러 발생하면 stdin stream 지우기.
연산자
산술연산자: 사칙, 비트, 시프트
C/C++, JAVA와 거의 동일
[], ., &, *, …, :, <- 등은 후술 예정
함수
- 함수 키워드
- 함수명
- 매개변수
- 반환 타입
- 함수 코드 블록
1
2
3
func Add(a int, b int) int {
return a+b
}
아래와 같은 multi-return function도 가능
1
2
3
4
5
6
7
8
9
10
func Dividie(a, b int) (int, bool) {
if b ==0 {
return 0, false
}
return a / b, true
}
func main() {
c, success := Divide(9, 3)
}
당연히 재귀 호출도 가능
상수
Go의 상수 type: boolean, rune, 정수/실수/복소수, 문자열
1
const ConstValue int = 10
- 변하면 안 되는 값에 사용
```go package main
import “fmt”
func main() { const PI1 float64 = 3.141592 … }
1
2
3
4
5
6
위 예시에서 PI1를 변경, 변수에 다른 값을 할당(대입)하려고 하면 컴파일 에러 발생함.
2. 코드값을 통해 숫자에 의미를 부여할 때 사용
```go
const Pig int = 0
const Cow int = 1
...
위와 같은 경우는 iota로 간편하게 열거값 활용 가능
1
2
3
4
5
const (
Red int = iota // 0
Blue int = iota // 1
Green int = iota // 2
)
첫 줄만 iota 명시해도 상관없음. 이후 동일한 연산에 따라 뒤따라오는 변수들의 값이 정의됨.
type 없는 상수도 정의 가능한데, 변수에 복사될 때 타입이 정해짐.
당연히, 상수는 리터럴과 같이 취급하기에, 컴파일 타임에 구문들이 변환됨.(const 관련된 표현식 계산에 CPU 자원 사용 X)
Flow Control
- if문
1 2 3
if {...} else if {...} else {...}
논리 연산자는 short-circuit 방식.
특이하게, if문에 초기문을 정의할 수 있음.1 2 3 4 5 6 7 8
func getMyAge() (int, bool) { return 33, true } func main() { if age, ok := getMyAge(); ok && age < 20 { ... } }
단, 위에서 정의된 age, ok는 if-else 조건문 코드 블록을 벗어나면 소멸되는 local variables.
- swtich문 => const 열거값(w/ iota)와 연관하여 사용 시, 활용도 높음
주의할 점: break 없어도 case하나만 실행됨. C처럼 통과하고 싶다면? fallthrough 사용. - for문
1 2 3 4 5
func main() { for i := 0; i < 10; i++ { fmt.Print(i, ", ") } }
배열
1
var arr [5]float64 // 실수 자료형 5개의 element로 구성된 array
배열 선언 시 당연히 개수는 항상 상수(C언어와 동일, const로 선언된 상수도 가능)
1
2
3
4
5
6
7
8
9
func main() {
nums := [...]int{10, 20, 30, 40, 50}
nums[2] = 300
for i := 0; i < len(nums); i++ {
fmt.Println(nums[i])
}
}
위처럼 for문 돌려도 되지만,
1
2
3
4
5
6
7
func main() {
var t [5]float64 = [5]float64{24.0, 25.9, 27.8, 26.9, 26.2}
for i, v := range t{
fmt.Println(i, v)
}
}
range 키워드 활용해 배열 요소 순회 가능 (like python’s in이나 enumerate. 인덱스와 요소를 multi-return)
C처럼 배열은 adjacent-index를 가지고, 다차원 배열을 지원함.
구조체와 포인터
1
2
3
4
5
6
7
8
9
10
11
12
type Student struct {
Name string
Class int
No int
Score float64
}
func main(){
var a Student // 모든 구조체 내의 필드가 기본값으로 초기화(다른 설정 X)
var a Student = Student{"홍길동", 3, 23, 56.7} // 정의한 값들로 초기화
fmt.Printf("%d", a.No) // . 접근자를 활용해 구조체 내 필드에 접근
}
구조체 내 구조체도 가능, 8bytes 기준 메모리 정렬 및 메모리 패딩 고려한 필드 순서 설정 등 C언어와 동일
포인터: 메무리 주소를 값으로 갖는 타입
1
2
var p *int // 기본값은 nil
p = &a
포인터를 쓰는 이유? : 구조체 복사해서 함수 호출하면 비용이 너무 큼(call by value) => call by ref 방식을 활용해 주소를 불러와서 연산에 활용. 구조체 전부가 복사되지 않고 연산에 활용 가능
인스턴스: 메모리에 존재하는 데이터의 실체
=> 구조체 포인터를 함수 매개변수로 받는다는 말은 구조체 인스턴스로 입력을 받겠다는 의미
클래스 대신 구조체를 채택한 이유 by Gemini
단순성, 조합(Composition), 그리고 인터페이스(Interface)라는 핵심 키워드로 요약할 수 있습니다. 이는 기존 객체 지향 언어의 복잡성을 피하고, 더 명료하며 실용적인 프로그래밍을 지향하기 위한 의도적인 설계 결정입니다.
- 단순성과 명료성: 데이터와 행위의 분리
Go는 복잡한 클래스 문법 대신, 순수하게 데이터 필드의 묶음인 구조체(Struct)와 특정 타입에 연결된 함수인 메서드(Method)를 사용합니다. 이는 데이터와 행위를 의도적으로 분리하여 코드의 역할을 명확히 구분합니다. 클래스처럼 데이터와 기능이 하나의 거대한 단위로 강하게 결합하는 대신, 필요한 데이터 구조를 정의하고 그 구조체에 필요한 메서드를 연결하는 방식을 채택했습니다. 이로 인해 코드의 구조가 단순해지고, 각 부분의 역할을 이해하고 예측하기가 쉬워집니다.- 상속 대신 조합 (Composition over Inheritance)
Go는 전통적인 객체 지향의 상속(Inheritance)을 의도적으로 배제했습니다. 상속은 부모와 자식 클래스 간의 강한 결합을 만들어 코드의 유연성을 해치고, 부모 클래스의 변경이 모든 자식 클래스에 예기치 않은 영향을 미치는 ‘취약한 기반 클래스 문제’를 야기할 수 있기 때문입니다.
대신 Go는 조합(Composition)을 핵심 원칙으로 삼습니다. 이는 한 구조체가 다른 구조체를 필드로 포함하는 방식(Embedding)으로, 필요한 기능을 ‘물려받는’ 것이 아니라 ‘포함하여’ 사용합니다. 이 접근법은 코드의 재사용성을 높이고, 각 구성 요소 간의 의존성을 낮춰 훨씬 더 유연하고 확장성 있는 설계를 가능하게 합니다.- 인터페이스 기반 다형성과 덕 타이핑 (Duck Typing)
Go는 클래스 없이도 인터페이스(Interface)를 통해 강력하고 유연한 다형성을 구현합니다. Go의 인터페이스는 어떤 메서드들을 가져야 하는지에 대한 ‘행위의 규약’만을 정의합니다. 어떤 구조체든 이 인터페이스가 요구하는 메서드들을 모두 구현하면, 명시적인 선언(implements) 없이도 해당 인터페이스 타입으로 인정됩니다.
이러한 덕 타이핑(Duck Typing) 방식은 타입 간의 결합을 최소화합니다. 구현체가 특정 클래스 계층에 묶일 필요 없이 오직 필요한 행위를 만족하는지만 중요하기 때문에, 코드의 의존성이 낮아지고 테스트와 확장이 매우 용이해집니다.