C언어는 main 함수를 비롯해서 모든 것이 함수로 되어 있다.
함수를 자동차 부품에 비교하자면 아주 작은 부품이 모여서 엔진이 되고, 타이어가 되며, 변속장치가 되고 바디가 되고 계기판 등 여러 부품이 모여서 자동차 하나를 이루는 것 처럼 작은 함수들이 모여서 큰 함수가 되며, 그러한 함수들로 이루어진 것이 C언어인것이다.
함수
- 기능 중심
- 요약 하기
- 중복된 코드의 재활용
기능 중심
특별한 기능을 하도록 만들어 놓은 함수이다.
printf 는 출력을 위한 함수, scanf 는 입력을 위한 함수, fopen 는 파일을 위한 함수, sin, cos, sqrt 등의 수학 함수 등 기능별로 유용하게 사용할 수 있도록 만들어 놓은 것이다.
요약하기
신문을 볼때 가장 기사 제목 또는 소제목 등을 보며 내가 필요한 정보들을 중심으로 글을 보게 된다.
포털싸이트에서도 마찮가지로 특정 제목만을 보여준다. 기사의 내용을 전부 펼쳐서 보여준다면
필요한 정보를 빨리 찾을 수 있을까?
C언어에서도 마찬가지이다.
아래는 내가 만든 소스 중 일부이다. C++ 언어로 만들었지만 어떤 일을 하게 되는지 요약하는 역할을 함수가 하게 된다.
OpenGLApp::OpenGLApp()
{
Register();
Create();
OneTimeSceneInit();
ResizeScene(m_iClntWidth, m_iClntHeight);
}
과연 이 부분을 펼쳐서 본다면 뭐가 뭔지 과연 알 수 있을까? 찾기가 매우 힘들다.
복사만 하면 되는 관계로 전부다 펼쳐 보이겠다.
OpenGLApp::OpenGLApp()
{
// set the pixel format we want
PIXELFORMATDESCRIPTOR pfd = {
sizeof(PIXELFORMATDESCRIPTOR), // size of structure
1, // default version
PFD_DRAW_TO_WINDOW | // window drawing support
PFD_SUPPORT_OPENGL | // OpenGL support
PFD_DOUBLEBUFFER, // double buffering support
PFD_TYPE_RGBA, // RGBA color mode
m_DisplayDeviceInfo.m_iScreenBitsPixel, // 32 bit color mode
0, 0, 0, 0, 0, 0, // ignore color bits, non-palettized mode
0, // no alpha buffer
0, // ignore shift bit
0, // no accumulation buffer
0, 0, 0, 0, // ignore accumulation bits
16, // 16 bit z-buffer size
0, // no stencil buffer
0, // no auxiliary buffer
PFD_MAIN_PLANE, // main drawing plane
0, // reserved
0, 0, 0 }; // layer masks ignored
GLuint pixelFormat;
// choose best matching pixel format
if (!(pixelFormat = ChoosePixelFormat(m_hdc, &pfd)))
{
MessageBox(NULL, "Can't find an appropriate pixel format", "Error", MB_OK | MB_ICONEXCLAMATION);
return FALSE;
}
// set pixel format to device context
if(!SetPixelFormat(m_hdc, pixelFormat,&pfd))
{
MessageBox(NULL, "Unable to set pixel format", "Error", MB_OK | MB_ICONEXCLAMATION);
return FALSE;
}
return TRUE;
// create the OpenGL rendering context
if (!(m_hrc = wglCreateContext(m_hdc)))
{
// reset the display
MessageBox(NULL, "Unable to create OpenGL rendering context", "Error",MB_OK | MB_ICONEXCLAMATION);
return FALSE;
}
// now make the rendering context the active one
if(!wglMakeCurrent(m_hdc, m_hrc))
{
// reset the display
MessageBox(NULL,"Unable to activate OpenGL rendering context", "ERROR", MB_OK | MB_ICONEXCLAMATION);
return FALSE;
}
return TRUE;
CreateBitmapFont("Arial", 20);
glShadeModel(GL_SMOOTH); // use smooth shading
glEnable(GL_DEPTH_TEST); // hidden surface removal
// 컬링을 위한 부분 막아두기로 한다. GL_BACK 이 기본 설정 된다.
glEnable(GL_CULL_FACE); // do not calculate inside of poly's
// glEnable(GL_POLYGON_SMOOTH);
glFrontFace(GL_CCW); // counter clock-wise polygons are out
glEnable(GL_LIGHTING); // enable lighting
glEnable(GL_TEXTURE_2D); // enable lighting
// Setup the materials for LIGHT0
glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, matAmbient);
glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, matDiff);
// Now setup LIGHT0
glLightfv(GL_LIGHT0, GL_AMBIENT, ambientLight); // setup the ambient element
glLightfv(GL_LIGHT0, GL_DIFFUSE, diffuseLight); // the diffuse element
glLightfv(GL_LIGHT0, GL_POSITION, lightPosition); // place the light in the world
// Enable the light
glEnable(GL_LIGHT0);
InitMultiTexture();
m_Shade.LoadTexture("models\\Shade\\Shade.bmp");
//m_Shade.LoadTexture("models\\ogro\\ogrobase.pcx");
Texture *pTexture = &m_Shade;
// set the proper parameters for an MD2 texture
glGenTextures(1, &pTexture->m_iTexID);
glBindTexture(GL_TEXTURE_2D, pTexture->m_iTexID);
// glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_CLAMP);
// glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_CLAMP);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_NEAREST);
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_ADD);
switch (pTexture->m_textureType)
{
case BMP:
gluBuild2DMipmaps(GL_TEXTURE_2D, GL_RGB, pTexture->m_iWidth, pTexture->m_iHeight,
GL_RGB, GL_UNSIGNED_BYTE, pTexture->m_pImageData);
break;
case PCX:
gluBuild2DMipmaps(GL_TEXTURE_2D, GL_RGBA, pTexture->m_iWidth, pTexture->m_iHeight,
GL_RGBA, GL_UNSIGNED_BYTE, pTexture->m_pImageData);
break;
case TGA:
gluBuild2DMipmaps(GL_TEXTURE_2D, GL_RGB, pTexture->m_iWidth, pTexture->m_iHeight,
GL_RGB, GL_UNSIGNED_BYTE, pTexture->m_pImageData);
break;
default:
break;
}
m_Model.Load("models\\pknight\\tris.md2", "models\\pknight\\knight_white.bmp");
// set active texture unit to 0
glActiveTextureARB(GL_TEXTURE0_ARB);
glEnable(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D, m_Model.m_plistModelTexture[0].m_iTexID); // bind checkerboard texture to texture unit 1
glActiveTextureARB(GL_TEXTURE1_ARB);
glEnable(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D, pTexture->m_iTexID); // bind smile texture to texture unit 0
return TRUE;
m_iClntWidth = iWidth;
m_iClntHeight = iHeight;
SetTransformations(ePerspectiveProjection);
return TRUE;
//===============================================================================================
OpenGLApp::OpenGLApp()
{
// set the pixel format we want
PIXELFORMATDESCRIPTOR pfd = {
sizeof(PIXELFORMATDESCRIPTOR), // size of structure
1, // default version
PFD_DRAW_TO_WINDOW | // window drawing support
PFD_SUPPORT_OPENGL | // OpenGL support
PFD_DOUBLEBUFFER, // double buffering support
PFD_TYPE_RGBA, // RGBA color mode
m_DisplayDeviceInfo.m_iScreenBitsPixel, // 32 bit color mode
0, 0, 0, 0, 0, 0, // ignore color bits, non-palettized mode
0, // no alpha buffer
0, // ignore shift bit
0, // no accumulation buffer
0, 0, 0, 0, // ignore accumulation bits
16, // 16 bit z-buffer size
0, // no stencil buffer
0, // no auxiliary buffer
PFD_MAIN_PLANE, // main drawing plane
0, // reserved
0, 0, 0 }; // layer masks ignored
GLuint pixelFormat;
// choose best matching pixel format
if (!(pixelFormat = ChoosePixelFormat(m_hdc, &pfd)))
{
MessageBox(NULL, "Can't find an appropriate pixel format", "Error", MB_OK | MB_ICONEXCLAMATION);
return FALSE;
}
// set pixel format to device context
if(!SetPixelFormat(m_hdc, pixelFormat,&pfd))
{
MessageBox(NULL, "Unable to set pixel format", "Error", MB_OK | MB_ICONEXCLAMATION);
return FALSE;
}
return TRUE;
// create the OpenGL rendering context
if (!(m_hrc = wglCreateContext(m_hdc)))
{
// reset the display
MessageBox(NULL, "Unable to create OpenGL rendering context", "Error",MB_OK | MB_ICONEXCLAMATION);
return FALSE;
}
// now make the rendering context the active one
if(!wglMakeCurrent(m_hdc, m_hrc))
{
// reset the display
MessageBox(NULL,"Unable to activate OpenGL rendering context", "ERROR", MB_OK | MB_ICONEXCLAMATION);
return FALSE;
}
return TRUE;
CreateBitmapFont("Arial", 20);
glShadeModel(GL_SMOOTH); // use smooth shading
glEnable(GL_DEPTH_TEST); // hidden surface removal
// 컬링을 위한 부분 막아두기로 한다. GL_BACK 이 기본 설정 된다.
glEnable(GL_CULL_FACE); // do not calculate inside of poly's
// glEnable(GL_POLYGON_SMOOTH);
glFrontFace(GL_CCW); // counter clock-wise polygons are out
glEnable(GL_LIGHTING); // enable lighting
glEnable(GL_TEXTURE_2D); // enable lighting
// Setup the materials for LIGHT0
glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, matAmbient);
glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, matDiff);
// Now setup LIGHT0
glLightfv(GL_LIGHT0, GL_AMBIENT, ambientLight); // setup the ambient element
glLightfv(GL_LIGHT0, GL_DIFFUSE, diffuseLight); // the diffuse element
glLightfv(GL_LIGHT0, GL_POSITION, lightPosition); // place the light in the world
// Enable the light
glEnable(GL_LIGHT0);
InitMultiTexture();
m_Shade.LoadTexture("models\\Shade\\Shade.bmp");
//m_Shade.LoadTexture("models\\ogro\\ogrobase.pcx");
Texture *pTexture = &m_Shade;
// set the proper parameters for an MD2 texture
glGenTextures(1, &pTexture->m_iTexID);
glBindTexture(GL_TEXTURE_2D, pTexture->m_iTexID);
// glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_CLAMP);
// glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_CLAMP);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_NEAREST);
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_ADD);
switch (pTexture->m_textureType)
{
case BMP:
gluBuild2DMipmaps(GL_TEXTURE_2D, GL_RGB, pTexture->m_iWidth, pTexture->m_iHeight,
GL_RGB, GL_UNSIGNED_BYTE, pTexture->m_pImageData);
break;
case PCX:
gluBuild2DMipmaps(GL_TEXTURE_2D, GL_RGBA, pTexture->m_iWidth, pTexture->m_iHeight,
GL_RGBA, GL_UNSIGNED_BYTE, pTexture->m_pImageData);
break;
case TGA:
gluBuild2DMipmaps(GL_TEXTURE_2D, GL_RGB, pTexture->m_iWidth, pTexture->m_iHeight,
GL_RGB, GL_UNSIGNED_BYTE, pTexture->m_pImageData);
break;
default:
break;
}
// m_Model.Load("models\\ogro\\tris.md2", "models\\ogro\\ogrobase.pcx");
m_Model.Load("models\\pknight\\tris.md2", "models\\pknight\\knight_white.bmp");
// m_Model.Load("models\\pknight\\tris.md2", "models\\pknight\\ctf_b.pcx");
// m_Model.Load("models\\phantom\\tris.md2", "models\\phantom\\phantom_white.pcx");
// m_Model.Load("models\\rhino\\tris.md2", "models\\rhino\\rhino_white.tga");
// set active texture unit to 0
// set active texture unit to 0
glActiveTextureARB(GL_TEXTURE0_ARB);
glEnable(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D, m_Model.m_plistModelTexture[0].m_iTexID); // bind checkerboard texture to texture unit 1
glActiveTextureARB(GL_TEXTURE1_ARB);
glEnable(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D, pTexture->m_iTexID); // bind smile texture to texture unit 0
return TRUE;
m_iClntWidth = iWidth;
m_iClntHeight = iHeight;
SetTransformations(ePerspectiveProjection);
return TRUE;
---> SetTransformations(ePerspectiveProjection);
이 함수의 내용을 또 풀어 놓으면
switch (iType)
{
case eOrthogonalProjection:
glViewport(m_iClntWidth/2, m_iClntHeight/2, m_iClntWidth, m_iClntHeight);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluOrtho2D(0, m_iClntWidth, 0, m_iClntHeight);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
break;
case ePerspectiveProjection:
if (m_iClntHeight == 0) m_iClntHeight = 1;
glViewport(0, 0, m_iClntWidth, m_iClntHeight);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluPerspective(90.0, m_iClntWidth/m_iClntHeight, 1.0, 100.0);
glMatrixMode(GL_MODELVIEW);
break;
}
return TRUE;
중복된 코드의 재활용
앞서 말한 기능 중심의 함수와 요약하기 함수 등은 여러번 호출 해서 실행할 수 있는데 미미하지만 코드를 중복해서 쓰지 않고 재사용을 할 수 있는 효과가 있는 것이다.
사용자 함수
C컴파일러에서 제공하는 표준함수도 있지만 그것으로 충분하지 않으며, 필요한 함수들을 사용자가 정의해서 만들 수 있다.
<문법>
자료형 함수이름(인수) // 여기서 인수는 0개부터 여러개 올 수 있다.
{
문장;
return 값; // 반드시 있어야한다.
}
자료형이 void 라면
void 함수이름(인수)
{
문장;
} // return 이 없거나
void 함수이름(인수)
{
문장;
return; // 값 없이 return 만 쓸 수 있다.
}
자료형(data types)에는 되돌려 주는(return) 값(value)이 어떤 자료형으로 되어 있는지를 알려주는 기능을 한다.
int 함수이름(인수)
{
문장;
return 0; // 정수값이 리턴된다.
}
int 함수이름(인수)
{
int a = 1;
int b = 2;
return (a + b); // 정수값이 리턴된다.
}
float 함수이름(인수)
{
float fPoint;
scanf("%f", &fPoint);
return fPoint; // 실수(float)값이 리턴된다.
}
함수이름
함수이름은 숫자가 아닌 알파벳 문자가 첫문자가 되도록 해야하며,
되도록이면 함수이름만 보았을 때 어떤 역할 또는 기능을 하는지에 대해
설명없이도 짐작할 수 있을 정도로 Full Name 으로 쓰고,
첫문자는 대문자로 시작하며 단어단위로 끊어서 첫문자를 대문자로 만들어 준다.
<좋은 코딩 습관>
옛날 C언어에서는 함수 또한 소문자로 시작했으나, 그건 옛날 코딩 스타일일뿐이고,
함수의 첫문자는 항상 대문자가 되도록 하는 것이 좋다.
변수는 함수와 구분되게 소문자로 시작하는 것이 좋다.
// 어떤 의미일까? 함수 안을 들여다 보아야지 기능을 알 수 있을 것이다.
int f()
{
return 0;
}
// 어떤 의미일까? file 가지고 어떤 동작을 한다는 말일까?
int file()
{
return 0;
}
// 파일을 연다는 뜻인것 같은데 좀 더 자세한 의미를 알 수 없을까?
int openfile()
{
return 0;
}
// 이제서야 text 파일에서부터 파을을 열겠다는 뜻이라는 것을 짐작할 수 있으나
// 단어의 의미가 눈에 들어 오지 않는다.
int openfilefromtext()
{
return 0;
}
// 띄워 쓰기를 할 수 없으므로 공백 대신 '_' (under bar)를 넣겠다.
// 옛날 코딩 스타일이다.
int open_file_from_text()
{
return 0;
}
// 이제 왜 이렇게 Full Name 으로 쓰고 대소문자를
// 구분해서 적는 이유를 이제는 느꼈으면 한다.
int OpenFileFromText()
{
return 0;
}
사용자 함수 만들기
<문법>
[자료형] 함수이름(인수,....)
{
return [자료형에 대응하는 상수 or 변수];
}
[자료형 (Data Types)]: void, char, short, int, float, double, bool 등이 올 수 있다.
void*, char*, short*, int*, float*, double*, bool 등 , 구조체, 클래스 등
[함수이름 (Function Name)]: 앞에서 설명한것 처럼 가급적이면 Full Name 첫문자, 단어 단위로 첫문자 대문자
예) CreateTextureFromFile, SetTextureStageState
[인수 (Parameters)]: 0개~다수개
예)
void Initialize(); // 0개
double sqrt(double x); // 1개
double pow(double x, double y); // 2개
void putpixel(int x, int y, int color); // 3개
// 인수들은 서로 다른 자료형이 올 수도 있다.
char* itoa(int value, char *string, int radix);
[return] : 자료형에 대응하는 상수 or 변수가 오며, 함수가 호출된 뒤에 값을 반환한다. 자료형을 void로 선언했을 경우 return을 생략할 수 있으며, return; 라고 쓰기도 한다. return을 쓰게 되면 뒷문장과는 상관없이 함수의 끝으로 가서 종료하게 된다.
--> 여기서 설명한 sqrt, pow, putpixel, itoa 는 첫문자가 소문자인 옛날 방식의 함수들이다.
최근에는 [함수이름]에서 설명한 것 처럼 첫문자가 대문자이며, 자바에서는 아래와 같은 함수를 쓰기도 한다.
자바스타일 함수) createTextureFromFile
그럼 사용자 함수를 만들어 보자
c = a + b; 라고 간단히 쓸 수 있지만 함수로 만들어 보면
int Add(int iNum1, int iNum2)
{
return iNum1+iNum2;
}
라고 작성하면
c = Add(a, b); 라고 호출하면 된다.
int iNum1 = a, int iNum2 = b 값이 복사된다.
예제>
#include <stdio.h>
int Add(int iNum1, int iNum2)
{
return iNum1+iNum2;
}
void main()
{
int a, b;
scanf("%d%d", &a, &b);
printf("%d + %d = %d\n", a, b, a+b);
}
만약에 아래와 같이 작성하고 컴파일 하면 어떤 결과가 나올까?
#include <stdio.h>
void main()
{
int a, b;
scanf("%d%d", &a, &b);
printf("%d + %d = %d\n", a, b, a+b);
}
int Add(int iNum1, int iNum2)
{
return iNum1+iNum2;
}
<실습>
아래와 같이 실행되는 프로그램을 함수를 이용해서 작성하시오!
1. +
2. -
3. *
4. /
5. 종료
1(enter)
두 수를 입력하세요!
2 3(enter)
2 + 3 = 5
1. +
2. -
3. *
4. /
5. 종료
3 (enter)
4 5
4 * 5 = 20
-------------------------------------------
반복문(while or for) 사용, switch~case 문 사용
<실습>
1~n 까지 합을 구하는 함수 만들기
int Sum(int n);
<실습>
n! 팩토리얼 함수 만들기
int Factorial(int n);
<실습>
두 수 중 큰 값을 구하는 함수와 두 수중 작은 값을 구하는 함수 만들기
int Max(int n1, n2);
int Min(int n1, n2);
<실습>
|n|절대값 구하는 함수 만들기
int Abs(int n);
<실습>
x의 y승 계산하는 함수를 만들기
int Pow(int x, int y);
재귀호출 (Recursion)
재귀호출이란 자기 자신의 함수안에서 자신의 함수를 호출하는 경우 재귀호출이라 부른다.
예)
void PrintHello()
{
printf("Hello");
PrintHello();
}
자료구조 시간에 트리(Tree)라는 구조를 배우게 되면 재귀 호출을 쓰는 이유를 잘 알 수 있을 것이다. 또한 디렉토리를 검색하는 알고리즘을 만들 때도 재귀 호출을 쓰면 아주 쉽게 코드를 만들 수 있다.
재귀호출이 자신을 여러번 호출하기 때문에 비재귀호출보다는 성능상 비효율적이기는 하나, 사람의 생각과 많이 닮아 있기 때문에 적절하게 잘 이용한다면 복잡한 구조를 단순화 시킬 수 있다.
n! 의 경우
n*(n-1)*(n-2)*...*2*1
로 설명될 수 있다.
재귀호출을 쓴다면
void RecursiveFactorial(int n)
{
return n * RecursiveFactorial(n-1);
}
로 설명될 수 있다.
이렇게 된다면 만약 n 에 3을 넣었을 경우 어떻게 될까?
RecursiveFactorial(3);
->
void RecursiveFactorial(3)
{
return 3 * RecursiveFactorial(2);
}
RecursiveFactorial(2);
->
void RecursiveFactorial(2)
{
return 2 * RecursiveFactorial(1);
}
RecursiveFactorial(1);
->
void RecursiveFactorial(1)
{
return 1 * RecursiveFactorial(0);
}
RecursiveFactorial(0);
->
void RecursiveFactorial(0)
{
return 0 * RecursiveFactorial(-1);
}
결국엔
RecursiveFactorial(3);
->
void RecursiveFactorial(3)
{
return 3 * 2 * 1 * 0 * (-1) * (-2) * (-3) * ........;
}
끝없이 호출 될 것이다.
따라서 n 의 값이 1 이 될 때 더이상 재귀호출을 하지 않고 끝낼수 있도록 수정해야한다.
if (1 == n)
{
return 1;
}
<실습>
재귀호출하는 팩토리얼 함수를 만들어 보자
void RecursiveFactorial(int n);