한 글자씩 쓰기

텍스트 파일에 한 글자씩 쓰는 함수는 fputc(c, fp)다. (문자c를 fp에 씀) 

파일 "alphabet.txt"에 a부터 z까지 쓰는 코드는 아래와 같다.

#include <stdio.h>

int main(void)
{
	FILE* fp = NULL;

	fp = fopen("alphabet.txt", "w");
	if (fp == NULL) {
		fprintf(stderr, "파일 alphabet.txt를 열 수 없습니다.\n");
		exit(1);
	}

	char c;
	for (c = 'a'; c <= 'z'; c++)
		fputc(c, fp);

	fclose(fp);
	return 0;
}

결과는 아래와 같다.

예전에 교수님이 과제나 시험 코드 자꾸 cpp파일로 제출하라고 하셨었는데 cpp로 위에 코드 실행하면

식별자"exit"을 찾을 수 없다 고 오류가 뜬다. (왜 그런지 찾아보기)

c로 저장하니까 정상적으로 실행은 된다.

 

한 글자씩 읽기

이번에는 파일 alphabet에서 한 글자씩 읽어보겠다.

fgetc(fp)를 이용한다. (fp에서 하나의 문자를 읽어서 반환)

#include <stdio.h>

int main(void)
{
	FILE* fp = NULL;
	int c;

	fp = fopen("alphabet.txt", "r");
	if (fp == NULL) {
		fprintf(stderr, "파일 alphabet.txt를 열 수 없습니다.\n");
		exit(1);
	}

	while ((c = fgetc(fp)) != EOF)
		putchar(c);

	fclose(fp);
	return 0;
}

EOF는 stdio.h에 정의되어 있고 -1이다. 일반적인 문자의 값은 -1이 아니어서 fp에서 가져온 문자c가 EOF가 아닐 때까지 반복하여 하나의 문자씩 출력이 가능하다.

결과는 이렇다.

 

한 줄씩 읽고 쓰기

텍스트 파일에서 한줄씩 읽고 쓰려면 fputs(), fgets()를 사용한다.

fputs(s, fp) : 문자열 s를 fp에 쓴다.

fgets(fp) : fp에서 한 줄을 읽어서 반환한다.

#include <stdio.h>

int main(void)
{
	FILE* fp = NULL;
	char str[100];

	fp = fopen("file.txt", "r");

	if (fp == NULL) {
		fprintf(stderr, "파일 file.txt를 열 수 없습니다.\n");
		exit(0);
	}

	do {
		gets(str);
		fputs(str, fp);
	} while (strlen(str) != 0);

	fclose(fp);
	return 0;
}

 

파일 안의 내용을 변경시키는 방법을 주로 복습했다. 세번째 코드가 잘 되다가 뭘 잘못만졌는지 오류가 나고부터는 실행이 안되는데 이것도 수정을 해봐야겠다. 다음에는 시험에 나왔을 때 어려웠던 원하는 줄에 있는 문장 읽는 법을 알아보겠다. 

사실 이거는 Java라는 언어에 한해서만 중요한 지식이 아니지만, 적당한 카테고리를 찾지 못해서 이 카테고리에 적는다. 싱글톤 패턴은 주로 그 프로그램 내에 오직 하나만 생성되고, 생성된 인스턴스를 어디서나 접근 가능하게 한다. 이러한 싱글톤 패턴은 어플 내 데이터베이스나 통신 클라이언트에서 쓰인다. 여기서는 Retrofit의 통신을 위한 Client를 싱글톤 패턴으로 구현하는 것을 예시로 들 것이다. 약간 처음 보는 코드도 있겠지만은, 객체가 오직 하나만 '생성'된다는 점에 주목하고 코드를 보자.

public class RetrofitClient {
    private RetrofitInterface retrofitInterface;
    private String baseUrl = "URL";

    public RetrofitClient() {
        Retrofit retrofit = new Retrofit.Builder()
                .baseUrl(baseUrl)
                .addConverterFactory(GsonConverterFactory.create())
                .build();
        retrofitInterface = retrofit.create(RetrofitInterface.class);
    }

    public RetrofitInterface getRetrofitInterface() {
        return retrofitInterface;
    }
}

예제로 사용하기에는 살짝 과한 감이 있지만, 다음의 코드가 있다고 생각해보자. 여기서 Retrofit에 대해서 잠깐 설명을 해보자면, 통신이 있을 때마다, RetrofitClient의 getRetrofitInterace 메소드를 통해 인터페이스를 받아와 통신에 대한 코드를 작성한다. (Retrofit에 대해서 아직 그렇게 깊은 지식을 가지고 있지 못해 확답을 못하겠다.)

예를 들어, 통신이 필요한 곳이 클래스1, 클래스2, 클래스3, 총 3 곳이 있다고 생각해보자.

public class one {
        RetrofitClient retrofitClient = new RetrofitClient();
        RetrofitInterace retrofitInterace = retrofitClient.getRetrofitInterace();

        retrofitInterface.통신관련 메소드 {
            통신 관련 내용
        }
}
public class two {
        RetrofitClient retrofitClient = new RetrofitClient();
        RetrofitInterace retrofitInterace = retrofitClient.getRetrofitInterace();

        retrofitInterface.통신관련 메소드 {
            통신 관련 내용
        }
}
public class three {
        RetrofitClient retrofitClient = new RetrofitClient();
        RetrofitInterace retrofitInterace = retrofitClient.getRetrofitInterace();

        retrofitInterface.통신관련 메소드 {
            통신 관련 내용
        }
}

그러면 대충 이런 식의 코드를 짜게 될 것이다. 이렇게 해도 작동 상의 큰 문제는 없긴 하다. 하지만 사용할 때마다 RetrofitClient 객체를 만들게 되고 만약 이러한 작업이 많아지면 어떻게 될까? 사실상 RetrofitClient 객체는 이 프로그램에 하나만 필요함에도 불구하고 여러 개가 생성되며 메모리 낭비가 발생하고 만약 이 객체가 모든 클래스가 공유해야 될 객체라면 더더욱 문제가 된다.

자바의 문법을 익혔다면, private 접근 지정자를 알 것이다. private는 그 클래스 내에서만 호출이 가능하고 밖에서는 접근이 불가능하다. 싱글톤 객체를 만들기 위해서는 생성자를 private로 선언한다. 예를 들어서 이렇게 말이다. 

public class RetrofitClient {
    private RetrofitInterface retrofitInterface;
    private static String baseUrl = "URL";

    // 생성자를 private로 선언!!
    private RetrofitClient() {
        Retrofit retrofit = new Retrofit.Builder()
                .baseUrl(baseUrl)
                .addConverterFactory(GsonConverterFactory.create())
                .build();
        retrofitInterface = retrofit.create(RetrofitInterface.class);
    }
    // 생성자를 private로 선언!!
    
    public RetrofitInterface getRetrofitInterface() {
        return retrofitInterface;
    }
}

이제 밖에서는 이 클래스의 객체를 생성하지 못한다. 밖에서 생성하지 못하는 객체를 어떻게 사용하냐는 궁금증이 생길 수도 있지만, 해결법은 생각외로 간단한다. 내부에서 객체를 생성해주면 된다.

private static RetrofitClient instance = null;

public static RetrofitClient getInstance() {
    if (instance == null) {
        instance = new RetrofitClient();
    }
    return instance;
}

우선 클래스 내에 해당 객체를 담을 instance 변수를 선언하고 getInstance 메소드를 통해 이 instance를 받아올 수 있도록 한다. getInstance 메소드 내에서 instance가 비어있는 상태(null)인 상태일 때만 객체를 생성하므로, 객체가 오직 하나만 생성된다. 이를 만들 때 클래스에 고정된 멤버이므로 static으로 만들어주자.

public class RetrofitClient {
    private static RetrofitClient instance = null;
    private static RetrofitInterface retrofitInterface;
    private static String baseUrl = "URL";

    private RetrofitClient() {
        Retrofit retrofit = new Retrofit.Builder()
                .baseUrl(baseUrl)
                .addConverterFactory(GsonConverterFactory.create())
                .build();
        retrofitInterface = retrofit.create(RetrofitInterface.class);
    }

    public static RetrofitClient getInstance() {
        if (instance == null) {
            instance = new RetrofitClient();
        }
        return instance;
    }

    public static RetrofitInterface getRetrofitInterface() {
        return retrofitInterface;
    }
}

최종 형태는 다음과 같다. 싱글톤 패턴을 구현하는 방법의 경우, 인터넷을 보면 다양한 방법이 있고 이는 그 중 하나이다. 따라서 그때그때 알맞은 방법으로 구현해주면 된다.

저는 자바를 이용하여 간단한 기능을 포함한 수강신청 프로그램을 차차 만들어나갈 생각입니다.

<메인 코드>

package main;
import presentation.PMain;

public class Main {
  public static void main(String[] args) {
	PMain main = new PMain();
	main.show();
  }
}

<메인 화면 코드>

package presentation;

import java.util.Scanner;
import valueObject.VPersonalInfo;

public class PMain {

	public PMain() {
	}
	
	public void show() {
		Scanner scanner = new Scanner(System.in);
		
		System.out.println("수강신청 시스템입니다.");
		System.out.println("수강신청을 하려면 로그인 하세요.");
		System.out.println("회원가입이 안되신 분들은 회원가입 하세요.");
		System.out.println("다음 메뉴를 선택 하세요.");
		
		boolean finished = false; 
		while (!finished) {
			System.out.println("1:로그인, 2:회원가입, 3:나가기");
			System.out.println("선택 : ");
		
			int menuSelection = scanner.nextInt();
			if(menuSelection == 1) // 로그인 선택시
          	{ finished = true;
			} else if (menuSelection == 2) // 회원가입 선택시
         	{ finished = true;
			} else if (menuSelection == 3) // 나가기 선택시
        	{ finished = true;
			} else // 1,2,3이 아닌 다른 숫자를 선택했을 시 
          	  { System.out.println("잘못 입력 하셨습니다.");
	}}
		scanner.close();
}}

<회원가입 화면 코드>

package presentation;
import java.util.Scanner;
import service.SRegistration;
import valueObject.VPersonalInfo;

public class PRegistration {	
	
	public void show(Scanner scanner) {
		
		System.out.println("개인정보를 입력하세요.");
		
		VPersonalInfo vPersonalInfo = new VPersonalInfo();
		System.out.print("아이디를 입력하세요.\r\nID:");
		VPersonalInfo.id = scanner.next();
		System.out.print("비밀번호를 입력하세요.\r\n비밀번호:");
		VPersonalInfo.password = scanner.next();
		System.out.print("이름을 입력하세요.\r\n이름:");
		VPersonalInfo.name = scanner.next();
		
		System.out.println("개인정보가 입력되었습니다.");
	}
}

오늘은 우선 여기까지만 만들어봤습니다. 

아직까진 너무 엉성하게 만들어서 별다른 기능이 없지만 차후 점차 늘려나갈 계획입니다.  코드에 이상한 부분이 있다면 댓글로 알려주시면 감사하겠습니다.

// 필자 = 김재현 

글을 시작하기 앞서 거진 2개월 넘게 운영하는 이 블로그에 내가 쓸 글의 방향성을 제시해 놓는 것이 중요할 것 같다. 이 모임에 가입한 이유는 전과하기 앞서 프로그래밍 언어를 숙지하기 위함이기에 난 개발자 모임 블로그에 쓰는 나의 모든 글을 오답 검토와 개념 복습으로 쓸 예정이다.

 

출력되는 각 숫자간의 간격을 띄기 위해 무엇을 해야 할지 망설여 졌던 문제. 해결책은 printf("%d 뒤에 한칸은 띄는 것이었다. 코드를 정답에 따라 고치는 과정에서 덧붙여진 printf("\n")의 존재 이유는 아직 무엇인지 잘 모르겠다.

if else 문 사이에 다른 if else 이 들어갈 수 있다는 사실을 망각하여 못 풀었던 문제. "단, 같은 수를 입력받았을 때의 처리도 가능해야 합니다"라는  문구가 애매하게 느껴졌는데 정답은 간단했다.

어제 들었던 함수 강의 백지 암기법. 지역 정적 변수와 전역 정적 변수의 목적이 재밌었다. 

 

 

 

 

+ Recent posts