본문 바로가기

멋쟁이 사자처럼 BE School

[멋쟁이사자처럼 Back-End School 1기] Day 15. 전처리기, 응집도와 결합도, Composition, 배열과 리스트

C언어

전처리기

💡 전처리기?

  • 컴파일 전에 처리하는 것
    define : 매크로
      - 해당 변수 및 함수의 값을 미리 정해두는 것
     ● 단순 매크로 : 기존의 기호상수 기능에서 사용하는 것과 같다.
       - 가독성이 높아진다.
       - 상수의 변경이 매우 용이해진다.
     ● 함수 매크로 : 마치 함수인 것 처럼 흉내를 낼 수 있는 기능

    include

 

Chapter 26

문제 4. start_with 함수를 구현

#include <stdio.h>

typedef int bool;

#define true 1 // 전처리기 true의 값을 1로 변환
#define false 0
int get_str_len(char* str) {
  for ( int i = 0; true; i++ ) {
    if ( str[i] == '\\0' ) {
      return i;
    }
  }
}

bool starts_with(char* str, char* str2) {
  int str_len = get_str_len(str);
  int str2_len = get_str_len(str2);

  if ( str_len < str2_len ) {
    return false;
  }

  for ( int i = 0; i < str2_len; i++ ) {
    if ( str[i] != str2[i] ) {
      return false;
    }
  }

  return true;
}

int starts_with2(char* str1, char* str2) {
    for (int i = 0; str1[i] != '\\0'; i++) {
        if (strstr(str1, str2) != NULL) {
            return true;
        }
    }
    return false;
}

int main(void) {
  
  bool rs;
  
  rs = starts_with("abc", "ab");
  printf("rs : %d\\n", rs); // 출력 rs : 1

  rs = starts_with("kbs", "kb");
  printf("rs : %d\\n", rs); // 출력 rs : 1

  rs = starts_with("mbc", "mc");
  printf("rs : %d\\n", rs); // 출력 rs : 0

  return 0;
}

문제 5. end_with 함수 구현

#include <stdio.h>

typedef int bool;

#define true 1
#define false 0

int get_str_len(char* str) {
  for ( int i = 0; true; i++ ) {
    if ( str[i] == '\\0' ) {
      return i;
    }
  }
}

int ends_with(char* str1, char* str2) {
    int s1_len = get_str_len(str1);
    int s2_len = get_str_len(str2);

    int start = s1_len - s2_len;
    int end = s1_len;

    if (s1_len > s2_len) {
        return false;
    }

    for(int i = start; i < end; i++) {
        if (str1[i] != str2[i - start]) {
            return false;
        }
    }
    return true;
}

int main(void) {
  
  bool rs;
  
  rs = ends_with("abc", "bc");
  printf("rs : %d\\n", rs); // 출력 rs : 1

  rs = ends_with("kbs", "kb");
  printf("rs : %d\\n", rs); // 출력 rs : 0

  rs = ends_with("kbs", "bs");
  printf("rs : %d\\n", rs); // 출력 rs : 1

  rs = ends_with("mbc", "mc");
  printf("rs : %d\\n", rs); // 출력 rs : 0

  return 0;
}

문제6. str_equals 함수 구현

#include <stdio.h>
#define true 1
#define false 0

typedef int bool;

int get_str_len(char* str) {
  for ( int i = 0; true; i++ ) {
    if ( str[i] == '\\0' ) {
      return i;
    }
  }
}

int str_equals(char* str1, char* str2) {
    if (strstr(str1, str2) != NULL) {
        return true;
    }
    return false;
}

int str_equals2(char* str1, char* str2) {
    int str1_len = get_str_len(str1);
    int str2_len = get_str_len(str2);
    if(str1_len != str2_len) {
        return false;
    }
    int rst = 0;
    for (int i = 0; i < str1_len; i++) {
        if (str1[i] != str2[i]) {
            return false;
        }
    }
    return true;
}

int main(void) {
  char* str1 = "abc";
  char* str2 = "abc";
  char* str3 = "abcd";
  char* str4 = "bbc";
  char str5[] = "abc";

  printf("`%s` is equals to `%s` : %d\\n", str1, str1, str_equals2(str1, str1));
  // 출력 => `abc` is equals to `abc` : 1

  printf("`%s` is equals to `%s` : %d\\n", str1, str2, str_equals2(str1, str2));
  // 출력 => `abc` is equals to `abc` : 1

  printf("`%s` is equals to `%s` : %d\\n", str1, str3, str_equals2(str1, str3));
  // 출력 => `abc` is equals to `abcd` : 0

  printf("`%s` is equals to `%s` : %d\\n", str1, str4, str_equals2(str1, str4));
  // 출력 => `abc` is equals to `bbc` : 0

  printf("`%s` is equals to `%s` : %d\\n", str1, str5, str_equals2(str1, str5));
  // 출력 => `abc` is equals to `abc` : 1

  return 0;
}

Chapter 27.

문제 1. str_part_equals 함수 구현

#include <stdio.h>
#define true 1
#define false 0

int str_part_equals(char* str1, int start, int end, char* str2) {
    int j = 0;
    
    for(int i = start; i < end; i++) {
        if(str1[i] != str2[j]) {
            return false;
        }
        j++;
    }
    return true;

}

int main(void) {
  printf("str_part_equals(\\"abcd\\", 0, 2, \\"ab\\") : %d\\n", str_part_equals("abcd", 0, 2, "ab"));
  // 출력 => str_equals("abcd", 0, 2, "ab") : 1

  printf("str_part_equals(\\"abcd\\", 1, 2, \\"b\\") : %d\\n", str_part_equals("abcd", 1, 2, "b"));
  // 출력 => str_equals("abcd", 1, 2, "b") : 1

  printf("str_part_equals(\\"abcd\\", 2, 2, \\"\\") : %d\\n", str_part_equals("abcd", 2, 2, ""));
  // 출력 => str_equals("abcd", 2, 2, "") : 1

  printf("str_part_equals(\\"abcd\\", 2, 4, \\"cb\\") : %d\\n", str_part_equals("abcd", 2, 4, "cb"));
  // 출력 => str_equals("abcd", 2, 4, "cb") : 0

  printf("str_part_equals(\\"abcd\\", 2, 4, \\"cd\\") : %d\\n", str_part_equals("abcd", 2, 4, "cd"));
  // 출력 => str_equals("abcd", 2, 4, "cb") : 1

  return 0;
}

문제2. 특정 문장 위치 반환 함수 구현

#include <stdio.h>

#define true 1
#define false 0

int get_str_len(char* str) {
  for ( int i = 0; true; i++ ) {
    if ( str[i] == '\\0' ) {
      return i;
    }
  }
}

int starts_with(char* str, char* str2) {
  int str_len = get_str_len(str);
  int str2_len = get_str_len(str2);

  if ( str_len < str2_len ) {
    return false;
  }

  for ( int i = 0; i < str2_len; i++ ) {
    if ( str[i] != str2[i] ) {
      return false;
    }
  }

  return true;
}

int str_part_equals(char* str1, int start, int end, char* str2) {
    int j = 0;
    
    for(int i = start; i < end; i++) {
        if(str1[i] != str2[j]) {
            return false;
        }
        j++;
    }
    return true;
}

int get_index_of_str(char* str1, char* str2) {
    int str1_len = get_str_len(str1);
    int str2_len = get_str_len(str2);
    int compare = str1_len - str2_len + 1;

    if (str2_len > str1_len) { 
        return -1;
    }
    
    for (int i = 0; i < compare; i++) {
        if (starts_with(str1 + i, str2)) {
            return i;
        }
    }
    return -1;
}

int get_index_of_str2(char* str1, char* str2) {
    
}

int main(void) {
  int index;
  
  index = get_index_of_str("abc", "b");
  printf("index : %d\\n", index);
  // 출력 => index : 1

  index = get_index_of_str("test", "es");
  printf("index : %d\\n", index);
  // 출력 => index : 1

  index = get_index_of_str("abcd", "bd");
  printf("index : %d\\n", index);
  // 출력 => index : -1

  return 0;
}

JAVA

💡 코드 작성 시 중요하게 생각할 것

  1. 가독성
  2. 높은 응집도, 낮은 결합도
  3. 중복 제거
  4. 변화하는 것을 변화하지 않는 것으로부터 분리

 

응집도와 결합도

💡 응집도와 결합도
    → 좋은 설계는 높은 응집도와 낮은 결합도를 가지도록 구성
응집도(Cohesion)
  - 모듈에 포함된 내부 요소들이 하나의 책임 / 목적을 위해 연결되어 있는 연관된 정도
  - 요소들간의 연관성 척도
  - 높을수록 좋음
  - 응집도가 높으면 변경 대상과 범위가 명확해지는 장점이 있어 유지보수 용이
결합도(Coupling)
  - 모듈 수정을 위해 다른 모듈의 변경을 요구하는 척도
  - 모듈이 다른 모듈에 의존하는 정도의 척도
  - 모듈 간 상호 결합 정도
  - 낮을수록 좋다
  - 결합도가 높으면 검토해야 하는 소스의 수가 많아져 유지보수 어려움

 

상속과 구성

💡 상속(Inherit)
  ● 상위 클래스에 대해 종속적
    → 상위 클래스의 변화를 항상 감지해야 하고, 그에 맞추어 진화해야 한다.
  ● 상위 클래스에서 새로운 메소드 추가 시 하위 클래스에서 반환타입이 다를 경우 컴파일 에러 발생 가능성 있음

 

💡 구성(Composition)
  ● 상속의 문제점 보완
  ● 다른 객체의 인스턴스를 자신의 인스턴스 변수로 포함해서 메소드를 호출하는 기법
  ● 해당 인스턴스의 내부 구현이 변경되어도 영향을 받지 않음

 

Chapter 16.

문제 1. 전사의 무기변경 메소드 구현, 데미지 추가

public class c16p1 {
    public static void main(String[] args) {
        전사 a전사 = new 전사();

        a전사.공격();
        // 칼(으)로 공격합니다.
        // 데미지 : 78

        a전사.창_모드로_변경();
        a전사.공격();
        // 창(으)로 공격합니다.
        // 데미지 : 80

        a전사.지팡이_모드로_변경();
        a전사.공격();
        // 지팡이(으)로 공격합니다.
        // 데미지 : 12
    }
}

class 전사 { // 하나의 클래스 안에 넣는 것은 가장 좋지 않은 예
    String 무기이름;
    int 데미지;

    전사() {
        무기이름 = "칼";
        데미지 = 78;
    }

    void 공격() {
        System.out.println(무기이름 + "(으)로 공격합니다.");
        System.out.println("데미지 : " + 데미지);
    }

    void 창_모드로_변경() {
        this.무기이름 = "창";
        this.데미지 = 80;
    }

    void 지팡이_모드로_변경() {
        무기이름 = "지팡이";
        데미지 = 12;
    }
}

문제2 v1. 문제 1 + a무기 인스턴스 추가

  • 좋은 예시
class c16p2 {
    public static void main(String[] args) {
        전사 a전사 = new 전사();

        a전사.공격();
        // 칼(으)로 공격합니다.
        // 데미지 : 78

        a전사.창_모드로_변경();

        a전사.공격();
        // 창(으)로 공격합니다.
        // 데미지 : 80

        a전사.지팡이_모드로_변경();

        a전사.공격();
        // 지팡이(으)로 공격합니다.
        // 데미지 : 12
    }
}

class 전사 {
    무기 a무기;

    전사() {
        a무기 = new 칼();
    }

    void 공격() {
        a무기.사용();
    }

    void 창_모드로_변경() {
        a무기 = new 창();
    }

    void 지팡이_모드로_변경() {
        a무기 = new 지팡이();
    }
}

abstract class 무기 {
    String 이름;
    int 데미지;

    void 사용() { // 중복을 줄여 상위 클래스에 통합
        System.out.println(이름 + "(으)로 공격합니다.");
        System.out.println("데미지 : " + 데미지);
    }
}

class 칼 extends 무기 {
    칼() {
        이름 = "칼";
        데미지 = 78;
    }
}

class 창 extends 무기 {
    창() {
        이름 = "창";
        데미지 = 80;
    }
}

class 지팡이 extends 무기 {
    지팡이() {
        이름 = "지팡이";
        데미지 = 12;
    }
}

문제2 v2. 좋지 않은 예시

  • 중복을 사용하여 응집도가 낮아지고, 결합도가 높아진다.
public class c16p2v1 {
    public static void main(String[] args) {
        전사 a전사 = new 전사();

        a전사.공격();
        // 칼(으)로 공격합니다.
        // 데미지 : 78

        a전사.창_모드로_변경();

        a전사.공격();
        // 창(으)로 공격합니다.
        // 데미지 : 80

        a전사.지팡이_모드로_변경();

        a전사.공격();
        // 지팡이(으)로 공격합니다.
        // 데미지 : 12
    }
}

class 전사 {
    무기 a무기;

    전사() {
        칼_모드로_변경();
    }

    void 공격() {
        a무기.사용();
    }

    void 창_모드로_변경() {
        a무기 = new 창();
    }

    void 칼_모드로_변경() {
        a무기 = new 칼();
    }

    void 지팡이_모드로_변경() {
        a무기 = new 지팡이();
    }
}

// 응집도는 높고 결합도가 낮은 것이 좋은 것.
abstract class 무기 { // 자유도는 높지만, 중복되는 것이 많다.
    abstract void 사용();
}
class 칼 extends 무기 {
    void 사용() {
        System.out.println("칼(으)로 공격합니다.");
        System.out.println("데미지 : 78");
    }
}
class 창 extends 무기 {
    void 사용() {
        System.out.println("창(으)로 공격합니다.");
        System.out.println("데미지 : 80");
    }
}
class 지팡이 extends 무기 {
    void 사용() {
        System.out.println("지팡이(으)로 공격합니다.");
        System.out.println("데미지 : 12");
    }
}

 

배열 vs 리스트 and 스트림

배열

💡 ● 크기를 미리 정해야 한다.
     ● 크기에 따라 컴파일 타임이 정해진다.
     ● 상황에 따라 공간을 더 늘리거나 줄일 수 없다.

// 숫자 5개
int[] arr = new int[5]; // 크기가 정해져있다.
// 입력
int[0] = 10;
int[1] = 20;
// 출력
System.out.println(arr[1]);
private static void sol2() {
        String[] sBits = line.split(" ");
        int[] numbers = new int[sBits.length];
        int numbersLastIndex = -1;
        int sum = 0;

        for( String bit : sBits ) {
            if (bit.matches("\\\\d+")) {
                numbers[++numbersLastIndex] = Integer.parseInt(bit);
            }
        }

        for (int i = 0; i <= numbersLastIndex; i++) {
            sum += numbers[i];
        }
        System.out.println(sum);
    }

리스트

💡 ● 배열의 문제점 해결
     ● 빈틈없는 데이터의 적재
     ● 순차성을 보장하지 못한다.

// 데이터 N개
ArrayList list = new ArrayList(); // 크기를 직접 정할 필요가 없다.
// 입력
list.add(10);
list.add(20);
// 출력
System.out.println(list.get(1));

// 숫자 N개
ArrayList<Integer> list = new ArrayList<Integer>();
ArrayList<Integer> list = new ArrayList<>();
List<Integer> list = new ArrayList<>();
private static void sol3() {
    String[] sBits =line.split(" ");
    ArrayList<Integer> numbers = new ArrayList<>(); // 기억할 능력이 있음
    int sum = 0;

    for( String bit : sBits ) {
        if (bit.matches("\\\\d+")) {
            numbers.add(Integer.parseInt(bit));
        }
    }

    for (int number : numbers) {
        sum += number;
    }

    System.out.println(sum);
}

스트림

💡 스트림?
    ● 데이터의 흐름
    ● 배열 or 컬렉션 인스턴스에 함수 여러개를 조합해 원하는 결과 필터링 및 가공 가능
    ● 람다를 이용해 코드의 양을 줄일 수 있다.
    ● 간단하게 병렬 처리가 가능하다.

private static void sol1() {
    int sum = Arrays
            .stream(line.split(" ")) // 배열 => 스트림
            .filter((s) -> s.matches("\\\\d+")) 
// "\\d+" : 10진수 정수 / .matches("\\\\d+") : 10진수 정수로만 이뤄져 있는지 / filter : 필터의 값이 true인 것만 남겨둠
            .mapToInt(Integer::parseInt) // 정수 형변환
            .sum();

    System.out.println(sum);
}

 

후기 : 매우어렵다. 으악으악 그래도 재미있다 진짜로.. 즐기자 정말 즐기면서 해보자 화이팅