반응형

파이썬을 배우기 시작하면 처음에 마주하게 될 개념이 리스트,튜플,딕셔너리일 것입니다.

C/C++로 치면, 배열과 map같은 애들인데  파이썬에서는 데이터분석이나 업무자동화(엑셀을 다루는)의 경우에는 위 개념들이 항상 쓰이기 때문에 사용빈도가 체감상 많이 높은 듯 합니다.

 

  • 리스트?

리스트라는 것은 문자를 담고 있는 배열이라 보시면 됩니다. 

리스트

리스트는 위에서처럼 대괄호 표시 [] 를 사용하여 안에다 원소를 담는 형식입니다.

 

(이어서 작성 예정)

반응형
반응형

모든 언어가 그렇듯 개개별 프로그래밍 언어는 자료형이 존재합니다.

C언어라면 int 정수형, char 문자형, double 실수형 등등이 있습니다.

파이썬도 마찬가지입니다.

구글코랩 사용

각각 변수들의 타입을 확인해보니 a는 float(실수형) , b와 c는 정수형 d는 문자형으로 나옵니다.

C언어와 다르게 일일이 int a라던지 char b라던지, 이런 식으로 잡아줄 필요는 없다는 것에서 벌써 행복하네요.

C언어로 코테 그만보기 위해 우리 다같이 탈출갑시다.

 

정수나 실수는 그냥 저희의 직관대로 정의내리면 되는데, 문자를 선언할 때는 큰 따옴표와 작은 따옴표에 대해 헷갈려하실 수도 있습니다. 결론부터 말씀드리자면, 둘다 뭐를 써도 상관없습니다.

큰따옴표든 작은따옴표든 문자열로 인식을 합니다.

하지만 이렇게 혼용하는건 안됩니다. 그리고 또 하나 좋은 팁!

씝떡 빙의

문자열 안에 따옴표를 문장기호로 인식을 잘 한다는 것입니다. C언어였다면 Escape처리를 위해 백슬래시를 걸어야만 했는데 파이썬은 그딴거 없습니다. 벌써부터 남다른 언어라는 냄새가 납니다.

 

그러면 우리 문자열을 갖고 좀 더 배워보도록 합시다. 모든걸 다 알 필요는 없고 자주쓰이는 애들만 다뤄서 여러 프로그램을 뚝딱 만들어봅시다.

 

  • 문자열을 쪼개보다.

C언어에서는 문자열에 관련된 함수들을 사용하기 위해서는 string.h 헤더파일을 불러와서 strlen, strcpy, strcmp 등등을 했습니다. 파이썬도 동일한 함수가 있습니다. 근데 C언어와 다르게 헤더파일같은건 선언안해도 기존에 다 내장되어있기에 아주 훌륭합니다.

 

먼저 제 블로그 명인 Crush on Study를 문자열 변수에 담아보도록 합시다.

네, 잘 담겨졌고, 잘 출력됩니다. 문자열을 다루다보면 가장 많이 사용되는 함수가 바로 문자의 길이를 알고자할때입니다.

C언어에서는 strlen함수였는데 파이썬에서는 len입니다.

파이썬
C언어

짜쟌, 이게 다에요. 딱봐도 파이썬이 훨씬 간단해보입니다. (C언어에서 길이 15로 뜨는건 널포인트까지 계산해서 그렇습니다.)

 

길이구하는 함수 배웠고... 이번엔 한 문자열에 동일한 문자가 몇개있는지 카운팅해보는 함수도 알아봅시다.

파이썬의 count함수입니다.

C언어에서의 count코드를 구현한 것입니다.

파이썬은 혁명입니다.

 

이번에는 문자열 슬라이싱을 해보고자 합니다.

슬라이싱은 문자의 몇번째 위치들만 따로 모아보고싶거나, 문장의 어느부분까지만 끊을 때 주로 사용합니다.

그 이외에도 다양한 경우일 때도 사용하니까 알아두면 좋습니다. 이거도 쉬워요.

문자열 슬라이싱입니다. 주석처리로 달은 것처럼  0부터 시작해서 9까지 2칸씩 띄어서 출력해라라는 의미인데요.

우리 C언어에서도 배웠듯이 배열의 첫 인덱스넘버는 0입니다.

즉, num[5] = {1,2,3,4,5}; 로 정의를 했다면 1은 num[0]과 대응됩니다. 파이썬도 똑같습니다.

출력을 할 때, 인덱스 0 이상 ~ 10 미만 까지만 출력하되, +2씩해서 출력. 

 

따라서, a[0] / a[2] / a[4] / a[6] / a[8]을 출력하는 것입니다.

a[0] = C

a[2] = u

a[4] = h

a[6] = o

a[8] = (n과 S사이의 스페이스)

 

이렇게 잘 출력되어있음을 알 수 있습니다.

오케이 그럼 여기서 응용해서 우리는 이렇게도 다뤄볼 수 있죠.

a[0:]은 0부터 문장의 끝까지 출력하라는 걸로 사실상 print(a)와 같습니다.

a[:10]은 문장의 처음부터 길이 10미만까지만 출력하라합니다.

a[::2]는 문장을 처음부터 끝까지 출력하되 +2씩 해서 a[0],a[2],....,a[N-2],a[N]까지 출력하라는 거구요.

a[::-1]은 맨뒤에서부터 거꾸로 출력하라는 뜻입니다. 0이 문장의 시작부분이면 -1은 문장의 끝부분입니다.

그럼 a[::-2]로 출력하면 대충 문장이 뒤집힌 다음 인덱싱이 -2씩 출력되겠네요?

 

이렇게 슬라이싱하는 것도 가능하고, 문자열을 이어붙이는것도 가능합니다.

다 응용에 불과합니다.

그러면 여기서 간단한 퀴즈!

네모칸에 들어갈 코드를 짜보세요.

반응형
반응형

예로부터, 사업을 시작하거나 부동산 투자를 할때 우리는 항상 은행으로부터 대출을 빌리거나 했습니다.

부자가 아닌 이상!

클라우드는 프로그래밍 세계의 '은행' 역할을 한다보면 됩니다. 적절한 비유인듯 하네요.

클라우드란 기업의 인프라와 IT자원을 빌려주는 서비스 업종입니다. 일반적으로 기업은 주력이 되는 비즈니스를 키워감으로써 이익을 창출합니다. 근데 대부분의 업종은 컴퓨터 네트워크를 기반으로 업무를 보는 일이 많죠. 회사의 서버라던가, 기업을 운영하면서 쌓인 데이터들을 저장할 스토리지라던가. 이런 서비스까지 자기들이 직접 구축하고 관리하면서

본인들의 비즈니스를 키워가는 것. 불가능하지않지만 확실히 어렵고 투자비용이 클 겁니다.

 

그래서 기업이 비즈니스에만 집중할 수 있도록 인프라와 IT자원을 담당해서 케어해주는 일. 이게 클라우드 서비스회사가 하는 일입니다.

 

대표적으로 너무나도 잘 아는 아마존(AWS) / 구글(GCP) / 마이크로소프트 (Azure)가 있고 국내기업으로는 KT / Naver가 있습니다.

또는 삼성SDS / LG CNS처럼 위 클라우드 서비스회사와 협업하면서 고객에 필요한 소프트웨어를 구축해주는 일을 하는 회사도 있습니다.

 

그럼 클라우드에는 어떤 종류가 있는지 먼저 살펴봅시다.

  • Private Cloud : 보안이 중요한, 예를 들면 기밀정보나 자산과 관련하여 외부로부터 접근을 막는 것이 중요한 금융업계에서 각광받는 클라우드 Deployment Service입니다. 이 경우는 이 클라우드를 사용하는 기업이 아닌 이상 그 누구도 접근할 수 없습니다.
  • Public Cloud : Private과 반대로 외부에서도 쉽게 접근이 가능한 클라우드 서비스입니다. 보통 많은 사람들이 사용하는 웹페이지같은데서 사용하는데요. 가끔 웹페이지가 떡상해서 동접하는 유저들이 엄청 많을 때, '트래픽' 초과때문에 페이지가 다운되었다! 이런 말 하실 때가 있을겁니다. 그러한 트래픽 초과를 막기 위해 동적으로 서버를 늘려서 유저의 유입을 받아주는 Deployment service입니다.
  • Hybrid Cloud : Private과 Public 둘의 특징을 모두 갖고 있습니다. 금융업계가 Private에서 Hybrid로, 뿐만 아니라 많은 기업들이 Hybrid로 옮기는 추세입니다. 개인정보같은 사항은 Cloud Service Provider들이 제공하는 클라우드 서비스를 믿기 힘드니까, 사용자(기업)만 접근이 가능한 Private을 쓰되, 고객(기업의 고객)들이 트래픽 초과없이 드나들 수 있도록 Public한 특징도 보유한 것이라 볼 수 있겠습니다.
  • Community Cloud : 동일한 클라우드 서비스를 사용하는 기업끼리는 데이터 교류가 되도록 하는 클라우드 서비스입니다. 보통 대기업의 계열사들이 이런 서비스를 쓰곤 합니다. 같은 계열사끼리 공유하는 환경이죠.

위 4가지가 Deployment Service라는 클라우트 컴퓨팅의 종류 중 하나였구요. 다른 종류는 서비스 모델이라 해서 IaaS,SaaS,PaaS라는게 있습니다. 다음 포스팅에서 서술해보겠습니다.

반응형
반응형
# -*- coding: utf-8 -*-
import tensorflow as tf
import numpy as np
import pandas as pd

# 1. 데이터 프로세싱

data = pd.read_csv('gpascore.csv')

data = data.dropna()
 # print(data.isnull().sum())
 # data = data.dropna()  빈칸 제거
 # data = data.fillna(100)  빈칸 100으로 채움
 # exit()  # breakpoint임

ydata = data['admit'].values

xdata = []
for i,rows in data.iterrows():  # 판다스에서 쓰는 것으로 data라는 데이터프레임을 가로 한줄씩 출력
    xdata.append([rows['gre'], rows['gpa'], rows['rank']])

# 2. 모델 구현

model = tf.keras.models.Sequential([
    tf.keras.layers.Dense(120, activation = 'relu'),
    tf.keras.layers.Dense(300, activation = 'relu'),
    tf.keras.layers.Dense(1, activation = 'sigmoid')
])
 # binary_crossentropy는 0,1사이의 확률 문제에서 손실값을 보정해주는 함수로 자주 쓰임
 # adam은 그냥 대표적인 경사하강법 함수
model.compile(optimizer = 'adam', loss = 'binary_crossentropy' , metrics = ['accuracy'])

model.fit(np.array(xdata),np.array(ydata), epochs = 500)

# 3. 예측값 넣기

showme = model.predict([ [825,3.64,1], [0,4.01,1]])
print(showme)

 

반응형
반응형

데이터분석은 제조업에서 수율개선 및 공정 개선점을 발견하는데 좋은 이정표가 되어줍니다. 디스플레이 하나를 만드는데 수많은 공정데이터가 쌓이게 되는데요. 이러한 데이터표본을 컴퓨터에게 제시해주고 정답을 찾기 위한 가이드라인. 메뉴얼까지 제공하여 학습을 시키는 것이 머신러닝입니다.

 

반대로 딥러닝은 데이터표본만 컴퓨터에게 던져주면, 컴퓨터가 알아서 가이드라인을 짜고 분류를 하여 학습을 시작합니다.

 

머신러닝의 대표적이면서도 핵심 예시는 선형회귀분석입니다.

1차 선형함수로 되어있고, 결과값을 y라 한다면  x는 데이터 값들을 의미하구요.  기울기 a는 가중치를, 절편 b는 보정치를 의미합니다.

출처 - IT용어위키

빨간색 선은 주어진 데이터표본에 가장 최적화된 기울기 값을 찾기 위해 가설을 세운 것들입니다. 물론 우리 눈에는 어떤 기울기가 제일 적합한지 보이지만, 컴퓨터는 아직 모르기에 이런 식으로 가설들을 세웁니다.

(최적화되었다는게 오차 0를 의미하는 것은 아닙니다.)

 

'코딩애플' 이라는 유튜브에서 참고했던 내용인데 예시를 너무 잘 들어주셔서 인용하면서 복습해보고자 합니다.

수능성적을 예측하기 위해서 3월부터 10월 모의고사까지의 성적을 참고하고자 합니다.

수학 모의고사 가중치 결과값
3월 - 68점  0.05 ??
4월 - 66점 0.05
6월 - 84점 0.3
7월 - 84점 0.1
9월 - 92점 0.4
10월 - 88점 0.1

이렇게 주어졌어요. 그러면 여기서 데이터 값은 x라하고 w (기울기)는 가중치가 됩니다.

퍼셉트론

이런 상태에요. 그러면 저거 일단 계산을 먼저 해봅시다.

 

3월 = 68*0.05 = 3.4

4월 = 66*0.05 = 3.3

6월 = 84*0.3 = 25.2

7월 = 84*0.1 = 8.4

9월 = 92*0.4 = 36.8

10월 = 88*0.1 = 8.8

 

y = w1*x1 + w2*x2 + w3*x3 + w4*x4 + w5*x5 + w6*x6 = 85.9점   즉, 수능 수학 성적을 86점이라고 예측을 했습니다.

가중치 값은 저희가 임의로 넣은거에요. 보통 평가원모의고사 6,9월께 중요하잖아요? 28살 아죠씨라 수능친지는 까마득하지만 ㅎㅎ;;;

 

근데 실제 수능 수학점수가 92점이 나왔다? 그럼 오차값이 6점이나 나죠?  그럼 이제 여기서 컴퓨터가 가중치를 바꿔도 보고, 보정값 b를 넣어주면서 이 오차값을 최소한 줄이고자 노력합니다.

 

그 가중치를 바꾸기 위한 공식을 cost reduction이라 합니다. cost (w,b)로 표현을 하는데요. 간단합니다.

실제값 y1과  예측값 y2 사이의 차이들의 total sum을 구합니다. 그 sum에다 '제곱'을 한 뒤, 그걸 데이터 표본개수로 나눕니다.

수식으로 치면 이런거죠.  제곱을 해주는 이유. 이걸 평균제곱오차라 하는데, 예측값 (위에 ^ 달린 애)이랑 실제값의 차이가 음수거나 양수일 때가 있습니다. 이걸 제곱을 안해주고 더해버리면 오히려 total sum값이 0에 가까워지는 이슈가 발생해요. 오차는 실제로 많이 났는데  정확성이 높다! 라고 판정해버리는거죠. 그래서 제곱을 해주는 겁니다.

 

머신러닝을 더 깊게 들어가면 글이 너무 길어지니까, 일단 딱 여기까지만 다루고요. 딥러닝은 어떤지 이걸 이어서 생각해보겠습니다.

 

머신러닝은 위처럼 가중치와 보정값으로 구성되어있는데, 딥러닝도 똑같습니다. 가중치와 보정값이 있는데  여기에 추가로 '상황별 케이스'를 고려합니다. 즉, Input Layer와 Output Layer로 이뤄진 퍼셉트론에서 이 중간에 'Hidden Layer'라는 노드가 추가됩니다. 이러한 딥러닝 퍼셉트론들이 모이고모인게 '뉴럴 네트워크'입니다.

 

대충 그림 그려봤습니다. 중간에 추가된 노드들을 Hidden Layer라고 합니다. 즉, 여기선 3가지 케이스를 염두해두고 Input Layer에서 임의의 가중치를 두고 계산해낸 값을 H1에 저장했다. 또 다른 가중치를 두고 계산해낸 값을 H2, H3에 각각 저장했다. 라고 볼 수 있습니다.

 

그렇게해서 다양한 '케이스'들을 고려해서 도출해낸 결과값이 이겁니다~ 하는게 딥러닝. 인데.

그냥 Hidden Layer 하나 추가했다고 머신러닝이 갑자기 딥러닝이 되고 그런건 아닙니다. 

이 Hidden Layer에서 추가되는게 바로 활성 함수입니다. (Activation Function)

 

활성 함수는 여러개가 있겠지만, 많이 쓰이고 이것만큼은 알아가야한다는게 몇개 있어서 꼽아 봤습니다.

 

1) 시그모이드

Binary 분류의 대표적인 함수로 보통 출력층에서 자주 사용됩니다. 0과 1 사이의 범위값을 가지므로 보통 '확률'을 보여줄 때 씁니다.

시그모이드

시그모이드 함수의 원형입니다. x축 데이터들을 입력하면 결과값은 0과 1사이의 실수로 출력됩니다. 시그모이드는 사실

그리 좋은 함수는 아닙니다. Vanishing Gradient Problem이라 해서 고질적인 Output층의 신뢰도 하락을 일으키는게 있는데요. 굉장히 많은 Input층을 0과 1이라는 범위 내로 '우겨넣는' Activation Function때문에 Input에서 큰 변화가 일어나도 Output에서는 그 변화가 미미하기 때문입니다.

 

또한, 시그모이드는 데이터가 많으면 많을수록 그리 효율성이 좋지 않아 현업에선 잘 쓰지않습니다. 마치, 버블소트 알고리즘처럼 '교육용 입문개념'에 그쳐있습니다.

 

시그모이드와 같이 교육에서만 다루는 활성함수가 하나 또 있습니다.

2) tanh (하이퍼볼릭 탄젠트)

얘도 Input 데이터는 방대한데, Output은 [-1,1]로 굉장히 짓이겨놓은 식입니다.

 

요약

- 시그모이드와 tanh는 Output의 범위를 짓이겨놓았기 때문에 Input에서 큰 변화가 있더라도 Output에선 경미하게 발생한다.

 

그럼 어떤 함수를 쓰는게 좋냐? 대표적인게 ReLu입니다.

 

3) ReLu

- Relu의 원형은

렐루 함수

음수 데이터값은 그냥 모두 0으로 반환하고, 양수 값에는 제한이 없는 상태로 둡니다.

따라서, 데이터를 짓이겨넣음으로써 발생하는 기울시 소실 (Vanishing Gradient Problem)을 막을 수 있어요.

또한, 기울기가 일정하기 때문에 가중치 업데이트도 다른 함수보다 훨씬 빠릅니다.  뭐 비선형성을 띠는 함수들은 가중치 업데이트에서 시간이 좀 걸리는데 얘는 간단하니까요!

 

다만, ReLu는 Hidden Layer층에서만 사용하는걸 권장하는 편입니다.

가중치 업데이트 과정에서 total sum이 0이 되면 그 뒤론 계속 0만 반환합니다. 즉, 죽어가는 뉴런이 되어버리는거에요.

따라서 Output층엔 쓰면 안됩니다. 차라리, ReLu로 Hidden Layer층 활성화시키고, Output층으로 계산때릴 때, 결과값이 뭐 0과 1 사이의 확률로 나타내야한다! 하면 시그모이드를 채택하는게 적합할 것입니다.

 

 

* 경사하강법

마지막으로 짚고 갈 것은 경사하강법입니다. 비선형 데이터함수가 주어졌다고 가정해봅시다.

출처 - 리브레위키 '경사하강법'

이런 cost가 주어졌다 합시다. 이 함수 그래프에서 C는 cost를 의미하고 y는 가중치를 의미합니다. 아무 사진이나 갖고온거라 변수는 다를 수 있지만 아무튼  x축이 가중치  y축이 cost다! 라고 보시면 되겠습니다.

 

맨 처음에는 제일 왼쪽에 있는 빨간색 지점에 가중치를 뒀다 생각해봅시다. 근데 cost 비용이 상당히 큽니다. 딱 봤을 때,

Best! 부분이 가장 낮은 cost값을 갖고 있어요. 그럼 어떻게해야하나? cost를 낮추는 방향으로 움직여야 합니다.

그 방향을 어떻게 정해주느냐? 일단 기울기가 양과 음이 바뀌는 그 순간에 주목해봅시다. 양과 음이 바뀌는 그 순간은 기울기 값이 0입니다.

 

따라서, 현재 임의로 지정한 가중치가 '음의 기울기'라면요? 양의 기울기가 되도록 값을 더하게 될 것입니다.

빨간부분에 해당하는 파란색 함수의 w1값과 초록색 선형근사 함수의 기울기값. 이 2개를 합하면 됩니다.

즉, 음의 기울기인 경우 선형근사식의 기울기 + w1로 하면 0값에 가까워지니까요.

그럼 눈치 빠르신분은들은 양의 기울기인 경우에는 선형근사식의 기울기 - w1로 하면 0값에 가까워지니까 

이를 그림으로 생각해보면 기울기가 0인 지점을 향해 달려가는 '경사하강하는 모습'이 되지 않겠습니까?

 

근데, 이걸로 끝이 아닙니다. 위에 그린 함수처럼 local적인 부분에서 minimum인 지점이 있어요. 인간이 보기엔 딱 봐도 최소비용 지점이 보이는데 컴퓨터는 경사하강을 진행하다보면 기울기가 0이네? ㅇㅋ 여긴가보다~ 하고 멈추거든요.

 

그래서 등장한게 'Learning Rate'개념입니다.

말 그대로 경사하강에 대한 비율을 정해주는 것입니다. Learning Rate가 많이 작으면 정밀하게 기울기 0값을 향해 찾아가겠지만 Local Minimum을 피하긴 어렵습니다. 쉽게 말해서 함정을 '점프'해가는 슈퍼마리오같은애라 보면 됩니다.

 

그래서 적절한 Learning Rate값을 찾아주는 것도 또 하나의 중요한 포인트입니다. 과하지도, 부족하지도 않은게 좋거든요.

 

 

 

근데 딥러닝에선 keras가 이거 다 알아서 해줍니다 ㅋ

 

 

아무튼 여기까지 개념복습을 마치겠습니다.

반응형
반응형

malloc 함수를 설명하기 전에 변수가 저장되는 메모리 공간의 종류들에 대해 설명을 먼저 해보고자 합니다.

 

메모리 공간은 여러 가지가 있지만 대표적으로 전역변수가 저장되는 Data영역 , 지역변수가 저장되는 Stack영역. 그리고 메모리의 은행 창고의 역할을 하는 Heap영역이 있습니다. 이 글에서 쓰는 동적 메모리란 사용자가 메모리를 자유자재로 할당받는다는 것을 의미합니다. 즉, Heap영역으로부터 메모리를 필요한만큼 빌려쓰고 반환을 합니다.

이게 참 좋은게 배열을 다룰 때입니다.

예를 들어서, 대학교에서 어떤 강의의 수강생에 대한 재수강 여부를 관리하고자 합니다. 여러분들도 아시다시피 중간에 수강철회하는 사람, 새로 수강신청을 한 사람들이 있습니다. 즉, 상황에 따라 유동적으로 바뀝니다.

대략 이 강의를 50명 정도가 듣는다 치고  배열을 [50]으로 잡았는데, 생각보다 많은 사람이 빠져나가서 할당한 메모리가 남게 되었습니다.

즉, 메모리를 쓸데없이 많이 쓰고 있는 상태이므로 이러한 프로그램은 상당히 비효율적입니다.

이를 위해 상황에 따라 유동적으로 메모리를 할당하겠다. 하는게 C언어에서는 '동적 메모리 할당 malloc함수' 입니다.

 

그럼 이를 활용한 간단한 소스코드를 보면서 설명하겠습니다.

#include <stdio.h>
#include <stdlib.h>

int main()
{
	int i,count; // 학생 수 , 점수  
	int sum = 0;
	
	printf("학생 몇명임? : "); 
	scanf("%d",&count);
	int *student = malloc(sizeof(int)*count);
	if (student == NULL)
	{
		printf("Fail to Allocation.\n");
		exit(1);
	} 
	
	for (i=0; i<count; i++)
	{
		printf("[%d/%d] score? : ",i+1,count);
		scanf("%d",&student[i]);
		sum += student[i];
	}
	
	for (i=0; i<count; i++)
	{
		printf("[%d/%d] Score : %d\n",i+1,count,student[i]);
	}
	
	printf("%d",sum/i);
	
	
	free(student);
	return 0;
}

1. 제일 먼저 동적메모리 함수를 사용하기 위해선 stdlib.h 헤더파일을 선언해야 합니다.

2. 이제 눈여겨 봐야할 점.  malloc과 free입니다.

이 둘은 항상 같이 다니는 세트입니다. malloc : 메모리 빌려줘!  free : 메모리 갚어!

int *student = malloc(sizeof(int)*count);

이 코드의 의미를 보겠습니다. malloc을 통해서 int (4Byte) 자료형 크기의 메모리를 받을건데 그건 count 수만큼 합산해서 빌려줘! 이겁니다. 즉, count = 5면  20Byte를 빌리겠다는거겠죠?

 

포인터변수 student가 할당받은 메모리 공간의 주소를 가리키고 있습니다. 

 

3. if (student == NULL) 은 만약 할당받은 메모리 공간이 없다면 아래 몸체에 Fail to Allocation을 출력하여 뭔가 오작동되었음을 알립니다.

 

4. 이제 할당받은 student들의 점수를 채워넣도록 하겠습니다. 20Byte 크기의 공간을 빌렸으니 5명의 점수를 입력받습니다.

 

5. 프로그램 종료 시 사용한 20Byte를 반환하기 위해 free(student)를 해주었습니다. 이를 안할 시, 메모리 누수가 되어 지뢰찾기 하는데 메모리를 오지게 사용하게 되는 꼴이 됩니다.

 

이를 통해서 우리는 메모리를 필요한만큼만 사용해서 보다 효율적으로 관리를 하는게 가능해졌습니다.

아래는 동적메모리할당을 활용한 간단한 성적 관리 프로그램입니다.

 

#include <stdio.h>
#include <stdlib.h>

int main()
{
	int i,count; // 학생 수 , 점수  
	int sum = 0;
	
	printf("학생 몇명임? : "); 
	scanf("%d",&count);
	int *student = malloc(sizeof(int)*count);
	if (student == NULL)
	{
		printf("Fail to Allocation.\n");
		exit(1);
	} 
	
	for (i=0; i<count; i++)
	{
		printf("[%d/%d] score? : ",i+1,count);
		scanf("%d",&student[i]);
		if (student[i] > 100 || student[i] < 0)
		{
			printf("wrong!\n");
			return;
		 } 
		sum += student[i];
	}
	
	int average = sum/i;
	printf("\n\n");
	
	
	printf("평균 점수는 : %d\n",average);
	
	for (i=0; i<count; i++)
	{
		if (student[i]<average)
		{
			printf("[%d] 평균 아래입니다.재수강하세요. \n",i+1);	
		}
		
		else if (student[i]>=average)
		{
			printf("[%d ]PASS.\n",i+1);
		}
	} 
	
	
	free(student);
	return 0;
}
반응형
반응형

C언어를 공부하면서 가장 어려웠던 3가지 개념을 꼽으라면  동적메모리할당, 포인터, 구조체였던거 같습니다.

오늘 작성할 글은 포인터입니다. 저도 많이 부족하지만 포인터에 대한 간략한 이해를 도와보겠습니다.

 

포인터는 어떤 변수가 메모리 공간을 할당받고 저장되어있을 때, 그 변수가 살고있는 메모리 주소값을 가리키는 것입니다. 말 그대로 진짜 'Point!'인 셈이죠.  레이저 포인터할 때 그 포인터입니다.

 

우리가 어떤 변수의 초기값을 선언할 때는 항상 그 값들이 가지는 고유주소를 배정받게 됩니다. 이 주소의 값을 보는 방법은 printf를 통해 형식 지정자 "%p"를 사용하면 됩니다.

5라는 값은 현재 000000000062FE1C 라는 주소에서 살고 있군요.

 

* 주의사항 : 앰퍼샌드 & 기호는 두개를 쓰면 'AND'를 의미하는 논리연산자지만 하나만 쓴다면 해당 변수의 고유주소값을 호출하는 기능을 합니다. 위 소스코드를 보면 저는 number라는 변수의 주소값을 알기 위해 &number라 표현했습니다. 여기까진 이해될겁니다.

 

그럼 반대로 생각해볼 수도 있습니다. &가 변수가 가지는 고유주소를 호출하는 기능을 한다면 반대로 해당 주소에 살고 있는 변수값을 호출하는 것도 되지않나?

맞습니다. 그게 바로 * 연산자 입니다.

포인터 변수 pointer를 선언하고 &number로 초기화시켰습니다. 그리고 이에 대한 주소와 변수값을 출력했더니

위에 number의 주소와 변수값과 동일하게 나옵니다.

 

여기서 주목할 것은 4개의 printf들입니다.

일반 변수는 값을 호출할 때는 ("%d", 변수명) , 주소값을 호출할 때는 ("%p",&변수명)입니다.

근데 포인터 변수일 때는 반대입니다.

맨 위에 설명했듯이 포인터는 메모리 공간을 할당받은 변수가 살고있는 '주소값'을 가리키기 때문입니다.

따라서 주소값을 호출할 때는 ("%p",포인터변수명) , 값을 호출할 때는 ("%d" , *포인터변수명) 입니다.

 

&가 주소값을 알고싶을 때 쓰는거라면 *는 변수값을 알고싶을 때 쓰는 것이라 했으니까요.

그러면 위의 소스코드 결과를 한번 봅시다. 주소값 동일하구요. 같은 주소에 살지만 변수가 다르게 나왔습니다.

왜냐?

#include <stdio.h>

int main()
{
	int number = 5;  // 1. 처음에 number 변수에 5로 초기화했구요.
	printf("%p\n",&number);  // 2. number의 메모리 주소값을 호출
	printf("%d\n",number); // 3. number의 변수값 5를 호출
	
	int *pointer = &number;  // 4. 포인터변수 *pointer를 선언한 뒤 number의 
                             // 메모리 주소값으로 초기화
	*pointer = 3;  // 5. 포인터변수 값을 3으로 초기화
	printf("%p\n",pointer); // 6. pointer가 가리키는 메모리 주소는 number의 메모리 주소와 동일
	printf("%d",*pointer);  // 7. 4번이 실행되었으니 포인터변수를 통해 기존의 값 5에서 3으로 
                            // 변경한 것이 출력됨.
	
	return 0;
}

이렇기 때문입니다.

 

그럼 이쯤에서 포인터를 왜 배우며, 왜 쓰는지 생각해볼때가 온거 같습니다.

예시를 들겠습니다.

같은 수업을 듣는 학생 3명 A,B,C가 있다 합시다. 이 중 A는 공부를 잘해서 수업 중에 배운 내용을 잘 요약정리했어요. 그래서 친구인 B,C를 도와주기 위해 본인이 공부한 내용을 다시 또 종이에 써서 각각 나눠줬습니다.

그냥 교재의 몇페이지, 몇페이지 부분이 중요하니 그 부분들을 자세히 읽어봐라. 하면 될걸 말이죠.

 

컴퓨터도 똑같습니다. 일일이 함수를 만들어주면서 거기에 매개변수를 전달해서 실행하게 하면, 직관적으로는 괜찮아보이지만 함수가 사용하는 메모리도 발생하게 되고  프로그램을 수행하는데 있어서도 처리속도가 증가할 수 밖에 없습니다. 즉, 위 A학생이 종이를 쓴 것은 메모리 사용을 의미하구요. 공부한 내용을 종이에다 쓰는 행동은 프로그램을 수행하는 것이라 보면 됩니다. 딱봐도 시간 & 공간 다 불필요한 낭비죠.

교재의 N페이지를 읽어봐라. 한 마디면 시간절약 공간절약이니까요.

 

그래서 포인터를 쓰는 겁니다.  여기까진 이해 잘 되었으리라 생각합니다.

뒤에 문자열을 설명할 때도, 동적메모리를 설명할 때도 쭉 포인터를 같이 설명할테니  여기선 마지막으로 1차원 배열과 포인터의 관계에 대해 적고 끝내겠습니다.

 

배열은 변수들이 모여있는 하나의 아파트라 보면 되었습니다.

int num[6] = {0,1,2,3,4,5}는  num이라는 아파트에 0 , 1 , 2 , 3 , 4 , 5 라는 사람들이 살고 있습니다. 근데 다 같이 사는게 아니라 각 호수에 1명씩 들어가 살고 있다 보시면 됩니다.

왜냐하면, 배열은 변수의 수에 따라 메모리 공간을 할당해주는 것이기 때문에 이들은 모두 다 다른 메모리주소값을 갖고 있습니다.

다 다르죠? 근데 신기한게 뒷자리 숫자가 4씩 증가하는거 같아보입니다. 따라서 추측하건데 num은 int 자료형으로 선언되었기에 4Byte씩 할당되었음으로 파악됩니다.

이걸 주목해야해요.

첫번째 인덱스의 주소만 알면 그 뒤의 인덱스들의 주소는 자연스럽게 파악이 되거든요.

즉, 포인터 연산을 통해 배열의 데이터 주소들로 접근이 굉장히 용이해진다는 점. 이 개념까지 갖고 가시면 되겠습니다.

 

좀 더 이어서 쓰면 글이 길어질 것 같아 여기까지로 끊고 문자열이나 배열 예제들 풀 때 계속해서 덧붙여 설명하겠습니다.

반응형
반응형

10818번: 최소, 최대 (acmicpc.net)

 

10818번: 최소, 최대

첫째 줄에 정수의 개수 N (1 ≤ N ≤ 1,000,000)이 주어진다. 둘째 줄에는 N개의 정수를 공백으로 구분해서 주어진다. 모든 정수는 -1,000,000보다 크거나 같고, 1,000,000보다 작거나 같은 정수이다.

www.acmicpc.net

 

주어진 문제는 이렇습니다. 입력받을 정수의 개수를 먼저 선언한 뒤, 정수들을 입력했을 때  최소, 최대값만 나오게 하는 것입니다. 일단 문제를 딱 읽었을 때, 배열과 조건문. 그리고 기준이 될 최대&최소 변수의 선언이 필요해보입니다.

#include <stdio.h>

int main()
{
	int i,count;
    int max = -1000000;
    int min = 1000000;
	scanf("%d",&count);
	
	int num[count];
	for (i=0; i<count; i++)
	{
		scanf("%d",&num[i]);
	}
	
	return 0;
}

따라서 기본적인 틀은 이렇게 잡을 수 있겠습니다. 배열의 크기를 입력받았고 (=입력받을 정수의 개수) 각 배열안에

넣을 정수들을 for문으로 입력받았습니다.

 

중요한건 최대,최소값의 기준입니다. 

 

max를 가장 낮은 값, min을 가장 높은 값으로 했습니다. 따라서, if문에서 처음에는 무조건 num[0]이  max가 되고 min이 됩니다. 제가 입력한 숫자 25,32,46,8,3을 보면

num[0] = 25
num[1] = 32
num[2] = 46
num[3] = 8
num[4] = 3

입니다. 즉, num[0]은 max=min=25가 되었습니다.

이제 if (num[i]>max)부터 볼게요.  num[1]>25라면  max=num[1]이 됩니다. num[1]은 32이므로  max= 32가 되었고

if (num[i]<min)은  32<25는 성립하지 않으므로 min = 25입니다. 이걸 5번째 인덱스까지 반복할 경우

 

max는 46, min은 3이 됩니다. 하나하나 파헤쳐보면 의외로 어렵지 않습니다.

우리가 기억할 것은 프로그래밍에서 알고리즘과 여러 문법들은 그저 사고력을 현실화시키기 위한 도구일뿐. 수학적 사고력이 가장 중요하므로 한 문제를 두고 끊임없이 고민하도록 합시다.

반응형

+ Recent posts