루비로 시작하는 프로그래밍

일반인을 위해 프로그래밍을 가르쳐주는 튜토리얼.

루비로 시작하는 프로그래밍 일반인을 위해 프로그래밍을 가르쳐주는 튜토리얼.

5장. 메서드 심화학습

More about methods

메서드에 대해 좀 더 알아보기

지금까지 puts, gets 등 여러가지 메서드에 대해 살펴 보았습니다. 여기서 깜짝 퀴즈 하나요! 이제까지 봤던 메서드들을 모두 나열해 보세요. 다 합해서 열 개에요. 답은 아래에 나와 있습니다. 그런데 메서드라는 게 뭔지에 대해서는 아직 얘기를 나누지 않았어요. 메서드가 무엇을 해주는지에 대해서는 알게 되었지만, 메서드가 대체 뭔지는 아직 모르는 상태죠.

그런데 말이죠, 무언가를 해주는 것, 그게 바로 메서드예요. string이나 intergers같은 객체(objects)가 루비 언어에서 명사라고 한다면, 메서드는 동사라고 할 수 있어요.

그리고 영어에서와 마찬가지로, 동사를 쓰려면, 주어인 명사가 있어야 해요. 예를 들어 ‘똑딱거린다’라는 동사에 대해 생각해 보죠. 이 동사를 쓰려면 똑딱거리는 ‘시계’가 있어야 해요.

영어에서는 ‘시계가 똑딱거린다.’라고 표현합니다. 루비라는 언어에서는 “시계.똑딱거린다”라고 표현합니다. 단, 시계가 루비의 오브젝트라고 가정했을 때 말이에요. 그리고 프로그래머들은 이 표현을 어떻게 읽느냐면요, “시계의 똑딱거리다라는 메서드를 호출햇다.” 또는 “시계에 대해서 똑딱거리다를 호출했다.”고 읽는답니다.

아까 내드린 퀴즈는 풀어보셨나요? 푸셨다고요? 잘 하셨습니다. (토닥토닥) 여러분이 puts, gets, chomp라는 메서드는 기억해 냈다고 생각할게요. 바로 전에 이 메서드들을 다루었으니까요. To_i, to_f, to_s 같은 메서드들도 기억해 냈을지도 모르죠. 이 메서드들은 자료형을 바꿔주는 메서드였죠. 그런데, 나머지 네 개의 메서드는 기억해 냈나요?

마지막 메서드 네개는 산수에서 쓰는 더하기, 빼기, 곱하기, 나누기였죠!
모든 동사에는 주어인 명사가 있어야 하듯이, 모든 메서드에는 객체(object)가 필요합니다.
보통의 경우에는 메서드를 실행시키고 있는 객체가 무엇인지 알기가 쉽습니다.
보통은 점 바로 앞에 오는 것이 바로 메서드를 실행시키는 객체이지요. 위에서 봤던 clock.tick에서는 .바로 앞에 있는 clock이 tick 메서드를 실행시키는 객체예요. 아니면 101.to_a의 경우를 볼까요? 네, 101라는 객체가 to_a라는 메서드를 실행시키고 있습니다.

그런데 가끔은 어떤 객체가 메서드를 호출하고 있는지 알아내는 게 쉽지 않을 때도 있어요. 산수의 연산메서드를 사용할 때가 그렇죠. 5+5라고 쓰는 것은 원래는 5.+ 5. 라고 쓴 것을 줄인 거예요.

puts 'hello '.+ 'world'
puts (10.* 9).+ 9

#실행결과

hello world
99

이렇게 쓰면 예쁘지가 않아요. 그래서 이렇게 쓰지 않는답니다. 하지만 어떻게 돌아가는지는 이해해야 해요. (아, 제 컴퓨터에서 ‘경고’ 메시지가 떴네요.
‘경고: 다음번에는 인자를 괄호 안에 넣어주세요.’

이 메시지의 의미는 뭐냐면요, 코드를 실행시킬 수는 있지만, 컴퓨터가 제가 원하는 것을 알아내는 게 어렵다고 말하는 거예요. 그래서 다음번에는 괄호를 좀 더 많이 써달라고 하는 거예요.)

이 예를 보면, 왜 ‘pig’*5라고는 쓸 수 있어도, 5*’pig라고는 쓸 수 없는지를 이해할 수 있어요. : ‘pig’ * 5의 의미는 ‘pig’에게 반복을 시키는 건데요, 5*’pig’의 의미는 5에게 반복을 시키는 거거든요.

자, 그럼 첫번째 경우에서 ‘pig’를 다섯 번 반복하라고 하면, ‘pig’를 다섯 번 반복할 수 있겠죠? 그런데 5에게 ‘pig’만큼 반복하라고 하면 훨씨 어렵죠. 5를 ‘pig’만큼 반복한다는 걸 컴퓨터가 알 수 있을까요?

그리고, puts와 gets 메서드에 대해서도 설명 드릴게요. 여기서 이 메서드의 객체는 어디있는 걸까요? 왜 안 보이는 걸까요? 영어에서도 가끔은 명사를 안 쓰는 경우도 있어요. 예를 들면 ‘죽어랏!’ 라고 하는 경우, 명백히 드러나있지는 않지만, 추측할 수 있는 객체는 ‘죽어랏’을 듣는 상대가 되겠죠. 루비에서 puts ‘사느냐 죽느냐’라고 치면, 이것의 의미는 self.puts ‘사느냐 죽느냐’라고 쓰는 것과 같은 의미에요.

아니, 그럼 여기서 self는 또 뭘까요? Self는 특별한 변수에요. 그리고 여러분이 현재 위치한 객체(object)를 가리키죠. 흠. 그런데 객체(object)안에 있는 다는 것이 어떤 의미인지 모르신다고요? 아직 안 배웠죠. 여하튼, 이것을 이해하기 전까지는 이렇게 말씀해두죠. 우리는 어떤 큰 객체 안에 있는 겁니다. 그 객체는요….. 바로 프로그램이에요! 다행스럽게도 프로그램에는 몇 가지 자신의 (??) 메서드가 있습니다. 예를 들면 puts나 gets 메서드 같은 것이죠. 다음을 살펴볼까요?

iCantBelieveIMadeAVariableNameThisLongJustToPointToA3 = 3
puts iCantBelieveIMadeAVariableNameThisLongJustToPointToA3
self.puts iCantBelieveIMadeAVariableNameThisLongJustToPointToA3

#실행결과

3
3

위에 내용이 다 이해 안되신다고요? 괜찮습니다. 위의 예를 보면 이런 점을 알 수 있죠. 모든 메서드에는 그 메서드를 실행시키는 객체가 있고요, 두번째 줄에서처럼, 메서드 앞에 점이 없더라도, 그래도 그 메서드를 실행시키는 객체가 있다는 겁니다.

이해되셨나요? 자, 이제 여러분은 준비가 되었습니다.

문자열을 다루는 고급 메서드에 대해 알아봅시다.

문자열을 다루는 몇 가지 재미있는 메서드에 대해서 알아봅시다. 이 메서드들은 다 기억하지 않으셔도 되요. 잊어버리면 어떡하느냐고요? 이 글을 다시 보면 되죠. 여기서는 문자열이 할 수 있는 것 중 몇 가지를 보여드리려고 해요.

이 뒷 부분부터 존댓말로 바꾸기.. 


사실, 저도 문자열 메서드 중에 절반도 다 외우지 못해요. 하지만 괜찮아요. 인터넷에는 모든 종류의 문자열 메서드를 나열해놓고 각각에 대해 설명을 해 놓은 훌륭한 참고자료가 얼마든지 있거든요. 이 튜토리얼의 마지막 부분에 참고자료를 찾을 수 있는 위치를 써 두었습니다.

그리고 이 모든 문자열 메서드를 다 외우고 싶지도 않아요. 그건 마치 사전에 나오는 모든 단어를 외우는 것과 같죠. 사전을 통째로 외우지 않아도 언어생활을 하는 데에는 아무런 지장이 없잖아요? 그리고, 사전의 역할이 바로 이런 것 아니었나요? 외우지 않아도 되게끔 도와 주는 역할이요.

몇 가지 메서드를 같이 살펴 봅시다. 우선 살펴 볼 문자열 메서드는 reverse 예요. reverse 메서드는 문자열을 거꾸로 뒤집는 역할을 해주죠.

var1 = 'stop'
var2 = 'stressed'
var3 = '뒤집힌 문장을 읽을 수 있겠니?'

puts var1.reverse
puts var2.reverse
puts var3.reverse
puts var1
puts var2
puts var3

#실행결과

pots
desserts
?니겠있 수 을읽 을장문 힌집뒤
stop
stressed
뒤집힌 문장을 읽을 수 있겠니?

#역주 : 그대로 하면 3번째 결과에서 이상한 값이 나옵니다. reverse 메소드가 한글이 2바이트라는 걸 몰라서 그렇습니다.

여기서 보다시피, reverse 메서드는 문자열 원본을 변경시키지는 않습니다. 단지 앞뒤 순서를 변경한 새로운 문자열을 생성할 뿐이죠. 그렇기 때문에 var1에 reverse 메서드를 적용한 후에도 var1이 여전히 'stop'을 값을 갖고 있어요. 또 다른 문자열 메서드는 length 메서드라고 해요. 이름을 보면 감이 오시죠? 이 메서드는 문자열 내에 있는 문자의 수 몇 개인지를 알려 줘요. 이 때 공백도 포함해서 문자의 수를 세죠.

puts '네 이름이 뭐니?'
name = gets.chomp
puts '네 이름이 총 ' + name.length + ' 글자인 줄 알고 있었니, ' + name + '야(아)?'

#실행결과

네 이름이 뭐니?
박명수
#

이런! name = gets.chomp 다음에 뭔가 잘못된 것 같군요. 어디가 문제인지 눈치 채셨나요? 어디가 잘못됐는지 한번 봅시다. 문제는 바로 length에 있었어요. length 메서드는 수를 결과물로 산출하는데, 우리는 문자열을 기준으로 프로그램을 짰거든요. 이 문제를 해결하려면 to_s를 쓰면 되요.

puts '네 이름이 뭐니?'
name = gets.chomp
puts '네 이름이 총 ' + name.length.to_s + ' 글자인 줄 알고 있었니, ' + name + '야(아)?'

#실행결과

네 이름이 뭐니?
박명수
네 이름이 총 3글자인 줄 알고 있었니, 박명수 야(아)?

아뇨, 몰랐는데요? 
주의 : 이름에 사용된 글자가 총 몇 개인지를 세어 주는 것이지, 총 몇 개 종류의 글자가 사용되었는지 세는 프로그램이 아닙니다.

이제는 이름과 성을 각각 따로 질문한 뒤에 모두 합해서 몇 개의 글자가 사용되었는지 대답해 주는 프로그램도 짤 수 잇을 것 같군요. 한번 해 볼까요? 좋아요, 시작!

완성했나요? 좋습니다! 꽤 괜찮은 프로그램이죠?

앞으로 공부를 더 하다보면 스스로도 놀랄 만큼 멋진 프로그램을 만들 수 있을 거예요.

대/소문자를 바꿔 주는 메서드도 있습니다. upcase 라는 메서드는 소문자를 대문자로 바꿔주고, downcase 라는 메서드는 대문자를 소문자로 바꿔주죠. swapcase 라는 메서드는 대/소문자를 서로 반대로 바꿔줍니다. 마지막으로 capitalize 라는 메서드는 downcase와 동일한 기능을 하되, 가장 앞에 위치한 글자만 대문자로 만들어 줍니다. (맨 앞 글자가 문자인 경우에 말이죠. 숫자이면 변화가 없겠죠?)

letters = 'aAbBcCdDeE'
puts letters.upcase
puts letters.downcase
puts letters.swapcase
puts letters.capitalize
puts ' a'.capitalize
puts letters

#실행결과

AABBCCDDEE
aabbccddee
AaBbCcDdEe
Aabbccddee
a
aAbBcCdDeE

이렇게 대소문자를 전환해야 하는 경우가 많이 있죠. ' a'.capitalize의 예에서 볼 수 있듯이, capitalize라는 메서드는 첫번째 ??(letter)가 아니라 첫번째 ??(character)를 대문자로 바꿉니다. (역주: 즉 처음에 공백이 나오면, 공백을 대문자로 바꾸어 줍니다, 물론 나오지 않겠죠. 처음으로 나오는 알파벳인 a가 아니라, 제일 앞에 있는 문자를 대문자로 바꾸어 줍니다.)

그리고 이제까지 살펴 봤듯이, 이렇게 메서드를 호출해도 변수 안에 들어있는 문자가 실제로 바뀌는 것은 아닙니다. 이 점에 대해서 여기에서 장황하게 다루고 싶지는 않지만요, 이 점을 이해하는 것은 중요합니다.

관련된 오브젝트에 변화를 가하는 메서드들도 몇몇 있어요. 하지만 우리는 아직까지는 살펴보지 않았죠. 그리고 당분간은 살펴보지 않을 겁니다.

마지막으로 살펴볼 문자열을 다루는 메서드는요, formatting과 관련된 메서드, 보이는 모양을 바꿔주는(??표현어색) 것과 관련된 것들입니다.

우선 처음으로 살펴볼 메서드는 ‘center’라는 것인데요, 이 메서드를 호출하면, 문자열의 앞과 되에 공백을 넣어주어서, 문자열이 중간 위치에서 보이게 만들어줍니다.

하지만, puts과 + 메서드를 사용하는 경우를 떠올려보세요. Puts를 사용할 때에는 무엇을 화면에 보여줄 지 알려줘야 하고요, +를 사용할 때는 무엇을 더하고 싶은지 알려줘야 했었죠. 마찬가지로 center를 사용할 때에도, 중간에 위치시킨 문자열이 얼마나 넓게 퍼지게 보이고 싶은지 알려줘야 합니다.

시 한편을 중간에 뿌려서 보여주는 경우를 생각해 보죠. 저라면 이렇게 하겠습니다 :

lineWidth = 50
puts( 'Old Mother Hubbard'.center(lineWidth))
puts( 'Sat in her cupboard'.center(lineWidth))
puts( 'Eating her curds an whey,'.center(lineWidth))
puts( 'When along came a spider'.center(lineWidth))
puts( 'Which sat down beside her'.center(lineWidth))
puts('And scared her poor shoe dog away.'.center(lineWidth))

#실행결과

Sat in her cupboard
Eating her curds an whey,
When along came a spider
Which sat down beside her
And scared her poor shoe dog away.

흠.. nursery rhyme이 이게 맞는지 모르겠군요. 하지만 찾아보자니 귀차니즘이 발동합니다.
그래서 문자열 앞에 공백을 추가로 두었어요.

프로그래머들은요, 프로그램 안에서 무언가가 예쁘다고 하는 것에 대해서, 각자만의 느낌을 갖습니다. 그리고 그 느낌은 프로그래머마다 다르죠.

프로그램을 더 많이 하면 할수록, 자기만의 스타일을 갖게 됩니다.
귀차니즘에 대한 얘기가 나와서 말인데요, 게으름은 프로그래밍을 하는 데에 있어서 꼭 나쁜 것만은 아니예요.

예를 들어, lineWidth라는 변수에다 시를 보여주는 너비를 저장한 것에 대해서 살펴볼까요?

이렇게 하면 뭐가 편하냐면요, 시를 좀더 폭이 넓게 바꿔주고 싶을 때, 프로그램의 제일 앞부분에 있는 이 변수의 값만 바꿔주면 되요. 중간정렬을 하는 부분을 모두 일일이 찾아서 바꿔줄 필요가 없죠. 시 한편이 긴 경우에, 이렇게 해두면 시간이 많이 절약되죠. 이런 류의 게으름은 프로그래밍에서는 미덕이라고 할 수 있죠.

다시 중간 정렬에 대한 얘기를 하죠. 위에서 중간 정렬된 결과를 보면, 워드 프로세서에서처럼 예쁘지는 않다고 느꼈을 거예요. 그런데 말이죠, 정말 완벽하고 예쁘게 중간 정렬을 하려면요, 그리고 폰트도 더 예쁘게 하려면요, 그럼 워드 프로세서를 써야겠죠!!

루비는 정말 훌륭한 도구인 언어에. 하지만 모든 기능을 다 하는 도구는 없죠.

문자열의 포맷팅(??)을 다루는 메서드를 좀 더 살펴보죠. Ljust와 rjust라는 메서드가 있습니다. 각각 leftjustify와 right justify를 가리킵니다.

이 두 메서드는 center메서드와 비슷하긴 한데요, 차이점이 있어요.
문자열을 공백으로 pad(??) 를 해요. <안쪽여백을 둔다는 의미인가? ?a>
Ljust는 왼쪽에 공백으로 pad를 하고요, rjust는 문자열의 오른쪽에 공백으로 pad를 하죠.

-  문장 약간 다듬기.

lineWidth = 40
str = '--> text <--'
puts str.ljust lineWidth
puts str.center lineWidth
puts str.rjust lineWidth
puts str.ljust (lineWidth/2) + str.rjust (lineWidth/2)

#실행결과

--> text <--
--> text <--
--> text <--
--> text <--

프로그램 만들어보기

열받은 상사.. 라는 프로그램을 만들어봅시다.

이 프로그램은요, 여러분이 원하는 것이 뭔지를 무례하게 물어봅니다.
뭐라도 대답하든지, 열받은 상사는 여러분에게 소리치며 대답할 거예요. 그리고 여러분을 해고하는 거죠.

예를 들어, 여러분이 ‘연봉을 올려주세요.’라고 치면, 프로그램은 이렇게 외치는 거죠.

뭐라고라 “연봉을 올려주세요.” 라고라 ?!? 자네 해고야!!

center, ljust, rjust를 가지고 사용하기 위해서 몇 가지 해야할 것들이 있어요. 우선은 아래처럼 목차를 보여주는 프로그램을 만들어 보세요.

Table of Contents

Chapter 1: Numbers
Chapter 2: Letters
Chapter 3: Variables
 

고급 수학(Higher Math)

(이 부분은 꼭 읽어야 하는 부분은 아니예요. 안 읽어도 좋습니다. 이 부분을 읽으려면 어느 정도 수학 지식이 필요합니다. 관심이 없으시면, 바로 Flow Control(흐름 제어하기) 부분으로 넘어가셔도 되요. 아무 문제 없습니다.)

 다만, 임의의 수(Random Numbers) 부분은 슬쩍 훑어두시면 나중에 도움이 될 겁니다.)

수 메서드는 문자열 메서드에 비해서는 종류가 적습니다. 그래도 외우지는 못합니다. 이번에는 사칙연산과 관련된 메서드를 좀 더 알아보고 난 다음, 난수 발생기와 Math 라는 객체(삼각함수와 초월수에 대한 메서드와 함께)에 대해 공부해 보겠습니다.

사칙연산에 대해 좀더 알아 봅시다.

사칙연산과 관련하여 **(제곱) 과 %(나머지) 메서드 두 가지를 소개할게요.

루비에서 "5의 제곱"이라고 적고 싶으면, 5**2 라고 입력하면 됩니다.
실수를 사용할 수도 있는데, 만약 5의 제곱근을 구하고 싶을 때는 5**0.5 라고 입력하면 되죠.
계수란, 어떤 숫자를 나누었을 때 나머지를 말하는데요, 예를 들어, 7을 3으로 나누면 몫이 2이고 나머지가 1이 나옵니다.
프로그램을 만들어서 어떻게 작동하는지 알아볼까요?

puts 5**2  
puts 5**0.5
puts 7/3   
puts 7%3   
puts 365%7

#실행결과
               
25
2.23606797749979
2
1
1

마지막 줄에서, 윤달이 낀 해가 아니라면 1년은 일정한 갯수의 주에 하루를 더한 만큼의 날짜가 들어 있다는 점을 알 수 있습니다. 즉, 이번 해에 생일이 화요일이었다면 다음 해에는 수요일이 생일이 되는 거죠.

그리고, modulus 메서드에는 실수를 사용할 수도 있어요. 사실, 그럴 수 밖에 없는데.. 이에 대해서는 더 알아 보기로 하죠.

난수에 대해서 설명하기 전에 마지막으로 abs라는 메서드를 살펴 보죠. 이 메서드는 숫자의 절대값을 출력해 줍니다.

puts((5-2).abs)
puts((2-5).abs)

#실행결과

3
3

난수(임의의 수)는 어떻게 만들까요?

루비에는 상당히 성능 좋은 난수 발생기가 있습니다. rand 라는 메서드인데, 무작위로 숫자를 추출해서 출력해 줍니다.
그냥 rand 메서드를 적용하면, 0.0 이상이고 1.0 미만인 실수가 추출됩니다. 그리고 만약 정수(예를 들어 5)에 rand 메서드를 적용하면, 0 이상이고 5 미만인 정수를 추출합니다. (결국, 0부터 4까지 총 5개의 가능성이 존재하죠.)
그럼 직접 실습해 볼까요? (프로그램을 계속 실행할 때마다 다른 숫자가 나올 거예요. 저는 매번 예제 프로그램을 실행해 보거든요--v!!)

puts rand
puts rand
puts rand
puts(rand(100))
puts(rand(100))
puts(rand(100))
puts(rand(1))
puts(rand(1))
puts(rand(1))
puts(rand(99999999999999999999999999999999999999999999999999999999999))
puts('기상청에서 오늘 비 올 확률이 '+rand(101).to_s+'% 라고 하던데,')
puts('하지만 그 말을 어떻게 믿겠어.')

#실행결과

0.866769322351658
0.155609260113273
0.208355946789083
61
46
92
0
0
0
22982477508131860231954108773887523861600693989518495699862
기상청에서 오늘 비 올 확률이 82% 라고 하던데,
하지만 그 말을 어떻게 믿겠어.

마지막 행에서 0부터 100사이의 숫자를 추출하기 위해 rand(101)이라고 입력했다는 점에 유의합시다. 그리고 rand(1)은 언제나 0을 결과 값으로 출력합니다.

rand 메서드를 사용할 때 사람들이 가장 자주 헷갈리는 것이 있는데요, 그 중에 하나가 숫자가 추출되는 범위를 혼돈하는 거예요. 심지어 현업 프로그래머들도 이런 실수를 저지르는데, 시중에서 판매되는 제품 중에서도 이런 경우를 발견할 수가 있죠. CD 플레이어를 샀는데, "무작위 재생(Random Play)"을 했을 때 마지막 곡이 재생되지 않는 경우가 바로 그런 경우이죠. (CD 안에 노래가 1곡 밖에 안들어 있다면 어떻게 될는지...)

숫자가 무작위로 추출하되, 프로그램을 2번 실행했을 때에는 각각 같은 결과가 나오는 것을 원할 수도 있습니다.
(예를 들어, 컴퓨터 게임을 할 때 무작위로 어떤 맵(판)을 선택했는 데 그게 정말 마음에 들어서 다음 번에도 또 그 맵을 선택해서 게임을 하거나 친구에게 소개를 해 주고 싶은 경우에 말이죠) 그러기 위해서는 srand 메서드를 사용해서 단서를 만들어 놓아야 합니다.
 

srand 1776
puts(rand(100))
puts(rand(100))
puts(rand(100))
puts(rand(100))
puts(rand(100))
puts ''
srand 1776
puts(rand(100))
puts(rand(100))
puts(rand(100))
puts(rand(100))
puts(rand(100))

#실행결과

24
35
36
58
70

24
35
36
58
70

이렇게 같은 숫자를 사용하는 같은 단서를 달아 주기면 하면, 매번 같은 숫자가 추출됩니다. 만약 다시 완전 무작위로 숫자를 추출하고 싶다면(srand 를 사용하기 전처럼), srand 0 으로 설정해 주면 되고요. 그러면 지금 컴퓨터가 표시하고 있는 시간부터 밀리세컨드 단위까지 할 거 없이 정말 이상한 숫자가 설정될 거예요.

수학관련 객체(Math)에 대해 알아볼까요?

마지막으로, Math 객체에 대해서 알아봅시다. 거두절미하고 본론으로 들어가면..

puts(Math::PI)
puts(Math::E)
puts(Math.cos(Math::PI/3))
puts(Math.tan(Math::PI/4))
puts(Math.log(Math::E**2))
puts((1 + Math.sqrt(5))/2)

#실행결과

3.14159265358979
2.71828182845905
0.5
1.0
2.0
1.61803398874989

5장을 마무리하며

가장 먼저 눈에 들어오는 건 아마도 :: 표기법일 것 같네요. 이 표시의 작용 범위와 내용(무엇이 무엇이며 그게 무슨 일을 하는지)을 설명하는 것은, 음.. 이번 강의의 범위를 벗어나고요. 말장난이 아니고, 진짜예요. 여기서는 그냥 Math::PI 를 그냥 사용하면 되요. Math 객체는 정밀한 공학용 계산기의 기능은 다 갖고 있거든요. 그리고 언제나처럼, 실수는 정말 정답에 근접합니다.

자, 그럼 이제 프로그램의 흐름을 제어하는 법을 배워 볼까요!

참고

댓글

댓글 본문
  1. 닭을치워라
    수고하십니다. 많은 도움이 되네요. 프로그래밍 기초가 없어서
    c책을 봐도 뭔소린지 전혀 모르겟거든요.
  2. ㄷㅇ
    흠 번역어투가 조금 아리송했지만 그래도 집중하면 읽을만 합니다!
버전 관리
김나솔
현재 버전
선택 버전
graphittie 자세히 보기