On a couch

5. 프로그래밍 응용 (2) 본문

프론트엔드 공부/CS50

5. 프로그래밍 응용 (2)

couch 2022. 4. 20. 21:51

06 커맨드 라인

커맨드라인

프로그램을 명령줄(Command-line)에서 실행시킬 때, 보통 ./프로그램_이름 같은 명령어로 실행했을 것입니다. C에서는 프로그램의 명령행 인자(command-line arguments)들을 명시할 수 있고, 명령줄에 인자들을 명시하여 사용자가 프로그램의 main 함수에 인자들을 전해줄 수 있게 합니다. 이 기능은 GetString처럼 프로그램이 실행 중일 때 입력값을 전달받는 방법 대신 사용할 수 있습니다.

 

argc, argv

여러분이 써보았을 법한 make, cd, clang, mkdir와 같은 명령어 프로그램들은 모두 명령행 인자들을 받습니다. C에서 명령행 인자들은 main 함수에 입력값으로 전달됩니다. 하지만 우리가 이전에 작성했던 main 함수에서는 void를 써주었기 때문에 아무런 인자도 받지 않았습니다.

명령행 인자를 받기 위해서는, main 함수가 정수형 argc 문자열 배열 argv라는 두 인자를 받아야 합니다. argc는 argument count의 약자로, 명령줄에 전달되는 인자의 수를 나타냅니다. 공백으로 구분되는 각 단어는 하나의 인자로 인식되고, ./hello와 같이 프로그램을 호출하는 것도 하나의 인자로 인식됩니다. argv는 argument vector의 약자로, 인자들 그 자체를 나타내는 배열입니다. 배열의 각 값은 문자열입니다.

/hello와 같이 프로그램을 인자 없이 실행시킬 때 argc와 argv를 살펴보면, 프로그램을 호출하는 것만이 유일한 인자이기 때문에 argc는 1이 됩니다. 반면, argv는 인덱스 0에 문자열 ./hello를 원소 하나만 갖고 있는 배열이 될 것입니다.

 

명령행 인자 사용하기

#include <stdio.h>

int main(int argc, char* argv[])
{
	for (int i = 0; i < argc; i++)
	{
		printf("%s\n", argv[i]);
	}
}

행에서 main 함수가 argc와 argv 인자를 포함하는 것으로 바뀌었습니다. argv의 크기는 3행에서 명시하지 않았으므로 크기와 상관없이 그 어떤 배열이나 main 함수로 전달될 수 있습니다. main 함수 안에서, 프로그램은 배열을 인덱스 0부터 시작하여 하나씩 올라가며 i가 argc보다 작을 때까지 순환합니다. 매번 순환할 때마다 프로그램은 argv[i]를 출력합니다. 이 프로그램의 실행 결과를 보면, 각 명령행 인자가 한 줄씩 출력됩니다

 

07 종료 코드

종료 코드

main 함수가 정의된 것을 보면 정수를 반환한다는 것을 알 수 있었을 것입니다. 하지만 우리는 지금까지 main 함수의 끝에서 어떤 값도 반환해주지 않았습니다. main 함수 안에서 아무 것도 반환하지 않으면 기본적으로 컴파일러는 자동으로 main 함수가 0을 반환한다고 추정합니다. main 함수가 반환하는 값은 종료 코드라고 불립니다. 프로그램이 길어지고 더 복잡해질수록, 종료 코드가 더 값지게 쓰일 수 있습니다.

 

 

종료코드 사용하기

관례적으로, 프로그램이 문제없이 성공적으로 끝나면 종료 코드 0을 반환해야 합니다. 그래서 main 함수 끝에 반환이 명시되지 않으면 컴파일러는 프로그램이 0을 돌려주었다고 추정합니다. 0이 아닌 종료 코드(보통 1이나 -1)는 일반적으로 프로그램이 실행되는 동안 프로그램이 성공적으로 끝나지 못하게 하는 어떤 오류가 있었다는 것을 의미합니다.

입력 유효성(input validation) 검사 과정에서 종료 코드가 흔히 쓰이는데, 입력 유효성이란, 사용자가 제공한 입력값이 유효한지 프로그램이 확인하는 것입니다. 예를 들어 프로그램이 두 개의 명령행 인자를 받는 것으로 알고 있는데, 한 개만 받았다면, 0이 아닌 종료 코드를 반환해 오류를 알립니다.

 

#include 

int main(int argc, string argv[])
{
	if (argc ==2)
	{
		printf("hello, %s\n", argv[1]);
		return 0;
	}
	else
	{
		return 1;
	}
}

여기서는 프로그램 이름 뒤에 명령행 인자로 사용자의 이름을 받아 '안녕'이라고 말합니다. 6행에서 main 함수가 시작되면, 프로그램은 먼저 argc가 2인지 확인합니다. 2가 맞으면 사용자의 입력값은 유효합니다. 프로그램은 안녕이라고 말할 수 있고 종료 코드 0을 반환합니다.

반면에 argc는 2가 아니라면 프로그램은 else 블록의 코드를 실행시켜 종료 코드 1을 반환함으로써 프로그램 실행에 오류가 있었다는 것을 알려줍니다.

 

종료 코드 확인하기

보통 명령줄로 프로그램을 실행시키면, main 함수가 반환하는 반환 값을 볼 수 없습니다. 그러나 프로그래머들이 코드에서 어떤 문제가 있는지 찾을 수 있도록 도와주는 프로그램 디버깅 도구를 사용하면 main 함수가 어떤 종료 코드로 종료됐는지 볼 수 있습니다.

종료 코드를 확인하는 것은 프로그램이 왜 실패했는지 알아낼 수 있는 좋은 방법입니다. 각 오류에 따라 다른 종료 코드가 반환되기 때문에 입력 유효성을 많이 확인해야 하는 프로그램을 작성할 때 큰 도움이 됩니다.

 

08 라이브러리

라이브러리

라이브러리는 프로그래머들이 이미 만들어진 코드를 다시 개발하지 않아도 되게 하고, 서로 함께 작업할 수 있도록 만들어주는 함수의 모음입니다. 

 

라이브러리 사용하기

라이브러리를 불러오기 위해서는 #include를 사용해 해당 라이브러리의 헤더 파일을 포함해주면 됩니다. 라이브러리에 속한 함수(printf, scanf 등)들을 사용할 수 있습니다.

 

C에서 자주 쓰이는 라이브러리 함수

ctype.h : 문자 분류 함수, 문자열 처리시 문자의 유형에 따라 구분해서 처리해야 하는 경우에 사용하는 함수 라이브러리입니다.

math.h : 수학 관련 함수, 대부분의 수학 관련 함수는 double형의 인자를 갖고, double형의 값을 리턴합니다.

 

stdlib.h : 데이터 변환 함수, 데이터 변환 함수는 데이터 간의 형태 변환이 필요할 때 사용됩니다.

string.h : 문자열 처리 함수, 하나 혹은 두 개의 문자열을 입력 받아 문자열의 값을 처리합니다.

위에서 설명한 라이브러리와 다른 라이브러리에는 더 많은 함수가 있습니다. 이미 만들어져 있는 C 라이브러리를 살펴보고 어떤 함수를 쓸 수 있는지 보는 것이 좋습니다. 이렇게 하면 코드를 다시 만들지 않고 라이브러리에서 가져다 쓸 수 있습니다.

 

09 구조체와 캡슐화

구조체와 캡슐화

일반적인 자료형들을 사용하기에 적합하지 않은 상황에서 우리는 데이터를 캡슐화하여 어떤 개체에 연관되는 정보들을 한 덩어리로 묶을 수 있게 됩니다. 예를 들어 학생은 이름(string형), 나이(int형), 평점(float형)와 같은 정보들을 갖고 있는데, 이 정보들은 단독으로는 큰 의미를 갖고 있지 않습니다. 이 정보들이 모여 학생이라는 개체를 이룰 때 의미를 갖게 되고 C에서는 구조체(structure)라는 방법을 사용합니다.

 

배열과 구조체

배열은 같은 데이터형의 변수들을 하나로 묶을 수 있지만, 서로 다른 데이터형의 변수를 묶어서 사용할 수 없습니다. 그리고 사용 전에 배열의 크기를 선언해야만 합니다.

typedef struct
{
	string name;
	int year;
	float gpa;
}
student;

구조체를 사용하면 서로 다른 자료형의 변수를 하나로 묶어 새로운 자료형을 만들 수 있습니다. student 구조체에는 학생과 연관된 정보들이 string형, int형, float형으로 구성되어 있습니다. 이러한 정보들을 각각 멤버라고 부릅니다. 구조체 자료형의 특정 멤버에 접근하고 싶다면 구조체명.멤버명 (student.name)으로 하면 됩니다. 데이터를 구조체로 저장할 때의 또 다른 장점은 더 이상 학생이 몇 명이 있는지 선언할 필요가 없다는 것입니다. 하지만 배열이 인덱스를 사용하여 각 멤버들을 순환하는 것과 달리 구조체는 멤버를 순환할 수는 없습니다.

 

구조체 구현하기

위의 코드에서는 student라는 새로운 자료형을 정의했습니다. int가 자료형인 것처럼 student는 우리가 정의한 자료형인 겁니다. student 자료형으로 된 새로운 변수를 만들기 위해서는 int형 변수를 선언하듯이 사용하면 됩니다. s1의 gpa에 접근하고 싶다면 2행처럼 s1.gpa라고 하면 됩니다.

student s1 = {'Zamyla', 2014, 4.0};
s1.gpa = 3.5;

 

10 재귀

함수를 사용할 때 main 안에서 필요한 순간에 호출하여 사용합니다. 그런데 main 역시 함수라는걸 기억하시나요? main이라는 함수 안에서 또 다른 함수를 사용한 것입니다. 이 사실을 알게 되었을 때, 우리는 함수가 본인 스스로를 호출해서 사용할 수 있는지에 대해 의문을 가질 수 있습니다. 이에 대한 대답은 할 수 있다 이며, 이러한 것을 재귀(Recursion)라고 부릅니다.

 

재귀의 사용

시그마는 주어진 수 n 부터 1까지의 연속된 수를 모두 합한 값입니다. 시그마를 지금까지 우리가 배운 개념을 이용하여 프로그래밍 해보면 <코드 1>과 같습니다. 3행부터 6행까지는 양의 정수가 아닐 경우에 무한반복을 피하기 위해서 작성된 부분입니다. 8행부터 11행까지가 실질적으로 함수가 구현된 부분입니다. sum 변수에 i를 n까지 1씩 늘려가며 더하면 시그마 값이 반환됩니다.

 

그렇다면 동일한 기능의 프로그램을 재귀를 이용하여 작성한다면 어떻게 될까요? <코드 2>를 보면 for문이 없는 것을 확인할 수 있습니다. else 안에 작성된 부분이 이 함수의 핵심입니다. 반환값 안에 자기 자신(함수)가 호출됩니다. 9행의 코드는 결과적으로 n + (n-1) + (n-2)+…+ 1 + 0이 되어 n부터 1까지의 합이 반환됩니다.

 

스택

재귀 함수에서 동일한 함수를 계속해서 호출될 때마다 함수를 위한 메모리가 계속해서 할당됩니다. 함수가 호출될 때 마다 사용되는 메모리를 스택이라고 부릅니다. 운영체제는 함수를 실행할 수 있도록 일정량의 바이트를 주고, 그 공간에 함수의 변수나 다른 것들을 저장할 수 있도록 합니다. 재귀함수를 이용하다 보면 함수가 종료되지 않고 계속해서 호출되는 경우가 발생하기도 합니다. 이 경우 스택 공간은 초과되고 프로그램 충돌이 발생됩니다. 그렇기 때문에 재귀를 사용할 때는 과도하게 스택 메모리가 사용되지 않도록 주의해야 합니다. 매우 유의 해야 하지만, 특정 자료구조를 다룰 때 매우 유용하게 사용됩니다.

 

11 파일 입출력

파일 입출력

프로그램은 한 번 실행하고 나면 그 안에 입력했던 데이터들이 모두 사라집니다. 프로그램이 변수를 만들 때 사용하는 메모리는 임시 작업공간이기 때문입니다. 데이터를 영구적으로 저장해두고 싶다면 프로그램이 실행될 때 파일에 저장해두면 됩니다. 이 파일에 저장된 데이터는 다음 번에 프로그램이 실행될 때 다시 읽어올 수 있습니다. C에는 파일 입출력(File IO) 기능이 있어서 파일에 데이터를 저장하거나 읽어오도록 만들 수 있습니다.

 

파일 열기

C의 표준 입출력 라이브러리인 stdio.h에서 파일을 읽고 쓰는 기능을 제공합니다. 먼저 파일에 접근해야 파일의 데이터를 읽어오거나 쓸 수 있습니다. 파일은 FILE이라고 하는 구조체로 구현되어 있습니다. 따라서 <코드 1>의 1행과 같이 FILE* file; 코드를 이용해 변수를 선언할 수 있습니다.

FILE* file;
file = fopen("file.txt", "r");

2행의 fopen 함수는 파일을 열고 그 파일에 접근할 수 있는 포인터라는 것을 넘겨줍니다.(포인터는 변수나 다른 어떤 것이 저장된 메모리의 주소입니다. 즉 데이터 값 그 자체가 아니라 데이터에 접근할 수 있는 길이라고 생각하면 됩니다.) 만약 file에 NULL이 반환되었다면 요청한 파일을 열지 못한 것입니다.

fopen 함수를 호출할 때 전달하는 첫 번째 인자는 파일 이름이고 두 번째 인자는 모드입니다. 즉, ‘r’은 읽기 모드, ‘w’는 쓰기 모드, ‘a’는 추가 모드입니다.

 

파일 열고 쓰기

 

파일 닫기

fclose(file);

파일을 한 번 열면 그 파일은 반드시 닫아야 합니다. 왜냐하면 파일을 계속 열기만하면 파일을 열 수 있는 시스템 리소스가 소진되어 더 이상 파일을 열 수 없게 될 수도 있기 때문입니다. UNIX 시스템에서는 프로그램이 종료될 때 열려있는 파일이 있으면 자동으로 닫아주기도 합니다. 


*커맨드라인 챕터에서 argc가 '들어온 인자의 개수'를 저장한다는 건 어디서 정의한 걸까? 변수명은 임의라고 하는 걸 보니 이미 정의된 메소드 같지는 않고.. 그럼 main함수에서 첫 번째로 받는 인자는 전체 인자의 수라는 게 이미 정해진 것인지...?

=> 라고 생각하면서 다음 강의를 틀었는데 마치 JS에서의 event 배열처럼 운영체제가 제공하는 배열이라고 한다. 하하!

 

*사람의 인지구조가 참 신기한 게 한글로 된 강의를 들을때는 온갖 용어들이 다 외계어 같았는데 영어로 된 강의에서 말하는 걸 보고 있자니 이게 'human friendly'한 언어라는 느낌이 확 든다;; 영어 잘 해야 되겠다는 깨달음 스택도 적립.

 

'프론트엔드 공부 > CS50' 카테고리의 다른 글

7. 웹 프로그래밍  (0) 2022.04.22
6. 인터넷과 네트워크  (0) 2022.04.21
4. 프로그래밍 응용 (1)  (0) 2022.04.19
3. 프로그래밍 기초 (C)  (0) 2022.04.19
2. 알고리즘  (0) 2022.04.18