Loading presentation...

Present Remotely

Send the link below via email or IM

Copy

Present to your audience

Start remote presentation

  • Invited audience members will follow you as you navigate and present
  • People invited to a presentation do not need a Prezi account
  • This link expires 10 minutes after you close the presentation
  • A maximum of 30 users can follow your presentation
  • Learn more about this feature in our knowledge base article

Do you really want to delete this prezi?

Neither you, nor the coeditors you shared it with will be able to recover it again.

DeleteCancel

7. 함수

No description
by

형열 최

on 21 December 2013

Comments (0)

Please log in to add your comment.

Report abuse

Transcript of 7. 함수

7. 함수
1. 함수 선언과 정의
2. 인자 전달.
3. 반환값
4. 함수 오버로딩
5. 해석 모호성 해결.
6. 기본 인자.
7. 가변 인자.
8. 함수 포인터.
9. 매크로.
1. 함수 선언

해당 함수의 이름,
함수가 반환하는 값의 타입
함수에 넣어 주는 인자의 개수 및 타입

함수 호출시에 행하는 인자전전달의 처리과정은 객체 초기화 과정과 의미적으로 똑같다.

1.1 함수 정의

함수가 호출 되려면 그 함수의 몸퉁이 프로그램이 어딘가에 정의되어 있어야 한다.

extern void swap(int*, int*);
void swap(int* p, int*q)
{
int t = *p;
*p = *q;
*q = t;
}

한 함수에 대해 소스코드에 써 넣는 선언문과 정의문은 반드시 타입이 같아야 한다. 인자의 이름은 '타입' 에 속하지 않기 때문에 꼭 같을 필요는 없다.
void search( table* t, const char* key, const char*)
{
// 3번째 인자는 사용하지않음.
}

함수 인라인(inline) 이라는 형식으로도 정의 될수 있다.
inline int fac(int n)
{
return (n<2)? 1: n*fac(n-1);
}



1.2 정적 변수
지역 변수의 초기화는 함수가 호출될 때마다 일어나며, 사본 역시 초기화 될때 마다 만들어진다.

static 이라는 키워드로 선언되는 지역변수
함수가 되풀이해서 호출되더라도 메모리 주소는 바뀌지 않아서, 초기화는 함수가 처음 호출 될 때 단 한번번 이루어 진다.

void( int a )
{
while( a -- )
{
static int n = 0;
int x =0;
cout << "n == " << n++ << ", x == " << x ++ << " \n";

}
}

전역 변수와의 차이는 정적 변수가 차지 하는 메모리를 그 변수가 선언된 함수 이외의 함수에서는 건드릴수 없다.
2. 인자 전달
인자 전달의 의미 구조는 변수 초기화 의미구조와 정확히 똑같다.

void f( int val, int& ref)
{
val++;
ref++;
}

void g()
{
int i = 1;
int j = 1;
f( i, j);

}

인자 전달의 처리과정은 변수 초기화와 의미적으로 통일하지만, 대입과는 다르다.

리터럴, 상수, 타입 변환이 필요한 인자는 상수 참조자(const&) 타입으로 넘길 수 있으나, 비상수 참조자 타입으로는 못 넘어간다 const T& 타입에 대한 변환이 허용되기 때문에, 이런 인자는 임시 객체에 저장했다가 전달하게 함으로써 T 인자와 똑같은 값을 사용할 수 있게 되는 것 이다
2.1 배열 인자의 전달
함수 인자로 배열이 사용되면 그 배열의 첫 번째 원소에 대한 포인터가 전딜된다.
int strlen( const char*);
void f()
{
char v[] = "an array";
int i = strlen(v);
int j = strlen("Nicholas");
}

호출된 힘수 쪽에서 는 배열의 크기를 딩연히 알 수 없다.
3. 값의 반환
void로 선언되지 않은 함수라면 반드시 어떤 하나의 값을 반환이 한다.
retun 문은 함수에서 어떤 값을 반환할 때 쓰는 문장이다.

자기 자신을 되 부르는 함수를 재귀 함수 라고 한다.
int fac(int n) { return (n>1) ? n*fac(n -1); 1; }

함수가 호출되면 인자 및 지역 변수의 사본이 새로 만들어진다. 이 들이 저장되는 공간은 이 함수가 종료된 후에는 재사용될 수 있기 때문에, 지역 변수에 대한 포인터는 절대로 반환 할 수 없다. 포인터가 가리키는 메모리에 있는 내용이 언제 어떻게 바뀔지 모르기 때문이다.

int* fp() { int local = 1; /* ... */ return &local; }
int& fr( ) { int local = 1; /* ... */ return local; }

천만다행으로 지역 변수의 참조자를 반환하는 문장은 컴파일러가 쉽게 잡아낼 수 있다.

4. 오버로딩 되는 함수 이름
다른 인자 타업에 대해 같은 함수 이름을 사용하는 것을 오버로딩이라고 한다.

f란 이름이 오버로딩되어 있는 상태에서 함수 f가 호출된다고 가정하면, 컴파일러 는 이름 f의 힘수들 가운데 어떤 것을 호출할지 파악한다. 이것이 가능한 이유는, 컴파일러가 f란 이름의 모든 함수에 대해 형식 인자의 타입 과 실제 인자의 타입 사이의 비교를 수행 하기 때문이다.

실제 인자와 가정 정확하게 들어 맞은 함수를 찾아 그것 의 호출코드를 만들고, 이런 함수가 없으면 컴파일타임 에러를 낸다.

void printf( double);
void printf( long );

void f()
{
printf( 1L );
printf( 1.0 );
printf( 1 );
}

함수 일치 기준.

[1] 완전 일치 (exact match)
[2] 승격(promotion)을 사용한 일치
[3] 표준 변환(standard conversions)을 사용한 일치
[4] 사용자 정의 변환{user-defined conversion)을 사용한 일치 (SII .4)
[5] 함수 선언에 인자 생략 표기 인 ".를 사용한 일치 (S7.6)


4.1 오버로딩과 반환 타입
float sqrt(float) ;
double sqrt(double);

반환 타입의 경우, 오버로딩 모호성 해결과정에서 제외된다.
만약에 반환 타입까지 일일이 고려되었다면 sqrt( )를 호출하는 부분만 떼어 놓고 어떤 함수를
호출 할지를 결정하기란 불가능했을 것 이다.
4.2 오버로딩과 유효 범위
void f( int );
void g()
{
void f(double);
f(1);
}

서로 다른 유효범위에 선언된 함수는 오버로딩되지 않는다.
4.3 오버로딩 모호성 해결을 프로그래머가 직접 해 주는 경우
void f1(char*);
void f1(int*);

void k(int i )
{
f1(0);
}

명시적 타입 변환

f1( static_case<int*> 0);

을 해주어도 된다.
4.3 여러 개의 인자에 대한 모호성 해결
int pow(int, int);
double pow(double, double);
complex pow(double, complex);
complex pow(complex, int);
complex pow(complex, double);
complex pow(complex, complex);

void k(complex z)
{
int i = pow(2,2);
double d = pow(2.0,2.0);
complex z2 = pow(2,z );
complex z3 = pow(z,2);
complex z4 = pow(z,z );
}


5. 기본 인자
- 함수의 원형 선언시 매개변수에 자료형과 기본값을 주어 호출시 값을 부여하지 않아도 매개변수의 기본값이 자동으로 지정되게 하는 기능

- 함수 기본 인자의 올바른 사용 예
void f(int, int, int = 100);
void f(int, int = 100, int = 450);
void f(int, int =0, char* = 0);

- 나쁜 예
void g( int = 0, int = 0, char* );
void g(int =0, int, char* = 0 );
void g(char *= 0 );


6. 인자의 개수 및 타입이 가변적인 경우
함수 호출 시 인자의 개수 및 각 인자의 타입이 어떻게 될지 예측할 수 없는 경우가 있다. 이런 함수는 인자 리스트 뒤에 생략기호( ... )를 써 줌으로써 선언한다.
int printf(const char* ... );
int testInt(const int ...);

구현은.
void testInt( const int test ...)
{
va_list ap;
var_start(ap, test);
for(;;)
{
char *p = va_arg(ap, char*);
if(p==0) break;
cerr< p << '';
}

var_end(ap);

}



5. 함수 포인터
함수에 대해 할 수 있는 일은 두가지 , 즉 호출 및 주소 획득
어떤 함수의 주소 획득을 통해 손에 넣은 포인터는 그 함수를 통해 다시 쓸 수 있다.

void error(string s) {/*.... */}
void (*efcf)(string);
void f()
{
efcf = &error;
efcf("error");
}

void (*f1)(string) = &error;
void (*f2)(string) = error;
void g()
{
f1( "Vasa");
(*f1)("Mary Rose");
}

함수 포인터의 경우 원래의 함수가 선언될 때 함께 했던 인자 타입 그대로 가지므로 포인터에 대입할 때 함수의 타입이 완전히 일치 해야한다.

void (*pf)(string); // void(string) 에 대한포인터
void f1 (string); // void(string)
int f2(string); // int(string) 타입
void f3(int*) ; //void(int*) 타입

void f()
{
pf = &f1;
pf = &f2;
pf = &f3;

pf("hera");
pf (1);
int i = pf("zeus");
}
함수 포인터의 배열
typedef void (*PF)();

PF edit_ops[] = {
&cut, &paste, &copy, &search
}



오버 로딩된 함수의 주소 역시, 여느 함수와 마찬가지로 주소를 뽑아 함수 포인터에 대입하거나 함수
포인터 를 초기화할 수 있다. 이때도 오버 로딩된 함수들 중 적절한 함수를 골라야 하는데,좌변에 있는
포인터의 타입이 사용된다
void f (int);
int f(char);
void (*pf1)(int) = &f; // void f(int)
int (*pf2 )(char) = &f: // int f(char)
void (*pf3)(char) = &f; // 에러 void f(char) 는 없다


6. 매크로
기본적으로 간단한 형태의 매크로는 이렇게 정의한다.

#define NAME rest of line

여기서 NAME이 토큰으로 출현한 부분은 모두 rest of line 으로 바뀐다. 즉,
named=NAME

위의 코드는 이 렇 게 바뀐다.
named = rest of line

인자를 받는 매 크로도 정의할 수 있다
#define MAC(x, y) argument1: x argument2: y

MAC이 사용된 부분에는 반드시 인자 두개가 주어져야 한다 MAC() 이 확장될 때 이 인지들이 x와 y를 대신한다. 이를테면,
expanded = MAC(foo bar, yuk yuk)

위의 코드는 이렇게 확장된다
expanded = argument1: foo bar argument2: yuk yuk

매크로는 이름을 오버로딩할 수 없고 재귀 호출 역시 처리할 수 없다.
#define PRlNT(a,b) cout<<(a)<<(b)
#define PRlNT(a, b, c) cout<<(a)<<(b)<<(c) // 재정의 에러가 발생한다 오버로딩이 불가능하니까.
#define FAC(n) (n> l)?n * FAC(n -1):1 // 골칫거리 재귀 매크로..

매크로가 하는 일은 단순히 문자열 바꿔 쓰기 일 뿐, C++ 문법 이나 C++ 타입 , 유효 범위 규칙 같은 것은 하나도 신경 쓰지 않는다. 게다가 매크로가 확정된 꼴은 컴파일러만 볼 수 있기 때문에, 매크로에
들어 있는 에러는 매크로가 확장될 때 컴파일러에 의해 보고될 뿐, 매크로를 정의할 때 는 전혀 손을
댈 수 없다. 결국 에러 메시지에 있어서도 대단히 불투명한 흉물이 매크로이다.


그리고 이런 위험한 매크로들도 있다.
#define SQUARE(a) a*a
#define INCR_xx (xx)++

왜 위험하냐고? 한번 써 보면 안다.
int xx = 0;
void f()
{
int xx = 0;
int y = SQUARE(xx+2); // y = xx+2*xx+2; xx;
INCR_xx //지역변수 xx 증가
}

정말 매크로를 써야 한다면, 전역 이름에는 반드시 범위 지정 연산자 :: 를 사용하고 매크로 인wk는 괄호로 둘러싸 빈틈을 만들지 않도록 하자. 아래의 예처럼 말이다.

#define MIN(a,b) (((a)<(b))?(a):(b))


주석문을 붙여야 할 정도로 복잡한 매크로라면 /* */ 형태의 주석문을 사용하는 것이 좋다.
왜냐하면 // 형태의 주석문을 인식하지 않는 C 전처리자가 c++ 개발 도구에서도 쓰일 경우가 있기 때문이다.

const, inline, template, enum, namespace 등의 메커니즘은 전처리자에 의한 구시대적 프로그램 구성 기능을 대신하기 위해서 나온 것들이다.

6. 주건부 컴파일
#ifdef identifier 형태의 지시자를 사용하면 조건에 따라 #endif 지시자 이전의 모든 텍스트를 무시해 버릴 수 있다. 예제로 확인하자.
int f (int a
#ifdef arg_two
,int b
#endif
);

위의 코드는 다음과 같이 바뀐다.
int f(int a
);

arg_two 라는 매크로기- #ifdef에 의해 미리 정의되어 있지 않으면, 컴파일러가 위와 같이 해석한다.
Full transcript