반응형

메서드에 대한 컨벤션

메서드 정의 시 Go에서는 아래와 같은 컨벤션을 일반적으로 따른다.

  • 리시버 인자 정의
    • 리시버 인자의 변수 이름은 리시버 타입 이름의 첫 글자를 사용한다
    • 변수는 하나의 글자로만 선언한다
  • 밸류 vs 포인터 선언
    • 값을 변경할 필요가 없는 경우에는 배류 리시버로 선언해야 하지만, 통일성을 위해서 밸류와 포인터를 섞어서 선언하지 않고 포인터로 선언한다 (참고 : Head First Go)

 

Pointer Receiver

  • Value Receiver  : 메서드 사용 시 새로운 객체를 생성할 필요가 있으면 값 타입 메서드 
  • Pointer Receiver : 메서드 사용 시 새로운 객체 생성이 아니라 값을 참조해와야하면 포인트 타입 메서드 
    • 사용하는 이유는
      • 메서드가 리시버가 가리키는 값을 수정할 수 있음
      • 각각의 메서드 call에서의 value 복사 문제를 피하기 위해서 
        => 리시버가 큰 구조체라면 이것은 더 효율적일 수 있음 
package method_interface

import "fmt"

// 메서드 사용 시 새로운 객체를 생성할 필요가 있으면 값 타임 메서드
// 메서드 사용 시 새로운 객체를 생성할 필요가 있으면 포인트 타입 메서드

type Person struct {
	Name string
	Age  int
}

func (p *Person) AddAge() {
	p.Age += 1 // value receiver
}

type Temperature float64

func (t Temperature) Up(temp float64) Temperature {
	return t + Temperature(temp) // pointer receiver
}

func Main2() {
	p1 := Person{Name: "Rob", Age: 4}

	fmt.Println("P1: ", p1)
	p1.AddAge()
	fmt.Println("P1: ", p1)

	t := Temperature(30.0)

	fmt.Println("T: ", t)
	t = t.Up(4.0)
	fmt.Println("T: ", t)
}

package main

func main(){
	method_interface.Main2()
}

 

Pointer Indirection / dereference (메서드와 포인트 역참조)

포인터를 다루는 데 있어 함수, 메서드 간의 차이가 존재 

  • 함수에 포인터 인자로 선언한 인자는 포인터 인자만 인자로 받을 수 있다
  • 메서드의 리시버 인자의 경우에는 포인터와 밸류 인자 둘 다 받을 수 있다

포인터 인자가 밸류일때?

area(r *Rectangle) 함수의 인자는 포인터 인자로 선언되어 밸류 값을 넘기면 컴파일 오류가 발생하고 포인터 인자만을 넘길 수 있다.

func area(r *Rectangle) {
	fmt.Println(r.height * r.width)
}

func Example_Indirection_Func_Pointer_Parameter() {
	r := Rectangle{
		height: 10,
		width:  3,
	}

	//area(r) //컴파일 오류 - 함수는 포인터 인자만 받을 수 있음
	area(&r)

	//Output:
	//30
}

 

r.area(), r은 포인터가 아닌 밸류 값이지만, 포인터 리시버 인자의 메서드가 호출될 때
Go에서 자동으로 r.area() -> (&r).area()로 해석을 해서 실행

func (r *Rectangle) area() {
	fmt.Println(r.height * r.width)
}

func Example_Indirection_Method_Pointer_Receiver() {
	r := Rectangle{
		height: 10,
		width:  3,
	}

	r.area()
	(&r).area()

	//Output:
	//30
	//30

}

 

리시버 인자가 밸류일때?

perimeter(r Rectangle) 함수는 밸류 인자로 선언되어 perimeter(&r) 포인터 값을 인자로 넘겨주면 컴파일 오류가 발생

func perimeter(r Rectangle) {
	fmt.Println(2 * (r.height * r.width))
}

func Example_Indirection_Func_Value_Parameter() {
	r := Rectangle{
		height: 10,
		width:  3,
	}

	//perimeter(&r) //컴파일 오류 - 함수는 value 인자만 받을 수 있음
	perimeter(r)

	//Output:
	//60
}

리시버 인자의 경우, (&r).perimeter() 호출 시 Go는 리시버 인자는 밸류 인자로 선언되어 (*r).perimeter()로 자동으로 해석해서 실행

func (r Rectangle) perimeter() {
	fmt.Println(2 * (r.height * r.width))
}

func Example_Indirection_Method_Value_Receiver() {
	r := Rectangle{
		height: 10,
		width:  3,
	}

	r.perimeter()
  (&r).perimeter()

	//Output:
	//60
	//60
}

 

메서드는 함수에 리시버 인자를 추가한 버전으로 이해하는 걸로 하자 

반응형

'Language Study > Go' 카테고리의 다른 글

[Golang]Quick sort(퀵 정렬)  (0) 2022.12.13
[Golang]bubble sort(버블 정렬)  (0) 2022.12.13
[Golang]array, slice, map  (0) 2022.12.04
[Golang]변수, 상수  (0) 2022.08.21
[Golang]클래스, 구조체, 인스턴스  (0) 2022.08.08
반응형

퀵 정렬(quick sort) 알고리즘

오름차순 정렬

  • 퀵 정렬은 불안정 정렬 에 속하며, 다른 원소와의 비교만으로 정렬을 수행하는 비교 정렬에 속한다.
  • 분할 정복 알고리즘의 하나로, 평균적으로 매우 빠른 수행 속도를 자랑하는 정렬 방법
    • 합병 정렬(merge sort)과 달리 퀵 정렬은 리스트를 비균등하게 분할한다.
  • 분할 정복(divide and conquer) 방법
    • 문제를 작은 2개의 문제로 분리하고 각각을 해결한 다음, 결과를 모아서 원래의 문제를 해결하는 전략이다.
    • 분할 정복 방법은 대개 순환 호출을 이용하여 구현한다.
  • 하나의 리스트를 피벗(pivot) 을 지정하여 그를 기준으로 두 개의 비균등한 크기로 분할하고, 
    분할된 부분 리스트를 정렬 -> 두 개의 정렬된 부분 리스트를 합하여 전체가 정렬된 리스트가 되게 한다. 
  • 단계
    1. 분할(Divide): 입력 배열을 피벗을 기준으로 비균등하게 2개의 부분 배열(피벗을 중심으로 왼쪽: 피벗보다 작은 요소들, 오른쪽: 피벗보다 큰 요소들)로 분할한다.
    2. 정복(Conquer): 부분 배열을 정렬한다. 부분 배열의 크기가 충분히 작지 않으면 순환 호출 을 이용하여 다시 분할 정복 방법을 적용한다.
    3. 결합(Combine): 정렬된 부분 배열들을 하나의 배열에 합병한다.
    4. 순환 호출이 한번 진행될 때마다 최소한 하나의 원소(피벗)은 최종적으로 위치가 정해지므로, 
      반드시 이 알고리즘은 끝난다는 것을 보장한다. 

 

 

알고리즘 특징

  • 장점
    • 속도가 빠르다.
    • 시간 복잡도가 O(nlog₂n)를 가지는 다른 정렬 알고리즘과 비교했을 때도 가장 빠르다.
    • 추가 메모리 공간을 필요로 하지 않는다.
    • 퀵 정렬은 O(log n)만큼의 메모리를 필요로 한다.
  • 단점
    • 정렬된 리스트에 대해서는 퀵 정렬의 불균형 분할에 의해 오히려 수행시간이 더 많이 걸린다.
    • 퀵 정렬의 불균형 분할을 방지하기 위하여 피벗을 선택할 때 더욱 리스트를 균등하게 분할할 수 있는 데이터를 선택한다
      EX) 리스트 내의 몇 개의 데이터 중에서 크기순으로 중간 값(medium)을 피벗으로 선택한다.

시간복잡도

  • 최선의 경우
    • 비교 횟수 
      • 순환 호출의 깊이 
      • T(n) = O(nlog₂n)
  • 최악의 경우 
    • 리스트가 계속 불균형하게 나누어 지는 경우 ( 이미 정렬된 리스트에 대해 퀵 정렬을 실행하는 경우)
    • T(n) = O(n^2)
  • 평균
    • T(n) = O(nlog₂n)
    • 다른 정렬 알고리즘과 비교했을 때도 가장 빠르다 
    • 퀵 정렬이 불필요한 데이터 이동을 줄이고 먼 거리의 데이터를 교환할 뿐 아니라, 한 번 결정된 피벗들이 추후 연산에서 제외되는 특성으로 빠르다
https://gmlwjd9405.github.io/2018/05/10/algorithm-quick-sort.html

 

퀵 정렬을 C언어 코드로 

# include <stdio.h>
# define MAX_SIZE 9
# define SWAP(x, y, temp) ( (temp)=(x), (x)=(y), (y)=(temp) )

// 1. 피벗을 기준으로 2개의 부분 리스트로 나눈다.
// 2. 피벗보다 작은 값은 모두 왼쪽 부분 리스트로, 큰 값은 오른쪽 부분 리스트로 옮긴다.
/* 2개의 비균등 배열 list[left...pivot-1]와 list[pivot+1...right]의 합병 과정 */
/* (실제로 숫자들이 정렬되는 과정) */
int partition(int list[], int left, int right){
  int pivot, temp;
  int low, high;

  low = left;
  high = right + 1;
  pivot = list[left]; // 정렬할 리스트의 가장 왼쪽 데이터를 피벗으로 선택(임의의 값을 피벗으로 선택)

  /* low와 high가 교차할 때까지 반복(low<high) */
  do{
    /* list[low]가 피벗보다 작으면 계속 low를 증가 */
    do {
      low++; // low는 left+1 에서 시작
    } while (low<=right && list[low]<pivot);

    /* list[high]가 피벗보다 크면 계속 high를 감소 */
    do {
      high--; //high는 right 에서 시작
    } while (high>=left && list[high]>pivot);

    // 만약 low와 high가 교차하지 않았으면 list[low]를 list[high] 교환
    if(low<high){
      SWAP(list[low], list[high], temp);
    }
  } while (low<high);

  // low와 high가 교차했으면 반복문을 빠져나와 list[left]와 list[high]를 교환
  SWAP(list[left], list[high], temp);

  // 피벗의 위치인 high를 반환
  return high;
}

// 퀵 정렬
void quick_sort(int list[], int left, int right){

  /* 정렬할 범위가 2개 이상의 데이터이면(리스트의 크기가 0이나 1이 아니면) */
  if(left<right){
    // partition 함수를 호출하여 피벗을 기준으로 리스트를 비균등 분할 -분할(Divide)
    int q = partition(list, left, right); // q: 피벗의 위치

    // 피벗은 제외한 2개의 부분 리스트를 대상으로 순환 호출
    quick_sort(list, left, q-1); // (left ~ 피벗 바로 앞) 앞쪽 부분 리스트 정렬 -정복(Conquer)
    quick_sort(list, q+1, right); // (피벗 바로 뒤 ~ right) 뒤쪽 부분 리스트 정렬 -정복(Conquer)
  }

}

void main(){
  int i;
  int n = MAX_SIZE;
  int list[n] = {5, 3, 8, 4, 9, 1, 6, 2, 7};

  // 퀵 정렬 수행(left: 배열의 시작 = 0, right: 배열의 끝 = 8)
  quick_sort(list, 0, n-1);

  // 정렬 결과 출력
  for(i=0; i<n; i++){
    printf("%d\n", list[i]);
  }
https://gmlwjd9405.github.io/2018/05/10/algorithm-quick-sort.html

 

고언어 통한 quick sort

func Quick(arr []int) {

	if len(arr) <= 1 {
		return
	}
	p := Divide(arr)
	Quick(arr[:p])
	Quick(arr[p:])

}

func Divide(arr []int) int {
	pivot, end := arr[0], len(arr)-1
	start := 1

	for {
		for ; start < len(arr); start++ { 
			if arr[start] > pivot {
				break
			}
		}
		for ; end > 0; end-- {
			if arr[end] < pivot {
				break
			}
		}
		if start > end { 
			break
		}
		arr[start], arr[end] = arr[end], arr[start]
	}
	arr[0], arr[end] = arr[end], arr[0]
	return end + 1
}

func main() {
	arr := []int{3, 7, 4, 1, 7, 5, 4, 8}
    algorithm.Quick(arr)
	fmt.Println(arr)

}
반응형

'Language Study > Go' 카테고리의 다른 글

[Golang]Method  (0) 2022.12.14
[Golang]bubble sort(버블 정렬)  (0) 2022.12.13
[Golang]array, slice, map  (0) 2022.12.04
[Golang]변수, 상수  (0) 2022.08.21
[Golang]클래스, 구조체, 인스턴스  (0) 2022.08.08
반응형

버블 정렬(bubble sort) 알고리즘 개념 

  • 서로 인접한 두 원소를 검사하여 정렬하는 알고리즘
    • 인접한 2개의 레코드 비교, 크기가 순서대로 되어있지 않으면 서로 교환
  • 버블 정렬은 첫 번째 자료와 두 번째 자료를, 두 번째 자료와 세 번째 자료를, 세 번째와 네 번째를, …
    이런 식으로 (마지막-1)번째 자료와 마지막 자료를 비교하여 교환하면서 자료를 정렬
  • 1회전 수행하면 가장 큰 자료가 맨 뒤로 이동 하므로 2회전에는 맨 끝에 있는 자료는 제외되고, 
    2회전에는 끝에서 두 번째 자료까지는 정렬에서 제외 
    => 정렬을 1회전 수행할때마다 정렬에서 제외되는 데이터가 하나씩 증가 

 

 

알고리즘 특징

  • 장점
    • 구현이 매우 간단하다.(옆에 레코드와 비교해서 큰 값을 뒤로 넘겨주면 됨)
  • 단점
    • 순서에 맞지 않은 요소를 인접한 요소와 교환한다.
    • 하나의 요소가 가장 왼쪽에서 가장 오른쪽으로 이동하기 위해서는 배열에서 모든 다른 요소들과 교환되어야 한다.
    • 특히 특정 요소가 최종 정렬 위치에 이미 있는 경우라도 교환되는 일이 일어난다.
    • 일반적으로 자료의 교환 작업(SWAP)이 자료의 이동 작업(MOVE)보다 더 복잡하기 때문에 버블 정렬은 단순성에도 불구하고 거의 쓰이지 않는다.

 

 

시간 복잡도

  • 비교 횟수
    최상, 평균, 최악 모두 일정
    n-1, n-2, … , 2, 1 번 = n(n-1)/2
  • 교환 횟수
    • 입력 자료가 역순으로 정렬되어 있는 최악의 경우, 한 번 교환하기 위하여 3번의 이동(SWAP 함수의 작업)이 필요하므로 (비교 횟수 * 3) 번 = 3n(n-1)/2
    • 입력 자료가 이미 정렬되어 있는 최상의 경우, 자료의 이동이 발생하지 않는다.
  • T(n) = O(n^2)

 

버블 정렬(bubble sort)의 c언어 코드 

# include <stdio.h>
# define MAX_SIZE 5

// 버블 정렬
void bubble_sort(int list[], int n){
  int i, j, temp;

  for(i=n-1; i>0; i--){
    // 0 ~ (i-1)까지 반복
    for(j=0; j<i; j++){
      // j번째와 j+1번째의 요소가 크기 순이 아니면 교환
      if(list[j]<list[j+1]){
        temp = list[j];
        list[j] = list[j+1];
        list[j+1] = temp;
      }
    }
  }
}

void main(){
  int i;
  int n = MAX_SIZE;
  int list[n] = {7, 4, 5, 1, 3};

  // 버블 정렬 수행
  bubble_sort(list, n);

  // 정렬 결과 출력
  for(i=0; i<n; i++){
    printf("%d\n", list[i]);
  }
}
https://gmlwjd9405.github.io/2018/05/06/algorithm-bubble-sort.html

 

Go언어로 된 버블 정렬 알고리즘은?

//bubble sort func
package bubblesort

func Bubble_sort(array []int) []int {
	for i := 0; i < len(array)-1; i++ {
		for j := 0; j < len(array)-i-1; j++ {
			if array[j] > array[j+1] {
				array[j], array[j+1] = array[j+1], array[j]
			}
		}
	}
	return array
}


//main
package main

import (
	"fmt"
	"study01/package1/bubblesort"
)
func main() {
	fmt.Print(bubblesort.Bubble_sort([]int{11, 8, 2, 3, 4, 5}))
}

 

반응형

'Language Study > Go' 카테고리의 다른 글

[Golang]Method  (0) 2022.12.14
[Golang]Quick sort(퀵 정렬)  (0) 2022.12.13
[Golang]array, slice, map  (0) 2022.12.04
[Golang]변수, 상수  (0) 2022.08.21
[Golang]클래스, 구조체, 인스턴스  (0) 2022.08.08
반응형

개요

고에서는 array, slice, map을 지원한다

 

slice는 가변적인 배열로 , 파이썬에서는 list

map은 key-vaule 의 형태의 자료형으로 dict로 생각하면 된다. 

 

slice와 map은 함께 많이 사용한다. 어떻게 사용하는지 정리해본다. 

 

배열

package array

import "fmt"

func NewArray(){
	var a [5]int // 다른 데이터 형태와는 달리 var 변수명 뒤에 자료형을 생략할 수 없음
	b := [4]int{1,2,3,4} //배열은 선언과 동시에 초기화가 가능

	fmt.Println(a,len(a))
	fmt.Println(b, len(b))
}

[0 0 0 0 0] 5 // 값을 지정하지 않으면 0 으로 초기화
[1 2 3 4] 4

 

슬라이스

package slice


func NewSlice(){
	// slice 선언 두가지 방법으로
    var a []int
    b := make([]int,5,10)
    //int 자료형인 길이 5, 최대 길이 10 
     
    //즉, slice는 고정길이 배열과 달리 초기화시 최대 길이를 지정할 수 있고,
    //해당 slice를 통해 최대 길이를 넘지 않는 범위에서 가변길이 배열(=슬라이스)을 얼마든 생성해낼 수 있다

	//append같은 메소드를 사용하면 슬라이스의 길이를 확장시킬 수 있다
    b = append(a, 5,2,3,4)
    fmt.Println(a,b)
}

[] [5 2 3 4]

 

슬라이스와 배열의 차이는 가변 길이를 정하는 것이고, 

리스트 선언 시 길이를 명시해주면 배열(생성한 길이 만큼만 사용할 수 있음)

길이를 명시하지 않으면 슬라이스(유연하게 조정하고 늘려가며 사용할 수 있음)

 

package map1

import "fmt"


func NewMap(){
	// 주어진 타입을 초기화하고 사용할 수 있는 맵을 반환
	
	a := make(map[int]string)
	a[1] = "A"
	a[2] = "B"

	// 초기화 시 값을 넣어서 맵을 반환할 수 있음 
	map_int_str := map[int]string {
		1: "월",
		2: "화",
		3: "수",
		4: "목",
		5: "금",
		6: "토",
		7: "일",
		//마지막 값 입력 시에도 , 가 빠지면 에러
		}

	fmt.Println(a,map_int_str)
	//map[1:A 2:B] map[1:월 2:화 3:수 4:목 5:금 6:토 7:일]
}

만약 없는 요소라면? 숫자일 경우에는 0을 문자열의 경우에는 "" 을 출력한다 

 // 만약, 값이 주어지지 않은 요소를 꺼내온다면?

package map1

import "fmt"

func NotInMap(){
	a := make(map[int]int)

	a[1] = 2
	a[3] = 4

	fmt.Println(a[2])
}
// 0

 

정확히 맵은 어떤 타입을 반환하는걸까 보면 

package map1

import "fmt"

func NotInMap(){
	a := make(map[int]int)

	a[1] = 2
	a[3] = 4

	value1, ok := a[1]
	fmt.Println(value1,ok)
	// 2 true

	value2, ok := a[2]
	fmt.Println(value2,ok)
	// 0 false 

	value3,ok := a[3]
	fmt.Println(value3,ok)
	// 4 true
}

존재하지 않는 키 값을 요청한다면, 두 번째 인자로 false 를 반환한다. 

아래는 맵의 요소 확인과 삭제 문법

package map1

import "fmt"

func NotInMap(){
	a := make(map[int]int)

	// 맵에 요소를 추가하거나 업데이트 
	a[1] = 2
	a[3] = 4

	value1, ok := a[1]
	fmt.Println(value1,ok)
	// 2 true

	value2, ok := a[2]
	fmt.Println(value2,ok)
	// 0 false 

	// 요소 제거 
	delete(a,3)

	value3,ok := a[3]
	fmt.Println(value3,ok)
	// 0 false
}

 

따라서 go에서는 아래와 같이 예외 처리를 한다. 

 

package map1

import "fmt"

func NotInMap(){
	a := map[int]int{
		1 : 2,
		2 : 3,
		3 : 4,
		4 : 5,
	}
	
	// key를 0~7까지 대입하고, a[key] 의 결과로 ok 값을 받아 
	//ok가 true일 경우(해당 키에 대한 value값 존재 시 ) key에 해당하는 value를 출력
	//그렇지 않을 경우(false/ value값이 미존재 시 ) 문자열 출력
	for key:= 0; key < 8; key++{
		if value, ok := a[key];ok{
				fmt.Println(value)
			} else {
			fmt.Println("key", key, "is not exist")
		}
	}
}

key 0 is not exist
2
3
4
5
key 5 is not exist
key 6 is not exist
key 7 is not exist

 

go tour > 맵 연습하기 (링크 : https://go-tour-ko.appspot.com/moretypes/23) 

// go tour map 연습하기

// 문자열 s가 주어진다
//주어진 문자열을 단어 단위로 나눠 단어가 몇번 쓰였는지 맵으로 기록한다
// wc.test 함수로 우리가 작성한 함수를 여러 테스트 케이스로 테스트해준다.

package map1

import (
	"strings"

	"golang.org/x/tour/wc"
)

func WordCount(s string) map[string]int {
	//strings filed는 유니코드에서 정의한 대로 
	//하나 이상의 연속된 공백 문자의 각 인스턴스를 기준으로 문자열 분할
	words := strings.Fields(s)
	//일단 문자열을 단어 단위의 배열로 반환 

	result := make(map[string]int)

	// for 인덱스 , 요소 := range 배열 {...}
	// 아래는 요소만 사용할 때 
	for _, w:= range words{
		if result[w] == 0 {
			result[w] = 1
		}else{
			result[w] = result[w] +1
		}
	}
	return result
}


func main() {
	wc.Test(WordCount)
}

//PASS
 f("I am learning Go!") = 
 map[string]int{"Go!":1, "I":1, "am":1, "learning":1}
PASS
f("The quick brown fox jumped over the lazy dog.") = 
 map[string]int{"The":1, "brown":1, "dog.":1, "fox":1, "jumped":1, "lazy":1, "over":1, "quick":1, "the":1}
PASS
f("I ate a donut. Then I ate another donut.") = 
 map[string]int{"I":2, "Then":1, "a":1, "another":1, "ate":2, "donut.":2}
PASS
f("A man a plan a canal panama.") = 
 map[string]int{"A":1, "a":2, "canal":1, "man":1, "panama.":1, "plan":1}

 

반응형

'Language Study > Go' 카테고리의 다른 글

[Golang]Method  (0) 2022.12.14
[Golang]Quick sort(퀵 정렬)  (0) 2022.12.13
[Golang]bubble sort(버블 정렬)  (0) 2022.12.13
[Golang]변수, 상수  (0) 2022.08.21
[Golang]클래스, 구조체, 인스턴스  (0) 2022.08.08
반응형

Go는 정적타입 언어 

정적 타입은 컴파일 타임에 타입이 결정되며 런타임 중에 동적으로 타입이 변경될 수 없다 

대신 빠름(왜? 런타임 중에 타입 추론하거나 다시 최적화를 해야할 필요는 없기 때문에)

 

변수

값을 담는 공간 

변수 이름은 문자 

변수의 초기화는 런타임에 동작 / 사용하지 않는 변수가 있는 경우 에러를 뱉는다. 

또 변수를 선언만하고 명시적으로 초기화 하지 않은 상태에서 디폴트로 할당되는 값은 제로값 / int타입은 0  / 각 타입별 제로값 확인하는 함수는 Go IsZero()

구분자료형제로값
숫자형 int, float64 0
불리언 형 bool false
문자열 string "" (빈 문자열)

 

전역 변수 선언

전역 변수는 함수 내에서 선언하는 것과 달리 단축 구문(:=)을 사용하면 에러 

package main

import "fmt"

var hi string = "hi" // 가능
var hello = "hello" // 가능

goodbye := "goodbye" // 에러 발생

func main(){
    //
}

 

함수 외부에서는 모든 문장이 var , func 같은 키워드로 시작되므로 키워드가 없는 단축 구문은 외부에서 사용할 수 없음

 

여러 개 변수 선언

변수를 여러 개 선언하고 초기화 할때는 반드시 선언하는 벼눗와 초기화 하는 값의 개수가 같고, 타입은 같지 않아도 된다. 

package main

import "fmt"

func main(){
    hi, hello, goodbye := "hi", "hello", "goodbye"
}
 

 

var()을 이용해서도 여러 개 변수 선언 가능 

어떨 때 쓰는지? → 연관성 있는 변수 한번에 선언하기 위해서

package main

import "fmt"

func main(){
        var (
        i = 10
        j = 20
    )
}

 

변수 재할당 , 선언

아래 코드 상으로는 m 변수가 재선언하고 값이 변경됨 

정확히는 재선언은 아니고 string으로 m이 이미 선언되어있기 때문에 값만 변경된 것

package main

import "fmt"

func main() {
    m := "hello"
    m, i := "hihi", false

    fmt.Println(m, i)
}

>>hihi false

 

단축 변수 선언 할때 여러 개의 변수를 함께 선언할때 하나라도 새로운 변수가 있으면 

단축 변수 선언 문법을 사용하더라도 기존 변수에 대해서는 재할당 처리 

package main

import "fmt"

func main() {
    m := "hello"
    m := "hihi"

    fmt.Println(m)
}

>># command-line-arguments
.\test.go:12:4: no new variables on left side of :=

만약,

    m := "hello"
    m = "hihi"
로 :가 제거되면 재선언이 되지 않고 값만 변경되기 때문에 hihi 출력

Type 변환

다른type의 요소들 간의 할당에는 명시적인 변환 필요

package main

import (
    "fmt"
    "math"
)

func main() {
    var x, y int = 3, 4
    var f  = math.Sqrt(float64(x*x + y*y))
// /만약 float64 명시적 타입 선언을 제거하면? 에러 발생  prog.go:10:21: cannot use (x * x + y * y) (value of type int) as type float64 in argument to math.Sq
    fmt.Println(x, y, f)
} -> 3 4 5

 

이름 쉐도잉

패키지, 타입과 똑같은 이름의 변수를 선언해도 에러나지않는다. 

package main

import "fmt"

func main() {
    string := 10

    fmt.Print(string)
} --> 해당 코드는 정상 출력하지만 (10)

package main

import "fmt"

func main() {
    string := 10
    var m string
    m = "hi"

    fmt.Print(m)
} --> 해당 코드는 .\test.go:7:6: string is not a type 에러를 뱉는다. 왜? string이라는 변수가 var m 변수에서는 string 타입을 선언하고 있으니까

상수 

한번 선언되고 할당되면 값을 바꿀 수 없다 

선언과 할당을 동시에 진행되어야 하며 const 키워드를 사용하면 상수를 선언 가능 

자료형 명시도 선택적으로 해주면 된다. 어차피 컴파일러가 추론해줌 

package main

import "fmt"

func main() {
    const max_size int = 10
    const max_size = 10 //type 명시 안해도 가능

    fmt.Print(max_size)
}--> 10

여러 상수 선언

변수 처럼 여러개 선언 가능

package main

import "fmt"

func main() {
    const (
        max_size, lang = 10, "golang"
    )
    fmt.Print(max_size, lang)
}10golang

 

반응형

'Language Study > Go' 카테고리의 다른 글

[Golang]Method  (0) 2022.12.14
[Golang]Quick sort(퀵 정렬)  (0) 2022.12.13
[Golang]bubble sort(버블 정렬)  (0) 2022.12.13
[Golang]array, slice, map  (0) 2022.12.04
[Golang]클래스, 구조체, 인스턴스  (0) 2022.08.08
반응형

구조체 

  • 하나 이상의 변수를 묶어서 새로운 자료형을 정의하는 커스텀 데이터 타입
  • 필드들의 집합체이자, 컨테이너 = 정보의 집합
  • 다른 언어의 구조체와 같이, 내부에 변수를 가지고 있으며, 접근제한 가능, method를 가짐 
  • Go언어에는 class 키워드가 없어 구조체를 사용하여 클래스를 정의한다. 
    • Go 언어는 객체 지향을 따르기 때문에 java의 객체지향은 class, 상속 등의 개념이 존재하지 않으나 Go언어에서는 존재 
    • 객체지향의 class가 field 와 method를 함께 갖는 것과 다르게, Go언어의 구조체는 fIled만 가지고 method는 별도로 분리
  • Go언어의 구조체 vs 클래스의 차이는 인스턴스 만들어 넘길 때 
    • 구조체는 값을 복사해서 넘김
    • 클래스는 그 값이 담긴 인스턴스의 주소를 넘김 

 

** 인스턴스란?

설계도를 바탕으로 소프트웨어 세계에 구현된 구체적인 실체 

>> 객체를 소프트웨어에 실체화 하면 그것을 인스턴스라 부르고, 실체화된 인스턴스는 메모리에 할당

Oop의 관점에서 객체가 메모리에 할당되어 실제 사용될 때 인스턴스라 부른다. 

  • 객체는 클래스의 인스턴스 
  • 객체 간의 링크는 클래스 간의 연관 관계의 인스턴스 

 

구조체 문법

구조체 선언 → string 변수 name을 가지는 구조체를 Dog이라 한다. 

type Dog struct {
  name string
}

구조체 메소드 선언 → Go언어에서의 접근 제한은 패지키 밖에서 해당 패키지를 사용할 때 접근 제한 / 변수 및 메소드는 소문자로 시작하면 패키지 내에서만 사용하며, 대문자면 패키지 외부에서도 사용할 수 있음 

func (d Dog) Sounds(){
  Fmt.Printf("%s : wal\n", d.name)
}

구조체와 Class

인스턴스를 만들 때 구조체는 struct 그대로, 클래스는 struct 를 가리키는 포인터 생성 

package main

import "fmt"

type Dog struct{
   name string
}

type Cat struct{
  name string
}

func NewDog(name string){
  var d Dog 
  d.name = name
  return d
} // 구조체 

func (d Dog) Sounds(){
   Fmt.Printf("%s : wal\n", d.name)
}

func NewCat(name string) *Cat{
  var c Cat
  c.name = name
  return &c
} // 클래스 -> struct를 가리키는 포인터를 생성하고 주소값을 불러오면 된다


func (c *Cat) Sounds(){
  fmt.Printf("%s : yaong\n", c.name)
}

Func main(){
  d1 := NewDog("Baduk")
  d2 := d1
  d2.name = "Nureong"
  d1.Sounds()
  d2.Sounds() c1 := NewCat("Happy")
  c2 := c1
  c2.name = "Merry"
  c1.Sounds()
  c2.Sounds()

}


>> 출력문
Baduk: wal
Nureong: wal
Merry: yaong.
Merry: yaong.

** 서로 연관성이 적은 문법 요소(구조체, 포인터)를 합성하여 새로운 문법을 만들어내는 성질을 직교성이라하는데

언어의 직교성이 높을 수록 문법이 단순해지며 유연해진다. 직교성을 높여 문법을 유연해지게 하는 것이 Go언어의 설계 목표와 부합

 

 

구조체 종류 

중첩 구조체

  • 내장 타입 방식 → 일반 구조체 선언 방식과 같음 / 구조체 필드에 다른 구조체 타입의 필드 선언 / 중첩 구조체의 필드에 접근하기 위해 (구조체).(중첩구조체).(중첩구조체필드명)으로 접근함
package main

import "fmt"

tyue User struct{
  name string
  id string
  age int
}

type VipUser struct{
  UserInfo User // 내장 타입
  VipLevel int
  Price int
}

func main(){
    user1 := User("js", "oh_exp", 29)
    user1_vip := VipUser(user1, 1, 1000)
     
    user2_vip := VipUser{
            User("test", "abc",24),
            1,
            10000}

    fmt.Println(user1_vip.Userinfo.ID)
    fmt.Println(user2_vip.Userinfo.Age)
}
 

포함된 필드 방식

  • 구조체에서 다른 구조체를 포함할때 필드 명을 생략하면 .을 이용하여 한번에 접근 가능하다 / (구조체).(중첩구조체 필드명)으로 바로 접근
package main

import "fmt"

tyue User struct{
  name string
  id string
  age int
}

type VipUser struct{
  User // 포함된 필드 방식
  VipLevel int
  Price int
}

func main(){
    user1 := User("js", "oh_exp", 29)
    user1_vip := VipUser(user1, 1, 1000)
     
    user2_vip := VipUser{
            User("test", "abc",24),
            1,
            10000}

    fmt.Println(user1_vip.ID)
    fmt.Println(user2_vip.)
}
  • 그러나 중복되는 필드가 있을 시는 겹치는 필드가 어느 타입에 해당하는 구조체 필드인지 명시해줘야함
package main

import "fmt"

type User struct {
    Name  string
    ID    string
    Age   int
    Level int // 중첩
}

type VIPUser struct {
    User
    Level int /// 중첩
    Price int
}

func main() {
    user1 := User{"Hoplin", "hoplin1234", 24, 30}
    user1_vip := VIPUser{user1, 1, 10000}
    fmt.Println(user1_vip.Level) // VIPUser구조체의 Level필드
    fmt.Println(user1_vip.User.Level) // VIPUser구조체의 중첩된 User타입의 Level필드
}

메소드

구조체를 클래스처럼 사용한다면, 구조체 밖에서 기능을 표현한 것이 메소드 

구조체 밖에 메소드를 정의할때는 리시버(메소드가 속하는 타입 알려주는 기법)라는 기능을 사용하여 정의

**통상적인 OOP 에서 클래스는 메소드를 멤버로 속성을 표현하는 필드(data)와 기능을 표현하는 메소드(function)을 가진다. / /이중 메소드는 특정 작업을 수행하기 위한 명령문 집합

 

별칭 리시버 타입

타입에 대한 별칭을 지정 /  기존 타입에 대해서도 별칭을 지정할 수 있고, 사용자가 정의한 구조체 타입으로 별칭 지을 수 있음 

 

package main

import "fmt"

type MyInt int // int 타입 별칭

type Info struct {
Name string
age int
} // 구조체

type MyStruct Info //사용자 정의 구조체 타입 별칭

func main() {
var a MyInt = 20

var b int = 10

//MyInt 가 int의 별칭이지만, 별칭 타입이기 때문에 다른 타입으로 인식
fmt.Println(int(a) + b)

var e MyStruct = MyStruct{
"jisu",
29,
}
fmt.Print(e.Name)
} }

 

메소드 선언하기

메소드 선언하기 위해서 리시버 사용 / 리시버는 func 키워드 , 함수 이름 사이 명시

type testStruct struct {
    width int
    height int
}

func (r testStruct) info() int{
    return r.width * r.height
}// 리시버
// 해당 리시버를 통해 testStruct 타입에 속함

 

위를 python으로 바꾸면?

패키지 내에서 선언된 구조체, 별칭 타입들이 리시버가 될 수 있음.

class testStruct(object):
    # 초기화 메서드  (클래스의 객체가 만들어질때 자동으로 호출되어 그 객체가 갖게 될 여러 가지 성질을 정해줌)
    def __init__(self, width : int = 10, height : int = 20) -> None:
        self.width = width
        self.height = height

    def info(self) -> int:
            return self.width * self.height

# 직접 실행시켰을 때만 실행되기를 원하는 코드들을 넣어줌
if __name__ == "__main__":
    t = testStruct(20,20)
    print(t.info())

 

구조체 메소드 선언

package main

import "fmt"

type account1 struct {
    balance int
}

func (a *account1) withdrawPointer(amount int) {
    a.balance -= amount
    // *account 타입에 속한 메소드 / *account 타입의 인스턴스(구조체를 생성해 저장한 변수 (=데이터 실체))들은 해당 메소드를 사용할 수 있음
    // a는 account 구조체 포인터 타입의 메소드 / account 구조체의 필드에 대해 "." 연산자 통해 접근 가능
    // 메소드는 (리시버타입).(메소드)() 형태로 접근
    // 각 리시버 타입의 함수라는 응집성이 있음
}

func withdrawFunc(a *account1, amount int) {
    a.balance -= amount
    // void 반환 타입의 함수
    // 구조체 포인터의 매개변수를 받아 해당 구조체의 데이터를 변경하는 함수
    // (함수명)() 형태로 호출
    // 함수는 응집성이 없음
}

func main() {
    a := &account1{100} // account1 타입의 인스턴스 생성
    withdrawFunc(a, 30)

    a.withdrawPointer(10)
    fmt.Println(a.balance)
}

 

 

별칭 리시버 타입

별칭타입도 리시버가 될 수 있음 

package main

import "fmt"

type MyInt int

func (a MyInt) add(b int) int {
    return int(a) + b
}

func main() {
    var a MyInt = 10
    fmt.Println(a.add(20))
}

 

 

포인터 메소드 vs 값 타입 메소드 

  • 포인터 메소드 : 포인터 메소드 호출 시 호출한 구조체의 메모리 주소가 복사 / 복사된 주소에 있는 구조체에 대해 메소드 연산 수행
  • 값 타입 메소드 : 호출한 구조체의 필드가 새로운 구조체 필드로 복사 / 값 타입 메소드를 호출한 구조체 인스턴스와 메소드 내에서 사용하는 구조체 인스턴스는 서로 다른 구조체 
    (그래서 아래 A.ValueMethod(20) fmt.Println(A.balance) // 70 가 위의 포인터 메소드 호출과 값이 같았던 이유)
  • 값 반환 타입 메소드 : 값 타임 메소드와 원리는 동일하고 거기에 구조체 반환하는 점이 추가 / 구조체 반환하면 반환된 구조체 필드 값들이 새로운 구조체 필드에 복사된 후 변수에 저장 
    (결론적으로 값이 변경된 구조체를 복사한 것이기 때문에 값이 변경되긴 함)
package main

import "fmt"

// 구조체 생성
type account struct {
    balance   int
    firstName string
    lastName  string
}

//포인터 메소드
func (a1 *account) PointMethod(amount int) {
    a1.balance -= amount
}

//값 타입 메소드
func (a2 account) ValueMethod(amount int) {
    a2.balance -= amount
}

//수정된 값을 반환하는 값 타입 메소드
func (a3 account) modifiedValueMethod(amount int) account {
    a3.balance -= amount
    return a3
}

func main() {
    var A *account = &account{100, "oh", "jisu"}
    A.PointMethod(30)
    fmt.Println(A.balance) //70

    A.ValueMethod(20)
    fmt.Println(A.balance) // 70

    var B account = A.modifiedValueMethod(20)
    fmt.Println(B.balance) // 50

    B.PointMethod(30)
    fmt.Println(B.balance) //20
    fmt.Println(A.balance) // 70
}
반응형

'Language Study > Go' 카테고리의 다른 글

[Golang]Method  (0) 2022.12.14
[Golang]Quick sort(퀵 정렬)  (0) 2022.12.13
[Golang]bubble sort(버블 정렬)  (0) 2022.12.13
[Golang]array, slice, map  (0) 2022.12.04
[Golang]변수, 상수  (0) 2022.08.21
반응형

캐싱이란?

- 애플리케이션 처리 속도를 높여주는 이미 가져온 데이터나 계산된 결과값의 복사본을 저장하여 처리 속도를 향상시키고 , 이를 통해 향후 요청을 더 빠르게 처리할 수 있다. 

- 대부분의 프로그램이 동일한 데이터나 명령어에 반복하여 액세스하기 때문에 캐싱은 효율적인 아키텍쳐 패턴

 

캐싱 적합한 데이터

  • 반복적이고 동일한 결과가 나오는 기능의 반환값
  • 업데이트가 자주 발생하지 않는 데이터
  • 자주 조회되는 데이터
  • 입력값과 출력값이 일정한 데이터
  • 캐싱된 데이터는 데이터 갱신으로 인해 DB와 불일치가 발생할 수 있다.
    => 그렇기 때문에 데이터 Update가 잦게 일어나거나 데이터 불일치시 비즈니스 로직 상 문제가 발생할 수 있는 기능은 캐싱 대상으로 적합하지 않음

 

캐싱 타입

  • Local Cache
    • 서버마다 캐시를 따로 저장한다.
    • 다른 서버의 캐시를 참조하기 어렵다.
    • 서버 내에서 작동하기 때문에 속도가 빠르다.
    • 로컬 서버 장비의 Resource를 이용한다. (Memory, Disk)
    • 캐시에 저장된 데이터가 변경되는 경우:
      • 해당 서버를 제외한 모든 peer에 변경 사항 전달
      • All-to-All Replication
      • WAS 인스턴스가 늘어나고, 캐시 저장 데이터 크기가 커지면 성능이 저하되는 이유는 이 때문
  • Global Cache
    • 여러 서버에서 캐시 서버에 접근하여 참조 할 수 있다.
    • 별도의 캐시 서버를 이용하기 때문에 서버 간 데이터 공유가 쉽다.
    • 네트워크 트래픽을 사용해야 해서 로컬 캐시보다는 느리다.
    • 데이터를 분산하여 저장 할 수 있다.
      • Replication: 두 개의 이상의 DBMS 시스템을 Mater / Slave 로 나눠서 동일한 데이터를 저장하는 방식
      • Sharding: 같은 테이블 스키마를 가진 데이터를 다수의 데이터베이스에 분산하여 저장하는 방법
    • 캐시에 저장된 데이터가 변경되는 경우:
      • 추가적인 작업 필요없음
      • 서비스 확장으로 WAS 인스턴스가 늘어나고, Cache 데이터 크기가 커질 수록 효과적인 이유

 

캐싱용 인메모리 DB (Redis VS Memacached)

두 기능 모두 NoSQL 형식으로 키-값 형태를 이루어 두 솔루션 모두 캐시 레이어로서 동작 

Memcachedsms In-Memory Key-Value 저장소라 한다면, Redis는 단순한 KEY-VALUE를 저장소에서 더 나아가 일종의 데이터 구조 스토어라고 합니다. 

자바 언어에서는 Memcached는 Xmemcached과 Memcached-java-client를 제공하고

Redis는 Jedis, Lettuce, Redisson을 제공합니다.

 

 

주요 특징 비교 

 

 

데이터 자료형, 그에 따른 메모리 사용량

Memcached : key와 value가 String 자료형으로 최대 1MB까지 저장 / String으로만 구성되어 Redis 보다 빠르고 
Redis의 Hash로 직렬화, 역직렬화 과정을 거치지 않고 객체를 저장할 수 있어 애플리케이션 개발이 수월해지고 IO 과정이 줄어 들어 효율적

Redis :  5개의 데이터 자료형(String, Hash, List, Set, Sorted set)을 사용하며 키와 값 모두 512MB까지 저장 가능 

 

구조 , 확장법

Memcached : 멀티 코어 구조로 된 멀티 쓰레드를 지원 / vertical scale up(=수평적 확장) 으로 확장성 얻음

Redis :  싱글 쓰레드 구조 / 슈평, 수직 확장 가능 / 수평적 확장은 노드 그룹(=샤드) 개수 조정 , 수직 확장은 클러스터 크기 증가

 

Data Eviction 알고리즘(=메모리 여유공간 없을 때 자원 쫓아낼때 사용되는 알고리즘)

Memcached : LRU(Least Recently Used)

Redis : 하기 8가지 정책 중 선택

noeviction 메모리가 다 찬 경우 에러 표시
allkeys-LRU 가장 사용되지 않은 데이터 축출
volatile-LRU 가장 사용되지 않음 + 만료 기간 설정
allkeys-random 랜덤하게 축출
volatile-random 랜덤 + 만료 기간 축출
volatile-TTL 제일 짧은 TTL + 만료 기간 설정
volatile-lfu 제일 사용되지 않음 + 만료 기간 설정(Redis 4.0부터 추가)
allkeys-lfu 제일 사용되지 않는 데이터 축출(Redis 4.0부터 추가)

 

트랜잭션

Memcached : 원자성은 있지만, 트랜잭션 미제공  / 멀티 쓰레드 구조로 여러 쓰레드가 한번에 동일한 트랜잭션 기능에 접근하게되면 원자성을 보장하게되도, 다른 쓰레드의 값과 겹쳐질 수 있음. 

Redis : 트랜잭션 기능 제공 / WATCH , MULTI , EXEX 등의 명령어 기반 optimistic lock 기반 트랜잭션 지원 

반응형

'Web > web dev' 카테고리의 다른 글

Proxy와 사용자 IP(X-Forwarded-For(XFF))  (0) 2022.05.18
HTTP/3  (0) 2022.01.25
레이턴시, 대역폭  (0) 2021.12.16
로드밸런스(L4 vs L7)  (0) 2021.12.16
LDAP , AD  (0) 2021.05.31
반응형

개요

X-Forwarded-For(XFF) 란?
- XFF 는 HTTP Header 중 하나로 HTTP Server 에 요청한 Client 의 IP 를 식별하기 위한 표준

- 웹 서버나 WAS 앞에 L4 같은 Load balancers 나 Proxy server, caching server 등의 장비가 있을 경우,
  웹서버는 Proxy server 이나 장비 IP 에서 접속한 것으로 인식

==> 웹 서버는 실제 클라이언트 IP가 아니라 앞단 Proxy 서버 를 요청한 IP로 인식 / Proxy  장비 IP로 웹 로그 남김

 

웹 프록시 동작에서의 사용자 IP 변환

1번 과정에서 클라는 웹 서버와 세션을 맺으려하나 실제로는 웹 프록시와 TCP 세션을 맺게 되며

3번 과정에서 웹 서버는 클라이언트와 TCP 세션을 맺으려 하나 웹 프록시와 TCP 세션을 맺게 됨.

==> 지연 바인딩 과정(Delayed Binding)

 

3번의 과정에서부터 웹 프록시와 웹 서버 간의 동작에서 소스 IP가 웹프록시 IP로 변경되며 

그로 인해 웹 서버는 실제 사용자 IP를 확인하지 못합니다.

 

 

IP 변경에 따른 고려사항

사용자 IP가 변경되게 된다면

해당 데이터를 통해 수집되는 로그에서 사용자 IP를 구별할 수 없어 

사용자 IP 기반의 서비스 및 정책들에 제약이 걸려 사용할 수 없게 되기 때문에 클라이언트 IP를 찾을 수 있어야 합니다. 

 

 

XFF

HTTP 프록시나 LB를 통해 웹 서버에서 접속하는 클라이언트의 실제 IP주소를 식별하는 표준 헤더로 

문법은 아래와 같습니다. 

X-Forwarded-For: <client>, <proxy1>, <proxy2>

- client : 클라이언트  ip 주소

- proxy1, proxy2 : 하나의 요청이 여러 프록시를 거치면 각 프록시 ip주소들이 차례로 열거 / 가장 오른쪽 ip 주소가 가장 마지막에 거친 프록시 ip 주소

 

관련 코드 

String ip = request.getHeader("X-Forwarded-For");

if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
    ip = request.getHeader("Proxy-Client-IP");
     # WAS(WebLogic)의 webserver 연계 모듈인 weblogic connector 에서 사용되는 헤더
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
    ip = request.getHeader("WL-Proxy-Client-IP");
    # WAS(WebLogic)의 webserver 연계 모듈인 weblogic connector 에서 사용되는 헤더
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
    ip = request.getHeader("HTTP_CLIENT_IP");
     # PHP . ASP 에서 실제 Clinet IP를 구할때 사용하는 헤더
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
    ip = request.getHeader("HTTP_X_FORWARDED_FOR");
    # PHP . ASP 에서 실제 Clinet IP를 구할때 사용하는 헤더
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
    ip = request.getRemoteAddr();
}

 

PHP 사용방식

function getRealClientIp() {
    ...
    if (getenv('HTTP_CLIENT_IP')) {
        ip = getenv('HTTP_CLIENT_IP');
    }
    if(getenv('HTTP_X_FORWARDED_FOR')) {
        ip = getenv('HTTP_X_FORWARDED_FOR');
    }
    ...
}

 

ASP 사용방식

string ip = Request.ServerVariables["HTTP_X_FORWARDED_FOR"]; 
if(string.IsNullOrEmpty(ipaddr)) {
    ip = Request.ServerVariables["REMOTE_ADDR"];
}
반응형

'Web > web dev' 카테고리의 다른 글

Cashing- 캐싱 개요 및 인메모리 DB 비교  (0) 2022.06.13
HTTP/3  (0) 2022.01.25
레이턴시, 대역폭  (0) 2021.12.16
로드밸런스(L4 vs L7)  (0) 2021.12.16
LDAP , AD  (0) 2021.05.31

+ Recent posts