Golang 기초 (4) : fmt 패키지에 대하여

vamos_eon·2021년 12월 2일
1

Golang 기초 

목록 보기
4/14
post-thumbnail

안녕하세요, 주니어 개발자 Eon입니다.

이번 포스트는 Golang의 가장 기본적인 표준 라이브러리인 fmt 패키지를 다루겠습니다.

📌 fmt package
  📍 변수
  📍 변수의 출력
  📍 변수의 입력
  📍 문자열 반환

📌 연산자
  📍 사칙 연산
  📍 비트 연산
  📍 시프트 연산

📌 fmt package

fmt는 Formatted I/O(Input / Output)를 구현한 패키지입니다.
C언어의 표준 입출력 라이브러리인 stdio.h의 역할이라고 보시면 됩니다.
fmt 패키지를 import함으로써 입출력을 가능하게 합니다.
처음 Golang 설치를 확인하기 위해 Hello World!!를 출력했었습니다.

package main // 해당 프로젝트의 시작점을 알리며, 항상 main으로 존재해야 함

import "fmt" // fmt 패키지를 임포트하여 사용할 수 있는 상태로 만듦

func main() { // 해당 패키지의 가장 처음에 실행되는 함수이며, 항상 main으로 존재해야 함
	fmt.Println("Hello World!!") // fmt 패키지의 Println 함수를 사용해, Hello World!!를 출력하고 개행함
}

이와 같이 fmt 패키지는 입력과 출력을 가능하게 하며 모든 실행의 결과물을 출력할 수 있게 됩니다.
컴퓨터의 연산과 계산 결과 등을 출력할 수 있습니다.
1 + 1의 결과 2를 결과물로 볼 수 있게 되는 것입니다.
딱히 하나도 놀라울 것이 없지만 fmt 패키지가 없다면 우리는 우리의 코드가 제대로 작성된 것인지 파악하기가 굉장히 어려울 것입니다.


📍 변수

변수는 변하는 수를 의미합니다.
상황에 따라서 값을 변경함으로써 프로그램의 동작을 구현하고 제어합니다.

변수는 메모리에 저장됩니다.
그리고 변수를 통해, 값이 담긴 주소을 알 수 있고, 그 주소에 저장된 값을 불러올 수 있습니다.

fmt docs : 포맷 지정자 (%d, %f, %s ...)

출력 값의 자릿수를 지정할 수 있습니다. 자릿수를 넘어가면 값 그대로를 출력합니다.
%5d : 정수형 값을 5자리로 출력합니다. 5자리보다 작은 값인 경우, 공백으로 채웁니다.
%05d : 정수형 값을 5자리로 출력합니다. 5자리보다 작은 값인 경우, 0으로 5자리를 맞춥니다.
등 여러 가지 포맷 지정 방법이 있습니다.
아래에서 설명할 때 등장하는 포맷 지정자는 다음과 같습니다. :
%d, %s, %c, %08b


📍 변수의 출력

모든 변수는 값을 가지며, 그 값은 출력할 수 있습니다.
변수의 타입마다 출력할 수 있는 방식이 정해져 있고, 그 방식은 한 가지로 국한되지 않습니다.
본인이 원하는 대로 출력하기 위해서는 아래에 설명하는 출력 방식을 모두 사용할 수 있어야 합니다.

기본 출력 함수 func Print

func Print(a ...interface{}) (n int, err error)
// Print formats using the default formats for its operands and writes to standard output. 
// Spaces are added between operands when neither is a string. 
// It returns the number of bytes written and any write error encountered.

Printf() 함수는 출력 시에 변수의 기본 타입에 맞추어 출력합니다.
공백을 출력하고 싶다면 string 타입에 맞추어 " "로 표현해야 합니다.
인자를 함수에 넣을 때, 공백을 넣더라도 string 타입으로 넣지 않으면 아무 의미 없습니다.

package main

import "fmt"

func main() {
	var x int8 = 5
	var y int8 = 3
	fmt.Print("x is ", x, "\n", "y", "is", y, "\n")
}
// x is 5
// yis3

공백 포함 출력 함수 func Println

func Println(a ...interface{}) (n int, err error)
// Println formats using the default formats for its operands and writes to standard output.
// Spaces are always added between operands and a newline is appended.
// It returns the number of bytes written and any write error encountered.

Println() 함수는 Print() 함수와 다르게 각 인자 사이에 공백 string을 추가하며, 줄 끝에 개행을 덧붙입니다.
아래의 Printf() 함수와 더불어 가장 많이 사용하는 함수입니다.

package main

import "fmt"

func main() {
	var x int8 = 5
	var y int8 = 3
	fmt.Println("x is ", x, "\n", "y", "is", y)
}
// x is  5 
//  y is 3
// **" y is 3" : y앞에 공백 하나가 포함돼 있는 것이 맞습니다.

포맷 지정 출력 함수 func Printf

func Printf(format string, a ...interface{}) (n int, err error)
// Printf formats according to a format specifier and writes to standard output.
// It returns the number of bytes written and any write error encountered.

변수의 출력을 특정한 형식으로 지정하여 출력할 수 있습니다.
표준 입출력 fmt 패키지에서는 Printf() 함수만이 가능합니다.
fmt.Printf package docs

Printf 함수는 string 타입으로 format을 지정하고 표준 출력을 수행합니다.
함수 동작이 끝나면 byte 수와 에러를 반환합니다.
Hello World!!를 출력하고 그에 대한 결과를 보겠습니다.

package main

import "fmt"

func main() {
	fmt.Printf("%s\n", "Hello World!!")
	// 포맷 지정자를 string으로 정하고 개행문자를 포함했습니다.
	// %s에 들어갈 값으로 "Hello World!!"를 넣습니다.
	
	fmt.Printf("Hello World!!\n")
	// 출력할 포맷을 제외하고 문자열 출력만 하도록 간소화했습니다.
	// Printf는 기본 string 타입을 받기 때문에 string 타입은 포맷 지정 없이도 사용이 가능합니다.
	
	var s string = "hi\n"
	fmt.Printf(s)
	// hi를 출력하고 개행을 합니다.
	// 단, 변수를 포맷 지정 없이 Printf로 출력하는 것은 권장되지 않습니다. (go-staticcheck)
	// 에러는 발생하지 않으며 정상 출력됩니다. 다만 가능할 뿐, 이런 사용 방식은 지양합니다.
	
	xByte, err := fmt.Printf("Hello World!!\n")
	fmt.Printf("The value of xByte : %d\nError : %v\n", xByte, err)
	// The value of xByte : 14
	// Error : <nil>
	// string은 글자 하나가 8bit, 1byte로 저장되기 때문에 "Hello World!!"에 대한 Byte 수는 13이 됩니다.
	// \n 개행 문자도 1byte입니다.
}

📍 변수의 입력

모든 프로그램의 동작은 변수의 제어를 통해 이루어집니다.
변수를 제어하기 위해서는 값을 부여하고 연산을 해야 합니다.
그리고 유동적으로 프로그램을 동작시키기 위해, 적절한 곳에서 변수에 값을 '입력'해야 하기도 합니다.

기본 입력 함수 func Scan

func Scan(a ...interface{}) (n int, err error)
// Scan scans text read from standard input, storing successive space-separated values into successive arguments. 
// Newlines count as space. 
// It returns the number of items successfully scanned. 
// If that is less than the number of arguments, err will report why.

Scan()함수는 값을 입력 받아, 변수에 저장합니다.
변수 간의 구분은 입력 시의 공백이나 개행으로 이루어집니다.

package main // Hello World!!를 입력 및 출력합니다.

import "fmt"

func main() {
	var x string
	var y string
	fmt.Scan(&x, &y) // string 두 개의 값을 입력 받아, 각각 x와 y에 저장합니다.
	// Hello World!! 
	// 또는
	// Hello
	// World!!
	// z, _ := fmt.Scan(&x, &y) // 인자의 개수를 z에 초기화합니다.
	fmt.Println(x, "and", y)
	// Hello and World!!
	// fmt.Println(z)
}

개행 종료 입력 함수 func Scanln

func Scanln(a ...interface{}) (n int, err error)
// Scanln is similar to Scan, but stops scanning at a newline and after the final item there must be a newline or EOF.

Scanln()함수는 값을 입력 받을 때, 공백으로만 구분합니다.
성공적으로 입력된 인자의 개수를 반환하며, 에러가 있을 시 에러를 같이 반환합니다.
개행을 하거나 EOF이 오면 종료됩니다.

package main // Hello World!!를 입력 및 출력합니다.

import "fmt"

func main() {
	func main() {
	var x string
	var y string
	var z string
	fmt.Scanln(&x, &y, &z) // string 세 개의 값을 입력 받아, 각각 x, y, z에 저장합니다.
	// Hello and World!!
	// Hello and `New line 또는 Ctrl + D`
	fmt.Println(x, y, z)
	// Hello and World!!
	// Hello and
}

포맷 지정 입력 함수 func Scanf

func Scanf(format string, a ...interface{}) (n int, err error)
// Scanf scans text read from standard input, storing successive space-separated values into successive arguments as determined by the format. 
// It returns the number of items successfully scanned. 
// If that is less than the number of arguments, err will report why. 
// Newlines in the input must match newlines in the format. 
// The one exception: the verb %c always scans the next rune in the input, even if it is a space (or tab etc.) or newline.

Scanf()함수는 값을 입력 받을 때, 형식에 맞는 인자를 받아서 값을 저장합니다.
성공적으로 입력된 인자의 개수를 반환하며, 에러가 있을 시 에러를 같이 반환합니다.
입력 값에 개행을 사용하려면 포맷에도 개행 문자를 알맞는 자리에 명시해야 합니다.
그렇지 않은 경우, 개행을 하거나 EOF이 오면 종료됩니다.
한 가지 예외 : %c (rune 타입)은 언제나 모든 rune을 입력 값으로 받기 때문에, %c로 값을 전달 받을 때, space나 \t, \n 등이 오더라도 모두 rune 타입 문자로 취급됩니다.

package main // Hello World!!를 입력 및 출력합니다.

import "fmt"

func main() {
	var x int8
	var y int8
	var z rune
	fmt.Scanf("%d%c\n%d", &x, &z, &y) 
	// 정수 x 입력, rune 타입 'space 공백' 입력,  \n 포맷에 맞추어 개행, 정수 y입력
	// 5 
	// 3
	fmt.Printf("%c", z) // 입력 마무리로 현위치, rune 타입 z 출력으로 space 공백 출력
	//  
	fmt.Println(x + y) // x + y 값 출력
	//  8	
}
// 5
// 3
//  8
// **(결과로 한 칸 뒤에 8이 출력되는 게 맞습니다.)

📍 문자열 반환

문자열 반환 함수 func Sprint, Sprintf, Sprintln

func Sprint(a ...interface{}) string
// Sprint formats using the default formats for its operands and returns the resulting string.
// Spaces are added between operands when neither is a string.
func Sprintln(a ...interface{}) string
// Sprintln formats using the default formats for its operands and returns the resulting string. 
// Spaces are always added between operands and a newline is appended.
func Sprintf(format string, a ...interface{}) string
// Sprintf formats according to a format specifier and returns the resulting string.

Sprint()함수는 Print() 함수가 출력하는 결과를 string 타입으로 반환합니다.
Sprintln()함수는 Println() 함수를, Sprintf()함수는 Printf()함수로 출력되는 결과를 string 타입으로 반환합니다.

package main

import "fmt"

func main() {
	const name, age = "Kim", 22
	s := fmt.Sprint(name, " is ", age, " years old.\n")
	fmt.Println(s)
	// Kim is age 22 years old.
}

📌 연산자

Golang에서 지원하는 연산자는 아래와 같습니다.

📍 산술 연산자

구분연산자연산피연산자 타입
사칙 연산과 나머지+덧셈정수, 실수, 복소수, 문자열
-뺄셈정수, 실수, 복소수
*곱셈정수, 실수, 복소수
/나눗셈정수, 실수, 복소수
%나머지정수
비트 연산&AND 비트 연산정수
|OR 비트 연산정수
^XOR 비트 연산정수
&^비트 클리어정수
시프트 연산<<왼쪽 시프트정수 << 양의 정수
>>오른쪽 시프트정수 >> 양의 정수

%연산자 (modular arithmetic)

%연산은 modular arithmetic이라고 하며, 나머지 연산이라고도 합니다. 결과 값은 나눗셈의 나머지입니다.
5 % 3 연산의 결과는 5을 3로 나눈 나머지 2가 결과 값이 됩니다.

package main

import "fmt"

func main() {
	var a int8 = 5 // 변수 a를 int8, 8비트 정수로 선언하고 5(으)로 초기화합니다.
	var b int8 = 3 // 변수 b를 int8, 8비트 정수로 선언하고 3(으)로 초기화합니다.
	fmt.Printf("%d\n", a % b) // a % b 연산을 한 결과를 출력합니다.
} // 2

%연산자를 다른 산술 연산자로 바꾸어 출력할 수 있습니다.


📍 비트 연산자

비트 연산자 & (AND)

비트 연산자는 비트 단위의 연산에 사용합니다.

ABA & B
000
100
010
111

위와 같이 A와 B 모두가 1인 경우에만 A & B의 결과 값이 1이 되는 것을 볼 수 있습니다.
1byte = 8bit 이므로,
십진수 5를 8비트 표기법으로 이진수로 나타내면 00000101이 됩니다.
십진수 3을 8비트 표기법으로 이진수로 나타내면 00000011이 됩니다.
5 & 3 의 연산은 아래와 같이 진행됩니다.
    0 0 0 0 0 1 0 1
   & 0 0 0 0 0 0 1 1
ㅡㅡㅡㅡㅡㅡㅡㅡㅡ
    0 0 0 0 0 0 0 1

이렇게 첫 번째 자리만 모두 1이어서 결과 값으로 1이 출력됩니다.

코드로 작성하면 아래와 같습니다.

package main

import "fmt"

func main() {
	var a int8 = 5 // 변수 a를 int8, 8비트 정수로 선언하고 5(으)로 초기화합니다.
	var b int8 = 3 // 변수 b를 int8, 8비트 정수로 선언하고 3(으)로 초기화합니다.
	fmt.Printf("%08b\n", a & b) // a & b 연산을 한 결과를 출력합니다.
} // 00000001

비트 연산자 | (OR)

비트 연산자는 비트 단위의 연산에 사용합니다.

ABA | B
000
101
011
111

위와 같이 A와 B 둘 중 하나가 1인 경우에는 A | B의 결과 값이 1이 되는 것을 볼 수 있습니다.
마찬가지로 십진수 5와 3을 통해 연산을 진행합니다.
5 | 3 의 연산은 아래와 같이 진행됩니다.
    0 0 0 0 0 1 0 1
    | 0 0 0 0 0 0 1 1
ㅡㅡㅡㅡㅡㅡㅡㅡㅡ
    0 0 0 0 0 1 1 1

이렇게 둘 중 하나가 1이어서 결과 값으로 111이 출력됩니다.

코드로 작성하면 아래와 같습니다.

package main

import "fmt"

func main() {
	var a int8 = 5 // 변수 a를 int8, 8비트 정수로 선언하고 5(으)로 초기화합니다.
	var b int8 = 3 // 변수 b를 int8, 8비트 정수로 선언하고 3(으)로 초기화합니다.
	fmt.Printf("%08b\n", a | b) // a | b 연산을 한 결과를 출력합니다.
} // 00000111

비트 연산자 ^ (XOR)

비트 연산자는 비트 단위의 연산에 사용합니다.

ABA ^ B
000
101
011
110

위와 같이 A와 B 둘 중 하나만 1인 경우에만 A ^ B의 결과 값이 1이 되는 것을 볼 수 있습니다.
마찬가지로 십진수 5와 3을 통해 연산을 진행합니다.
5 ^ 3 의 연산은 아래와 같이 진행됩니다.
    0 0 0 0 0 1 0 1
   ^ 0 0 0 0 0 0 1 1
ㅡㅡㅡㅡㅡㅡㅡㅡㅡ
    0 0 0 0 0 1 1 0

이렇게 둘 중 하나가 1이어서 결과 값으로 110이 출력됩니다.

코드로 작성하면 아래와 같습니다.

package main

import "fmt"

func main() {
	var a int8 = 5 // 변수 a를 int8, 8비트 정수로 선언하고 5(으)로 초기화합니다.
	var b int8 = 3 // 변수 b를 int8, 8비트 정수로 선언하고 3(으)로 초기화합니다.
	fmt.Printf("%08b\n", a ^ b) // a ^ b 연산을 한 결과를 출력합니다.
} // 00000110

Golang에서는 비트 단위 연산에 NOT 연산이 없습니다.
그렇기 때문에 비트 반전이 필요한 경우, XOR -> ^ 비트 연산자를 사용합니다.


비트 연산자 &^ (비트 클리어)

비트 연산자는 비트 단위의 연산에 사용합니다.

ABA &^ B
000
101
010
110

위와 같이 A가 1이고 B가 0인 경우에만 A &^ B의 결과 값이 1이 되는 것을 볼 수 있습니다.
마찬가지로 십진수 5와 3을 통해 연산을 진행합니다.
5 &^ 3 의 연산은 아래와 같이 진행됩니다.
    0 0 0 0 0 1 0 1
& ^ 0 0 0 0 0 0 1 1
ㅡㅡㅡㅡㅡㅡㅡㅡㅡ
    0 0 0 0 0 1 0 0

이렇게 둘 중 하나가 1이어서 결과 값으로 100이 출력됩니다.
비트 클리어 연산은 &와 ^가 결합된 형태입니다.
A가 1이면서, & 그리고 ^둘 중 하나만 1인 경우 즉, B가 0인 경우를 의미합니다.

코드로 작성하면 아래와 같습니다.

package main

import "fmt"

func main() {
	var a int8 = 5 // 변수 a를 int8, 8비트 정수로 선언하고 5(으)로 초기화합니다.
	var b int8 = 3 // 변수 b를 int8, 8비트 정수로 선언하고 3(으)로 초기화합니다.
	fmt.Printf("%08b\n", a &^ b) // a &^ b 연산을 한 결과를 출력합니다.
} // 00000100

📍 시프트 연산자

시프트 연산자 << (왼쪽 시프트)

시프트 연산자는 비트 단위의 연산에 사용합니다.

마찬가지로 십진수 5와 3을 통해 연산을 진행합니다.
5 << 3 의 연산은 아래와 같이 진행됩니다.
    0 0 0 0 0 1 0 1
 << 0 0 0 0 0 0 1 1
ㅡㅡㅡㅡㅡㅡㅡㅡㅡ
    0 0 1 0 1 0 0 0

시프트 연산은 위와 같이 비트 자릿수로 하는 연산이 아닙니다.
바로 위에 작성한 계산법은 아무 의미가 없습니다.
5 << 3은 이진수 5의 비트 자릿수를 3만큼 왼쪽으로 옮기는 연산입니다.
직접 해보면 아래와 같습니다.

0 0 0 0 0 1 0 1
0 0 1 0 1 0 0 0

그리고 이 값을 십진수로 표현하면 28이 됩니다.

코드로 작성하면 아래와 같습니다.

package main

import "fmt"

func main() {
	var a int8 = 5 // 변수 a를 int8, 8비트 정수로 선언하고 5(으)로 초기화합니다.
	var b int8 = 3 // 변수 b를 int8, 8비트 정수로 선언하고 3(으)로 초기화합니다.
	fmt.Printf("%08b\n", a << b) // a << b 연산을 한 결과를 출력합니다.
} // 00101000

시프트 연산자 >> (오른쪽 시프트)

시프트 연산자는 비트 단위의 연산에 사용합니다.

마찬가지로 십진수 5와 3을 통해 연산을 진행합니다.
5 >> 3 의 연산은 아래와 같이 진행됩니다.
    0 0 0 0 0 1 0 1
 >> 0 0 0 0 0 0 1 1
ㅡㅡㅡㅡㅡㅡㅡㅡㅡ
    0 0 0 0 0 0 0 0

시프트 연산은 위와 같이 비트 자릿수로 하는 연산이 아닙니다.
바로 위에 작성한 계산법은 아무 의미가 없습니다.
5 >> 3은 이진수 5의 비트 자릿수를 3만큼 오른쪽으로 옮기는 연산입니다.
직접 해보면 아래와 같습니다.

5 >> 0 : 0 0 0 0 0 1 0 1
5 >> 1 : 0 0 0 0 0 0 1 0
5 >> 2 : 0 0 0 0 0 0 0 1
5 >> 3 : 0 0 0 0 0 0 0 0

그리고 이 값을 십진수로 표현하면 0이 됩니다.

코드로 작성하면 아래와 같습니다.

package main

import "fmt"

func main() {
	var a int8 = 5 // 변수 a를 int8, 8비트 정수로 선언하고 5(으)로 초기화합니다.
	var b int8 = 3 // 변수 b를 int8, 8비트 정수로 선언하고 3(으)로 초기화합니다.
	fmt.Printf("%08b\n", a >> b) // a >> b 연산을 한 결과를 출력합니다.
} // 00000000

아래 링크는 Golang의 연산자에 대한 스펙 문서입니다.
golang Spec # Arithmetic_operators


이번 포스트는 Golang의 가장 기본이 되는 "fmt" 패키지에 대한 내용이었습니다.
감사합니다.👍

profile
주니어 개발자

1개의 댓글

comment-user-thumbnail
2021년 12월 2일

유익한 내용입니다!!

답글 달기