오늘은 handler interceptor 에 대한 이야기를 조금 해보고자 한다.

Filter 는 자바 servlet 에서 제공하는 기술이지만 interceptor는 spring에서 제공하는 기술로써

세션 인증과 관련된 부분을 공부하다가 적어본다.

 

▶ 그래서 interceptor 가 뭔가요?

spring MVC 에서 인터셉터란 결국

지정된 URI에 관한 호출을 가로채는 역할을 하는 기능입니다.

이러한 호출을 가로채서 뭐하냐 싶지만

우리는 로그인을 한다거나 어떠한 게시물에 조회수를 올린다거나 하는 작업을 할 때 와 같은

공통된 관심사를 컨트롤 하기 위해서 사용하는 것 이다.

 

이는 이전 게시글인 Filter 와 같은데 두개의 차이점은 확실히 존재한다.

  Filter 와 Interceptor의 차이점

Filter와 Interceptor는 그 기능이 매우 흡사하여 나와같은 초보들이 가장 많이 헷갈려하는게

두 기능은 확연한 차이가 존재한다.

 

spring MVC request LifeCycle

위 사진을 보면 해당 차이를 조금 더 확실하게 알 수 있는데

Filter는 요청을 보내면 Tomcat과 같은 WAS 서버에 들어오자마자 작동되는 것으로

Spring의 영역에 접근하여 사용하기가 어렵습니다.

 

반면에 interceptor는 Spring 내에서 관리되는 기능이기에 Spring의 영역 내에서 사용되고 있는 것을 볼 수 있습니다.

 

이것으로 인해서 알 수 있는 차이는 생성된 Bean에 접근이 용이한가로 볼 수 있습니다.

 

◆ 차이점 정리

▶ Interceptor와 Filter는 호출 시점의 차이

  • Filter는 Dispatcher Servlet 호출 전(WAS의 단계)
  • Interceptor은 Dispatcher Servlet 호출 후 실행 됨

 

▶ Interceptor 의 용도

  • 인증 인가 등과 같은 공통 작업
  • Controller로 넘어가는 정보의 가공
  • API 호출에 대한 로깅 또는 감사

▶ Filter의 용도

  • 보안 관련 공통 작업
  • 모든 요청에 대한 로깅 
  • 이미지, 데이터 압축 및 문자열 인코딩

이제 이어지는 사용법을 알아보자

 

Handler Interceptor는 Interface 이므로 class 에 implements 하여 사용하면 된다.

public interface HandlerInterceptor {
    // controller 호출 전에 실행 (핸들러 어댑터 실행 전)
    default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        return true;
    }

	// controller 호출 후에 실행 (핸들러 어댑터 호출 후)
    default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception {
    }

	// view의 랜더링 이후 실행
    default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception {
    }
}

위 코드가 HandlerInterceptor의 기본 모습이며 사용처는 내부에 적어두었다.

 

이를 사용하여 만든 간단한 예문을 적고 마무리 이 글을 마무리 하고자 한다.

 

▶ 예문

LOG 를 찍어 흐름 확인하기

@Slf4j
public class LogInterceptor implements HandlerInterceptor {

    public static final String LOG_ID = "logId";

    @Override // controller 호출 전에 실행 (핸들러 어댑터 실행 전)
    public boolean preHandle(HttpServletRequest request,
    			HttpServletResponse response,
                Object handler) throws Exception {

        String reqURI = request.getRequestURI();

        String uuid = UUID.randomUUID().toString();
        request.setAttribute(LOG_ID, uuid);


        //@RequestMapping : HandlerMethod
        // 정적 리소스 : ResourceHttpRequestHandler
        if (handler instanceof HandlerMethod) {
            HandlerMethod hm = (HandlerMethod) handler;
            // 호출할 컨트롤러의 메서드의 모든 정보
        }

        log.info("REQ [{}][{}][{}]",uuid, reqURI,handler);


        return true;
    }

    @Override // controller 호출 후에 실행 (핸들러 어댑터 호출 후)
    public void postHandle(HttpServletRequest request, 
    			HttpServletResponse response, 
                Object handler,
                ModelAndView modelAndView) throws Exception {
                
        log.info("postHandle [{}]", modelAndView);
        
    }

    @Override // view의 랜더링 이후 실행
    public void afterCompletion(HttpServletRequest request,
                HttpServletResponse response, 
                Object handler, 
                Exception ex) throws Exception {
                
        String reqURI = request.getRequestURI();
        String logId = (String) request.getAttribute(LOG_ID);
        log.info("RES [{}][{}]", logId, reqURI);
        if (ex != null) {
            log.error("afterCompletion error!", ex);
        }
        
    }
}

 

※ Interceptor를 정의한 클레스

 

@Configuration
public class WebConfig implements WebMvcConfigurer {
	
    
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // logInterceptor register
        registry.addInterceptor(new LogInterceptor()) // 인터셉터 등록
                .order(1) // 인터셉터의호출 순서를 지정, 낮을수록 먼저 호출
                .addPathPatterns("/**") // 인터셉터를 적용할 URL패턴 지정
                .excludePathPatterns("/css/**", "/*.ico", "/error"); //인터셉터에서 제외할 패턴 지정


}

 

※ Interceptor 등록

 

 

 

♣ 참고 및 강의

인터페이스 차이점과 정의 관련 참고 블로그

 

https://inf.run/GMo43 - 김영한의 스프링MVC2 

 

스프링 MVC 2편 - 백엔드 웹 개발 활용 기술 강의 - 인프런

웹 애플리케이션 개발에 필요한 모든 웹 기술을 기초부터 이해하고, 완성할 수 있습니다. MVC 2편에서는 MVC 1편의 핵심 원리와 구조 위에 실무 웹 개발에 필요한 모든 활용 기술들을 학습할 수 있

www.inflearn.com

 

'Web > spring' 카테고리의 다른 글

servlet filter  (0) 2024.01.19

2024.01.19

오늘 정리하고자 하는 내용은 서블릿 필터에 관한 내용이다.

김영한의 MVC2 강의 내용정리

우리는 흔히 로그인 시스템을 만들었을 때 로그인 유지 및 그에 따른 사용자 구분 등을 어떻게 해결할 지 고민하게 되는데

이 고민을 해결해 줄 부분이 servlet filter 이다. servlet filter 는 각 로직에서 공통적으로 사용되는 공통 관심사

(영어로는 cross-cutting) 를 하나로 통일시켜 우리를 훨씬 편하게 만들어줄 수 있는 친구다.

 

물론 이러한 부분에 대해서는 AOP 가 정말 대표적이지만 필자는 아직 AOP 공부안해봤음...ㅎ...

 

어쨋든 공부하다가 보니 웹에서는 servlet filter나 spring interceptor 을 사용하는게 좋다고 하길래 적는 내용.

 

필터는 javax.servlet 으로 import 할 수 있는 인터페이스다.

public interface Filter {
      public default void init(FilterConfig filterConfig) throws ServletException{
      
      }
      
      
      public void doFilter(ServletRequest request, ServletResponse response,
              FilterChain chain) throws IOException, ServletException;
              
              
      public default void destroy() {
      
      }
 }

 

기본적으로는 이렇게 생겼는데

init() 의 경우 필터  초기화 메서드로, 서블릿 컨테이너가 생성될 때 호출된다.

doFilter() 의 경우 요청이 올 때 마다 해당 메서드가 호출되는데, 여기서 필터의 로직을 구현하면 된다.

destroy() 의 경우 필터 종료 메서드로, 서블릿 컨테이너가종료될 때 호출된다.

 

이러한 필터는 체인 형태로 구성되어 중간중간에 자유롭게 추가가 가능하다.

 

이게 무슨소린가 싶은 미래의 나를 위해 그림 추가 ↓

 

 

더보기
필터의 전체적인 흐름
필터의 작동 흐름
필터 체인의 자유로운 추가

 

 


사용법

package hello.login.web.filter;


import lombok.extern.slf4j.Slf4j;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.UUID;

/**
 * 필터를 사용하기 위한 필터 인터페이스 구현
 */
@Slf4j
public class LogFilter implements Filter {


    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
//        Filter.super.init(filterConfig);
        log.info("log filter init");
    }

    /**
     * HTTP요청 시 doFilter 가 호출
     */
    @Override
    public void doFilter(ServletRequest request, 
    			ServletResponse response, 
                FilterChain chain) throws IOException, ServletException {

        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        // public void doFilter(ServletRequest request...) 
        // 이 부분은 HTTP요청이 아닌 경우 (HTTPS 등) 까지 고려해서 만든 인터페이스이다.
        // HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        // 로 다운케스팅 해서 사용하면 된다.

        String requestUrl = httpServletRequest.getRequestURI();

        String uuid = UUID.randomUUID().toString();
        //요청 구분을 위한 임의의 uuid 생성

        try {

            log.info("REQ [{}] [{}]", uuid, requestUrl);
            chain.doFilter(request, response);
            // 다음 필터가 존재하면 필터를 호출, 필터가 없으면 서블릿을 호출
            // 만일 이부분이 없으면 다음 단계로 진행되지 않음.

        } catch (Exception e) {
            throw e;
        } finally {
            log.info("RES [{}] [{}]", uuid, requestUrl);
        }
    }

    @Override
    public void destroy() {
//        Filter.super.destroy();
        log.info("log filter destroy");
    }
}

위 와 같이 필터로 지정할 메서드를 하나 만들어주고

 

package hello.login;

...

import javax.servlet.Filter;

@Configuration
public class WebConfig {

    @Bean
    public FilterRegistrationBean logFilter() {
        FilterRegistrationBean<Filter> filterFilterRegistrationBean 
        							= new FilterRegistrationBean<>();

        filterFilterRegistrationBean.setFilter(new LogFilter());
        // 등록할 필터 지정
        filterFilterRegistrationBean.setOrder(1);
        // 필터는 체인으로 동작하는데 이 때 순서가 필요하다. 낮을 수록 먼저 동작하게 된다.
        filterFilterRegistrationBean.addUrlPatterns("/*");
        // 필터를 적용할 URL패턴을 지정. 한번에 여러 패턴을 지정할 수 있다.


        return filterFilterRegistrationBean;
    }

}

@Bean을 통해 bean 등록을 진행해준다.

 

★ 만약 스프링 부트를 사용한다면 FilterRegistrationBean 을 사용해서 등록

 

이러한 필터를 적용할 때 중요한 부분이 있는데 이것이

whitelist

이다.

 

항상 코딩하면서 생각하는거지만 별거아니라고 생각되는 무언가가 제일 날 화나게해..

 

Filter 메서드를 Override 해보면 알겠지만 이 whitelist란 해당 웹사이트에서 이 필터가 적용되지 말아야 할 부분은 어디인가를 제외하는 문자열 배열이다.

 

public class LoginCheckFilter implements Filter {

    private static final String[] whitelist 
    	= {"/", "/signin", "/login", "/logout", "/css/*"};
 	
    ...
}

 

 

이런식으로 작성해서 해당 클레스에서만 사용되도록 되어있는데 따로 config로 빼서 관리해도 좋을 듯 싶다. (개인적인 생각)

 

이렇게 빈으로써 등록해두면 어디에 접속하든 로그에

2024-01-19 20:53:34.742  ...  : RES [16eacd30-94dc-4f11-b046-663d3c291f21] [/login]
2024-01-19 20:53:39.443  ...  : REQ [69525432-505e-4cac-9076-f34c996176ea] [/]

이런식으로 찍히는 걸 볼 수 있다.

 

이렇게 찍히는게 보였으면 filter 적용완료이다.

 

 

 

 

'Web > spring' 카테고리의 다른 글

servlet interceptor  (0) 2024.01.22

2024.01.17 

김영한의 Spring MVC2 편을 보면서 이해가 가지않은 부분이 있었다.

해당 편의 로그인 처리하기 - Servlet HTTP Session 2  부분이었는데

 

public String homeLogin(@SessionAttribute(name = SessionConst.LOGIN_MEMBER, required = false)
            Member loginMember,
            Model model) {

        if (loginMember == null) {
            return "home";
        }

        model.addAttribute("member", loginMember);
        return "loginHome";
    }

 

위 코드에서 @SessionAttribute(name = SessionConst.LOGIN_MEMBER, ...) 부분이다.

 

해당 부분에서 name 값을 상수로 고정하는데

 

해당 부분에 대해 나온 코멘트는 

 

HttpSession 에 데이터를 보관하고 조회할 때, 같은 이름이 중복 되어 사용되므로, 상수를 하나 정의했다.

 

였는데 이 말만으로는 정확하게 이해가 가지 않았다.

 

저렇게 정의해두면 하나의 대상만을 가져오지 않는가?

 

라는 의문이 들었다.

 

이전 코드를 보면 login 의 코드를 보면

 

// create session by HTTPSession
        HttpSession session = request.getSession();
        /**
         * request.getSession(true);
         * 세션이 있으면 기존 세션을 반환
         * 없으면 새로운 세션 생성 및 반환

         * request.getSession(false);
         * 세션이 있으면 기존 세션 반환
         * 없으면 새로운 세션을 생성하지 않고 null 을 반환
         */


        // set login member info in session
        session.setAttribute(SessionConst.LOGIN_MEMBER, loginMember);

 

 

위와 같이 session에 상수값을 key, 멤버를 value 로 저장하게 되는데 이러면 동시 로그인에서 문제가 생기진 않을까? 하는 의문이 들었다.

 

해당 부분에 대해서 검색해본 결과 어떤 블로거분의 글을 보게 되었다.

 

해당 글 링크

 

웹 기초] HTTP Session 공부

HttpSession을 공부하게 된 이유. www.youtube.com/watch?v=3ArYMq5AomI 우연히 jojoldu.tistory.com/ 블로그의 주인님(?)이신 조졸두님(향로님)이 나오는 유튭영상을 보고 정리하게 됐습니다. 향로님의 대표적인 질

jjunii486.tistory.com

 

이 부분은 이전 유튜브에서도 나온 질문이라는데 나의 의문에 대한 답변이 나와있다.

[인터넷 네트워크]

2024.01.17

오늘 정리할 부분은 인터넷 네트워크에 관한 부분입니다.

개발자로 취업 후에 기초 개념 부분에서 가장 부족했던 제게 많은 도움이 되었던 강의를 다시 보고 정리하고자 합니다.

 

인프런 - 김영한 강의 http웹 기초 정리 https://inf.run/8ZEU8

 

모든 개발자를 위한 HTTP 웹 기본 지식 강의 - 인프런

실무에 꼭 필요한 HTTP 핵심 기능과 올바른 HTTP API 설계 방법을 학습합니다., [사진] 📣 확인해주세요!본 강의는 자바 스프링 완전 정복 시리즈의 세 번째 강의입니다. 우아한형제들 최연소 기술

www.inflearn.com

 

 

 

1. 인터넷 통신

인터넷에서 두 컴퓨터가 통신하는 방법은

클라이언트(User)가 서버로 요청을 보내고 서버는 그에 응답하는 식이다.

 

중요한것은 이때 클라이언트 방향에서 보낸 요청은 거대한 인터넷의 세계를 탐험하게 되는데.

수많은 클라이언트와 서버가 연결된 복잡한 인터넷 망 안에서

수많은 노드들을 보고 정확한 서버를 찾아서 해당 요청에 따른 정보를 전달하고

그에 대한 응답을 받아오는 것은 매우 복잡하고 어려운 방식이다.

 

따라서 우리는 아래의 개념을 명확하게 이해하여야 한다.

 

2. IP(Internet Protocol)

IP란 각 노드들에 부여되는 하나의 주소이다.

클라이언트가 보낸 요청은 상대 서버에 부여된 주소를 보고 찾아갈 수 있는 것이며

이때 요청과 응답에 담긴 정보들은 하나의 패킷이라는 통신단위로 전달된다.

 

IP 패킷에 담긴 정보 = 출발지IP, 목적지IP, 기타 정보 및 데이터

 

- IP프로토콜의 한계

  • 비연결성
    • 대상이 패킷을 받을 수 없거나 불능 상태여도 패킷 전송
  • 비신뢰성
    • 패킷의 소실 가능성
    • 패킷 정보의 순서 구분
  • 프로그램(애플리케이션) 구분
    • 같은 IP를 공유하는 하나의 서버내 통신하는 애플리케이션(프로그램)이 두개 이상이라면 패킷 소실 가능성이 있음

 

 

3.TCP,UDP

 

TCP (Transmission Control Protocol) 즉 전송 제어 프로토콜로 해석할 수 있다.

연결형 서비스를 지원하는 프로토콜로 인터넷 환경에서 기본으로 사용된다.
이러한 TCP는 IP와 함께 사용되는것이 보편적인데 이때 IP는 데이터의 배달을 처리하는 부분이며
TCP는 패킷을 추적 및 관리하게 된다. 

 

 

[TCP의 특징]

  • 연결 지향 방식으로 3-way handshacke 방식을 사용하여 연결을 수립하고 4-way. handshacke과정을 통해 해제한다.(가상 연결)
  • 데이터 전달 보증 및 순서를 보장 (높은 신뢰도)
  • 데이터의 흐름 및 혼잡 제어
  • UDP보다 느림

 

UDP (User Datagram Protocol) 즉 사용자 *데이터그램 프로토콜 로 해석할 수 있다.

이는 비연결형 프로토콜로 연결을 위한 논리적 경로가 없다.

 

* Datagram 은 독립적 관계를 지니는 패킷이라는 의미

 

[UDP의 특징]

  • 하얀 도화지에 비유 (기능이 거의 없음)
  • 비연결형 서비스로 연속성이 더욱 우선된다.
  • 데이터 전달 보증 X 및 순서 보장 X (낮은 신뢰도)
  • IP와 거의 같으며 PORT와 CheckSum 필드를 통해 최소한의 오류만을 검사한다.
  • 애플리케이션 내 추가작업이 필수이다.

 



TCP, UDP는 기본적으로 전송 계층이다.

개인적으로 저는 전공자가 아니라서 이 부분에 대해서 이해하기 쉽지 않았습니다.

OSI 7계층? TCP/IP 4계층?

처음에 접했을 때는 완전히 다른 부분에서 사용되는 개념인 줄 알았고 무엇에 사용되는 개념인지도 잘 몰랐습니다.

이는 두 개념 전부 네트워크 통신을 나눈 단계 입니다.

https://shlee0882.tistory.com/110

 

OSI 7 계층이란?, OSI 7 계층을 나눈 이유

1. OSI 7 계층이란? OSI 7 계층은 네트워크에서 통신이 일어나는 과정을 7단계로 나눈 것을 말한다. 1.1 OSI 7 계층을 나눈이유는? 계층을 나눈 이유는 통신이 일어나는 과정이 단계별로 파악할 수 있

shlee0882.tistory.com

https://velog.io/@dyunge_100/Network-TCPIP-4계층에-대하여

 

[Network] TCP/IP 4계층에 대하여

두 계층 모두 데이터 통신을 표현한 계층이긴 하지만 OSI 7계층은 데이터 통신에 필요한 계층과 역할을 정확하게 정의하려고 한 모델이다. 그에 반해 TCP/IP 4계층은 현재 인터넷에서 사용되는 프

velog.io

 

 

4.PORT

하나의 컴퓨터에서 여러개의 작업을 할 경우 컴퓨터 서버내 PORT라는 것을 반드시 알아야 한다.

실무를 진행하면서 하나의 서버에서 여러개의 포트를 사용해서 복합적인 프로그램을 사용하는 것을 보고

이런 방식으로 포트라는 것이 사용되며 이러한 포트를 통해 프로세스를 구분하여 사용된다는 것을 알았다.

개인적으로는 반드시 완벽히 이해해야하는 개념이라고 생각한다.

 

PORT 란 "논리적인 접속장소" 를 뜻한다.

이는 TCP/IP 를 사용 할 때 클라이언트의 프로그램 및 요청이 네트워크 상의 특정 서버, 특정 프로그램을 지정하는 방법으로 사용된다.

풀어서 이야기하자면 위 개인의 경험으로 알게되었듯이

하나의 요청이 여러개의 프로그램이 작동되고 있는 서버 내에서 정확한 프로그램을 찾아 가기 위해 연결된 접속장소 인 것이다.

개발자라면 3000 or 8080 을 정말 많이 들어보았을텐데

1xx.xxx.xx.xx:3000

2xx.xxx.xx.xx:8080

서버(집)의 주소와 상세 주소 정도로 생각하면 좋을 듯 싶다.

• 0 ~ 65535 할당 가능
• 0 ~ 1023: 잘 알려진 포트, 사용하지 않는 것이 좋음
• FTP - 20, 21
• TELNET - 23
• HTTP - 80
• HTTPS - 443

 

 

5.DNS

DNS란 (Domain Name System) 의 약자로 즉 IP 주소에 이름을 붙혀서 명명하는 시스템이다.

예시를 들어보면 125.209.222.141 이 IP 주소는 NAVER 의 IP 주소로 

우리는 흔히 주소창에 naver.com 을 치면 위 도메인 네임이 125.209.222.141 로 변환되어 요청되는 방식이다.

또한 이러한 IP는 변경될 수 있다는 것을 생각하면 도메인은 상당히 편한 시스템이 아닐 수 없다.

 

 

 

1. 접근 제한자
1-1. 접근 제한자의 종류와 설명
public 모든 곳에서 해당 클래스에 접근이 가능
private 클래스 내부에서만 접근 가능
internal 같은 어셈블리 내에서만 public 으로 접근 가능
protected 클래스의 외부접근은 불가능하나 파생된 클래스에서는 가능
protected internal 같은 어셈블리 내에서만 protected 로 접근 가능

2. 접근제한자의 각 예시

 

2-1. public
namespace ConsoleApp1
{
    public class Student
    {
        public String name;
        public int age;

        public void show()
        {
            Console.WriteLine($"{this.name}학생은 올해로 {this.age}세 입니다.");
        }
    }


    class Program
    {
        static void Main(string[] args)
        {
            Student student1 = new Student();
            student1.name = "김이박";
            student1.age = 20;
            student1.show();

        }
    }
}

public 접근제한자는 Program 클래스와는 다른 클래스임에도 불구하고

무리없이 접근하여 사용가능한 것을 볼 수 있습니다. 


2-2. private 

이번에는 class는 접근제한자를 표기하지않고 필드에는 private를 직접 작성해보겠습니다.

(private는 C#의 default 접근제한자로 만약 필드의 접근제한자에 아무것도 적지않으면 자동으로 private임.)

namespace ConsoleApp1
{
    class Student
    {
        private String name;
        private int age;
        
        public void show()
        {
            Console.WriteLine($"{this.name}학생은 올해로 {this.age}세 입니다.");
        }
    }


    class Program
    {
        static void Main(string[] args)
        {
            Student student1 = new Student();
            student1.name = "김이박";
            student1.age = 20;
            student1.show();

        }
    }
}

이렇게 작성하니 뭔가 에러가 뜨는것을 확인할 수 있는데요

바로 이러한 에러가 나옵니다. '보호수준' 때문에 필드에 접근할 수 없다는 이야기도 함께 나오네요.

위에 설명과 같이 private 접근제한자는 같은 클래스 내부에서만 사용이 가능합니다.

그렇다면 같은 클래스 내부에서만 접근이 가능하다면

우리는 main영역에 다 적어주는 것과 다를 바 없는 불편한 사용을 계속 하게될까요?

아닙니다. 우리는 이러한  private필드에 public 메서드 라는 구멍을 뚫어 접근할 수 있도록 해볼 것 입니다.

이때 필요한 것이 this 인데요. 


this ?  저번 강의에도 조금 언급했듯이 this 속성은 클래스 내부의 필드 자신을 직접 가리키는 것입니다.
이를 이해하기쉽도록 예시를 사용하겠습니다.

namespace ConsoleApp1
{
    class Student
    {
        private String name;
        private int age;
        
        public String getName()
        {
            return name;
        }
        public void setName(String name)
        {
            this.name = name;
        }

        public int getAge(int age)
        {
            return age;
        }
        public void setAge(int age)
        {
            this.age = age;
        }
    }


    class Program
    {
        static void Main(string[] args)
        {
            Student student1 = new Student();
            student1.name = "김이박";
            student1.age = 20;
            student1.show();

        }
    }
}

여러가지 메소드가 추가되었습니다.

잘 보시면 this.name = name , this.age=age 라는 부분을 보실 수 있는데요.

이 부분은 그림으로 설명드리면

이렇게 사용되고 있는 것입니다.

this.name은 필드를 가리키고

name은 들어오는 매개변수를 가리키는 것입니다.

즉 이를 해석하면

필드(name) 에 메서드에 입력되는 매개변수를 담는다.

라고 보시면 됩니다.

두 변수의 이름이 같으니 이를 나누어주는 역할을 하기도 합니다!

 

 

 

 

 

 

이러한 부분의 사용까지 예시를 본면

namespace ConsoleApp1
{
    class Student
    {
        private String name;
        private int age;
        
        public String getName()
        {
            return name;
        }
        public void setName(String name)
        {
            this.name = name;
        }

        public int getAge()
        {
            return age;
        }
        public void setAge(int age)
        {
            this.age = age;
        }
    }


    class Program
    {
        static void Main(string[] args)
        {
            Student student1 = new Student();

            student1.setName("김이박");
            student1.setAge(20);

            Console.WriteLine($"{student1.getName()} 학생은 올해로 {student1.getAge()}입니다.");
            Console.WriteLine();
        }
    }
}
  • 생성자를 만든다
  • 생성자를 통해 set메서드를 사용한다.
  • 사용된 set메서드를 통해 만들어진 필드를 get메서드를 통해 가져온다.

이렇게 사용하는 것입니다. 사실 getter setter 메서드는 자바에서 흔히 사용되는 개념인데요.

C#은 자바와 통하는 부분이 많아서 이렇게 사용할 수 있다는 것을 빼와보았습니다.


2-3. protected

protected 접근제한자는 '파생된' 클래스에서만 사용이 가능하다.

위 '파생된' 이라는 부분은 굉장히 핵심적인 설명입니다.

우리가 지금까지 사용했던 class들은 각자 다른 부분을 적어두고 끌어와서 사용한다는 느낌이 강합니다.

하지만 protected의 경우 class A에서 파생된 class B에서 class A를 사용할 수 있다.  즉 클래스 B에 클래스 A 전체를 담는다는 느낌이 강하죠.

예시를 사용해서 이해를 도와보겠습니다.

아래의 예는 상속이며 이는 추후 설명하겠습니다.

namespace ConsoleApp1
{
    class Student
    {
        protected char student1 = 'A';
    }


    class A_class : Student
    {
        static void Main(string[] args)
        {
            Student st_a = new Student();
            A_class a_cl = new A_class();

            st_a.student1 = 'B'; //class Student에 student1은 protected 접근제한자이다.
                                 //따라서 Student 클래스의 파생된 클래스 A_class 에서만 
                                 //접근이 가능하다.

        }
    }
}

위 코드를 보시면 타이핑과 동시에 표시부분에 에러가 나옵니다. 아래와 같은 오류 내용을 확인 할 수 있는데요

이 내용을 보면 파생된 형식이어야 사용할 수 있다는 에러 내용까지 확인 하실 수 있습니다.

 

 

 

 

코드를 보면 생성자가 두가지 생성되어있는 것을 볼 수 있습니다. 하나는 st_a, 다른 하나는 a_cl 입니다.

student class의 생성자인 st_a 에서 접근이 안되는 것을 보니 두번째인 a_cl 에서 접근해보도록 하겠습니다.

namespace ConsoleApp1
{
    class Student
    {
        protected char student1 = 'A';
    }


    class A_class : Student
    {
        static void Main(string[] args)
        {
            Student st_a = new Student();
            A_class a_cl = new A_class();

            
            a_cl.student1 = 'B';
        }
    }
}

이 코드에서는 Student 클래스에 있는 student1 에 접근 할 수 있는 것을 확인 할 수 있습니다.


다음편은 생성자, 소멸자입니다.

'C#' 카테고리의 다른 글

[C#_10_(1)] Class  (0) 2021.10.08
[C#_9] 배열 (Array)  (0) 2021.10.07
[C#_8] 메서드 method  (0) 2021.10.02
[C#_7] 반복문 제어 (Break, Continue, goto)  (0) 2021.09.29
[C#_6] 반복문(for,foreach,while,do while)  (0) 2021.09.24
Class 를 배우기 전에...
클래스를 배우기 전에 우리는 객체 지향 프로그래밍(Object Oreinted Programming, OOP) 에 관하여 알아두어야 합니다.
왜냐하면 C# 자체가 객체 지향성 프로그래밍을 더욱 원활히 하기 위해서 만들어진 언어이기 때문인데요.
이러한 객체 지향 프로그래밍은 우리에게 더욱 원활한 프로그램의 유지와 보수를 가능하게 해주기 때문입니다.
그렇다면 이러한 객체 지향 프로그래밍에는 어떤 것이 필요할까요?
이름에도 적혀있듯이 중요한 것이 객체 입니다.
객체(Object)

프로그래밍에서 객체란 클래스에서 정의한 것을 토대로 메모리에 할당된 것 으로 프로그램에서 사용되는 데이터 또는 식별자에 의해 참조되는 공간을 의미하며, 변수, 자료 구조, 함수 또는 메소드가 될 수 있다고 정의하고있습니다.

 

그러나 저는 처음에 이를 접할 때 코드를 짜보지 않고서는 이해가 조금 어려웠는데요.

그래서 사물로 예를 들어보겠습니다.

 

우리가 현재 사용하고 있는 컴퓨터나 핸드폰, 키보드나 마우스 등

'하나의 물건' 으로 보여지는 것 모든 것이 객체가 될 수 있습니다.

그런데 이러한 객체들은 각각의 행동이 있습니다.

컴퓨터는 사용된다. 핸드폰으로 전화를 건다. 등 각각의 객체가 취할 수 있는 행동이 정의되어 있습니다.

이를 상태(state) 또는 행동(behavior) 이라고 합니다.

  • 상태(state)를 C#에서 표현한다면 변수(class 내부의 변수는 필드)
  • 행동(behavior)을 C#에서 표현한다면 메서드 로 표현 할 수 있겠습니다.
  • 그리고 이러한 상태와 행동을 하나의 틀로 정의해주는 것이 바로 Class 입니다.
1. Class
/*접근제한자*/class 클래스이름
{
    /*접근제한자*/필드
    
    /*접근제한자*/ 반환타입 메소드이름(매개변수)
    {
        코드
        
        return 반환값 // 반환값이 존재할 시 - 메서드 부분 참고             
    }
}

위 코드는 클래스의 기본 형태입니다. 클래스의 이름 중 첫 글자는 대문자로 하는 것이 명명규칙입니다.(구분을 위해)

접근제한자를 적어둔 곳은 접근제한자를 사용하여 접근을 제한 시켜도 된다고 표기한 것 입니다.

접근 제한자에 관하여 다음강의에 적어두겠습니다.

 

사실 우리는 이러한 클래스를 항상 사용해왔는데요 우리가 visual studio 2019를 켜면

namespace ConsoleApp1
{//******************************************
    class Program
    {
        static void Main(string[] args)
        {
            
            
        }
    }
}//******************************************

이러한 형태로 class를 사용하고 있던 것을 알 수 있습니다.

그런데 사실 이렇게만 보면 이 class를 어떻게 사용해야 하는지 또 어떤식으로 활용되는지 잘 이해가 되지 않습니다.

그래서 작은 예를 하나 들어보겠습니다.

class Student
{
    String studentID;//필드1
    String name;//필드2
    int age;//필드3


    public void regist(String studentID, String name, int age)
    {					//메서드 생성 필드를 매개변수로 사용
    
        Console.WriteLine($"{this.name} 의 학번은 {this.studentID} 입니다.");
        if(age>20)
        {
            Console.WriteLine($"{this.age}세로 성인 공부방에 입장합니다.");
        }
        else
        {
            Console.WriteLine($"{this.age}세로 청소년 공부방에 입장합니다.");
        }
    }
}

Student 라는 클래스를 생성해 내부에 String타입의 studentID 와 name, integer 타입의 age 필드를 만들어 줬습니다.

이후 regist라는 '학원에 등록하는 행동을 하는' 메서드를 만들어 주었습니다.

※ this. 의 경우 이 class 안에 필드를 사용한다는 의미 입니다. 다음 강의에 같이 다루게 될 내용입니다.

하지만 이곳은 메인영역이 아니죠 따라서 클래스와 객체를 사용하는 방법을 알아야 합니다.

이 클래스와 객체를 사용하는 방법은 바로 생성자를 만들어주는 것 인데요.

생성자를 만들어 우리가 사용할 수 있도록 인스턴스로 변화시켜줘야 하기 때문입니다.

이러한 생성자는 객체를 초기화하는 역할을 합니다.

 

 

 

[클래스이름] [객체로 사용될 이름] = new [생성자]();

↑생성자의 기본적인 형태

바로 예시를 들겠습니다.

 static void Main(string[] args)
{
	Student student = new Student();
}

위와 같이 생성자를 만들어줍니다.

인스턴스화로 인해 아무런 형체가 없던 글은 드디어 형체를 가지고 우리가 사용할 수 있게 되었습니다.

바로 사용 예시를 들어보면

    class Program
    {
        static void Main(string[] args)
        {
            Student student = new Student();

            student.name = "강호동";
            student.age = 22;
            student.studentID = "20211008.325";
            student.regist(student.studentID, student.name, student.age);//Student클래스 메소드
            Console.WriteLine();
        }
    }

 

이 student는 class Student 와 연결이 되어있어 하나씩 꺼내서 사용 하고 있는 것인데요.

student . 을 사용해서 하나씩 꺼내와 내용에 넣어주고 있는 것 입니다.

그리고 마지막에 regist 메소드를 사용하여 출력까지의 결과를 보면

이렇게 나오는 것을 확인 할 수 있습니다.

하지만 하나만 사용해서는 이 객체지향프로그래밍의 장점을 알 수가 없죠

여러가지 객체를 만드는 것을 보면 어느정도 이해가 가실 것 입니다.

    class Program
    {
        static void Main(string[] args)
        {
            //############################ 생성자 1 ################################
            Student student = new Student();

            student.name = "강호동";
            student.age = 22;
            student.studentID = "20211008.325";
            student.regist();
            Console.WriteLine();
            
            //############################ 생성자 2 ################################
            Student student2 = new Student();//새로운 생성자 생성

            student2.name = "유재석";
            student2.age = 19;
            student2.studentID = "20211008.326";
            student2.regist();
            Console.WriteLine();

            //############################ 생성자 3 ################################
            Student student3 = new Student();//새로운 생성자 생성

            student3.name = "이수근";
            student3.age = 21;
            student3.studentID = "20211008.327";
            student3.regist();
            Console.WriteLine();

            //############################ 생성자 4 ################################
            Student student4 = new Student();//새로운 생성자 생성

            student4.name = "박명수";
            student4.age = 17;
            student4.studentID = "20211008.328";
            student4.regist();
            Console.WriteLine();
        }
    }

위 와같이 각 생성자만을 새로 만들어 새 변수에 담아 하나의 필드를 여러번 사용하는 것이 가능하다는 장점이 있습니다.  물론 이는 반복문을 통해서 더욱 간결하게 만들수 있으니 이는 여러분이 해결해보시면 좋을 것 같습니다.

 


 

오늘 사용된 전체 코드 ↓

더보기
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ConsoleApp1
{
    class Student
    {
        public String studentID;
        public String name;
        public int age;

        public void regist()
        {
            Console.WriteLine($"{this.name} 의 학번은 {this.studentID} 입니다.");
            if(age>20)
            {
                Console.WriteLine($"{this.age}세로 성인 공부방에 입장합니다.");
            }
            else
            {
                Console.WriteLine($"{this.age}세로 청소년 공부방에 입장합니다.");
            }
        }
    }


    class Program
    {
        static void Main(string[] args)
        {
            //############################ 생성자 1 ################################
            Student student = new Student();

            student.name = "강호동";
            student.age = 22;
            student.studentID = "20211008.325";
            student.regist();
            Console.WriteLine();
            
            //############################ 생성자 2 ################################
            Student student2 = new Student();//새로운 생성자 생성

            student2.name = "유재석";
            student2.age = 19;
            student2.studentID = "20211008.326";
            student2.regist();
            Console.WriteLine();

            //############################ 생성자 3 ################################
            Student student3 = new Student();//새로운 생성자 생성

            student3.name = "이수근";
            student3.age = 21;
            student3.studentID = "20211008.327";
            student3.regist();
            Console.WriteLine();

            //############################ 생성자 4 ################################
            Student student4 = new Student();//새로운 생성자 생성

            student4.name = "박명수";
            student4.age = 17;
            student4.studentID = "20211008.328";
            student4.regist();
            Console.WriteLine();
        }
    }
}

 


다음에서는 접근제한자this. 에 대해서 알아보겠습니다.

'C#' 카테고리의 다른 글

[C#_10_(2)] 접근제한자, this  (0) 2021.10.18
[C#_9] 배열 (Array)  (0) 2021.10.07
[C#_8] 메서드 method  (0) 2021.10.02
[C#_7] 반복문 제어 (Break, Continue, goto)  (0) 2021.09.29
[C#_6] 반복문(for,foreach,while,do while)  (0) 2021.09.24
1. 배열

Array 라고 불리는 이 배열의 개념은 동일한 자료형의 집합체로 같은 타입의 자료를 연속된 공간에 나열하고 각 자료에 index를 부여한 것이기 때문입니다.

1-2. 배열의 선언

이전에 반복문을 할 때 잠시 배열을 언급하며 넘어갔었는데요.(반복문 게시물 2)

이 때 언급한 것이 배열의 형태 였습니다.

타입[] 배열이름 = new 자료형[배열의 길이]

이는 배열의 가장 기본이 되는 형태인데요, 이해를 돕기위해 예제를 사용해보겠습니다.

int[] intArray = new int[5];

이렇게 'int형태의 배열이며 길이가 5인 배열을 선언하겠다' 라는 것을 코드로 적어둔 것 입니다.

사실 배열을 선언하는 방법은 두 가지가 있는데요.

  • 첫 번째 : 배열의 길이를 먼저 선언하고 나중에 배열의 인자를 넣어주는 방법
  • 두 번째 : 배열의 선언과 동시에 인자를 넣어주는 방법
  • 세 번째 : index를 이용하여 인자를 넣어주는 방법 (이는 index 설명과 함께 설명하겠습니다.)

위에 사용한 예시가 바로 첫번째 방법입니다.

내부에 배열의 인자를 작성하려면 아래와 같은 방법으로 하시면 됩니다.

int[] intArray = new int[5];
intArray = new int[] { 1, 2, 3, 4, 5 };

 

두 번째 방법을 예시로 들자면 위 첫번째 방법을 한번으로 하는

int[] intArray = new int[] { 1, 2, 3, 4, 5 };

과 같은 방법과 조금 약식처럼 쓰는 아래와 같은 방법이 있습니다.

int[] intArray = { 1, 2, 3, 4, 5 };

두 번째 방법들은 배열의 인자들을 넣어주면서 길이를 자동으로 지정해주기 때문에

따로 길이를 설정해줄 필요는 없습니다.

2. index

배열을 선언하면서 내부에 인자들을 넣었으나 우리는 이 인자에 대한 접근을 몰라 출력을 해줄수가 없습니다.

일반적인 방법으로 출력한다면 결과가 이상하기 때문인데요.

static void Main(string[] args)
{
	int[] intArray = new int[5];
	intArray = new int[] { 1, 2, 3, 4, 5 };
            
	Console.WriteLine(intArray);
}

이대로 출력하게 된다면

이렇게 이상한 값이 나오게 됩니다.(게시글 마지막 부분 추가설명)

이 때 우리가 필요해진 것이 인덱스(index) 인데요.

위에서 설명했듯이 각 인자들에 index 가 부여되어있기 때문에 접근 할 수 있게 된 것입니다. 

  • 부여된 인덱스는 0부터 시작합니다.

위처럼 길이가 10인 배열을 선언하면 0~9까지의 인덱스가 부여되어 있는 것을 확인할 수 있습니다.

인덱스를 이용한 인자를 넣는 방법도 있으니

2-1. index를 이용한 배열의 출력

우리는 이러한 index를 통해 배열을 출력해볼 것 입니다.

예를 들어보겠습니다. 

static void Main(string[] args)
{
      String[] subject = new string[5];
      subject[0] = "kor";
      subject[1] = "eng";
      subject[2] = "math";
      subject[3] = "social";
      subject[4] = "science";
}

subject라는 String 타입의 배열을 선언해준 뒤 길이를 5로 설정해주었습니다.

이후 각 index를 이용하여 내부에 인자를 넣어주었습니다.

이렇게 각 index로 인자에 접근할 수 있는 것을 보니 index를 사용한다면 우리가 출력까지도 해줄 수 있을 것 같으니

시도해보겠습니다.

static void Main(string[] args)
{
      String[] subject = new string[5];
      subject[0] = "kor";
      subject[1] = "eng";
      subject[2] = "math";
      subject[3] = "social";
      subject[4] = "science";

      Console.WriteLine(subject[0]);
      Console.WriteLine(subject[1]);
      Console.WriteLine(subject[2]);
      Console.WriteLine(subject[3]);
      Console.WriteLine(subject[4]);
}

이렇게 잘 출력되는 것을 볼 수 있는데요.

잘 보니까 이전에 배웠던 것을 활용하여 출력할 수 있을 것 같은데요.

바로 반복문입니다.

2-2. 반복문을 이용한 배열 출력
static void Main(string[] args)
{
      String[] subject = new string[5];
      subject[0] = "kor";
      subject[1] = "eng";
      subject[2] = "math";
      subject[3] = "social";
      subject[4] = "science";

      for(int i=0; i<subject.Length; i++)
      {
     	 Console.WriteLine(subject[i]);
      }
}

위와같이 반복문으로 출력했을 때도

같은 결과가 나온 것을 볼 수 있습니다.

그런데 코드를 보니 조금 특이한 부분이 있죠?

바로 for문의 조건문 부분입니다.

subject 배열의 Length(길이) 만큼의 값을 준다는 것 입니다.

namespace ConsoleApp1
{
    class Program
    {
        static void Main(string[] args)
        {
            String[] subject = new string[5];

            Console.WriteLine(subject.Length);
        }
    }
}

이를 출력해보면

이렇게 5라는 값이 나온 것을 볼 수 있습니다.

이러한 length를 통해서 배열의 길이를 확인하거나

따로 배열의 길이를 책정할 필요없이 유동적 사용이 가능한 속성입니다.

유용하니 꼭 알아두시길 바랍니다.


3. 다차원 배열

다차원 배열이란 말 그대로 여러가지 차원을 가진 배열입니다.

우리가 지금까지 하나의 선과 같은 1차원 배열을 사용했다면

다차원 배열은 면 또는 상자 처럼 여러가지 차원을 가지고 있다는 이야기 입니다.

 

먼저 2차원 배열부터 알아보겠습니다.

2차원 배열은 

가로, 세로의 평면 구조를 가지고 있습니다.

따라서 이를 코드로 보면

자료형[,] 배열명 = new 자료형[행 수, 열 수];

의 기본형태를 가지고있습니다.

※자료형 뒤에 배열임을 선언할 때 ' , ' 가 있는 것을 볼 수 있는데 이는 본래 자료형의 크기를 넣을 때 행과 열로 나눌 필요가 없었기 때문입니다. 

다차원 배열의 경우는 이를 따로 선언해 줄 필요가 있기 때문에 ' , '가 들어간 것 입니다.

 

 

 

 

그렇다면 이러한 2차원 배열을 예시로 들어보겠습니다.

int[,] multiArr = new int[2, 4] { { 0, 1, 2, 3 }, { 4, 5, 6, 7 } };

이러한 2차원 배열을 도형으로 표시하면

와 같습니다.  이를 출력해보면

static void Main(string[] args)
{
    int[,] multiArr = new int[2,4]{ { 0, 1, 2, 3}, { 4, 5, 6, 7} };

    Console.WriteLine(multiArr[1,2]); // 6
    Console.WriteLine(multiArr[0,3]); // 3
    Console.WriteLine(multiArr[1,1]); // 5
}

 

위치 값에 따라 결과가0 나오는 것을 볼 수 있습니다.

 

 

 

 

 

 

 

이러한 2차원 배열 말고 3차원 4차원 배열도 있습니다만 코드가 너무 복잡해져 일반적으로는 잘 사용하지 않습니다.

자료형[,,] 배열명 = new 자료형[높이, 세로, 가로];

3차원 배열의 기본형식은 이렇습니다.

이 3차원 배열은 2차원 배열을 인자로 가지고 있는 형식입니다.

조금 복잡하니 이해를 돕기위해 예를 들면

namespace ConsoleApp1
{
    class Program
    {
        static void Main(string[] args)
        {
            int[,,] multiArr = new int[3, 2, 3]
            {
                {{1,1,1}, {1,1,2}},
                {{2,1,1}, {2,1,2}},
                {{3,1,1}, {3,1,2}}
            };
            
        }
    }
}

이를 도형으로 만든다면 아래와 같은 모형이 됩니다.

 


※ 추가설명

사실 배열을 선언할 때 배열의 이름을 설정하면 설정한 이름에 공간이 만들어져 배열의 인자가 설정된 공간으로 들어가는 형식이 아닙니다. (일반적인 변수)

배열의 이름을 설정하면 사실 배열의 실제 공간은 따로 있고 배열의 이름은 이를 직접적으로 참조하여 값을 공간으로 보내는 역할을 하는 '주소값'의 역할을 하고있는 것 입니다.


여기까지 배열이었습니다. 고생하셨습니다.

'C#' 카테고리의 다른 글

[C#_10_(2)] 접근제한자, this  (0) 2021.10.18
[C#_10_(1)] Class  (0) 2021.10.08
[C#_8] 메서드 method  (0) 2021.10.02
[C#_7] 반복문 제어 (Break, Continue, goto)  (0) 2021.09.29
[C#_6] 반복문(for,foreach,while,do while)  (0) 2021.09.24
메서드?
C# 에서 메서드란 C 또는 C++에서 함수(Function)의 역할을 하는 것.

메서드의 기본형태는

static void 메서드이름(매개변수) //매개변수의 또다른 이름은 파라미터
{
    코드
}

입니다.

 

이러한 기본형태는 사실 우리가 흔히 쓰고있었 습니다.

바로 코드를 작성할 때 우리가 가장 기본적으로 사용하고 있는 main에서 인데요

static void Main(string[] args)
//				└매개변수
{
    
}

메인 메서드는 컴퓨터가 프로그램을 실행하면 가장 먼저 찾아서 실행해주는 영역입니다.

그래서 메서드를 선언할 때 이 메인 메서드 영역 밖에서 메서드를 작성하고

그 메서드를 메인에 불러오는 형식으로 작성해주면 되는데요.

예를 들면

//예시 1
namespace ConsoleApp1
{
    class Program
    {
        static void plus(int a, int b)
        {
            Console.WriteLine(a+b);
        }
        static void Main(string[] args)
        {
            int a = 10;
            int b = 20;

            plus(a, b);
        }
    }
}

의 형식인 것입니다. 코드를 보면 class 영역 내부 main영역 외부에 작성된 메서드를 보실 수 있는데요.

  1. 프로그램 시작
  2. main메서드 실행
  3. 변수 a , b 선언
  4. plus 메서드에 변수 a,b를 매개변수로 넣고 호출
  5. plus메서드 영역으로 이동
  6. 출력문 출력
  7. plus메서드 종료
  8. 메인 메서드 종료

이러한 순서로 진행되고 있는 것입니다.


메서드를 배우면서 저는 궁금증이 들었는데요 static? void? 이것은 무엇인가? 하는 것이었습니다.

이중 static 의 경우는

이중 void는 '리턴 타입'인데요

이는 메소드를 실행하고 난 이후의 값의 타입이 무엇인가에 따라서 다르게 작성해주어야 합니다.

더보기

void : 리턴값의 타입이 없음

int   : 리턴값의 타입이 정수형

double : 리턴값의 타입이 실수형

String : 리턴값의 타입이 문자열

char : 리턴값의 타입이 문자

boolean : 리턴값의 타입이 bool

만일 리턴타입이 존재하게 작성을 한다면 우리는 반드시

내부에 return을 작성하여 그에 맞는 타입을 return해주어야합니다.(자동 형태변환)

//예시2
namespace ConsoleApp1
{
    class Program
    {
        static int plus(int a, int b)
        {
            return a+b;
        }
        static void Main(string[] args)
        {
            int a = 10;
            int b = 20;

            int c = plus(a, b);
            Console.WriteLine(c);
        }
    }
}

위 코드를 보면 예시1에서 사용했던 void가 아닌 int로 작성되어있는 것을 볼 수 있습니다.

위에 설명했던 것 처럼 int 타입으로 값을 리턴한다는 이야기 입니다.

이렇게 리턴타입이 있을경우 메인 함수에서는 그 리턴된 값을 받아줄 변수가 하나 필요하며

이 변수에 리턴된 값을 담아 출력된 것을 볼 수 있습니다.

 

위 예시1은 리턴값이 없어 메서드 기능에서 출력시킨 것


1-1. call by value (간접 참조)
1-2. call by reference (직접 참조)

이 두개를 한번에 정리하는 이유는 하나는 직접 참조이고 하나는 간접 참조이므로 둘이 비교하며 보여드리면

조금 더 이해가 쉬울 것 이기 때문입니다.

 

namespace ConsoleApp1
{
    class Program
    {
    	static void Swap(int a, int b)
        {
            int temp; 
            temp = a;
            a = b;
            b = temp;
        }// 두 변수의 값을 맞바꿔주는 swap 메서드
        
        static void Main(string[] args)
        {
            int a = 10;
            int b = 20;

            Console.WriteLine($"Swap메서드 실행 전: a={a}, b={b}");

            Swap(a, b);

            Console.WriteLine($"Swap메서드 실행 후: a={a}, b={b}");
        }
    }
}

위 코드를 보시면  a는 10 , b는 20의 값을 담고 있습니다.

이를 출력문으로 확인해주고 swap메서드를 이용하여 두 변수에 담긴 값을 맞바꿔줍니다.

이 값이 잘 바뀌었는지 확인해보면

값이 바뀌지 않은 것을 볼 수 있습니다.

이는 call by value 즉 간접참조 형식을 사용했음을 할 수 있는데요.

이는 메서드에서 매개변수 값을 복사하여 내부적으로 처리한 것 입니다.

 

위 코드는 리턴값이 없는 void Swap(int a, int b) 메서드 인 것을 확인 할 수 있습니다.

매개변수를 받으면서 int a와 int b라는 공간을 만들고 내부 코드를 돌립니다. 하지만

메인 메서드 내부에서 값을 받아주지 않았으니까 Swap()메서드 영역이 종료되면서

복사된 a와 b의 공간이 같이 사라진 것 입니다.

그래서 메인으로 돌아왔을 때 a와 b값은 그대로인 것이죠.

 

이를 해결하고 싶을 때 우리는 call by reference(직접참조) 형식을 사용하면됩니다.

예시를 들어보겠습니다.

namespace ConsoleApp1
{
    class Program
    {
        static void Main(string[] args)
        {
            int a = 10;
            int b = 20;

            Console.WriteLine($"Swap메서드 실행 전: a={a}, b={b}");

            Swap(ref a,ref b); //변경점 : ref

            Console.WriteLine($"Swap메서드 실행 후: a={a}, b={b}");
        }

        static void Swap(ref int a,ref int b) //변경점 : ref
        {
            int temp; 
            temp = a;
            a = b;
            b = temp;
        }
    }
}

이렇게 ref(erence) 를 메서드 호출과 메서드 생성시 매개변수에 넣어주면서

메모리 주소값에 직접적으로 관여해 값을 바꾸는 형식으로 해결할 수 있습니다.


2. 메서드 오버로딩 (overloading)

메서드 오버로딩은 메서드의 이름이 같을 때 매개변수의 타입과 갯수에 따라서 실행을 다르게 하는 시스템입니다.

method A(int a, int b)
{
	return a+b;
}

method A(int a, int b, int c)
{
 	return a+b+c;
}

예시와 같이 같은 이름을 가진 두개의 메서드 A가 있습니다. 

우리가 메인메서드에서 A메서드를 호출해줄 때 메서드 이름만 가지고 컴퓨터가 메서드영역을 불러온다면 [Ex) A(1,2)]

첫 A 메서드는 실행이 되어도 두번째 A메서드에서는 매개변수 부족으로 인한 오류가 나기 때문에

프로그램은 망가지게 됩니다.

이를 방지해 주는 것이 메서드 오버로딩입니다.

매개변수의 갯수 또는 타입을 구분하여 메인 메서드에서 A(1,2) 로 호출시 첫 A메서드만 호출 되는 것 입니다.

예시로 보자면

namespace ConsoleApp1
{
    class Program
    {
        static int Plus(int a, int b)// 정수형 매개변수 두개
        {
            Console.Write("int 끼리 더합니다.");
            return a + b;
        }

        static int Plus(int a, int b, int c)// 정수형 매개변수 세개
        {
            Console.Write("int 끼리 더합니다.");
            return a + b + c;
        }

        static double Plus(double a, double b)// 실수형 매개변수 두개
        {
            Console.Write("double 끼리 더합니다.");
            return a + b;
        }
        static void Main(string[] args)
        {
            Console.WriteLine(Plus(10, 20));//출력문(함수호출)
            Console.WriteLine();
            Console.WriteLine(Plus(10.5, 20.5));
            Console.WriteLine();
            Console.WriteLine(Plus(10, 20, 30));
            Console.WriteLine();
        }
    }
}

셋 다 같은 Plus 라는 이름을 가진 메서드이지만 그 결과를 보면

 

이렇게 각 메서드가 잘 호출되어 나타난 것을 알 수 있습니다.

'C#' 카테고리의 다른 글

[C#_10_(1)] Class  (0) 2021.10.08
[C#_9] 배열 (Array)  (0) 2021.10.07
[C#_7] 반복문 제어 (Break, Continue, goto)  (0) 2021.09.29
[C#_6] 반복문(for,foreach,while,do while)  (0) 2021.09.24
[C#_5] 조건문 (if, else, switch)  (0) 2021.09.23

이전강의에서 저희는 반복문을 배워보았습니다.

이 반복문은 잘못하면 무한루프에 빠진다고 이전시간에 언급했었는데요.

이 반복문을 제어할 수 있는 방법이 있습니다.

Break, continue, goto 제어문을 통해서 입니다.


1. Break

break는 '가장 가까운 반복문을 빠져나오는 역할' 을 수행하는 제어문입니다.

namespace ConsoleApp1
{
    class Program
    {
        static void Main(string[] args)
        {
            int ForEnd = 0;
            
            for(int i=0; i<10; i++)
            {
                if(ForEnd<5)
                {
                    Console.WriteLine(ForEnd);
                    ForEnd++;
                }
                else
                {
                    break;
                }

            }
        }
    }
}

코드를 해석해보자면

반복문(for)가 돌아가고 있는 중 ForEnd라는 변수가 5보다 작을 때는 변수에 담긴 수를 출력하고

ForEnd에 1씩 더한다는 내용이 if문 내부에 있습니다.`

else에 들어갈 조건은 당연하게도 'ForEnd가 5와 같거나, 보다 클 때' 인데요, 이 때 break와 가장 가까운 반복문

즉, int 형 변수 i를 사용하는 반복문을 종료한다는 것 입니다.

 결과를 보시면 ForEnd변수가 5가 됨에 따라 else문에서 break를 만나 4에서 출력이 멈춘것을 볼 수 있습니다.

위에 강조해둔 break와 가장 가까운 반복문 이라는 부분은 중요한 포인트입니다.

이를 코드로 확인해보겠습니다.

더보기
namespace ConsoleApp1
{
    class Program
    {
        static void Main(string[] args)
        {
            
            for(int i=0; i<10; i++)
            {
                for(int j=0; j<10;j++)
                {
                    Console.WriteLine($"i : {i}, j : {j}");
                }

            }
        }
    }
}

이는 평범한 다중 for문 형태입니다.

그 결과는

이처럼  i가 9, j가 9가 될 때까지 나오는 것을 볼 수 있는데요.

이제 분기문을 사용해서 j가 3이 되었을 때 끊어보겠습니다. 

namespace ConsoleApp1
{
    class Program
    {
        static void Main(string[] args)
        {
            
            for(int i=0; i<10; i++)
            {
                for(int j=0; j<10;j++)
                {
                    Console.Write($"i : {i}, j : {j}\t");
                    if (j == 3)
                        break;
                }
                Console.WriteLine();
                Console.WriteLine();
            }
        }
    }
}

보기 편하도록 코드를 적어보았는데요. 그 결과는

이렇습니다. j가 3이 되면 j반복문이 끊어지고 다시 i반복문으로 향합니다. 

i가 1 올라가고 다시 j반복문으로 내려와 3까지 진행한후 끊어지는 것을 반복하는 모습을 볼 수 있습니다.

이처럼 break는 break와 가까운 반복문을 탈출하는 모습을 볼 수 있습니다.


2. continue

 

 

 

continue는 반복문에서 '조건을 검사하는 부분으로 가는' 제어문입니다.

예시코드를 보겠습니다.

namespace ConsoleApp1
{
    class Program
    {
        static void Main(string[] args)
        {
            
            for(int i=1; i<=5; i++)
            {
                if (i == 3)
                    continue;
                for (int j=1; j<=5;j++)
                {
                    Console.WriteLine($"i : {i}, j : {j}");
                }
                Console.WriteLine();
            }
        }
    }
}

이전 break는 반복문 자체를 탈출하여 그 뒤로는 실행이 안되었습니다.

하지만 continue는 반복문 자체를 탈출하는 것이 아닌 그 조건일 때

반복문의 조건을 검사하는 부분으로 올라가는 것이므로 3이외의 모든 코드가 나와야 합니다.

i가 3일 때 continue를 해주어서 i값이 3일 때가 없는 것을 볼 수 있습니다.


3. goto

goto문은 '지정된 레이블로 건너뛰는' 제어문입니다.

※ 레이블(Lable) : 코드 내에 데이터 위치를 나타내주는 역할

 

goto 레이블명;
...
레이블: 실행될 코드

위 코드는 goto의 기본형태입니다.

 

이를 코드로 보자면

namespace ConsoleApp1
{
    class Program
    {
        static void Main(string[] args)
        {
            for(int i=1; i<=10; i++)
            {
                if(i==3)
                {
                    goto numThree;
                }
                Console.WriteLine($"i = {i}");
            }
            numThree: Console.WriteLine("숫자 3입니다.");
        }
    }
}

i를 계속 출력하던 중 if조건문을 만나 goto 문으로 반복문 바깥부분으로 넘어가는 것을 볼 수 있습니다.

goto 문은 이렇게 반복문 탈출로도 사용할 수 있지만 위에 언급한 것 처럼

원하는 부분으로 코드를 뛰어넘는 역할을 하는 것으로 이해하면 원활한 이해가 가능할 것 같습니다.

namespace ConsoleApp1
{
    class Program
    {
        static void Main(string[] args)
        {
            for (int i=1; i<=9; i++)
            {
                if (i == 3)
                    goto Jump;//여기서 점프
                Console.WriteLine($"i = {i}");
            }

            Console.WriteLine("점프되는 부분");//건너뛰어짐으로 출력되지않음

            Jump://여기로 점프
            Console.WriteLine();//줄바꿈
            for (int k = 5; k <= 9; k++)
            {
                Console.WriteLine($"k = {k}");
            }

        }
    }
}

코드를 보시면 i가 3이 되고 Jump레이블로 이동을 시작합니다. 반복문을 종료시키고 중간에 껴있는 출력문 역시 건너뛰어 Jump레이블로가 줄바꿈과 새로운 for 반복문을 출력합니다.

 

'C#' 카테고리의 다른 글

[C#_9] 배열 (Array)  (0) 2021.10.07
[C#_8] 메서드 method  (0) 2021.10.02
[C#_6] 반복문(for,foreach,while,do while)  (0) 2021.09.24
[C#_5] 조건문 (if, else, switch)  (0) 2021.09.23
[C#_4] 출력문, 입력문, 연산자, 문자열 보간  (0) 2021.09.21
반복문

for, foreach, while, do while 4가지의 반복문이 있습니다.

전부 조금씩 다르니 포인트를 잘 잡아야 합니다.


1. for
for (int i = 0; i < length; i++)
{
	//반복될 문장
}

(for문의 기본형태)

위 코드는 for문의 기본형태입니다.

코드에서 보면 괄호 안에 지금까지와는 다르게 적혀있는 조건을 볼 수 있는데요.

각 부분으로 나누어 보자면

for (초기화식 ; 조건식 ; 증감식) 으로 보시면 됩니다.

int i라는 변수를 0에서부터(초기화식) ; length(=길이)보다 작을 때까지{조건식) ; 하나씩 올려준다(증감식)

로 해석할 수 있습니다.

 

namespace ConsoleApp1
{
    class Program
    {
        static void Main(string[] args)
        {
            for (int i = 0; i < 10; i++)
            {
                Console.Write(i + " ");
            }
            Console.WriteLine();
        }

    }
}

(이해를 돕기 위한 예시코드)

위 코드를 실행해보면 

위 사진과 같은 결과가 나옵니다.

0에서 부터 시작된 int i 가 10보다 작을 때 까지 하나씩 증가 되면서 출력 된 것을 알 수 있습니다.

 

기본 형태라고 적어뒀지만 그 형태에 집착하지 말아야 합니다.

이유는 초기화식;조건식; 증감식 의 변화가 가능하기 때문인데요

코드로 보자면

namespace ConsoleApp1
{
    class Program
    {
        static void Main(string[] args)
        {
            for (int i = 10; i >= 0; i -= 2)
            {
                Console.Write(i + " ");
            }
            Console.WriteLine();
        }

    }
}

과 같이 for문의 모든 식이 달라지는 것을 확인할 수 있습니다.

결과


1-2 이중(다중) for문

if(조건문)에서 보여드렸던 if문 안에 if문이 들어갈 수 있는 것을 기억하시나요?

for문 역시 마찬가지로 for문안에 for문이 들어갈 수 있습니다.

코드로 보겠습니다.

namespace ConsoleApp1
{
    class Program
    {
        static void Main(string[] args)
        {
            for (int i = 2; i < 10; i++)
            {
                for(int j = 1; j < 10; j++)
                {
                    Console.WriteLine($"{i}X{j}={i*j}");
                }
                Console.WriteLine();
            }
            
        }

    }
}

다중for문의 i반복문(외(外)반복문) 이 2일 때 j반복문(내(內)반복문)은 1~9까지 전부 돌고

다시 외반복문으로 돌아와서 i에 1을 더해 3이 되고 j가 1~9까지 도는 것

이 과정들이 i가 10이 됨과 동시에 내외반복문이 모두 종료되며 끝나는 것 입니다.

 


2. foreach

foreach문을 알려면 먼저 배열을 알아야 합니다.

하지만 배열은 나중에 다루게 될 부분이라 이번에는 대략적인 부분만 짚고 넘어가겠습니다.

배열의 기본적인 형태는 

int[] Array = {0,1,2,3,4,5};

int타입의 [배열] 배열이름 = {배열의 인자들}

배열의 내부의 인자들은 반드시 선언시 {중괄호}를 사용해서 만들어주어야 합니다.


이 배열을 foreach문을 통해서 내부 인자들을 출력해볼 것인데요.

foreach (변수타입 변수이름 in 배열)
{
	//Console.WriteLine(변수이름)
}

foreach의 기본형태

기본형태에서 변수 타입은  int, char, String 등이 올 수 있습니다.

※ 보통 var이라고 하는 컴파일러가 컴파일링 할 때 알아서 변수타입을 지정하는 유동적인 형태의 변수타입을 사용합니다.

변수이름은 우리가 편하게 알아볼 수 있는 이름 을 사용하면 됩니다.

(Ex : 배열의 인자가 숫자라면 num, 문자열이라면 str 등) 

배열에는 우리가 미리 선언한 배열의 이름을 사용해주시면 됩니다.

 

이해를 돕기 위해 예시코드를 보겠습니다.

namespace ConsoleApp1
{
    class Program
    {
        static void Main(string[] args)
        {
            int[] intArray = { 0, 1, 2, 3, 4, 5 };

            foreach (var num in intArray)
            {
                Console.WriteLine($"num : {num}");
            }
            
        }

    }
}

예시코드에서는 배열을 먼저 선언해주고 foreach문에서 intArray에 있는 인자들을 차례대로 꺼내와

변수 num에 담아서 출력한 모습입니다.

출력된 모습


3. while

while문은 특정 조건을 만족할 때 까지 반복하는 문장입니다.

아래는 while문의 기본형태입니다.

while(조건)
{
	실행할 문장
}

if문에서 배웠던 것 처럼 while문에도 조건이라는 부분이 붙어있습니다.

조건 부분에 들어갈 연산자는 Boolean값이 나오는 연산자를 사용해야합니다.(관계연산자, 논리연산자 등)

while문은 조건이 True일 때 반복을 지속하며 False일 때 반복문을 빠져나온다는 것 입니다.

만약 조건 코드를 잘못하여 조건이 계속 True가 나오게 된다면 무한루프에 빠지게 되는 것 입니다.

 

↓ 무한루프

더보기
int a = 0;

while(a<10)
{
	Console.WriteLine(a); //무한히 0이 출력됨
}

※ 코드가 길어졌을 때 무한루프에 빠지게 된다면 심한 경우 컴퓨터에 큰 무리가 갈 수 있습니다.

무한루프 시 모습

↓ 무한루프 해결

더보기
int a = 0;

while(a<10)
{
	Console.WriteLine(a);
    	a++; //루프가 한번 돌 때 a의 값을 1씩 증가시켜 a가 10이 될 때 종료시킴
}

int 변수 a 가 1씩 증가하면서 a가 10이 될 때 조건이 False가 되면서 반복문을 탈출함

 

조건이 True, False인 만큼 Boolean타입을 넣어서 탈출 할 수도 있습니다.

namespace ConsoleApp1
{
    class Program
    {
        static void Main(string[] args)
        {
            bool tBool = true;
            int num = 0;

            while(tBool)
            {
                Console.WriteLine(a);
                num++;
                if (num == 10) 
                {
                    tBool = false;
                }
            }
        }

    }
}

Boolean 타입의 변수 tBool을 True로 사용하기 위해 선언한 뒤 조건에 넣었습니다.

이때 조건이 계속 true가 되는 것 입니다. 우리가 전에 배운 if문을 사용하여 int변수 num이 10이 될 때

Boolean 타입의 변수 tBool 을 False로 바꿔주어 반복문을 탈출 시킨 것 입니다.


4. do while

do while문은 기본적으로 while문과 동일하나 차이점이 있습니다.

우선 do while문의 기본형태입니다.

do{
	실행코드
}while(조건);

do : 실행하고

while : 반복하고

조건 : 조건을 검사한다.

이렇게 먼저 한번 실행을 진행하고 반복으로 넘어가면서 조건을 검사해 반복해주는 코드입니다.

예시로 보겠습니다.

namespace ConsoleApp1
{
    class Program
    {
        static void Main(string[] args)
        {
            int n = 0;
            do
            {
                Console.WriteLine($"{n+1}회 실행");
                n++;
            } while (false);
        }

    }
}

코드를 보시면 일부러 while조건 부분을 false로 반복문이 실행되지 않도록 해두었습니다.

하지만 결과를 보시면

이처럼 한번은 무조건 실행되는 모습을 볼 수 있습니다.

이후 조건을 검사했는데 false라 더이상 반복을 진행하지 않은 모습역시 확인할 수 있습니다.

기본적으로 while문과 동일하나 한번은 무조건적으로 실행시켜주어야 할 때 사용하면 좋은 코드입니다.

'C#' 카테고리의 다른 글

[C#_8] 메서드 method  (0) 2021.10.02
[C#_7] 반복문 제어 (Break, Continue, goto)  (0) 2021.09.29
[C#_5] 조건문 (if, else, switch)  (0) 2021.09.23
[C#_4] 출력문, 입력문, 연산자, 문자열 보간  (0) 2021.09.21
[C#_3] 변수, 자료형  (0) 2021.09.12

+ Recent posts