Golang { Syntax int := 3 }

Golang Syntax _3


8. 기본 자료형

Go에 존재하는 자료형

  • bool : true 혹은 false를 나타내는 부울 타입이다.
  • string : 문자열
  • int [ int8, int16, int32, int64 ]
  • uint [ uint8, uint16, uint32, uint64 ]
  • rune [ int32의 alias이다. ] : 대개 유니코드 코드 포인트 값을 표현하는데 사용된다.
  • float32, float64
  • complex64, complex128

+ Go에선 Type casting을 위해선 uint8(10)과 같이 castring 할 수 있다.

 

9. 구조체 (struct)

type Vertex struct {
    X int
    Y int
}

func main() {
    v := Vertex{1, 2}
    v.X = 4
    fmt.Println(v.X)
}
  • type 구조체이름 struct { ... } 으로 정의하고, 구조체이름( ... )으로 선언한다.
  • 구조체 필드에 속한 데이터는 dot(.)으로 접근할 수 있다.
  • 구조체 리터럴(Struct Literals)은 필드와 값을 나열해 구조체를 새로 할당하는 방법이다.
    순서의 상관없이 {name: value}구문을 통해 할당 할 수 있다.

    package main
    import "fmt"
    
    type Vertex struct {
        X, Y int
    }
    
    var (
        p = Vertex{1, 2}  // has type Vertex
        r = Vertex{X: 1}  // Y:0 is implicit
        s = Vertex{}      // X:0 and Y:0
    )
    
    func main() {
        fmt.Println(p, r, s) // {1 2} {1 0} {0 0}
    }
    

     

10. 포인터

Go에선 포인터는 & 접두어로 사용되며, C처럼 포인터 연산은 지원하지 않는다.

package main

import "fmt"

type Vertex struct {
    X int
    Y int
}

func main() {
    p := Vertex{1, 2}
    q := &p
    q.X = 1e9
    fmt.Println(p) // {1000000000 2}
}
  • C에서의 동작과 유사하게 돌아간다. ( 포인터를 통해 접근시 가르키고 있는 데이터도 변경이 이루어짐 )
  • 포인터 연산이 안되니 오히려 C++의 참조자와 비슷한것아닌지…(주관적인생각)

11. new 함수

new(T)는 모든 필드가 0(Zero value)이 할당된 T 타입의 포인터를 반환한다.
(Zero value는 숫자 타입에서는 0, 참조 타입에선 nil을 뜻한다.)

package main
import "fmt"

type Vertex struct {
    X, Y int
}

func main() {
    v := new(Vertex)
    // or var v *Vertex = new(Vertex)
    fmt.Println(v)
    v.X, v.Y = 11, 9
    fmt.Println(v) // &{0 0} &{11 9}
}

 

12. 슬라이스 (Slices)

슬라이스란 Go에서의 배열의 값을 가르킨다. 그리고 배열의 길이(length)를 가지고 있다.

package main

import "fmt"

func main() {
    p := []int{2, 3, 5, 7, 11, 13}
    fmt.Println("p ==", p)
    fmt.Println("p[1:4] ==", p[1:4])

    // missing low index implies 0
    fmt.Println("p[:3] ==", p[:3])

    // missing high index implies len(s)
    fmt.Println("p[4:] ==", p[:])
    /** Output **
    p == [2 3 5 7 11 13]
    p[1:4] == [3 5 7]
    p[:3] == [2 3 5]
    p[4:] == [2 3 5 7 11 13]
    */
}
  • 슬라이스는 []T이렇게 선언 할 수 있으며 T 타입의 요소를 가지는 슬라이스이다.
  • 슬라이스에 저장된 데이터를 가져올때 [0보다 큰값:슬라이스의 길이보다 작은 값]을 통해서 부분적으로 잘라서 가져올 수 있다.
  • [lo:hi]일 경우 슬라이스의 lo번째 데이터에서 hi-1 번째 데이터 까지 가져온다. 전체를 가져오고 싶은 경우, 
    [:]을 통해서 모든 값을 가져 올 수 있다.
  • 슬라이스는 make(타입, 길이, 용량)함수를 통해서 만들 수 있다.
    cap()함수는 슬라이스의 용량(capacity)를 가져온다.

    package main
    import "fmt"
    
    func main() {
        a := make([]int, 5)
        printSlice("a", a)
        b := make([]int, 0, 5)
        printSlice("b", b)
        c := b[:2]
        printSlice("c", c)
        d := c[2:5]
        printSlice("d", d)
        fmt.Println(b[:4])
    }
    
    func printSlice(s string, x []int) {
        fmt.Printf("%s len=%d cap=%d %v\n",
            s, len(x), cap(x), x)
    }
    /** Output **
    a len=5 cap=5 [0 0 0 0 0]
    b len=0 cap=5 []
    c len=2 cap=5 [0 0]
    d len=3 cap=3 [0 0 0]
    [0 0 0 0]
    */
  • 빈 슬라이스는 슬라이스의 zero value가 nil 이다.
  • nil 슬라이스는 길이와 용량이 이다.
    package main
    import "fmt"
    
    func main() {
        var z []int
        fmt.Println(z, len(z), cap(z))
        if z == nil {
            b := [...]string{"Penn", "Teller"}
            fmt.Println("nil!", cap(b))
        }
    }
    /** Output **
    [] 0 0
    nil! 2
    */

 

13. 레인지 Range

for 반복문에서 range를 사용하면 슬라이스나 맵을 순회(iterates)할 수있다.

package main

import "fmt"

var pow = []int{1, 2, 4, 8, 16, 32, 64, 128}

func main() {
    for i, v := range pow {
        fmt.Printf("2**%d = %d\n", i, v)
    }
}
/** Output **
2**0 = 1
2**1 = 2
2**2 = 4
2**3 = 8
2**4 = 16
2**5 = 32
2**6 = 64
2**7 = 128
*/ 
  • for index, value := range pow {...}에서 index와 value는 슬라이스의 index와 value를 의미한다.
  • 만약 index나 value를 무시하려면 _를 통해서 무시하고 진행 할 수 있다.
    for _, v := range pow {...} or for i := range pow {...}

 

14. 슬라이스 연습

Pic이라는 함수를 구현합니다. 이 함수는 dy개 만큼의 길이를 가지는 슬라이스를 리턴해야 하는데, 각각의 요소들은 또한 dx 개의 8비트 부호없는 8비트 정수 타입을 가지는 슬라이스입니다. 프로그램을 실행하면 이 정수값들을 흑백 (사실은 파란색)을 나타내는 값으로 해석하여 그림을 보여줄 것입니다.

package main
import "code.google.com/p/go-tour/pic"
	
func Pic(dx, dy int) [][]uint8 {
    y := make([][]uint8, dy, dy)
    
    for i := 0; i < int(dy); i++{
    	y[i] = make([]uint8, dx, dx)
    }
    for i, pe := range y {
        for x := 0; x < dx; x++{
            pe[x] = uint8(x) ^ uint8(i)
        }
    }
	return y
}

func main() {
	pic.Show(Pic)
}

=== Output ===

 

Golang { Syntax int := 2 }

Golang Syntax _2


5. 반복문

package main
import "fmt"

func main() {
    sum := 0
    for i := 0; i < 10; i++ {
        sum += i
    }
    fmt.Println(sum)
}
  • Go에선 반복엔 for만이 존재하며 while 은 사용하지 않는다.
  • for 반복문은 C와 유사하지만 소괄호는 사용하지 않는다. 하지만 실행문을 위해선 중괄호가 필요하다.
  • for sum < 2000 { ... }
    C처럼 전, 후처리문을 제외한 조건문만을 사용할 수 있다. (while과 유사하다.)
  • for { ... }
    무한 루프의 경우 아무 조건을 작성하지 않으면 된다.

 

6. 조건문

package main
import (
    "fmt"
    "math"
)

func sqrt(x float64) string {
    if x < 0 {
        return sqrt(-x) + "i"
    }
    return fmt.Sprint(math.Sqrt(x))
}

func main() {
    fmt.Println(sqrt(2), sqrt(-4))
}
  • for(반복문)과 유사하게 조건의 소괄호 ( ) 을 사용하지 않으며, 실행문을 위한 중괄호 { }가 필요하다.
    다만 C 와 다른점은 실행문이 한줄뿐이더라도 반드시 중괄호{ } 가 필요하다.
  • if v := math.Pow(x, n); v < lim{ ... }
    if의 조건문 앞에 짧은 문장을 실행 할 수 있다.
  • if의 짧은 명령문에서 선언된 변수는 else if, else구문에서도 사용할 수 있다.
    package main
    import (
        "fmt"
        "math"
    )
    
    func pow(x, n, lim float64) float64 {
        if v := math.Pow(x, n); v < lim {
            return v
        } else if v == lim {
            fmt.Printf("%g == %g\n", v, lim)
        } else {
            fmt.Printf("%g >= %g\n", v, lim)
        }
        // can't use v here, though
        return lim
    }
    
    func main() {
        fmt.Println(
            pow(3, 2, 10),
            pow(3, 3, 27),
        )
    }
    

7. 연습문제[src]

7-1 함수와 루프의 사용법을 익히는 간단한 연습으로, 제곱근 함수를 뉴턴의 방법(Newton’s method)을 이용하여 구현합니다.
여기서 뉴턴의 방법이란 초기값 z를 선택한 후에 다음의 공식을 이용하여 반복적으로 Sqrt(x) 함수의 근사값을 찾아가는 방법을 말합니다:
z = z - (z * z - x) / (2 * z)
처음에는 계산을 10번만 반복하여 여러분이 작성한 함수가 다양한 값들 (1, 2, 3, …)에 대하여 얼마나 정확한 값을 찾아내는지 확인합니다.

7-2 루프의 조건을 수정하여 값이 더이상 바뀌지 않을 때 (혹은 아주 작은 차이가 발생할 때) 루프를 종료하도록 합니다. 이렇게 하면 반복문의 실행 횟수가 어떻게 달라지는지 확인합니다. 결과값이 math.Sqrt함수의 값과 얼마나 비슷한가요?

package main
import (
    "fmt"
    "math"
)

func Sqrt(x float64) float64{
    var a, z, c float64 = 0, 1, 0
    for a = 1; a < 10; a++ {
        if math.Abs(z - c) > 1e-10 {
            c = z
            z = z - (z * z - x)/(2*z)
        } else {
            break
        }
    }
    return z
}

func main() {
    fmt.Println(Sqrt(2))    
}

Golang { Syntax int := 1 }

Golang Syntax


https://go-tour-kr.appspot.com/

1. Hello GO!

package main
import (
    "fmt"
    "math"
)

func main() {
    fmt.Println("Happy", math.Pi, "Day")// Happy 3.141592653589793 Day
}

Go 프로그램은 모두 package(패키지)로 구성 되어 있다.
위 Go 코드는 main패키지로 부터 가져오고 있다.

위 코드에선 fmtmath를 import 하고 있다.
import( ..., ... )
와 같이 한번에 입력 할 수 도 있으며,
import fmt 
import math
 
로 각각 표현할 수 있다.

패키지를 import 하면 패키지의 메소드, 변수, 상수등을 접근하기 위해선 export 해주어야 하는데,
Go에서는 첫 문자가 대분자로 시작하면, 외부에서 접근이 가능한 exported name이 된다. 

즉, math.pi 는 외부에선 접근이 불가능하지만 math.Pi는 외부에서 접근이 가능하단 뜻이다. 

 

2. 함수

package main
import "fmt"

func add(x int, y int) int {
    return x + y
}

func main() {
    fmt.Println(add(42, 13))// 55
}

Go에서의 함수 선언은 func 함수명(매개변수) 반환형으로 정의한다.

  • 두개 이상의 매개변수를 받을 때 같은 타입일 경우 마지막 매개변수에만 타입을 명시하고 나머지는 생략 할 수 있다.
    add(x int, y int) >> add(x, y int)
  • Go의 함수에선 여러개의 결과를 return 할 수 있다.
    package main
    import "fmt"
    
    func swap(x, y string) (string, string) {
        return y, x
    }
    
    func main() {
        a, b := swap("hello", "world")
        fmt.Println(a, b)// world hello
    }

    대신 반환형에 각각 반환할 타입((string, string))을 적어줘야한다.

  • Go의 함수에선 반환할 변수들의 이름을 미리 정해 놓고 사용 후, 간단히 return할 수 있다.
    package main
    import "fmt"
    
    func split(sum int) (x, y int) {
        x = sum * 4 / 9
        y = sum - x
        return
    }
    
    func main() {
        fmt.Println(split(17))//7 10
    }
    

     

3. 변수

Go에선 변수를 선언하는 다양한 방법이 있다.

  • var x, y, z int
    var 키워드를 통해 여러 변수를 간히 변수를 선언할 수 있다.
    참고로 아무값도 넣지 않을 경우 각각의 기본 값 (int: 0, bool: false, string: “”)으로 초기화 된다.
    + 아무값도 넣지 않는 경우엔 자료형을 지정해주지 않는다면, 에러가 발생한다.
  • var c, python, java = true, false, "no!"
    또한 초기화시 따로 자료형을 명시하지 않는다면, 자료형을 알아서 정해준다.
  • x, y, z := 1, 2, 3
    := 키워드를 사용하면 간단히 변수를 사용할 수 있으며, 알아서 자료형을 정해준다.
    하지만 := 키워드는 함수 밖에선 사용할 수 없고, x int := 1 처럼 자료형을 지정해 줄 수 없다.

 

4. 상수

const Pi = 3.14와 같이 const 키워드를 통해 문자, 문자열, 부울, 숫자 자료형을 상수로 선언 할 수 있다.

  • 여러 상수를 선언할 땐 const ( ... )로 상수 여러개를 한번에 지정해 줄 수 있다.
    const (
        Pi = 3.14
        hello = "world"
    )
  • 상수는 문맥의 따라 자동으로 타입을 지정해주게 된다.
    또한 숫자의 경우 일반적인 변수 보다 정밀한 값을 표현 할 수 있다.

    package main
    import "fmt"
    
    const (
        Big   = 1 << 100
        Small = Big >> 99
    )
    
    func needInt(x int) int { return x*10 + 1 }
    func needFloat(x float64) float64 {
        return x * 0.1
    }
    
    func main() {
        fmt.Println(needInt(Small))  // 21
        fmt.Println(needFloat(Small))// 0.2
        fmt.Println(needFloat(Big))  // 1.2676506002282295e+29
    }
    

[C++/Syntax] 예외 처리와 형변환

Try … catch

try {} 구문 안의 내용중 예외(Exception)이 발생한다면, catch구문안의 내용을 실행하는 문법이다.
이 예외라는것은 런타임 에러와 아주 조금이나마 유사하다고 볼수있을것이다.
컴파일 중엔 에러가 발생하지 않는 에러에 대해서 대처할 수 잇기 때문이다.


Syntax

try{
   Some Code ...
} catch (exception_type val) {
   Exception controlling ...
}

위와 같은 구문이며 if else if 처럼 catch를 중첩해서 쓸 수 도있다.

 

Throw

예외를 발생 시키는 키워드 이다.
만약 try 구문 안에서 throw가 이루어진다면 catch로 throw된 에러가 전달 된다.

try{
    ...
    throw expction;
    ...
} catch (type expc){
    ...
}

위처럼 원하는 예외를 발생 시켜 catch구분으로 넘어가게 한다.
만약 다른 try 구문안에 존재하는 함수 내부에서 throw가 발생했을때 만약 그안에 catch구문이 없다면, catch를 만날때 가지 함수 밖(?)으로 나가게된다.
그 과정에서 쌓여있던 (stack)이 풀리게(free)된다. 이를 스택 풀기(Stack unwinding)이라고 하는 것 같다.

++ 예외(throw)의 경우 함수 밖으로 나가게 될때 해당 함수의 반환(return)타입에 국한되지 않고 함수를 빠져나가게되며, 에러를 전달한다.
물론 이를 제한하기 위한 방법이 아래와 같은 방법으로 존재한다.

int func(int num) throw(int){...}
위와 같이 함수를 선언하게 되면 (int)형으로만 예외 발생을 제한하며,
만약 throw ( ) 와같이 안에 아무것도 들어있지 않다면, 어떠한 예외도 전달하지 않는 단 뜻이다. 
이때 호출되는 함수는 unexpected()이다. 이 함수가 호출되면 프로그램을 종료하는 함수를 호출해 프로그램을 강제 종료 시킨다.

 

예외 클래스

예외를 발생 시킬 때 type에 따라 받을 수 있다고 했는데, class를 통해서도 받을 수 있다.
이를 예외 클래스라고 하는데, 일반 클래스와 다른게 거의 없고 throw를 통해 throw CLASS var(param)이렇게 하게되면 해당 클래스로된 예외가 전달 되게 되며,
해당 클래스를 catch하는 구문이 나올때 까지 전달하게 된다.

다만, 클래서에서 상속에 대해선 조심해야되는데, 만약 예외 클래스A를 상속한 예외 클래스 B, C 가있다면,
이를 catch할 때 A를 먼저 catch하게 되면 B C예외가 발생하더라도 A클래스를 catch하는 catch문에 잡히게 되므로 주의를 요한다.

 

모든 예외를 처리하는 catch 블록

어떠한 예외가 발생하더라도 모든 자료형에 대해서 catch하는 try-catch구문이 있다.
try { } catch (...) { }이 구문에서 ( … )는 모든 예외를 받겠단 뜻이다.

 


C++에서의 형변환

C에선 형변환을 위해선 (int)var 같은 형식으로 작성하였는데, C++에선 오래된 형변환으로 지원만할 뿐 C++에서 사용되는 형 변환방법이 따로 존재한다.

  • static_cast
    : A 타입에서 B타입으로 
  • const_cast
    : const 성향을 삭제
  • dynamic_cast
    : 상속 관계에서의 안전한 형변환
  • reinterpret_cast
    : 상관없는 자료형으로 변환

이들을 사용하기 위해선 ~~_cast<바꿀 type>var 이렇게 사용하면 된다. 

C++ 문법정리 끝.

[C++/Syntax] Template (in Class)

클래스에서의 탬플릿

템플릿은 함수뿐만아니라 클레스에서도 사용이가능하다.
대부분의 경우 선언, 작동방식, 특수화의 경우는 함수와 유사하다.

동일한 부분에 대해선 짧게 넘어가고, 클래스와 템플릿에 대해서 설명하지 못한 부분에대해서 추가적으로 이야기해본다.


Syntax

template <typename T>
class TEST{
private:
    T num;
public:
    TEST(T x=0);
    void show();

함수와 사용방법은 동일하다. template을 선언하고 <typename T>를 기준으로 T로 설정하고 싶은 변수들에게 T를 부여하면된다(..?)

당연하겠지만 TEST를 변수로 갖는 TESTER 클래스가 있다면, T에 TEST 클래스가 들어갈 수 있게된다. TESTER<TEST<int>>() 와 같은 형식으로..

또한 함수처럼 클래스를 다음과 같이 정의하여 특수화를 할 수 있다.
template<>
class TEST<int> { ... }

이렇게 정의한다면, TEST<int>에 대해선 일반 템플릿을 사용하지 않고 사용자가 선언한 클래스에서 불러오게된다.

++ 부분특수화가 가능하다.
template <class T1>
class TEST<T1, int> {...}
위와같은 방식으로 하게되면 < ? , int>인 템플릿 클래스는 모두 위 클래스를 불러온다.

템플릿에게도 인자를 부여할 수 있는데 이를 템플릿 메개변수라고 한다.
template <class T, int len>
class TEST {...}

이를 호출할땐 TEST<int, 5> test; 와 같이 정의 할 수 있다.

또한 템플릿 매개변수는 디폴트 값 설정도 가능하다.
template <class T, int len=10>
class TEST {...}

호출할때 따로 len값을 전달하지 않으면 10이 기본적으로 들어가게된다.

 

템플릿과 static 변수

static은 같은 템플릿 함수 내부에서 값을 공유 하는데, 이는 템플릿의 type에 따라 각각 작동한다.
예를들어, TEST<int>()에서의 static값을 TEST<long>()에서의 static값에 영향을 주지 못한다.

클래스 또한 유사한 방식으로 작동한다.
템플릿 클래스 별로 static 변수를 공유하게된다.
즉, class<int>와 class<int> 의 static변수는 서로 공유되지만, class<int>와 class<long>의 static변수는 다르게 작동한다.

 

[C++/Syntax] Template (in Func)

템플릿(Template)

템플릿이란 ‘틀’이란 뜻 그대로 틀만을 짜주고, 이후 컴파일 과정에서 컴파일러가 알아서 해주는 도구이다.
우린 틀만을 만들어주면 컴파일러가 완성된 코드를 만들어 실행하게 하는 방식으로 하여금 코드의 크기를 줄이고 반복된 코드를 줄인다.


Syntax

선언

template <typename T> 혹은 <class T>
T test(T num1, T num2){
    return num1+num2;
}

위 예시를 보면 타입명 대신 T가 들어간 것을 볼 수 있다. 
해당 템플릿으로 선언된 함수를 호출 하는 코드는 아래와 같다.

test<int>(1,2); test<double>(1.1,2.2);

위 처럼 test<타입>(..)과 같이 호출하게 되면 T 대신 ‘타입’이 들어가게 되면서 호출된다.
혹은 test(1.1, 2) 이렇게 호출하더라도 컴파일러가 알아서 입력된 데이터가 손실 되지 않는 자료형으로 바꿔주기도 한다.
즉, test(1.1, 2)를 호출한다면 test<dobule>(1.1, 2)가 호출 된다는 뜻이다.

둘 이상의 type의 경우

선언은 

template <typename T1, typename T2> 혹은 <class T1, class T2>
T test(T1 num1, T2 num2){
    return (T1)num1+(T2)num2;
}

위와 같이 선언하며, 호출시에는 

test<int, int>(1, 2); test<int, double>(1, 2.2);

위처럼 각각의 형을 정의해 준다.


대략적인 동작원리

템플릿은 컴파일러가 선언된 템플릿을 기반으로 호출되는 각각의 자료형 마다 함수를 새로 만든다.

즉, test<int>()를 호출하게되면,
int test(int num1, int num2) ...가 컴파일시 생성된다는 뜻이다.

이는 컴파일 과정에서 시간이 걸리지만 실행에 있어서는 문제가 되지 않는다.
참고로 이렇게 템플릿으로 생성된 함수를 ‘템플릿 함수’라고하며, 함수의 템플릿을 ‘함수 템플릿’이라고 한다.

또한 사용자가 int test(..)를 정의해놓앗다면 컴파일러는 따로 템플릿 함수를 생성하지 않고 정의된 함수를 사용한다.


함수 템플릿의 특수화

앞서 말한것 중에서 자용자가 int test(…)와 같이 미리 정의한것이 있다면 해당 함수를 불러온다고 이야기 했다.
이는 일정 자료형에 대해서 사용자가 다른 동작을 원하는 경우 따로 코드를 작성해 사용할 수 있다는 뜻이다.

위 내용을 종합해만든 아래 코드를 보자.

template <typename T1, class T2>
void print(T1 num1, T2 num2);

template <>
void print<int, double>(int num1, double num2);

int main() {
	print(1, 2);
	print(1, 2.3);
	return 0;
}

template <typename T1, class T2>
void print(T1 num1, T2 num2) {
	std::cout << num1 << " " << num2 << std::endl;
}

template <>
void print<int, double>(int num1, double num2) {
	std::cout << num1 << " dobule:" << num2 << std::endl;
}

템플릿 함수를 선언한 후에,
template<> 아래에선 특별한 경우 (int, double)가 들어올 경우 두 값사이에 ‘double:’ 을 넣도록 하고 있다.
이처럼 몇몇 경우에 대해서 사용자가 따로 특수화를 시켜 다른 동작을 하게 하는 것 또한 가능하다.


파일을 나누어 저장 할 경우 주의사항

컴파일러는 파일별로 컴파일 한다는 것을 알고있다.
하지만 템플릿은 컴파일러가 템플릿을 보고 각각의 형에 따라 함수를 만든다.

즉, 헤더파일에 템플릿를 선언만 해놓고 cpp에서 해당 템플릿을 정의할 경우 헤더따로, cpp 파일 다로 컴파일하기에 에러가 발생한다.
이럴경우 헤더 뿐만아니라 ‘ #include “~~.cpp” ‘와 같이 템플릿이 정의된 cpp파일을 include 해야한다.

[C++/Practice] String Class의 구현

Hand made String Class

지금까지 공부한 내용을 바탕으로 C++의 String 클래스를 직접 만들어 본다.
String Class의 기능들은 다음과 같다.

  1. 선언 방식은 String(), String(char*), String(String)이 있다.
  2. ‘==’ 연산은 두 문자열을 비교한다.
  3. ‘+’ 연산은 두 문자열을 연결한 String class를 반환한다.
  4. ‘+=’ 연산은 기존 String class에 이어 붙여 준다.
  5. ‘<<‘ 연산은 문자열을 출력(out)한다.
  6. ‘>>’ 연산은 문자열을 입력(in)받는다.

위와 같은 기능을 하는 String class를 직접 만들어본다.


String.h Class Header

#ifndef CUS_STR
#define CUS_STR
#include <cstring>
#include <iostream>

class String {
private:
	int len;
	char *str;
public:
	String();
	String(const char *s);
	String(const String &s);
	~String();
	String&	operator=	(const String& s);
	String&	operator+=	(const String& s);
	bool	operator==	(const String& s);
	String	operator+	(const String& s);

	friend std::ostream& operator<< (std::ostream& os, const String& s);
	friend std::istream& operator>> (std::istream& is, String& s);
};

#endif // !CUS_STR

String.cpp Class code

#include "Stringh.h"

String::String() {
	len = 0;
	str = NULL;
}

String::String(const char *s) {
	len = strlen(s) + 1;
	str = new char[len];
	strcpy(str, s);
}

String::String(const String& s) {
	len = s.len;
	str = new char[len];
	strcpy(str, s.str);
}

String::~String() {
	if (str != NULL)
		delete[]str;
}

String& String::operator= (const String& s) {
	if (str != NULL)
		delete[]str;

	len = s.len;
	str = new char[len];
	strcpy(str, s.str);
	return *this;
}

String& String::operator+= (const String& s) {
	len += s.len;
	char *tmpstr = new char[len];
	strcpy(tmpstr, str);
	strcat(tmpstr, s.str);

	if (str != NULL)
		delete[]str;
	str = tmpstr;
	return *this;
}

bool String::operator==	(const String& s) {
	return strcmp(str, s.str) ? false : true;
}

String String::operator+ (const String& s) {
	char *tmpstr = new char[len + s.len - 1];
	strcpy(tmpstr, str);
	strcat(tmpstr, s.str);

	String tmp(tmpstr);
	delete[]tmpstr;
	return tmp;
}

std::ostream& operator<< (std::ostream& os, const String& s) {
	os << s.str;
	return os;
}

std::istream& operator>> (std::istream& is, String& s) {
	char str[100];
	is >> str;
	s = String(str);
	return is;
}

Main.c

#include "Stringh.h"
int main()
{
	String str1 = "Str 1";
	String str2 = "Str 1";
	String str3 = str1 + str2;

	std::cout << str1 << std::endl;
	std::cout << str2 << std::endl;
	std::cout << str3 << std::endl;

	str1 += str2;
	if (str1 == str3)
		std::cout << "동일!" << std::endl;
	else
		std::cout << "다름!" << std::endl;

	String str4;
	std::cout << "문자열 입력: ";
	std::cin >> str4;
	std::cout << "입력된 문자열: ";
	std::cout << str4 << std::endl;
	return 0;
}

 

[C++/Syntax] 연산자 오버로딩

Operation Overloading

C++에선 연산자를 호출할 때 a+b를 그대로진행하는 것이 아닌 a.operator+(b)와 같은 함수형식으로 연산을 진행한다.
즉, +, /, -, *, =, += 과 같은 연산자는 함수오버로딩을 지원해, 클래스마다 연산방식을 제어 해 줄 수 있다.


Syntax

문법은 '반환형' operator'연산자'(피연산변수){ ... }이다.
예를 들어 ‘+’ 연산자를 오버로딩 할 경우

class Test2 {
    ...
    Test2 operator+(const Test2 &ref) {
        Test2 t(Test2num + ref.Test2num);
        return t;
    }
}

위 처럼 class 내부에서 할 수 있으며, Test2형 클래스를 ‘+’ 연산을 할 경우 Test2::operator+( ... ) 함수를 호출하게 된다.

또한 전역함수로 연산자 오버로딩을 할 경우 frind가 적절히 사용되는 몇 안되는 경중 하나인데,

friend Test2 operator+(const Test2 &ref, const Test2 &ref1);
...
Test2 operator+(const Test2 &ref, const Test2 &ref1) {
    return ref.Test2num + ref2.Test2num;
}

Test2 클래스 끼리 + 연산을 할 때 내부 operator+함수가 아닌 전역 함수인 operator+( 피연산자1 , 피연사자2 )를 호출하게 된다.


단항 연산자의 오버로딩

단항 연산자 또한 오버로딩이 가능하다. 
다만, 특정 형태에 대해서 전역 함수형태로 오버로딩된 연산자는 동작이 다르게 되는데 이에 대해서도 알아보자.

class Optest {
private:	int num;
public:	Optest(int num) : num(num) {}	void Show() { std::cout << num << " "; }
	Optest& operator++() {
		num += 1;
		return *this;
	}
	friend Optest& operator--(Optest &ref);
};

Optest& operator--(Optest &ref) {
	ref.num -= 1;
	return ref;
}

int main(){
	Optest op(10);
	++op;	op.Show();
	--op;	op.Show();

	++(++op);	op.Show();
	--(--op);	op.Show();
	return 0;
}

++의 경우 return값이 *this 이다. 이를 연산하는 과정은
++(++op) > ++(op.operator++(10)) > ++ (op의 참조값); > (op의 참조값).operator++(  )
위와 같이 진행된다. 계산이 완료되고 op의 참조값이 반환되면서 일반적인 ++ 연산이 가능한것이다.

–의 경우엔 return값이 ref 이다. 전달받은 잠조자를 return한다. 이를 연산하는 과정은
–(–op) > –(operator–(op)) >> –(op의 참조값) > operator(op의 참조값)
위처럼 진행된다.

전위 증가와 후위 증가

C++에선 전위 증가와 후위증가를 오버로딩 하기위해서, 키워드 int를 사용해 구분하고 있다.
++num은 operator++()
num++는 operator++(int)


교환 법칙의 구현

‘*’ 연산자의 경우엔 교환 법칙이 성립하는데 이를 기존의 operator* 하나론 교환 법칙이 성립되지 않게된다.
이를 해결하기 위해 operator*를 추가해 한번 더 오버로딩 해주면 된다.

class Optest {
private:
	int num;
public:
	Optest(int num) :num(num) {}
	int operator*(int num) {
		return this->num*num;
	}
	friend int operator*(int num, Optest& ref);
};

int operator*(int num, Optest& ref) {
	return ref.num*num;
}

int main(){
	Optest op(10);
	std::cout << 3*op << " " << op*3;
	return 0;
}

 

++”<<“, “>>” 오버로딩

우리가 std::cout에 사용하는 “<<” 혹은 “>>”또한 연산자이기에 오버로딩이 가능하다.
즉, 클래스가 출력 혹인 입력을 받을 때에 일정하게 출력할 수 있다.

+++ [ ], new, delete, new[], delete[], () 오버로딩

각각의 사항들은 쓸 때 정리한다.