<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>self_controller.go</title>
        <link>https://velog.io/</link>
        <description>주니어 개발자</description>
        <lastBuildDate>Sun, 02 Oct 2022 15:56:33 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>self_controller.go</title>
            <url>https://images.velog.io/images/vamos_eon/profile/0ff8c506-9a42-4e49-8a25-079d2003522a/1639390682300.png</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. self_controller.go. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/vamos_eon" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[Docker desktop 개발 환경 분리하기]]></title>
            <link>https://velog.io/@vamos_eon/Docker-Windows-WSL2-container</link>
            <guid>https://velog.io/@vamos_eon/Docker-Windows-WSL2-container</guid>
            <pubDate>Sun, 02 Oct 2022 15:56:33 GMT</pubDate>
            <description><![CDATA[<p>안녕하세요, 주니어 개발자 Eon입니다.</p>
<p>vscode에서 제공하는 개발용 컨테이너를 만드는 방법에 대해 소개합니다.</p>
<h1 id="vscode-extension-remote-containers">vscode extension; remote-containers</h1>
<h2 id="extension-설치">extension 설치</h2>
<p><img src="https://velog.velcdn.com/images/vamos_eon/post/7eeb649c-3ba4-4b52-977f-ae07f371bacf/image.png" alt=""></p>
<h4 id="설치-링크-remote-containers">설치 링크 <a href="https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers">remote-containers</a></h4>
<p><br><br></p>
<h2 id="에러-발생-및-docker-desktop-설치-진행">에러 발생 및 docker desktop 설치 진행</h2>
<p><strong>위 과정 이전에 <code>Docker desktop</code>을 먼저 설치해도 상관없습니다.</strong>
<img src="https://velog.velcdn.com/images/vamos_eon/post/99ef1e47-01f5-4a17-b32e-1418d3374297/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/vamos_eon/post/1bb4f8b2-55ad-4e04-b130-364bdc47ff4c/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/vamos_eon/post/81a0d2d9-fb2c-4ebe-a0fe-6bbba6a1aa6d/image.png" alt=""></p>
<p><br><br></p>
<h2 id="docker-desktop-의존성-wsl2-설치">Docker desktop 의존성, WSL2 설치</h2>
<p><code>WSL2</code> 커널 업데이트하라는 문구와 함께 설치를 마치면 <code>Restart</code>를 누르도록 합니다.
<img src="https://velog.velcdn.com/images/vamos_eon/post/c659bfaa-b892-43f9-8766-1566ed380edb/image.png" alt=""></p>
<p>커널 업데이트 링크 누르면 아래 주소로 리다이렉트됩니다.</p>
<h4 id="httpslearnmicrosoftcomko-krwindowswslinstall-manualstep-4---download-the-linux-kernel-update-package"><a href="https://learn.microsoft.com/ko-kr/windows/wsl/install-manual#step-4---download-the-linux-kernel-update-package">https://learn.microsoft.com/ko-kr/windows/wsl/install-manual#step-4---download-the-linux-kernel-update-package</a></h4>
<p><img src="https://velog.velcdn.com/images/vamos_eon/post/c411d93b-9193-426c-927d-2a12c2a104d4/image.png" alt=""></p>
<h4 id="볼드체-링크를-누르면-다운로드가-시작됩니다">볼드체 링크를 누르면 다운로드가 시작됩니다.</h4>
<p><img src="https://velog.velcdn.com/images/vamos_eon/post/50a5a42a-463e-46b3-8c87-0b6de49d7df0/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/vamos_eon/post/1844a894-f062-43f1-a9be-be3af8f79cda/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/vamos_eon/post/99fc79fb-5316-45c8-a089-4b29e7e0aa29/image.png" alt=""></p>
<h4 id="powershell-관리자모드로-실행-및-커맨드를-입력합니다">PowerShell 관리자모드로 실행 및 커맨드를 입력합니다.</h4>
<p><img src="https://velog.velcdn.com/images/vamos_eon/post/0e59ba48-c181-4c26-a520-9ab9fe18de6a/image.png" alt=""></p>
<p><br><br></p>
<h2 id="docker-desktop-restart-및-tutorial">Docker desktop Restart 및 Tutorial</h2>
<p>아래는 <code>Docker desktop tutorial</code> 과정입니다.
해당 튜토리얼 과정을 진행해도 좋습니다.
(스킵하시면 <a href="##docker-image-build">## Docker image build</a> 를 보시면 됩니다.)
<img src="https://velog.velcdn.com/images/vamos_eon/post/cd3f00ac-baba-4748-8152-0dd09b25760d/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/vamos_eon/post/0f9444ae-53fb-4d1b-882b-9b11e99fcba5/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/vamos_eon/post/b4983b25-23fc-49a2-8e44-6f10638558a1/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/vamos_eon/post/f92c1224-6bbd-4122-a6c3-e163e5957c7b/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/vamos_eon/post/3b8cfe36-cda6-4f5b-b891-f5772722cac8/image.png" alt=""></p>
<p><br><br></p>
<h2 id="docker-image-build">Docker image build</h2>
<p>컨테이너 환경을 만들기 위해 image를 빌드합니다.
빌드할 베이스 이미지는 <code>microsoft</code>에서 제공하는 우분투로 합니다.
<code>vscode extension; remote-containers</code>를 사용한 이미지 빌드에서는 아래와 같은 file 구조가 필요합니다.</p>
<pre><code>mount_directory
    ⨽ .devcontainer
        ⊢ devcontainer.json
        ⨽ Dockerfile</code></pre><p>저는 vamos라는 디렉터리 를 컨테이너에 마운트해서 사용합니다.
<img src="https://velog.velcdn.com/images/vamos_eon/post/4a727d8e-64e8-4f0b-9bbb-4e7fe8ca55d6/image.png" alt="">
<img src="https://velog.velcdn.com/images/vamos_eon/post/a26e7f63-d5b4-4ff2-bfbb-488c18b19832/image.png" alt=""></p>
<p><code>mount_directory</code> 디렉터리에서 우클릭 후, <code>vscode</code>로 해당 디렉터리를 열고 위와 같이 file 구조를 만듭니다.
(vscode에서 <code>mount_directory</code> 열기)
<img src="https://velog.velcdn.com/images/vamos_eon/post/3b9dfb63-ec1c-483a-b7cd-3956dc553e44/image.png" alt=""></p>
<p><br><br></p>
<h3 id="파일-작성">파일 작성</h3>
<p>참고 <a href="https://code.visualstudio.com/docs/remote/containers#_installation">https://code.visualstudio.com/docs/remote/containers#_installation</a></p>
<blockquote>
</blockquote>
<h4 id="devcontainerjson">devcontainer.json</h4>
<p>json은 주석을 지원하지 않지만, vscode 내 configuration 항목들에 대한 json 내의 주석은 허용됩니다.</p>
<pre><code class="language-json">// For format details, see https://aka.ms/devcontainer.json. For config options, see the README at:
// https://github.com/microsoft/vscode-dev-containers/tree/v0.245.2/containers/ubuntu
{
    &quot;name&quot;: &quot;Ubuntu&quot;,
    &quot;build&quot;: {
        &quot;dockerfile&quot;: &quot;Dockerfile&quot;,
        // Update &#39;VARIANT&#39; to pick an Ubuntu version: jammy / ubuntu-22.04, focal / ubuntu-20.04, bionic /ubuntu-18.04
        // Use ubuntu-22.04 or ubuntu-18.04 on local arm64/Apple Silicon.
        &quot;args&quot;: { &quot;VARIANT&quot;: &quot;ubuntu-18.04&quot; }
    },
&gt;
    // Use &#39;forwardPorts&#39; to make a list of ports inside the container available locally.
    // &quot;forwardPorts&quot;: [],
&gt;
    // Use &#39;postCreateCommand&#39; to run commands after the container is created.
    // &quot;postCreateCommand&quot;: &quot;uname -a&quot;,
&gt;
    // Comment out to connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root.
      &quot;workspaceMount&quot;: &quot;source=${localWorkspaceFolder}/workspace,target=/workspace,type=bind&quot;,
      // container 실행 시, workspaceFolder 설정한 곳에서 시작함
    &quot;workspaceFolder&quot;: &quot;/home/eon&quot;,
      // 사용자 eon으로 시작함
    &quot;remoteUser&quot;: &quot;eon&quot;,
    &quot;features&quot;: {
        &quot;docker-in-docker&quot;: &quot;latest&quot;,
        &quot;git&quot;: &quot;os-provided&quot;,
        &quot;sshd&quot;: &quot;latest&quot;
    }
}</code></pre>
<blockquote>
</blockquote>
<h4 id="dockerfile">Dockerfile</h4>
<pre><code class="language-yaml"># See here for image contents: https://github.com/microsoft/vscode-dev-containers/tree/v0.245.2/containers/ubuntu/.devcontainer/base.Dockerfile
&gt;
# [Choice] Ubuntu version (use ubuntu-22.04 or ubuntu-18.04 on local arm64/Apple Silicon): ubuntu-22.04, ubuntu-20.04, ubuntu-18.04
ARG VARIANT=&quot;ubuntu-18.04&quot;
FROM mcr.microsoft.com/vscode/devcontainers/base:${VARIANT}
&gt;
# [Optional] Uncomment this section to install additional OS packages.
# RUN apt-get update &amp;&amp; export DEBIAN_FRONTEND=noninteractive \
#     &amp;&amp; apt-get -y install --no-install-recommends &lt;your-package-list-here&gt;
&gt;
# vscode가 기본 user이고, 기본 값으로 설정돼 있던 user를 eon으로 변경함
ARG USERNAME=eon
ARG OLDNAME=vscode
RUN usermod -l $USERNAME $OLDNAME \
    &amp;&amp; groupmod -n $USERNAME $OLDNAME \
    &amp;&amp; usermod -d /home/$USERNAME -m $USERNAME
&gt;
# [Optional] https://code.visualstudio.com/remote/advancedcontainers/add-nonroot-user
RUN echo $USERNAME ALL=\(root\) NOPASSWD:ALL &gt; /etc/sudoers.d/$USERNAME \
    &amp;&amp; chmod 0440 /etc/sudoers.d/$USERNAME
&gt;   
USER $USERNAME</code></pre>
<p><br><br></p>
<h3 id="이미지-빌드">이미지 빌드</h3>
<p><code>vscode</code>에서 <code>F1</code>을 누르고, <code>Remote-Containers: Rebuild Container</code>를 선택하면 빌드가 시작되고, 컨테이너가 실행됩니다.</p>
<p><img src="https://velog.velcdn.com/images/vamos_eon/post/0f58ec90-d8c1-4219-b4dc-ac09133d36ce/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/vamos_eon/post/eae377ad-b045-4e86-b822-07fe4c210add/image.png" alt=""></p>
<p>빌드된 이미지는 로컬 환경에서 <code>Docker desktop</code>을 통해 관리합니다.</p>
<p><a href="https://code.visualstudio.com/remote/advancedcontainers/overview">https://code.visualstudio.com/remote/advancedcontainers/overview</a></p>
<hr>
<p>이상 개발용 컨테이너 생성에 대한 내용이었습니다.
감사합니다.👍</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Golang 기초 (14) : 메서드에 대하여]]></title>
            <link>https://velog.io/@vamos_eon/go-method</link>
            <guid>https://velog.io/@vamos_eon/go-method</guid>
            <pubDate>Sun, 22 May 2022 15:56:35 GMT</pubDate>
            <description><![CDATA[<p>안녕하세요, 주니어 개발자 Eon입니다.</p>
<p>이번에 다룰 내용은 메서드입니다.</p>
<h1 id="📝-method">📝 Method</h1>
<p>메서드는 함수와 큰 차이가 없으나, <strong>객체에 속한다</strong>는 특징이 있습니다.
함수는 객체와 상관없이 호출하면 사용이 가능하지만, 메서드는 해당 <strong>객체를 통해서만 호출</strong>이 가능합니다.</p>
<pre><code class="language-go">package main

import &quot;fmt&quot;

type Parameter struct {
    XX string
}

func nameOfFunction(p *Parameter) {
    p.XX = &quot;YY&quot;
}

func main() {
    p := Parameter{}
    p.XX = &quot;XX&quot;
    nameOfFunction(&amp;p)
    fmt.Println(p)
}</code></pre>
<pre><code class="language-go">package main

import &quot;fmt&quot;

type Receiver struct {
    XX string
}

func (r *Receiver) nameOfMethod() {
    r.XX = &quot;YY&quot;
}

func main() {
    r := Receiver{}
    r.XX = &quot;XX&quot;
    r.nameOfMethod()
    fmt.Println(r)
}</code></pre>
<br>

<h2 id="📌-왜-method를-사용할까">📌 왜 Method를 사용할까?</h2>
<p>그렇다면 왜 제약조건이 더 붙은 메서드를 사용할까?
유지보수 측면에서 더 유리할 수 있습니다.
프로그램이 동작하는 데에 있어서는 큰 차이가 없으나, 객체의 타입마다 사용할 수 있는 메서드를 제한할 수 있습니다.</p>
<ul>
<li>어떤 객체에서 에러가 발생<ul>
<li>그 객체의 메서드를 디버깅하여 개선할 수 있음</li>
</ul>
</li>
<li><strong>기능을 분리하여 객체지향 모델을 만들 수 있음</strong><ul>
<li>기능을 독립적으로 수행하게 하기 위함</li>
</ul>
</li>
</ul>
<br>

<h3 id="📍-receiver">📍 Receiver</h3>
<blockquote>
<p>메서드는 위에 함수와 다르게 함수명과 함수 선언부 사이에 리시버가 들어갑니다.
<code>Receiver</code>는 어떤 타입의 객체가 사용할 수 있는 메서드인지 제한하고, 해당 타입의 객체만 이 메서드를 사용할 수 있게 합니다.</p>
</blockquote>
<pre><code class="language-go">func (s *Student) StudentInfoUpdate() {
// TODO: update student&#39;s information
...
}</code></pre>
<blockquote>
<p>여기서 <code>StudentInfoUpdate()</code>라는 메서드는 <code>Student</code>라는 타입의 <code>리시버</code>가 지정되어 있습니다.
<strong><code>Student</code> 객체 말고는 이 메서드를 사용할 수 없습니다.</strong></p>
</blockquote>
<hr>
<br>

<h2 id="📌-사용법">📌 사용법</h2>
<p>함수와 마찬가지로 <strong>포인터형</strong>과 <strong>밸류형</strong>이 있습니다.</p>
<p><strong>포인터형</strong>은 당연하게도 <strong><code>인스턴스</code>를 직접 조작</strong>합니다.
<strong>밸류형</strong>은 값을 복사해서 사용하기 때문에 포인터형과 다르게 <strong><code>인스턴스</code>를 새로 만들어서 조작</strong>합니다.</p>
<h3 id="📍-포인터형-리시버--메서드">📍 포인터형 리시버 / 메서드</h3>
<pre><code class="language-go">package main

import &quot;fmt&quot;

type Student struct {
    Name  string
    Age   int8
    Email string
}

func (s *Student) HappyNewYear() {
    s.Age += 1
}

func (s *Student) UpdateEmail(NewEmail string) {
    s.Email = NewEmail
}

func main() {
    s1 := new(Student)
    s1.Name = &quot;David&quot;
    s1.Age = 20
    s1.Email = &quot;empty@email.invalid&quot;
    s1.HappyNewYear()
    s1.UpdateEmail(&quot;david@email.com&quot;)
    fmt.Println(*s1)
}

/* output
{David 21 david@email.com}
*/</code></pre>
<p>위와 같이 반환값이 없어도 <strong><code>포인터</code>로 <code>인스턴스</code>를 직접 조작하기 때문에 변경된 값이 유지</strong>되는 것을 볼 수 있습니다.</p>
<br>

<h3 id="📍-밸류형-리시버--메서드-반환-없이">📍 밸류형 리시버 / 메서드 (반환 없이)</h3>
<pre><code class="language-go">package main

import &quot;fmt&quot;

type Student struct {
    Name  string
    Age   int8
    Email string
}

func (s Student) HappyNewYear() {
    s.Age += 1
}

func (s Student) UpdateEmail(NewEmail string) {
    s.Email = NewEmail
}

func main() {
    s2 := Student{}
    s2.Name = &quot;David&quot;
    s2.Age = 20
    s2.Email = &quot;empty@email.invalid&quot;
    s2.HappyNewYear()
    s2.UpdateEmail(&quot;david@email.com&quot;)
    fmt.Println(s2)
}


/* output
{David 20 empty@email.invalid}
*/</code></pre>
<blockquote>
<p>위와 같이 반환하지 않고 객체를 밸류형으로 조작하는 경우, 호출된 메서드에서 <strong><code>새로운 인스턴스</code></strong> 가 만들어집니다.</p>
</blockquote>
<pre><code class="language-go">func (s Student) UpdateEmail(NewEmail string) {
    s.Email = NewEmail
    fmt.Println(s.Email)
}
/* output
david@email.com
*/</code></pre>
<blockquote>
<p>이렇게 <code>UpdateEmail()</code> 메서드 내에서는 값이 정상적으로 변경된 것을 확인할 수 있습니다.
<strong>값 반환을 하지 않았기 때문에 해당 메서드 내에서 변경된 값은 의도한 <code>s2</code> 객체에 아무런 영향을 주지 않습니다.</strong></p>
</blockquote>
<br>

<h3 id="📍-밸류형-리시버--메서드-반환-포함">📍 밸류형 리시버 / 메서드 (반환 포함)</h3>
<pre><code class="language-go">package main

import &quot;fmt&quot;

type Student struct {
    Name  string
    Age   int8
    Email string
}

func (s Student) HappyNewYear() Student {
    s.Age += 1
    return s
}

func (s Student) UpdateEmail(NewEmail string) string {
    s.Email = NewEmail
    return s.Email
}

func main() {
    s2 := Student{}
    s2.Name = &quot;David&quot;
    s2.Age = 20
    s2.Email = &quot;empty@email.invalid&quot;
    s2 = s2.HappyNewYear()
    s2.Email = s2.UpdateEmail(&quot;david@email.com&quot;)
    fmt.Println(s2)
}


/* output
{David 21 david@email.com}
*/</code></pre>
<p>위와 같이 알맞게 값을 반환했더니 의도한 대로 <code>Age</code>와 <code>Email</code>이 변경된 것을 확인할 수 있습니다.</p>
<hr>
<br>

<p>이번 포스트는 메서드에 대한 내용이었습니다.</p>
<p>감사합니다.👍</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Golang 기초 (13) : 슬라이스에 대하여]]></title>
            <link>https://velog.io/@vamos_eon/go-slice</link>
            <guid>https://velog.io/@vamos_eon/go-slice</guid>
            <pubDate>Mon, 18 Apr 2022 18:25:42 GMT</pubDate>
            <description><![CDATA[<p>안녕하세요, 주니어 개발자 Eon입니다.
이번 포스트는 <strong>슬라이스</strong>에 관한 내용입니다.</p>
<br>

<h1 id="📝-슬라이스">📝 슬라이스</h1>
<h2 id="📌-슬라이스란">📌 슬라이스란?</h2>
<p>Golang의 동적 배열입니다.
배열과 다르게 배열 길이의 확장과 축소가 가능합니다.</p>
<br>

<h2 id="📌-슬라이스-변수의-선언">📌 슬라이스 변수의 선언</h2>
<h3 id="📍-일반적-선언">📍 일반적 선언</h3>
<blockquote>
</blockquote>
<pre><code class="language-go">var intSlice1 []int
var intSlice2 []int = []int{1, 2, 3, 4, 5}
var intSlice3 = []int{6, 7, 8, 9, 10}
intSlice4 := []int{}
intSlice5 := []int{11, 12, 13, 14, 15}</code></pre>
<p>슬라이스 변수의 요소 타입을 <code>int</code>로 선언하려면 위와 같이 <code>[]int</code>로 타입을 지정하면 됩니다.</p>
<br>

<h3 id="📍-슬라이스-안의-슬라이스">📍 슬라이스 안의 슬라이스</h3>
<blockquote>
</blockquote>
<pre><code class="language-go">var slSlice1 [][]int
var slSlice2 [][]int = [][]int{{1, 2, 3, 4, 5}, {6, 7, 8}}
var slSlice3 = [][]int{{6, 7, 8}, {1, 2, 3, 4, 5}}
slSlice4 := [][]int{}
slSlice5 := [][]int{{}} // [][]int{{}, {}} // [][]int{{}, {}, {}}
slSlice6 := [][]int{{6, 7, 8}, {1, 2, 3, 4, 5}}</code></pre>
<p>위와 같이 슬라이스를 이중으로 사용할 수도 있습니다.
이 경우, 동적 이차원 배열이 됩니다.</p>
<br>

<h3 id="📍-슬라이스-인덱스-별-지정하여-선언">📍 슬라이스 인덱스 별 지정하여 선언</h3>
<blockquote>
</blockquote>
<pre><code class="language-go">var intSlice1 []int = []int{0: 1, 3: 4, 5, 6}
fmt.Println(intSlice1)
/* output
[1 0 0 4 5 6]
*/</code></pre>
<p>위와 같이 인덱스를 지정하여 선언할 수 있습니다.
초기화되지 않은 인덱스는 <code>zero-value</code>로 초기화됩니다.</p>
<blockquote>
</blockquote>
<pre><code class="language-go">var intSlice1 []int = []int{3: 4, 7, 8, 9, 0: 1, 5, 6}
fmt.Println(intSlice1)
/* output
[1 5 6 4 7 8 9]
*/</code></pre>
<p>이렇게 인덱스 순서를 다르게 하면 마지막에 지정한 인덱스 뒤에, 인덱스를 지정하지 않은 값들을 순서대로 초기화됩니다.
위의 상황에서 <code>5</code>, <code>6</code>으로 초기화 시킨 값 뒤에 인덱스 하나를 더 추가할 경우, <code>0</code>번 인덱스 초기화 뒤에 이미 2개의 값이 들어갔고 그 뒤 <code>3</code>번 인덱스로 인식이 되어 <code>duplicated index</code> 에러가 발생합니다.</p>
<hr>
<br>

<h1 id="📝-슬라이스의-구성">📝 슬라이스의 구성</h1>
<h2 id="📌-slice는-string과-비슷하다">📌 slice는 string과 비슷하다?</h2>
<p><code>slice</code>는 <code>string</code>과 비슷합니다.
단순 값 복사를 하면 헤더가 복사돼, 서로 다른 변수라도 같은 <code>slice</code>를 가리키게 됩니다.
<code>string</code>을 복사할 때, <code>StringHeader</code>가 복사된 것처럼<code>slice</code>도 <code>SliceHeader</code>가 복사되기 때문입니다.</p>
<br>

<h3 id="📍-sliceheader">📍 SliceHeader</h3>
<blockquote>
</blockquote>
<pre><code class="language-go">// SliceHeader is the runtime representation of a slice.
// It cannot be used safely or portably and its representation may
// change in a later release.
// Moreover, the Data field is not sufficient to guarantee the data
// it references will not be garbage collected, so programs must keep
// a separate, correctly typed pointer to the underlying data.
type SliceHeader struct {
    Data uintptr
    Len  int
    Cap  int
}
&gt;
...
type StringHeader struct {
    Data uintptr
    Len  int
}
...</code></pre>
<p><code>SliceHeader</code>는 <code>Data</code>, <code>Len</code>, <code>Cap</code> 필드를 가지고 있습니다.
<code>StringHeader</code>는 <code>Data</code>와 <code>Len</code> 필드를 가지고 있습니다.</p>
<br>

<h4 id="data-field">Data field</h4>
<p>실제 데이터의 위치를 가리킵니다.</p>
<h4 id="len-field-length">Len field (Length)</h4>
<p>데이터의 길이를 나타냅니다.</p>
<h4 id="cap-field-capacity">Cap field (Capacity)</h4>
<p>데이터를 수용할 수 있는 범위를 나타냅니다.</p>
<hr>
<br>

<h1 id="📝-슬라이스-활용">📝 슬라이스 활용</h1>
<h2 id="📌-slice-related-golang-built-in-functions">📌 slice related golang built-in functions</h2>
<br>

<h3 id="📍-make">📍 make()</h3>
<pre><code class="language-go">// The make built-in function allocates and initializes an object of type
// slice, map, or chan (only). Like new, the first argument is a type, not a
// value. Unlike new, make&#39;s return type is the same as the type of its
// argument, not a pointer to it. The specification of the result depends on
// the type:
//    Slice: The size specifies the length. The capacity of the slice is
//    equal to its length. A second integer argument may be provided to
//    specify a different capacity; it must be no smaller than the
//    length. For example, make([]int, 0, 10) allocates an underlying array
//    of size 10 and returns a slice of length 0 and capacity 10 that is
//    backed by this underlying array.
//    Map: An empty map is allocated with enough space to hold the
//    specified number of elements. The size may be omitted, in which case
//    a small starting size is allocated.
//    Channel: The channel&#39;s buffer is initialized with the specified
//    buffer capacity. If zero, or the size is omitted, the channel is
//    unbuffered.
func make(t Type, size ...IntegerType) Type</code></pre>
<p><code>slice</code>, <code>map</code>, <code>chan</code>의 선언 및 초기화하는 방법 중 하나이며, golang <code>내장함수</code>입니다.
<code>new</code>는 포인터 타입을 반환하지만, <strong><code>make</code>는 지정한 타입으로 반환합니다.</strong></p>
<ul>
<li><code>make</code>의 인자 (for slice)<ul>
<li>첫 번째 인자는 <code>요소의 타입</code></li>
<li>두 번째 인자는 <code>Len</code></li>
<li>세 번째 인자는 <code>Cap</code></li>
</ul>
</li>
</ul>
<blockquote>
</blockquote>
<pre><code class="language-go">var slice []int = make([]int, 10, 12)
fmt.Println(slice)
/* output
[0 0 0 0 0]
*/</code></pre>
<p>길이가 <code>10</code>이고 최대 길이가 <code>12</code>인 <code>[]int</code> 슬라이스를 생성합니다.
생성된 슬라이스는 <code>zero-value</code>로 초기화됩니다.</p>
<br>

<h3 id="📍-append">📍 append()</h3>
<pre><code class="language-go">// The append built-in function appends elements to the end of a slice. If
// it has sufficient capacity, the destination is resliced to accommodate the
// new elements. If it does not, a new underlying array will be allocated.
// Append returns the updated slice. It is therefore necessary to store the
// result of append, often in the variable holding the slice itself:
//    slice = append(slice, elem1, elem2)
//    slice = append(slice, anotherSlice...)
// As a special case, it is legal to append a string to a byte slice, like this:
//    slice = append([]byte(&quot;hello &quot;), &quot;world&quot;...)
func append(slice []Type, elems ...Type) []Type</code></pre>
<p>슬라이스의 끝에 요소를 &#39;덧붙이는&#39; golang <code>내장함수</code>입니다.
<code>capacity</code>가 여유있는 경우엔 새 요소를 그대로 붙이고, 여유가 없는 경우에는 새로운 슬라이스가 할당됩니다.
<code>append()</code> 내장함수는 <strong>새로 업데이트된 슬라이스를 반환합니다.</strong></p>
<pre><code class="language-go">slice = append(slice, elem1, elem2)
slice = append(slice, anotherSlice...)</code></pre>
<p>위와 같이 사용 가능합니다. 물론 요소의 자료형은 <code>slice</code> 요소의 자료형과 동일해야 합니다.
(<code>slice...</code> 의 <code>...</code>은 슬라이스를 덧붙일 때 사용합니다.)</p>
<blockquote>
</blockquote>
<h4 id="append의-메모리-할당">append()의 메모리 할당</h4>
<pre><code class="language-go">func main() {
    slice := []int{1, 2, 3, 4, 5}
    slHeader := (*reflect.SliceHeader)(unsafe.Pointer(&amp;slice))
    fmt.Printf(&quot;slHeader.Data : %v\n&quot;, slHeader.Data)
    fmt.Printf(&quot;slHeader.Len : %v\n&quot;, slHeader.Len)
    fmt.Printf(&quot;slHeader.Cap : %v\n&quot;, slHeader.Cap)
    fmt.Println()
    slice = append(slice, 6, 7, 8)
    slHeader = (*reflect.SliceHeader)(unsafe.Pointer(&amp;slice))
    fmt.Printf(&quot;slHeader.Data : %v\n&quot;, slHeader.Data)
    fmt.Printf(&quot;slHeader.Len : %v\n&quot;, slHeader.Len)
    fmt.Printf(&quot;slHeader.Cap : %v\n&quot;, slHeader.Cap)
}
/* output
slHeader.Data : 824634273488
slHeader.Len : 5
slHeader.Cap : 5
&gt;
slHeader.Data : 824634433536
slHeader.Len : 8
slHeader.Cap : 10
*/</code></pre>
<p><code>append()</code>를 사용했을 때, 기존 슬라이스의 <strong>공간이 부족하면 두 배 크기의 공간을 할당</strong>해서 <strong>완전히 새로운 슬라이스를 만들어서 반환</strong>합니다.</p>
<pre><code class="language-go">...
slice2 := []int{6, 7, 8, 9, 10, 11}
slice = append(slice, slice2...)
...
/* output
slHeader.Data : 824634318848
slHeader.Len : 11
slHeader.Cap : 12
*/</code></pre>
<p>만약 위와 같이 새로 덧붙여지는 <strong>요소 / 슬라이스</strong>의 크기가 기존의 슬라이스보다 큰 경우, <strong>더 큰 크기의 슬라이스를 기준으로 두 배의 공간을 할당해서 새로운 슬라이스를 만들고 반환</strong>합니다.</p>
<br>

<h3 id="📍-copy">📍 copy()</h3>
<pre><code class="language-go">// The copy built-in function copies elements from a source slice into a
// destination slice. (As a special case, it also will copy bytes from a
// string to a slice of bytes.) The source and destination may overlap. Copy
// returns the number of elements copied, which will be the minimum of
// len(src) and len(dst).
func copy(dst, src []Type) int</code></pre>
<p><code>src</code> 슬라이스를 <code>dst</code> 슬라이스로 복사합니다. <code>string</code>의 <code>byte</code> 값을 <code>[]byte</code> 자료형으로 하여 복사하기도 합니다.
<code>copy()</code> <code>내장함수</code>는 복사된 요소의 개수를 반환합니다.</p>
<br>

<h2 id="📌-슬라이스의-사용">📌 슬라이스의 사용</h2>
<h3 id="📍-슬라이스-대입">📍 슬라이스 대입</h3>
<blockquote>
</blockquote>
<pre><code class="language-go">func main() {
    slice1 := []int{1, 2, 3, 4, 5}
    slice2 := slice1
    fmt.Println(slice2)
}
/* output
[1 2 3 4 5]
*/</code></pre>
<p>위와 같이 슬라이스 대입을 수행하면, 같은 값을 가지는 걸 볼 수 있습니다.</p>
<pre><code class="language-go">slice1[0] = 100
fmt.Println(slice2)
/* output
[100 2 3 4 5]
*/</code></pre>
<p>이렇게 <code>slice1</code>을 수정하면 <code>slice2</code>까지 영향을 받는 걸 볼 수 있습니다.
<strong>슬라이스를 대입 연산을 하면 슬라이스 헤더가 대입되어 두 변수 모두 하나의 인스턴스를 가리키게 됩니다.</strong></p>
<pre><code class="language-go">func main() {
    slice1 := []int{1, 2, 3, 4, 5}
    slice2 := slice1
    fmt.Println(slice2)
    slice1[0] = 100
    fmt.Println(slice2)
    slHeader1 := (*reflect.SliceHeader)(unsafe.Pointer(&amp;slice1))
    slHeader2 := (*reflect.SliceHeader)(unsafe.Pointer(&amp;slice2))
    fmt.Println()
    fmt.Println(&quot;*-*-*-*-* slice1 *-*-*-*-*&quot;)
    fmt.Printf(&quot;slHeader1.Data : %v\n&quot;, slHeader1.Data)
    fmt.Printf(&quot;slHeader1.Len : %v\n&quot;, slHeader1.Len)
    fmt.Printf(&quot;slHeader1.Cap : %v\n&quot;, slHeader1.Cap)
    fmt.Println()
    fmt.Println(&quot;*-*-*-*-* slice2 *-*-*-*-*&quot;)
    fmt.Printf(&quot;slHeader2.Data : %v\n&quot;, slHeader2.Data)
    fmt.Printf(&quot;slHeader2.Len : %v\n&quot;, slHeader2.Len)
    fmt.Printf(&quot;slHeader2.Cap : %v\n&quot;, slHeader2.Cap)
}
/* output
[1 2 3 4 5]
[100 2 3 4 5]
&gt;
*-*-*-*-* slice1 *-*-*-*-*
slHeader1.Data : 824634376240
slHeader1.Len : 5
slHeader1.Cap : 5
&gt;
*-*-*-*-* slice2 *-*-*-*-*
slHeader2.Data : 824634376240
slHeader2.Len : 5
slHeader2.Cap : 5
*/</code></pre>
<p>같은 <code>Data</code> 필드를 가진 것을 볼 수 있습니다.</p>
<br>

<h3 id="📍-슬라이스-복사">📍 슬라이스 복사</h3>
<blockquote>
</blockquote>
<h4 id="슬라이스-순회를-이용한-복사">슬라이스 순회를 이용한 복사</h4>
<pre><code class="language-go">func main() {
    slice1 := []int{1, 2, 3, 4, 5}
    slice2 := make([]int, len(slice1))
    for i, v := range slice1 {
        slice2[i] = v
    }
    fmt.Println(slice2)
}
/* output
[1 2 3 4 5]
*/</code></pre>
<p>이 방법은 <code>slice1</code>와 같은 길이를 가지는 <code>slice2</code>를 빈 값으로 선언 및 초기화를 먼저 진행합니다.
그리고 빈 슬라이스인 <code>slice2</code>에 <code>slice1</code>의 요소를 순회하며 대입하는 방식으로, 값은 같으나 서로 다른 인스턴스를 가리키게 합니다.
(서로 같은 인스턴스를 가리키는지 확인하려면 위의 <code>SliceHeader</code>를 출력하는 구문을 사용하면 됩니다.)</p>
<br>

<blockquote>
</blockquote>
<h4 id="append-내장함수를-이용한-복사">append() 내장함수를 이용한 복사</h4>
<pre><code class="language-go">func main() {
    slice1 := []int{1, 2, 3, 4, 5}
    slice2 := append([]int{}, slice1...)
    fmt.Println(slice2)
}
/* output
[1 2 3 4 5]
*/</code></pre>
<p><code>append()</code> <code>내장함수</code>를 사용하여 새로운 빈 슬라이스에 기존 슬라이스를 덧붙여, <strong>같은 값을 가지지만 서로 다른 인스턴스인 슬라이스를 생성할 수 있습니다.</strong></p>
<br>

<blockquote>
</blockquote>
<h4 id="copy-내장함수를-이용한-복사">copy() 내장함수를 이용한 복사</h4>
<pre><code class="language-go">func main() {
    slice1 := []int{1, 2, 3, 4, 5}
    slice2 := make([]int, 3)
    copy(slice2, slice1)
    fmt.Println(slice2)
}
/*    output
[1 2 3]
*/</code></pre>
<p><code>func copy(dst, src []Type) int</code> 의 <code>dst</code>의 크기 내에서 <code>src</code>를 복사합니다.
<code>dst</code>의 범위를 벗어나는 요소는 복사해오지 않습니다.</p>
<br>

<h3 id="📍-슬라이스-요소-제거">📍 슬라이스 요소 제거</h3>
<blockquote>
</blockquote>
<h4 id="특정-인덱스를-제거한-새로운-슬라이스">특정 인덱스를 제거한 새로운 슬라이스</h4>
<pre><code class="language-go">func SubElement(index int, slice []int) (subSlice []int) {
    subSlice = []int{}
    for i, v := range slice {
        if index == i {
            continue
        }
        subSlice = append(subSlice, v)
    }
    return
}
func main() {
    slice1 := []int{1, 2, 3, 4, 5}
    slice2 := SubElement(3, slice1)
    slice2Header := (*reflect.SliceHeader)(unsafe.Pointer(&amp;slice2))
    fmt.Println(&quot;slice2 :&quot;, slice2)
    fmt.Println(&quot;*-*-*-*-* slice2 *-*-*-*-*&quot;)
    fmt.Printf(&quot;slice2Header.Data : %v\n&quot;, slice2Header.Data)
    fmt.Printf(&quot;slice2Header.Len : %v\n&quot;, slice2Header.Len)
    fmt.Printf(&quot;slice2Header.Cap : %v\n&quot;, slice2Header.Cap)
}
/*    output
slice2 : [1 2 3 5]
*-*-*-*-* slice2 *-*-*-*-*
slice2Header.Data : 824634425344
slice2Header.Len : 4
slice2Header.Cap : 4
*/</code></pre>
<p>기존 슬라이스를 보존하면서 특정 인덱스를 뺀 슬라이스를 새로 만듭니다.</p>
<br>

<blockquote>
</blockquote>
<h4 id="기존-슬라이스에서-특정-인덱스를-제거">기존 슬라이스에서 특정 인덱스를 제거</h4>
<pre><code class="language-go">func main() {
    slice := []int{1, 2, 3, 4, 5}
    index := 3
    beforeHeader := (*reflect.SliceHeader)(unsafe.Pointer(&amp;slice))
    fmt.Println(&quot;slice :&quot;, slice)
    fmt.Println(&quot;*-*-*-*-* slice *-*-*-*-*&quot;)
    fmt.Println(beforeHeader)
    for i := index + 1; i &lt; len(slice); i++ {
        slice[i-1] = slice[i]
    }
    slice = slice[:len(slice)-1]
    fmt.Println()
    resultHeader := (*reflect.SliceHeader)(unsafe.Pointer(&amp;slice))
    fmt.Println(&quot;slice :&quot;, slice)
    fmt.Println(&quot;*-*-*-*-* slice *-*-*-*-*&quot;)
    fmt.Println(resultHeader)
}
/* output
slice : [1 2 3 4 5]
*-*-*-*-* slice *-*-*-*-*
&amp;{824634376240 5 5}
&gt;
slice : [1 2 3 5]
*-*-*-*-* slice *-*-*-*-*
&amp;{824634376240 4 5}
*/</code></pre>
<p>슬라이스를 처음 만들었을 때의 <code>capacity</code>가 유지되는 것을 볼 수 있습니다.</p>
<br>

<h4 id="슬라이싱">슬라이싱</h4>
<pre><code class="language-go">func main() {
    slice1 := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
    fmt.Println(slice1[:3])
    fmt.Println(slice1[5:])
    fmt.Println(slice1[:])
    fmt.Println(slice1[2:5])
}
/* output
[1 2 3]
[6 7 8 9 10]
[1 2 3 4 5 6 7 8 9 10]
[3 4 5]
*/</code></pre>
<p><strong><code>slice[포함부터 : 전까지]</code></strong>
슬라이스를 위와 같은 방법으로 절단할 수 있습니다.</p>
<blockquote>
</blockquote>
<h4 id="슬라이싱으로-특정-인덱스-제거">슬라이싱으로 특정 인덱스 제거</h4>
<pre><code class="language-go">func main() {
    slice := []int{1, 2, 3, 4, 5}
    index := 3
    beforeHeader := (*reflect.SliceHeader)(unsafe.Pointer(&amp;slice))
    fmt.Println(&quot;slice :&quot;, slice)
    fmt.Println(&quot;*-*-*-*-* slice *-*-*-*-*&quot;)
    fmt.Println(beforeHeader)
    slice = append(slice[:index], slice[index+1:]...)
    fmt.Println()
    resultHeader := (*reflect.SliceHeader)(unsafe.Pointer(&amp;slice))
    fmt.Println(&quot;slice :&quot;, slice)
    fmt.Println(&quot;*-*-*-*-* slice *-*-*-*-*&quot;)
    fmt.Println(resultHeader)
}
/* output
slice : [1 2 3 4 5]
*-*-*-*-* slice *-*-*-*-*
&amp;{824634384432 5 5}
&gt;
slice : [1 2 3 5]
*-*-*-*-* slice *-*-*-*-*
&amp;{824634384432 4 5}
*/</code></pre>
<p>슬라이스를 처음 만들었을 때의 <code>capacity</code>가 유지되는 것을 볼 수 있습니다.</p>
<br>

<blockquote>
</blockquote>
<h4 id="copy-내장함수로-특정-인덱스-제거">copy() 내장함수로 특정 인덱스 제거</h4>
<pre><code class="language-go">func main() {
    slice := []int{1, 2, 3, 4, 5}
    index := 3
    beforeHeader := (*reflect.SliceHeader)(unsafe.Pointer(&amp;slice))
    fmt.Println(&quot;slice :&quot;, slice)
    fmt.Println(&quot;*-*-*-*-* slice *-*-*-*-*&quot;)
    fmt.Println(beforeHeader)
    copy(slice[index:], slice[index+1:])
    slice = slice[:len(slice)-1]
    fmt.Println()
    resultHeader := (*reflect.SliceHeader)(unsafe.Pointer(&amp;slice))
    fmt.Println(&quot;slice :&quot;, slice)
    fmt.Println(&quot;*-*-*-*-* slice *-*-*-*-*&quot;)
    fmt.Println(resultHeader)
}
/* output
slice : [1 2 3 4 5]
*-*-*-*-* slice *-*-*-*-*
&amp;{824634376240 5 5}
&gt;
slice : [1 2 3 5]
*-*-*-*-* slice *-*-*-*-*
&amp;{824634376240 4 5}
*/</code></pre>
<p>슬라이스를 처음 만들었을 때의 <code>capacity</code>가 유지되는 것을 볼 수 있습니다.</p>
<br>

<h3 id="📍-슬라이스-요소-삽입">📍 슬라이스 요소 삽입</h3>
<blockquote>
</blockquote>
<h4 id="슬라이스-요소-밀어내어-삽입">슬라이스 요소 밀어내어 삽입</h4>
<pre><code class="language-go">func main() {
    slice := []int{1, 2, 3, 4, 5}
    slice = append(slice, 0)
    index := 3
    const location = 2
    for i := len(slice) - location; i &gt;= index; i-- {
        slice[i+1] = slice[i]
    }
    slice[index] = 100
    fmt.Println(slice)
}
/* output
[1 2 3 100 4 5]
*/</code></pre>
<p>길이를 하나 늘리면, 기존 슬라이스의 마지막 인덱스는 <code>len(바뀐 슬라이스)-2</code>에 위치합니다.
슬라이스의 요소들을 <code>index</code> 기준으로 하나씩 뒤로 미루고, <code>index</code> 위치에 새로운 요소를 삽입합니다.</p>
<br>

<blockquote>
</blockquote>
<h4 id="append-내장함수와-슬라이싱으로-요소-삽입">append() 내장함수와 슬라이싱으로 요소 삽입</h4>
<pre><code class="language-go">func main() {
    slice := []int{1, 2, 3, 4, 5}
    index := 3
    slice = append(slice[:index], append([]int{100}, slice[index:]...)...)
    fmt.Println(slice)
}
/* output
[1 2 3 100 4 5]
*/</code></pre>
<p><code>(미포함)까지인 슬라이스</code>에, <code>append(추가하려는 요소 + (포함)슬라이스)</code>를 덧붙입니다.
단, <code>append()</code> 내에서 임시로 만든 새 슬라이스에 <code>(포함)슬라이스</code>를 덧붙이는 과정에 <strong>임시 버퍼를 사용한다는 단점이 존재</strong>합니다.</p>
<br>

<blockquote>
</blockquote>
<h4 id="copy-내장함수와-슬라이싱으로-요소-삽입">copy() 내장함수와 슬라이싱으로 요소 삽입</h4>
<pre><code class="language-go">func main() {
    slice := []int{1, 2, 3, 4, 5}
    index := 3
    slice = append(slice, 0)
    copy(slice[index+1:], slice[index:])
    slice[index] = 100
    fmt.Println(slice)
}
/* output
[1 2 3 100 4 5]
*/</code></pre>
<p><code>zero-value</code> 요소를 추가하고, 특정 인덱스부터의 요소를 한 칸씩 밀어냅니다.
이 과정에서 <code>slice[index]</code>의 값은 유지가 되어, <code>slice[index+1]</code>과 동일한 값을 가집니다.</p>
<hr>
<br>

<p>이번 포스트는 슬라이스에 관한 내용이었습니다.
감사합니다.👍</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Golang 기초 (12) : 패키지 & 모듈 & 워크스페이스에 대하여]]></title>
            <link>https://velog.io/@vamos_eon/go-pkg-mod-work</link>
            <guid>https://velog.io/@vamos_eon/go-pkg-mod-work</guid>
            <pubDate>Wed, 16 Mar 2022 17:14:47 GMT</pubDate>
            <description><![CDATA[<p>안녕하세요, 주니어 개발자 Eon입니다.
이번 포스트는 <strong>패키지 &amp; 모듈 &amp; 워크스페이스</strong>에 관한 내용입니다.</p>
<br>

<h1 id="📝-package">📝 Package</h1>
<p>Golang의 <strong>코드를 묶는 단위</strong>이며, <strong>모든 코드는 패키지에 속해야 합니다.</strong></p>
<br>

<h2 id="📌-패키지를-선언하는-방법">📌 패키지를 선언하는 방법</h2>
<h3 id="📍-package-main">📍 package main</h3>
<blockquote>
</blockquote>
<pre><code class="language-go">package main
&gt;
func main(){...}</code></pre>
<blockquote>
</blockquote>
<p><code>main 패키지</code>를 선언할 수 있고, <code>main()</code> 함수를 작성할 수 있습니다.</p>
<blockquote>
</blockquote>
<p><code>main 패키지</code>가 가지는 의미는 다음과 같습니다.
<strong>&#39;프로그램의 시작 패키지&#39;</strong>
<strong>모든 Golang 프로그램은 main 패키지의 main()함수부터 실행</strong>됩니다.
(아래에서 다루지만 패키지 초기화 함수와는 다릅니다.)</p>
<br>

<h3 id="📍-package-other">📍 package other</h3>
<blockquote>
</blockquote>
<pre><code class="language-go">package other
&gt;
func mainIsNotAllowed(){...}</code></pre>
<blockquote>
</blockquote>
<p><strong><code>main 패키지</code> 외에는 <code>main()</code> 함수를 가질 수 없습니다.</strong></p>
<br>

<h2 id="📌-패키지를-사용하는-방법">📌 패키지를 사용하는 방법</h2>
<blockquote>
</blockquote>
<pre><code class="language-go">package main
&gt;
import &quot;fmt&quot;
&gt;
func main() {
    fmt.Println(&quot;Hello World!!&quot;)
}
// Hello World!!</code></pre>
<p><code>import</code>를 하고 나면 그 <strong>패키지가 제공하는 것들(함수, 구조체 등)을 사용할 수 있습니다.</strong></p>
<hr>
<blockquote>
</blockquote>
<pre><code class="language-go">package other
&gt;
import &quot;some-other-package&quot;
&gt;
func OtherFunc() {
    someotherpackage.SOPfunc()
}</code></pre>
<p><strong>&#39;-&#39;를 포함</strong>한 <code>import</code>를 하면, 패키지를 사용할 때 <strong>&#39;-&#39;를 빼고 사용</strong>할 수 있습니다.</p>
<hr>
<pre><code class="language-go">package other
&gt;
import &quot;some_other_package&quot;
&gt;
func OtherFunc() {
    some_other_package.SOPfunc()
}</code></pre>
<p><strong>&#39;_&#39;를 포함</strong>한 <code>import</code>는 <strong>변형없이 사용</strong>할 수 있습니다.</p>
<hr>
<pre><code class="language-go">package other
&gt;
import (
    &quot;github.com/just/awsome&quot;
    va &quot;github.com/very/awsome&quot;
    na &quot;github.com/not/awsome&quot;
)
&gt;
func OtherFunc() {
    awsome.JustAwsomeFunc()
    va.VeryAwsomeFunc()
    na.NotAwsomeFunc()
}</code></pre>
<p><strong><code>import</code>한 패키지명이 길고 복잡하거나 겹치는 경우</strong>, 위와 같이 <strong>alias</strong>를 줄 수 있습니다.</p>
<hr>
<pre><code class="language-go">package other
&gt;
import    _ &quot;github.com/just/initialize&quot;</code></pre>
<p><strong>특정 패키지의 초기화가 필요한 경우</strong>, 위와 같이 <strong>직접적으로 사용하지 않아도 특정 패키지를 포함할 수 있습니다.</strong></p>
<br>

<h3 id="📍-같은-패키지-다른-go-파일">📍 같은 패키지, 다른 .go 파일</h3>
<p><strong>같은 패키지 내의 모든 소스코드는 안의 자원을 공유</strong>합니다.</p>
<pre><code>${GOSRC}/example/
    ⊢ ex1.go
    ∣    ⊢ UpperSth()
    ∣    ⨽ lowerSth()
    ⨽ ex2.go
        ⊢ ExportSth()
        ⨽ cannotExportSth()</code></pre><pre><code class="language-go">package example</code></pre>
<p>두 파일 모두 <strong>같은 패키지 내</strong>에 있습니다.
이 때, <code>ex1.go</code> 의 아무 함수 내에 <code>ex2.go</code>의 모든 함수를 호출할 수 있습니다.
<strong>(파일이 분리되어 있어도 패키지가 같으면 바로 사용이 가능)</strong>
단, <strong>패키지 외부에서 함수를 호출할 때는 함수 이름이 대문자로 시작하는 경우만 호출할 수 있습니다.</strong>
<strong><a href="https://velog.io/@vamos_eon/Golang-function#-the-rule-of-naming-in-golang">https://velog.io/@vamos_eon/Golang-function#-the-rule-of-naming-in-golang</a></strong></p>
<br>

<h3 id="📍-패키지-초기화-함수">📍 패키지 초기화 함수</h3>
<blockquote>
</blockquote>
<pre><code class="language-go">func init(){...}</code></pre>
<p><code>init()</code> 함수는 패키지가 <code>import</code>될 때, <strong>호출없이 가장 먼저 실행되는 함수</strong>입니다.
패키지 내에서 <strong>중복해서 선언도 가능</strong>하며, <strong>사용자 임의로 호출은 불가</strong>합니다.
프로그램 실행 시, main 함수보다도 먼저 호출됩니다.</p>
<h4 id="init-함수-실행-순서">init() 함수 실행 순서</h4>
<ul>
<li><strong>가장 안쪽 패키지부터</strong></li>
<li><strong>파일 정렬 위에서 아래로</strong></li>
<li><strong>함수 정렬 위에서 아래로</strong></li>
</ul>
<hr>
<br>

<h1 id="📝-module">📝 Module</h1>
<p>모듈은 <strong>종속성 관리를 위해 Golang이 지원</strong>하는 것입니다.
<strong>모듈은 패키지의 모음</strong>이며, <strong>모듈 파일이 있는 곳이 패키지의 루트 경로</strong>가 됩니다.
<strong>(모듈 : 패키지 = 1 : N)</strong></p>
<br>

<h2 id="📌-module의-초기화">📌 Module의 초기화</h2>
<blockquote>
</blockquote>
<pre><code class="language-bash">go mod init module_path        # 1
&gt;
go mod init module            # 2
go mod init module/test        # 3</code></pre>
<blockquote>
</blockquote>
<p>모듈은 위와 같이 초기화할 수 있습니다.
2번의 경우, 빌드하면 실행파일 이름이 <code>module</code>이 되고, <strong>root 패키지의 경로는 module/</strong> 입니다.
3번의 경우, 빌드하면 실행파일 이름이 <code>test</code>가 되고, <strong>root 패키지의 경로는 module/test/</strong> 입니다.</p>
<br>

<h2 id="📌-module의-구성">📌 Module의 구성</h2>
<blockquote>
</blockquote>
<pre><code>&gt;
module module/test
&gt;
go 1.17
&gt;
require (
    github.com/package/example v1.2.0
    ...
)</code></pre><p><code>go.mod</code> 파일을 보면 <strong>모듈 이름</strong>과 <strong>Golang 버전</strong>이 표기됩니다.
그리고 <strong>외부 패키지를 포함했다면, 외부 패키지의 버전도 함께 표시</strong>합니다.
<code>indirect</code>가 붙으면 <strong>직접적으로 사용하지 않더라도</strong> 사용한 외부 패키지에 <code>import</code> 돼 있는 <strong>의존성 패키지</strong>입니다.</p>
<p>Golang wiki 페이지에 정리된 내용이 있습니다.
<a href="https://github.com/golang/go/wiki/Modules">https://github.com/golang/go/wiki/Modules</a>
<a href="https://github.com/golang/go/wiki/Modules#can-a-module-consume-a-package-that-has-not-opted-in-to-modules">https://github.com/golang/go/wiki/Modules#can-a-module-consume-a-package-that-has-not-opted-in-to-modules</a></p>
<br>

<h3 id="📍-gosum-">📍 go.sum ?</h3>
<p>위에서 <code>go.mod</code>에 대한 내용을 다뤘습니다.
<strong>외부 패키지를 포함하여 빌드를 하고 나면 <code>go.sum</code> 파일이 생성</strong>됩니다.
<code>go.sum</code>은 <strong>외부 패키지의 버전에 대한 <code>checksum</code>을 모아둔 파일</strong>입니다.
<code>go.sum</code> 파일이 없으면 빌드할 때 다시 생성합니다.</p>
<hr>
<br>

<h1 id="📝-workspace">📝 Workspace</h1>
<p><strong>Golang 코드가 짜여지고 관리되는 공간</strong>입니다.
권장 방법은 링크를 참고하시면 됩니다.
<a href="https://go.dev/doc/gopath_code#Workspaces">https://go.dev/doc/gopath_code#Workspaces</a></p>
<p>문서와는 조금 다르게, 조금 더 간단하게 사용할 수도 있습니다.
<code>/home/user/</code> 아래에 <code>workspace</code>로 사용할 디렉터리를 만들고 사용할 수 있습니다.
<code>/home/user/awsome-project/</code>     (단일 프로그램 또는 프로젝트)
<code>/home/user/go-practice/</code>        (여러 프로그램 또는 프로젝트)
위와 같이 여러 <code>workspace</code>를 만들 수도 있습니다.</p>
<br>

<h2 id="📌-workspace-구조">📌 Workspace 구조</h2>
<blockquote>
</blockquote>
<pre><code>~/awsome-project/
    ⊢ go.mod
    ⊢ go.sum
    ⊢ main.go
    ⊢ package_dir
    ∣    ⊢ hello.go
    ∣    ⨽ bye.go
    ⨽ other_package_dir
        ⊢ other_code1.go
        ⨽ other_code2.go</code></pre><p>위와 같이 하나의 프로그램을 위한 <code>workspace</code>를 만들고 관리할 수 있습니다.</p>
<blockquote>
</blockquote>
<pre><code>~/go-practice/
    ⊢ awsome-project/
    ∣    ⊢ go.mod
    ∣    ⊢ go.sum
    ∣    ⊢ main.go
    ∣    ⊢ package_dir
    ∣    ∣    ⊢ hello.go
    ∣    ∣    ⨽ bye.go
    ∣    ⨽ external_pkg_used_dir
    ∣        ⊢ ext_used1.go
    ∣        ⨽ ext_used2.go
    ⊢ hello-bye
    ∣    ⊢ hello-bye*
    ∣    ⊢ go.mod
    ∣    ⊢ main.go
    ∣    ⨽ hello-bye
    ∣        ⊢ hello.go
    ∣        ⨽ bye.go
    ⨽ calculator
        ⊢ calculator*
        ⊢ go.mod
        ⊢ main.go
        ⨽ arithmetic-operation
            ⊢ sum.go
            ⊢ sub.go
            ⊢ mul.go
            ⊢ div.go
            ⨽ mod.go</code></pre><p>위와 같이 여러 개의 프로그램을 위한 <code>workspace</code>를 만들고 관리할 수 있습니다.</p>
<h2 id="📌-visual-studio-code-golang-workspace">📌 Visual Studio Code Golang workspace</h2>
<p><strong><code>Vscode</code></strong> 를 사용하시는 분이라면 연습용 디렉터리 내에 여러 프로그램을 작성하고 테스트하고 싶을 겁니다.
하지만 <code>Vscode</code>의 Explorer Folder 내에 <strong>같은 이름의 모듈이 존재하면 빨갛게 표시</strong>됩니다.</p>
<p><img src="https://images.velog.io/images/vamos_eon/post/129c264c-96c8-4444-a4dd-f5244bf04040/image.png" alt=""></p>
<p>이는 테스트할 때마다 불편하게 합니다. (빌드도 정상적으로 되는데 괜히 마음 불편한..)
<code>go mod init main</code> 명령어로 간단하게 모듈 이름 상관없이 짓고 빌드하고 싶을 때 말입니다.
<strong><code>go.work</code></strong>를 작성하면 이런 불편이 사라집니다.</p>
<br>

<h3 id="📍-gowork-작성">📍 go.work 작성</h3>
<blockquote>
</blockquote>
<p>~/go-practice/go.work</p>
<pre><code>go 1.17
&gt;
directory (
    ./awsome-project
    ./hello-bye
    ./calculator
)</code></pre><p><code>go.work</code> file을 생성하기만 해도 <strong>모듈 이름 중복에 대한 문제는 해결</strong>됩니다.
다만 <strong>workspace를 지정해서 사용하려면 위와 같이 디렉터리 별로 설정할 수도 있습니다.</strong></p>
<blockquote>
</blockquote>
<p><code>vscode go workspace 관련 이슈</code> : <a href="https://github.com/golang/go/issues/45713">https://github.com/golang/go/issues/45713</a></p>
<hr>
<br>

<h1 id="📝-직접-만들고-사용해보자">📝 직접 만들고 사용해보자</h1>
<h2 id="1-main-패키지-작성">1. main 패키지 작성</h2>
<h3 id="📍-workspace-세팅-및-maingo-작성">📍 workspace 세팅 및 main.go 작성</h3>
<pre><code>${GOSRC}/example/
    ⨽ main.go</code></pre><blockquote>
</blockquote>
<pre><code class="language-go">package main
&gt;
func main(){}</code></pre>
<br>

<h2 id="2-모듈-초기화">2. 모듈 초기화</h2>
<h3 id="📍-모듈명-설정">📍 모듈명 설정</h3>
<blockquote>
</blockquote>
<pre><code class="language-bash">~/${GOSRC}/example$ go mod init local_package</code></pre>
<br>

<h2 id="3-로컬-패키지-생성">3. 로컬 패키지 생성</h2>
<h3 id="📍-패키지-파일-생성-및-작성">📍 패키지 파일 생성 및 작성</h3>
<pre><code>${GOSRC}/example/
    ⊢ go.mod
    ⊢ main.go
    ⨽ something
        ⊢ ex1.go
        ⨽ ex2.go</code></pre><blockquote>
</blockquote>
<p><code>ex1.go</code></p>
<pre><code class="language-go">package something
&gt;
import &quot;fmt&quot;
&gt;
type CaseStruct struct {
    UpperValue string
    lowerValue string
}
&gt;
func UpperSth(){
    fmt.Println(&quot;UpperSth() Called&quot;)
}
&gt;
func lowerSth(){
    fmt.Println(&quot;lowerSth() Called&quot;)
}</code></pre>
<blockquote>
</blockquote>
<p><code>ex2.go</code></p>
<pre><code class="language-go">package something
&gt;
import &quot;fmt&quot;
&gt;
func ExportSth(){
    fmt.Println(&quot;ExportSth() Called&quot;)
}
&gt;
func cannotExportSth(){
    fmt.Println(&quot;cannotExportSth() Called&quot;)
}</code></pre>
<br>

<h2 id="4-로컬-패키지-사용">4. 로컬 패키지 사용</h2>
<h3 id="📍-maingo-추가-작성">📍 main.go 추가 작성</h3>
<blockquote>
</blockquote>
<pre><code class="language-go">package main
&gt;
import &quot;local_package/something&quot;
&gt;
func main() {
    // added
    sth_case := something.CaseStruct{}
    sth_case.UpperValue = &quot;Upper value.&quot;
    fmt.Println(&quot;sth_case&#39;s UpperValue:&quot;, sth_case.UpperValue)
    something.UpperSth()
}</code></pre>
<br>

<h3 id="📍-빌드-하기">📍 빌드 하기</h3>
<blockquote>
</blockquote>
<pre><code class="language-bash">~/${GOSRC}/example$ go build</code></pre>
<pre><code class="language-bash">~/${GOSRC}/example$ ls -alF</code></pre>
<pre><code class="language-py"># output
total 1748
drwxrwxr-x 3 eon eon    4096 Mar 16 12:04 ./
drwxrwxr-x 4 eon eon    4096 Mar 16 11:57 ../
-rw-rw-r-- 1 eon eon      30 Mar 16 12:01 go.mod
-rwxrwxr-x 1 eon eon 1766536 Mar 16 12:04 local_package*
-rw-rw-r-- 1 eon eon      86 Mar 16 12:05 main.go
drwxrwxr-x 2 eon eon    4096 Mar 16 12:05 something/</code></pre>
<br>

<h4 id="실행-파일-이름-지정하여-빌드">실행 파일 이름 지정하여 빌드</h4>
<blockquote>
</blockquote>
<pre><code class="language-bash">~/${GOSRC}/example$ go build -o bin-name</code></pre>
<br>

<h3 id="📍-실행하기">📍 실행하기</h3>
<blockquote>
</blockquote>
<pre><code class="language-bash">~/${GOSRC}/example$ ./local_package</code></pre>
<blockquote>
</blockquote>
<pre><code class="language-bash"># output
sth_case&#39;s UpperValue: Upper value.
UpperSth() Called</code></pre>
<br>

<h2 id="5-동일-패키지-함수-사용">5. 동일 패키지 함수 사용</h2>
<h3 id="📍-ex1go에서-ex2go-함수-호출하기">📍 ex1.go에서 ex2.go 함수 호출하기</h3>
<blockquote>
</blockquote>
<p><code>ex1.go</code></p>
<pre><code class="language-go">package something
&gt;
import &quot;fmt&quot;
&gt;
func UpperSth() {
    fmt.Println(&quot;UpperSth() Called&quot;)
    lowerSth()
    ExportSth()
    cannotExportSth()
}
&gt;
func lowerSth() {
    fmt.Println(&quot;lowerSth() Called&quot;)
}</code></pre>
<h4 id="빌드-후-실행-결과">빌드 후 실행 결과</h4>
<pre><code class="language-bash">UpperSth() Called
lowerSth() Called
ExportSth() Called
cannotExportSth() Called</code></pre>
<br>

<h2 id="6-외부-패키지-사용">6. 외부 패키지 사용</h2>
<h3 id="📍-외부-패키지-import-및-사용">📍 외부 패키지 import 및 사용</h3>
<blockquote>
</blockquote>
<p><code>ex2.go</code></p>
<pre><code class="language-go">package something
&gt;
import (
    &quot;fmt&quot;
&gt;
    &quot;github.com/google/uuid&quot;
)
&gt;
func ExportSth() {
    fmt.Println(&quot;ExportSth() Called&quot;)
}
&gt;
func cannotExportSth() {
    fmt.Println(&quot;cannotExportSth() Called&quot;)
}
&gt;
// added
func ExternalPackage() {
    fmt.Println(uuid.New())
}</code></pre>
<blockquote>
</blockquote>
<p><code>main.go</code></p>
<pre><code class="language-go">package main
&gt;
import &quot;local_package/something&quot;
&gt;
func main() {
    // function call edited
    something.ExternalPackage()
}</code></pre>
<br>

<h3 id="📍-외부-패키지-다운로드-및-초기화">📍 외부 패키지 다운로드 및 초기화</h3>
<h4 id="1-모듈-정리다운로드-종속관계-정리-후-빌드">(1) 모듈 정리(다운로드, 종속관계 정리) 후 빌드</h4>
<pre><code class="language-bash">go mod tidy
go build</code></pre>
<br>

<h4 id="2-다운로드만-하고-빌드">(2) 다운로드만 하고 빌드</h4>
<pre><code class="language-bash">go get github.com/google/uuid
go build</code></pre>
<pre><code>${GOSRC}/example/
    ⊢ go.mod
    ⊢ go.sum
    ⊢ main.go
    ⨽ something
        ⊢ ex1.go
        ⨽ ex2.go</code></pre><ul>
<li><h4 id="gosum">go.sum</h4>
<ul>
<li>위의 두 경우 (1), (2) 모두 <code>go.sum</code> 파일이 생성되며 <code>checksum</code>을 기록합니다.</li>
</ul>
</li>
</ul>
<ul>
<li><h4 id="모듈-정리를-안할-경우">모듈 정리를 안할 경우</h4>
<ul>
<li><code>go.mod</code><pre><code>module local_package
</code></pre></li>
</ul>
<p>go 1.17</p>
<p>require github.com/google/uuid v1.3.0 // indirect
```
직접 <code>import</code>하는 외부 패키지임에도 불구하고 위와 같이 <strong><code>indirect</code></strong> 문구가 붙으며 사용되지 않은, <strong>간접 dependency로써 존재</strong>하게 됩니다.
따라서 <strong>모듈을 정리해주는 과정이 필요</strong>합니다.</p>
</li>
<li><p><em>외부 패키지 종속성이 변경되었다면 빌드 전에 <code>go mod tidy</code> 명령어를 꼭 실행하는 것을 권장*</em>합니다.
<code>go mod tidy</code> 명령어 실행 후에는 <code>// indirect</code> 문구는 사라집니다.</p>
</li>
</ul>
<br>

<h4 id="3-빌드-후-실행-결과">(3) 빌드 후 실행 결과</h4>
<pre><code class="language-bash">c1c36290-2516-4e2a-ad60-696e90ec6328</code></pre>
<br>

<h2 id="7-패키지-초기화-함수-init-사용">7. 패키지 초기화 함수 init() 사용</h2>
<h3 id="📍-init-동작-순서-테스트">📍 init() 동작 순서 테스트</h3>
<blockquote>
</blockquote>
<p><code>main.go</code></p>
<pre><code class="language-go">package main
&gt;
import &quot;local_package/something&quot;
&gt;
func main() {
    something.ExternalPackage()
}
&gt;
// function added
func init(){
    fmt.Println(&quot;init called in main package&quot;)
}</code></pre>
<blockquote>
</blockquote>
<p><code>ex1.go</code></p>
<pre><code class="language-go">package something
&gt;
import &quot;fmt&quot;
&gt;
func init() {
    fmt.Println(&quot;ex1.go init 1&quot;)
}
&gt;
func UpperSth() {
    fmt.Println(&quot;UpperSth() Called&quot;)
    lowerSth()
    ExportSth()
    cannotExportSth()
}
&gt;
func lowerSth() {
    fmt.Println(&quot;lowerSth() Called&quot;)
}
&gt;
func init() {
    fmt.Println(&quot;ex1.go init 2&quot;)
}</code></pre>
<blockquote>
</blockquote>
<p><code>ex2.go</code></p>
<pre><code class="language-go">package something
&gt;
import (
    &quot;fmt&quot;
&gt;
    &quot;github.com/google/uuid&quot;
)
&gt;
func ExportSth() {
    fmt.Println(&quot;ExportSth() Called&quot;)
}
&gt;
func cannotExportSth() {
    fmt.Println(&quot;cannotExportSth() Called&quot;)
}
&gt;
func init() {
    fmt.Println(&quot;ex2.go init 1&quot;)
}
&gt;
func init() {
    fmt.Println(&quot;ex2.go init 2&quot;)
}
&gt;
func ExternalPackage() {
    fmt.Println(uuid.New())
}</code></pre>
<br>

<h4 id="빌드-후-실행-결과-1">빌드 후 실행 결과</h4>
<pre><code>ex1.go init
ex1.go init2
ex2.go init
ex2.go init2
init called in main package
f4887be3-d7db-4b3e-9760-51a1b676cf96</code></pre><br>

<h3 id="📍-init-내에서-전역변수-설정">📍 init() 내에서 전역변수 설정</h3>
<blockquote>
</blockquote>
<p><code>main.go</code></p>
<pre><code class="language-go">package main
&gt;
import (
    &quot;fmt&quot;
    &quot;local_package/something&quot;
)
&gt;
func main() {
    fmt.Println(&quot;final Cnt :&quot;, something.Cnt)
}
&gt;
func init() {
    something.Cnt = 1
}</code></pre>
<blockquote>
</blockquote>
<p><code>ex1.go</code></p>
<pre><code class="language-go">package something
&gt;
import (
    &quot;fmt&quot;
)
&gt;
func init() {
    fmt.Println(&quot;ex1.go init 1&quot;)
    Cnt = 10
}
&gt;
func UpperSth() {
    fmt.Println(&quot;UpperSth() Called&quot;)
    lowerSth()
    ExportSth()
    cannotExportSth()
}
&gt;
func lowerSth() {
    fmt.Println(&quot;lowerSth() Called&quot;)
}</code></pre>
<blockquote>
</blockquote>
<p><code>ex2.go</code></p>
<pre><code class="language-go">package something
&gt;
import (
    &quot;fmt&quot;
)
&gt;
var Cnt int8
&gt;
func ExportSth() {
    fmt.Println(&quot;ExportSth() Called&quot;)
}
&gt;
func cannotExportSth() {
    fmt.Println(&quot;cannotExportSth() Called&quot;)
}
&gt;
func init() {
    fmt.Println(&quot;ex2.go init 1&quot;)
    Cnt = 20
}</code></pre>
<br>

<h4 id="빌드-후-실행-결과-2">빌드 후 실행 결과</h4>
<pre><code>ex1.go init 1
ex2.go init 1
final Cnt : 1</code></pre><hr>
<br>

<p>이상 패키지 / 모듈 / 워크스페이스에 대한 내용이었습니다.
감사합니다.👍</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Golang 기초 (11) : 문자열에 대하여]]></title>
            <link>https://velog.io/@vamos_eon/go-string</link>
            <guid>https://velog.io/@vamos_eon/go-string</guid>
            <pubDate>Sun, 27 Feb 2022 17:39:30 GMT</pubDate>
            <description><![CDATA[<p>안녕하세요, 주니어 개발자 Eon입니다.
이번 포스트는 문자열에 대한 내용입니다.</p>
<br>

<h1 id="📝-문자열-string">📝 문자열 (string)</h1>
<p>문자의 집합이자 <code>문자들로 이루어진 배열</code>입니다.
프로그래밍을 처음 시작할 때 출력하는 <code>&quot;Hello World!!&quot;</code> 또한 <code>문자열</code>입니다.
<code>&#39;H&#39;, &#39;e&#39;, &#39;l&#39;, &#39;l&#39;, &#39;o&#39;, &#39; &#39;, &#39;W&#39;, &#39;o&#39;, &#39;r&#39;, &#39;l&#39;, &#39;d&#39;, &#39;!&#39;, &#39;!&#39;</code>
위의 문자들이 모여, 하나의 <code>문자열</code>을 구성한 것입니다.
<code>golang</code>의 자료형 중, <code>string</code>과 <code>rune</code>으로 표현할 수 있습니다.</p>
<br>

<h2 id="📌-유니코드">📌 유니코드</h2>
<p><code>ASCII code</code> (0~255; 1byte)로는 표현할 수 있는 문자에 한계가 있었습니다.
<a href="https://www.ascii-code.com/">https://www.ascii-code.com/</a>
그래서 개발된 게 <code>유니코드</code>입니다.
<code>유니코드</code>는 국제 표준이며, 여러 가지 종류가 있습니다. <a href="https://ko.wikipedia.org/wiki/%EC%9C%A0%EB%8B%88%EC%BD%94%EB%93%9C">(utf-1, utf-7, utf-8, utf-EBCDIC, utf-16, utf-32)</a></p>
<ul>
<li><code>utf-16</code><ul>
<li><strong>문자 하나를 표현하는 데에 2 바이트가 필요</strong>합니다.</li>
<li>장점 : <strong>모든 문자를 2바이트</strong>로 표현 가능합니다.</li>
<li>단점 : <strong>1바이트로 표현 가능한 문자도 2바이트를 차지</strong>합니다.</li>
</ul>
</li>
<li><code>utf-8</code> (<code>golang</code>에서 사용되는 문자 인코딩 방식)<ul>
<li><strong>문자 하나를 표현하는 데에 1 ~ 4 바이트가 필요</strong>합니다. </li>
<li>장점 : <strong><code>ASCII</code> 1바이트 문자들로 구성을 많이 할수록 메모리를 적게 사용</strong>합니다.</li>
<li>단점 : 그 외의 문자들의 사용이 많아질수록 메모리 사용량이 커집니다.</li>
</ul>
</li>
</ul>
<h3 id="📍-utf-8">📍 utf-8</h3>
<p><strong>인터넷 공간의 데이터 중 80%를 영어와 숫자가 차지한다</strong>고 합니다.
이러한 점에서 <code>utf-8</code>은 매우 <strong>효율적인 인코딩 방식</strong>이라고 볼 수 있습니다.</p>
<p>모든 문자에 대하여 같은 메모리가 아닌, 모든 문자에 각각 필요로 하는 만큼 메모리를 할당합니다.</p>
<p>1바이트 : <code>ASCII</code> 0~127 총 128개
2바이트 : 그리스어, 키릴어, 콥트어, 아르메니아어, 히브리어, 아랍어, 시리아어, 타나 문자, 분음 부호 결합 총 1920개
3바이트 : 대부분의 중국어, 일본어, <strong>한국어</strong>
4바이트 : 다른 평면 문자, 일반적이지 않은 한중일어, 수학 기호, 이모지 등이 포함</p>
<p>한글의 자음, 모음, 받침을 합쳐서 3바이트인 것인가하는 추측은 하지 말아야 합니다.
전혀 그렇지 않고, 그저 국제 표준일 뿐입니다.</p>
<hr>
<br>

<h1 id="📝-문자열의-활용">📝 문자열의 활용</h1>
<h2 id="📌-문자열의-선언">📌 문자열의 선언</h2>
<blockquote>
</blockquote>
<pre><code class="language-go">var str string
var str string = &quot;Hello World!!&quot;
var str string = `Hello World!!`
var str string = `
Hello 
World!!
`</code></pre>
<p><code>문자열</code>의 선언은 위의 방법 네 가지가 있습니다.</p>
<p>1) 초기화 없이 선언합니다. 이 때, 값은 <code>&quot;&quot;</code> 빈 <code>문자열</code>이 됩니다.
2) <code>Hello World!!</code>로 초기화를 합니다. <strong>동일한 줄에서 큰 따옴표를 닫아야 합니다.</strong>
3) <strong>`</strong> <strong>`</strong>로 초기화합니다.
4) 3)에서 초기화한 값에 <strong>개행을 포함하여 초기화</strong>합니다. <strong>개행은 마지막 줄을 제외한 모든 줄에 포함</strong>돼 있습니다.</p>
<br>

<h2 id="📌-문자열의-순회">📌 문자열의 순회</h2>
<p>문자열은 반복문 제어를 통해 그 안의 문자들을 순회할 수 있습니다.
방법은 아래와 같습니다.</p>
<br>

<h3 id="📍-len">📍 len()</h3>
<p><code>golang</code>의 기본 <code>built-in</code> 함수입니다.
<code>string</code> 타입을 넣으면 <code>string</code> 변수의 <strong>바이트 길이가 반환</strong>됩니다.
<code>builtin.go</code>에서 <code>len()</code>에 대하여 다음과 같은 설명을 포함합니다.</p>
<pre><code class="language-go">// The len built-in function returns the length of v, according to its type:
//    Array: the number of elements in v.
//    Pointer to array: the number of elements in *v (even if v is nil).
//    Slice, or map: the number of elements in v; if v is nil, len(v) is zero.
//    String: the number of bytes in v.
//    Channel: the number of elements queued (unread) in the channel buffer;
//             if v is nil, len(v) is zero.
// For some arguments, such as a string literal or a simple array expression, the
// result can be a constant. See the Go language specification&#39;s &quot;Length and
// capacity&quot; section for details.
func len(v Type) int</code></pre>
<br>

<h3 id="📍-c-like-for문-순회-영어">📍 C-like for문 순회 (영어)</h3>
<p>C-like 순회는 바이트 단위로 순회합니다.</p>
<blockquote>
</blockquote>
<pre><code class="language-go">var str string = &quot;Hello Wolrd!!&quot;
for i := 0; i &lt; len(str); i++ {
    fmt.Printf(&quot;Type : %T, Character : %c, Code : %d\n&quot;, str[i], str[i], str[i])
}
/* OUTPUT
Type : uint8, Character : H, Code : 72
Type : uint8, Character : e, Code : 101
Type : uint8, Character : l, Code : 108
Type : uint8, Character : l, Code : 108
Type : uint8, Character : o, Code : 111
Type : uint8, Character :  , Code : 32
Type : uint8, Character : W, Code : 87
Type : uint8, Character : o, Code : 111
Type : uint8, Character : l, Code : 108
Type : uint8, Character : r, Code : 114
Type : uint8, Character : d, Code : 100
Type : uint8, Character : !, Code : 33
Type : uint8, Character : !, Code : 33
*/</code></pre>
<p><code>string</code>은 문자들의 배열입니다.
그리고 C-like for문은 <strong>바이트 단위로 순회</strong>합니다.
<code>&quot;Hello World!!&quot;</code> 안의 모든 문자는 각각 1바이트입니다.
따라서 위와 같이 for문 순회를 했을 때, 아무런 문제 없이 원했던 결과를 얻을 수 있었습니다.</p>
<br>

<h3 id="📍-c-like-for문-순회-한글">📍 C-like for문 순회 (한글)</h3>
<blockquote>
</blockquote>
<pre><code class="language-go">var 스트링 string = &quot;헬로 월드!!&quot;
for i := 0; i &lt; len(스트링); i++ {
    fmt.Printf(&quot;Type : %T, Character : %c, Code : %d\n&quot;, 스트링[i], 스트링[i], 스트링[i])
}
/* OUTPUT
Type : uint8, Character : í, Code : 237
Type : uint8, Character : , Code : 151
Type : uint8, Character : ¬, Code : 172
Type : uint8, Character : ë, Code : 235
Type : uint8, Character : ¡, Code : 161
Type : uint8, Character : , Code : 156
Type : uint8, Character :  , Code : 32
Type : uint8, Character : ì, Code : 236
Type : uint8, Character : ode : 155
Type : uint8, Character : , Code : 148
Type : uint8, Character : ë, Code : 235
Type : uint8, Character : , Code : 147
Type : uint8, Character : , Code : 156
Type : uint8, Character : !, Code : 33
Type : uint8, Character : !, Code : 33
*/</code></pre>
<p>앞서 설명한 바와 같이, <code>utf-8</code>에서 <strong>한글</strong>은 글자 하나 당 3바이트로 구성됩니다.
C-like for문 순회는 <code>len()</code>을 사용해야 하는데, <code>len()</code>은 <strong>바이트 길이를 반환</strong>합니다.
그리고 C-like for문은 바이트 길이만큼 순회를 하기 때문에 한글 글자 하나를 구성하는 3개 바이트 중 하나씩만 읽을 수 있습니다. 
때문에 <strong>바이트 순회로는 한글을 온전히 읽지 못합니다.</strong>
위의 결과에서 사이에 공백과 맨 아래 느낌표를 제외하고 나머지 부분에서 3개 바이트 당 한글 글자 1개라는 것을 유추할 수 있습니다.</p>
<br>

<h3 id="📍-for-range-순회-영어">📍 for-range 순회 (영어)</h3>
<blockquote>
</blockquote>
<pre><code class="language-go">var str string = &quot;Hello Wolrd!!&quot;
for _, v := range str {
    fmt.Printf(&quot;Type : %T, Character : %c, Code : %d\n&quot;, v, v, v)
}
/* OUTPUT
Type : int32, Character : H, Code : 72
Type : int32, Character : e, Code : 101
Type : int32, Character : l, Code : 108
Type : int32, Character : l, Code : 108
Type : int32, Character : o, Code : 111
Type : int32, Character :  , Code : 32
Type : int32, Character : W, Code : 87
Type : int32, Character : o, Code : 111
Type : int32, Character : l, Code : 108
Type : int32, Character : r, Code : 114
Type : int32, Character : d, Code : 100
Type : int32, Character : !, Code : 33
Type : int32, Character : !, Code : 33
*/</code></pre>
<p>영어는 무리 없이 <code>range</code>로 순회되는 모습을 볼 수 있습니다.
단, 한 가지 차이점이 있다면 <code>Type</code>이 <code>int32</code>로 출력되는 모습을 볼 수 있습니다.</p>
<br>

<h3 id="📍-for-range--string---rune">📍 for-range : string -&gt; rune</h3>
<blockquote>
</blockquote>
<p><code>range</code>에 <code>string</code>을 넣으면 <code>rune</code> 타입으로 변환하여 순회를 하기 때문에 그렇습니다.
<code>golang</code> <code>for-range</code>의 특징이라고 보시면 됩니다.
<code>rune</code>타입은 <code>string</code>처럼 바이트 단위로 쪼개지 않고도 문자 하나를 온전히 표현하는 4바이트의 크기를 가지고 있기 때문에 온전히 모든 문자의 표현이 가능합니다.</p>
<blockquote>
</blockquote>
<p><img src="https://images.velog.io/images/vamos_eon/post/fa9498c3-c71f-4d1c-85f6-dcb48168d5fd/image.png" alt=""></p>
<br>

<h3 id="📍-for-range-순회-한글">📍 for-range 순회 (한글)</h3>
<blockquote>
</blockquote>
<pre><code class="language-go">var 스트링 string = &quot;헬로 월드!!&quot;
for _, v := range 스트링 {
    fmt.Printf(&quot;Type : %T, Character : %c, Code : %d\n&quot;, v, v, v)
}
/* OUTPUT
Type : int32, Character : 헬, Code : 54764
Type : int32, Character : 로, Code : 47196
Type : int32, Character :  , Code : 32
Type : int32, Character : 월, Code : 50900
Type : int32, Character : 드, Code : 46300
Type : int32, Character : !, Code : 33
Type : int32, Character : !, Code : 33
*/</code></pre>
<p><code>for-range</code>로 순회를 했더니 <strong>한글도 문제없이 출력</strong>되는 모습을 확인할 수 있습니다.</p>
<br>

<h3 id="📍-c-like-for문-순회-한글-rune">📍 C-like for문 순회 (한글); []rune</h3>
<blockquote>
</blockquote>
<pre><code class="language-go">var 스트링 string = &quot;헬로 월드!!&quot;
var 룬슬라이스 []rune = []rune(스트링)
for i := 0; i &lt; len(룬슬라이스); i++ {
    fmt.Printf(&quot;Type : %T, Character : %c, Code : %d\n&quot;, 룬슬라이스[i], 룬슬라이스[i], 룬슬라이스[i])
}
/* OUTPUT
Type : int32, Character : 헬, Code : 54764
Type : int32, Character : 로, Code : 47196
Type : int32, Character :  , Code : 32
Type : int32, Character : 월, Code : 50900
Type : int32, Character : 드, Code : 46300
Type : int32, Character : !, Code : 33
Type : int32, Character : !, Code : 33
*/</code></pre>
<p><code>len()</code>은 위에 써있듯이 <code>슬라이스</code>에 대해서는 <strong>요소의 개수를 반환</strong>합니다.
따라서 <code>[]rune</code>의 요소인 <code>rune</code> 타입의 값들을 출력합니다.</p>
<br>

<h2 id="📌-문자열의-연산">📌 문자열의 연산</h2>
<p><code>문자열</code>은 <strong>연산이 가능</strong>합니다.
단, 사칙연산 중에는 <strong>덧셈</strong>만 가능하고, <strong>대입</strong> 연산이나 <strong>비교</strong> 연산이 가능합니다.</p>
<br>

<h3 id="📍-문자열의-연산--덧셈-연산">📍 문자열의 연산 : 덧셈 연산</h3>
<p><strong>덧셈</strong> 연산이 가능합니다.</p>
<blockquote>
</blockquote>
<pre><code class="language-go">var str1 string = &quot;Hello&quot;
var str2 string = &quot;World&quot;
var strMerge string = str1 + &quot; &quot; + str2 + &quot;!!&quot;
fmt.Println(strMerge)
// Hello World!!</code></pre>
<p>위와 같이 <code>&quot;Hello&quot;</code>라는 <code>문자열</code>과 <code>&quot;World!!&quot;</code>라는 <code>문자열</code>을 하나의 <code>문자열</code>로 합치는 게 가능합니다.</p>
<br>

<h3 id="📍-문자열의-연산--대입-연산">📍 문자열의 연산 : 대입 연산</h3>
<p><strong>대입</strong> 연산이 가능합니다.</p>
<blockquote>
</blockquote>
<pre><code class="language-go">var str1 string = &quot;Hello&quot;
var str2 string = &quot;&quot;
str2 = str1
fmt.Println(str2)
// Hello</code></pre>
<p><code>str2</code>는 빈 문자열로 초기화를 했다가, <code>str1</code>을 <strong>대입</strong>했습니다.
<code>str2</code>의 값으로 <code>Hello</code>가 출력된 것을 볼 수 있습니다.
문자로 이루어진 배열이라 했지만 <strong>길이가 서로 다른 문자열끼리도 대입 연산이 가능</strong>합니다.</p>
<br>

<h3 id="📍-문자열의-연산--비교-연산">📍 문자열의 연산 : 비교 연산</h3>
<p><code>문자열</code>은 문자들로 이루어진 배열입니다.
각각의 요소는 문자이며, <code>golang</code>은 <code>utf-8</code>을 지원하고 <strong><code>utf-8</code>은 모든 문자를 4byte 내로 표현할</strong> 수 있습니다.
<code>golang</code>에서 문자는 <code>rune</code> 타입이며 <strong><code>rune</code> 타입은 <code>utf-8</code>의 모든 문자를 표현할 수 있는 4바이트 크기의 <code>int32</code>의 별칭 타입</strong>입니다.
그래서 <strong>문자 코드는 정수로 표현할 수 있습니다.</strong>
문자열의 비교 연산은 문자 코드의 비교입니다.</p>
<blockquote>
</blockquote>
<pre><code class="language-go">var str1 string = &quot;ABCDE&quot;
var str2 string = &quot;abcde&quot;
if str1 &gt; str2 {
    fmt.Println(&quot;str1 &gt; str2&quot;)
} else if str1 &lt; str2 {
    fmt.Println(&quot;str1 &lt; str2&quot;)
}
// str1 &lt; str2</code></pre>
<p><code>문자열</code>의 <strong>비교 연산은 각 문자의 문자 코드 단위로 이루어집니다.</strong>
<code>A</code>와 <code>a</code>를 비교하고, <code>B</code>와 <code>b</code>, <code>C</code>와 <code>c</code> 순으로 비교합니다.
조건문 <code>str1 &gt; str2</code>의 비교를 하면, 맨 처음 비교하게 되는 <code>A</code>와 <code>a</code>의 비교에서 이미 조건을 만족하지 않기 때문에 뒤에 오는 문자의 비교는 하지 않습니다.
바로 다음 조건문으로 넘어가, <strong>모든 문자열 요소들에 대해 조건을 만족하는지 확인</strong>합니다.</p>
<hr>
<br>

<h1 id="📝-문자열-덧셈-대입-연산-시-메모리의-동작">📝 문자열 덧셈, 대입 연산 시, 메모리의 동작</h1>
<p><code>문자열</code>은 문자의 배열이라고 했습니다.
<strong><code>golang</code>에서는 배열의 크기가 한 번 정해지면 변경이 불가</strong>합니다.
<code>문자열</code>의 <strong>덧셈</strong> 연산이 가능하다고 해서 한 번 정한 크기를 변경할 수 있는 것은 아닙니다.
내부 동작에서 메모리 할당을 알아서 해주니 사용자는 신경 쓸 필요가 없습니다.
다만, 알고는 있어야 메모리 사용량을 줄일 수 있을 것입니다.</p>
<br>

<h2 id="📌-문자열-변수의-실제-메모리-크기">📌 문자열 변수의 실제 메모리 크기</h2>
<p><code>string</code> 변수 <code>str</code>를 선언하고 초기화를 하면 실제로 할당되는 메모리의 크기는 다음과 같습니다.</p>
<blockquote>
</blockquote>
<p><strong><code>len(str)+ unsafe.Sizeof(reflect.StringHeader{})</code></strong>
<code>문자열 바이트 길이 + StringHeader의 크기</code></p>
<br>

<h3 id="📍-stringheader">📍 StringHeader</h3>
<p><code>문자열</code>에 대한 정보를 담고 있습니다.</p>
<blockquote>
</blockquote>
<pre><code class="language-go">type StringHeader struct {
    Data uintptr
    Len  int
}</code></pre>
<p><code>Data</code> : 실제 값이 저장된 메모리 주소
<code>Len</code> : 문자열의 길이</p>
<br>

<h2 id="📌-문자열의-덧셈-대입-연산">📌 문자열의 덧셈, 대입 연산</h2>
<blockquote>
</blockquote>
<pre><code class="language-go">var str1 string = &quot;Hello&quot;
var str2 string = &quot;World&quot;
str2 += str1
var str3 string = str2
strHeader1 := (*reflect.StringHeader)(unsafe.Pointer(&amp;str1))
strHeader2 := (*reflect.StringHeader)(unsafe.Pointer(&amp;str2))
strHeader3 := (*reflect.StringHeader)(unsafe.Pointer(&amp;str3))
fmt.Println(&quot;Value of strHeader1 :&quot;, strHeader1)
fmt.Println(&quot;Value of strHeader2 :&quot;, strHeader2)
fmt.Println(&quot;Value of strHeader3 :&quot;, strHeader3)
fmt.Printf(&quot;Pointer value of strHeader1 : %p\n&quot;, strHeader1)
fmt.Printf(&quot;Pointer value of strHeader2 : %p\n&quot;, strHeader2)
fmt.Printf(&quot;Pointer value of strHeader3 : %p\n&quot;, strHeader3)
&gt;
/* OUTPUT
Value of strHeader1 : &amp;{4807070 5}
Value of strHeader2 : &amp;{824634400768 10}
Value of strHeader3 : &amp;{824634400768 10}
Pointer value of strHeader1 : 0xc00008a210
Pointer value of strHeader2 : 0xc00008a220
Pointer value of strHeader3 : 0xc00008a230
*/</code></pre>
<p>위의 <code>Pointer value</code> 결과를 보면, 각 <code>str1</code>, <code>str2</code>, <code>str3</code>의 주소값은 모두 다른 것을 볼 수 있으며, <strong>그 차이가 16바이트인 것을 확인</strong>할 수 있습니다.
<img src="https://images.velog.io/images/vamos_eon/post/b35d00ac-b649-4c16-bd1c-e7b6e101c434/image.png" alt="">
위 그림과 같이 <code>str1</code>, <code>str2</code>, <code>str3</code> 변수가 선언되고 초기화될 때마다 <strong>16바이트씩 메모리가 할당</strong>된 것을 볼 수 있습니다.
<code>문자열</code>을 더하든, 대입하든 <code>string</code> 변수 자체는 16바이트 크기의 <strong><code>StringHeader</code></strong> 를 가지기 때문에 이러한 연산이 가능합니다.
단, 유동적으로 <code>string</code> 변수의 크기를 줄였다가 늘렸다가 할 수 있는 것이 아니기 때문에 <strong>연산을 할 때마다 새로 메모리를 할당</strong>한다는 단점이 있습니다.</p>
<hr>
<br>

<h1 id="📝-문자열-일부-변경이-가능할까">📝 문자열 일부 변경이 가능할까?</h1>
<p><code>string</code> 타입만으로는 불가능합니다.
<strong>문자열 일부 변경은 타입 변환을 통해서 수행가능</strong>합니다.</p>
<br>

<h2 id="📌-string-to-rune-or-byte-rune-or-byte-to-string">📌 string to rune (or byte), rune (or byte) to string</h2>
<blockquote>
</blockquote>
<pre><code class="language-go">var str string = &quot;a&quot;
var slice []rune = []rune(str)
// var slice []byte = []byte(str)</code></pre>
<p><code>문자열</code>의 일부를 변경하기 위해서는 먼저 <code>[]rune</code> or <code>[]byte</code> 타입으로 변환해야 합니다.
위와 같이 문자 1개로만 이루어진 <code>string</code> 변수도 마찬가지입니다.</p>
<blockquote>
</blockquote>
<pre><code class="language-go">str = string(slice)</code></pre>
<p><code>[]run</code> 타입의 변수를 <code>string</code>으로 변환하기 위해서는 위와 같이 작성하면 됩니다.</p>
<br>

<h2 id="📌-슬라이스-변수의-실제-메모리-크기">📌 슬라이스 변수의 실제 메모리 크기</h2>
<p><code>slice</code> 변수 <code>slice</code>를 선언하고 초기화를 하면 실제로 할당되는 메모리의 크기는 다음과 같습니다.
<strong><code>len(slice)+ unsafe.Sizeof(reflect.SliceHeader{})</code></strong>
<code>슬라이스 바이트 길이 + SliceHeader의 크기</code></p>
<br>

<h3 id="📍-sliceheader">📍 SliceHeader</h3>
<p><code>슬라이스</code>에 대한 정보를 담고 있습니다.</p>
<blockquote>
</blockquote>
<pre><code class="language-go">type SliceHeader struct {
    Data uintptr
    Len  int
    Cap  int
}</code></pre>
<p><code>Data</code> : 실제 값이 저장된 메모리 주소
<code>Len</code> : 슬라이스의 길이
<code>Cap</code> : 슬라이스의 용량 (슬라이스의 길이를 늘릴 경우, Capacity를 넘지 않으면 그냥 붙여서 늘리고, Capacity를 넘어서면 새로운 슬라이스를 기존의 용량의 두 배로 만듭니다.)</p>
<br>

<h2 id="📌-문자열-일부-변경하기">📌 문자열 일부 변경하기</h2>
<blockquote>
</blockquote>
<pre><code class="language-go">var str string = &quot;hello world&quot;
var runeSlice []rune = []rune(str)
fmt.Println(&quot;Original string value :&quot;, str)
strHeader := (*reflect.StringHeader)(unsafe.Pointer(&amp;str))
fmt.Println(&quot;Value of strHeader :&quot;, strHeader)
fmt.Printf(&quot;Pointer value of strHeader : %p\n\n&quot;, strHeader)
for i, v := range runeSlice {
    if v == &#39;o&#39; {
        runeSlice[i] = &#39;!&#39;
    }
}
sliceHeader := (*reflect.SliceHeader)(unsafe.Pointer(&amp;runeSlice))
fmt.Println(&quot;Value of sliceHeader :&quot;, sliceHeader)
fmt.Println(&quot;Size of Slice :&quot;, unsafe.Sizeof(runeSlice))
fmt.Println()
str = string(runeSlice)
fmt.Println(&quot;Changed string value :&quot;, str)
fmt.Println(&quot;Value of strHeader :&quot;, strHeader)
fmt.Printf(&quot;Pointer value of strHeader : %p\n&quot;, strHeader)
/* OUTPUT
Original string value : hello world
Value of strHeader : &amp;{4815093 11}
Pointer value of strHeader : 0xc000010240
&gt;
Value of sliceHeader : &amp;{824633811136 11 12}
Size of Slice : 24
&gt;
Changed string value : hell! w!rld
Value of strHeader : &amp;{824633819312 11}
Pointer value of strHeader : 0xc000010240
*/</code></pre>
<p><code>string</code>타입의 변수 <code>str</code>을 <code>[]rune</code>타입으로 변환한 뒤, 순회를 합니다.
순회 중, 알파벳 <code>o</code>를 만나면 해당<code>[]rune</code>에 <code>!</code>로 값을 바꾸도록 했습니다.
다시 <code>[]rune</code>타입을 <code>string</code>으로 변환 후에 출력하니, 값이 바뀐 것을 확인할 수 있었습니다.</p>
<hr>
<br>

<p>이상 문자열에 대한 내용이었습니다.
감사합니다.👍</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Golang 기초 (10) : 포인터에 대하여]]></title>
            <link>https://velog.io/@vamos_eon/go-pointer</link>
            <guid>https://velog.io/@vamos_eon/go-pointer</guid>
            <pubDate>Sat, 05 Feb 2022 16:15:29 GMT</pubDate>
            <description><![CDATA[<p>안녕하세요, 주니어 개발자 Eon입니다.</p>
<p>이번 포스트는 포인터에 대한 내용입니다.</p>
<h1 id="📝-포인터란">📝 포인터란?</h1>
<p><strong>&#39;메모리 주소를 가리키는 것&#39;</strong> 을 의미합니다.
변수를 선언하면 메모리 영역에 공간이 할당되는데, 그 메모리 영역의 주소를 가리키는 것을 포인터라고 합니다.
<strong><code>포인터 변수</code>는 메모리 주소를 값으로 가집니다.</strong>
<img src="https://images.velog.io/images/vamos_eon/post/2a0c22ab-7b29-4f61-be15-287356c9c32a/image.png" alt=""></p>
<hr>
<br>

<h1 id="📝-인스턴스란">📝 인스턴스란?</h1>
<p><strong>메모리의 실체</strong>를 말합니다.
우리가 변수를 선언하면 변수의 자료형 크기만큼 메모리가 할당됩니다.
<strong><code>인스턴스</code>는, 그 할당된 메모리의 실체</strong>를 가리키는 말입니다.
<img src="https://images.velog.io/images/vamos_eon/post/c0a777f1-a671-443e-8059-819577dc82b0/image.png" alt=""></p>
<hr>
<br>

<h1 id="📝-포인터는-왜-사용할까">📝 포인터는 왜 사용할까?</h1>
<p><code>포인터</code>를 사용하면 <strong>메모리 영역에 직접 접근</strong>하여 <code>인스턴스</code>를 조작할 수 있습니다.
어디서든 <code>인스턴스</code>에 접근할 수 있어, 메모리를 중복으로 할당하는 경우를 줄이고 <strong>불필요한 메모리 낭비를 막을 수 있습니다.</strong></p>
<blockquote>
</blockquote>
<ul>
<li><strong>퍼포먼스를 신경쓸 때</strong><ul>
<li><code>포인터</code>는 퍼포먼스를 고려하지 않을 땐 사용할 이유가 없다.
물론, <code>포인터</code>타입을 반환하는 함수를 사용할 땐 사용할 수 밖에 없다.</li>
<li><code>인스턴스</code>를 메모리에 통째로 복사해서 사용할 때, <code>인스턴스</code>의 주소만 넘김으로써 메모리 낭비를 줄일 때 사용한다.</li>
</ul>
</li>
</ul>
<hr>
<br>

<h1 id="📝-포인터">📝 포인터</h1>
<h2 id="📌-포인터의-초기화">📌 포인터의 초기화</h2>
<blockquote>
</blockquote>
<pre><code class="language-go">var p1 *int
&gt;
// var a int
var p1 *int = &amp;a
&gt;
p1 := &amp;a
&gt;
// type Sth struct {}
p1 := &amp;Sth{}
&gt;
p1 := new(int)</code></pre>
<p>위의 선언은 각기 다른 <code>포인터 변수</code>의 선언 방법을 나타낸다.</p>
<ul>
<li><code>var p1 *int</code> : <code>pointer int</code>형의 변수 <code>p1</code>을 선언한다.
이때, <code>p1</code>의 값은 <code>nil</code>이다. (아래 내용 참조)</li>
<li><code>var p1 *int = &amp;a</code>, <code>p1 := &amp;a</code> : <code>pointer int</code>형의 변수 <code>p1</code>을 선언하고 <code>&amp;a</code>로 초기화한다.
<code>&amp;a</code>는 변수 <code>a</code>의 주소값이다.</li>
<li><code>p1 := &amp;Sth{}</code> : <code>pointer Sth{}</code>형의 변수 <code>p1</code>을 선언한다.</li>
<li><code>p1 := new(int)</code> : <code>pointer int</code>형의 변수 <code>p1</code>을 선언한다.
<code>new(Type)</code>은 <code>pointer Type</code>의 <code>zero value</code>를 반환한다.</li>
</ul>
<br>

<h2 id="📌-포인터-변수의-기본값은-nil">📌 포인터 변수의 기본값은 nil</h2>
<blockquote>
</blockquote>
<pre><code class="language-go">var pt *int
fmt.Println(pt)
// &lt;nil&gt;</code></pre>
<ul>
<li>초기화를 하지 않고 <code>zero value</code> 상태의 <code>포인터 변수</code>를 출력하면 <code>&lt;nil&gt;</code>이 출력된다.</li>
<li><code>nil</code>은 출력할 때 항상 <code>&lt;</code>, <code>&gt;</code>을 포함한다.</li>
<li><code>포인터 변수</code>의 값이 <code>nil</code>이라는 것은 가리키고 있는 <code>인스턴스</code>가 없음을 말한다.</li>
</ul>
<br>

<h3 id="📍-nil이란">📍 nil이란?</h3>
<blockquote>
</blockquote>
<p>builtin.go</p>
<pre><code class="language-go">// nil is a predeclared identifier representing the zero value for a
// pointer, channel, func, interface, map, or slice type.
var nil Type // Type must be a pointer, channel, func, interface, map, or slice type</code></pre>
<p><code>nil</code>은 <code>pointer, channel, func, interface, map, or slice type</code>의 <code>zero value</code>(기본값)이다.
(예 : <code>int</code>의 <code>zero value</code>는 <code>0</code>이다.)</p>
<br>

<h2 id="📌-포인터의-사용">📌 포인터의 사용</h2>
<h3 id="📍-예시-1">📍 예시 1</h3>
<blockquote>
</blockquote>
<pre><code class="language-go">var a int = 10
var b int = 20
&gt;
var p1 *int = &amp;a
var p2 *int = &amp;b
&gt;
fmt.Printf(&quot;variable a : %d, address : %p\n&quot;, a, p1)
fmt.Printf(&quot;variable b : %d, address : %p\n&quot;, b, p2)
fmt.Printf(&quot;variable p1 is pointing a : %d\n&quot;, *p1)
&gt;
// ***** result of the output *****
// variable a : 10, address : 0xc0000a8000
// variable b : 20, address : 0xc0000a8008
// variable p1 is pointing a : 10</code></pre>
<ul>
<li>변수 <code>a</code>와 <code>b</code>를 <code>int</code>형으로 선언하고, 각각 <code>10</code>, <code>20</code>으로 초기화한다.</li>
<li>변수 <code>p1</code>과 <code>p2</code>를 <code>*int</code>형(<code>pointer int</code>형)으로 선언하고, 각각 <code>a</code>와 <code>b</code>의 주소값으로 초기화한다.</li>
<li>초기화한 값들을 출력하여 확인한다.
여기서 <code>a</code>와 <code>b</code>의 주소값의 차이가 <code>8</code>만큼 나는 것을 확인할 수 있다.
둘 다 <code>int</code>형 변수이고, 64비트 OS라서 <code>int</code>형이 <code>8byte</code>의 사이즈를 가진다.
<code>a</code>의 메모리를 할당하고 가장 빠른 메모리 영역이 <code>8</code>만큼 뒤의 영역이고, <code>b</code>는 <code>a</code>의 바로 뒤에 메모리가 할당되었다는 것을 알 수 있다.</li>
<li><code>포인터</code> 자료형의 출력은 <code>%p</code>로 할 수 있다.</li>
<li><code>*p1</code>으로 <code>p1</code>이 가리키는 <code>인스턴스</code>의 값을 나타내고, 출력하여 값을 확인할 수 있다.
여기서 <code>p1</code>은 <code>a</code>의 주소값을 가지고 있고 해당 주소값을 <code>*</code> <code>(pointer)</code>로 가리켜, <code>인스턴스</code>의 값을 나타낸다.</li>
</ul>
<h3 id="📍-예시-2">📍 예시 2</h3>
<blockquote>
</blockquote>
<pre><code class="language-go">package main
&gt;
import (
    &quot;fmt&quot;
    &quot;unsafe&quot;
)
&gt;
type pTest struct {
    pStr string
    pInt int
}
&gt;
func setStr(getStruct *pTest) {
    getStruct.pStr = &quot;struct pTest&#39;s byte size :&quot;
}
&gt;
func setInt(getStruct *pTest) {
    getStruct.pInt = int(unsafe.Sizeof(pTest{}))
}
&gt;
func main() {
    var example pTest
&gt;
    setStr(&amp;example)
    setInt(&amp;example)
&gt;
    fmt.Println(example)
}
// {struct pTest&#39;s byte size : 24}</code></pre>
<p>위와 같이 다른 서로 다른 함수에서 같은 구조체 <code>인스턴스</code>인 <code>example</code>에 접근하여 요소를 조작했다.
출력 결과를 통해, 같은 인스턴스에 대하여 조작했다는 것을 확인할 수 있다.</p>
<h3 id="📍-예시-3">📍 예시 3</h3>
<blockquote>
</blockquote>
<pre><code class="language-go">package main
&gt;
import (
    &quot;fmt&quot;
    &quot;sync&quot;
)
&gt;
type pTest struct {
    pStr string
    pInt int
}
&gt;
func addStr(wg *sync.WaitGroup, getStruct *pTest, str rune) {
    getStruct.pStr = getStruct.pStr + string(97+str)
    defer wg.Done()
}
&gt;
func addInt(wg *sync.WaitGroup, getStruct *pTest, num int) {
    getStruct.pInt += num
    defer wg.Done()
}
&gt;
func printStruct(wg *sync.WaitGroup, getStruct *pTest, num int) {
    fmt.Println(getStruct)
    defer wg.Done()
}
&gt;
func main() {
    var example pTest
    defer fmt.Println(&quot;===== Finished =====&quot;)
    wg := sync.WaitGroup{}
    defer wg.Wait()
    for i := 0; i &lt;= 10; i++ {
        wg.Add(1)
        go addStr(&amp;wg, &amp;example, rune(i))
        wg.Add(1)
        go addInt(&amp;wg, &amp;example, i)
        wg.Add(1)
        go printStruct(&amp;wg, &amp;example, i)
    }
}</code></pre>
<p>위의 코드를 실행하면 실행할 때마다 결과가 바뀐다.
<code>goroutine</code>(golang의 멀티 쓰레드)을 사용한 것인데, golang에서 <code>동시성 프로그래밍</code>을 할 때 사용한다.
(os 쓰레드보다 훨씬 가볍게 동작하는 가상 쓰레드이며, 비동기(asynchronously) 실행을 한다.)
아무튼 위와 같이 사용하면 <code>인스턴스</code>를 사용자가 예측 불가하게 무작위로 조작하기 때문에, 동시에 <strong>같은 <code>인스턴스</code>에 접근 및 조작하는 로직을 구현할 때는 주의</strong>해야 한다.</p>
<hr>
<br>

<h1 id="📝-스택--힙-메모리">📝 스택 &amp; 힙 메모리</h1>
<br>

<h2 id="📌-스택-메모리">📌 스택 메모리</h2>
<p><code>스택 메모리</code>는 함수 호출 시에 함수에 <code>자동으로 할당되는 메모리</code>를 말합니다.
<strong>함수가 끝날 때 자동으로 정리되는 메모리</strong>입니다.</p>
<br>

<h2 id="📌-힙-메모리">📌 힙 메모리</h2>
<p><code>힙 메모리</code>는 프로그래머가 <code>수동으로 할당하는 메모리</code>를 말합니다.
수동으로 할당하기 때문에 <strong>프로그래머가 수동으로 해제해야 하는 메모리</strong>입니다.
그렇지 않으면 메모리에서 자동으로 해제되지 않기 때문에 메모리 낭비로 이어집니다.</p>
<br>

<h2 id="📌-메모리-관리는-어떻게-해야-할까">📌 메모리 관리는 어떻게 해야 할까?</h2>
<p>Golang은 <code>스택</code>이든 <code>힙</code>이든 <code>가비지 컬렉터</code>가 <strong>알아서 메모리를 정리</strong>해줍니다.
때문에 사용자가 고심하며 메모리 할당에 대해 고민할 필요가 없습니다.
물론, 나중에 무거운 기능을 수행하는 코드를 작성할 때에는 고려해야만 합니다.</p>
<blockquote>
</blockquote>
<p>예를 들어, file을 읽어오는데 그 file의 크기를 이미 알고 있다면 그에 맞게 메모리를 할당해, 힙 영역에 메모리가 생기지 않게 하는 것이 좋습니다. </p>
<h3 id="📍-힙은-비용이-비싸다">📍 힙은 비용이 비싸다?</h3>
<p><strong>힙은 스택에 비해 비용이 비쌉니다.</strong>
비용이 비싸다는 것은, <strong>&quot;힙은 엑세스와 메모리 해제가 스택에 비해 느리다&quot;</strong> 라는 것을 말합니다.
여러 관점에서, 힙에 메모리가 할당되는 것은 지양하는 것이 좋습니다.</p>
<br>

<h2 id="📌-탈출-분석">📌 탈출 분석</h2>
<p>탈출 분석은 함수에서 함수로 <strong><code>인스턴스</code>가 탈출하는지 여부를 분석</strong>하는 것입니다.
탈출 분석을 통해 <strong>탈출하는 인스턴스의 메모리를 스택이 아닌 힙에 할당</strong>합니다.
<strong>인스턴스가 함수 외부에서도 사용이 된다면 스택 메모리에 할당하는 것은 적절하지 않기 때문</strong>입니다.</p>
<br>

<h3 id="📍-힙-메모리-할당-확인">📍 힙 메모리 할당 확인</h3>
<p>아래의 명령어로 <code>.go</code> 파일을 컴파일하면 컴파일 과정에서 힙 메모리의 사용 여부를 확인할 수 있습니다.</p>
<pre><code class="language-bash">go build -gcflags &#39;-m -l&#39;
go build -gcflags &#39;-m&#39;
go build -gcflags &#39;-m=2&#39;</code></pre>
<ul>
<li>&#39;-m -l&#39; : 옵티마이징 레벨을 1로 하고, 인라이닝을 없앱니다.</li>
<li>&#39;-m&#39; : 옵티마이징 레벨을 1로 합니다.</li>
<li>&#39;-m=2&#39; : 옵티마이징 레벨을 2로 합니다. (보다 상세하게 나옵니다.)</li>
</ul>
<p>컴파일 옵션은 아래의 명령어로 확인할 수 있습니다.</p>
<pre><code class="language-bash">go tool compile -help</code></pre>
<h3 id="📍-예시">📍 예시</h3>
<blockquote>
</blockquote>
<pre><code class="language-go">package main
&gt;
func get(p *int) *int {
    example := *p
    return &amp;example
}
&gt;
func main() {
    var a int
    var p *int = &amp;a
    a = 20
    _ = get(p)
}</code></pre>
<blockquote>
</blockquote>
<hr>
<blockquote>
</blockquote>
<pre><code class="language-bash">eon@vamos-eon:~/goprojects/pointer$ go build -gcflags &#39;-m -l&#39;
# main
./pointer.go:5:10: p does not escape
./pointer.go:6:2: moved to heap: example
./pointer.go:16:13: ... argument does not escape
./pointer.go:16:14: &quot;&quot; escapes to heap</code></pre>
<p>위와 같이 <code>get()</code>함수에서 변수 <code>example</code>을 할당했으나, <strong><code>example</code>의 <code>인스턴스</code>를 <code>get()</code>함수 외부로 반환하여 스택이 아닌 힙에 메모리가 할당</strong>된 것을 확인할 수 있다.</p>
<hr>
<p>이번 포스트는 포인터에 대한 내용이었습니다.
감사합니다.👍</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Golang 기초 (9) : 구조체에 대하여]]></title>
            <link>https://velog.io/@vamos_eon/go-structure</link>
            <guid>https://velog.io/@vamos_eon/go-structure</guid>
            <pubDate>Sun, 16 Jan 2022 12:32:58 GMT</pubDate>
            <description><![CDATA[<p>안녕하세요, 주니어 개발자 Eon입니다.</p>
<p>이번 포스트에서는 구조체에 대해 다루겠습니다.</p>
<h1 id="📝-구조체란">📝 구조체란?</h1>
<p><strong>Structure</strong> 라고 한다.</p>
<ul>
<li>여러 필드를 묶어둔 타입</li>
</ul>
<p><img src="https://images.velog.io/images/vamos_eon/post/3ee70ce8-4639-4d3a-87f9-97ec5e695fe9/image.png" alt="">
위의 그림을 보면, 직원 한 명을 특정하기 위해 총 8개의 속성이 사용됐습니다.
구조체를 어떻게 사용하는지 아직은 모르지만, 저 속성들 모두 사용해야 한다면 아래와 같이 사용할 수 있습니다.</p>
<pre><code class="language-go">var Employee1_Name string = &quot;Eon&quot;
var Employee1_Rank string = &quot;staff&quot;
var Employee1_No uint32 = 200803
var Employee1_Department string = &quot;Development&quot;
var Employee1_Vacation int8 = 15
var Employee1_AnnualIncome uint64 = 50000000
var Employee1_Email string = &quot;vamos_eon@email.com&quot;
var Employee1_InCharge string = &quot;backend&quot;</code></pre>
<p>정말 복잡합니다. 최대한 가독성 좋게 만들어 보려고 해도 표현할 직원이 몇이나 더 생길지도 모르고, 사용할 때마다 사이드 이펙트를 고려해야 합니다.
가령, <code>Employee1</code>의 이름을 다른 사람으로 바꾼다면 그건 전혀 의미가 없는 정보가 됩니다.
따라서 무엇 하나 바꾼다면 그게 다른 속성에 영향을 끼치는지 확인 후에 전부 함께 바꿔야 합니다.
앞에 <code>Employee1_</code>를 항상 써가면서 말입니다.</p>
<p>구조체를 사용하면 위의 그림처럼, 직원마다 다른 정보를 넣어 관리하기 편리해집니다.</p>
<hr>
<br>

<h1 id="📝-구조체">📝 구조체</h1>
<p>구조체로 표현하면 값을 바꿀 때도, 새로 값을 넣을 때도 편하게 넣을 수 있습니다.</p>
<br>

<h2 id="📌-구조체의-선언">📌 구조체의 선언</h2>
<blockquote>
</blockquote>
<pre><code class="language-go">type 타입명 struct {
    필드명 타입
     ...
    필드명 타입
}</code></pre>
<p>작성할 구조체의 이름을 정합니다.
구조체의 이름은 변수를 선언할 때 작성하는 타입과 비슷하게 작성할 수 있습니다.</p>
<p>위의 사용법을 바탕으로 직원에 대한 구조체를 작성해보겠습니다.</p>
<pre><code class="language-go">type Employee struct {
    Name        string
    Rank        string
    No        uint32
    Department    string
    Vacation    int8
    AnnualIncome    uint64
    Email        string
    InCharge    string
}</code></pre>
<p>구조체를 작성했습니다.</p>
<br>

<h2 id="📌-구조체-변수의-선언">📌 구조체 변수의 선언</h2>
<blockquote>
</blockquote>
<pre><code class="language-go">var 구조체변수명 구조체타입명</code></pre>
<p>이어서, 구조체 변수를 선언하겠습니다.</p>
<pre><code class="language-go">var employee Employee</code></pre>
<p>이제 구조체 변수 <code>employee</code>은 구조체 <code>Employee</code>가 가지는 필드를 모두 사용할 수 있습니다.</p>
<br>

<h2 id="📌-구조체-변수의-초기화">📌 구조체 변수의 초기화</h2>
<p>위에서 선언한 구조체 변수 <code>employee</code>에 값을 초기화해서 사용해보겠습니다.
구조체 변수에 값을 대입하기 위해서는 아래의 방법들이 있습니다.</p>
<blockquote>
</blockquote>
<h3 id="📍-방법-1-각-필드를-따로-초기화">📍 방법 1. 각 필드를 따로 초기화</h3>
<pre><code class="language-go">var employee Employee
employee.Name = &quot;Eon&quot;
employee.Rank = &quot;staff&quot;
employee.No = 200803
employee.Department = &quot;Development&quot;
employee.Vacation = 15
employee.AnnualIncome = 50000000
employee.Email = &quot;vamos_eon@email.com&quot;
employee.InCharge = &quot;backend&quot;
&gt;
fmt.Println(employee)
// {Eon staff 200803 Development 15 50000000 vamos_eon@email.com backend}</code></pre>
<p>구조체 변수 <code>employee</code>에 대하여 속성을 모두 초기화하고 출력했습니다.
<strong>주의 : 초기화하지 않으면 해당 필드에 대해서는 zero-value로 초기화됩니다.</strong></p>
<blockquote>
<br>

</blockquote>
<h3 id="📍-방법2-모든-필드를-한-번에-초기화">📍 방법2. 모든 필드를 한 번에 초기화</h3>
<pre><code class="language-go">var employee Employee
employee = Employee{ 
    &quot;Kim&quot;, 
    &quot;Manager&quot;, 
    210109, 
    &quot;DEV&quot;, 
    20, 
    70000000, 
    &quot;kim@email.com&quot;, 
    &quot;back-end&quot;, 
}
&gt;
fmt.Println(employee)
// {Kim Manager 210109 DEV 20 70000000 kim@email.com back-end}</code></pre>
<p>필드 순서에 맞추어 값을 초기화할 수도 있습니다.
<strong>주의 : 중간에 초기화하지 않는 필드를 작성조차 하지 않으면 타입이 안 맞을 수 있어, 에러가 발생하기 때문에 zero-value로라도 꼭 초기화를 해야 합니다.</strong></p>
<blockquote>
<br>

</blockquote>
<h3 id="📍-방법3-선택한-필드만-한-번에-초기화">📍 방법3. 선택한 필드만 한 번에 초기화</h3>
<pre><code class="language-go">var employee Employee = Employee{Name: &quot;Eon&quot;, Email: &quot;vamos_eon@email.com&quot;}
fmt.Println(employee)
&gt;
employee = Employee{Name: &quot;Kim&quot;}
fmt.Println(employee)
// {Eon  0  0 0 vamos_eon@email.com }
// {Kim  0  0 0  }</code></pre>
<p>선택한 필드만 초기화할 수도 있습니다.
<strong>주의 : 이미 초기화된 필드가 있는 상태에서 위와 같은 방법으로 대입을 하면, 선택된 필드 외에는 zero-value가 대입됩니다.</strong></p>
<hr>
<br>

<h1 id="📝-구조체를-포함하는-구조체">📝 구조체를 포함하는 구조체</h1>
<p>구조체를 포함하는 구조체는 조금 더 상세한 표현을 가능하게 합니다.</p>
<p><img src="https://images.velog.io/images/vamos_eon/post/813cfefc-4d6c-4754-ad18-3ff7044aec7e/image.png" alt=""></p>
<p>처음에 그린 구조와 조금 달라졌습니다.
처음에는 직원 한 명에 대해서만 정보를 넣으면 됐으니 그 한 명을 특정하고 정보를 입력했다면, 이번에는 부서 전체에 대하여 그 구성원들의 정보를 넣을 겁니다.</p>
<p>저렇게 많은 것들을 넣게 되면 개념을 잡을 때 복잡할 수 있으니, 일단은 아래 그림과 같이 부서를 중심으로 직원1에 대해서만 구조체를 간단히 선언해보겠습니다.</p>
<p><img src="https://images.velog.io/images/vamos_eon/post/9a78c198-249b-403b-8e98-3a253e36e6fb/image.png" alt=""></p>
<br>

<h2 id="📌-구조체를-포함하는-구조체의-선언">📌 구조체를 포함하는 구조체의 선언</h2>
<blockquote>
</blockquote>
<pre><code class="language-go">type 구조체명1 struct {
    필드명 타입
    ...
    필드명 구조체명2
    ...
}
&gt;
type 구조체명2 struct {
    필드명 타입
    ...
    필드명 타입
    ...
}</code></pre>
<p>구조체 안에 타입을 구조체로 둠으로써, 구조체 안의 구조체를 선언했습니다.</p>
<p>위의 사용법을 바탕으로 부서에 대한 구조체를 작성해보겠습니다.</p>
<pre><code class="language-go">type Department struct {
    DepName        string
    Location    string
    Project        string
    Employee1    Employee
}

type Employee struct {
    Name        string
    Rank        string
    No        uint32
    Vacation    int8
    AnnualIncome    uint64
    Email        string
    InCharge    string
}</code></pre>
<p>구조체를 작성했습니다.</p>
<br>

<h2 id="📌-구조체를-포함하는-구조체-변수의-선언">📌 구조체를 포함하는 구조체 변수의 선언</h2>
<blockquote>
</blockquote>
<pre><code class="language-go">var 구조체변수명 구조체타입명</code></pre>
<p>이어서, 구조체 변수를 선언하겠습니다.</p>
<pre><code class="language-go">var department Department</code></pre>
<p>이제 구조체 변수 <code>department</code>는 구조체 <code>Department</code>가 가지는 필드를 모두 사용할 수 있습니다.</p>
<br>

<h2 id="📌-구조체를-포함하는-구조체-변수의-초기화">📌 구조체를 포함하는 구조체 변수의 초기화</h2>
<p>위에서 선언한 구조체 변수 <code>department</code>에 값을 초기화해서 사용해보겠습니다.
구조체 변수를 초기화하기 위한 방법은 위에서 소개한 구조체 변수의 초기화와 동일합니다.
다만, 구조체 내부의 구조체에 접근하려면 <strong>점(.)</strong> 을 두 번 사용해서 접근합니다.</p>
<blockquote>
</blockquote>
<h3 id="📍-방법-1-각-필드를-따로-초기화-1">📍 방법 1. 각 필드를 따로 초기화</h3>
<pre><code class="language-go">var department Department
&gt;
department.DepName = &quot;Development&quot;
department.Location = &quot;8F&quot;
department.Project = &quot;Hello World!!&quot;
&gt;
department.Employee1.Name = &quot;Eon&quot;
department.Employee1.Rank = &quot;staff&quot;
department.Employee1.No = 200803
department.Employee1.Vacation = 15
department.Employee1.AnnualIncome = 50000000
department.Employee1.Email = &quot;vamos_eon@email.com&quot;
department.Employee1.InCharge = &quot;backend&quot;
&gt;
fmt.Println(department)
// {Development 8F Hello World!! {Eon staff 200803 15 50000000 vamos_eon@email.com backend}}</code></pre>
<p>구조체 변수 <code>department</code> 대하여 속성을 모두 초기화하고 출력했습니다.</p>
<blockquote>
</blockquote>
<p><strong>주의 : 초기화하지 않으면 해당 필드에 대해서는 zero-value로 초기화됩니다.</strong></p>
<blockquote>
<br>

</blockquote>
<h3 id="📍-방법2-모든-필드를-한-번에-초기화-1">📍 방법2. 모든 필드를 한 번에 초기화</h3>
<pre><code class="language-go">var department Department = Department{
    &quot;Development&quot;,
    &quot;8F&quot;,
    &quot;Hello World!!&quot;,
    Employee{
        &quot;Eon&quot;,
        &quot;staff&quot;,
        200803,
        15,
        50000000,
        &quot;vamos_eon@email.com&quot;,
        &quot;backend&quot;,
    },
}
&gt;
fmt.Println(department)
// {Development 8F Hello World!! {Eon staff 200803 15 50000000 vamos_eon@email.com backend}}</code></pre>
<p>필드 순서에 맞추어 값을 초기화할 수도 있습니다.
구조체 안의 구조체답게 필드 값을 초기화할 때 구조체 형태로 초기화합니다.</p>
<blockquote>
</blockquote>
<p><strong>주의 : 중간에 초기화하지 않는 필드를 작성조차 하지 않으면 타입이 안 맞을 수 있어, 에러가 발생하기 때문에 zero-value로라도 꼭 초기화를 해야 합니다.</strong></p>
<blockquote>
<br>

</blockquote>
<h3 id="📍-방법3-선택한-필드만-한-번에-초기화-1">📍 방법3. 선택한 필드만 한 번에 초기화</h3>
<pre><code class="language-go">var department Department = Department{
    DepName: &quot;Development&quot;,
    Employee1: Employee{
        Name:  &quot;Eon&quot;,
        Email: &quot;vamos_eon@email.com&quot;,
    },
}
&gt;
fmt.Println(department)
// {Development   {Eon  0 0 0 vamos_eon@email.com }}</code></pre>
<blockquote>
<p>선택한 필드만 초기화할 수도 있습니다.
<br></p>
</blockquote>
<pre><code class="language-go">var department Department = Department{
    &quot;Development&quot;,
    &quot;8F&quot;,
    &quot;Hello World!!&quot;,
    Employee{
        Name:  &quot;Eon&quot;,
        Email: &quot;vamos_eon@email.com&quot;,
    },
}
&gt;
fmt.Println(department)
// {Development 8F Hello World!! {Eon  0 0 0 vamos_eon@email.com }}</code></pre>
<p>부분적으로 필드 선택 방식을 사용할 수도 있습니다.
<strong>주의 : 이미 초기화된 필드가 있는 상태에서 위와 같은 방법으로 대입을 하면, 선택된 필드 외에는 zero-value가 대입됩니다.</strong></p>
<hr>
<br>

<h1 id="📝-필드명을-사용하지-않고-구조체를-포함하는-구조체">📝 필드명을 사용하지 않고 구조체를 포함하는 구조체</h1>
<p>말이 굉장히 길고 복잡합니다. 하지만 다르게 표현할 수 있는 방법을 모르겠습니다.
굳이 표현한다면 <strong>&#39;내장 구조체&#39;</strong> 정도로 정리할 수 있겠습니다.</p>
<p><strong>embedded struct field</strong> 방식이라고 합니다.
<strong>embedded struct field</strong> 방식은 구조체 필드를 선언할 때, 필드명을 작성하지 않고 타입만 선언합니다.</p>
<br>

<h2 id="📌-내장-구조체의-선언">📌 내장 구조체의 선언</h2>
<blockquote>
</blockquote>
<pre><code class="language-go">type 구조체명1 struct {
    필드명 타입
    ...
    구조체명2
    ...
}
&gt;
type 구조체명2 struct {
    필드명 타입
    ...
}</code></pre>
<p>필드명 없이, 구조체 타입인 구조체명만 적어서 내장 구조체를 선언합니다.</p>
<p>위의 선언 방법을 바탕으로 구조체를 작성해보겠습니다.</p>
<pre><code class="language-go">type Department struct {
    DepName        string
    Location    string
    Project        string
    Employee
}

type Employee struct {
    Name        string
    Rank        string
    No        uint32
    Vacation    int8
    AnnualIncome    uint64
    Email        string
    InCharge    string
}</code></pre>
<p>내장 구조체 선언을 했습니다.
위 <code>Department</code>구조체 내의 <code>Employee</code> 구조체가 내장 구조체로 선언된 것을 볼 수 있습니다.</p>
<br>

<h2 id="📌-내장-구조체를-포함하는-구조체-변수의-초기화">📌 내장 구조체를 포함하는 구조체 변수의 초기화</h2>
<p>위에서 선언한 내장 구조체를 포함하는 구조체 변수 <code>department</code>에 값을 초기화해서 사용해보겠습니다.
내장 구조체 변수를 초기화하기 위한 방법은 지금까지와는 다릅니다.
초기화할 때 필드명을 적어주었던 게 전부 빠지고 구조체명으로 대체되기 때문입니다.
특이한 점으로는 내장 구조체 필드에 접근할 때, <strong>점(.)</strong> 을 하나만 사용해도 됩니다.</p>
<blockquote>
</blockquote>
<h3 id="📍-방법-1-각-필드를-따로-초기화-2">📍 방법 1. 각 필드를 따로 초기화</h3>
<pre><code class="language-go">var department Department
&gt;
department.DepName = &quot;Development&quot;
department.Location = &quot;8F&quot;
department.Project = &quot;Hello World!!&quot;
department.Name = &quot;Eon&quot;
department.Rank = &quot;staff&quot;
department.No = 200803
department.Vacation = 15
department.AnnualIncome = 50000000
department.Email = &quot;vamos_eon@email.com&quot;
department.InCharge = &quot;backend&quot;
&gt;
fmt.Println(department)
// {Development 8F Hello World!! {Eon staff 200803 15 50000000 vamos_eon@email.com backend}}</code></pre>
<p>구조체 변수 <code>department</code>에 대하여 속성을 모두 초기화하고 출력했습니다.
<strong>필드명이 없기 때문에 내장 구조체의 필드에 접근할 때는 구조체에서 바로 접근할 수 있습니다.</strong></p>
<blockquote>
</blockquote>
<p><strong>주의 : 초기화하지 않으면 해당 필드에 대해서는 zero-value로 초기화됩니다.</strong></p>
<blockquote>
<br>

</blockquote>
<h3 id="📍-방법2-모든-필드를-한-번에-초기화-2">📍 방법2. 모든 필드를 한 번에 초기화</h3>
<pre><code class="language-go">var department Department = Department{
    &quot;Development&quot;,
    &quot;8F&quot;,
    &quot;Hello World!!&quot;,
    Employee{
        &quot;Eon&quot;,
        &quot;staff&quot;,
        200803,
        15,
        50000000,
        &quot;vamos_eon@email.com&quot;,
        &quot;backend&quot;,
    },
}
&gt;
fmt.Println(department)
// {Development 8F Hello World!! {Eon staff 200803 15 50000000 vamos_eon@email.com backend}}</code></pre>
<p>이 방법은 위에 소개한 <strong>구조체를 포함하는 구조체 변수의 초기화 방법2</strong> 와 동일합니다.</p>
<blockquote>
</blockquote>
<p><strong>주의 : 중간에 초기화하지 않는 필드를 작성조차 하지 않으면 타입이 안 맞을 수 있어, 에러가 발생하기 때문에 zero-value로라도 꼭 초기화를 해야 합니다.</strong></p>
<blockquote>
<br>

</blockquote>
<h3 id="📍-방법3-선택한-필드만-한-번에-초기화-2">📍 방법3. 선택한 필드만 한 번에 초기화</h3>
<pre><code class="language-go">var department Department = Department{
    DepName: &quot;Development&quot;,
    Employee: Employee{
        Name:  &quot;Eon&quot;,
        Email: &quot;vamos_eon@email.com&quot;,
    },
}
&gt;
fmt.Println(department)
// {Development   {Eon  0 0 0 vamos_eon@email.com }}</code></pre>
<blockquote>
<p>선택한 필드만 초기화할 수도 있습니다.
<strong>단, 보시는 바와 같이 구조체명을, 필드명 대신 작성해야 합니다.</strong>
<br></p>
</blockquote>
<pre><code class="language-go">var department Department = Department{
    &quot;Development&quot;,
    &quot;8F&quot;,
    &quot;Hello World!!&quot;,
    Employee{
        Name:  &quot;Eon&quot;,
        Email: &quot;vamos_eon@email.com&quot;,
    },
}
&gt;
fmt.Println(department)
// {Development 8F Hello World!! {Eon  0 0 0 vamos_eon@email.com }}</code></pre>
<p>부분적으로 필드 선택 방식을 사용할 수도 있습니다.
<strong>주의 : 이미 초기화된 필드가 있는 상태에서 위와 같은 방법으로 대입을 하면, 선택된 필드 외에는 zero-value가 대입됩니다.</strong></p>
<br>

<h2 id="📌-같은-필드명을-가지고-있는-내장-구조체의-선언">📌 같은 필드명을 가지고 있는 내장 구조체의 선언</h2>
<blockquote>
</blockquote>
<pre><code class="language-go">type 구조체명1 struct {
    필드명1 타입
    ...
    구조체명2
    ...
}
&gt;
type 구조체명2 struct {
    필드명1 타입
    ...
}</code></pre>
<p><strong>위와 같이 서로 다른 구조체에 필드명이 중복되어 있고, 그 두 구조체가 내장 구조체의 관계를 가지고 있습니다.</strong>
<strong>이 때, 각 구조체의 필드명1에 값을 서로 다르게 부여하고 두 필드명1에 모두 접근할 수 있습니다.</strong></p>
<p>위의 선언 방법을 바탕으로 구조체를 작성해보겠습니다.</p>
<pre><code class="language-go">type Department struct {
    Name        string
    Location    string
    Project        string
    Employee
}

type Employee struct {
    Name        string
    Rank        string
    No        uint32
    Vacation    int8
    AnnualIncome    uint64
    Email        string
    InCharge    string
}</code></pre>
<p>두 구조체의 <code>Name</code>이라는 필드명이 중복되어 있는 것을 볼 수 있습니다.</p>
<br>

<h2 id="📌-같은-필드명을-가지고-있는-내장-구조체-변수의-초기화">📌 같은 필드명을 가지고 있는 내장 구조체 변수의 초기화</h2>
<p>같은 필드명을 가지고 있는 내장 구조체 변수의 초기화 방법은 또 조금 다릅니다.
초기화할 때 필드명을 적어주었던 게 전부 빠지고 구조체명으로 대체되기 때문입니다.
특이한 점으로는 내장 구조체 필드에 접근할 때, <strong>점(.)</strong> 을 하나만 사용해도 됩니다.</p>
<blockquote>
</blockquote>
<h3 id="📍-방법-1-각-필드를-따로-초기화-3">📍 방법 1. 각 필드를 따로 초기화</h3>
<pre><code class="language-go">var department Department
&gt;
department.Name = &quot;Development&quot;
department.Location = &quot;8F&quot;
department.Project = &quot;Hello World!!&quot;
department.Employee.Name = &quot;Eon&quot;
department.Rank = &quot;staff&quot;
department.No = 200803
department.Vacation = 15
department.AnnualIncome = 50000000
department.Email = &quot;vamos_eon@email.com&quot;
department.InCharge = &quot;backend&quot;
&gt;
fmt.Println(department)
// {Development 8F Hello World!! {Eon staff 200803 15 50000000 vamos_eon@email.com backend}}</code></pre>
<p>구조체 변수 <code>department</code>에 대하여 속성을 모두 초기화하고 출력했습니다.
<strong>필드명이 없기 때문에 내장 구조체의 필드에 접근할 때는 구조체에서 바로 접근할 수 있습니다.</strong></p>
<blockquote>
<blockquote>
</blockquote>
<p><strong>같은 필드명의 내장 구조체 내의 필드에 접근하려면 구조체명을 사용해서 접근할 수 있습니다.</strong>
<strong><code>department.Employee.Name = &quot;Eon&quot;</code></strong>
<strong>같은 필드명이 아니더라도 구조체명을 사용해서 접근할 수도 있습니다.</strong>
<strong>주의 : 초기화하지 않으면 해당 필드에 대해서는 zero-value로 초기화됩니다.</strong></p>
<br>

</blockquote>
<h3 id="📍-방법-2-모든-필드를-한-번에-초기화">📍 방법 2. 모든 필드를 한 번에 초기화</h3>
<blockquote>
</blockquote>
<p>위에서 소개한 방식과 같습니다.</p>
<h3 id="📍-방법3-선택한-필드만-한-번에-초기화-3">📍 방법3. 선택한 필드만 한 번에 초기화</h3>
<blockquote>
</blockquote>
<p>위에서 소개한 방식과 같습니다.</p>
<hr>
<br>

<h1 id="📝-구조체-배열">📝 구조체 배열</h1>
<p>구조체도 배열의 형태로 저장할 수 있습니다.
위에서 보여드린 구조체에 대한 그림 중, 아래의 그림을 예시로 사용하겠습니다.
<img src="https://images.velog.io/images/vamos_eon/post/0fc0f490-2473-4ffb-bef5-e33bf17e9d38/image.png" alt=""></p>
<h2 id="📌-구조체-배열의-선언">📌 구조체 배열의 선언</h2>
<blockquote>
</blockquote>
<pre><code class="language-go">type 구조체명1 struct {
    필드명 타입
    ...
    필드명 [배열크기]구조체명2
    ...
}
&gt;
type 구조체명2 struct {
    필드명 타입
    ...
}</code></pre>
<p>위와 같은 방법으로 구조체 배열을 포함하는 구조체를 선언합니다.</p>
<p>위의 선언 방법을 바탕으로 구조체를 작성해보겠습니다.</p>
<pre><code class="language-go">type Department struct {
    DepName        string
    Location    string
    Project        string
    EmployeeInfo    [1]Employee
}

type Employee struct {
    Name        string
    Rank        string
    No        uint32
    LeaveInfo    Leave
    AnnualIncome    uint64
    Email        string
    InCharge    [1]Tasks
}

type Leave struct {
    AnnualLeave        uint8
    BereavementLeave    uint8
    LongServiceLeave    uint8
     SickLeave        uint8
}

type Tasks struct {
    Name        string
    Duration    uint8
    Progress    uint8
}</code></pre>
<br>

<h2 id="📌-구조체-배열-변수의-초기화">📌 구조체 배열 변수의 초기화</h2>
<blockquote>
</blockquote>
<pre><code class="language-go">var OurComapny [1]Department = [1]Department{}
&gt;
OurComapny[0].DepName = &quot;Development&quot;
OurComapny[0].Location = &quot;8F&quot;
OurComapny[0].Project = &quot;Hello World!!&quot;
OurComapny[0].EmployeeInfo[0].Name = &quot;Eon&quot;
OurComapny[0].EmployeeInfo[0].Rank = &quot;staff&quot;
OurComapny[0].EmployeeInfo[0].Email = &quot;vamos_eon@email.com&quot;
OurComapny[0].EmployeeInfo[0].AnnualIncome = 50000000
OurComapny[0].EmployeeInfo[0].No = 200803
OurComapny[0].EmployeeInfo[0].LeaveInfo.AnnualLeave = 15
OurComapny[0].EmployeeInfo[0].LeaveInfo.BereavementLeave = 5
OurComapny[0].EmployeeInfo[0].LeaveInfo.LongServiceLeave = 0
OurComapny[0].EmployeeInfo[0].LeaveInfo.SickLeave = 30
OurComapny[0].EmployeeInfo[0].InCharge[0].Name = &quot;Golang installation&quot;
OurComapny[0].EmployeeInfo[0].InCharge[0].Duration = &quot;~2022-01-16&quot;
OurComapny[0].EmployeeInfo[0].InCharge[0].Progress = &quot;80%&quot;</code></pre>
<p>위와 같이 초기화해서 접근할 수 있습니다.</p>
<p><code>slice</code>로 선언하면 훨씬 유동적이고 쉽게 초기화 및 접근이 가능합니다.
<code>slice</code>에 대한 내용은 나중에 다루겠습니다.</p>
<hr>
<br>

<h1 id="📝-메모리-할당">📝 메모리 할당</h1>
<h2 id="📌-구조체의-메모리-할당">📌 구조체의 메모리 할당</h2>
<p>구조체의 메모리 할당에 가장 큰 특징은 <strong>메모리 패딩</strong>입니다.
빈 공간을 추가로 할당하는 것인데, 왜 그런지 알아보겠습니다.</p>
<h3 id="📍-레지스터에-대한-이해">📍 레지스터에 대한 이해</h3>
<p>운영체제에 대한 이야기를 조금이라도 들어봤다면 <strong>32비트 운영체제</strong>와 <strong>64비트 운영체제</strong>가 있다는 것은 알 수 있습니다.</p>
<p>요즘 나오는 운영체제는 대부분이 64비트 운영체제입니다.
더 많은 양을 처리할 수 있기 때문에 뭔가 특별한 제약이 있는 경우가 아니라면 32비트를 쓸 이유가 없습니다.</p>
<p>이 32비트와 64비트가 의미하는 것은 바로 <strong>&#39;한 번에 처리할 수 있는 데이터의 크기&#39;</strong> 라고 보시면 됩니다.
정확히는 주소의 길이입니다.
32비트 운영체제는 주소의 단위가 4바이트입니다. 따라서 한 번에 처리할 수 있는 데이터의 크기는 4바이트입니다.
<strong>64비트 운영체제는 주소의 단위가 8바이트입니다. 따라서 한 번에 처리할 수 있는 데이터의 크기는 8바이트입니다.</strong></p>
<p>우리가 코딩을 하고 컴파일한 후에 실행하면, 선언한 변수들에 대한 메모리가 할당이 됩니다.
그 변수가 사용될 때, 데이터가 <strong>RAM</strong>에 먼저 올라가게 되고, CPU가 연산을 하기에 앞서, <strong>Register</strong>에 해당 데이터를 가지고 온 다음에 <strong>CPU</strong>가 Register에 올라온 데이터를 가지고 연산을 합니다.</p>
<p>32비트 운영체제에서 8바이트짜리 데이터는 두 번 읽어서 사용합니다.
DRAM과 캐시를 이용하는데, 이것은 운영체제에 관련한 공부를 따로 요구합니다.
아무튼 32비트 운영체제에서도 8바이트짜리 데이터를 읽고 사용하는 데에는 문제가 없습니다.</p>
<p>대개 64비트 운영체제를 사용하기 때문에, 운영체제는 메모리 할당을 8바이트의 배수 단위로 하려고 할 것입니다.
이것이 <strong>&#39;메모리 정렬&#39;</strong> 입니다.</p>
<h3 id="📍-메모리-정렬과-메모리-패딩">📍 메모리 정렬과 메모리 패딩</h3>
<p>64비트 운영체제는 메모리를 8바이트 단위로 할당한다고 했습니다.
한 번에 처리할 수 있는 데이터의 최대 크기가 8바이트이기 때문입니다.
<strong><code>bool</code> 변수를 하나 선언하면 1바이트입니다.
<code>int16</code> 변수를 하나 선언하면 2바이트입니다.
이 두 변수를 선언하면 총 3바이트입니다.</strong>
<img src="https://images.velog.io/images/vamos_eon/post/ecaa75d9-8396-49c5-a282-76d2bdd2c7e4/image.png" alt=""></p>
<p>그런데, 위의 두 변수를 하나의 구조체에서 사용하면 <strong>메모리 정렬</strong>이 일어납니다.
변수를 따로 선언하는 것은, 메모리의 어디에든 따로 할당되어 있어도 상관없습니다.
하지만 구조체는 구조체 자체가 하나의 변수처럼 되어 있는 구조이므로 연속된 메모리에 할당됩니다.
구조체 안에 <code>bool</code>과 <code>int16</code>을 선언하겠습니다.</p>
<pre><code class="language-go">type structure1 struct {
    vBool    bool
    vInt16    int16
}

...
fmt.Println(unsafe.Sizeof(structure1{})
// 4</code></pre>
<p><code>structure1</code>의 크기는 4바이트입니다.
메모리 정렬로 인해 생긴 <strong>메모리 패딩</strong> 때문입니다.
<img src="https://images.velog.io/images/vamos_eon/post/278930fd-3fd2-44e7-af8d-365ce83183f5/image.png" alt=""></p>
<p>메모리 정렬은 CPU가 메모리에서 데이터를 가지고 올 때 효율적으로 동작하기 위해 수행합니다.
메모리 정렬은 구조체에 선언된 변수의 순서대로 진행되며, 구조체 내의 변수 중 가장 큰 사이즈가 기준이 됩니다.
위의 경우, <code>vInt16</code>이 <code>structure1</code> 내의 가장 사이즈가 큰 변수이므로 <code>int16</code>의 사이즈인 2바이트가 데이터를 불러올 때의 단위가 됩니다.
그래서 <code>bool</code> 타입의 <code>vBool</code>을 메모리에 할당할 때, 2바이트로 할당하게 되는 것입니다.
그렇게 <strong><code>bool</code> 타입의 실제 사이즈인 1바이트가 아닌 해당 변수에 2바이트가 할당되어 사용하지 않는 1바이트가 생겨나게 됩니다.</strong>
이것이 <strong>메모리 패딩</strong>입니다.</p>
<p>예시를 한 가지 더 보겠습니다.
아래와 같이 구조체를 선언해보겠습니다.</p>
<pre><code class="language-go">type structure2 struct {
    v1Int32    int32
    vInt16    int16
    v2Int32    int32
    vBool    bool
}</code></pre>
<p><img src="https://images.velog.io/images/vamos_eon/post/b5b78639-3927-4973-9816-45e7564df4f1/image.png" alt="">
보시는 바와 같이 구조체 내의 가장 큰 변수 사이즈인 <code>int32</code>에 맞추어, 4바이트를 기준으로 메모리 할당이 된 것을 볼 수 있습니다.
4바이트 간격으로 <code>int16</code>은 2바이트를 차지하고, 전체 4바이트 공간 중 나머지 2바이트는 사용하지 않는 메모리 패딩으로 남습니다.</p>
<hr>
<h3 id="📍-구조체를-만드는-가장-좋은-방법은">📍 구조체를 만드는 가장 좋은 방법은?</h3>
<p>구조체를 만들 때 <strong>필드의 사이즈를 내림차순</strong>으로 만드는 것이 가장 좋습니다.
자료형들의 사이즈를 보면 모두 2의 거듭제곱으로 표현된다는 것을 알 수 있습니다.
각각 1, 2, 4, 8 바이트입니다.
<strong>내림차순으로 만들게 되면 메모리 패딩을 줄이고 구조체의 크기를 줄일 수 있습니다.</strong></p>
<pre><code class="language-go">type structure2 struct {
    v1Int32    int32
    v2Int32    int32
    vInt16    int16
    vBool    bool
}</code></pre>
<p><img src="https://images.velog.io/images/vamos_eon/post/c1264f99-8858-421d-810f-daebd5d76fad/image.png" alt=""></p>
<br>
<br>

<h2 id="📌-구조체-사이즈-확인">📌 구조체 사이즈 확인</h2>
<p>위에서 잔뜩 메모리 정렬과 메모리 패딩에 대해서 이야기했는데 정작 메모리가 실제로 어떻게 잡히는지 사이즈 측정을 하지 않았습니다.
여기서는 사이즈를 구해보도록 하겠습니다.</p>
<p><strong><a href="https://pkg.go.dev/unsafe">unsafe 패키지</a></strong> 를 이용하면 됩니다.
unsafe 패키지는 안전하지 않은 유형들을 포함하는 패키지입니다.
사용할 때는 조심해야 하며 Go 1 호환성 가이드라인으로 보호받지 않습니다.</p>
<h3 id="📍-sizeof--func-sizeofx-arbitrarytype-uintptr">📍 Sizeof() : func Sizeof(x ArbitraryType) uintptr</h3>
<p><code>ArbitraryType</code>은 실제 <code>unsafe</code>패키지에 포함된 타입이 아닙니다.
문서화를 위한 타입일 뿐입니다.</p>
<p><code>Sizeof()</code> 함수는 그 타입의 크기를 바이트 단위로 반환합니다.
단, 참조되는 메모리의 크기를 그대로 반환하는 것은 아닙니다. 
<code>slice</code>와 같은 경우, 메모리의 크기가 아니라 <code>slice</code> 설명자의 크기를 반환한다고 되어 있습니다.</p>
<h3 id="📍-unsafe--변수와-구조체-사이즈-구하기">📍 unsafe : 변수와 구조체 사이즈 구하기</h3>
<p>여기서, 변수의 크기와 구조체의 크기를 구해보겠습니다.</p>
<blockquote>
</blockquote>
<pre><code class="language-go">package main
&gt;
import (
    &quot;fmt&quot;
    &quot;unsafe&quot;
)
&gt;
func main() {
    fmt.Printf(&quot;Size of %T : %d\n&quot;, int64(0), unsafe.Sizeof(int64(0)))
    fmt.Printf(&quot;Size of %T : %d\n&quot;, int32(0), unsafe.Sizeof(int32(0)))
    fmt.Printf(&quot;Size of %T : %d\n&quot;, int16(0), unsafe.Sizeof(int16(0)))
    fmt.Printf(&quot;Size of %T : %d\n&quot;, int8(0), unsafe.Sizeof(int8(0)))
    fmt.Printf(&quot;Size of %T : %d\n&quot;, bool(false), unsafe.Sizeof(bool(false)))
}
/* The result of the ouput
Size of int64 : 8
Size of int32 : 4
Size of int16 : 2
Size of int8 : 1
Size of bool : 1
*/</code></pre>
<p>golang의 자료형 사이즈대로 잘 나오는 것을 확인할 수 있습니다.</p>
<br>

<p>구조체의 크기도 구해보겠습니다.</p>
<blockquote>
</blockquote>
<pre><code class="language-go">package main
&gt;
import (
    &quot;fmt&quot;
    &quot;unsafe&quot;
)
&gt;
type structure1 struct {
    vBool  bool
    vInt16 int16
}
&gt;
func main() {
    var s structure1
    fmt.Printf(&quot;Size of %T struct: %d bytes\n&quot;, s, unsafe.Sizeof(s))
}
// Size of main.structure1 struct: 4 bytes</code></pre>
<p>위에서 설명드린 바와 같이, <code>bool</code> 타입에 1바이트의 메모리 패딩이 붙어, 총 4바이트가 된 것을 확인할 수 있습니다.</p>
<br>

<h2 id="📌-string의-메모리-할당">📌 string의 메모리 할당</h2>
<p><code>string</code>의 크기를 구해보겠습니다.</p>
<h3 id="📍-string의-사이즈-구하기">📍 string의 사이즈 구하기</h3>
<p><code>string</code>을 <code>Sizeof()</code> 함수로 크기를 구해보면 <code>string</code> 변수의 값이 말도 안 되게 길어도 항상 16바이트를 반환합니다.</p>
<pre><code class="language-go">fmt.Printf(&quot;Size of %T : %d\n&quot;, string(&quot;abcdefghijklmnopqrstuvwxyz&quot;), unsafe.Sizeof(string(&quot;abcdefghijklmnopqrstuvwxyz&quot;)))
// Size of string : 16</code></pre>
<p><code>unsafe.Sizeof(string(&quot;&quot;))</code>은 <code>string</code>의 설명자의 크기를 반환하기 때문입니다.
<code>string</code>은 <code>StringHeader</code>, <strong>바이트 데이터</strong>로 구성되어 있습니다.</p>
<pre><code class="language-go">type StringHeader struct {
    Data uintptr
    Len  int
}</code></pre>
<p><code>StringHeader</code>는 위와 같습니다.
<code>Data</code>는 실제 데이터가 있는 위치, <code>Len</code>은 문자열 길이를 저장합니다.
따라서, <strong><code>StringHeader</code>의 크기는 <code>uintptr(8bytes) + int(8bytes)</code>로, 16바이트가 됩니다.</strong></p>
<p><code>string</code> 타입의 데이터가 실제로 메모리에 할당되는 크기는 아래와 같습니다.</p>
<blockquote>
</blockquote>
<pre><code class="language-go">var str string = &quot;Hello&quot;
stringSize := len(str) + int(unsafe.Sizeof(str))
fmt.Println(stringSize)
// 21</code></pre>
<p><strong>string 타입의 실제 메모리 = 문자열 길이 + StringHeader의 크기</strong></p>
<h2 id="📌-string을-포함한-구조체의-크기">📌 string을 포함한 구조체의 크기</h2>
<p><code>unsafe.Sizeof(string(&quot;&quot;))</code>은 <code>StringHeader</code>의 크기를 반환한다고 했습니다.
구조체에서는 필드 중 가장 사이즈가 큰 값을 기준으로 메모리를 할당한다고 했습니다.
<code>string</code>이 끼어 있는 경우, 메모리 할당이 어떻게 이루어지는지 보겠습니다.</p>
<pre><code class="language-go">type strInclude struct {
    vStr    string
    vInt64    int64
}
...
fmt.Println(unsafe.Sizeof(strInclude{}))
// 24</code></pre>
<p><img src="https://images.velog.io/images/vamos_eon/post/4e4ddeff-65ee-4786-8674-36426afae266/image.png" alt=""></p>
<p>이렇게 할당됩니다.
이유는 간단합니다.
<code>unsafe.Sizeof(strInclude{})</code>는 <code>StringHeader</code>의 사이즈를 반환하고, <code>StringHeader</code>는 <code>uintptr, int</code>로 이루어져 있으며 이 두 자료형 모두 8바이트입니다.</p>
<p>따라서 아래와 같이 8바이트 단위로 메모리 정렬 및 할당이 이루어집니다.</p>
<p><img src="https://images.velog.io/images/vamos_eon/post/1d6b1ba4-dbf4-4a32-a89b-563febd59637/image.png" alt=""></p>
<p>당연하게도, <code>strInclude</code> 구조체 내에 <code>int64</code> 자료형의 필드가 <code>int32</code>가 되더라도 <code>strInclude</code>의 크기는 24로 나오게 됩니다.</p>
<hr>
<p>이번 포스팅은 구조체에 대한 내용이었습니다.
감사합니다.👍</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Golang 기초 (8) : 배열에 대하여]]></title>
            <link>https://velog.io/@vamos_eon/go-array</link>
            <guid>https://velog.io/@vamos_eon/go-array</guid>
            <pubDate>Sat, 08 Jan 2022 16:41:29 GMT</pubDate>
            <description><![CDATA[<p>안녕하세요, 주니어 개발자 Eon입니다.</p>
<p>이번 포스트는 배열에 관한 내용입니다.</p>
<h1 id="📝-배열이란">📝 배열이란?</h1>
<p>배열은 같은 타입을 가지는 변수들의 묶음입니다.
또, 선언된 변수들은 각각 연속된 메모리 공간에 할당됩니다.</p>
<p>배열은 수학의 행렬과 비슷하다고 볼 수 있습니다.
행과 열의 크기를 정하고, 그 안에 값을 저장합니다.</p>
<p><strong>수학에서의 행렬</strong></p>
<blockquote>
</blockquote>
<p><strong>1 × 2 행렬</strong>
<img src="https://images.velog.io/images/vamos_eon/post/3602111c-4f07-43c2-9f23-6a1cb54829da/image.png" alt=""></p>
<blockquote>
</blockquote>
<p><strong>2 × 2 행렬</strong>
<img src="https://images.velog.io/images/vamos_eon/post/9c334c5a-a33f-4b64-afd6-df03238e6d5b/image.png" alt=""></p>
<hr>
<p><strong>프로그래밍에서의 배열</strong></p>
<blockquote>
</blockquote>
<p><strong>1 × 2 의 형태는 1차원 배열</strong></p>
<pre><code class="language-go">var a [2]int = [2]int{1, 2}</code></pre>
<blockquote>
</blockquote>
<p><strong>2 × 2 의 형태는 2차원 배열</strong></p>
<pre><code class="language-go">var a [2][2]int = [2][2]int{{1, 2}, {3, 4}}</code></pre>
<hr>
<br>

<h1 id="📝-배열의-구성">📝 배열의 구성</h1>
<p>배열은 <code>인덱스</code>와 <code>요소</code>로 구성돼 있습니다.
<strong><code>인덱스</code></strong> : 배열에 들어가 있는 각 요소들의 순서이며, 0부터 시작함
<strong><code>요소</code></strong> : 각 인덱스가 가지는 값</p>
<p><img src="https://images.velog.io/images/vamos_eon/post/1bc6acf3-eafe-43a4-b14d-ea28dd9027ae/image.png" alt=""></p>
<blockquote>
</blockquote>
<p>배열 <code>a</code>는 <code>int</code>타입의 <code>요소</code> <code>4개</code>로 이루어졌습니다.
각 <code>요소</code>를 <code>인덱스</code> 순서에 맞게 <code>{1, 2, 3, 4}</code>로 초기화합니다.
각 <code>인덱스</code>의 <code>요소</code>는 <code>a[인덱스] = 요소</code>로 나타낼 수 있습니다.</p>
<hr>
<br>

<h1 id="📝-선언">📝 선언</h1>
<h2 id="📌-지금까지의-변수-선언">📌 지금까지의 변수 선언</h2>
<pre><code class="language-go">var a int</code></pre>
<p>위와 같이 <code>int</code> 타입의 변수 <code>a</code>를 선언했습니다.
여러 개의 변수를 선언하기 위해서는 아래와 같이 수행했습니다.</p>
<pre><code class="language-go">var a, b int</code></pre>
<h2 id="📌-배열의-선언">📌 배열의 선언</h2>
<pre><code class="language-go">var a [5]int</code></pre>
<p>위에서 <code>int</code> 타입의 변수 <code>5개</code>를 <code>a</code>라는 배열에 선언했습니다.
배열 <code>a</code>는 <code>5개</code>의 <code>int</code> 타입의 <code>요소</code>를 가집니다.</p>
<blockquote>
</blockquote>
<p>배열은 초기에 지정한 크기를 변경할 수 없습니다.
<strong>위의 예시에서 <code>인덱스</code>가 <code>5개</code>인 배열을 선언했는데, 이 배열의 크기는 4로 줄어들 수도, 6으로 늘어날 수도 없습니다.</strong>
때문에, 변수를 배열의 크기를 지정할 때 사용할 수 없고 <strong>상수</strong>는 사용이 가능합니다.</p>
<hr>
<br>

<h2 id="📌-여러-종류의-배열-선언-방법">📌 여러 종류의 배열 선언 방법</h2>
<p>배열을 선언하는 방법은 위에서 소개한 방법 외에도 몇 가지 더 있습니다.
위에서 소개한 방법을 포함해서 작성하겠습니다.</p>
<h3 id="📍-var-a-5int--초기화-없이-선언--값을-지정하는방법">📍 var a [5]int // 초기화 없이 선언 &amp; 값을 지정하는방법</h3>
<h4 id="각-요소에-개별적으로-값을-지정하는-방법">각 요소에 개별적으로 값을 지정하는 방법</h4>
<pre><code class="language-go">var a [5]int
a[0] = 1
a[1] = 2 
a[2] = 3 
a[3] = 4 
a[4] = 5 </code></pre>
<p>위에서 소개한 방법입니다.
먼저 배열을 선언함과 동시에 각 <code>요소</code>에 개별적으로 접근하여 값을 지정했습니다.</p>
<blockquote>
</blockquote>
<p>값을 지정하지 않으면, 초기화를 하지 않았기에 <code>a</code> 안의 모든 <code>요소</code>의 값은 <code>int</code>타입의 zero-value인 <code>0</code>입니다.</p>
<br>

<h3 id="📍-var-a-5int--5int1-2-3-4-5--선언--초기화하는-방법">📍 var a [5]int = [5]int{1, 2, 3, 4, 5} // 선언 &amp; 초기화하는 방법</h3>
<h4 id="초기화와-동시에-배열에-통째로-값을-지정하는-방법">초기화와 동시에 배열에 통째로 값을 지정하는 방법</h4>
<pre><code class="language-go">var a [5]int = [5]int{1, 2, 3, 4, 5}</code></pre>
<blockquote>
</blockquote>
<p><strong>참고 : golang go-static에서는 위와 같이 선언과 초기화를 같은 줄에 작성하는 걸 권장합니다.</strong></p>
<pre><code class="language-go">var a [5]int = [5]int{1, 2, 3, 4}</code></pre>
<p><code>인덱스</code>가 <code>5개</code>인 배열에 값을 <code>4개</code>만 지정하는 경우, 초기화하지 않은 <code>인덱스</code>의 값은 zero-value가 됩니다.</p>
<pre><code class="language-go">fmt.Println(a[4])
// 0</code></pre>
<br>

<h3 id="📍-b--3booltrue-false-true--짧은-선언--초기화하는-방법">📍 b := [3]bool{true, false, true} // 짧은 선언 &amp; 초기화하는 방법</h3>
<pre><code class="language-go">b := [3]bool{true, false, true}</code></pre>
<p>위와 같이 짧은 선언과 초기화를 동시에 할 수 있습니다.</p>
<br>

<h3 id="📍-var-c--5float641-11-3-33--인덱스를-지정하여-값을-초기화하는-방법">📍 var c = [5]float64{1: 1.1, 3: 3.3} // 인덱스를 지정하여 값을 초기화하는 방법</h3>
<h4 id="특정-인덱스에만-값을-초기화하는-방법">특정 인덱스에만 값을 초기화하는 방법</h4>
<pre><code class="language-go">var c = [5]float64{1: 1.1, 3: 3.3}</code></pre>
<blockquote>
</blockquote>
<pre><code class="language-go">var 배열이름 = [사이즈]타입{인덱스 : 요소, 인덱스 : 요소, ...}</code></pre>
<pre><code class="language-go">fmt.Println(c)
// [0 1.1 0 3.3 0]</code></pre>
<br>

<h3 id="📍-d--stringone-two-three--요소의-개수로-크기를-지정하는-방법">📍 d := [...]string{&quot;one&quot;, &quot;two&quot;, &quot;three&quot;} // 요소의 개수로 크기를 지정하는 방법</h3>
<h4 id="배열의-크기를-먼저-지정하지-않아도-되는-방법">배열의 크기를 먼저 지정하지 않아도 되는 방법</h4>
<pre><code class="language-go">d := [...]string{&quot;one&quot;, &quot;two&quot;, &quot;three&quot;}
fmt.Println(len(d))
// 3</code></pre>
<blockquote>
</blockquote>
<p><strong>len()</strong> 은 길이(length)를 반환하는 기본 내장함수입니다.
위에서 len()은 배열 d의 길이를 반환합니다.</p>
<br>

<h3 id="📍-var-mul-23int--23int1-2-3-4-5-6--다차원-배열-선언하는-방법">📍 var mul [2][3]int = [2][3]int{{1, 2, 3}, {4, 5, 6}} // 다차원 배열 선언하는 방법</h3>
<h4 id="1-한-줄에-선언-및-초기화">1. 한 줄에 선언 및 초기화</h4>
<pre><code class="language-go">var mul [2][3]int = [2][3]int{{1, 2, 3}, {4, 5, 6}}</code></pre>
<h4 id="2-여러-줄로-선언-및-초기화">2. 여러 줄로 선언 및 초기화</h4>
<pre><code class="language-go">var mul [2][3]int = [2][3]int{
    {1, 2, 3}, 
    {4, 5, 6},
}</code></pre>
<blockquote>
</blockquote>
<p>이렇게 여러 줄로 초기화할 수 있습니다.
<strong>단, 새로 개행을 하고 괄호를 닫을 경우, 마지막 원소의 끝에 콤마(,)를 붙여야 합니다.</strong></p>
<hr>
<br>

<h2 id="📌-다차원-배열은-언제-사용할까">📌 다차원 배열은 언제 사용할까?</h2>
<p>가장 쉬운 예시로, 차원을 표현할 때 사용할 수 있습니다.</p>
<h3 id="📍-이미지-표현">📍 이미지 표현</h3>
<p>이미지는 2차원입니다.
x, y축으로 이미지를 표현할 수 있습니다.
각 픽셀의 위치를 좌표로 하고, 그 위치에 들어가는 값으로 색을 정할 수 있습니다.</p>
<h3 id="📍-3d-물체-표현">📍 3D 물체 표현</h3>
<p>3D 물체는 3차원입니다.
x, y, z축으로 표현할 수 있습니다.
이미지와 마찬가지로 모든 좌표에 대하여 값을 넣음으로써 물체를 표현할 수 있습니다.</p>
<hr>
<br>

<h2 id="📌-배열의-순회">📌 배열의 순회</h2>
<p>배열의 각 요소에 순차적으로 접근할 수가 있습니다.
앞서 반복문에 대한 포스트에서도 짧게 소개했습니다.</p>
<p>먼저 아래 배열 순회 예시에서 사용되는 배열 하나를 아래와 같이 선언하겠습니다.</p>
<pre><code class="language-go">var a [5]int = [5]int{1, 2, 3, 4, 5}</code></pre>
<h3 id="📍-for-init-condition-post---c-like-for">📍 for init; condition; post {...} : C-Like for</h3>
<pre><code class="language-go">a[2] = 10
for i := 0; i &lt; len(a); i++ {
    fmt.Printf(&quot;%d &quot;, a[i])
}
// 1 2 10 4 5 </code></pre>
<blockquote>
</blockquote>
<p><strong><code>배열[인덱스]</code></strong> 로 원하는 인덱스 요소를 사용할 수 있습니다.
<strong>위와 같이 특정 인덱스의 값을 바꿀 수도 있습니다.</strong></p>
<br>

<h3 id="📍-for-condition---c-like-while">📍 for condition {...} : C-Like while</h3>
<pre><code class="language-go">i := 0
for i &lt; len(a) {
    fmt.Printf(&quot;%d &quot;, a[i])
}
// 1 2 3 4 5 </code></pre>
<br>

<h3 id="📍-for---c-like-for">📍 for {...} : C-Like for(;;)</h3>
<pre><code class="language-go">i := 0
for {
    if i &gt;= len(a) {
        break
    }
    fmt.Printf(&quot;%d &quot;, a[i])
    i++
}
// 1 2 3 4 5 </code></pre>
<br>

<h3 id="📍-for---range--for-loop-for-array-slice-string-or-map-or-reading-from-a-channel">📍 for - range : &#39;for loop&#39; for array, slice, string, or map, or reading from a channel</h3>
<pre><code class="language-go">for i, v := range a {
    fmt.Println(&quot;index : %d &amp; value : %d&quot;, i, v)
}
/* The result of output
index : 0 &amp; value : 0
index : 1 &amp; value : 0
index : 2 &amp; value : 0
index : 3 &amp; value : 0
index : 4 &amp; value : 0
*/</code></pre>
<blockquote>
</blockquote>
<p><strong><code>for i, v := range a {...}</code></strong> : <code>i (index), v (value)</code>가 들어갑니다.
이 루프 안에서는 <code>a[i]</code>를 사용하고 <code>v</code>를 사용하지 않아도 됩니다.
golang에서는 선언하고 사용하지 않는 변수가 있으면 컴파일 에러를 일으키기 때문에, <code>a[i]</code>로 사용하려면 아래와 같이 <code>v</code>를 선언하지 않으면 됩니다.
<strong><code>for  i := range a {...}</code></strong></p>
<hr>
<br>

<h1 id="📝-메모리-할당">📝 메모리 할당</h1>
<h2 id="📌-배열의-메모리-할당">📌 배열의 메모리 할당</h2>
<p>배열을 선언하면 다른 변수와 마찬가지로 메모리에 공간이 할당됩니다.
아래 그림은 배열이 아닌 변수와 배열의 메모리 할당을 나타냅니다.
<img src="https://images.velog.io/images/vamos_eon/post/acdb21e4-a520-4a67-8502-423ac463f09f/image.png" alt=""></p>
<p>변수는 할당되면 그 변수 타입의 크기만큼 메모리 공간이 할당됩니다.
배열도 마찬가지입니다.
<strong>다만 배열은 맨 처음 인덱스를 기준으로 하여, 이어지는 인덱스는 전부 연속적인 메모리 공간에 할당됩니다.</strong></p>
<p><img src="https://images.velog.io/images/vamos_eon/post/af274e33-29ff-4071-a756-be7121d79f7a/image.png" alt="">
위의 그림은 <code>int32</code>타입의 배열이므로, 각 인덱스의 <strong><em>주소값</em></strong>은 <code>int32</code>의 크기만큼의 차이를 둡니다.
0번 인덱스의 <strong><em>주소값</em></strong>이 <code>100</code>이라고 할 때, 1번 인덱스의 주소값은 <code>100 + sizeOf(int32)</code>가 됩니다.
(golang에서 sizeOf는 unsafe 패키지 내에 Sizeof() 함수로 사용할 수 있습니다. 다른 포스트에서 설명하겠습니다.)
32bit = 4byte 이므로, 각 인덱스의 주소값은 4바이트 단위로 증가합니다.
<strong><em>주소값</em></strong> : 메모리 공간의 주소 (할당된 공간의 위치를 나타냄)</p>
<br>
<br>

<h2 id="📌-다차원-배열의-메모리-할당">📌 다차원 배열의 메모리 할당</h2>
<p>golang에서의 다차원 배열은, 메모리 영역 할당이 연속적입니다.</p>
<p><img src="https://images.velog.io/images/vamos_eon/post/cbfbcccc-2b34-46d5-8638-92912e29baae/image.png" alt=""></p>
<p>위와 같이 서로 떨어져 있는 메모리 영역에 공간을 할당받는 것이 아니라, 
아래와 같이 연속된 메모리 영역에 공간을 할당받습니다.</p>
<p><img src="https://images.velog.io/images/vamos_eon/post/b78a2415-1545-4f24-a700-4165856998ac/image.png" alt=""></p>
<hr>
<br>

<h1 id="📝-배열-복사">📝 배열 복사</h1>
<p>값의 복사는 쉽게 할 수 있습니다.</p>
<br>

<h4 id="변수의-복사">변수의 복사</h4>
<pre><code class="language-go">var a, b int = 2, 3
a = b
fmt.Println(a)
// 3</code></pre>
<p>위와 같이 변수에 값을 복사하는 것은 &#39;대입&#39;이라는 개념을 통해서 이해할 수 있습니다.
단, golang은 최강타입 언어라고 했습니다.
타입이나 크기가 다르면 값의 복사를 불허합니다.</p>
<pre><code class="language-go">var a int = 2
var b int64 = 3
a = b</code></pre>
<p>위 과정은 에러가 발생합니다. 64비트 운영체제에서 사용해도 말입니다.
int는 64비트 운영체제에서 int64와 사이즈가 동일합니다.
하지만 타입명이 다릅니다. 그래서 에러가 발생합니다.</p>
<br>

<h4 id="배열의-복사">배열의 복사</h4>
<pre><code class="language-go">var a [5]int = [5]int{1, 2, 3, 4, 5}
var b [5]int = [5]int{6, 7, 8, 9, 10}
a = b
fmt.Println(a)
// [6 7 8 9 10]</code></pre>
<p>위의 경우엔 정상적으로 복사된 배열을 출력합니다.
배열의 타입과 크기가 모두 동일해서 가능했습니다.
여기서 타입이나 크기를 서로 다르게 지정을 한다면 복사가 불가능합니다.</p>
<hr>
<p>이번 포스트는 배열에 대한 내용이었습니다.
감사합니다.👍</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[2021 주니어 개발자 회고록]]></title>
            <link>https://velog.io/@vamos_eon/2021-%EC%A3%BC%EB%8B%88%EC%96%B4-%EA%B0%9C%EB%B0%9C%EC%9E%90-%ED%9A%8C%EA%B3%A0%EB%A1%9D</link>
            <guid>https://velog.io/@vamos_eon/2021-%EC%A3%BC%EB%8B%88%EC%96%B4-%EA%B0%9C%EB%B0%9C%EC%9E%90-%ED%9A%8C%EA%B3%A0%EB%A1%9D</guid>
            <pubDate>Thu, 30 Dec 2021 15:51:39 GMT</pubDate>
            <description><![CDATA[<p>안녕하세요, 주니어 개발자 Eon입니다.</p>
<p>(어투는 제 회고이니 양해 바랍니다.)
2021년을 마무리하며 올 한 해동안 느낀 점들을 작성하고 회고하는 시간을 가져보겠다.</p>
<p>뭐 대단한 사람도 아니고 기껏해야 주니어 개발자이지만 성장 일기 느낌으로 기록을 남기고 싶어서 작성한다.</p>
<hr>
<h1 id="개발자로서의-시작">개발자로서의 시작</h1>
<p>때는 바야흐로 작년 8월.. (시간의 흐름대로 작성, 2021년의 시점이 언제인지는 모른다..!!)
개발자로서의 첫 발을 내딛었을 때, 처음 맡게 된 업무는 이미 개발된 웹페이지에 대한 테스트였다.
이미 개발되었고, 서비스 중인 웹페이지를 테스트하고 디버깅하는 업무는 시작부터 고민이었다.</p>
<p>&quot;어디서부터 시작해야 하지?&quot;</p>
<p>하지만 고민의 시간은 짧았다.
사용자의 입장에서 이것 저것 눌러보고 메뉴얼의 일반적인 순서대로 진행하는 것이 아닌 억지로 순서를 바꿔가며 테스트하기 시작했다.</p>
<p>결과는 역시 &#39;여러 가지 문제점을 발견했다.&#39; 였다.</p>
<p>그러나 대뜸 시작한 마구잡이식 테스트였기에 내가 뭘 눌러서 이렇게 됐는지 재현하는 게 쉽지 않았다.
문제점을 인지하고 곧바로 테스트를 진행하며 동시에, 내가 뭘 눌렀는지 하나씩 정리하기 시작했다.</p>
<p>그렇게 테스트 / 디버깅 업무에서부터 하나 배우고 시작하게 됐다.</p>
<hr>
<h1 id="개발의-시작-그런데-go를-쓴다고">개발의 시작, 그런데 Go를 쓴다고?</h1>
<p>인사이동으로 팀이 배정이 되고, 본격적으로 개발 업무를 시작하게 됐다.
그리고 곧바로 업무분장을 통해 직무를 부여 받았고, 백엔드를 담당하게 됐다.</p>
<h2 id="hello-golang">Hello, Golang</h2>
<p>Golang을 사용하게 됐다.
처음 접근은 처참하리만큼 실패였다고 말할 수 있다.</p>
<p>대개 &#39;신입 사원에게는 기대 안 한다.&#39; 라는 말들을 하지만, 성격상 월급 꼬박꼬박 받으며 일도 잘 못하는 그런 기간은 빨리 줄이고 싶었다.</p>
<p>그래서 코드를 분석하는 기간이 짧았고, 팀장님께서 요구하신 REST-API 개발을 시작했다.</p>
<p>Go를 써본 적도 없는데 말이다.</p>
<p>처음 보는 문법에 헷갈리는 게 한두 가지가 아니었고 새로운 언어를 배울 때, 누가 가르쳐주지 않고 홀로 배우는 것은 처음이었기에 더 어려웠다.</p>
<p>홀로 공부하는 것은 아직까지도 많은 노력이 필요하다.</p>
<h3 id="뭐야-이거-순서가-왜-이래-콜론은-왜-붙어">뭐야? 이거 순서가 왜 이래? 콜론은 왜 붙어..?</h3>
<pre><code class="language-go">var value int
var value int = 0
value := 0</code></pre>
<p>Go를 처음 접할 때, 헷갈린 것들 중 하나가 바로 변수 선언, 초기화다.
뭐, 그래. 좋아! 여기까진 적응하는 데에 어려움은 없었다.
그런데 문제는 이게 왜 이렇게 쓰이는지 찾아보질 않고 계속 코드만 들여다 보고 있었던 거다.</p>
<p>Go에 대한 어떤 기본적인 강의 영상이나 문서나 블로그 글을 찾아보지도 않고 그저 보고 있던 게 내가 평가하는 나의 가장 큰 실수였다.</p>
<p>개발자의 덕목 중 하나가 바로 <strong>검색 능력</strong>이다.
학교 다닐 때 프로젝트할 때만 해도 잘만 검색해서 개발하곤 했는데, 막상 현업에 발을 내딛고 심지어 새로운 언어를 접하게 되니 머릿속이 백지가 돼 버린...</p>
<p>코드를 쭉 보다가 갑자기 <code>Msg string `json:&quot;message&quot;`</code>
응???</p>
<h3 id="뭐야-구조체-변수명-뒤에-저런-게-붙어">뭐야? 구조체 변수명 뒤에 저런 게 붙어..?</h3>
<p>struct tag에 부딪히고야 말았다.
뭐, 어차피 곧바로 개발 시작한 것도 아니었고 기껏해야 Go가 어떤 언어인지 보고 있었으니 이제라도 검색을 해서 공부하기 시작했다.</p>
<p>이제서야 Golang으로 짜여진 코드를 볼 수 있게 됐다.
완벽히 모든 것들을 공부하고 시작한 것은 아니어도, 작성돼 있는 것들은 얼추 어떤 역할을 하는 코드인지 파악할 수 있게 된 것이다.</p>
<h3 id="if-err--nil--다른-에러처리-기법은-없나-try-catch는">if err != nil {...} 다른 에러처리 기법은 없나? try catch는..</h3>
<p>Golang에는 try catch가 없습니다.. 네?
이걸 몰랐을 때는 코드를 보면서도 참 지저분하다고 생각했다.</p>
<p>API 하나에서 처리되는 에러만 해도 3개 이상..
그런데 이걸 예쁘게 한 번에 처리할 수도 없고 이렇게 써야 한다니..!!</p>
<p>라고 생각했지만, 이렇게 에러가 튀어나올 때마다 바로바로 처리해주는 게 습관이 되고 나서 보니, 에러처리에 대한 직관성이 잘 확보되는 것 같다고 생각이 바뀌게 되었다.</p>
<p>장단점이 확실하지만 뭐 어쩌겠습니까? 지금 제가 써야 하는 언어인 걸요!</p>
<hr>
<h1 id="rest-api란-무엇인가">REST-API란 무엇인가</h1>
<p>REST-API 개발 업무를 맡게 되고 하루 빨리 API 하나 하나 뚝딱뚝딱 만들어내야겠다는 생각이 머릿속을 지배했다.
REST-API에서 Request를 보내는 파트는 go-resty라는 패키지를 사용한다.
코드를 보며 &#39;아 이렇게 보내는 거구나&#39; 라는 직감으로 바로 개발을 진행할 수 있었다.</p>
<p>그런데?</p>
<p>REST-API는 뭘까? 하는 생각이 뒤늦게 들고 말았다.
RESTful한 API.. 심오하다 심오해</p>
<p>퇴근길에 유튜브를 켜서 REST-API에 대한 약 50분 정도 되는 길이의 영상을 시청하게 됐다.</p>
<p>REST-API의 역사 그 자체를 알게 되고, 이제 그 목적을 정확히 알고 개발할 수 있겠다는 생각을 하게 되었다.</p>
<p>또, 여기서 느낀 것이..
&#39;개발자들은 서로의 지식을 공유하는 것이 어렵지 않구나.&#39;
&#39;검색만 하면 누군가 정리해둔 글들이 정말 많구나.&#39;
&#39;글 뿐만 아니라 영상, 강의도 정말 많네?&#39;</p>
<p>새삼스레 느낀 점들이다.
학교에서 개발할 때는 아무 생각없이 그저 &#39;인터넷에 올라와 있으니까.&#39; 찾아서 공부하고 썼던 것들인데, 사회로 나와 보니 이게 당연한 게 아니라는 것을 알게 되었다.</p>
<hr>
<h1 id="기술은-나눌수록-발전하는-것">기술은 나눌수록 발전하는 것</h1>
<p>다른 직업군에서는 볼 수 없는 문화가 개발자들 사이에는 형성되어 있는 것 같다.
부모님 세대에서만 봐도 &#39;사수가 일을 안 가르쳐주고 자기 밥그릇 챙기기에 급급했다.&#39; 는 얘기를 심심치 않게 들어볼 수 있는데..</p>
<p>정말 자기가 하는 일이 전문화되어 있고, &#39;이 일은 나 아니면 못 해&#39; 가 아니면 일자리조차 보장되지 않던 시절이었으니까.</p>
<p>아직까지도 이런 마인드가 남아 있는 회사들이 정말 많은 것 같다.
주변 다른 직업군 친구들 얘기만 들어보더라도.. 있다. 분명히..</p>
<p>그런데 개발자들은 자신의 기술을 공유한다.
stackoverflow, velog, github page, tistory 등등 본인이 시행착오를 겪어 이룬 기술을 다른 사람들과 공유한다.</p>
<p>또! 오픈소스라는 분야는 정말 대단한 것 같다.
엄청난 노력이 들어간 코드를 무료로 제공한다는 점이..!!
오픈소스가 없었다면 기술 블로그와 같은 문화도 자리 잡을 일은 없었을 것 같다.</p>
<p>내가 velog를 시작한 이유이기도 하다.
(감사함은 진작에 느꼈고 기여하기 위해 velog를 시작한 것은 꽤나 최근이다.)</p>
<p>비록 알고 있는 범위가 다른 선배 개발자들보다야 훨씬 좁지만, 감사함에 힘입어 시작했다.</p>
<p>포스팅 주기가 그리 짧지 않고 늘어지기도 하지만, 꾸준히 작성해서 사회에 기여하고 싶다.</p>
<hr>
<h1 id="보안에-대한-고찰">보안에 대한 고찰</h1>
<p>REST-API를 만들다 보니, 보안에 대한 부분을 신경쓰지 않을 수 없었다.
어떻게 해야 공격을 줄일 수 있을까?
어떻게 해야 공격을 안 받을 수 있을까?
피할 수 없다면 어떻게 해야 서버가 뻗지 않을 수 있을까?</p>
<p>아무리 고민해도 모자라지 않은, 하지만 과하면 독이 되는..</p>
<p>보안은 참 어렵다. 어렵다 어려워 너무..</p>
<h2 id="jwt를-쓰면-된다">JWT를 쓰면 된다!</h2>
<p>그러면 사용자를 특정할 수 있고, API 호출을 할 때 유효성 검사만 하면 되지 않느냐?</p>
<p>아니다!!
어쨌든 토큰이고, 어떤 방법으로든 토큰은 탈취당할 수 있다.</p>
<p>탈취당하면 끝이다!!</p>
<h3 id="만료-기간을-짧게-설정하면-되잖아">만료 기간을 짧게 설정하면 되잖아?</h3>
<p>뭐 얼마나 짧게..??
10분 15분? 은행 웹 페이지처럼??</p>
<p>절대 안 된다.
뭐, 절대라고까진 아니어도 사실 그렇게까지 짧게 설정하면 좋지 않다.</p>
<p>JWT를 쓰지 않았을 때를 가정하고 사용자가 로그인을 한 후에, API 요청을 하게 되면!
서버는 API 요청에 알맞게 동작을 수행하고 응답을 보낸다.</p>
<p>이 과정 사이에 JWT 토큰을 보관하는 DB가 필요하고, 10분 15분이 지나서 토큰이 만료되면 DB에 새로운 토큰을 넣어, 새로 발급해야 한다.
또한 사용자마다 토큰을 다르게 해야 하기 때문에 그 양은 상당하다.</p>
<p>동접자 10만명일 때, (물론 지금 개발하는 서비스가 그렇게까지 거대한 것도 아니지만..) 10만명이 어떤 동작을 수행할 때마다 API 요청을 하고, 거기에 추가로!
추가로, 토큰 시간이 만료되기 전에 새로운 토큰을 발급받는다면??</p>
<p>없어도 됐을 10만 번의 트랜잭션이 10분 15분마다 한 번씩 추가되는 셈이다.</p>
<h3 id="그럼-뭐-어떡해">그럼 뭐 어떡해..?</h3>
<p>어떡하긴!
JWT만 사용하는 방식에서 벗어나면 된다.</p>
<p>이중 보안!
JWT와 Basic Authentication을 활용할 수 있다.</p>
<p>근데 Basic Auth도 탈취당하면?</p>
<p>CORS라는 방법이 있다!</p>
<h2 id="cors">CORS</h2>
<p>Cross Origin Resource Sharing
정말 극악무도하지 않을 수 없다.
특정한 도메인에서 오는 요청만 허용할 수 있게 하는 보안 옵션인데, 이게 설정하는 게 여간 까다롭고 복잡하다.</p>
<p>하지만 잘만 설정하면 내가 지정한 도메인에서의 요청만 받고, 나머지 요청은 거절하게 된다.</p>
<p>아무튼 나도 CORS 때문에 고생깨나 했다.</p>
<h3 id="cors까지만-써도-되는가">CORS까지만 써도 되는가?</h3>
<p>NO!! 그렇지 않다.
하지만 어쩌겠는가..
철통보안을 위해 가져다 쓸 수 있는 모든 보안체계를 투입하게 되면 서버는 버티지 못할 것이다.
적절한 절충안이 필요했고, 일단 당장은 여기까지만 하자는 결론에 이르렀다.</p>
<hr>
<h1 id="협업툴-wiki--jira">협업툴 Wiki / Jira</h1>
<h2 id="wiki">Wiki</h2>
<p>사내 문서화를 위해 사용하는 Wiki는 이 벨로그마냥 정리하기 편하다.
여러 협업툴들이 있지만 당장 현업에서 사용하고 있는 게 wiki이기에.</p>
<p>주로 Wiki에는 사내 프로젝트에서 쓰인 기술이나 코드 기능에 대한 부분, API 문서 등을 작성한다.
그 외에 오픈되어 있는 기술들에 대해서는 velog에 작성하기도 한다.</p>
<p>뭐 아무튼 사내 기밀 유지를 위해 API 기능들을 velog에 소개하진 않고, 어떻게 API 기능을 작성하는지 정도는 공유할 수 있을 것이다.</p>
<h2 id="jira">Jira</h2>
<p>버전 관리, 이슈 관리를 위해 사용하는 Jira는 정말 편리하다.
나의 업무 진척도를 바로 바로 공유할 수 있고, 내 앞에 티켓이 몇 개나 있는지도 확인할 수 있다.
서로 메신저나 직접적인 대화를 하지 않더라도 업무 부여나 문제 제기가 가능하다.</p>
<p>가장 좋은 점은, 내가 할 일이 무엇이있는지 고민할 필요가 없다는 점이다.
내 앞에 있는 티켓을 전부 처리하면 나는 자유다.
물론 이마저도 성격상 일을 다 처리하면 다른 일거리 없는지 찾아보곤 한다.</p>
<p>&#39;월급 루팡&#39; 이란 내 사전에 없다..
앗, 아직까지 재택근무할 땐 약간 업무 효율이 떨어지는 것 같기도..</p>
<hr>
<h1 id="코로나가-바꾼-일상">코로나가 바꾼 일상</h1>
<h2 id="코로나로-인한-재택근무">코로나로 인한 재택근무</h2>
<p>바로 위에 재택근무에 대한 부분을 언급하며 급 떠올린 재택근무..</p>
<p>재택근무를 번갈아 가며 하고 있는데 재택을 하면 장단점이 너무나 확실하다.</p>
<h3 id="출퇴근-시간이-내-시간">출퇴근 시간이 내 시간!!</h3>
<p>출퇴근 시간이 온전히 내 시간이 된다.
버스를 타는 시간도, 지하철을 타는 시간도, 사실 출퇴근하며 유튜브나 구글링으로 공부를 하기도 하고 넷플릭스나 모바일 게임을 즐기며 딱히 지루하지 않은 시간들을 보내고 있었지만..
잠을 더 오래 잘 수 있고, 퇴근이 빠르고! 좀 더 게을러질 수 있었다.(?)</p>
<h3 id="놀-게-너무-많다">놀 게 너무 많다!!</h3>
<p>집에는 놀 게 너무 많다!!
당장 내 책상을 보면 바로 앞에 데스크탑, 노트북 (노트북은 업무용으로 사용합니다), 닌텐도 스위치, 스마트폰, 태블릿..!!
유혹이 너무나 많다..</p>
<p>심지어 옆으로 고개만 돌리면 침대가 보이니.. 할 말 다 했다.</p>
<p>유혹을 참고 일을 하려니 너무 힘들다 ㅎㅎ..
그렇다고 온전히 일만 8시간 꽉 채워서 한다고 할 수는 없지만..!!
딴 짓은 최대한 안 하려고 하는 편이다.</p>
<p>최근에는 이를 방지하기 위해 스터디 카페를 간다.
적어도 업무 보는 시간엔 휴게 공간이랑 분리해두는 게 좋은 것 같다.</p>
<p>덕분에 재택임에도 업무 능력 상승!!</p>
<p>그럼에도 나는 회사로 출근하는 게 더 맞는 것 같다....</p>
<hr>
<h1 id="2021년의-나를-평가하자면">2021년의 나를 평가하자면?</h1>
<p>신입!! 말 그대로 신입 그 자체였다.</p>
<p>좌충우돌 개발자 적응기이며, 꿈만 큰 똥덩어리(?)랄까..</p>
<p>열심히 하고자 하는 의욕은 넘치고 배우고자 하는 마음도 크지만 당장에 실력은 없는..
그런 상태다.</p>
<p>그럼에도 불구하고 정말 많은 걸 배웠다는 확신은 있는 그런 한 해였다.
정말, 많은 성장을 할 수 있는 한 해였다.</p>
<p>지금 계속해서 포스팅하고 있는 Golang 기초 시리즈는 꽤 오래 지속될 것으로 보인다.
당장 1년 동안 사용한 Golang이지만 아직까지도 제대로 활용하고 있지 못하다는 느낌이 크다.</p>
<p>공부를 완벽히 해두고 시작한 게 아닌 탓일까, 아니면 아직도 공부하는 방법을 모르는 걸까.</p>
<p>아무튼 기초 시리즈를 포스팅하면서 새로 배우는 것들도 많고 다시 한 번 생각하게 되는 그런 시간들을 가지고 있다.</p>
<h2 id="사회-초년생-신입-개발자">사회 초년생, 신입 개발자</h2>
<p>이 두 가지 타이틀을 걸고 열심히, 치열하게 살아낸 2021년이다.</p>
<p>&#39;나, 이제 제법 개발자의 모습을 갖췄을지도?&#39;
라고 착각을 하곤 한다.</p>
<p>내년엔 어떤 일들이 있을까?
내가 만들고 있는 서비스는 무사히 런칭할 수 있을까?
나는 어떤 개발자가 되어 있을까?</p>
<p>당장에 기대되는 것들이다.</p>
<p>내년엔 제대로 Gopher가 돼 보겠다.</p>
<hr>
<h1 id="새해-다짐">새해 다짐</h1>
<h2 id="항상-발전하자-그리고-거기에-안주하지-말자">항상 발전하자. 그리고 거기에 안주하지 말자!</h2>
<hr>
<p>이상입니다.👍</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Golang 기초 (7) : 반복문에 대하여]]></title>
            <link>https://velog.io/@vamos_eon/go-loops</link>
            <guid>https://velog.io/@vamos_eon/go-loops</guid>
            <pubDate>Thu, 30 Dec 2021 13:30:25 GMT</pubDate>
            <description><![CDATA[<p>안녕하세요, 주니어 개발자 Eon입니다.</p>
<p>이번에 다룰 내용은 반복문입니다.</p>
<p>Golang에서의 반복문은 오로지 <code>for</code>를 사용하는 방법만 존재합니다.
물론 이 <code>for</code>문을 어떻게 사용하느냐에 따라 다른 언어에 존재하는 반복문 형태를 손쉽게 구현할 수 있습니다.</p>
<h2 id="📌-반복문">📌 반복문</h2>
<p>반복문은 <strong>코드의 반복</strong>을 위해 존재합니다.
간단한 작업같아도 반복된 작업을 해야 할 때, 우리는 반복문을 사용합니다.
이는 <strong>코드의 간결화, 가독성 확보</strong>를 위해 꼭 필요합니다.
<strong>반복문의 사용은 이중, 삼중으로도 사용이 가능</strong>한데 너무 많이 겹쳐두는 것은 좋지 않습니다.
가독성도 좋지 않고, 유지•보수하기 좋지 않습니다.
제 경우엔 삼중을 최대로 두려고 합니다.</p>
<p>그렇다고 겹겹이 쌓인 반복문을 함수로 처리하는 게 좋은가?
그건 또 아닙니다. 콜스택이 쌓이면 퍼포먼스에서 미세하게 차이가 날 수 있으므로 적절한 타협이 필요합니다.
물론 퍼포먼스에 큰 차이는 없지만 극한의 퍼포먼스를 보장해야 하는 상황이라면 직접 테스트하고 사용해야 합니다.</p>
<p>만약, 반복문이 여러 번 반복된다면 해당 역할을 하는 함수를 작성해서 처리하는 방법이 코드의 간소화 및 유지•보수를 위해 좋을 것입니다.</p>
<p>반복문이 있음으로써 할 수 있는 것들은 무궁무진합니다.
가령, 1부터 1000까지의 정수를 출력할 때에도 <strong>1000줄을 쓰느냐, 3줄을 쓰느냐</strong>는 큰 차이입니다.</p>
<hr>
<h3 id="📝-반복문의-종류와-사용">📝 반복문의 종류와 사용</h3>
<p>위에서 말씀드린 것처럼 Go에서는 <code>for</code>문만을 제공합니다.
하지만, 이 <code>for</code>문으로 표현할 수 있는 반복문의 종류는 여러 가지 있습니다.
<strong><a href="https://go.dev/doc/effective_go#for">Effective go에서는 Golang의 반복문을 이와 같이 설명하고 있습니다.</a></strong>
<strong>Golang에는 <code>do-while</code>이 없습니다.</strong></p>
<hr>
<h4 id="📍-for-init-condition-post---c-like-for">📍 for init; condition; post {...} : C-Like for</h4>
<blockquote>
</blockquote>
<pre><code class="language-go">for i := 0; i &lt; 1000; i++ {
    fmt.Printf(&quot;%d &quot;, i)
}
/* The result of output
0 1 2 3 ... (중략) 999 
*/</code></pre>
<p><code>for 선언; 조건; 후처리 작업 {...}</code></p>
<p>C언어에서 사용하듯이 위와 같이 작성할 수 있습니다.
<strong>가장 기본적인 형태</strong>이며, 제어하기 가장 쉬운 형태라고 볼 수 있습니다.</p>
<hr>
<h4 id="📍-for-condition---c-like-while">📍 for condition {...} : C-Like while</h4>
<blockquote>
</blockquote>
<pre><code class="language-go">i := 0
for i &lt; 1000 {
    fmt.Printf(&quot;%d &quot;, i)
}
/* The result of output
0 1 2 3 ... (중략) 999 
*/</code></pre>
<p><strong><code>for 조건 {...}</code></strong></p>
<p>C언어에서 사용하는 <code>while</code>문을 이렇게 표현할 수 있습니다.
위의 <code>for</code>문은 <code>i &lt; 1000</code>이라는 조건을 만족하는 중에만 실행됩니다.
<code>{...}</code>안에 i값을 증가시키거나 다른 특정한 조건을 넣어, i값을 1000보다 큰 값으로 만들어 루프(반복문)를(을) 탈출할 수 있습니다.
(아래에 소개하는 <code>break</code>를 이용해서 탈출할 수도 있습니다.)</p>
<hr>
<h4 id="📍-for---c-like-for">📍 for {...} : C-Like for(;;)</h4>
<blockquote>
</blockquote>
<pre><code class="language-go">i := 0
for {
    if i &gt;= 1000 {
        break
    }
    fmt.Printf(&quot;%d &quot;, i)
    i++
}
/* The result of output
0 1 2 3 ... (중략) 999 
*/</code></pre>
<p>C언어에서 사용하는 <strong>무한 루프</strong>를 이렇게 표현할 수 있습니다.
아무런 조건이 없어, 그저 무한으로 반복되는 루프입니다.
탈출 조건을 명시하지 않으면 컴퓨터의 리소스를 모두 사용할 때까지 무한히 반복됩니다.
위의 코드에서는 i의 값이 1000 이상일 경우, <code>break</code>를 실행하게 했습니다.
<code>break</code>문을 실행하면 루프를 탈출하게 됩니다.</p>
<hr>
<h4 id="📍-for---range--for-loop-for-array-slice-string-or-map-or-reading-from-a-channel">📍 for - range : &#39;for loop&#39; for array, slice, string, or map, or reading from a channel</h4>
<blockquote>
</blockquote>
<pre><code class="language-go">var array [4]string
array[0] = &quot;Hi&quot;
array[1] = &quot;this&quot;
array[2] = &quot;is&quot;
array[3] = &quot;array&quot;
for i, v := range array {
    fmt.Printf(&quot;index : %d \t value : %s \n&quot;, i, v)
}
/* The result of output
index : 0        value : Hi 
index : 1        value : this 
index : 2        value : is 
index : 3        value : array 
*/</code></pre>
<p><code>for 인덱스, 요소 := range 배열 {...}</code></p>
<p>(<a href="https://velog.io/@vamos_eon/go-xfoz05f6"><strong>array</strong></a>, slice, string, map, channel은 아직 다루지 않은 내용이므로 추후 포스팅할 때 링크 걸어두도록 하겠습니다.)
간단하게 위에 사용된 <code>array</code>에 대해서만 설명하겠습니다.
<code>array</code>는 &#39;배열&#39;이며, 값들을 저장합니다.</p>
<ol>
<li>변수 <code>array</code>를 string 타입 4개짜리 배열로 선업합니다.</li>
<li>각 인덱스 요소에 string 타입 값을 넣습니다. 이 때, 인덱스는 0부터 시작해서 3까지만 넣을 수 있습니다.
(값을 넣지 않은 요소에는 zero-value(기본값)가(이) 들어갑니다.)</li>
<li><code>for</code>를 사용해, 배열의 인덱스와 요소들을 모두 순차적으로 출력합니다.</li>
</ol>
<blockquote>
</blockquote>
<p><code>for-range</code> 문은 아래와 같이 사용할 수 있습니다.</p>
<blockquote>
</blockquote>
<p><code>for i, v := range array {...}</code></p>
<ul>
<li><strong>인덱스, 요소 모두를 사용</strong>해야 할 때 사용합니다.<blockquote>
</blockquote>
<code>for i := range array {...}</code></li>
<li><strong>인덱스만 사용</strong>할 때 사용합니다.<blockquote>
</blockquote>
<code>for _, v := range array {...}</code></li>
<li><strong>요소만 사용</strong>할 때 사용합니다.</li>
</ul>
<hr>
<h4 id="📍-for---range--for-loop-for-strings">📍 for - range : &#39;for loop&#39; for strings</h4>
<blockquote>
</blockquote>
<pre><code class="language-go">for i, v := range &quot;yes, array&quot; {
    fmt.Printf(&quot;index : %d \t value : %c \n&quot;, i, v)
}
/* The result of output
index : 0        value : y 
index : 1        value : e 
index : 2        value : s 
index : 3        value : , 
index : 4        value :   
index : 5        value : a 
index : 6        value : r 
index : 7        value : r 
index : 8        value : a 
index : 9        value : y 
*/</code></pre>
<p><code>for 인덱스, 요소 := range &quot;문자열&quot; {...}</code></p>
<p>위와 같이 평범한 <code>strings</code>에 대해서도 사용이 가능합니다.
이 때, <code>v</code>로 받는 값이 각 문자가 되며, 문자 출력을 위해 <code>%c</code>를 사용합니다.</p>
<hr>
<h4 id="📍-for-loop-with-parallel-assignment">📍 &#39;for loop&#39; with parallel assignment</h4>
<blockquote>
</blockquote>
<pre><code class="language-go">a := [4]string{&quot;Hi&quot;, &quot;this&quot;, &quot;is&quot;, &quot;array&quot;}
for i, j := 0, len(a)-1; i &lt; j; i, j = i+1, j-1 {
    a[i], a[j] = a[j], a[i]
}
for i, v := range a {
    fmt.Printf(&quot;index : %d \t value : %s \n&quot;, i, v)
}
/* The result of output
index : 0        value : array 
index : 1        value : is 
index : 2        value : this 
index : 3        value : Hi 
*/</code></pre>
<p><strong><code>for 변수1, 변수2 := 변수1의 값, 변수2의 값; 조건식; 변수1, 변수2 = 변수1에 대한 계산식, 변수2에 대한 계산식 {...}</code></strong></p>
<p>C언어에서 위와 같이 배열의 순서를 뒤바꾸기 위해서 우리는 &#39;tmp&#39;라는 변수를 만들어 사용하곤 했습니다.
Golang에서는 그럴 필요 없이 <strong>&#39;병렬 할당&#39;</strong>을 이용하면 됩니다.</p>
<blockquote>
</blockquote>
<p><strong>병렬 할당</strong></p>
<pre><code class="language-go">a, b := 1, 100</code></pre>
<p><strong>위의 코드는 좌항 a와 b에, 우항 1과 100을 순차적으로 할당합니다.</strong></p>
<hr>
<h4 id="📍-loop-using-label">📍 &#39;loop&#39; using Label</h4>
<blockquote>
</blockquote>
<pre><code class="language-go">    var i int = 0
Label1:
    if i &gt;= 1000 {
        goto Label2
    }
    fmt.Printf(&quot;%d &quot;, i)
    i++
    goto Label1
Label2:
    fmt.Println(&quot;finished&quot;)
    /* The result of output
    0 1 2 3 ... (중략) 999 finished
    */</code></pre>
<p><code>레이블명:</code>
레이블명은 대소문자 관계없이 지정할 수 있다.</p>
<p><code>Label</code>은 위와 같이 사용할 수 있습니다.
레이블은 특정한 조건에 실행해야 하는 코드를 감싸고 있는 플래그라고 보시면 됩니다.
또, 루프의 기능을 하기 위해 만들어진 것은 아니기 때문에 <code>goto</code>를 사용해 특정 <code>Label</code>로 이동하는 것을 볼 수 있습니다.</p>
<hr>
<h4 id="📍-for-loop-using-label-2">📍 &#39;for loop&#39; using Label 2</h4>
<blockquote>
</blockquote>
<pre><code class="language-go">    var i int = 0
Label1:
    for {
        if i &gt;= 1000 {
            break Label1
        }
        fmt.Printf(&quot;%d &quot;, i)
        i++
    }
    fmt.Println(&quot;finished&quot;)
    /* The result of output
    0 1 2 3 ... (중략) 999 finished
    */</code></pre>
<p>Golang에서는 위와 같이 루프에 <code>Label</code>을 붙이고 해당 <code>Label</code>을 <code>break</code>해서 탈출할 수 있습니다.</p>
<hr>
<hr>
<h2 id="📌-반복문의-탈출">📌 반복문의 탈출</h2>
<p>반복문을 특정한 조건이 되면 그만두고 싶을 때, 탈출 조건을 명시해야 한다고 했습니다.
탈출 뿐만 아니라, 특정 조건에서의 반복문 점프도 가능합니다.</p>
<hr>
<h3 id="📝-반복문-탈출의-종류와-사용">📝 반복문 탈출의 종류와 사용</h3>
<p>반복문을 제어하기 위해 사용할 수 있는 것들은 <code>continue</code>, <code>break</code>, <code>goto</code>가 있습니다.</p>
<hr>
<h4 id="📍-break--to-escape-current-loop">📍 break : to escape current loop</h4>
<p>무한 루프가 아니더라도 반복문을 수행하다가 특정 조건에 해당 반복문을 빠져나와야 할 때 사용합니다.</p>
<blockquote>
</blockquote>
<pre><code class="language-go">a := 0
for {
    if a &gt;= 10 {
        break
    }
    a++
}
fmt.Println(&quot;value of a :&quot;, a)
/* The result of output
value of a : 10
*/</code></pre>
<p>위와 같이 무한 루프에서는 <code>break</code>를 사용해서 &#39;현재&#39;루프를 탈출합니다.
<strong>만약 이중 <code>for</code>문을 사용했고 가장 안 쪽 루프에서 <code>break</code>를 사용했다면 가장 안 쪽 루프만 탈출하게 됩니다.</strong></p>
<hr>
<h4 id="📍-continue--to-skip-current-iteration">📍 continue : to skip current iteration</h4>
<p>반복문을 짜다 보면 특정 조건에서 넘어가게 만들어야 할 때가 있습니다.
그럴 때 <code>continue</code>는 유용하게 쓰입니다.</p>
<blockquote>
</blockquote>
<pre><code class="language-go">b := 0
for {
    if b &lt; 10 {
        b++
        continue
    }
    fmt.Println(&quot;value of b :&quot;, b)
    break
}
/* The result of output
value of b : 10
*/</code></pre>
<p>b는 0부터 시작해, 1씩 증가합니다.
또, <code>if</code>조건문 이후에 나오는 결과 출력하는 부분이 있습니다.
하지만, 0부터 9까지는 <code>continue</code>를 만나게 돼, 출력 파트는 실행되지 않습니다.</p>
<hr>
<h4 id="📍-goto--to-go-to-specific-label-in-the-code">📍 goto : to go to specific Label in the code</h4>
<p>반복문 소개하며 보여드린 Label 파트와 중복되는 부분입니다만 <code>goto</code>를 다시 소개하겠습니다.</p>
<blockquote>
</blockquote>
<pre><code class="language-go">    c := 0
    for {
        if c &gt;= 10 {
            goto Label1
        }
        c++
    }
Label1:
    fmt.Println(&quot;value of c :&quot;, c)
/* The result of output
value of c : 10
*/</code></pre>
<p>역시 무한 루프를 돌다가 조건이 만족하면 <code>goto Label1</code>로 Label1으로 이동하여 루프를 탈출합니다.</p>
<hr>
<h4 id="📍-break-label--to-escape-from-the-label-loop">📍 break Label : to escape from the Label loop</h4>
<p>이 역시 위에서 한 번 언급이 있었으나, 다시 한 번 소개하겠습니다.</p>
<blockquote>
</blockquote>
<pre><code class="language-go">    d := 0
Loop1:
    for {
        if d &gt;= 10 {
            break Loop1
        }
        d++
    }
    fmt.Println(&quot;value of d :&quot;, d)
/* The result of output
value of d : 10
*/</code></pre>
<p>무한 루프에 레이블을 지정했고, 특정 조건에 <code>break Loop1</code>을 통해 Loop1 레이블에서 탈출할 수 있게 했습니다.
이렇게 루프에 레이블을 지정하면 <strong>특정 조건에 다시 해당 레이블로 이동해서 코드를 동작할 수 있다</strong>는 장점이 있습니다.
물론 위와 같이 탈출 조건을 명시해서 여러 번 사용할 수도 있습니다.</p>
<hr>
<p>이번 포스트는 반복문에 대한 내용이었습니다.
감사합니다.👍</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Golang 기초 (6) : 조건문에 대하여]]></title>
            <link>https://velog.io/@vamos_eon/go-condition</link>
            <guid>https://velog.io/@vamos_eon/go-condition</guid>
            <pubDate>Tue, 21 Dec 2021 14:00:33 GMT</pubDate>
            <description><![CDATA[<p>안녕하세요, 주니어 개발자 Eon입니다.</p>
<p>이번 포스트는 조건문에 대해 다루겠습니다.</p>
<h2 id="조건문">조건문</h2>
<p>조건문은 특정한 조건을 만족했을 때 실행하는 문장(코드) 을(를) 말합니다.
golang에서 조건문은 if / switch 두 가지가 있습니다.</p>
<h3 id="if문">if문</h3>
<p>조건을 만족하는지는 bool (true / false) 값으로 판단합니다.
조건문 if문은 아래와 같이 사용할 수 있습니다.</p>
<blockquote>
</blockquote>
<p>&#39;if&#39; : 만약에</p>
<pre><code>조건문 &#39;만일 어떠한 조건을 만족한다면~&#39; {
&#39;이 코드를 실행해라&#39;
} </code></pre><p>&#39;else if&#39; : 아니면 이렇다면</p>
<pre><code>아니면 이렇다면 {
&#39;이 코드를 실행해라&#39;
}</code></pre><p>&#39;else&#39; : 아니면</p>
<pre><code>아니면 {
&#39;이 코드를 실행해라&#39;
}</code></pre><hr>
<p>Golang에서는 아래와 같이 사용할 수 있습니다.</p>
<blockquote>
</blockquote>
<pre><code class="language-go">if condition1 {
    // TODO1
} else if condition2 {
    // TODO2
} else {
    // TODO3
}
// (단, condition1, condition2(은)는 각각 bool 값을 가짐)</code></pre>
<p>Golang에서의 &#39;else if&#39;와 &#39;else&#39;는 항상 if문이 끝나는 라인에 함께 써주어야 합니다.</p>
<hr>
<h3 id="if만-사용하면-안-될까">if만 사용하면 안 될까?</h3>
<p>if만 사용하면 안 될까?
안 되는 건 없습니다.
다만 비효율적이고 그렇게 하지 말아야 한다고 배웠을 뿐입니다.
이유를 한 번 파헤쳐 봅시다.</p>
<p>조건 A, B에 대하여 모든 경우의 수에 그에 맞는 특정한 코드를 실행해야 합니다.</p>
<blockquote>
</blockquote>
<p>먼저 if-else문을 보겠습니다.</p>
<pre><code class="language-go">if A &amp;&amp; B {
    // TODO AB
} else if A {
    // TODO A
} else if B {
    // TODO B
} else {
    // TODO nothing ..
}</code></pre>
<p>A와 B 모두 참인지 확인했고,
모두 참은 아닐 때, A가 참인지를 확인했고,
아니라면 B가 참인지를 확인했고,
그것도 아니라면 TODO nothing .. 파트를 실행합니다.</p>
<hr>
<p>다음은 if만 사용한 경우를 보겠습니다.</p>
<pre><code class="language-go">if A &amp;&amp; B {
    // TODO AB
}
if A &amp;&amp; !B {
    // TODO A
}
if !A &amp;&amp; B {
    // TODO B
}
if !A &amp;&amp; !B {
    // TODO nothing ..
}</code></pre>
<p>A와 B 모두 참인지 확인했습니다.
A가 참이고 B가 거짓일 때를 확인했습니다.
A가 거짓이고, B가 참일 경우를 확인했습니다.
A와 B 모두 거짓일 때를 확인했습니다.</p>
<hr>
<p>제가 쓴 문장들을 보시면 if-else에서는, &#39;~했고, ~했고, ~했고&#39;가 쓰였습니다.
연속된 것으로 볼 수가 있습니다.
또한, A에 대한 검증이 끝난 이후에는 A가 참인지 거짓인지는 확인하지 않았습니다.</p>
<blockquote>
</blockquote>
<p>반면에, if만 사용한 부분에서는, &#39;~했습니다. ~했습니다. ~했습니다.&#39;가 쓰였습니다.
연속적이지 않고, 모든 조건들이 독립되어 있음을 알 수 있습니다.
또한, A에 대한 검증이 끝난 것 같은데도 다시 A에 대하여 검증해야 했습니다.
A에 대한 검증을 다시 넣지 않았다면 A가 참이든 거짓이든 상관없이 B에 대한 검증만 하는 조건이 됐을 겁니다.</p>
<hr>
<p>같은 조건에 대하여 중복된 검증은 좋지 않습니다.
조건이라 함은 조건 연산으로 이루어져 있으며, 연산은 중복해서 수행하면 정확도에 관계없이 당연히 속도가 낮고, 퍼포먼스 저하로 이어집니다.
따라서 모든 프로그램 언어에서 if만 사용하는 것은 권장하지 않습니다.</p>
<hr>
<h3 id="if문-예제">if문 예제</h3>
<p>if 조건문을 사용하기에 앞서, 조건에 따라 다른 값을 반환하는 함수를 정의하고 시작하겠습니다.
입력값은 정수로 하고, 음수나 양수에 상관없이 양수를 반환하는 함수를 작성하겠습니다.</p>
<blockquote>
</blockquote>
<p>절댓값 반환 예제</p>
<pre><code class="language-go">func AbsoluteValue(input int64) int64 {
    if input &lt; 0 {
        return -input
    }
    return input
}</code></pre>
<blockquote>
</blockquote>
<p>if input &lt; 0 : 함수의 입력값 &#39;input&#39;이 0보다 작을 경우, {괄호} 안의 코드를 실행함
return -input : 음수일 경우에 실행되는 코드이므로, 앞에 &#39;- (마이너스)&#39;를 붙여 양수로 만들고 반환함
return input : if 조건문이 조건에 맞지 않아 실행되지 않을 때 (입력값이 양수일 때), 실행됨</p>
<p>이렇게 사용할 수 있습니다.
그럼 if 하나 당 조건 하나만 체크할 수 있는가?</p>
<p>아닙니다. &amp;&amp; 연산자로 아래와 같이 여러 조건을 동시에 만족할 경우를 작성할 수 있습니다.</p>
<blockquote>
</blockquote>
<p>절댓값 반환 예제 + 타입 변환</p>
<pre><code class="language-go">func AbsoluteValue(input int64) uint32 {
    if input &lt; 0 &amp;&amp; -input &lt;= 4294967295 {
        return uint32(-input)
    }
    if input &lt; 0 &amp;&amp; -input &gt; 4294967295 {
        return uint32(4294967295)
    }
    if input &gt;= 0 &amp;&amp; input &lt;= 4294967295 {
        return uint32(input)
    }
    if input &gt;= 0 &amp;&amp; input &gt; 4294967295 {
        return uint32(4294967295)
    }
    return uint32(input)
}</code></pre>
<blockquote>
</blockquote>
<p>&#39;4294967295&#39;는 uint32의 최댓값입니다.
4294967295에 1을 더해, 4294967296이 되면 오버플로우가 발생해, 값이 0이 됩니다.
4294967295에 2를 더한 결과값은 1이 됩니다.</p>
<blockquote>
</blockquote>
<p>if input &lt; 0 &amp;&amp; -input &lt;= 4294967295 : input이 0보다 작은 음수이며, 이를 절댓값으로 만들었을 때의 결과값이 uint32의 최댓값과 같거나 작을 경우에만 실행함</p>
<hr>
<h2 id="switch문">switch문</h2>
<p>switch는 표현식의 값을 case절의 표현식과 일치하는지 체크하고, 일치한다면 해당 case절을 실행시킵니다.
<strong>golang에서 특이한 점은, break문을 사용하지 않아도 case 한 개만 실행하고 탈출한다는 점입니다.
break문은 컴파일 단계에서 컴파일러가 추가해 줍니다.</strong></p>
<h3 id="switch문-예제">switch문 예제</h3>
<h4 id="값이-일치할-때-실행하는-switch">값이 일치할 때 실행하는 switch</h4>
<p>첫 번째로 볼 예제는 값이 일치할 때 실행하는 switch문입니다.</p>
<blockquote>
</blockquote>
<pre><code class="language-go">package main
&gt;
import &quot;fmt&quot;
&gt;
func main() {
    var name string = &quot;vamos&quot;
    switch name {
    case &quot;vamos&quot;:
        fmt.Printf(&quot;name is %s\n&quot;, name)
        fmt.Println(&quot;but it is not a name&quot;)
    case &quot;eon kim&quot;:
        fmt.Printf(&quot;name is %s\n&quot;, name)
        fmt.Println(&quot;yes, it is your name&quot;)
    default:
        fmt.Println(&quot;other..&quot;)
    }
}</code></pre>
<ul>
<li>var name string = &quot;vamos&quot; : name 변수를 string 타입으로 선언하고, 그 값으로 &quot;vamos&quot;를 넣었습니다.</li>
<li><strong>switch name {...}</strong> : switch문의 시작함
(여러 가지 작성법 중에 한 가지입니다.)</li>
<li><strong>case &quot;vamos&quot;:</strong> : name과 case절의 표현식의 값이 일치하면 해당 case절을 실행함
(이 예제에서는 name과 case절의 &quot;vamos&quot;가 서로 같은 값을 가지고 있어, 이 case절을 실행합니다.)</li>
<li><strong>case &quot;eon kim&quot;:</strong> : name의 값이 &quot;eon kim&quot;이 아니므로, 이 case절은 무시함</li>
<li><strong>default:</strong> : 위의 case절 모두 일치하는 게 없으면 실행함
(default절은 없어도 상관없습니다.)</li>
</ul>
<hr>
<h4 id="표현식을-사용하는-switch">표현식을 사용하는 switch</h4>
<p>두 번째로 볼 예제는 표현식을 사용하는 switch문입니다.</p>
<blockquote>
</blockquote>
<pre><code class="language-go">package main
&gt;
import &quot;fmt&quot;
&gt;
func main() {
    var x uint8 = 1
    switch fx := x*3 + 2; fx {
    case 14:
        fmt.Println(fx)
        fmt.Println(&quot;1st case&quot;)
    case 11:
        fmt.Println(fx)
        fmt.Println(&quot;2nd case&quot;)
    case 8, 5:
        fmt.Println(fx)
        fmt.Println(&quot;3rd case&quot;)
    default:
        fmt.Println(&quot;other..&quot;)
    }
}</code></pre>
<ul>
<li><strong>switch fx := x*3 + 2; fx {...}</strong> : switch문 안에서만 사용할 수 있는 변수 fx를 선언하고, 표현식 x*3 + 2를 지정합니다. 그리고, case절과 일치시킬 값으로 fx를 그대로 사용함
(여기서 fx를 그대로 사용하지 않고 싶다면 fx + 1이든 어떠한 식의 변형을 해서 사용할 수도 있습니다.)</li>
<li><strong>case 8, 5:</strong> : 마찬가지로 fx의 값이 일치할 경우에 실행합니다. 여기서 case절은 두 개의 값의 경우 모두에 대하여 해당 case절을 실행함
(&#39;8 || 5&#39;와 같다고 보시면 됩니다.)</li>
</ul>
<hr>
<h4 id="표현식을-사용하는-case">표현식을 사용하는 case</h4>
<p>세 번째로 볼 예제는 표현식을 사용하는 case절입니다.</p>
<blockquote>
</blockquote>
<pre><code class="language-go">package main
&gt;
import &quot;fmt&quot;
&gt;
func main() {
    var x uint8 = 3
    var y uint8 = 25
    switch y {
    case x*3 + 2:
        fmt.Println(y)
        fmt.Println(&quot;1st case&quot;)
    case x*5 + 3:
        fmt.Println(y)
        fmt.Println(&quot;2nd case&quot;)
    case x*7 + 4, x*9 + 5:
        fmt.Println(y)
        fmt.Println(&quot;3rd case&quot;)
    default:
        fmt.Println(&quot;other..&quot;)
    }
}</code></pre>
<ul>
<li><strong>case x*3 + 2:</strong> : 표현식 x*3 + 2 를 case절에 사용했고, y의 값이 case절의 값과 같으면 실행함</li>
<li><strong>case x*7 + 4, x*9 + 5:</strong> : 여기서 y값은 x*7 + 4의 결과와 값이 같으므로, 이 부분의 case절을 실행함</li>
</ul>
<hr>
<h4 id="case절에-조건식을-사용하는-switch">case절에 조건식을 사용하는 switch</h4>
<p>네 번째로 볼 예제는 case절에 조건식을 사용하는 switch문입니다.</p>
<blockquote>
</blockquote>
<pre><code class="language-go">package main
&gt;
import &quot;fmt&quot;
&gt;
func getGrade(score uint8) (grade string) {
    switch {
    case score &gt; 100:
        grade = &quot;Try again.. &quot;
    case score &gt;= 90:
        grade = &quot;A&quot;
    case score &gt;= 80:
        grade = &quot;B&quot;
    case score &gt;= 70:
        grade = &quot;C&quot;
    case score &gt;= 60:
        grade = &quot;D&quot;
    case score &lt; 60:
        grade = &quot;F&quot;
    }
    return
}
&gt;
func main() {
    fmt.Print(&quot;Your score : &quot;)
    var myScore uint8
    fmt.Scanf(&quot;%d&quot;, &amp;myScore)
    myGrade := getGrade(myScore)
    fmt.Println(myGrade)
}</code></pre>
<ul>
<li><strong>fmt.Scanf(&quot;%d&quot;, &amp;myScore)</strong> : 정수를 입력 받아, myScore에 저장함</li>
<li><strong>myGrade := getGrade(mySocore)</strong> : getGrade()함수의 파라미터로 myScore를 사용하고, 반환값을 myGrade에 넣음</li>
<li><strong>switch {...}</strong> : switch문임을 나타내고, case절의 조건에 따라 실행합니다.</li>
<li><strong>case score &gt; 100:</strong> : 입력받아, 파라미터로 넘겨받은 score값이 100이 넘어가면 해당 case절을 실행함</li>
</ul>
<hr>
<h4 id="fallthrough를-사용하는-switch">fallthrough를 사용하는 switch</h4>
<p>다섯 번째로 볼 예제는 fallthrough를 사용하는 switch문입니다.
golang에서는 break문을 작성하지 않으면 컴파일러가 추가하여 자동으로 case절 하나만 실행하고 탈출한다고 했습니다.
<strong>연속적으로 case절 여러 개를 실행해야 할 때는 fallthrough를 사용합니다.</strong></p>
<blockquote>
</blockquote>
<pre><code class="language-go">package main
&gt;
import &quot;fmt&quot;
&gt;
func main() {
    var test uint8 = 3
    switch test {
    case 1:
        fmt.Println(test)
        fmt.Println(&quot;1st case&quot;)
    case 2:
        fmt.Println(test)
        fmt.Println(&quot;2nd case&quot;)
    case 3:
        fmt.Println(test)
        fmt.Println(&quot;3rd case&quot;)
        fallthrough
    case 4:
        fmt.Println(test)
        fmt.Println(&quot;4th case&quot;)
        fallthrough
    default:
        fmt.Println(&quot;other..&quot;)
    }
}</code></pre>
<ul>
<li><strong>fallthrough</strong>: 해당 case절이 실행되고, 바로 다음 case절도 실행함</li>
</ul>
<hr>
<h4 id="type-switch">Type switch</h4>
<p>여섯 번째로 볼 예제는 Type을 case절로 판단하여 실행하는 switch문입니다.
<strong><a href="https://go.dev/doc/effective_go#type_switch">Effective go : Type Switch</a></strong></p>
<blockquote>
</blockquote>
<pre><code class="language-go">package main
&gt;
import &quot;fmt&quot;
&gt;
func functionOfSomeType() int {
    return 0
}
&gt;
func main() {
    var t interface{}
    t = functionOfSomeType()
    switch t := t.(type) {
    default:
        fmt.Printf(&quot;unexpected type %T\n&quot;, t) // %T prints whatever type t has
    case bool:
        fmt.Printf(&quot;boolean %t\n&quot;, t) // t has type bool
    case int:
        fmt.Printf(&quot;integer %d\n&quot;, t) // t has type int
    case *bool:
        fmt.Printf(&quot;pointer to boolean %t\n&quot;, *t) // t has type *bool
    case *int:
        fmt.Printf(&quot;pointer to integer %d\n&quot;, *t) // t has type *int
    }
}</code></pre>
<ul>
<li>t.(type) : t의 type을 값으로 하여 case절을 실행함
(type assertion이라고 하며, 위는 이를 사용하는 방법 중 하나입니다.)</li>
</ul>
<p>위와 같이 type으로도 case절을 실행할 수 있습니다.</p>
<hr>
<p>이번 포스트는 조건문에 대한 내용이었습니다.
감사합니다.👍</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Golang 기초 (5) : 함수에 대하여]]></title>
            <link>https://velog.io/@vamos_eon/go-function</link>
            <guid>https://velog.io/@vamos_eon/go-function</guid>
            <pubDate>Thu, 09 Dec 2021 15:49:53 GMT</pubDate>
            <description><![CDATA[<p>안녕하세요, 주니어 개발자 Eon입니다.</p>
<p>이번 포스트에서는 함수에 대해서 다루겠습니다.</p>
<h2 id="📌-함수--function">📌 함수 : function</h2>
<p>함수는 수학에서 <strong>f(x)</strong> 로 표현합니다.
그리고 함수 f(x)는 어떠한 수식을 담고 있습니다.
<strong>f(x) = x + 5</strong>
<strong>f(3) = 3 + 5 = 8</strong>
이렇게 표현하곤 합니다. 
컴퓨터에서의 함수는 언어마다 사용하는 방법만 조금 다를 뿐, 수학에서의 함수와 다를 바 없습니다. 
입력값에 대한 출력값, 이게 함수입니다.
f(x)라는 함수에 입력값 3를 넣어, 함수의 동작으로 5를 더한 뒤, 출력값 8이 반환되었습니다.</p>
<p>컴퓨터에서의 함수 또한 입력값과 반환값으로 되어 있고, 이를 사용해 프로그램을 구성합니다. </p>
<hr>
<h3 id="📍-the-rule-of-naming-in-golang">📍 The rule of naming in Golang</h3>
<p>Golang에서 변수나 함수의 이름을 작성하는 방법은 정해져 있어, 매우 쉬운 편입니다. 
(그저 이름을 뭐로 지을지가 고민일뿐..)</p>
<p>현재 <strong>패키지 외부에서도 쓰일 수 있게 하려면 대문자</strong>로, [Unicode upper case letter (Unicode class &quot;Lu&quot;);]
현재 <strong>패키지 내부에서만 쓰일 수 있게 하려면 그 외의 문자</strong>로 시작하면 됩니다. </p>
<p>이는 사용자 간의 약속이 아닌 Golang 자체적 기능으로 분류된 것입니다. 
영대문자가 아닌 문자로 선언된 변수나 함수는 전부 외부 패키지에서 다룰 수 없습니다.
<a href="https://go.dev/ref/spec#Exported_identifiers"><strong>go.dev/ref/spec#Exported_identifiers</strong></a></p>
<h4 id="변수나-함수를-한글로-이름-짓기">변수나 함수를 한글로 이름 짓기</h4>
<p>Golang은 기본 인코딩이 UTF-8입니다.
따라서, 한글로 된 이름도 선언이 가능합니다.</p>
<p>UTF-8 문자로 패키지 외부에서 사용할 수 있게 하려면 반드시 문자 앞에 영대문자를 붙여주어야 합니다.
&quot;한글함수&quot; =&gt; &quot;A한글함수&quot;
&quot;한글변수&quot; =&gt; &quot;A한글변수&quot;</p>
<hr>
<h3 id="📍-the-format-of-function-in-golang">📍 The format of function in Golang</h3>
<p>Golang에서의 함수는 다음과 같이 사용할 수 있습니다.</p>
<blockquote>
</blockquote>
<pre><code class="language-go">func NameOfFunc(a int, b int) (isTrue bool) {
    isTrue = (a == b)
    return
}</code></pre>
<ul>
<li>func : function(함수)의 약자로, 함수를 정의할 때 꼭 포함해야 함</li>
<li>NameOfFunction : 함수의 이름이며, 영대문자로 시작하기 때문에 외부 패키지에서도 사용이 가능함</li>
<li>(a int, b int) : 함수 내에서 사용될 매개변수 a와 b를 각각 int로 선언함</li>
<li>(isTrue bool) : 함수의 반환값을 변수 isTrue로 하며, 해당 변수는 bool 타입임</li>
<li>{ ... } : 함수의 내용을 적는 파트이며, 자유롭게 작성함</li>
<li>isTrue = (a == b) : 비교 연산자 &#39;==&#39;으로 변수 a와 b가 동일한지 체크함<ul>
<li>a == b 가 참일 때는 true, 거짓일 때는 false를 반환함</li>
</ul>
</li>
<li>return : 값을 반환함 -&gt; 반환할 값을 변수 isTrue로 지정했기 때문에 변수명을 적지 않아도 됨</li>
</ul>
<hr>
<p>아래는 같은 기능을 하는 함수를 다르게 표현함</p>
<hr>
<pre><code class="language-go">func nameOfFunc(a, b int) bool {
    isTrue := (a == b)
    return isTrue
}</code></pre>
<ul>
<li>nameOfFunc : 함수 이름이며, Lu class 문자가 아니기 때문에 외부 패키지에서 불러올 수 없음</li>
<li>(a, b int) : 변수 a와 b 모두 int타입이라, 한 번에 선언함</li>
<li>bool : <strong>반환값의 타입만 명시하고 반환할 값은 지정하지 않음</strong></li>
<li>isTrue := (a == b) : isTrue 변수를 (a == b) 결과값에 맞는 타입으로 선언함</li>
<li>return isTrue : bool 타입 변수인 isTrue를 반환함<blockquote>
</blockquote>
</li>
<li><em>참고 : Golang에는 삼항 연산자가 없다. return (a == b) ? true : false 를 하고 싶으나, Golang에서는 불가하다.*</em></li>
<li>*<a href="https://go.dev/doc/faq#Does_Go_have_a_ternary_form">go.dev/doc/faq#Does_Go_have_a_ternary_form</a>**</li>
</ul>
<hr>
<h3 id="📍-the-usage-of-function-in-golang">📍 The usage of function in Golang</h3>
<p>Golang에서 함수를 사용하는 방법은 다음과 같습니다.</p>
<p>프로젝트에서 위와 같이 직접 작성하거나, 외부 패키지를 임포트하여 해당 패키지의 함수를 사용할 수 있습니다.</p>
<p>아래는 main 함수에서 위에 작성한 함수를 사용하는 방법입니다.</p>
<blockquote>
</blockquote>
<pre><code class="language-go">package main
&gt;
import &quot;fmt&quot;
&gt;
func nameOfFunc(a, b int) bool {
    isTrue := (a == b)
    return isTrue
}
&gt;
func main() {
    isOk := nameOfFunc(3, 5)
    fmt.Println(isOk)
}</code></pre>
<ul>
<li>package main : main 패키지이며, 해당 프로그램의 진입 패키지임</li>
<li>import &quot;fmt&quot; : 아래 main 함수에서 결과값 출력을 위해 import함</li>
<li>func main() { ... } : <strong>main 함수는 매개변수와 반환값이 없어야 함; 프로그램 실행 시, 가장 먼저 실행되는 함수라서 어떠한 매개변수도 전달받지 않으며, main 함수의 내용이 순차적으로 실행되고 main 함수가 모두 실행되면 프로그램이 종료됨</strong></li>
<li>isOk := nameOfFunc(3, 5) : isOk라는 변수를 선언하며 값으로는 nameOfFunc(3, 5)의 반환값을 넣음</li>
<li>nameOfFunc(3, 5) : nameOfFunc() 함수에 매개변수 3과 5를 전달하며, 이는 각각 위의 nameOfFunc() 함수의 변수 a와 b로 사용됨</li>
</ul>
<p>이렇게 여러 가지 기능을 함수로 구현하고, 그 기능들을 복합적으로 활용하여 프로그램이 만들어집니다.</p>
<hr>
<h3 id="📍-the-recursive-function-in-golang">📍 The recursive function in Golang</h3>
<p>recursive function은 재귀함수라고 합니다.
함수 자기 자신이 자기 자신을 다시 호출하는 방식입니다.
이 방식은 call stack이 많이 쌓일 수 있어서 그리 선호되는 방식은 아닙니다.
<img src="https://images.velog.io/images/vamos_eon/post/c656ab2e-9537-42f7-a62a-d438a80f6f7a/recursive.png" alt=""></p>
<p>위 그림과 같이 main()에서 recursiveAddFunc()를 호출하고, 그 안에서 다시 recursiveAddFunc()을 호출하고, 그 안에서 ...</p>
<p>이렇게 반복되다가 탈출 조건을 만나고, 값을 return할 때, </p>
<p>한 번에 해당 값이 main()으로 반환되는 것이 아니라 콜스택 가장 위에 있는 함수에서 이전 함수로 반환하고, 그리고 또 이전 함수로 반환하고, 그리고 또 이전 ...</p>
<p>결국 최종적으로 얻고 싶었던 값조차 재귀함수가 호출된 횟수만큼 return을 거쳐야 하기 때문에 비효율적입니다.
<strong>가급적 반복적인 기능 수행이 필요한 경우에는 반복문을 통해서 수행</strong>하고, 재귀함수는 꼭 필요한 때에만 사용하도록 하는 게 좋습니다.</p>
<p>Golang에서 재귀함수는 다음과 같이 구현할 수 있습니다.</p>
<blockquote>
</blockquote>
<pre><code class="language-go">package main
&gt;
import &quot;fmt&quot;
&gt;
func recursiveAddFunc(a, b int) int {
    a += b
    if a &lt; 100 {
        fmt.Println(&quot;Before recursive call, a is :&quot;, a)
        a = recursiveAddFunc(a, b)
        fmt.Println(&quot;After recursive call, a is :&quot;, a)
    }
    return a
}
&gt;
func main() {
    fmt.Println(recursiveAddFunc(5, 3))
}</code></pre>
<ul>
<li>a += b : a에 b를 더한 값을 a에 대입함</li>
<li><strong>if a &lt; 100 { ... } : a가 100보다 작을 때, 조건문 내의 내용을 수행함; 이 코드의 재귀함수 탈출 조건으로 사용되었으며, a가 100보다 커지면 if 조건문을 넘어가서 return a 를 수행함
(추후 다른 포스트에서 다룰 내용임)</strong></li>
<li>a = recursiveAddFunc(a, b) : 재귀함수 호출을 하고, 반환값을 a에 대입함</li>
<li>func main() { fmt.Println(recursiveAddFunc(5, 3)) } : recursiveAddFunc(5, 3)의 반환값을 출력함<blockquote>
</blockquote>
</li>
<li><ul>
<li>위의 코드 실행으로, 재귀함수 동작을 이해하기 좋습니다. **</li>
</ul>
</li>
</ul>
<hr>
<h3 id="📍-예제--calculator-계산기">📍 예제 : Calculator 계산기</h3>
<p>아래는 Golang으로 만든 간단한 사칙연산 계산기 프로그램입니다.
아직 조건문에 대한 내용을 다루지 않아 함수로만 구현하였고, 한 번에 모든 연산에 대한 결과를 출력하는 프로그램입니다.</p>
<blockquote>
</blockquote>
<pre><code class="language-go">package main
&gt;
import &quot;fmt&quot;
&gt;
func addFunc(a, b int) int {
    return a + b
}
func subFunc(a, b int) int {
    return a - b
}
func mulFunc(a, b int) int {
    return a * b
}
func divFunc(a, b int) int {
    return a / b
}
func modFunc(a, b int) int {
    return a % b
}
func main() {
    var x, y int
    fmt.Println(&quot;write two values to calculate..&quot;)
    fmt.Println(&quot;format : &#39;value1 value2&#39; then &#39;press Enter&#39;&quot;)
    fmt.Scanf(&quot;%d %d&quot;, &amp;x, &amp;y)
    fmt.Println(&quot;result of addFunc(x, y) is :&quot;, addFunc(x, y))
    fmt.Println(&quot;result of subFunc(x, y) is :&quot;, subFunc(x, y))
    fmt.Println(&quot;result of mulFunc(x, y) is :&quot;, mulFunc(x, y))
    fmt.Println(&quot;result of divFunc(x, y) is :&quot;, divFunc(x, y))
    fmt.Println(&quot;result of modFunc(x, y) is :&quot;, modFunc(x, y))
}</code></pre>
<p>** 실행 후, 연산을 수행할 값 두 개를 입력하면 결과가 출력됩니다. **</p>
<hr>
<p>이번 포스트는 함수에 대한 내용이었습니다.
Go도 익명함수가 있습니다. 다만 나중에 다루도록 하겠습니다.
감사합니다.👍</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Golang 기초 (4) : fmt 패키지에 대하여]]></title>
            <link>https://velog.io/@vamos_eon/go-fmt</link>
            <guid>https://velog.io/@vamos_eon/go-fmt</guid>
            <pubDate>Thu, 02 Dec 2021 16:53:56 GMT</pubDate>
            <description><![CDATA[<p>안녕하세요, 주니어 개발자 Eon입니다.</p>
<p>이번 포스트는 Golang의 가장 기본적인 표준 라이브러리인 fmt 패키지를 다루겠습니다.</p>
<p>📌 <strong>fmt package</strong>
  &emsp;&emsp;📍 변수
  &emsp;&emsp;📍 변수의 출력
  &emsp;&emsp;📍 변수의 입력
  &emsp;&emsp;📍 문자열 반환</p>
<p>📌 <strong>연산자</strong>
  &emsp;&emsp;📍 사칙 연산
  &emsp;&emsp;📍 비트 연산
  &emsp;&emsp;📍 시프트 연산</p>
<h1 id="📌-fmt-package">📌 <a href="https://pkg.go.dev/fmt">fmt package</a></h1>
<p>fmt는 Formatted I/O(Input / Output)를 구현한 패키지입니다.
C언어의 표준 입출력 라이브러리인 stdio.h의 역할이라고 보시면 됩니다.
fmt 패키지를 import함으로써 입출력을 가능하게 합니다.
처음 Golang 설치를 확인하기 위해 Hello World!!를 출력했었습니다.</p>
<pre><code class="language-go">package main // 해당 프로젝트의 시작점을 알리며, 항상 main으로 존재해야 함

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

func main() { // 해당 패키지의 가장 처음에 실행되는 함수이며, 항상 main으로 존재해야 함
    fmt.Println(&quot;Hello World!!&quot;) // fmt 패키지의 Println 함수를 사용해, Hello World!!를 출력하고 개행함
}</code></pre>
<p>이와 같이 fmt 패키지는 입력과 출력을 가능하게 하며 모든 실행의 결과물을 출력할 수 있게 됩니다. 
컴퓨터의 연산과 계산 결과 등을 출력할 수 있습니다.
1 + 1의 결과 2를 결과물로 볼 수 있게 되는 것입니다.
딱히 하나도 놀라울 것이 없지만 fmt 패키지가 없다면 우리는 우리의 코드가 제대로 작성된 것인지 파악하기가 굉장히 어려울 것입니다.</p>
<hr>
<h2 id="📍-변수">📍 변수</h2>
<p>변수는 변하는 수를 의미합니다.
상황에 따라서 값을 변경함으로써 프로그램의 동작을 구현하고 제어합니다.
<img src="https://images.velog.io/images/vamos_eon/post/ded47a6b-bd51-4bc1-9a22-a7ba5a1c4fde/variable_box.png" alt="">
변수는 메모리에 저장됩니다.
그리고 변수를 통해, 값이 담긴 주소을 알 수 있고, 그 주소에 저장된 값을 불러올 수 있습니다.</p>
<h3 id="fmt-docs--포맷-지정자-d-f-s-"><a href="https://pkg.go.dev/fmt#hdr-Printing">fmt docs : 포맷 지정자 (%d, %f, %s ...)</a></h3>
<p>출력 값의 자릿수를 지정할 수 있습니다. 자릿수를 넘어가면 값 그대로를 출력합니다.
%5d : 정수형 값을 5자리로 출력합니다. 5자리보다 작은 값인 경우, 공백으로 채웁니다.
%05d : 정수형 값을 5자리로 출력합니다. 5자리보다 작은 값인 경우, 0으로 5자리를 맞춥니다. 
등 여러 가지 포맷 지정 방법이 있습니다.
아래에서 설명할 때 등장하는 포맷 지정자는 다음과 같습니다. :
%d, %s, %c, %08b</p>
<hr>
<h2 id="📍-변수의-출력">📍 변수의 출력</h2>
<p>모든 변수는 값을 가지며, 그 값은 출력할 수 있습니다.
변수의 타입마다 출력할 수 있는 방식이 정해져 있고, 그 방식은 한 가지로 국한되지 않습니다.
본인이 원하는 대로 출력하기 위해서는 아래에 설명하는 출력 방식을 모두 사용할 수 있어야 합니다.</p>
<h3 id="기본-출력-함수-func-print">기본 출력 함수 func Print</h3>
<pre><code class="language-go">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.</code></pre>
<p>Printf() 함수는 출력 시에 변수의 기본 타입에 맞추어 출력합니다.
공백을 출력하고 싶다면 string 타입에 맞추어 &quot; &quot;로 표현해야 합니다.
인자를 함수에 넣을 때, 공백을 넣더라도 string 타입으로 넣지 않으면 아무 의미 없습니다.</p>
<pre><code class="language-go">package main

import &quot;fmt&quot;

func main() {
    var x int8 = 5
    var y int8 = 3
    fmt.Print(&quot;x is &quot;, x, &quot;\n&quot;, &quot;y&quot;, &quot;is&quot;, y, &quot;\n&quot;)
}
// x is 5
// yis3</code></pre>
<hr>
<h3 id="공백-포함-출력-함수-func-println">공백 포함 출력 함수 func Println</h3>
<pre><code class="language-go">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.</code></pre>
<p>Println() 함수는 Print() 함수와 다르게 각 인자 사이에 공백 string을 추가하며, 줄 끝에 개행을 덧붙입니다.
아래의 Printf() 함수와 더불어 가장 많이 사용하는 함수입니다.</p>
<pre><code class="language-go">package main

import &quot;fmt&quot;

func main() {
    var x int8 = 5
    var y int8 = 3
    fmt.Println(&quot;x is &quot;, x, &quot;\n&quot;, &quot;y&quot;, &quot;is&quot;, y)
}
// x is  5 
//  y is 3
// **&quot; y is 3&quot; : y앞에 공백 하나가 포함돼 있는 것이 맞습니다.</code></pre>
<hr>
<h3 id="포맷-지정-출력-함수-func-printf">포맷 지정 출력 함수 func Printf</h3>
<pre><code class="language-go">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.</code></pre>
<p>변수의 출력을 특정한 형식으로 지정하여 출력할 수 있습니다.
표준 입출력 fmt 패키지에서는 Printf() 함수만이 가능합니다.
<strong><a href="https://pkg.go.dev/fmt#Printf">fmt.Printf package docs</a></strong></p>
<p>Printf 함수는 string 타입으로 format을 지정하고 표준 출력을 수행합니다.
함수 동작이 끝나면 byte 수와 에러를 반환합니다.
Hello World!!를 출력하고 그에 대한 결과를 보겠습니다.</p>
<pre><code class="language-go">package main

import &quot;fmt&quot;

func main() {
    fmt.Printf(&quot;%s\n&quot;, &quot;Hello World!!&quot;)
    // 포맷 지정자를 string으로 정하고 개행문자를 포함했습니다.
    // %s에 들어갈 값으로 &quot;Hello World!!&quot;를 넣습니다.

    fmt.Printf(&quot;Hello World!!\n&quot;)
    // 출력할 포맷을 제외하고 문자열 출력만 하도록 간소화했습니다.
    // Printf는 기본 string 타입을 받기 때문에 string 타입은 포맷 지정 없이도 사용이 가능합니다.

    var s string = &quot;hi\n&quot;
    fmt.Printf(s)
    // hi를 출력하고 개행을 합니다.
    // 단, 변수를 포맷 지정 없이 Printf로 출력하는 것은 권장되지 않습니다. (go-staticcheck)
    // 에러는 발생하지 않으며 정상 출력됩니다. 다만 가능할 뿐, 이런 사용 방식은 지양합니다.

    xByte, err := fmt.Printf(&quot;Hello World!!\n&quot;)
    fmt.Printf(&quot;The value of xByte : %d\nError : %v\n&quot;, xByte, err)
    // The value of xByte : 14
    // Error : &lt;nil&gt;
    // string은 글자 하나가 8bit, 1byte로 저장되기 때문에 &quot;Hello World!!&quot;에 대한 Byte 수는 13이 됩니다.
    // \n 개행 문자도 1byte입니다.
}</code></pre>
<hr>
<h2 id="📍-변수의-입력">📍 변수의 입력</h2>
<p>모든 프로그램의 동작은 변수의 제어를 통해 이루어집니다.
변수를 제어하기 위해서는 값을 부여하고 연산을 해야 합니다.
그리고 유동적으로 프로그램을 동작시키기 위해, 적절한 곳에서 변수에 값을 &#39;입력&#39;해야 하기도 합니다.</p>
<h3 id="기본-입력-함수-func-scan">기본 입력 함수 func Scan</h3>
<pre><code class="language-go">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.</code></pre>
<p>Scan()함수는 값을 입력 받아, 변수에 저장합니다.
변수 간의 구분은 입력 시의 공백이나 개행으로 이루어집니다.</p>
<pre><code class="language-go">package main // Hello World!!를 입력 및 출력합니다.

import &quot;fmt&quot;

func main() {
    var x string
    var y string
    fmt.Scan(&amp;x, &amp;y) // string 두 개의 값을 입력 받아, 각각 x와 y에 저장합니다.
    // Hello World!! 
    // 또는
    // Hello
    // World!!
    // z, _ := fmt.Scan(&amp;x, &amp;y) // 인자의 개수를 z에 초기화합니다.
    fmt.Println(x, &quot;and&quot;, y)
    // Hello and World!!
    // fmt.Println(z)
}</code></pre>
<hr>
<h3 id="개행-종료-입력-함수-func-scanln">개행 종료 입력 함수 func Scanln</h3>
<pre><code class="language-go">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.</code></pre>
<p>Scanln()함수는 값을 입력 받을 때, 공백으로만 구분합니다.
성공적으로 입력된 인자의 개수를 반환하며, 에러가 있을 시 에러를 같이 반환합니다.
개행을 하거나 EOF이 오면 종료됩니다.</p>
<pre><code class="language-go">package main // Hello World!!를 입력 및 출력합니다.

import &quot;fmt&quot;

func main() {
    var x string
    var y string
    var z string
    fmt.Scanln(&amp;x, &amp;y, &amp;z) // string 세 개의 값을 입력 받아, 각각 x, y, z에 저장합니다.
    // Hello and World!!
    // Hello and `New line 또는 Ctrl + D`
    fmt.Println(x, y, z)
    // Hello and World!!
    // Hello and
}</code></pre>
<hr>
<h3 id="포맷-지정-입력-함수-func-scanf">포맷 지정 입력 함수 func Scanf</h3>
<pre><code class="language-go">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.</code></pre>
<p>Scanf()함수는 값을 입력 받을 때, 형식에 맞는 인자를 받아서 값을 저장합니다.
성공적으로 입력된 인자의 개수를 반환하며, 에러가 있을 시 에러를 같이 반환합니다.
입력 값에 개행을 사용하려면 포맷에도 개행 문자를 알맞는 자리에 명시해야 합니다.
그렇지 않은 경우, 개행을 하거나 EOF이 오면 종료됩니다.
한 가지 예외 : %c (rune 타입)은 언제나 모든 rune을 입력 값으로 받기 때문에, %c로 값을 전달 받을 때, space나 \t, \n 등이 오더라도 모두 rune 타입 문자로 취급됩니다.</p>
<pre><code class="language-go">package main // Hello World!!를 입력 및 출력합니다.

import &quot;fmt&quot;

func main() {
    var x int8
    var y int8
    var z rune
    fmt.Scanf(&quot;%d%c\n%d&quot;, &amp;x, &amp;z, &amp;y) 
    // 정수 x 입력, rune 타입 &#39;space 공백&#39; 입력,  \n 포맷에 맞추어 개행, 정수 y입력
    // 5 
    // 3
    fmt.Printf(&quot;%c&quot;, z) // 입력 마무리로 현위치, rune 타입 z 출력으로 space 공백 출력
    //  
    fmt.Println(x + y) // x + y 값 출력
    //  8    
}
// 5
// 3
//  8
// **(결과로 한 칸 뒤에 8이 출력되는 게 맞습니다.)</code></pre>
<hr>
<h2 id="📍-문자열-반환">📍 문자열 반환</h2>
<h3 id="문자열-반환-함수-func-sprint-sprintf-sprintln">문자열 반환 함수 func Sprint, Sprintf, Sprintln</h3>
<pre><code class="language-go">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.</code></pre>
<pre><code class="language-go">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.</code></pre>
<pre><code class="language-go">func Sprintf(format string, a ...interface{}) string
// Sprintf formats according to a format specifier and returns the resulting string.</code></pre>
<p>Sprint()함수는 Print() 함수가 출력하는 결과를 string 타입으로 반환합니다.
Sprintln()함수는 Println() 함수를, Sprintf()함수는 Printf()함수로 출력되는 결과를 string 타입으로 반환합니다.</p>
<pre><code class="language-go">package main

import &quot;fmt&quot;

func main() {
    const name, age = &quot;Kim&quot;, 22
    s := fmt.Sprint(name, &quot; is &quot;, age, &quot; years old.\n&quot;)
    fmt.Println(s)
    // Kim is age 22 years old.
}</code></pre>
<hr>
<h1 id="📌-연산자">📌 연산자</h1>
<p>Golang에서 지원하는 연산자는 아래와 같습니다.</p>
<h2 id="📍-산술-연산자">📍 산술 연산자</h2>
<table>
<thead>
<tr>
<th align="left">구분</th>
<th>연산자</th>
<th>연산</th>
<th>피연산자 타입</th>
</tr>
</thead>
<tbody><tr>
<td align="left"><strong>사칙 연산과 나머지</strong></td>
<td>+</td>
<td>덧셈</td>
<td>정수, 실수, 복소수, 문자열</td>
</tr>
<tr>
<td align="left"></td>
<td>-</td>
<td>뺄셈</td>
<td>정수, 실수, 복소수</td>
</tr>
<tr>
<td align="left"></td>
<td>*</td>
<td>곱셈</td>
<td>정수, 실수, 복소수</td>
</tr>
<tr>
<td align="left"></td>
<td>/</td>
<td>나눗셈</td>
<td>정수, 실수, 복소수</td>
</tr>
<tr>
<td align="left"></td>
<td>%</td>
<td>나머지</td>
<td>정수</td>
</tr>
<tr>
<td align="left"><strong>비트 연산</strong></td>
<td>&amp;</td>
<td>AND 비트 연산</td>
<td>정수</td>
</tr>
<tr>
<td align="left"></td>
<td>|</td>
<td>OR 비트 연산</td>
<td>정수</td>
</tr>
<tr>
<td align="left"></td>
<td>^</td>
<td>XOR 비트 연산</td>
<td>정수</td>
</tr>
<tr>
<td align="left"></td>
<td>&amp;^</td>
<td>비트 클리어</td>
<td>정수</td>
</tr>
<tr>
<td align="left"><strong>시프트 연산</strong></td>
<td>&lt;&lt;</td>
<td>왼쪽 시프트</td>
<td>정수 &lt;&lt; 양의 정수</td>
</tr>
<tr>
<td align="left"></td>
<td>&gt;&gt;</td>
<td>오른쪽 시프트</td>
<td>정수 &gt;&gt; 양의 정수</td>
</tr>
</tbody></table>
<h3 id="연산자-modular-arithmetic">%연산자 (modular arithmetic)</h3>
<p><strong>%연산은 modular arithmetic이라고 하며, 나머지 연산이라고도 합니다. 결과 값은 나눗셈의 나머지입니다.</strong>
5 % 3 연산의 결과는 5을 3로 나눈 나머지 2가 결과 값이 됩니다.</p>
<pre><code class="language-go">package main

import &quot;fmt&quot;

func main() {
    var a int8 = 5 // 변수 a를 int8, 8비트 정수로 선언하고 5(으)로 초기화합니다.
    var b int8 = 3 // 변수 b를 int8, 8비트 정수로 선언하고 3(으)로 초기화합니다.
    fmt.Printf(&quot;%d\n&quot;, a % b) // a % b 연산을 한 결과를 출력합니다.
} // 2</code></pre>
<p><strong>%연산자를 다른 산술 연산자로 바꾸어 출력할 수 있습니다.</strong></p>
<hr>
<h2 id="📍-비트-연산자">📍 비트 연산자</h2>
<h3 id="비트-연산자--and">비트 연산자 &amp; (AND)</h3>
<p>비트 연산자는 비트 단위의 연산에 사용합니다.</p>
<table>
<thead>
<tr>
<th align="center">A</th>
<th align="center">B</th>
<th align="center">A &amp; B</th>
</tr>
</thead>
<tbody><tr>
<td align="center">0</td>
<td align="center">0</td>
<td align="center">0</td>
</tr>
<tr>
<td align="center">1</td>
<td align="center">0</td>
<td align="center">0</td>
</tr>
<tr>
<td align="center">0</td>
<td align="center">1</td>
<td align="center">0</td>
</tr>
<tr>
<td align="center">1</td>
<td align="center">1</td>
<td align="center">1</td>
</tr>
<tr>
<td align="center">위와 같이 A와 B 모두가 1인 경우에만 A &amp; B의 결과 값이 1이 되는 것을 볼 수 있습니다.</td>
<td align="center"></td>
<td align="center"></td>
</tr>
<tr>
<td align="center">1byte = 8bit 이므로,</td>
<td align="center"></td>
<td align="center"></td>
</tr>
<tr>
<td align="center">십진수 5를 8비트 표기법으로 이진수로 나타내면 00000101이 됩니다.</td>
<td align="center"></td>
<td align="center"></td>
</tr>
<tr>
<td align="center">십진수 3을 8비트 표기법으로 이진수로 나타내면 00000011이 됩니다.</td>
<td align="center"></td>
<td align="center"></td>
</tr>
<tr>
<td align="center">5 &amp; 3 의 연산은 아래와 같이 진행됩니다.</td>
<td align="center"></td>
<td align="center"></td>
</tr>
<tr>
<td align="center">&nbsp; &nbsp; &emsp; 0 0 0 0 0 1 0 <strong>1</strong></td>
<td align="center"></td>
<td align="center"></td>
</tr>
<tr>
<td align="center">&nbsp; &nbsp; &nbsp;&amp; 0 0 0 0 0 0 1 <strong>1</strong></td>
<td align="center"></td>
<td align="center"></td>
</tr>
<tr>
<td align="center">ㅡㅡㅡㅡㅡㅡㅡㅡㅡ</td>
<td align="center"></td>
<td align="center"></td>
</tr>
<tr>
<td align="center">&nbsp; &nbsp; &emsp; 0 0 0 0 0 0 0 <strong>1</strong></td>
<td align="center"></td>
<td align="center"></td>
</tr>
</tbody></table>
<p>이렇게 첫 번째 자리만 모두 1이어서 결과 값으로 1이 출력됩니다.</p>
<p>코드로 작성하면 아래와 같습니다.</p>
<pre><code class="language-go">package main

import &quot;fmt&quot;

func main() {
    var a int8 = 5 // 변수 a를 int8, 8비트 정수로 선언하고 5(으)로 초기화합니다.
    var b int8 = 3 // 변수 b를 int8, 8비트 정수로 선언하고 3(으)로 초기화합니다.
    fmt.Printf(&quot;%08b\n&quot;, a &amp; b) // a &amp; b 연산을 한 결과를 출력합니다.
} // 00000001</code></pre>
<hr>
<h3 id="비트-연산자--or">비트 연산자 | (OR)</h3>
<p>비트 연산자는 비트 단위의 연산에 사용합니다.</p>
<table>
<thead>
<tr>
<th align="center">A</th>
<th align="center">B</th>
<th align="center">A | B</th>
</tr>
</thead>
<tbody><tr>
<td align="center">0</td>
<td align="center">0</td>
<td align="center">0</td>
</tr>
<tr>
<td align="center">1</td>
<td align="center">0</td>
<td align="center">1</td>
</tr>
<tr>
<td align="center">0</td>
<td align="center">1</td>
<td align="center">1</td>
</tr>
<tr>
<td align="center">1</td>
<td align="center">1</td>
<td align="center">1</td>
</tr>
</tbody></table>
<p>위와 같이 A와 B 둘 중 하나가 1인 경우에는 A | B의 결과 값이 1이 되는 것을 볼 수 있습니다.
마찬가지로 십진수 5와 3을 통해 연산을 진행합니다.
5 | 3 의 연산은 아래와 같이 진행됩니다.
&nbsp; &nbsp; &emsp; 0 0 0 0 0 <strong>1</strong> 0 <strong>1</strong>
&nbsp; &nbsp; &nbsp; | 0 0 0 0 0 0 <strong>1</strong> <strong>1</strong>
ㅡㅡㅡㅡㅡㅡㅡㅡㅡ
&nbsp; &nbsp; &emsp; 0 0 0 0 0 <strong>1</strong> <strong>1</strong> <strong>1</strong></p>
<p>이렇게 둘 중 하나가 1이어서 결과 값으로 111이 출력됩니다.</p>
<p>코드로 작성하면 아래와 같습니다.</p>
<pre><code class="language-go">package main

import &quot;fmt&quot;

func main() {
    var a int8 = 5 // 변수 a를 int8, 8비트 정수로 선언하고 5(으)로 초기화합니다.
    var b int8 = 3 // 변수 b를 int8, 8비트 정수로 선언하고 3(으)로 초기화합니다.
    fmt.Printf(&quot;%08b\n&quot;, a | b) // a | b 연산을 한 결과를 출력합니다.
} // 00000111</code></pre>
<hr>
<h3 id="비트-연산자--xor">비트 연산자 ^ (XOR)</h3>
<p>비트 연산자는 비트 단위의 연산에 사용합니다.</p>
<table>
<thead>
<tr>
<th align="center">A</th>
<th align="center">B</th>
<th align="center">A ^ B</th>
</tr>
</thead>
<tbody><tr>
<td align="center">0</td>
<td align="center">0</td>
<td align="center">0</td>
</tr>
<tr>
<td align="center">1</td>
<td align="center">0</td>
<td align="center">1</td>
</tr>
<tr>
<td align="center">0</td>
<td align="center">1</td>
<td align="center">1</td>
</tr>
<tr>
<td align="center">1</td>
<td align="center">1</td>
<td align="center">0</td>
</tr>
</tbody></table>
<p>위와 같이 A와 B 둘 중 하나만 1인 경우에만 A ^ B의 결과 값이 1이 되는 것을 볼 수 있습니다.
마찬가지로 십진수 5와 3을 통해 연산을 진행합니다.
5 ^ 3 의 연산은 아래와 같이 진행됩니다.
&nbsp; &nbsp; &emsp; 0 0 0 0 0 <strong>1</strong> 0 <strong>1</strong>
&nbsp; &nbsp; &nbsp;^ 0 0 0 0 0 0 <strong>1</strong> <strong>1</strong>
ㅡㅡㅡㅡㅡㅡㅡㅡㅡ
&nbsp; &nbsp; &emsp; 0 0 0 0 0 <strong>1</strong> <strong>1</strong> <strong>0</strong></p>
<p>이렇게 둘 중 하나가 1이어서 결과 값으로 110이 출력됩니다.</p>
<p>코드로 작성하면 아래와 같습니다.</p>
<pre><code class="language-go">package main

import &quot;fmt&quot;

func main() {
    var a int8 = 5 // 변수 a를 int8, 8비트 정수로 선언하고 5(으)로 초기화합니다.
    var b int8 = 3 // 변수 b를 int8, 8비트 정수로 선언하고 3(으)로 초기화합니다.
    fmt.Printf(&quot;%08b\n&quot;, a ^ b) // a ^ b 연산을 한 결과를 출력합니다.
} // 00000110</code></pre>
<p>Golang에서는 비트 단위 연산에 NOT 연산이 없습니다.
그렇기 때문에 비트 반전이 필요한 경우, XOR -&gt; ^ 비트 연산자를 사용합니다.</p>
<hr>
<h3 id="비트-연산자--비트-클리어">비트 연산자 &amp;^ (비트 클리어)</h3>
<p>비트 연산자는 비트 단위의 연산에 사용합니다.</p>
<table>
<thead>
<tr>
<th align="center">A</th>
<th align="center">B</th>
<th align="center">A &amp;^ B</th>
</tr>
</thead>
<tbody><tr>
<td align="center">0</td>
<td align="center">0</td>
<td align="center">0</td>
</tr>
<tr>
<td align="center">1</td>
<td align="center">0</td>
<td align="center">1</td>
</tr>
<tr>
<td align="center">0</td>
<td align="center">1</td>
<td align="center">0</td>
</tr>
<tr>
<td align="center">1</td>
<td align="center">1</td>
<td align="center">0</td>
</tr>
</tbody></table>
<p>위와 같이 A가 1이고 B가 0인 경우에만 A &amp;^ B의 결과 값이 1이 되는 것을 볼 수 있습니다.
마찬가지로 십진수 5와 3을 통해 연산을 진행합니다.
5 &amp;^ 3 의 연산은 아래와 같이 진행됩니다.
&nbsp; &nbsp; &emsp; 0 0 0 0 0 <strong>1</strong> 0 <strong>1</strong>
&nbsp; &amp;&amp;^ 0 0 0 0 0 0 <strong>1</strong> <strong>1</strong>
ㅡㅡㅡㅡㅡㅡㅡㅡㅡ
&nbsp; &nbsp; &emsp; 0 0 0 0 0 <strong>1</strong> <strong>0</strong> <strong>0</strong></p>
<p>이렇게 둘 중 하나가 1이어서 결과 값으로 100이 출력됩니다.
비트 클리어 연산은 &amp;와 ^가 결합된 형태입니다.
A가 1이면서, &amp; 그리고 ^둘 중 하나만 1인 경우 즉, B가 0인 경우를 의미합니다.</p>
<p>코드로 작성하면 아래와 같습니다.</p>
<pre><code class="language-go">package main

import &quot;fmt&quot;

func main() {
    var a int8 = 5 // 변수 a를 int8, 8비트 정수로 선언하고 5(으)로 초기화합니다.
    var b int8 = 3 // 변수 b를 int8, 8비트 정수로 선언하고 3(으)로 초기화합니다.
    fmt.Printf(&quot;%08b\n&quot;, a &amp;^ b) // a &amp;^ b 연산을 한 결과를 출력합니다.
} // 00000100</code></pre>
<hr>
<h2 id="📍-시프트-연산자">📍 시프트 연산자</h2>
<h3 id="시프트-연산자--왼쪽-시프트">시프트 연산자 &lt;&lt; (왼쪽 시프트)</h3>
<p>시프트 연산자는 비트 단위의 연산에 사용합니다.</p>
<p>마찬가지로 십진수 5와 3을 통해 연산을 진행합니다.
5 &lt;&lt; 3 의 연산은 아래와 같이 진행됩니다.
&nbsp; &nbsp; &emsp; 0 0 0 0 0 <strong>1</strong> 0 <strong>1</strong>
&nbsp;&nbsp;&lt;&lt; 0 0 0 0 0 0 <strong>1</strong> <strong>1</strong>
ㅡㅡㅡㅡㅡㅡㅡㅡㅡ
&nbsp; &nbsp; &emsp; 0 0 <strong>1</strong> <strong>0</strong> <strong>1</strong> <strong>0 0 0</strong></p>
<p>시프트 연산은 위와 같이 비트 자릿수로 하는 연산이 아닙니다.
바로 위에 작성한 계산법은 아무 의미가 없습니다.
5 &lt;&lt; 3은 이진수 5의 비트 자릿수를 3만큼 왼쪽으로 옮기는 연산입니다.
직접 해보면 아래와 같습니다.</p>
<p>0 0 0 0 0 <strong>1 0 1</strong>
0 0 <strong>1 0 1</strong> 0 0 0</p>
<p>그리고 이 값을 십진수로 표현하면 40이 됩니다.</p>
<p>코드로 작성하면 아래와 같습니다.</p>
<pre><code class="language-go">package main

import &quot;fmt&quot;

func main() {
    var a int8 = 5 // 변수 a를 int8, 8비트 정수로 선언하고 5(으)로 초기화합니다.
    var b int8 = 3 // 변수 b를 int8, 8비트 정수로 선언하고 3(으)로 초기화합니다.
    fmt.Printf(&quot;%08b\n&quot;, a &lt;&lt; b) // a &lt;&lt; b 연산을 한 결과를 출력합니다.
} // 00101000</code></pre>
<hr>
<h3 id="시프트-연산자--오른쪽-시프트">시프트 연산자 &gt;&gt; (오른쪽 시프트)</h3>
<p>시프트 연산자는 비트 단위의 연산에 사용합니다.</p>
<p>마찬가지로 십진수 5와 3을 통해 연산을 진행합니다.
5 &gt;&gt; 3 의 연산은 아래와 같이 진행됩니다.
&nbsp; &nbsp; &emsp; 0 0 0 0 0 <strong>1</strong> 0 <strong>1</strong>
&nbsp;&nbsp;&gt;&gt; 0 0 0 0 0 0 <strong>1</strong> <strong>1</strong>
ㅡㅡㅡㅡㅡㅡㅡㅡㅡ
&nbsp; &nbsp; &emsp; 0 0 0 0 0 <strong>0 0 0</strong></p>
<p>시프트 연산은 위와 같이 비트 자릿수로 하는 연산이 아닙니다.
바로 위에 작성한 계산법은 아무 의미가 없습니다.
5 &gt;&gt; 3은 이진수 5의 비트 자릿수를 3만큼 오른쪽으로 옮기는 연산입니다.
직접 해보면 아래와 같습니다.</p>
<p>5 &gt;&gt; 0 : 0 0 0 0 0 <strong>1 0 1</strong>
5 &gt;&gt; 1 : 0 0 0 0 0 <strong>0 1 0</strong>
5 &gt;&gt; 2 : 0 0 0 0 0 <strong>0 0 1</strong>
5 &gt;&gt; 3 : 0 0 0 0 0 <strong>0 0 0</strong></p>
<p>그리고 이 값을 십진수로 표현하면 0이 됩니다.</p>
<p>코드로 작성하면 아래와 같습니다.</p>
<pre><code class="language-go">package main

import &quot;fmt&quot;

func main() {
    var a int8 = 5 // 변수 a를 int8, 8비트 정수로 선언하고 5(으)로 초기화합니다.
    var b int8 = 3 // 변수 b를 int8, 8비트 정수로 선언하고 3(으)로 초기화합니다.
    fmt.Printf(&quot;%08b\n&quot;, a &gt;&gt; b) // a &gt;&gt; b 연산을 한 결과를 출력합니다.
} // 00000000</code></pre>
<hr>
<p>아래 링크는 Golang의 연산자에 대한 스펙 문서입니다.
<strong><a href="https://go.dev/ref/spec#Arithmetic_operators">golang Spec # Arithmetic_operators</a></strong></p>
<hr>
<p>이번 포스트는 Golang의 가장 기본이 되는 &quot;fmt&quot; 패키지에 대한 내용이었습니다.
감사합니다.👍</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Golang 기초 (3) : 변수와 자료형에 대하여
]]></title>
            <link>https://velog.io/@vamos_eon/go-vartypes</link>
            <guid>https://velog.io/@vamos_eon/go-vartypes</guid>
            <pubDate>Thu, 25 Nov 2021 16:58:49 GMT</pubDate>
            <description><![CDATA[<p>안녕하세요, 주니어 개발자 Eon입니다.</p>
<p>이번 포스트는 Golang 변수와 자료형에 대한 내용입니다.
순서는 아래와 같이 진행됩니다.</p>
<ol>
<li>Golang은 강타입 언어이다.<ul>
<li>약타입 언어란</li>
<li>강타입 언어란</li>
</ul>
</li>
<li>Golang의 변수<ul>
<li>자료형의 종류와 그 범위<ul>
<li>rune의 사용처</li>
<li>int와 uint에 대하여</li>
<li>true와 false에 대하여</li>
</ul>
</li>
<li>변수의 초기 값<ul>
<li>변수의 선언</li>
</ul>
</li>
<li>변수의 형변환</li>
</ul>
</li>
</ol>
<h2 id="golang은-강타입-언어이다">Golang은 강타입 언어이다.</h2>
<p>Golang은 강타입 중에도 강타입 언어입니다.
약타입이냐, 강타입이냐에 따라 연산을 할 수 있는 방법이 확장될 수도, 제한이 될 수도 있습니다.
먼저 약타입부터 보겠습니다.</p>
<h3 id="약타입-언어란">약타입 언어란</h3>
<p>프로그래밍 언어에는 타입이 존재합니다. 
그 타입에 따라 표현할 수 있는 값의 종류나 범위가 달라집니다.
약타입 언어는 정수와 실수 간의 계산을 허용하기도 합니다. 
기초 교육 과정의 수학처럼 말입니다.
1 + 0.5 = 1.5 라는 것을 우리는 알고 있습니다.
약타입 언어는 위와 같이 정수와 실수 간의 연산을 허용하기도 합니다.
연산에 있어, 타입이 절대적이지 않게 됩니다. 
또한, &quot;1&quot; + 1 = 2가 언어에 따라 2가 아닌 &quot;11&quot;이나 &quot;2&quot;의 결과가 되기도 합니다. 
이러한 허용을 하게 되면, 해당 언어의 타입에 따른 연산 결과를 정확하게 예측하지 않으면 실수로 이어지고, 이는 곧 에러나 버그로 이어질 수 있습니다.
물론 이를 자유자재로 사용한다면 쓰기 편하다는 장점은 확실합니다.
하지만 높은 자유도가 오히려 독이 될 수도 있다는 것입니다.</p>
<h3 id="강타입-언어란">강타입 언어란</h3>
<p>강타입 언어는 위의 약타입 언어와는 확연히 다르게, 타입이 다른 변수 간의 연산을 허용하지 않습니다.
물론 강타입 언어 중에서도 숫자에 한해서는 연산을 허용하기도 합니다.
정수 + 실수 연산을 허용하는 c언어처럼 말입니다.
강타입 언어의 장점은 타입을 정확하게 인지하고 사용한다는 점입니다.
사용자의 실수로 서로 다른 타입 간의 연산을 하더라도 컴파일 과정을 거치며 에러를 발생시키기 때문에 의도치 않은 변수 간의 연산으로 애를 먹지 않아도 됩니다.
단점으로는 타입에 병적으로 매달려야 한다는 점입니다.
Golang은 강타입 중에도 강타입 언어라고 말씀드렸습니다.
Golang은 서로 다른 타입 간의 연산을 일절 허용하지 않습니다.
따라서, 사용자가 서로 다른 타입 간의 연산을 하기 위해서는 정확하게 인지한 상태로 형변환을 거쳐 연산을 진행합니다.
불편할 수도 있으나, 타입의 확실한 지정으로 에러 발생을 줄일 수 있다는 장점이 있습니다.</p>
<hr>
<h2 id="golang의-변수">Golang의 변수</h2>
<p>저번 포스트에서 Golang으로 Hello World!!를 출력했습니다.
출력하며 기본 틀에 대해서 설명을 같이 작성했습니다.
기본이 되는 틀을 알았으니 본격적으로 프로그래밍을 하기에 앞서, 변수 제어를 위해 변수와 자료형에 대해 알아보도록 합니다.</p>
<h3 id="자료형의-종류와-그-범위">자료형의 종류와 그 범위</h3>
<p>Golang의 기본 타입은 아래와 같습니다.
(signed = 부호가 있는)
(unsigned = 부호가 없는)
(alias type = 별칭 타입; 별명 타입)</p>
<table>
<thead>
<tr>
<th align="center">Type</th>
<th>Size</th>
<th>Range or Value</th>
</tr>
</thead>
<tbody><tr>
<td align="center"><strong>bool</strong></td>
<td>1</td>
<td>true, false</td>
</tr>
<tr>
<td align="center"><strong>int8</strong></td>
<td>signed, 8 bit</td>
<td>-128 ~ 127</td>
</tr>
<tr>
<td align="center"><strong>int16</strong></td>
<td>signed, 16 bit</td>
<td>-32768 ~ 32767</td>
</tr>
<tr>
<td align="center"><strong>int32</strong></td>
<td>signed, 32 bit</td>
<td>-2147483648 ~ 2147483647</td>
</tr>
<tr>
<td align="center"><strong>int64</strong></td>
<td>signed, 64 bit</td>
<td>-9223372036854775808 ~ 9223372036854775807</td>
</tr>
<tr>
<td align="center"><strong>uint8</strong></td>
<td>unsigned, 8 bit</td>
<td>0 ~ 255</td>
</tr>
<tr>
<td align="center"><strong>uint16</strong></td>
<td>unsigned, 16 bit</td>
<td>0 ~ 65535</td>
</tr>
<tr>
<td align="center"><strong>uint32</strong></td>
<td>unsigned, 32 bit</td>
<td>0 ~ 4294967295</td>
</tr>
<tr>
<td align="center"><strong>uint64</strong></td>
<td>unsigned, 64 bit</td>
<td>0 ~ 18446744073709551615</td>
</tr>
<tr>
<td align="center"><strong>float32</strong></td>
<td>signed, 32 bit</td>
<td></td>
</tr>
<tr>
<td align="center"><strong>float64</strong></td>
<td>signed, 64 bit</td>
<td></td>
</tr>
<tr>
<td align="center"><strong>int</strong></td>
<td>signed, either 32 or 64 bits</td>
<td>check int32 and int64</td>
</tr>
<tr>
<td align="center"><strong>uint</strong></td>
<td>unsigned, either 32 or 64 bits</td>
<td>check uint32 and uint64</td>
</tr>
<tr>
<td align="center"><strong>uintptr</strong></td>
<td>unsigned, same as uint</td>
<td>the bit pattern of any pointer</td>
</tr>
<tr>
<td align="center"><strong>byte</strong></td>
<td><strong>alias type of uint8;</strong> unsigned, same as uint8</td>
<td>0 ~ 255</td>
</tr>
<tr>
<td align="center"><strong>rune</strong></td>
<td><strong>alias type of int32;</strong> signed, same as int32</td>
<td>-2147483648 ~ 2147483647</td>
</tr>
<tr>
<td align="center"><strong>string</strong></td>
<td>string is the set of all strings of 8 bit bytes</td>
<td>may be empty, but not nil; e.g: &quot;hello&quot; or &quot;&quot; ...</td>
</tr>
<tr>
<td align="center"><strong>complex64</strong></td>
<td>float 32 bit for real number and another float 32 bit for imaginary number</td>
<td>e.g.: 3 + 5i</td>
</tr>
<tr>
<td align="center"><strong>complex128</strong></td>
<td>float 64 bit for real number and another float 64 bit for imaginary number</td>
<td>e.g.: 3 + 5i</td>
</tr>
</tbody></table>
<h3 id="float가-보장하는-소수점">float가 보장하는 소수점</h3>
<p><img src="https://images.velog.io/images/vamos_eon/post/f50c1f2f-6aa8-4ec7-a043-22a1be6ae0c3/image.png" alt=""></p>
<p>아래의 이미지는 float32에 대한 설명입니다.
float32는 소수점 이하 7자리까지 보장됩니다.
float64는 소수점 이하 15자리까지 보장됩니다.
<img src="https://images.velog.io/images/vamos_eon/post/dbb0ea02-94b9-4b63-b06d-088096d4defb/image.png" alt=""></p>
<h3 id="rune의-사용처">rune의 사용처</h3>
<p>int32의 alias type인 rune은 utf-8의 문자를 표현할 수 있습니다.
물론 int32로도 utf-8 문자 표현이 가능합니다.</p>
<blockquote>
</blockquote>
<pre><code class="language-go">var runeInitialized rune = &#39;💕&#39;
fmt.Printf(&quot;%c\n&quot;, runeInitialized)
// 💕</code></pre>
<h3 id="int와-uint에-대하여">int와 uint에 대하여</h3>
<p>위의 표를 보시면 int와 uint는 32 bit 자료형이 될 수도, 64 bit 자료형이 될 수도 있다고 쓰여있습니다.
C언어를 예로 들자면 int는 항상 4 byte의 크기를 가집니다.
즉, 32 bit의 크기를 가집니다.
C언어는 실행되는 OS의 레지스터가 32 bit인지 64 bit인지를 따지지 않고 int에 32 bit를 할당합니다.
Golang은 OS의 레지스터에 따라 int의 사이즈가 달라집니다.
32 bit OS에서는 int == int32
64 bit OS에서는 int == int64
uint 또한 마찬가지입니다.
32 bit와 64 bit는 표현할 수 있는 값의 범위차가 상당히 큽니다.
그래서 항상 OS의 환경이 다를 수 있음을 염두에 두고 프로그래밍을 해야 합니다.
alias 타입과 int, uint는 사용에 유의해야 합니다.</p>
<h3 id="true와-false에-대하여">true와 false에 대하여</h3>
<p>C언어에서 stdbool.h 표준 라이브러리를 사용하면 bool type의 value인 true, false를 사용할 수 있습니다.
C언어에서 true는 1, false는 0의 값을 가집니다.</p>
<p>Golang에서는 이 두 가지의 참과 거짓을 나타내는 true, false의 값이 각각 1과 0의 값을 가지지 않습니다.
<strong>Golang에서 true와 false는 boolean value</strong>일 뿐입니다. </p>
<blockquote>
</blockquote>
<pre><code class="language-go">if true == 1 {
    fmt.Println(&quot;value of true is 1&quot;)
} // error would be occurred</code></pre>
<hr>
<h2 id="변수의-초기-값">변수의 초기 값</h2>
<p>모든 변수는 선언과 동시에 기본 값으로 초기화됩니다.
선언하면서 변수에 값을 대입할 수도 있지만 값을 주지 않으면 default 값을 가집니다.
아래는 각 자료형에 대한 초기 값에 대한 표입니다.</p>
<table>
<thead>
<tr>
<th align="center">Type</th>
<th>default value</th>
</tr>
</thead>
<tbody><tr>
<td align="center"><strong>bool</strong></td>
<td>false</td>
</tr>
<tr>
<td align="center"><strong>All numeric types</strong></td>
<td>0</td>
</tr>
<tr>
<td align="center"><strong>complex type</strong></td>
<td>(0+0i)</td>
</tr>
<tr>
<td align="center"><strong>uintptr</strong></td>
<td>0</td>
</tr>
<tr>
<td align="center"><strong>byte</strong></td>
<td><strong>alias type of uint8;</strong> 0</td>
</tr>
<tr>
<td align="center"><strong>rune</strong></td>
<td><strong>alias type of int32;</strong> 0</td>
</tr>
<tr>
<td align="center"><strong>string</strong></td>
<td>&quot;&quot;</td>
</tr>
</tbody></table>
<h3 id="변수의-선언">변수의 선언</h3>
<p>변수가 어떤 값을 가지게 하고 싶다면 먼저 변수를 선언해야 합니다.
변수의 선언 방법은 아래와 같습니다.</p>
<blockquote>
</blockquote>
<p>var 변수명 타입명 (= 값, optional) (변수 선언하며 초기화)</p>
<pre><code class="language-go">var nameOfVariable variableType = value </code></pre>
<hr>
<p>타입을 지정하며 선언 후 초기화한다.</p>
<pre><code class="language-go">var nameOfVariable variableType // let value of nameOfVariable as it&#39;s default value 
nameOfVariable = value</code></pre>
<hr>
<p>타입을 지정없이 선언하며 초기화 : 값에 따라 타입이 자동으로 설정된다. (단, default 값에 유의해야 함. 의도치 않은 결과가 나올 수 있음.)</p>
<pre><code class="language-go">nameOfVariable := value</code></pre>
<hr>
<p>var 변수명 타입명 (= 값, optional) (여러 개의 변수 동시 선언 및 초기화)</p>
<pre><code class="language-go">var c, python, java = true, false, &quot;no!&quot;</code></pre>
<hr>
<h2 id="변수의-형변환">변수의 형변환</h2>
<p>변수의 형변환은 심심치 않게 필요한 경우들이 생깁니다.
가령 개발을 하다가 A에서 B로 데이터의 이동이 필요한데, B에서는 string 타입이 필요한데 A에서는 해당 데이터를 int 형태로 가공해서 사용한 케이스가 있을 수 있습니다.
그렇게 되면 A에서 string으로 변환해서 B에 전달을 하거나, A가 int 형태로 B에 전달해서 B가 string으로 가공해서 사용하는 방법이 있습니다.
둘 중 어느 케이스든 형변환은 무조건적으로 필요하게 됩니다.</p>
<p>Golang에서의 형변환은 아래와 같은 방법으로 이루어집니다.</p>
<blockquote>
</blockquote>
<pre><code class="language-go">TypeThatWannaBe(ValueThatWillBeConverted)
// e.g.: 
var ValueThatWillBeConverted int8
var TypeUint64Variable uint64
... // now ValueThatWillBeConverted has value..
TypeUint64Variable = uint64(ValueThatWillBeConverted)</code></pre>
<p>Numeric Type &lt;---&gt; string 은 strconv 패키지를 import하여 수행할 수 있다.
<strong><a href="https://pkg.go.dev/strconv">Golang strconv docs</a></strong></p>
<hr>
<p>이번 포스트에서는 Golang의 변수와 자료형에 대한 내용이었습니다.
프로그램을 제어하기 위해서는 변수의 활용이 필수적입니다.
최대한 자세하게 다룬다고 다룬 것인데 충분할지는 모르겠습니다.
기회가 된다면 나중에 보충하거나 새로 다시 포스팅하도록 하겠습니다.
감사합니다.👍</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Golang 기초 (2) : 설치 및 개발 환경 설정]]></title>
            <link>https://velog.io/@vamos_eon/go-settings</link>
            <guid>https://velog.io/@vamos_eon/go-settings</guid>
            <pubDate>Thu, 18 Nov 2021 16:58:36 GMT</pubDate>
            <description><![CDATA[<p>안녕하세요, 주니어 개발자 Eon입니다.</p>
<p>이번 포스트는 Golang 개발환경 세팅입니다.</p>
<p>환경은 ubuntu 18.04 LTS입니다.
순서는 다음과 같이 진행하겠습니다.</p>
<ul>
<li>ubuntu 18.04 LTS에 Golang 설치하기</li>
<li>Golang 환경변수 GOPATH 설정하기</li>
<li>Golang 환경변수 확인</li>
<li>Golang 개발 환경 설정하기</li>
<li>Golang Hello World!! 출력하기</li>
</ul>
<h3 id="golang-installation-document"><a href="https://golang.org/doc/install">Golang installation document</a></h3>
<p>위의 링크에 접속하시면 환경마다의 설치 과정이 소개되어 있습니다.
저는 ubuntu 환경이므로 linux 환경의 순서로 소개하겠습니다.</p>
<p>현재 가장 최신 릴리즈 버전은 <strong>1.17.3</strong>입니다. (현재 2021-11-19, 최신 릴리즈 2021-11-04)
위의 링크에 Download Go for Linux 버튼을 클릭하시면 바로 다운로드가 진행됩니다.
Windows 환경에 Linux 환경의 VM을 사용하는 분이라면 해당 링크를 복사해서 wget으로 다운로드 받으시면 됩니다.</p>
<pre><code class="language-bash">wget https://golang.org/dl/go1.17.3.linux-amd64.tar.gz</code></pre>
<p><img src="https://images.velog.io/images/vamos_eon/post/6b966732-5cb0-46ea-b000-9548e30117be/image.png" alt=""></p>
<p>이후엔 가이드에 나온 것처럼 기존 버전이 있다면 제거를 먼저 진행하고, 다운로드 받은 gzip 압축 파일의 압축 해제를 합니다.</p>
<p><img src="https://images.velog.io/images/vamos_eon/post/1a4388f8-fa46-4f61-ae17-f6583b489655/image.png" alt=""></p>
<pre><code># rm -rf /usr/local/go &amp;&amp; tar -C /usr/local -xzf go1.17.3.linux-amd64.tar.gz</code></pre><p><strong>root가 아니라면 아래와 같이 sudo를 붙여서 진행</strong>하시면 됩니다.</p>
<pre><code>$ sudo rm -rf /usr/local/go &amp;&amp; sudo tar -C /usr/local -xzf go1.17.3.linux-amd64.tar.gz</code></pre><p><img src="https://images.velog.io/images/vamos_eon/post/9bee0895-bfa9-4e20-a761-45e05cf0a371/image.png" alt="">
위와 같이 압축 해제된 go 디렉터리가 새로 생긴 것을 확인했습니다.</p>
<h3 id="golang-환경변수-gopath-설정하기">Golang 환경변수 Gopath 설정하기</h3>
<p>설치가 완료되었으면 환경변수인 GOPATH를 설정함으로써 golang을 사용할 수 있게 해주어야 합니다.</p>
<p><img src="https://images.velog.io/images/vamos_eon/post/004cee5e-0f29-4a19-b54a-0ec87aee85a3/image.png" alt=""></p>
<p>아래의 환경변수 세팅을 <strong>~/.profile</strong>에 추가합니다.</p>
<pre><code class="language-bash">export PATH=$PATH:/usr/local/go/bin</code></pre>
<p>추가가 완료되었으면 변경된 환경변수를 적용하기 위해 아래의 명령어를 실행합니다.
<strong>.profile은 재부팅하면 적용이 되지만 현재 쉘 환경에 바로 적용시키기 위함</strong>입니다.</p>
<pre><code class="language-bash">source ~/.profile</code></pre>
<p><img src="https://images.velog.io/images/vamos_eon/post/7a37d363-f65d-4914-b68b-734d99212699/image.png" alt=""></p>
<h3 id="golang-환경변수-확인">Golang 환경변수 확인</h3>
<p>환경변수 설정이 끝났으면 아래와 같이 go 명령어가 잘 실행되는지 확인합니다.</p>
<pre><code class="language-bash">go version</code></pre>
<p><img src="https://images.velog.io/images/vamos_eon/post/893a2cdc-18e7-49af-8ed5-2f5dbac3c861/image.png" alt=""></p>
<p>아래와 같이 go 환경변수를 모두 확인할 수도 있습니다.</p>
<pre><code class="language-bash">go env</code></pre>
<p><img src="https://images.velog.io/images/vamos_eon/post/3c232598-705d-4853-b330-2087e1bcc42b/image.png" alt=""></p>
<p>위와 같이 모두 문제 없이 잘 설치된 것을 알 수 있습니다.</p>
<h3 id="golang-개발-환경-설정하기">Golang 개발 환경 설정하기</h3>
<p>VScode 상에서 개발 환경 설정은 go extension을 설치하는 것으로 모두 끝납니다.
아래의 extension을 설치하고, 우측 하단에 gopkgs, gopls 등 설치되지 않은 게 있다며 알림이 올라오는데, install All 한 번만 클릭하시면 자동으로 기본적인 tool들을 설치합니다.</p>
<p><img src="https://images.velog.io/images/vamos_eon/post/befe730a-08b2-4def-b807-44564a47efa8/image.png" alt=""></p>
<p><img src="https://images.velog.io/images/vamos_eon/post/0a8167f9-1bbd-41e8-bcaa-2d76cbbb576d/image.png" alt=""></p>
<h3 id="golang-hello-world-출력하기">Golang Hello World!! 출력하기</h3>
<p>먼저, 아래와 같이 디렉터리 경로를 만들고 hello.go 파일을 생성합니다.
<img src="https://images.velog.io/images/vamos_eon/post/dab8577a-2e2d-430a-bee7-b20bb10b5078/image.png" alt=""></p>
<pre><code class="language-bash">mkdir -p ~/goprojects/hello
touch ~/goprojects/hello/hello.go</code></pre>
<p>이제 VScode에서 hello.go 파일을 수정하여 Hello World!!를 출력하겠습니다.
<img src="https://images.velog.io/images/vamos_eon/post/b0ea8a91-a471-4edc-84ea-06fbd1346d6e/image.png" alt=""></p>
<pre><code class="language-go">package main

import &quot;fmt&quot;

func main() {
    fmt.Println(&quot;Hello World!!&quot;)
}</code></pre>
<blockquote>
</blockquote>
<ul>
<li><strong>package main</strong> : hello.go 파일은 <strong>main 패키지임을 알립니다</strong>.</li>
<li><em>main 패키지는 진입점*</em>이며, main 패키지 파일 내에는 리턴이 없는 <strong>main 함수가 필요</strong>합니다.</li>
<li><strong>import &quot;fmt&quot;</strong> : golang standard library 중 <strong>fmt 표준 라이브러리를 포함</strong>합니다.</li>
<li><strong>func main() {...}</strong> : main 함수를 작성합니다. <strong>패키지를 초기화한 후에 가장 먼저 실행되는 함수</strong>입니다.</li>
<li><strong>fmt.Println(&quot;Hello World!!&quot;)</strong> : fmt는 <strong>formatted I/O</strong>으로, C언어의 stdio와 같은 <strong>표준 입출력 라이브러리</strong>입니다. <strong>Println</strong>은 <strong>각각의 피연산자 사이에 공백을 추가</strong>하고, 가장 <strong>마지막에 줄바꿈을 포함</strong>합니다. fmt.Print는 공백을 추가하지 않으며, 줄바꿈도 하지 않습니다.</li>
</ul>
<p>이제 위의 코드를 실행하겠습니다.
실행하는 방법은 두 가지를 소개하겠습니다.</p>
<pre><code class="language-bash">go run hello.go
go build hello.go &amp;&amp; ./hello</code></pre>
<blockquote>
</blockquote>
<ul>
<li><strong>go run hello.go</strong> : main 패키지 파일을 컴파일 후에 바이너리 파일을 남기지 않고 메모리 위에서 바로 실행합니다.</li>
<li><strong>go build hello.go</strong> : main 패키지 파일을 import 시킨 모든 라이브러리, 연결된 패키지와 함께 컴파일하며 바이너리 파일을 생성합니다.</li>
<li><strong>./hello</strong> : 빌드된 hello 바이너리 파일을 실행합니다.</li>
</ul>
<p><img src="https://images.velog.io/images/vamos_eon/post/1c1e00c1-5225-42d8-b568-9237d3cba2b4/image.png" alt="">
위와 같이 Hello World!!가 출력되면 완료입니다.</p>
<hr>
<p>이상으로 Golang 개발 환경 설정 포스팅을 마치겠습니다.
감사합니다.👍</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Golang 기초 (1) : 소개 & 대표 플랫폼]]></title>
            <link>https://velog.io/@vamos_eon/go-intro</link>
            <guid>https://velog.io/@vamos_eon/go-intro</guid>
            <pubDate>Sun, 14 Nov 2021 14:45:23 GMT</pubDate>
            <description><![CDATA[<p>안녕하세요, 주니어 개발자 Eon입니다.</p>
<p>이번에 포스팅할 시리즈는 Golang입니다.
제가 많이 사용하는 언어이고 아직 많이 부족하지만 뛰어난 퍼포먼스와 쉬운 사용으로 인해 굉장히 마음에 드는 언어입니다.
아직 열심히 공부 중이며, 이미 알고 있는 내용들을 정리해보려 합니다.
나아가, Go에 대한 다른 여러 기능들을 포스팅할 수 있게 되었으면 좋겠습니다.</p>
<hr>
<h2 id="the-summary-of-go">The summary of Go.</h2>
<p>Google이 만든 언어이고 만들어진 계기는 &#39;C++의 복잡성이 싫어서&#39;라고 합니다.
웃긴 것은 C언어를 만든 켄 톰슨이 Go를 탄생시킨 3인(켄 톰슨, 로버트 그리즈머, 롭 파이크)의 대표자 중 한 명이라는 것입니다.
추가로, Golang의 패키지에 포함할 내용을 정하는 것은 아직까지 3인 만장일치제를 따른다고 합니다.</p>
<p>Golang 코드 실행은 아래의 사이트에서 쉽게 해보실 수 있습니다.
<strong><a href="https://play.golang.org/">https://play.golang.org/</a>
<a href="https://replit.com/languages/go">https://replit.com/languages/go</a></strong></p>
<hr>
<h2 id="the-performance-of-go">The performance of Go.</h2>
<p>Golang은 컴파일 언어이지만 그 속도가 매우 빨라, 빌드하는 시간이 짧은 쾌적한 언어이다.
<strong>goroutine이라는 비동기 방식은 멀티 스레드 방식</strong>이며, 따로 관리할 필요 없이 그저 실행할 코드 앞에 <strong>&#39;go&#39;를 붙여주면 사용</strong>할 수 있다.
goroutine은 Golang 자체의 스케줄러에 의해 관리되는 경량 스레드인 관계로, <strong>수천 개의 goroutine을 실행시켜도 성능에 지장이 없다.</strong>
Go를 빌드할 때에는 배포 환경에 따라 환경변수 지정을 해주면 문제가 없다.</p>
<hr>
<h2 id="some-famous-platforms-that-are-using-go">Some famous platforms that are using Go.</h2>
<p>Go의 뛰어난 퍼포먼스는 몇몇 커다란 플랫폼의 개발진을 매료시키기에 충분했습니다.
우리가 사용하는, 알고 있는 생각보다 많은 플랫폼들은 Go로 만들어졌거나 중요한 파트에 Go가 쓰였습니다.</p>
<hr>
<hr>
<h3 id="twitch">Twitch</h3>
<p>라이브 스트리밍 플랫폼인 twitch는 퍼포먼스가 중요한 시스템에 Go를 사용했습니다.
바로, 스트리머가 스트리밍함으로 들어오는 RTMP 스트림을 다수의 HLS 스트림으로 변환하는 시스템입니다.
twitch는 HLS를 사용함으로써 시청자가 화질을 조절할 수가 있습니다.
HLS는 HTTP Live Streaming으로, 스트리밍을 위한 통신 프로토콜입니다.
네트워크 상태에 따라 화질을 조절할 수 있는 Adaptive HTTP Streaming을 사용할 수 있습니다.
HLS는 아래와 같이 동작합니다.
<img src="https://d2.naver.com/content/images/2015/06/helloworld-7122-1.png" alt="출처 : 네이버 D2, https://d2.naver.com/helloworld/7122"></p>
<p><img src="https://images.velog.io/images/vamos_eon/post/5df3f308-7500-44bf-a1d1-7a9638275740/hls,as.png" alt=""></p>
<ol>
<li>퍼블리셔가 스트림 데이터를 생성하고, 서버에 전송합니다.</li>
<li><strong>서버의 미디어 인코더가 스트림 데이터를 인코딩합니다.</strong></li>
<li><strong>스트림 세그멘터가 인코딩된 미디어를 ts포맷으로 변환 및 분할합니다.</strong></li>
<li>이때, ts파일은 하나의 스트림 파일을 1080p, 720p, 480p, 360p 처럼 설정한 해상도마다 중복 존재합니다.
이 ts파일은 클라이언트의 요청이나 네트워크 상태에 따라 ts index 정보가 담긴 m3u8이라는 플레이리스트와 함께 제공됩니다.
m3u8에 담긴 정보로 인해 클라이언트는 스트림 화질이 변경되더라도 바로 이어서 시청할 수 있게 됩니다.</li>
</ol>
<p><a href="https://blog.twitch.tv/en/2015/12/18/twitch-engineering-an-introduction-and-overview-a23917b71a25/">Twitch Engineering: An Introduction and Overview</a> 발췌
바로 위의 2, 3의 과정이 Go가 사용된 파트입니다.
순수 Go만 사용된 것은 아니고 C/C++과 조합해서 사용했다고 합니다.
C/C++ 은 백엔드에서 High-performance를 위해 여전히 많이 사용되고 있는 언어입니다.
그러한 특징이 있는 언어들과 함께 쓰였다는 것은 Go도 상당한 퍼포먼스를 지녔다는 걸 간접적으로 증명합니다. (물론 C/C++에 비해 성능이 떨어지는 것은 사실입니다.)</p>
<hr>
<p>Twitch는 사용자에게 최고의 스트리밍 환경을 제공하기 위해 <strong>지리적으로 다른 POP에</strong> HLS를 배포합니다.
이 부분 역시 대부분 Go로 만들어졌습니다.
GLB 세팅도 마찬가지입니다.</p>
<ul>
<li>POP : Point of presence -&gt; 네트워크 접속 지점으로, ISP를 의미한다고 보시면 되겠습니다.</li>
<li>GLB / GSLB : Global (Server) Load Balancer : 글로벌 로드 밸런싱이며, 네트워크 상태를 판단하여 가장 적합한 서버에 우선순위를 높여 배포하도록 할 수 있습니다. </li>
</ul>
<hr>
<p>Twitch의 채팅 기능 역시 Go로 만들어졌습니다.
twitch는 Go를 채택한 이유에서 확장성이 뛰어나고 실시간 분산 시스템을 구축하기에 적합했다고 설명하고 있습니다.</p>
<p>추가로, Twitch는 <strong>Web API, 라우팅, 캐싱, 데이터 스토리지</strong>에 Go를 사용했다고 설명합니다.</p>
<hr>
<h3 id="paypal">PayPal</h3>
<p>해외 유명 결제 시스템인 PayPal은 Go를 기반 언어로 채택했습니다.
PayPal에는 Golang 개발자가 100명이 넘어갑니다.</p>
<p>Go의 라이브러리, 도구, 동시성, 단순성, 확장성, 가비지 콜렉팅 등을 활용하고, 플랫폼 규모에 맞는 애플리케이션 생성 및 실행의 복잡성을 단순화할 수 있었다.
라고 설명합니다.</p>
<p>PayPal의 지불 처리 플랫폼의 핵심은 PayPal이 C++로 개발한 독점 NoSQL 데이터베이스입니다.
<strong>PayPal은 Golang으로 NoSQL 데이터베이스를 성공적으로 이식하면서 Go의 사용 범위를 확대</strong>했습니다.
또한 PayPal은 Go로 만들어진 인프라를 구축해, 서비스 빌드 및 테스트까지 가능한 테스트 팜을 보유하고 있습니다.</p>
<p><a href="https://go.dev/solutions/paypal">PayPal Taps Go to Modernize and Scale</a> 발췌</p>
<hr>
<h3 id="uber">Uber</h3>
<p>한국에 최근에 들어온 우버에 Go가 사용됩니다.</p>
<p>Uber는 지리 데이터를 기반으로 한 운송 서비스 플랫폼입니다.
우버에 있는 지오펜스 조회 서비스가 Go로 대체되었습니다.</p>
<p><strong>지오펜스(geofence)는 geo + fence로, 지리적 경계를 의미</strong>합니다.
사람이 정의한 지리적 경계로, 아래의 이미지와 같은 형태를 띱니다.
<img src="https://images.velog.io/images/vamos_eon/post/8f00c4d2-df21-4f2b-9f6b-4156dac02f7d/geofence.png" alt=""></p>
<p>이 기술은 우버의 핵심 기술이자 <strong>우버가 제공하는 모든 서비스 중에 가장 QPS(초당 쿼리 수)가 높은 서비스</strong>입니다.
많은 양의 쿼리의 처리를 이전까지는 Node.js로 수행했습니다.
하지만 Node.js의 싱글 스레드 방식은 이 많은 양의 쿼리를 감당하기엔 부담이 되었고, 백그라운드 리프레싱은 CPU 사용을 장시간 멈추어, 쿼리 응답 시간을 늦추게 되는 결과를 초래했습니다.</p>
<p><strong>Go의 goroutine은 멀티 CPU core에서 따로 동작하며 이러한 쿼리 응답 시간 급증에 대한 문제를 해결합니다.</strong></p>
<p>무중단 백그라운드 로딩으로, geological 데이터를 바로 가져올 수 있습니다.
<a href="https://eng.uber.com/go-geofence-highest-query-per-second-service/">How We Built Uber Engineering’s Highest Query per Second Service Using Go</a> 발췌</p>
<hr>
<h3 id="etc">ETC</h3>
<p><strong><a href="https://go.dev/solutions/#case-studies">Golang solutions</a></strong>
<strong>Microsoft, Google, Facebook, NETFLIX, RIOT Games, Twitter, trivago 등</strong>이 Go를 사용합니다.
자세하게 더 알고 싶으신 분은 위의 링크로 이동하시면 잘 설명된 자료들이 있습니다.</p>
<hr>
<p>이번 시리즈를 시작하면서, 첫 포스트로 Go에 대해 알아보는 글을 작성했습니다.
다음 포스트부터는 Golang 기초를 포스팅하도록 하겠습니다.
감사합니다.👍</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Janus Gateway (3) :: Demo Service 구동하기]]></title>
            <link>https://velog.io/@vamos_eon/Janus-Gateway-3-Demo-Service-%EA%B5%AC%EB%8F%99%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@vamos_eon/Janus-Gateway-3-Demo-Service-%EA%B5%AC%EB%8F%99%ED%95%98%EA%B8%B0</guid>
            <pubDate>Sat, 06 Nov 2021 17:44:14 GMT</pubDate>
            <description><![CDATA[<p>안녕하세요, 주니어 개발자 Eon입니다.</p>
<p>이번 포스트에서는 Janus Gateway Demo Service 구동하는 순서에 대해 알아보겠습니다.
먼저, Janus Gateway는 http를 사용한 long poll 연결 방식과 websocket 연결 방식을 지원합니다.
말고도 rabbitmq, mqtt 등을 사용할 수도 있지만 보편적으로 사용되는 방식인 http와 websocket 방식을 사용하겠습니다.
또, 이 두 가지 방법이 demo에서 바로 제공되고 있는 방식이라 바로 구현하기가 편합니다.</p>
<p>진행 순서는 다음과 같습니다.
<strong>- docker : docker-compose.yml 살펴보기</strong>
<strong>- demo service 접속하기</strong>
<strong>- http / websocket 사용하기</strong>
<strong>- local 세팅 사용자를 위한 Browser flag 설정</strong></p>
<p>우리가 올려야 할 서비스는 두 개입니다.
하나는 janus-gateway, 다른 하나는 web-server입니다.
web-server 구동하는 방법은 이전에 <a href="https://velog.io/@vamos_eon/Docker-3-Web-Server-%EA%B5%AC%EC%B6%95-%EB%B0%8F-%EC%9B%B9-%ED%8E%98%EC%9D%B4%EC%A7%80-%EC%A0%9C%EC%9E%91"><strong>docker 설치하며 테스트하는 내용</strong></a>을 올린 포스트에서 확인하실 수 있습니다.
web-server가 무엇인지 간략한 소개가 필요한 분만 위의 포스트를 읽어보시면 될 것 같습니다.
서비스 두 개 모두 docker가 아닌 로컬 환경에서 구동하는 분은 다음 태그로 이동하시면 됩니다.</p>
<hr>
<h3 id="docker-composeyml-살펴보기-docker로-서비스-구동하는-분만-해당">docker-compose.yml 살펴보기 (docker로 서비스 구동하는 분만 해당)</h3>
<blockquote>
</blockquote>
<p><strong># docker-compose.yml for janus gateway</strong></p>
<pre><code>version: &#39;3.8&#39;
services:
  nimbus:
    user: root
    image: &quot;&lt;image_name:TAG&gt;&quot;
    volumes:
      - ./config/:/opt/janus/etc/janus/
      - ./log/:/var/log/
    restart: always
    ports:
      - 7088:7088
      - 8088:8088
      - 8188:8188
      - 10000-10030:10000-10030/udp</code></pre><p>위와 같이 compose를 구성하면 됩니다.
포트 구성에 대해 간략히 설명드리겠습니다.
각 port 구성은 아래에 나와 있는 jcfg 파일에서 수정할 수 있습니다.</p>
<ul>
<li>7088 : janus http admin request port : janus.transport.http.jcfg (set &#39;admin_http&#39; to true)</li>
<li>8088 : janus http general request port : janus.transport.http.jcfg (default)</li>
<li>8188 : janus websocket general request port : janus.transport.websocket.jcfg (default)</li>
<li>10000-10030 : only rtp packet udp port : janus.jcfg (should modify)</li>
</ul>
<blockquote>
</blockquote>
<p><strong># docker-compose.yml for web server</strong></p>
<pre><code>version: &quot;3.8&quot;
services:
   web_server:
      user: &quot;root&quot;
      image: httpd:alpine
      restart: always
      ports:
         - 80:80
      volumes:
         - ./janus:/usr/local/apache2/htdocs</code></pre><p>위와 같이 compose를 구성하면 됩니다.
janus 디렉터리를 마운트했습니다.
이 디렉터리는 janus-gateway의 html 디렉터리의 이름을 변경한 겁니다.</p>
<pre><code>git clone https://github.com/meetecho/janus-gateway.git</code></pre><p>janus-gateway를 클론해서 가져오시면 demo 페이지에 대한 소스가 html 디렉터리 안에 있습니다.</p>
<hr>
<h3 id="demo-service-접속하기">demo service 접속하기</h3>
<p>janus-gateway와 web-server를 구동합니다.
그리고 demo 페이지를 열어서 demo service에 접속합니다.
<img src="https://images.velog.io/images/vamos_eon/post/7e9361c0-7662-4969-b99a-565df0756817/janusdemo.png" alt="">
주소는 본인이 설치한 환경에 따라 다릅니다.
localhost가 될 수도 있고, 저처럼 192 대역폭일 수 있습니다.
web-server가 사용하는 ip 대역을 찾아야 합니다.
virtual-box의 경우, 127.0.0.1 즉, localhost로 포트포워딩을 통해 지정할 수 있습니다.
vmware는 저처럼 192 대역폭으로 잡힐 텐데 vm에 접속해, ifconfig 명령어로 확인할 수 있습니다.</p>
<h4 id="video-room">Video Room</h4>
<p><img src="https://images.velog.io/images/vamos_eon/post/327170b3-721c-4d19-8262-3aa7d348454c/democategory.png" alt="">
우리는 Video Room service를 이용할 겁니다.
바로 화상회의를 할 수 있게 하는 기능입니다.</p>
<p><img src="https://images.velog.io/images/vamos_eon/post/46d7369c-8aee-4e5b-9e85-570f6a79dbbc/videoroommain.png" alt=""></p>
<p>들어오면 이렇게 보입니다.
Start 버튼을 누르고, 본인의 display name을 쓰고 방에 참여를 할 수 있습니다.
<img src="https://images.velog.io/images/vamos_eon/post/5fca2cf0-e1f3-4883-ad9f-041a82c270f6/display.png" alt=""></p>
<p>방에 참여를 하면 아래와 같이 화상회의가 시작됩니다.
client를 하나 더 늘리고 싶으면 탭 하나 더 열고 접속하시면 됩니다.
그러면 아래와 같이 나옵니다.
<img src="https://images.velog.io/images/vamos_eon/post/294bf0b4-2a4b-4866-8946-0f2fadb74e1f/videoroomp2p.png" alt=""></p>
<h4 id="adminmonitor">Admin/Monitor</h4>
<p>위에서 한 것처럼 두 개의 클라이언트를 연결해두고 아래와 같이 Admin/Monitor 메뉴를 선택합니다.
<img src="https://images.velog.io/images/vamos_eon/post/73402023-b3eb-4eef-bc74-a5efb12b7126/adminmonitoring.png" alt=""></p>
<p>아래와 같이 Admin API secret을 입력하라는 창이 나옵니다.
janus.jcfg 안에 &quot;admin_secret&quot;가 API secret을 의미합니다.
default = &quot;janusoverlord&quot;
수정하신 분은 수정한 secret 값을 입력하시면 됩니다.
<img src="https://images.velog.io/images/vamos_eon/post/88fa3d0c-877e-4ed5-88db-92dbde209384/adminsecret.png" alt=""></p>
<p><img src="https://images.velog.io/images/vamos_eon/post/aa3417ee-dcfc-4d02-9021-8982f84842c3/adminmain.png" alt=""></p>
<p>접속한 janus demo의 Admin/Monitor 페이지에서는 우리가 빌드한 janus-gateway를 볼 수 있습니다.</p>
<ol>
<li><p>서버 정보 확인 가능</p>
</li>
<li><p>동적으로 몇 가지 설정 변경 가능</p>
</li>
<li><p>각종 plugin에 대하여 request 가능</p>
</li>
<li><p>사용 중인 session, handle에 대한 모니터링 가능</p>
</li>
<li><p>각종 플러그인에 대하여 request 가능
<img src="https://images.velog.io/images/vamos_eon/post/165b6d41-cd77-42a5-8095-6a4231cbd7da/pluginrequest.png" alt=""></p>
</li>
<li><p>사용 중인 session, handle에 대한 모니터링 가능
<img src="https://images.velog.io/images/vamos_eon/post/772657e0-bd02-4c45-af3f-f81005561350/monitoring.png" alt=""></p>
</li>
</ol>
<h4 id="sessionhandle-구분하기">Session/Handle 구분하기</h4>
<blockquote>
</blockquote>
<pre><code>{
    &quot;session_id&quot;: 1680235067534419,
    &quot;session_last_activity&quot;: 21099289581,
    &quot;session_timeout&quot;: 60,
    &quot;session_transport&quot;: &quot;janus.transport.http&quot;,
    &quot;handle_id&quot;: 7341683173592579,
    &quot;opaque_id&quot;: &quot;videoroomtest-owwI8GGU0D2Z&quot;,
    &quot;loop-running&quot;: true,
    &quot;created&quot;: 20004476526,
    &quot;current_time&quot;: 21114145471,
    &quot;plugin&quot;: &quot;janus.plugin.videoroom&quot;,
    &quot;plugin_specific&quot;: {
        &quot;type&quot;: &quot;publisher&quot;,
        &quot;room&quot;: 1234,
        &quot;id&quot;: 2088026101620201,
        &quot;private_id&quot;: 1858719391,
        &quot;display&quot;: &quot;eon1&quot;,
        &quot;viewers&quot;: 1,
        &quot;media&quot;: {
            &quot;audio&quot;: true,
            &quot;audio_codec&quot;: &quot;opus&quot;,
            &quot;video&quot;: true,
            &quot;video_codec&quot;: &quot;vp8&quot;,
            &quot;data&quot;: false
        },
        &quot;bitrate&quot;: 128000,
        &quot;audio-level-dBov&quot;: 0,
        &quot;talking&quot;: false,
        &quot;hangingup&quot;: 0,
        &quot;destroyed&quot;: 0
    },
    &quot;flags&quot;: {
        &quot;got-offer&quot;: true,
        &quot;got-answer&quot;: true,
        &quot;negotiated&quot;: true,
        &quot;processing-offer&quot;: false,
        &quot;starting&quot;: true,
        &quot;ice-restart&quot;: false,
        &quot;ready&quot;: true,
        &quot;stopped&quot;: false,
        &quot;alert&quot;: false,
        &quot;trickle&quot;: true,
        &quot;all-trickles&quot;: true,
        &quot;resend-trickles&quot;: false,
        &quot;trickle-synced&quot;: false,
        &quot;data-channels&quot;: false,
        &quot;has-audio&quot;: true,
        &quot;has-video&quot;: true,
        &quot;new-datachan-sdp&quot;: false,
        &quot;rfc4588-rtx&quot;: true,
        &quot;cleaning&quot;: false,
        &quot;e2ee&quot;: false
    },
    &quot;agent-created&quot;: 20012000422,
    &quot;ice-mode&quot;: &quot;full&quot;,
    &quot;ice-role&quot;: &quot;controlled&quot;,
    &quot;sdps&quot;: {
        &quot;profile&quot;: &quot;UDP/TLS/RTP/SAVPF&quot;,
        &quot;local&quot;: &quot;LOCAL-SDP&quot;,
        &quot;remote&quot;: &quot;REMOTE-SDP&quot;
    },
    &quot;queued-packets&quot;: 0,
    &quot;streams&quot;: [
        {
            &quot;id&quot;: 1,
            &quot;ready&quot;: -1,
            &quot;ssrc&quot;: {
                &quot;audio&quot;: 1011294199,
                &quot;video&quot;: 2619788864,
                &quot;video-rtx&quot;: 1820056821,
                &quot;audio-peer&quot;: 1760309472,
                &quot;video-peer&quot;: 456717877,
                &quot;video-peer-rtx&quot;: 1757326114
            },
            &quot;direction&quot;: {
                &quot;audio-send&quot;: false,
                &quot;audio-recv&quot;: true,
                &quot;video-send&quot;: false,
                &quot;video-recv&quot;: true
            },
            &quot;codecs&quot;: {
                &quot;audio-pt&quot;: 111,
                &quot;audio-codec&quot;: &quot;opus&quot;,
                &quot;video-pt&quot;: 96,
                &quot;video-rtx-pt&quot;: 97,
                &quot;video-codec&quot;: &quot;vp8&quot;
            },
            &quot;extensions&quot;: {
                &quot;urn:ietf:params:rtp-hdrext:sdes:mid&quot;: 4,
                &quot;urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id&quot;: 5,
                &quot;urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id&quot;: 6,
                &quot;http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01&quot;: 3,
                &quot;urn:ietf:params:rtp-hdrext:ssrc-audio-level&quot;: 1,
                &quot;urn:3gpp:video-orientation&quot;: 13
            },
            &quot;bwe&quot;: {
                &quot;twcc&quot;: true,
                &quot;twcc-ext-id&quot;: 3
            },
            &quot;nack-queue-ms&quot;: 200,
            &quot;rtcp_stats&quot;: {
                &quot;audio&quot;: {
                    &quot;base&quot;: 48000,
                    &quot;rtt&quot;: 0,
                    &quot;lost&quot;: 0,
                    &quot;lost-by-remote&quot;: 0,
                    &quot;jitter-local&quot;: 0,
                    &quot;jitter-remote&quot;: 0,
                    &quot;in-link-quality&quot;: 100,
                    &quot;in-media-link-quality&quot;: 100,
                    &quot;out-link-quality&quot;: 0,
                    &quot;out-media-link-quality&quot;: 0
                },
                &quot;video&quot;: {
                    &quot;base&quot;: 90000,
                    &quot;rtt&quot;: 0,
                    &quot;lost&quot;: 0,
                    &quot;lost-by-remote&quot;: 0,
                    &quot;jitter-local&quot;: 6,
                    &quot;jitter-remote&quot;: 0,
                    &quot;in-link-quality&quot;: 100,
                    &quot;in-media-link-quality&quot;: 100,
                    &quot;out-link-quality&quot;: 0,
                    &quot;out-media-link-quality&quot;: 0
                }
            },
            &quot;components&quot;: [
                {
                    &quot;DEPENDS-ON-USER-SETTING&quot;
                    ],
                    &quot;remote-candidates&quot;: [
                        &quot;DEPENDS-ON-USER-SETTING&quot;
                    ],
                    &quot;selected-pair&quot;: &quot;192.168.56.1:61131 [prflx,udp] &lt;-&gt; 192.168.56.1:57209 [host,udp]&quot;,
                    &quot;dtls&quot;: {
                        &quot;fingerprint&quot;: &quot;SECRET&quot;,
                        &quot;remote-fingerprint&quot;: &quot;SECRET&quot;,
                        &quot;remote-fingerprint-hash&quot;: &quot;sha-256&quot;,
                        &quot;dtls-role&quot;: &quot;active&quot;,
                        &quot;dtls-state&quot;: &quot;connected&quot;,
                        &quot;retransmissions&quot;: 0,
                        &quot;valid&quot;: true,
                        &quot;srtp-profile&quot;: &quot;SRTP_AES128_CM_SHA1_80&quot;,
                        &quot;ready&quot;: true,
                        &quot;handshake-started&quot;: 20012351711,
                        &quot;connected&quot;: 20012360099,
                        &quot;sctp-association&quot;: false
                    },
                    &quot;in_stats&quot;: {
                        &quot;audio_packets&quot;: 55089,
                        &quot;audio_bytes&quot;: 1875509,
                        &quot;audio_bytes_lastsec&quot;: 1377,
                        &quot;do_audio_nacks&quot;: false,
                        &quot;video_packets&quot;: 30906,
                        &quot;video_bytes&quot;: 9538501,
                        &quot;video_bytes_lastsec&quot;: 9140,
                        &quot;do_video_nacks&quot;: true,
                        &quot;video_nacks&quot;: 0,
                        &quot;video_retransmissions&quot;: 0,
                        &quot;data_packets&quot;: 2,
                        &quot;data_bytes&quot;: 1226
                    },
                    &quot;out_stats&quot;: {
                        &quot;audio_packets&quot;: 0,
                        &quot;audio_bytes&quot;: 0,
                        &quot;audio_bytes_lastsec&quot;: 0,
                        &quot;audio_nacks&quot;: 0,
                        &quot;video_packets&quot;: 0,
                        &quot;video_bytes&quot;: 0,
                        &quot;video_bytes_lastsec&quot;: 0,
                        &quot;video_nacks&quot;: 0,
                        &quot;data_packets&quot;: 2,
                        &quot;data_bytes&quot;: 789
                    }
                }
            ]
        }
    ]
}</code></pre><p>위의 정보에서 display는 publisher (session_id 1680235067534419)의 display 정보입니다.
동일한 session 내에 또 다른 handle 정보를 보시면 display는 없고 feed_display가 있는 걸 확인할 수 있습니다.
subscriber의 display 정보입니다.
다른 session에서 보면 두 display가 서로 다르게 나오는 걸 확인할 수 있습니다.</p>
<p>가장 아래를 보시면 in_stats, out_stats가 있습니다.
트래픽 정보라고 보시면 됩니다.
in_stats : janus-gateway로 들어오는 정보
out_stats : janus-gateway에서 나가는 정보
현재, 이 session/handle은 publisher가 자신의 영상을 janus를 통해 subscriber에게 보내는 중입니다.
이 handle에서는 보내는 것만 있을 뿐, rtp 패킷을 받지 않습니다.
동일 session의 다른 handle에서는 반대입니다.</p>
<h3 id="http--websocket-사용하기">http / websocket 사용하기</h3>
<p>Janus demo 페이지 소스를 봅니다.
그중 videoroomtest.js를 열어봅니다.
최상단 주석 부분은 간단한 설명이 있으니 읽어 보시기 바랍니다.
최상단 주석이 끝나는 부분에 우리가 볼 http / websocket 연결 방식을 정할 수 있는 부분이 나옵니다.</p>
<blockquote>
</blockquote>
<pre><code>// This will tell the library to try connecting to each of the servers
// in the presented order. The first working server will be used for
// the whole session.
//
var server = null;
if(window.location.protocol === &#39;http:&#39;)
    server = &quot;http://&quot; + window.location.hostname + &quot;:8088/janus&quot;;
else
    server = &quot;https://&quot; + window.location.hostname + &quot;:8089/janus&quot;;</code></pre><p>주소 창에 프로토콜이 http이면 8088포트에 연결합니다.
주소 창에 프로토콜이 https이면 8089포트에 연결합니다.
(우리는 https를 건드린 적 없고, https도 false로 두었으니 사용이 불가합니다.)</p>
<p>지금 기본적으로 http 연결 방식이 채택되어 있습니다.
이대로 사용하시면 http long poll을 사용하는 것이고, 여기서 websocket 방식으로 변경 가능합니다.</p>
<blockquote>
</blockquote>
<pre><code>if(window.location.protocol === &#39;http:&#39;)
    server = &quot;ws://&quot; + window.location.hostname + &quot;:8188/janus&quot;;
else
    server = &quot;wss://&quot; + window.location.hostname + &quot;:8989/janus&quot;;</code></pre><p>이런 식으로 http 연결이 있을 때, websocket으로 연결하도록 합니다.
wss는 마찬가지로 false로 설정했기 때문에 사용을 하지 않습니다.</p>
<p>위와 같이 두 가지 연결 방식 모두 테스트할 수 있으며, 위의 Admin/Monitor 기능을 사용해서 연결 방식을 확인할 수 있습니다.</p>
<hr>
<h3 id="local-세팅-사용자를-위한-browser-flag-설정">local 세팅 사용자를 위한 Browser flag 설정</h3>
<p>localhost로 접속하시는 분들은 별다른 문제를 겪지 않으실 겁니다.
local 장비에 vm을 이용하거나 web-server를 다른 툴을 이용해서 구동하는 경우, web-server의 ip 구성이 localhost가 아닐 수 있습니다.
browser는 https가 설정되지 않은 http 환경에서 media device에 접근을 막습니다.
화상회의를 위한 필수 device인 cam, mic가 대상이 됩니다.</p>
<p>대표적으로 chrome, firefox에서의 flag 설정하는 방법을 다뤄보겠습니다.
보안 문제가 발생할 수 있기 때문에 테스트가 끝나면 다시 원상 복구하는 걸 권장합니다.</p>
<h4 id="chrome-browser-media-device-permission-flag-setting">chrome browser media device permission flag setting</h4>
<p>chrome://flags/#unsafely-treat-insecure-origin-as-secure
browser 주소 창에 위와 같이 적습니다.
그러면 아래와 같이 지정한 flag에 대해 설정할 수 있게 됩니다.
<img src="https://images.velog.io/images/vamos_eon/post/580bdc32-f2c9-4394-aaa7-661ed9ae6615/chrome%20flag.png" alt=""></p>
<p>허용할 ip 목록을 적고, Enabled로 값을 변경하면 Relaunch 버튼이 생기는데, 이 버튼을 누르고 브라우저를 재실행하면 device 접근이 허용됩니다.</p>
<h4 id="firefox-browser-media-device-permission-flag-setting">firefox browser media device permission flag setting</h4>
<p>aout:config
browser 주소 창에 위와 같이 적습니다.
그러면 아래와 같이 경고 페이지가 나오고 그대로 진행합니다.
<img src="https://images.velog.io/images/vamos_eon/post/f531308a-7146-4618-8a18-e6c5b628700b/firefoxaboutconfig.png" alt=""></p>
<p>설정할 flag는 다음의 검색어로 한 번에 찾을 수 있습니다.</p>
<ul>
<li>insecure.enabled</li>
</ul>
<p>아래의 flag를 모두 true로 변경합니다.</p>
<ul>
<li>media.devices.insecure.enabled</li>
<li>media.getusermedia.insecure.enabled</li>
</ul>
<p><img src="https://images.velog.io/images/vamos_eon/post/3d2b6d17-5029-4d9c-b9b3-1e9e9e8b664c/firefoxflag.png" alt=""></p>
<p>이제 chrome과 firefox에서 local 주소가 어떻든 media device에 접근할 수 있게 됐습니다.</p>
<hr>
<p>이렇게 janus-gateway를 다루고 보는 방법을 알아봤습니다.
이것으로 janus demo service 구동하는 시리즈를 마치겠습니다.
감사합니다.👍</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Janus Gateway (2) :: Video room plugin을 위한 config 세팅]]></title>
            <link>https://velog.io/@vamos_eon/Janus-Gateway-2-config-%EC%84%B8%ED%8C%85-%EC%8B%A4%ED%96%89-%EB%B0%8F-Demo-%ED%8E%98%EC%9D%B4%EC%A7%80-%EC%97%B0%EB%8F%99</link>
            <guid>https://velog.io/@vamos_eon/Janus-Gateway-2-config-%EC%84%B8%ED%8C%85-%EC%8B%A4%ED%96%89-%EB%B0%8F-Demo-%ED%8E%98%EC%9D%B4%EC%A7%80-%EC%97%B0%EB%8F%99</guid>
            <pubDate>Tue, 02 Nov 2021 16:24:50 GMT</pubDate>
            <description><![CDATA[<p>안녕하세요, 주니어 개발자 Eon입니다.</p>
<p>이전 포스트는 Janus Gateway 설치 과정과 Docker로 실행하고 싶으신 분들을 위해 Dockerfile을 포함해서 올렸습니다.
이번 포스트는 Janus Gateway config 세팅을 해보려고 합니다.
Docker를 사용하지 않을 분은 config나 설정 값 모두 로컬 Janus나 웹 서버에 적용하시면 됩니다.</p>
<p>Janus 실행 먼저 보도록 하겠습니다.</p>
<h2 id="janus-gateway의-실행">Janus Gateway의 실행</h2>
<p>이전 포스트의 Dockerfile 마지막 부분이 실행하는 파트입니다.</p>
<blockquote>
</blockquote>
<pre><code class="language-bash">/opt/janus/bin/janus --v
/opt/janus/bin/janus</code></pre>
<p>janus binary를 --v 옵션과 함께 실행하면 version을 출력합니다.
그리고 옵션 없이 실행하면 janus가 실행됩니다.</p>
<p>이렇게 하면 Janus가 실행됩니다. 
실행은 시켰으나, 제대로 동작되고 있는지 알려면 config 세팅을 끝마친 후에 실행해야 합니다.
지금 실행한 것은 default값으로만 설정되어 있어, 사용이 어렵습니다.</p>
<hr>
<h2 id="janus-configs">Janus configs</h2>
<p>Janus config를 보겠습니다.
Dockerfile의 순서대로 진행하셨다면 아래의 경로에 config 파일이 있습니다.</p>
<blockquote>
</blockquote>
<pre><code class="language-bash">/opt/janus/etc/janus</code></pre>
<p>Videoroom plugin을 사용한 화상회의 세팅을 위해 우리가 봐야 할 것은 아래 리스트입니다.</p>
<blockquote>
</blockquote>
<pre><code class="language-bash">janus.jcfg
janus.plugin.videoroom.jcfg
janus.transport.http.jcfg
janus.transport.websocket.jcfg</code></pre>
<p>위에서부터 아래의 설정을 위한 config 파일들입니다.</p>
<ul>
<li>Janus 전반적인 general config</li>
<li>videoroom을 위한 config</li>
<li>http 관련 설정을 위한 config</li>
<li>websocket 관련 설정을 위한 config</li>
</ul>
<hr>
<h3 id="janusjcfg">janus.jcfg</h3>
<blockquote>
</blockquote>
<p>**
# Janus 메인 설정입니다.
**</p>
<pre><code class="language-ini"># General configuration: folders where the configuration and the plugins
# can be found, how output should be logged, whether Janus should run as
# a daemon or in foreground, default interface to use, debug/logging level
# and, if needed, shared apisecret and/or token authentication mechanism
# between application(s) and Janus.
general: {</code></pre>
<p><strong># folder path는 default 세팅을 따르겠습니다.</strong></p>
<pre><code class="language-ini">    configs_folder = &quot;/opt/janus/etc/janus&quot;            # Configuration files folder
    plugins_folder = &quot;/opt/janus/lib/janus/plugins&quot;            # Plugins folder
    transports_folder = &quot;/opt/janus/lib/janus/transports&quot;    # Transports folder
    events_folder = &quot;/opt/janus/lib/janus/events&quot;            # Event handlers folder
    loggers_folder = &quot;/opt/janus/lib/janus/loggers&quot;            # External loggers folder</code></pre>
<hr>
<p>**
# logging 관련 세팅입니다.
# log_to_file : log를 file로 남기기 위해, logfile의 경로를 지정합니다.
# debug_level : level 5로 설정하여, Verbose mode로 설정합니다.
# <a href="https://janus.conf.meetecho.com/docs/debug_8h.html#a8ba6afa85ef312f71d379ac4df032b3b">debug.h File Reference</a>
# debug_timestamps : timestamp를 같이 출력하여, 로그를 보기 쉽게 합니다.
# 로그 세팅은 본인이 편한대로 옵션을 지정하시면 됩니다.
**</p>
<pre><code class="language-ini">        # The next settings configure logging
    #log_to_stdout = false                    # Whether the Janus output should be written
                                            # to stdout or not (default=true)
    log_to_file = &quot;/var/log/janus.log&quot;        # Whether to use a log file or not
    debug_level = 5                            # Debug/logging level, valid values are 0-7
    debug_timestamps = true                # Whether to show a timestamp for each log line
    #debug_colors = false                    # Whether colors should be disabled in the log
    #debug_locks = true                        # Whether to enable debugging of locks (very verbose!)
    #log_prefix = &quot;[janus] &quot;                # In case you want log lines to be prefixed by some
                                            # custom text, you can use the &#39;log_prefix&#39; property.
                                            # It supports terminal colors, meaning something like
                                            # &quot;[\x1b[32mjanus\x1b[0m] &quot; would show a green &quot;janus&quot;
                                            # string in square brackets (assuming debug_colors=true).</code></pre>
<p>**
# demonize는 실행할 때 옵션으로 설정할 수 있으니, default로 두겠습니다.
**</p>
<pre><code class="language-ini">        # This is what you configure if you want to launch Janus as a daemon
    #daemonize = true                        # Whether Janus should run as a daemon
                                            # or not (default=run in foreground)
    #pid_file = &quot;/path/to/janus.pid&quot;        # PID file to create when Janus has been
                                            # started, and to destroy at shutdown</code></pre>
<p>**
# authenticate는 default로 사용하겠습니다. (설정해서 사용하는 것은 따로 포스팅하도록 하겠습니다.)
# api_secret : 모든 API request에 해당 string을 인증 수단의 하나로써 포함시켜야 합니다.
# token_auth : token 기반의 인증을 활성화합니다. 모든 request에 유효한 token을 포함해야 합니다. 이는 비밀번호 관리하듯이 관리할 수 있어 보안 수준이 아주 높진 않습니다.
# token_auth_secret : HMAC-SHA1 서명 token을 인증 수단으로 사용합니다. HMAC은 단방향 해시 암호화 알고리즘으로, 해독이 불가합니다. 따라서, 굉장히 높은 보안 수준을 자랑합니다. 물론 이 또한 token의 형태이므로, 탈취 자체에는 취약합니다.
# admin_secret : Admin API를 사용하기 위한 암호를 설정합니다. Admin API를 이용한 모니터링을 활용하기 위해서 필요합니다.
**</p>
<pre><code class="language-ini">        # There are different ways you can authenticate the Janus and Admin APIs
    #api_secret = &quot;janusrocks&quot;        # String that all Janus requests must contain
                                    # to be accepted/authorized by the Janus core.
                                    # Useful if you&#39;re wrapping all Janus API requests
                                    # in your servers (that is, not in the browser,
                                    # where you do the things your way) and you
                                    # don&#39;t want other application to mess with
                                    # this Janus instance.
    #token_auth = true                # Enable a token based authentication
                                    # mechanism to force users to always provide
                                    # a valid token in all requests. Useful if
                                    # you want to authenticate requests from web
                                    # users.
    #token_auth_secret = &quot;janus&quot;    # Use HMAC-SHA1 signed tokens (with token_auth). Note that
                                    # without this, the Admin API MUST
                                    # be enabled, as tokens are added and removed
                                    # through messages sent there.
    admin_secret = &quot;janusoverlord&quot;    # String that all Janus requests must contain
                                    # to be accepted/authorized by the admin/monitor.
                                    # only needed if you enabled the admin API
                                    # in any of the available transports.</code></pre>
<p>**
# 일반 설정 파트입니다.
# interface : SDP에 사용될 인터페이스를 정의합니다. Docker로 올릴 경우, Docker network interface가 설정되어, 로컬 네트워크 인터페이스로 설정하더라도 자동으로 변경됩니다. 이를 원치 않을 경우, Docker network를 수정해야 합니다. 이 포스트에서는 다루지 않도록 합니다.
# session_timeout : 아무 요청이 없을 때 세션이 자동 만료되는 시간을 설정합니다. 기본값이 60이며, 초 단위입니다. timeout을 짧게 잡게 되면 세션을 유지하기 위해 많은 횟수의 keepalive 요청이 필요합니다. 이는 퍼포먼스 저하로 이어질 수 있습니다. 따라서 timeout은 너무 부족하지 않게 설정하는 것이 좋습니다. 이 포스트에서는 default 값을 사용하겠습니다.
# candidates_timeout : ICE trickle 하는 과정에 대한 timeout입니다. 비동기 프로세스로 진행되기 때문에 큰 차이는 없겠지만 Janus가 candidates 정보를 가지고 있는 시간을 설정하는 것이므로, 너무 길면 퍼포먼스에 영향을 줄 수 있습니다. 이 포스트에서는 default 값을 사용하겠습니다.
# reclaim_session_timeout : 통신이 끝난 세션이 회수될 때까지의 시간을 설정합니다. timeout을 길게 잡으면 Janus가 세션 정보를 유지하고 있어야 합니다. 이 역시 퍼포먼스 저하로 이어질 수 있습니다. default는 0이며, 통신이 끝난 세션은 바로 회수됩니다. 이 포스트에서는 default 값을 사용하겠습니다.
# 나머지 설명이 없는 부분은 default 세팅을 사용하시면 됩니다. 세팅을 하고 싶으신 분은 주석의 설명을 읽고 설정하시기 바랍니다.
**</p>
<pre><code class="language-ini">        # Generic settings
    #interface = &quot;1.2.3.4&quot;            # Interface to use (will be used in SDP)
    #server_name = &quot;MyJanusInstance&quot;# Public name of this Janus instance
                                    # as it will appear in an info request
    #session_timeout = 60            # How long (in seconds) we should wait before
                                    # deciding a Janus session has timed out. A
                                    # session times out when no request is received
                                    # for session_timeout seconds (default=60s).
                                    # Setting this to 0 will disable the timeout
                                    # mechanism, which is NOT suggested as it may
                                    # risk having orphaned sessions (sessions not
                                    # controlled by any transport and never freed).
                                    # To avoid timeouts, keep-alives can be used.
    #candidates_timeout = 45        # How long (in seconds) we should keep hold of
                                    # pending (trickle) candidates before discarding
                                    # them (default=45s). Notice that setting this
                                    # to 0 will NOT disable the timeout, but will
                                    # be considered an invalid value and ignored.
    #reclaim_session_timeout = 0    # How long (in seconds) we should wait for a
                                    # janus session to be reclaimed after the transport
                                    # is gone. After the transport is gone, a session
                                    # times out when no request is received for
                                    # reclaim_session_timeout seconds (default=0s).
                                    # Setting this to 0 will disable the timeout
                                    # mechanism, and sessions will be destroyed immediately
                                    # if the transport is gone.
    #recordings_tmp_ext = &quot;tmp&quot;        # The extension for recordings, in Janus, is
                                    # .mjr, a custom format we devised ourselves.
                                    # By default, we save to .mjr directly. If you&#39;d
                                    # rather the recording filename have a temporary
                                    # extension while it&#39;s being saved, and only
                                    # have the .mjr extension when the recording
                                    # is over (e.g., to automatically trigger some
                                    # external scripts), then uncomment and set the
                                    # recordings_tmp_ext property to the extension
                                    # to add to the base (e.g., tmp --&gt; .mjr.tmp).
    #event_loops = 8                # By default, Janus handles each have their own
                                    # event loop and related thread for all the media
                                    # routing and management. If for some reason you&#39;d
                                    # rather limit the number of loop/threads, and
                                    # you want handles to share those, you can do that
                                    # configuring the event_loops property: this will
                                    # spawn the specified amount of threads at startup,
                                    # run a separate event loop on each of them, and
                                    # add new handles to one of them when attaching.
                                    # Notice that, while cutting the number of threads
                                    # and possibly reducing context switching, this
                                    # might have an impact on the media delivery,
                                    # especially if the available loops can&#39;t take
                                    # care of all the handles and their media in time.
                                    # As such, if you want to use this you should
                                    # provision the correct value according to the
                                    # available resources (e.g., CPUs available).
    #opaqueid_in_api = true            # Opaque IDs set by applications are typically
                                    # only passed to event handlers for correlation
                                    # purposes, but not sent back to the user or
                                    # application in the related Janus API responses
                                    # or events; in case you need them to be in the
                                    # Janus API too, set this property to &#39;true&#39;.
    #hide_dependencies = true        # By default, a call to the &quot;info&quot; endpoint of
                                    # either the Janus or Admin API now also returns
                                    # the versions of the main dependencies (e.g.,
                                    # libnice, libsrtp, which crypto library is in
                                    # use and so on). Should you want that info not
                                    # to be disclose, set &#39;hide_dependencies&#39; to true.
        # The following is ONLY useful when debugging RTP/RTCP packets,
        # e.g., to look at unencrypted live traffic with a browser. By
        # default it is obviously disabled, as WebRTC mandates encryption.
    #no_webrtc_encryption = true
        # Janus provides ways via its API to specify custom paths to save
        # files to (e.g., recordings, pcap captures and the like). In order
        # to avoid people can mess with folders they&#39;re not supposed to,
        # you can configure an array of folders that Janus should prevent
        # creating files in. If the &#39;protected_folder&#39; property below is
        # commented, no folder is protected.
        # Notice that at the moment this only covers attempts to start
        # an .mjr recording and pcap/text2pcap packet captures.
    protected_folders = [
        &quot;/bin&quot;,
        &quot;/boot&quot;,
        &quot;/dev&quot;,
        &quot;/etc&quot;,
        &quot;/initrd&quot;,
        &quot;/lib&quot;,
        &quot;/lib32&quot;,
        &quot;/lib64&quot;,
        &quot;/proc&quot;,
        &quot;/sbin&quot;,
        &quot;/sys&quot;,
        &quot;/usr&quot;,
        &quot;/var&quot;,
            # We add what are usually the folders Janus is installed to
            # as well: we don&#39;t just put &quot;/opt/janus&quot; because that would
            # include folders like &quot;/opt/janus/share&quot; that is where
            # recordings might be saved to by some plugins
        &quot;/opt/janus/bin&quot;,
        &quot;/opt/janus/etc&quot;,
        &quot;/opt/janus/include&quot;,
        &quot;/opt/janus/lib&quot;,
        &quot;/opt/janus/lib32&quot;,
        &quot;/opt/janus/lib64&quot;,
        &quot;/opt/janus/sbin&quot;
    ]
}</code></pre>
<p>**
# 인증서 파트입니다.
# 인증서를 사용해서 DTLS를 사용하고 싶으신 분은 설정을 진행하시면 됩니다.
# Janus는 기본적으로 DTLS를 지원합니다. 인증서가 없더라도 self-signed cert를 통해 DTLS를 사용할 수 있습니다.
# 이 포스트에서는 default 세팅을 사용하겠습니다.
**</p>
<pre><code class="language-ini"># Certificate and key to use for DTLS (and passphrase if needed). If missing,
# Janus will autogenerate a self-signed certificate to use. Notice that
# self-signed certificates are fine for the purpose of WebRTC DTLS
# connectivity, for the time being, at least until Identity Providers
# are standardized and implemented in browsers. If for some reason you
# want to enforce the DTLS stack in Janus to enforce valid certificates
# from peers, though, you can do that setting &#39;dtls_accept_selfsigned&#39; to
# &#39;false&#39; below: DO NOT TOUCH THAT IF YOU DO NOT KNOW WHAT YOU&#39;RE DOING!
# You can also configure the DTLS ciphers to offer: the default if not
# set is &quot;DEFAULT:!NULL:!aNULL:!SHA256:!SHA384:!aECDH:!AESGCM+AES256:!aPSK&quot;
# Finally, by default NIST P-256 certificates are generated (see #1997),
# but RSA generation is still supported if you set &#39;rsa_private_key&#39; to &#39;true&#39;.
certificates: {
    #cert_pem = &quot;/path/to/certificate.pem&quot;
    #cert_key = &quot;/path/to/key.pem&quot;
    #cert_pwd = &quot;secretpassphrase&quot;
    #dtls_accept_selfsigned = false
    #dtls_ciphers = &quot;your-desired-openssl-ciphers&quot;
    #rsa_private_key = false
}</code></pre>
<p>**
# 미디어 관련 세팅입니다.
# ipv6 : ipv6를 지원할 것인지에 대한 세팅입니다. 아직까지 ipv6가 꼭 필요한 경우는 거의 없다고 봐도 무방합니다. 또한, 이 포스트에서는 demo 서비스까지만 다룰 것이므로, ipv6는 지원하지 않도록 false로 세팅합니다.
# rtp_port_range : RTP와 RTCP를 위해 사용될 포트 범위를 지정합니다. 포트를 많이 사용할수록 Janus 리소스가 많이 사용됩니다. demo 서비스에서 개인이 테스트할 정도만 필요하니, 30개 포트만 사용하겠습니다. video, audio 모두 RTP 통신을 하므로 Peer 2개가 연결됐을 때, 총 4개의 RTP 포트가 사용됩니다. Peer 3개가 연결됐을 때는 각 3개씩 총 9개의 RTP 포트가 사용됩니다.
# 나머지 세팅은 default 값을 사용하겠습니다.
**</p>
<pre><code class="language-ini"># Media-related stuff: you can configure whether if you want
# to enable IPv6 support, the minimum size of the NACK queue (in ms,
# defaults to 200ms) for retransmissions no matter the RTT, the range of
# ports to use for RTP and RTCP (by default, no range is envisaged), the
# starting MTU for DTLS (1200 by default, it adapts automatically),
# how much time, in seconds, should pass with no media (audio or
# video) being received before Janus notifies you about this (default=1s,
# 0 disables these events entirely), how many lost packets should trigger
# a &#39;slowlink&#39; event to users (default=4), and how often, in milliseconds,
# to send the Transport Wide Congestion Control feedback information back
# to senders, if negotiated (default=200ms). Finally, if you&#39;re using BoringSSL
# you can customize the frequency of retransmissions: OpenSSL has a fixed
# value of 1 second (the default), while BoringSSL can override that. Notice
# that lower values (e.g., 100ms) will typically get you faster connection
# times, but may not work in case the RTT of the user is high: as such,
# you should pick a reasonable trade-off (usually 2*max expected RTT).
media: {
    ipv6 = false
    #min_nack_queue = 500
    rtp_port_range = &quot;10000-10030&quot;
    #dtls_mtu = 1200
    #no_media_timer = 1
    #slowlink_threshold = 4
    #twcc_period = 100
    #dtls_timeout = 500
    # If you need DSCP packet marking and prioritization, you can configure
    # the &#39;dscp&#39; property to a specific values, and Janus will try to
    # set it on all outgoing packets using libnice. Normally, the specs
    # suggest to use different values depending on whether audio, video
    # or data are used, but since all PeerConnections in Janus are bundled,
    # we can only use one. You can refer to this document for more info:
    # https://tools.ietf.org/html/draft-ietf-tsvwg-rtcweb-qos-18#page-6
    # That said, DON&#39;T TOUCH THIS IF YOU DON&#39;T KNOW WHAT IT MEANS!
    #dscp = 46
}</code></pre>
<p>**
# NAT 관련 세팅입니다. 로컬에서만 테스트할 때는 설정이 필요없습니다. NAT를 통하는 경우에는 설정을 해야 합니다.
# stun_server : stun 서버를 지정합니다. default stun 서버가 사용 가능한 상태이므로, 주석만 해제하여 사용하도록 하겠습니다.
# stun_port : public으로 열어둔 stun 서버의 포트는 대부분 3478입니다. IANA에 지정돼 있는 well-known port로, 표준입니다. 마찬가지로 주석을 해제하여 사용하도록 하겠습니다.
# nice_debug : libnice를 설치했다면 사용이 가능합니다. true로 설정할 경우, debug 모드가 활성화되어 candidate 관련 로그가 debug level로 출력됩니다. default 값이 주석이 해제된 상태로 false 설정되어 있습니다. default 값을 사용하도록 하겠습니다.
# nat_1_1_mapping : NAT 맵핑을 설정합니다. 머신의 public IP address를 맵핑 값으로 설정합니다.
# 해당 파트 다음으로 오는 설정들은 모두 default 값을 사용합니다.
**</p>
<pre><code class="language-ini"># NAT-related stuff: specifically, you can configure the STUN/TURN
# servers to use to gather candidates if the gateway is behind a NAT,
# and srflx/relay candidates are needed. In case STUN is not enough and
# this is needed (it shouldn&#39;t), you can also configure Janus to use a
# TURN server# please notice that this does NOT refer to TURN usage in
# browsers, but in the gathering of relay candidates by Janus itself,
# e.g., if you want to limit the ports used by a Janus instance on a
# private machine. Furthermore, you can choose whether Janus should be
# configured to do full-trickle (Janus also trickles its candidates to
# users) rather than the default half-trickle (Janus supports trickle
# candidates from users, but sends its own within the SDP), and whether
# it should work in ICE-Lite mode (by default it doesn&#39;t). Finally,
# you can also enable ICE-TCP support (beware that this may lead to problems
# if you do not enable ICE Lite as well), choose which interfaces should
# be used for gathering candidates, and enable or disable the
# internal libnice debugging, if needed.
nat: {
    stun_server = &quot;stun.voip.eutelia.it&quot;
    stun_port = 3478
    nice_debug = false
    #full_trickle = true
    #ice_lite = true
    #ice_tcp = true
    # By default Janus tries to resolve mDNS (.local) candidates: even
    # though this is now done asynchronously and shouldn&#39;t keep the API
    # busy, even in case mDNS resolution takes a long time to timeout,
    # you can choose to drop all .local candidates instead, which is
    # helpful in case you know clients will never be in the same private
    # network as the one the Janus instance is running from. Notice that
    # this will cause ICE to fail if mDNS is the only way to connect!
    #ignore_mdns = true
    # In case you&#39;re deploying Janus on a server which is configured with
    # a 1:1 NAT (e.g., Amazon EC2), you might want to also specify the public
    # address of the machine using the setting below. This will result in
    # all host candidates (which normally have a private IP address) to
    # be rewritten with the public address provided in the settings. As
    # such, use the option with caution and only if you know what you&#39;re doing.
    # Make sure you keep ICE Lite disabled, though, as it&#39;s not strictly
    # speaking a publicly reachable server, and a NAT is still involved.
    # If you&#39;d rather keep the private IP address in place, rather than
    # replacing it (and so have both of them as advertised candidates),
    # then set the &#39;keep_private_host&#39; property to true.
    # Multiple public IP addresses can be specified as a comma separated list
    # if the Janus is deployed in a DMZ between two 1-1 NAT for internal and
    # external users.
    nat_1_1_mapping = &quot;1.2.3.4&quot;  # The machine&#39;s public IP address
    #keep_private_host = true
    # You can configure a TURN server in two different ways: specifying a
    # statically configured TURN server, and thus provide the address of the
    # TURN server, the transport (udp/tcp/tls) to use, and a set of valid
    # credentials to authenticate...
    #turn_server = &quot;myturnserver.com&quot;
    #turn_port = 3478
    #turn_type = &quot;udp&quot;
    #turn_user = &quot;myuser&quot;
    #turn_pwd = &quot;mypassword&quot;
    # ... or you can make use of the TURN REST API to get info on one or more
    # TURN services dynamically. This makes use of the proposed standard of
    # such an API (https://tools.ietf.org/html/draft-uberti-behave-turn-rest-00)
    # which is currently available in both rfc5766-turn-server and coturn.
    # You enable this by specifying the address of your TURN REST API backend,
    # the HTTP method to use (GET or POST) and, if required, the API key Janus
    # must provide. Notice that the &#39;opaque_id&#39; provided via Janus API will be
    # used as the username for a specific PeerConnection by default; if that one
    # is missing, the &#39;session_id&#39; will be used as the username instead.
    #turn_rest_api = &quot;http://yourbackend.com/path/to/api&quot;
    #turn_rest_api_key = &quot;anyapikeyyoumayhaveset&quot;
    #turn_rest_api_method = &quot;GET&quot;
    # You can also choose which interfaces should be explicitly used by the
    # gateway for the purpose of ICE candidates gathering, thus excluding
    # others that may be available. To do so, use the &#39;ice_enforce_list&#39;
    # setting and pass it a comma-separated list of interfaces or IP addresses
    # to enforce. This is especially useful if the server hosting the gateway
    # has several interfaces, and you only want a subset to be used. Any of
    # the following examples are valid:
    #     ice_enforce_list = &quot;eth0&quot;
    #     ice_enforce_list = &quot;eth0,eth1&quot;
    #     ice_enforce_list = &quot;eth0,192.168.&quot;
    #     ice_enforce_list = &quot;eth0,192.168.0.1&quot;
    # By default, no interface is enforced, meaning Janus will try to use them all.
    #ice_enforce_list = &quot;eth0&quot;
    # In case you don&#39;t want to specify specific interfaces to use, but would
    # rather tell Janus to use all the available interfaces except some that
    # you don&#39;t want to involve, you can also choose which interfaces or IP
    # addresses should be excluded and ignored by the gateway for the purpose
    # of ICE candidates gathering. To do so, use the &#39;ice_ignore_list&#39; setting
    # and pass it a comma-separated list of interfaces or IP addresses to
    # ignore. This is especially useful if the server hosting the gateway
    # has several interfaces you already know will not be used or will simply
    # always slow down ICE (e.g., virtual interfaces created by VMware).
    # Partial strings are supported, which means that any of the following
    # examples are valid:
    #     ice_ignore_list = &quot;vmnet8,192.168.0.1,10.0.0.1&quot;
    #     ice_ignore_list = &quot;vmnet,192.168.&quot;
    # Just beware that the ICE ignore list is not used if an enforce list
    # has been configured. By default, Janus ignores all interfaces whose
    # name starts with &#39;vmnet&#39;, to skip VMware interfaces:
    ice_ignore_list = &quot;vmnet&quot;
    # In case you want to allow Janus to start even if the configured STUN or TURN
    # server is unreachable, you can set &#39;ignore_unreachable_ice_server&#39; to true.
    # WARNING: We do not recommend to ignore reachability problems, particularly
    # if you run Janus in the cloud. Before enabling this flag, make sure your
    # system is correctly configured and Janus starts after the network layer of
    # your machine is ready. Note that Linux distributions offer such directives.
    # You could use the following directive in systemd: &#39;After=network-online.target&#39;
    # https://www.freedesktop.org/software/systemd/man/systemd.unit.html#Before=
    #ignore_unreachable_ice_server = true
}
# You can choose which of the available plugins should be
# enabled or not. Use the &#39;disable&#39; directive to prevent Janus from
# loading one or more plugins: use a comma separated list of plugin file
# names to identify the plugins to disable. By default all available
# plugins are enabled and loaded at startup.
plugins: {
    #disable = &quot;libjanus_voicemail.so,libjanus_recordplay.so&quot;
}
# You can choose which of the available transports should be enabled or
# not. Use the &#39;disable&#39; directive to prevent Janus from loading one
# or more transport: use a comma separated list of transport file names
# to identify the transports to disable. By default all available
# transports are enabled and loaded at startup.
transports: {
    #disable = &quot;libjanus_rabbitmq.so&quot;
}
# As a core feature, Janus can log either on the standard output, or to
# a local file. Should you need more advanced logging functionality, you
# can make use of one of the custom loggers, or write one yourself. Use the
# &#39;disable&#39; directive to prevent Janus from loading one or more loggers:
# use a comma separated list of logger file names to identify the loggers
# to disable. By default all available loggers are enabled and loaded at startup.
loggers: {
    #disable = &quot;libjanus_jsonlog.so&quot;
}
# Event handlers allow you to receive live events from Janus happening
# in core and/or plugins. Since this can require some more resources,
# the feature is disabled by default. Setting broadcast to yes will
# enable them. You can then choose which of the available event handlers
# should be loaded or not. Use the &#39;disable&#39; directive to prevent Janus
# from loading one or more event handlers: use a comma separated list of
# file names to identify the event handlers to disable. By default, if
# broadcast is set to yes all available event handlers are enabled and
# loaded at startup. Finally, you can choose how often media statistics
# (packets sent/received, losses, etc.) should be sent: by default it&#39;s
# once per second (audio and video statistics sent separately), but may
# considered too verbose, or you may want to limit the number of events,
# especially if you have many PeerConnections active. To change this,
# just set &#39;stats_period&#39; to the number of seconds that should pass in
# between statistics for each handle. Setting it to 0 disables them (but
# not other media-related events).
events: {
    #broadcast = true
    #disable = &quot;libjanus_sampleevh.so&quot;
    #stats_period = 5
}</code></pre>
<hr>
<h3 id="januspluginvideoroomjcfg">janus.plugin.videoroom.jcfg</h3>
<p>이 파트에서는 두 개의 방을 생성해두고 사용하는 sample을 보여줍니다.
방을 구성하는 속성들을 설정해보고 테스트할 수 있습니다.
이 방들은 사전에 Janus에서 이와 같이 config 설정으로 만들어야만 하는 것은 아닙니다.
Janus에 REST-API로 방 생성 요청을 할 수 있고, 원하는 옵션을 선택해서 방을 생성할 수 있습니다.
따로 설정을 바꾸어 테스트하는 것은 포스트에 포함하지 않겠습니다.</p>
<blockquote>
</blockquote>
<pre><code class="language-ini"># room-&lt;unique room ID&gt;: {
# description = This is my awesome room
# is_private = true|false (whether this room should be in the public list, default=true)
# secret = &lt;optional password needed for manipulating (e.g. destroying) the room&gt;
# pin = &lt;optional password needed for joining the room&gt;
# require_pvtid = true|false (whether subscriptions are required to provide a valid private_id 
#            to associate with a publisher, default=false)
# publishers = &lt;max number of concurrent senders&gt; (e.g., 6 for a video
#              conference or 1 for a webinar)
# bitrate = &lt;max video bitrate for senders&gt; (e.g., 128000)
# bitrate_cap = true|false (whether the above cap should act as a hard limit to
#            dynamic bitrate changes by publishers; default=false, publishers can go beyond that)
# fir_freq = &lt;send a FIR to publishers every fir_freq seconds&gt; (0=disable)
# audiocodec = opus|g722|pcmu|pcma|isac32|isac16 (audio codec(s) to force on publishers, default=opus
#            can be a comma separated list in order of preference, e.g., opus,pcmu)
# videocodec = vp8|vp9|h264|av1|h265 (video codec(s) to force on publishers, default=vp8
#            can be a comma separated list in order of preference, e.g., vp9,vp8,h264)
# vp9_profile = VP9-specific profile to prefer (e.g., &quot;2&quot; for &quot;profile-id=2&quot;)
# h264_profile = H.264-specific profile to prefer (e.g., &quot;42e01f&quot; for &quot;profile-level-id=42e01f&quot;)
# opus_fec = true|false (whether inband FEC must be negotiated; only works for Opus, default=false)
# video_svc = true|false (whether SVC support must be enabled; only works for VP9, default=false)
# audiolevel_ext = true|false (whether the ssrc-audio-level RTP extension must
#        be negotiated/used or not for new publishers, default=true)
# audiolevel_event = true|false (whether to emit event to other users or not, default=false)
# audio_active_packets = 100 (number of packets with audio level, default=100, 2 seconds)
# audio_level_average = 25 (average value of audio level, 127=muted, 0=&#39;too loud&#39;, default=25)
# videoorient_ext = true|false (whether the video-orientation RTP extension must
#        be negotiated/used or not for new publishers, default=true)
# playoutdelay_ext = true|false (whether the playout-delay RTP extension must
#        be negotiated/used or not for new publishers, default=true)
# transport_wide_cc_ext = true|false (whether the transport wide CC RTP extension must be
#        negotiated/used or not for new publishers, default=true)
# record = true|false (whether this room should be recorded, default=false)
# rec_dir = &lt;folder where recordings should be stored, when enabled&gt;
# lock_record = true|false (whether recording can only be started/stopped if the secret
#            is provided, or using the global enable_recording request, default=false)
# notify_joining = true|false (optional, whether to notify all participants when a new
#               participant joins the room. The Videoroom plugin by design only notifies
#               new feeds (publishers), and enabling this may result extra notification
#               traffic. This flag is particularly useful when enabled with require_pvtid
#               for admin to manage listening only participants. default=false)
# require_e2ee = true|false (whether all participants are required to publish and subscribe
#             using end-to-end media encryption, e.g., via Insertable Streams; default=false)
#}
general: {
    #admin_key = &quot;supersecret&quot;        # If set, rooms can be created via API only
                                    # if this key is provided in the request
    #lock_rtp_forward = true        # Whether the admin_key above should be
                                    # enforced for RTP forwarding requests too
    #events = false                    # Whether events should be sent to event
                                    # handlers (default=true)
    # By default, integers are used as a unique ID for both rooms and participants.
    # In case you want to use strings instead (e.g., a UUID), set string_ids to true.
    #string_ids = true
}
room-1234: {
    description = &quot;Demo Room&quot;
    secret = &quot;adminpwd&quot;
    publishers = 6
    bitrate = 128000
    fir_freq = 10
    #audiocodec = &quot;opus&quot;
    #videocodec = &quot;vp8&quot;
    record = false
    #rec_dir = &quot;/path/to/recordings-folder&quot;
}
# This other demo room here is only there in case you want to play with
# the VP9 SVC support. Notice that you&#39;ll need a Chrome launched with
# the flag that enables that support, or otherwise you&#39;ll be using just
# plain VP9 (which is good if you want to test how this indeed affect
# what receivers will get, whether they&#39;re encoding SVC or not).
room-5678: {
    description = &quot;VP9-SVC Demo Room&quot;
    secret = &quot;adminpwd&quot;
    publishers = 6
    bitrate = 512000
    fir_freq = 10
    videocodec = &quot;vp9&quot;
    video_svc = true
}</code></pre>
<hr>
<h3 id="janustransporthttpjcfg">janus.transport.http.jcfg</h3>
<blockquote>
</blockquote>
<p>**
# Janus에 http request를 보낼 때의 설정입니다.
# admin_http : Janus Admin / Monitor API를 위해 true로 설정합니다. Janus에 생성된 세션과 핸들 정보를 모두 볼 수 있습니다.
# 나머지 설정은 default로 두겠습니다. cors, 인증서 파트는 Janus 앞에 Web Server를 두어 설정할 수도 있습니다.
**</p>
<pre><code class="language-ini"># Web server stuff: whether any should be enabled, which ports they
# should use, whether security should be handled directly or demanded to
# an external application (e.g., web frontend) and what should be the
# base path for the Janus API protocol. Notice that by default
# all the web servers will try and bind on both IPv4 and IPv6: if you
# want to only bind to IPv4 addresses (e.g., because your system does not
# support IPv6), you should set the web server &#39;ip&#39; property to &#39;0.0.0.0&#39;.
general: {
    #events = true                    # Whether to notify event handlers about transport events (default=true)
    json = &quot;indented&quot;                # Whether the JSON messages should be indented (default),
                                    # plain (no indentation) or compact (no indentation and no spaces)
    base_path = &quot;/janus&quot;            # Base path to bind to in the web server (plain HTTP only)
    http = true                        # Whether to enable the plain HTTP interface
    port = 8088                        # Web server HTTP port
    #interface = &quot;eth0&quot;                # Whether we should bind this server to a specific interface only
    #ip = &quot;192.168.0.1&quot;                # Whether we should bind this server to a specific IP address (v4 or v6) only
    https = false                    # Whether to enable HTTPS (default=false)
    #secure_port = 8089                # Web server HTTPS port, if enabled
    #secure_interface = &quot;eth0&quot;        # Whether we should bind this server to a specific interface only
    #secure_ip = &quot;192.168.0.1&quot;        # Whether we should bind this server to a specific IP address (v4 or v6) only
    #acl = &quot;127.,192.168.0.&quot;        # Only allow requests coming from this comma separated list of addresses
}
# Janus can also expose an admin/monitor endpoint, to allow you to check
# which sessions are up, which handles they&#39;re managing, their current
# status and so on. This provides a useful aid when debugging potential
# issues in Janus. The configuration is pretty much the same as the one
# already presented above for the webserver stuff, as the API is very
# similar: choose the base bath for the admin/monitor endpoint (/admin
# by default), ports, etc. Besides, you can specify
# a secret that must be provided in all requests as a crude form of
# authorization mechanism, and partial or full source IPs if you want to
# limit access basing on IP addresses. For security reasons, this
# endpoint is disabled by default, enable it by setting admin_http=true.
admin: {
    admin_base_path = &quot;/admin&quot;            # Base path to bind to in the admin/monitor web server (plain HTTP only)
    admin_http = true                    # Whether to enable the plain HTTP interface
    admin_port = 7088                    # Admin/monitor web server HTTP port
    #admin_interface = &quot;eth0&quot;            # Whether we should bind this server to a specific interface only
    #admin_ip = &quot;192.168.0.1&quot;            # Whether we should bind this server to a specific IP address (v4 or v6) only
    admin_https = false                    # Whether to enable HTTPS (default=false)
    #admin_secure_port = 7889            # Admin/monitor web server HTTPS port, if enabled
    #admin_secure_interface = &quot;eth0&quot;    # Whether we should bind this server to a specific interface only
    #admin_secure_ip = &quot;192.168.0.1        # Whether we should bind this server to a specific IP address (v4 or v6) only
    #admin_acl = &quot;127.,192.168.0.&quot;        # Only allow requests coming from this comma separated list of addresses
}
# The HTTP servers created in Janus support CORS out of the box, but by
# default they return a wildcard (*) in the &#39;Access-Control-Allow-Origin&#39;
# header. This works fine in most situations, except when we have to
# respond to a credential request (withCredentials=true in the XHR). If
# you need that, uncomment and set the &#39;allow_origin&#39; below to specify
# what must be returned in &#39;Access-Control-Allow-Origin&#39;. More details:
# https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS
cors: {
    #allow_origin = &quot;http://foo.example&quot;
}
# Certificate and key to use for HTTPS, if enabled (and passphrase if needed).
# You can also disable insecure protocols and ciphers by configuring the
# &#39;ciphers&#39; property accordingly (no limitation by default).
certificates: {
    #cert_pem = &quot;/path/to/cert.pem&quot;
    #cert_key = &quot;/path/to/key.pem&quot;
    #cert_pwd = &quot;secretpassphrase&quot;
    #ciphers = &quot;PFS:-VERS-TLS1.0:-VERS-TLS1.1:-3DES-CBC:-ARCFOUR-128&quot;
}</code></pre>
<hr>
<h3 id="janustransportwebsocketsjcfg">janus.transport.websockets.jcfg</h3>
<blockquote>
</blockquote>
<p>**
# 웹 소켓 관련 설정입니다.
# Admin 관련 API는 http를 통해서만 request하겠습니다.
# 모두 default 값으로 설정하겠습니다.
**</p>
<pre><code class="language-ini"># WebSockets stuff: whether they should be enabled, which ports they
# should use, and so on.
general: {
    #events = true                    # Whether to notify event handlers about transport events (default=true)
    json = &quot;indented&quot;                # Whether the JSON messages should be indented (default),
                                    # plain (no indentation) or compact (no indentation and no spaces)
    #pingpong_trigger = 30            # After how many seconds of idle, a PING should be sent
    #pingpong_timeout = 10            # After how many seconds of not getting a PONG, a timeout should be detected
    ws = true                        # Whether to enable the WebSockets API
    ws_port = 8188                    # WebSockets server port
    #ws_interface = &quot;eth0&quot;            # Whether we should bind this server to a specific interface only
    #ws_ip = &quot;192.168.0.1&quot;            # Whether we should bind this server to a specific IP address only
    wss = false                        # Whether to enable secure WebSockets
    wss_port = 8989                # WebSockets server secure port, if enabled
    #wss_interface = &quot;eth0&quot;            # Whether we should bind this server to a specific interface only
    #wss_ip = &quot;192.168.0.1&quot;            # Whether we should bind this server to a specific IP address only
    #ws_logging = &quot;err,warn&quot;        # libwebsockets debugging level as a comma separated list of things
                                    # to debug, supported values: err, warn, notice, info, debug, parser,
                                    # header, ext, client, latency, user, count (plus &#39;none&#39; and &#39;all&#39;)
    #ws_acl = &quot;127.,192.168.0.&quot;        # Only allow requests coming from this comma separated list of addresses
}
# If you want to expose the Admin API via WebSockets as well, you need to
# specify a different server instance, as you cannot mix Janus API and
# Admin API messaging. Notice that by default the Admin API support via
# WebSockets is disabled.
admin: {
    admin_ws = false                    # Whether to enable the Admin API WebSockets API
    admin_ws_port = 7188                # Admin API WebSockets server port, if enabled
    #admin_ws_interface = &quot;eth0&quot;        # Whether we should bind this server to a specific interface only
    #admin_ws_ip = &quot;192.168.0.1&quot;        # Whether we should bind this server to a specific IP address only
    admin_wss = false                    # Whether to enable the Admin API secure WebSockets
    admin_wss_port = 7989                # Admin API WebSockets server secure port, if enabled
    #admin_wss_interface = &quot;eth0&quot;        # Whether we should bind this server to a specific interface only
    #admin_wss_ip = &quot;192.168.0.1&quot;        # Whether we should bind this server to a specific IP address only
    #admin_ws_acl = &quot;127.,192.168.0.&quot;    # Only allow requests coming from this comma separated list of addresses
}
# Certificate and key to use for any secure WebSocket server, if enabled (and passphrase if needed).
# You can also disable insecure protocols and ciphers by configuring the
# &#39;ciphers&#39; property accordingly (no limitation by default).
# Examples of recommended cipher strings at https://cheatsheetseries.owasp.org/cheatsheets/TLS_Cipher_String_Cheat_Sheet.html
certificates: {
    #cert_pem = &quot;/etc/letsencrypt/live/nimbus-cert/fullchain.pem&quot;
    #cert_key = &quot;/etc/letsencrypt/live/nimbus-cert/privkey.pem&quot;
    #cert_pwd = &quot;nimbus0425&quot;
    #ciphers = &quot;ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256&quot;
}</code></pre>
<hr>
<p>이로써 Janus Gateway Video room config 설정이 끝났습니다.
다음 포스팅에서는 Janus Gateway Server와 Janus demo page의 연동을 다루겠습니다.
감사합니다.👍</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Janus Gateway (1) :: WebRTC란 무엇인가? & 설치까지]]></title>
            <link>https://velog.io/@vamos_eon/Janus-Gateway-1-WebRTC%EB%9E%80-%EB%AC%B4%EC%97%87%EC%9D%B8%EA%B0%80-%EC%84%A4%EC%B9%98%EA%B9%8C%EC%A7%80</link>
            <guid>https://velog.io/@vamos_eon/Janus-Gateway-1-WebRTC%EB%9E%80-%EB%AC%B4%EC%97%87%EC%9D%B8%EA%B0%80-%EC%84%A4%EC%B9%98%EA%B9%8C%EC%A7%80</guid>
            <pubDate>Sat, 30 Oct 2021 16:22:33 GMT</pubDate>
            <description><![CDATA[<p>안녕하세요, 주니어 개발자 Eon입니다.</p>
<p>오늘은 <strong>WebRTC</strong>에 대해 알아보겠습니다.</p>
<p>최근에 코로나 팬데믹으로 재택 근무가 자리 잡고, 학교는 비대면 수업을 진행하게 됐습니다. 
그로 인해, 화상회의가 비대면 만남의 매개체가 돼 주고 있습니다.
화상회의는 어떻게 만들어졌는지, 어떤 기술인지 알아보겠습니다.</p>
<p>이후의 포스팅은 Janus Gateway를 구동 및 demo 페이지 연동을 통한 나만의 화상회의 구현 과정이 되겠습니다.</p>
<h3 id="what-is-webrtc">What is WebRTC</h3>
<p>WebRTC (Web Real-Time Communication)은 브라우저 상에서 영상이나 오디오같은 미디어를 스트림하고 데이터 교환까지 할 수 있게 하는 기술입니다. 
어쭙잖은 설명으로 원래의 정의나 기술에 대해 잘 전달하지 못하게 될 수 있으니 공식 문서 URL을 첨부하겠습니다.
<a href="https://developer.mozilla.org/ko/docs/Web/API/WebRTC_API">MDN Web DOCs :: WebRTC_API</a>
문서에서 설명하고 있듯이 WebRTC는 브라우저 상에서 이용되는 기술입니다.
브라우저에서 사용할 수 있으니 별도의 Application 설치가 필요 없고, 동시에 PC의 저장 공간을 차지하지 않는다는 장점이 있습니다.
그리고, WebRTC는 Peer-to-peer 방식을 사용하여 완전한 P2P를 지원합니다.
때문에 중간에 서버를 거치는 것이 아닌 이용자 간의 통신이 가능합니다.</p>
<p>NAT를 나가서 외부 통신을 하는 경우엔 다른 서버의 도움을 받아야 합니다.
<img src="https://images.velog.io/images/vamos_eon/post/11e7c1ce-9aa1-4766-bcd6-913a7f688073/WebRTC.gif" alt="">
(gif image from <a href="https://www.wowza.com/blog/what-is-webrtc">wowza blog :: what is webrtc</a>)
위의 gif 이미지를 보면 WebRTC Cloud Server와 2개의 End-point가 서로 통신하는 모습을 볼 수 있습니다.
위의 WebRTC Server가 아닌, STUN / TURN 서버를 통해 시그널링을 하며 P2P로 연결할 수도 있습니다. 
WebRTC Server를 통해 상대에 대한 정보(SDP, offer, answer)를 주고 받고 P2P 통신을 할 수 있게 됩니다. 
(WebRTC에 대한 좀 더 개념적인 설명은 따로 포스팅해서 연결하겠습니다.)
<a href="https://webrtc.github.io/samples/">WebRTC - samples</a>
위의 샘플을 통해 WebRTC 예제를 실행, 테스트 및 학습할 수 있습니다.</p>
<p>위의 모든 것들을 좀 더 쉽게 개발하고 이용할 수 있게 해주는 것이 오픈소스 WebRTC Server입니다. 
그리고 우리는 Janus Gateway를 사용하도록 하겠습니다. </p>
<h3 id="janus-gateway">Janus Gateway</h3>
<p>Janus Gateway는 위에서 짧게 설명한 WebRTC를 사용할 수 있게 구현한 WebRTC Server입니다. 
기본적인 WebRTC 기능을 포함하며, Meetecho 팀이 구현한 여러 종류의 플러그인이 제공됩니다. 
브라우저에서는 제공된 플러그인의 여러 기능(echo tests, conference bridges, media recorders, SIP gateways 등)을 사용할 수 있습니다. 
또한 Janus는 ICE, DTLS, SRTP 모두를 제공합니다. 따로 구성할 필요가 없습니다.
<a href="https://janus.conf.meetecho.com/">Janus Docs</a>
<a href="https://github.com/meetecho/janus-gateway">Janus-Gateway Github</a>
Janus는 ICE Server 역할을 포함합니다. 
ICE Server 역할을 한다는 것은 P2P 연결의 중간자 역할을 한다는 것입니다.
어? 분명 완전한 P2P라고 하지 않았나?
Janus를 사용하지 않고 WebRTC를 구현할 때, NAT 외부의 사용자와 연결할 경우엔 꼭 STUN이나 TURN에 요청해, 상대방의 SDP 정보를 받은 후에 P2P 연결이 가능합니다.
Janus를 거치지 않을 경우, TURN 서버가 ICE Server의 역할을 할 수 있습니다.
하지만 우리는 Janus를 사용한 케이스만 보기로 합니다.
Janus가 ICE Server의 역할을 하며, 브라우저는 ICE Server를 통해 P2P 연결을 구성합니다.
ICE : Interactive Connectivity Establishment (상호 연결을 돕는 프레임워크)
그리하여 Janus가 P2P 연결의 중간자 역할을 담당합니다.</p>
<h3 id="janus-installation">Janus installation</h3>
<p>Janus 설치는 <a href="https://github.com/meetecho/janus-gateway">Github 링크</a>를 참조바랍니다.
Janus Github repo 페이지를 보시면 설명이 아주 친절하게 되어 있습니다.
정말 설치 과정 하나는 따로 설명하지 않아도 될 정도입니다.</p>
<blockquote>
<h4 id="dependencies">Dependencies</h4>
<p><strong>To install it, you&#39;ll need to satisfy the following dependencies:</strong></p>
</blockquote>
<p>Jansson</p>
<ul>
<li>Janus는 C언어로 개발된 서버입니다. C언어에서 JSON타입을 인코딩/디코딩하기 위해 사용되는 라이브러리입니다.</li>
</ul>
<p>libconfig</p>
<ul>
<li>config 세팅을 위한 라이브러리로, Janus config file 구성을 위해 필수입니다.</li>
</ul>
<p>libnice (at least v0.1.16 suggested, v0.1.18 recommended)</p>
<ul>
<li>ICE(Interactive Connectivity Setup) 표준(RFC 5245 &amp; RFC 8445)을 구현하는 라이브러리입니다.</li>
</ul>
<p>OpenSSL (at least v1.0.1e)</p>
<ul>
<li>TLS 및 SSL 사용을 위한 라이브러리입니다.</li>
</ul>
<p>libsrtp (at least v2.x suggested)</p>
<ul>
<li>SRTP(Secure Real-time Transport Protocol)를 위한 라이브러리입니다. README.md에도 쓰여 있듯이 2.2.0 버전을 사용하시기 바랍니다. 2.3.0 버전 사용 시, 에러가 발생할 수 있으며, 이는 --enable-openssl 이나 --enable-nss flag를 설정함으로써 해결할 수 있다고 나와 있지만 에러가 발생한 버전이니 추천하지 않는 모양입니다. (설치 과정에도 2.2.0 버전을 사용하게끔 안내되어 있습니다.)</li>
</ul>
<p>usrsctp (only needed if you are interested in Data Channels)</p>
<ul>
<li>데이터 채널을 사용할 때에만 필요합니다. </li>
</ul>
<p>libmicrohttpd (at least v0.9.59; only needed if you are interested in REST support for the Janus API)</p>
<ul>
<li>Janus API를 REST 방식으로 사용하려면 필요합니다. </li>
</ul>
<p>libwebsockets (only needed if you are interested in WebSockets support for the Janus API)</p>
<ul>
<li>Janus는 Websocket 방식과 http를 사용한 long poll 방식으로 사용이 가능합니다. libwebsockets는 Websocket 방식을 사용할 때 필요합니다.</li>
</ul>
<p>cmake (only needed if you are interested in WebSockets and/or BoringSSL support, as they make use of it)</p>
<ul>
<li>Websocket 방식을 사용하려면 필요합니다.</li>
</ul>
<p>rabbitmq-c (only needed if you are interested in RabbitMQ support for the Janus API or events)</p>
<ul>
<li>Messaging에 queue방식을 사용할 때 필요합니다.</li>
</ul>
<p>paho.mqtt.c (only needed if you are interested in MQTT support for the Janus API or events)</p>
<ul>
<li>C언어 기반의 MQTT 클라이언트이며, IOT를 위한 라이브러리입니다.</li>
</ul>
<p>nanomsg (only needed if you are interested in Nanomsg support for the Janus API)</p>
<ul>
<li>몇 가지 일반적인 통신 패턴을 제공하는 소켓 라이브러리입니다. 네트워킹 계층을 빠르게, 확장 가능하게, 사용하기 쉽게 만드는 것을 목표로 합니다.</li>
</ul>
<p>libcurl (only needed if you are interested in the TURN REST API support)</p>
<ul>
<li>멀티프로토콜 파일 전송 라이브러리로, 클라이언트 사이드의 URL 전송 라이브러리입니다.</li>
</ul>
<p>이하 라이브러리는 본인이 사용할 플러그인에 따라 설치하면 됩니다.
A couple of plugins depend on a few more libraries:</p>
<p>Sofia-SIP (only needed for the SIP plugin)
libopus (only needed for the AudioBridge plugin)
libogg (needed for the VoiceMail plugin and/or post-processor, and optionally AudioBridge and Streaming plugins)
libcurl (only needed if you are interested in RTSP support in the Streaming plugin or in the sample Event Handler plugin)
Lua (only needed for the Lua plugin)</p>
<p>아래 라이브러리 / 도구는 필요합니다.
Additionally, you&#39;ll need the following libraries and tools:</p>
<p>GLib
zlib
pkg-config
gengetopt</p>
<p>이상, 설치에 앞서 dependencies를 확인했습니다.
이하, 설치를 진행하도록 하겠습니다.</p>
<hr>
<p>설치 과정은 Janus Gateway Github 페이지에 나온 순서대로 진행하시면 됩니다.
그럼 제가 설명할 것은 없습니다.
Janus 서버 빌드하는 과정에 많은 dependencies가 요구되어, 로컬 pc의 환경에 설치하는 게 부담이 될 수 있습니다.
그런 분들은 아래 Dockerfile을 참조하셔서 Image를 빌드한 후에 사용하시면 됩니다.</p>
<h4 id="dockerfile">Dockerfile</h4>
<blockquote>
<pre><code>FROM ubuntu:bionic
RUN echo &#39; \n\
APT::Periodic::Update-Package-Lists &quot;0&quot;;\n\
APT::Periodic::Unattended-Upgrade &quot;1&quot;;\n&#39;\
&gt; /etc/apt/apt.conf.d/20auto-upgrades
RUN set -x \
    &amp;&amp; apt-get update &amp;&amp; apt-get install -y build-essential snapd aptitude git wget golang \
    python3 python3-pip python3-setuptools python3-wheel ninja-build \
    libgstreamer1.0-dev libgirepository1.0-dev libunwind-dev apt-utils libsrtp-dev \
    gdb \
    &amp;&amp; aptitude install -y libmicrohttpd-dev libjansson-dev libnice-dev \
    libssl-dev libsofia-sip-ua-dev libglib2.0-dev libopus-dev libogg-dev \
    libconfig-dev libavutil-dev libavcodec-dev libavformat-dev libnanomsg-dev \
    libcurl4-openssl-dev liblua5.3-dev pkg-config gengetopt libtool automake curl jq httpie vim screen doxygen graphviz
RUN set -x \
    &amp;&amp; wget https://cmake.org/files/v3.16/cmake-3.16.2.tar.gz \
    &amp;&amp; tar -xvzf cmake-3.16.2.tar.gz \
    &amp;&amp; cd cmake-3.16.2 \
    &amp;&amp; ./bootstrap --prefix=/usr/local \
    &amp;&amp; make &amp;&amp; make install
RUN set -x \
    &amp;&amp; python3 -m pip install meson \
    &amp;&amp; python3 -m pip install ninja
RUN  set -x \
    &amp;&amp; cd  \
    &amp;&amp; wget https://github.com/cisco/libsrtp/archive/v2.2.0.tar.gz \
    &amp;&amp; tar xfv v2.2.0.tar.gz  \
    &amp;&amp; cd libsrtp-2.2.0/ \
    &amp;&amp; ./configure --prefix=/usr --enable-openssl \
    &amp;&amp; make shared_library &amp;&amp; make install
RUN  set -x \
    &amp;&amp; git clone https://boringssl.googlesource.com/boringssl \
    &amp;&amp; cd boringssl \
    &amp;&amp; sed -i s/&quot; -Werror&quot;//g CMakeLists.txt \
    &amp;&amp; mkdir -p build \
    &amp;&amp; cd build \
    &amp;&amp; cmake -DCMAKE_CXX_FLAGS=&quot;-lrt&quot; .. \
    &amp;&amp; make \
    &amp;&amp; cd .. \
    &amp;&amp; mkdir -p /opt/boringssl \
    &amp;&amp; cp -R include /opt/boringssl/ \
    &amp;&amp; mkdir -p /opt/boringssl/lib \
    &amp;&amp; cp build/ssl/libssl.a /opt/boringssl/lib/ \
    &amp;&amp; cp build/crypto/libcrypto.a /opt/boringssl/lib/
RUN  set -x \
    &amp;&amp; git clone https://github.com/sctplab/usrsctp \
    &amp;&amp; cd usrsctp \
    &amp;&amp; ./bootstrap \
    &amp;&amp; ./configure --prefix=/usr --disable-programs --disable-inet --disable-inet6 \ 
    &amp;&amp; make &amp;&amp; make install
RUN  set -x \
    &amp;&amp; git clone https://github.com/warmcat/libwebsockets.git \
    &amp;&amp; cd libwebsockets \
    &amp;&amp; mkdir build \
    &amp;&amp; cd build \
    &amp;&amp; cmake -DLWS_MAX_SMP=1 -DLWS_WITHOUT_EXTENSIONS=0 -DCMAKE_INSTALL_PREFIX:PATH=/usr -DCMAKE_C_FLAGS=&quot;-fpic&quot; .. \
    &amp;&amp; make &amp;&amp; make install
RUN set -x \
    &amp;&amp; git clone https://github.com/eclipse/paho.mqtt.c.git \
    &amp;&amp; cd paho.mqtt.c \
    &amp;&amp; make &amp;&amp; make install
RUN set -x \
    &amp;&amp; git clone https://github.com/alanxz/rabbitmq-c \
    &amp;&amp; cd rabbitmq-c \
    &amp;&amp; git submodule init \
    &amp;&amp; git submodule update \
    &amp;&amp; mkdir build &amp;&amp; cd build \
    &amp;&amp; cmake -DCMAKE_INSTALL_PREFIX=/usr .. \
    &amp;&amp; make &amp;&amp; make install
RUN set -x \
    &amp;&amp; git clone https://gitlab.freedesktop.org/libnice/libnice \
    &amp;&amp; cd libnice \
    &amp;&amp; meson --prefix=/usr build &amp;&amp; ninja -C build &amp;&amp; ninja -C build install
RUN export JANUS_WITH_POSTPROCESSING
RUN  set -x \
    &amp;&amp; git clone https://github.com/meetecho/janus-gateway.git \
    &amp;&amp; cd janus-gateway \
    &amp;&amp; sh autogen.sh \
    &amp;&amp; ./configure --prefix=/opt/janus \
    &amp;&amp; make \
    &amp;&amp; make install \
    &amp;&amp; make configs
RUN  set -x \
    &amp;&amp; rm -rf /libwebsockets \
    &amp;&amp; rm -rf /janus-gateway \
    &amp;&amp; rm -rf /boringssl
RUN adduser --disabled-password --gecos &#39;&#39; janus
USER janus
RUN /opt/janus/bin/janus --v
CMD [&quot;/opt/janus/bin/janus&quot;]</code></pre></blockquote>
<p>```
위의 순서로 진행하시면 되고, 아래 몇 개의 디렉터리를 제거한 이유는 단순히 image 크기를 줄이기 위함입니다.
맨 위의 update 관련한 내용은 OS 버전 업그레이드를 자동으로 수행하는 걸 방지합니다.
apt 패키지 설치하는 파트에서 시간이 꽤 많이 소요되며, 메모리를 많이 사용하게 됩니다.
빌드할 때 가급적 다른 프로세스를 추가 동작하지 않는 게 좋습니다.
한 번 빌드를 하면 각 스텝 별로 caching되기 떄문에 다시 빌드할 때에 큰 부담이 없습니다.
apt 패키지만 설치하는 image를 만들고 그 image를 사용해서 새로운 Dockerfile을 작성하셔도 됩니다.</p>
<hr>
<p>이상으로 WebRTC가 무엇인지, WebRTC Server인 Janus Gateway 설치 과정을 포스팅했습니다.
이 다음은 Janus Gateway demo 페이지를 로컬 환경에 띄우고 직접 빌드한 Janus Gateway 서버를 연동하는 포스트를 준비하겠습니다.
감사합니다.👍</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Docker (4) :: image 빌드, push]]></title>
            <link>https://velog.io/@vamos_eon/Docker-4-Image-%EB%B9%8C%EB%93%9C</link>
            <guid>https://velog.io/@vamos_eon/Docker-4-Image-%EB%B9%8C%EB%93%9C</guid>
            <pubDate>Wed, 27 Oct 2021 16:50:48 GMT</pubDate>
            <description><![CDATA[<p>안녕하세요, 주니어 개발자 Eon입니다.</p>
<p>이번엔 Docker image 빌드에 관한 포스팅입니다.
이전 포스트에서 Docker image를 pull해서 사용하는 방법까지 알아봤습니다.
이번 포스트에서는 직접 image를 만들어서 사용하려고 합니다.</p>
<ul>
<li>Docker Repo : image push 및 Version Control<ul>
<li>Docker Repo : Create Repository</li>
<li>Docker Repo : push image</li>
<li>Docker Repo : Version Control</li>
</ul>
</li>
<li>Docker image build : <ul>
<li>Dockerfile 작성 : C언어를 시작하는 환경 구성하기</li>
<li>Build image using Dockerfile</li>
</ul>
</li>
</ul>
<hr>
<h3 id="docker-repo--image-push-및-version-control">Docker Repo : image push 및 Version Control</h3>
<p>이전 포스트에서 Docker image를 pull해서 사용했습니다.
다른 누군가, 다른 단체가 빌드해서 public repository 저장소에 push해서 올린 image를 pull해서 다운로드 받은 것입니다.
이처럼 Docker image를 다른 사람이 사용할 수 있게 하려면 image를 <a href="https://hub.docker.com/">Docker hub</a>에 올려야 합니다.
검색을 통해 image를 찾고, 사용할 수 있게 됩니다.</p>
<h4 id="docker-repo--create-repository">Docker Repo : Create Repository</h4>
<p>그럼 일단 계정이 있어야 합니다.
그건 이전 포스트에서 Docker hub 가입도 마쳤으니 이번엔 개인 repository를 생성합니다.
<img src="https://images.velog.io/images/vamos_eon/post/0cff4da4-8297-468b-be99-5f6e852d6316/dockerhub1.png" alt="">
여기 Repositories로 들어가서
<img src="https://images.velog.io/images/vamos_eon/post/f5fd4b28-63fb-411a-aa6a-fa53ba43b75c/dockerhub2.png" alt="">
Create Repository를 누르면 repo를 생성할 수 있습니다.
생성할 때, public이나 private로 생성할 수 있습니다.
<strong>Docker hub subscribe를 하지 않은 상태라면 private repository는 계정당 1개만 생성할 수 있습니다.</strong>
public repo는 모두에게 오픈되어 있으며, 아무나 사용이 가능합니다.
private repo는 나를 제외한 모두에게 열람 권한이 없습니다.
image 여러 개를 나만 쓰고 싶은데 private repo를 1개만 생성 가능해서 불편하다고 느끼신다면 아래에서 소개할 image 빌드를 통해 어느 정도 불편함을 해소할 수 있습니다. (사실 해소라기 보다 다른 불편함으로 대체되는 것입니다..)</p>
<p>저는 repository 이름을 c-project라고 지었습니다.
제 계정이 eon이라고 한다면 제 repository path는 eon/c-project 입니다.
repo path라고 말씀드렸지만 사실은 이 자체가 image의 이름입니다.</p>
<p>아직 image 빌드를 하지 않은 상태라 repository에 올릴 image가 없습니다.
그래도 일단 repository를 만들었으니 push하는 방법을 알아보겠습니다.</p>
<hr>
<h4 id="docker-repo--push-image">Docker Repo : push image</h4>
<blockquote>
<pre><code>docker push &lt;Image_Name&gt;:&lt;TAG&gt;</code></pre></blockquote>
<pre><code>image는 기본적으로 image 이름과 태그로 구성되어 있습니다.
TAG는 버전을 기입하는 용도로 쓰입니다.
TAG는 image를 빌드할 때 붙일 수 있으며, 빌드 이후에도 수정이 가능합니다.

위에서 언급한 제 repository path인 eon/c-project 가 image 이름이라고 했습니다.
그래서 push를 할 때는 아래와 같이 됩니다. 
&gt;```
  docker push eon/c-project:&lt;TAG&gt;</code></pre><p>첫 이미지가 될 테니 TAG는 0.0.1로 하겠습니다.</p>
<pre><code>  docker push eon/c-project:0.0.1</code></pre><p>이렇게 명령어를 실행하면 제가 생성한 repository에 빌드한 이미지가 올라옵니다.</p>
<hr>
<h4 id="docker-repo--version-control">Docker Repo : Version Control</h4>
<p>Docker image에 TAG를 부여함으로써 image의 버전을 표기했습니다.
TAG를 붙여서 push를 함으로써, push되는 모든 image에는 버전이 붙게 됩니다.
그리고 그 버전들은 각각 별개의 image가 됩니다.
TAG에 따라 발생할 문제가 다 다를 테니, 문제가 있는 버전은 repo에서 제거함으로써 안전한 사용을 위한 관리도 할 수 있습니다.</p>
<hr>
<h3 id="docker-image-build">Docker image build</h3>
<p>Docker image를 빌드하려면 Dockerfile을 작성해야 합니다.
Dockerfile의 작성법은 단순하면서도 복잡합니다.
단순히 OS를 처음 설치하고, 내가 원하는 서비스를 설치하는 과정을 그대로 텍스트로 작성하면 되는 것입니다.</p>
<h4 id="dockerfile-작성--c언어를-시작하는-환경-구성하기">Dockerfile 작성 : C언어를 시작하는 환경 구성하기</h4>
<blockquote>
<pre><code></code></pre></blockquote>
<h1 id="dockerfile">Dockerfile</h1>
<h1 id="the-base-image-os-is-ubuntu-bionic">The base image (OS) is ubuntu bionic..</h1>
<p>FROM ubuntu:bionic</p>
<h1 id="run-set--x-is-as-same-as-to-run-these-commands-after-">RUN set -x is as same as to run these commands after &#39;&#39;..</h1>
<p>RUN set -x \</p>
<h1 id="update-apt-package-list-as-latest">Update apt package list as latest..</h1>
<pre><code>&amp;&amp; apt-get update \</code></pre><h1 id="install-gcc-and-gdb-gcc-for-compile-c-language-gdb-for-debugger">Install gcc and gdb.. gcc for compile c language, gdb for debugger..</h1>
<pre><code>&amp;&amp; apt-get install -y gcc gdb</code></pre><p>RUN set -x <br>    &amp;&amp; mkdir -p ~/cproject/helloworldDir</p>
<h1 id="in-dockerfile-is-for-comment-so-if-the-line-including--right-below-it-would-be-ignored-by-dockerfile-syntax">In dockerfile, &#39;#&#39;is for comment.. so if the line including &#39;#&#39; right below, it would be ignored by dockerfile syntax..</h1>
<h1 id="to-resolve-this-problem-separate-the-lines-like-below">To resolve this problem, separate the lines like below..</h1>
<p>RUN echo &quot;#include &lt;stdio.h&gt;&quot; &gt; ~/cproject/helloworldDir/helloworld.c
RUN echo &#39;<br>int main(void){\n <br>    printf(&quot;helloworld!\n&quot;);\n <br>    return 0;\n <br>}&#39; &gt;&gt; ~/cproject/helloworldDir/helloworld.c
RUN set -x <br>    &amp;&amp; cat ~/cproject/helloworldDir/helloworld.c
RUN set -x <br>    &amp;&amp; gcc -o ~/cproject/helloworldDir/helloworld ~/cproject/helloworldDir/helloworld.c</p>
<h1 id="you-may-choose-one-below">You may choose one below..</h1>
<p>#CMD [&quot;/root/cproject/helloworldDir/helloworld&quot;]
CMD [&quot;/bin/bash&quot;]</p>
<pre><code>Dockerfile 작성 시, 위의 내용의 경우, 개행하는 방법을 알아두어야 합니다.
개행할 때는 &quot;\&quot;로 하고, 이어지는 명령어 작성은 &quot;&amp;&amp;&quot;로 이어갈 수 있습니다.
FROM, RUN , CMD와 같은 명령어 블럭은 각각 한 **step**을 의미합니다.
(step을 구성할 수 있는 명령어는 [Dockerfile reference docs](https://docs.docker.com/engine/reference/builder/)에 소개돼 있습니다.)
Dockerfile을 빌드하다가 error를 만나서 빌드가 중단되는 경우가 종종 있습니다.
때문에 **step을 적절하게 나눠주는 것**이 중요합니다.
**이미 앞에서 성공한 step은 Docker가 caching하기 때문에, 성공한 step을 다시 빌드하지는 않습니다.
단, 성공한 step 1과 step 2 사이에 step을 하나 추가하게 되면 추가한 step부터 다시 빌드를 합니다.**
Dockerfile 빌드에 시간이 꽤나 소요되기 때문에 효율적으로 step을 나누는 것이 좋습니다.
추가로, step은 image layer로 등록돼서 push할 때 조각 조각으로 나눠서 push하게 됩니다.
step 하나가 굉장히 길어지게 되면 해당 image layer만 엄청 커져서 push할 때 시간이 오래 걸릴 수도 있고 리소스를 많이 사용하게 돼, 다른 실행 중인 프로세스에 좋지 않은 영향을 줄 수 있습니다.
추가 +) 위와 같이 소스 코드를 echo를 활용해서 작성하는 방법은 당연히 좋지 않은 방법입니다. 그냥 이런 것도 가능하다는 걸 보여드리기 위함입니다. ubuntu 위에 C언어 환경이 구성된 container를 만들기 위함이라면 apt 패키지 설치까지와 최하단 CMD만 남겨두시면 됩니다.

이렇게 환경을 구성하고, 설치하고 즉시 실행까지 가능합니다.
위의 주석처리된 CMD 파트를 실행하면 컴파일된 바이너리 파일이 실행됩니다.
출력 결과는 당연하게도 &quot;helloworld!&quot;입니다.

위의 내용을 Dockerfile이라는 파일명으로 저장하시면 됩니다.
물론 다른 이름으로 저장하셔도 상관없으나, vscode 등의 IDE를 사용할 때에 파일명을 Dockerfile로 사용하시면 가독성이나 syntax 체크가 훨씬 편할 수 있습니다. (IDE가 제공하는 font 색상 등으로 line이나 주석 구분이 쉬워짐)
***
#### Build image using Dockerfile
Dockerfile 빌드를 해보겠습니다.
아래와 같이 명령어 입력 후 실행합니다.
&gt;```
  docker build -t ${IMAGE_NAME:TAG} -f ./Dockerfile .</code></pre><p>-t : 태그 옵션입니다.
-f : 빌드할 Dockerfile의 path를 지정합니다. (Default is &#39;PATH/Dockerfile&#39;)
-. : PATH | URL 입력하는 부분입니다. build 후에 이미지를 전송합니다.</p>
<p>마지막에 &#39;.&#39;은 다른 PATH를 참조하지 않을 때 사용합니다.
Docker hub repository를 참조할 경우, &#39;.&#39;을 default로 사용합니다.
외에 -t나 -f 옵션은 말 그대로 옵션입니다.
하지만 image를 쉽게 재사용이나 버전 관리를 하기 위해  -t 옵션을 사용해서 태그를 붙입니다.
-f 옵션은 Dockerfile이 있는 디렉터리에서 빌드할 경우엔 필요없는 옵션입니다.</p>
<p>저는 태그를 지정해서 image를 빌드했습니다.
eon/c-project:0.0.1</p>
<p>이제, 포스트 초반에 설명한 push를 할 수 있습니다.</p>
<blockquote>
<pre><code>  docker push eon/c-project:0.0.1</code></pre></blockquote>
<p>  ```
이제 eon/c-project repository에 0.0.1 태그가 달린 eon/c-project:0.0.1 image가 push됐습니다.</p>
<p>image의 사용은 꼭 pull을 하고 써야 하는가?
아닙니다. 위와 같이 로컬에서 빌드하고 바로 사용할 수 있습니다.
때문에 repo path나 TAG 등은 무시하고 빌드해도 image의 사용에는 문제가 없습니다.</p>
<hr>
<p>이번 포스트에서는 Dockerfile로 image를 빌드하는 방법과 image를 repo에 push하는 방법을 소개했습니다.
위에 예시로 만든 image는 ubuntu bionic 기반, gcc와 gdb도 설치하니 해당 image로 container를 생성해서 구동시키면 그 안에서 c언어 컴파일과 실행 및 디버깅이 가능합니다.</p>
<p>이것으로 오늘 포스팅을 마치겠습니다.
감사합니다.👍</p>
]]></description>
        </item>
    </channel>
</rss>