본문 바로가기

멋쟁이 사자처럼 BE School

[멋쟁이사자처럼 Back-End School 1기] Day 13. 문자열, GC, JAVA, TDD

문자열

💡 문자 : ‘a’

    문자열 : ‘abc’   

         → 문자 배열(배열도 자료구조이다.)

  • 항상 모든 문장에는 Null 포인터(”\0” == null)가 포함된다.

❗ Null 포인터가 포함되는 이유

- 문장이 종료되었음을 알림.

- C언어에서는 달리 방법이 없음.

- 변수에는 값을 1개만 넣을 수 있기 때문에(주소값을 1개만 넣을 수 있다.)

- Null 포인터가 없으면 계속 출력된다.

공공재

  • 공공재는 재활용된다.
  • 수정이 따로 불가능하다.
char* str = "문자열상수"; // 8byte(char*) + 15byte
// 공공재의 첫번째 문자를 가리킨다.
// 수정이 불가능하다.
char str[100] = "문자열변수"; // 100byte
// stack부분에 해당한다.
// 수정 가능
char* arr1 = "abc"; // 배열에 a, b, c, null 포함, 공공재이다.
// 포인터이기 때문에 8바이트
char* arr2 = "abc"; // 값이 같으면 같은 주소를 갖는다.(공공재를 재활용한다.)
//  가상메모리에서 위쪽에 저장된다.

Chapter26 문제 1

  • 문자배열을 만든 후 거기에 문장 ‘abc’를 저장해주세요.
  • 널문자로 끝내기
// Chapter26 Problem 1
// 문제 : 문자배열을 만들고 거기에 문장 `abc`를 저장해주세요.
// 힌트 : c언어에서 모든 문장은 \\0(널문자)로 끝나야 한다.

#include <stdio.h>

int main(void) {
  char arr[10]; // 문자 10개 저장 가능

  // 구현시작
  arr[0] = 'a';
  arr[1] = 'b';
  arr[2] = 'c';
  arr[3] = '\\0'; // null
  // 구현끝

  printf("%c%c%c\\n", arr[0], arr[1], arr[2]);
  // 출력 => abc

  // %s : 문장으로 출력한다.
  printf("%s\\n", arr);
  // 출력 => abc

  // %s : 문장으로 출력한다.
  printf("%s\\n", &arr[0]);
  // 출력 => abc

  return 0;
}

문자열 포인터의 가상 메모리 공유 코드

  • 공공재는 메모리를 공유한다.
// 문자열 포인터의 가상 메모리 공유 
#include <stdio.h>

int main(void) {
  // 가상메모리 위쪽에 저장된다.
  char* arr1 = "abc"; // 배열에 a, b, c, null 포함, 공공재이다.  
  char* arr2 = "abc"; // 값이 같으면 같은 주소를 갖는다.(공공재를 재활용을 한다) 
  char* arr3 = "bc"; // 주소의 값이 b부터 시작
  char* arr4 = "c"; // 4바이트 짜리 문자배열
  char* arr5 = ""; // 공공재에는 없는 값이 있기 때문에 새로운 메모리 주소
  char* arr6 = "abcd"; // abc 후 null이 들어가 공유를 할 수 없다.

  printf("arr1 : %ld\\n", (long)arr1); // arr1 : 4202500
  printf("arr2 : %ld\\n", (long)arr2); // arr2 : 4202500
  printf("arr3 : %ld\\n", (long)arr3); // arr3 : 4202501
  printf("arr4 : %ld\\n", (long)arr4); // arr4 : 4202502
  printf("arr5 : %ld\\n", (long)arr5); // arr5 : 4202520
  printf("arr6 : %ld\\n", (long)arr6); // arr6 : 4202504

  return 0;
}

Chapter 26 문제 2

  • ‘\0’이 없으면 계속 출력된다.
  • 포인터를 사용해 출력한다.
// Chapyer 26 Problem 2
// 문장을 출력하는 함수(print_str)를 만들어주세요.(%s 사용 금지)
// 조건 : %s를 사용할 수 없다.

#include <stdio.h>

void print_str(char* str) {
  for (int i = 0 ; *(str + i) != '\\0' ; i++) {
    printf("%c", *(str + i));
  }
  printf("\\n");
}

int main(void) {
  char str1[10];
  str1[0] = 'a';
  str1[1] = 'b';
  str1[2] = 'c';
  str1[3] = '\\0';

  
  // print_str 함수를 활용하여 문장 str1 출력 실행
  print_str(str1);
  // 출력 => abc

  char str2[10];
  str2[0] = 'h';
  str2[1] = 'e';
  str2[2] = 'l';
  str2[3] = 'l';
  str2[4] = 'o';
  str2[5] = ' ';
  str2[6] = 'c';
  str2[7] = '\\0';

  // print_str 함수를 활용하여 문장 str2 출력 실행
  print_str(str2);
  // 출력 => hello c

  return 0;
}

Chapter 25 문제 3

  • 4중 포인터 사용하여 출력한다.
  • 문자열을 사용하여 입력이 가능하다.
// Chapyer 25 Problem 3
// 문제 : 아래와 같이 출력되도록, change 함수를 구현해주세요.

#include <stdio.h>

void change(char**** pppp) {
  // 첫번째 방법
  ****pppp = 'a';
  *(***pppp + 1) = 'b';
  *(***pppp + 2) = 'c';
  *(***pppp + 3) = 'd';
  *(***pppp + 4) = '\\0';

  // 두번째 방법
  (***pppp)[0] = 'a';
  (***pppp)[1] = 'b';
  (***pppp)[2] = 'c';
  (***pppp)[3] = 'd';
  (***pppp)[4] = '\\0';
}

int main(void) {
  char str[10];

  char* p = str;
  char** pp = &p;
  char*** ppp = &pp;
  change(&ppp);

  printf("%s\\n", str);
  // 출력 => abcd

  return 0;
}

Garbage Collector, GC

참조 https://velog.io/@recordsbeat/Garbage-Collector-제대로-알기

  • 더 이상 사용하지 않는 메모리 영역을 알아서 해제 시켜주는 역할
  • 알아서 작동하여 실행조건은 일정하지 않음

GC방법 1. Reference Counting

  • GC ROOTS는 시작점으로써 연결되어 있으면 사용 가치가 있음을 의미한다.
  • 단순히 참조하는 수만 센다.

  • 서로 **순환참조(reference)**하고 있으면 삭제되지 않는 문제가 발생한다.

💡 순환참조

  • 참조하는 대상이 서로 물려 있어 참조할 수 없게 되는 현상

GC방법 2. Mark and Sweep

  • 가장 최신 방법
  • 표시하고 삭제하는 방법
  • Root Set부터 시작해 하나씩 따라가 참조 상태를 확인한다.
  • Reference Counting의 순환참조 문제는 해결된다.
  • 하지만, Root Set의 크기에 따라 Marking 시간이 늘어난다.

Java

💡 생성자 메소드 : 클래스와 같은 이름의 메소드

  • 객체 생성 후 객체의 초기화를 하는 역할 수행
  • return type은 없다.
class 칼 extends 무기 {    
	public 칼() { // 생성자 메소드
		this.무기 = "칼";
  }
}

💡 추상 메소드 : 자식 클래스에서 반드시 오버라이딩 해야만 사용할 수 있는 메소드

  • 메소드 중 abstract가 붙는다면 반드시 class에도 abstact가 붙어야 한다.
      → 아무런 작동을 하지 않고 오류가 발생하기 때문에
  • 다형성을 가지는 메소드의 집합을 정의할 수 있도록 한다.
  • 추상 class는 객체를 따로 생성하지 않는다.
      → new 클래스명; 불가능

 

Chapter 12 문제 2

  • 성향과 기호에 따른 적절한 음식 출력
  • 추상 메소드 사용
public class Food {
    public static void main(String[] args) {
        사람 a김철수 = new 사람();
        a김철수.이름 = "김철수";
        a김철수.전화번호 = "010-1234-1234";
        a김철수.a좋아하는_음식점 = new 영화반점();
        a김철수.선호하는_음식의_매운정도 = "매운";
        a김철수.선호하는_음식 = "짬뽕";

        사람 a김영희 = new 사람();
        a김영희.이름 = "김영희";
        a김영희.전화번호 = "010-4321-4321";
        a김영희.a좋아하는_음식점 = new 북경반점();
        a김영희.선호하는_음식의_매운정도 = "안매운";
        a김영희.선호하는_음식 = "짬뽕";

        a김철수.배달_음식_주문하다();
        // 영화반점에서 김철수(010-1234-1234)에게 매운 짬뽕(을)를 배달합니다.

        a김영희.배달_음식_주문하다();
        // 북경반점에서 김영희(010-4321-4321)에게 안매운 짬뽕(을)를 배달합니다.

        a김영희.a좋아하는_음식점 = a김철수.a좋아하는_음식점;
        a김영희.선호하는_음식의_매운정도 = "아주 매운";
        a김영희.선호하는_음식 = "짜장";

        a김영희.배달_음식_주문하다();
        // 영화반점에서 김영희(010-4321-4321)에게 아주 매운 짜장(을)를 배달합니다.
    }
}

class 사람 {
    String 이름;
    String 전화번호;
    음식점 a좋아하는_음식점;
    String 선호하는_음식의_매운정도;
    String 선호하는_음식;

    void 배달_음식_주문하다() {
        a좋아하는_음식점.주문(이름, 전화번호, 선호하는_음식, 선호하는_음식의_매운정도);
    }
}

abstract class 음식점 {
    abstract void 주문(String 주문자명, String 주문자_전화번호, String 음식, String 매운정도);
}

class 영화반점 extends 음식점 {
    void 주문(String 주문자명, String 주문자_전화번호, String 음식, String 매운정도) {
        System.out.println("영화반점에서 " + 주문자명 + "(" + 주문자_전화번호 + ")에게 " + 매운정도 + " " + 음식 + "을(를) 배달합니다.");
    }
}

class 북경반점 extends 음식점 {
    void 주문(String 주문자명, String 주문자_전화번호, String 음식, String 매운정도) {
        System.out.println("북경반점에서 " + 주문자명 + "(" + 주문자_전화번호 + ")에게 " + 매운정도 + " " + 음식 + "을(를) 배달합니다.");
    }
}

Chapter 12 문제 4

  • 들고있는 무기에 따라 다른 결과 출력
  • 생성자 메소드 사용
// 문제 : 아래가 실행되도록 해주세요.
// 조건 : 매개변수를 사용하지 말아주세요.
class Warrior {
    public static void main(String[] args) {
        전사 a전사 = new 전사();
        String 이름 = "칸";
        a전사.이름 = 이름;
        a전사.나이 = 20;
        a전사.자기소개();
        a전사.나이++;
        a전사.자기소개();
        a전사.나이 = 30;
        a전사.이름 = "카니";
        a전사.자기소개();
        a전사.a무기 = new 활();
        a전사.공격();
        // 출력 : 카니가 활로 공격합니다.
        a전사.a무기 = new 칼();
        a전사.공격();
        // 출력 : 카니가 칼로 공격합니다.
    }
}

class 전사 {
    // 인스턴스 변수
    String 이름;
    // 인스턴스 변수
    int 나이;
    // 인스턴스 변수
    무기 a무기;
    void 자기소개() {
        System.out.println("안녕하세요. 저는 " + this.나이 + "살 " + this.이름 + " 입니다.");
    }

    void 공격() {
        System.out.println(this.이름 + "가 " + a무기.무기 + "로 공격합니다.");
    }
}

class 무기 {
    String 무기;
}

class 칼 extends 무기 {
    public 칼() {
        this.무기 = "칼";
    }
}

class 활 extends 무기 {
    public 활() {
        this.무기 = "활";
    }
}

Chapter 13 문제 3

  • 손과 무기에 따라 다른 결과 출력
  • 추상 메소드를 사용하여 오버라이딩 및 파라미터를 통해 손과 무기의 값을 넣어서 출력
public class c3p4 {
    public static void main(String[] args) {
        전사5 a전사1 = new 전사5();

        a전사1.a왼손무기 = new 칼5();
        a전사1.공격();
        // 출력 => 전사가 왼손으로 칼(을)를 사용합니다.

        전사5 a전사2 = new 전사5();
        a전사2.a왼손무기 = new 창5();
        a전사2.a오른손무기 = new 도끼5();
        a전사2.공격();
        // 출력 => 전사가 왼손으로 창(을)를 사용합니다.
        // 출력 => 전사가 오른손으로 도끼(을)를 사용합니다.
    }
}

class 전사5 {
    무기5 a왼손무기;
    무기5 a오른손무기;
    void 공격() {
        if (a오른손무기 != null) {
            a오른손무기.공격("전사", "오른손");
        }

        if (a왼손무기 != null) {
            a왼손무기.공격("전사", "왼손");
        }
    }
}

class 무기5 {
    void 공격(String 사용자, String 부위) {}
}

class 칼5 extends 무기5 {
    void 공격(String 사용자, String 부위) {
        System.out.println(사용자 + "가 " + 부위 + "으로 칼(을)를 사용합니다.");
    }
}

class 활5 extends 무기5 {
    void 공격(String 사용자, String 부위) {
        System.out.println(사용자 + "가 " + 부위 + "으로 활(을)를 사용합니다.");
    }
}

class 창5 extends 무기5 {
    void 공격(String 사용자, String 부위) {
        System.out.println(사용자 + "가 " + 부위 + "으로 창(을)를 사용합니다.");
    }
}

class 도끼5 extends 무기5 {
    void 공격(String 사용자, String 부위) {
        System.out.println(사용자 + "가 " + 부위 + "으로 도끼(을)를 사용합니다.");
    }
}

테스트 주도 개발(TDD)

💡 애자일 방법론

  • 짝 프로그래밍
  • TDD(테스트 주도 개발)
  • 등….

TDD

  • 재미있다.
  • 신뢰도가 높은 프로그램을 만들 수 있다.
  • 설계도가 저절로 되는 측면이 있다.
  • 깔끔한 코드를 작성할 수 있다.(리팩토링을 통해)
  • 장기적으로 개발 비용을 줄일 수 있다.
  • 개발이 끝나면 테스트 코드를 작성하는 것은 매우 귀찮다. 실패 케이스면 더욱 그렇다.

TDD 규칙

  1. 실패하는 테스트를 생성. ⇒ 빨간색 출력
  2. 꼼수를 사용해 테스트를 통과하게 만들어라. ⇒ 초록색 출력
  3. 리팩토링 실시 ⇒ 파란색 출력

 

 

후기 : 죽겠다 어렵다. 어려운데 할만하다. 애매한게 제일 힘들다. 그래도 재밌다. 그래서 이해도 쏙쏙되고 재밌게 가르쳐주셔서 좋은 것 같다. 지금 잠시 바쁘지만 더 열심히 해보자