Go 기초 4. 배열, 구조체, 포인터

JINSOO PARK·2021년 10월 13일
0

배열 (Array) .1

배열이란 연관된 데이터를 하나의 변수에 그룹핑해서 관리하기 위한 방법이다.

배열의 선언 방법

var 배열명 [배열수] 데이터타입 {1,2,3,..}

A := [배열수] 데이터타입 {1,2,3,...}

ex1)

package main

import "fmt"

func main() {
	var A [10]int

	for i := 0; i < len(A); i++ { // 배열 (A) 의 length 길이가 들어가게 된다.
		A[i] = i * i
	}
	fmt.Println(A)
}
--------------------------------
[0 1 4 9 16 25 36 49 64 81]

배열의 특징

  • 메모리 주소를 가진다.
  • 길이(데이터 타입)x갯수
  • 문자열(string)도 배열이다.

문자열

문자열 배열은 기본 byte배열(==uint8 0~255)
ASCII 코드: 모든 코드가 1byte

UTF-8 : 영어,빈칸,기호 1byte 나머지 2~3byte

ex2)

package main

import "fmt"

func main() {
s := "Hello World"

	for i := 0; i < len(s); i++ {
		fmt.Print(s[i], ",")
	}

}
---------------------------------
72,101,108,108,111,32,87,111,114,108,100, // 각 문자에 해당하는 코드가 나온다.

ex3)

package main

import "fmt"

func main() {
s := "Hello World"

	for i := 0; i < len(s); i++ {
		fmt.Print(string(s[i]), ",")
	} // string으로 문자를 표현한다.

}
---------------------------------
H,e,l,l,o, ,W,o,r,l,d,

ex4) 한글의 경우

package main

import "fmt"

func main() {
s := "Hello 월드"

	for i := 0; i < len(s); i++ {
		fmt.Print(string(s[i]), ",")
	} 

}
---------------------------------
H,e,l,l,o, ,ì,,ë,,, //한글은 3바이트 3가지 코드가 합쳐져서 만들어 졌기 때문에 문자열 배열에 들어가게 되면 각 byte들이 나오게 되어 글자가 깨져서 나오게 된다.

ex5) 한글의 코드

package main

import "fmt"

func main() {

	s := "Hello 월드"

	for i := 0; i < len(s); i++ {
		fmt.Print(s[i], ",") // 코드를 출력 했을때
	}
}
------------------------------------
72,101,108,108,111,32,236,155,148,235,147,156, // 8개가 아닌 12개의 코드가 나온걸 볼 수 있다.

rune 배열

문자열(string)은 byte배열로 나타낼 수 있지만 rune배열로도 나타낼 수 있다.

rune: 고랭의 변수 데이터타입 중 하나로 UTF-8을 나타내는 타입이다. 그렇기 때문에 타입의 길이는 1 ~ 3byte로 어떤 문자냐에 따라서 길이가 변한다.

ex6)

package main

import "fmt"

func main() {
s := "Hello 월드"

	s2 := []rune(s) //데이터 타입을 rune으로 설정하고 s를 넣음
	fmt.Println("len(s2) = ", len(s2))
	for i := 0; i < len(s2); i++ {
		fmt.Print(s2[i], ", ")
	}
}
-------------------------------
len(s2) =  8
72, 101, 108, 108, 111, 32, 50900, 46300,   // byte와 달리 8개가 나옴

ex7)

package main

import "fmt"

func main() {
s := "Hello 월드"

	s2 := []rune(s)
	fmt.Println("len(s2) = ", len(s2))
	for i := 0; i < len(s2); i++ {
		fmt.Print(string(s2[i]), ", ")
	} // 문자열로 표현해 보았을때
}
---------------------------
len(s2) =  8
H, e, l, l, o,  , 월, 드, 

배열 (Array) .2

1.배열 복사

ex1)

package main

import "fmt"

func main() {
	arr := [5]int{1, 2, 3, 4, 5}
	clone := [5]int{}

	for i := 0; i < 5; i++ {
		clone[i] = arr[i] // clone에 arr의 배열을 초기화 시킴
	}
	fmt.Print(clone)
}
-----------------------------
[1 2 3 4 5]

2. 배열의 역순

ex2) 두번 복사

package main

import "fmt"

func main() {
arr := [5]int{1, 2, 3, 4, 5} //1~5까지 순서대로
	temp := [5]int{}

	for i := 0; i < len(arr); i++ {
		temp[i] = arr[len(arr)-1-i]
	} // temp에다가 arr[4]~ arr[0]까지 역순으로 집어넣음

	for i := 0; i < len(arr); i++ {
		arr[i] = temp[i] 
	} //역순으로 집어넣은걸 다시 arr에 집어넣음
	fmt.Println("temp:", temp)
	fmt.Println("arr:", arr)
}
----------------------------
temp: [5 4 3 2 1]
arr: [5 4 3 2 1]

ex3) 복사를 사용하지 않고

package main

import "fmt"

func main() {
	arr := [5]int{1, 2, 3, 4, 5}

	for i := 0; i < len(arr)/2; i++ {
		arr[i], arr[len(arr)-1-i] = arr[len(arr)-1-i], arr[i]
	} // arr[0],arr[4] = 5, 1 이라는 의미
	fmt.Println(arr)
}
----------------------------
[5 4 3 2 1]
  • 복사를 두번한 경우: for문 2번 사용하여 총 길이에 2배가 되어 처리 속도가 느림

  • 복사를 사용하지 않은 경우: 1/2 만큼 for문을 돌려서 처리 속도가 비교적 빠름


3. 정렬 Radix Sort

정렬은 배열에 숫자가 무작위로 들어가 있을 경우 숫자를 순서대로 정렬하는 것을 말한다.
알파벳도 가능

Radix Sort

임시 배열(temp)을 하나 생성한 후 정렬을 원하는 배열(arr)의 랜덤한 숫자의 순서를 정렬 시키고, 만약 같은은 수가 있으면 갯수를 세아린 후 순서대로 arr에 집어 넣어 정렬 시킨다.

특징

  • 특정 조건에서만 사용되어 질 수 있다.

  • 원소의 값의 범위가 제한되어 있어야 한다.

  • 똑같은 메모리의 배열을 또하나 만들기 때문에 배열 원소의 범위가 작아야한다.

ex1) Radix Sort

package main

import "fmt"

func main() {
	arr := [11]int{0, 5, 4, 9, 5, 4, 3, 2, 1, 9, 5}
	temp := [10]int{}

	for i := 0; i < len(arr); i++ {
		idx := arr[i] // arr[i] 에 해당하는 값을 idx로 한다
		temp[idx]++   // 각 원소 들이 몇번 나오는지 횟수를 저장한다.
	}

	idx := 0                         // 현재 인덱스
	for i := 0; i < len(temp); i++ { // temp의 갯수 10만큼 돈다
		for j := 0; j < temp[i]; j++ { //temp에 저장된 숫자 = 갯수 이므로 그 숫자 만큼 반복한다.
			arr[idx] = i
			idx++ // idx 0 부터 증가시켜 숫자를 정렬 시킴
		}
	}
	fmt.Println(arr)
}
---------------------------
[0 1 2 3 4 4 5 5 5 9 9]

structure

변수들을 하나로 묶어서 관리하기 쉽게 해줘서 응집성을 올려준다.

구조체 선언방법

type 구조체명 struct {
변수명 타입
변수명 타입
}

ex1) 기본형태

package main

import "fmt"

type Person struct {
	name string
	age  int
}   // name, age 변수를 구조체로 만듦

func main() {
	var p Person
	p1 := Person{"개똥이", 15}
	p2 := Person{name: "소똥이", age: 21}
	p3 := Person{name: "Carson"}
	p4 := Person{}

	fmt.Println(p, p1, p2, p3, p4)

	p.name = "Smith" //변수에 "."을 찍어서 불러 올 수 있다.
	p.age = 24

	fmt.Println(p)
}
----------------------------
{ 0} {개똥이 15} {소똥이 21} {Carson 0} { 0}
{Smith 24}

c언어에서는 속성(statement)만 가지고 있었지만, go언어에서는 구조에 속성(statement)과 기능(method)을 더 해서 first class라고 불린다.

ex2) 기능(method) 추가

package main

import "fmt"

type Person struct {
	name string
	age  int
}

func (p Person) PrintName() { // Person 이라는 객체에 PrintName이라는 기능을 추가한다.
	fmt.Print(p.name)
}

func main() {
	var p Person

	p.name = "Smith"
	p.age = 24

	fmt.Println(p)

	p.PrintName() // 매소드 호출
}
---------------------------------
{Smith 24}
Smith

ex3) 성적 조회

package main

import "fmt"

type Student struct {
	name  string
	class int

	grade Grade // grade에 Grade라는 타입을 설정해줌
}

type Grade struct {
	name  string
	grade string
}

func (s Student) ViewGrade() { // Student의 기능 ViewGrade로 성적 조회가 가능한 기능
	fmt.Println(s.grade)
}

func (s Student) Studentinfo() {
	fmt.Println(s.name, s.class, "반")
} // 이름과 반을 출력해주는 기능

func main() {

	var s Student
	s.name = "철수"
	s.class = 1

	s.grade.name = "수학"
	s.grade.grade = "C"

	s.Studentinfo()
	s.ViewGrade()
}
--------------------------
철수 1 반
{수학 C}

포인터

포인터는 변수가 가지는 메모리의 주소를 가르킨다.

*go언어에서는 c,c++ 과는 다르게 연산이나 캐스팅을 막아놓았다.

포인터 선언

var a *int //타입에 *을 붙여서 선언한다

ex1)

package main

import "fmt"

func main() {
	var a int
	var b int
	var p *int // *로 포인터 변수를 선언

	a = 3
	b = 2
	p = &a // &로 주소값을 받는다.

	fmt.Println(a)
	fmt.Println(p)  // p 는 a의 주소를 받았기 때문에 주소가 나옴
	fmt.Println(*p) // *p를 하면 p가 받은 주소에 저장된 값이 나옴

	p = &b // b의 주소를 받음

	fmt.Println(b)
	fmt.Println(p) 
	fmt.Println(*p)
}
------------------------------------------
3
0xc0000aa058
3
2
0xc0000aa070
2

포인터가 필요한 이유

ex2) 포인터를 사용하지 않았을 때

package main

import "fmt"

func main() {
	var a int
	a = 1
	Increase(a)  // 함수로 a 의 값을 줌

	fmt.Println(a) //함수에 들어갔다 나온 값을 출력함
}

func Increase(x int) {
	x++  // 인자로 받은 값을 증가 시켜줌
}
---------------------------
1   // 증가되지 않은 것을 알 수 있음
x란 변수는 a로부터 받은 값을 복사해서 가지고 있기 때문에 x를 증가 시켜도 a는 변하지 않음

ex3) 포인터를 사용하였을 때

package main

import "fmt"

func main() {
	var a int
	a = 1
	Increase(&a) // 함수로 a의 주소를 넘겨줌

	fmt.Println(a)
}

func Increase(x *int) { // x는 포인트 변수로 a에서 받은 주소를 저장함
	*x++ //해당 주소에 저장된 값에 1을 더함
}
--------------------------------
2 // a의 값이 증가 된 것을 알 수 있다.

구조체와 포인터

ex4) 포인터를 사용하지 않았을 때

package main

import "fmt"

type Student struct {
	name string
	age  int

	grade string
	class string
} // 변수들이 포함된 구조체를 만듦

func (a Student) PrintSungjuk() {
	fmt.Println(a.class, a.grade)
} // Student에 포함된 class와 grade을 출력해주는  메소드를 만듦

func (b Student) InputSungjuk(class string, grade string) {
	b.class = class
	b.grade = grade
}  // Student에 포함된 성적을 입력하는 메소드를 만듦

func main() {
	var s Student = Student{name: "jinsoo", age: 23, class: "수학", grade: "F"} //구조체를 통해 값을 입력

	s.InputSungjuk("과학", "C") // 성적입력 메소드를 통해 입력 값을 변경
	s.PrintSungjuk() //성적 출력 메소드로 성적을 출력한다.
}

------------------------
수학 F // 성적 입력 메소드로 입력 값을 변경했지만 s 값은 변경 되지 않았다.

ex5) 포인터를 사용하였을 때

package main

import "fmt"

type Student struct {
	name string
	age  int

	grade string
	class string
}

func (a *Student) PrintSungjuk() {
	fmt.Println(a.class, a.grade)
} // a 를 포인터 변수로 설정

func (b *Student) InputSungjuk(class string, grade string) {
	b.class = class
	b.grade = grade
} // b 를 포인터 변수로 설정

func main() {
	var s Student = Student{name: "jinsoo", age: 23, class: "수학", grade: "F"}

	s.InputSungjuk("과학", "C")
	s.PrintSungjuk()
}
--------------------------------
과학 C

*구조체를 호출 하면 구조체 안에있는 변수들이 다 같이 복사되기 때문에 메모리에 낭비가 크지만 포인터 형태로 호출하게 되면 주소만 저장되기 때문에 메모리의 낭비를 줄일 수 있다.

profile
개린이

0개의 댓글

관련 채용 정보