C에서는 동일한 이름의 다른 내용의 함수를 정의할 수 없다. 그러나 C++에서는 매개변수의 선언만 다르게 해주면 같은 이름의 함수를 정의 할 수 있다. C에서는 호출할 함수를 찾을 때 함수의 이름만을 이용하여 찾지만, C++에서는 호출할 함수를 찾을 때 함수의 이름과 매개변수 선언의 두가지 정보를 동시에 이용하여 찾기 때문이다. 함수 오버로딩의 예시는 다음과 같다.
#include <iostream>
void MyFunc(void)
{
std::cout << "MyFunc(void) called" << std::endl;
}
void MyFunc(char c)
{
std::cout << "MyFunc(char c) called" << std::endl;
}
void MyFunc(int a, int b)
{
std::cout << "MyFunc(int a, int b) called" << std::endl;
}
int main(void)
{
MyFunc();
MyFunc('A');
MyFunc(12, 13);
return 0;
}
C에서는 malloc과 free함수를 이용하여 힙 상에서 메모리의 할당과 해제를 지원하였다. C++에서 메모리의 할당과 해제를 지원하는 것은 new와 delete다. new로 힙 상에 메모리를 할당하고, delete로 할당한 메모리를 해제한다.
#include <iostream>
int main()
{
int* p = new int;
*p = 10;
std::cout << *p << std::endl;
delete p;
return 0;
}
다음과 같이 new로 배열을 할당할 수 있다.
#include <iostream>
int main()
{
int arr_size;
std::cout << "array size : ";
std::cin >> arr_size;
int *list = new int[arr_size];
for(int i = 0; i < arr_size; i++)
std::cin >> list[i];
for(int i = 0; i < arr_size; i++)
std::cout << i << "th element of list : " << list[i] << std::endl;
delete[] list;
return 0;
}
배열을 할당할 때는 자료형과 [] 안에 배열의 크기를 넣어주면 된다. 배열로 할당된 메모리를 해제할 때는 delete[]처럼 delete 뒤에 []을 붙여주면 된다.
C++에서는 포인터 말고도 레퍼런스(참조자)라는 방식으로 다른 변수나 상수를 가리킬 수 있다.
#include <iostream>
int main()
{
int a = 3;
int& another_a = a; // 참조자 사용
another_a = 5;
std::cout << "a : " << a << std::endl;
std::cout << "another_a : " << another_a << std::endl;
return 0;
}
위 코드에서는 a 의 참조자 another_a를 정의하였는데, 참조자는 가리키고자 하는 타입 뒤에 &를 붙이면 된다.
참조자를 선언한다는 것은 another_a는 a의 또 다른 이름이라고 컴파일러에게 알려주는 것이다. 따라서 위 코드에는 another_a에 5를 대입하였지만 a의 값이 5로 바뀐 것을 확인할 수 있다.
레퍼런스(참조자)는 포인터와 비슷하지만 몇가지 차이점이 있다. 레퍼런스는 반드시 정의할 때 어떤 변수나 상수의 별명이 될 것인지 지정해야 한다. int& another_a; 와 같은 형식의 코드는 불가능하다. 또한 레퍼런스가 한 번 별명이 되면 더 이상 다른 변수를 참조 할 수 없다.
레퍼런스는 메모리 상에 존재하지 않을 수도 있다. 위 코드의 경우 another_a 변수는 메모리 상의 공간을 차지하지 않고 another_a가 쓰이는 자리를 모두 a로 바꾸면 되기 때문이다.
#include <iostream>
int change_var(int& p)
{
p = 3;
return 0;
}
int main()
{
int num = 5;
std::cout << num << std::endl;
change_var(num);
std::cout << num << std::endl;
return 0;
}
위 코드에서는 함수의 인자로 레퍼런스를 받고 있다. 위 설명에서 int& p; 와 같은 코드는 사용할 수 없다고 했지만 함수의 인자로 사용하게 되면 int& p = num;의 의미가 되어 상관없다. 또한 인자로 변수를 넣을 때에도 포인터와 다르게 &num이 아닌 num을 넣으면 된다. 따라서 change_var()함수의 p=3; 이라는 코드는 num = 3; 이라는 코드와 동일하다.
역참조 연산자로 메모리에 접근하여 값을 저장할 수도 있다. 위 num1 변수에는 10이 들어있었는데 num1을 가리키는 포인터변수 numPtr에 역참조 연산자로 20을 저장했기 때문에 num1 과 numPtr을 출력하면 둘 다 20이 나온다. 만약 포인터 변수 numPtr에 num1을 할당하면 간접 참조 수준이 다르다는 경고가 발생한다. numPtr과 num1이 각각 포인터와 int로 자료형이 다르기 때문이다. 컴파일 경고가 발생하지 않도록 하려면 numPtr앞에 * 을 붙여 값을 가져오게 하여 int와 동일한 자료형으로 만들어야 한다. 값을 가져오는것은 자료형을 동일하게 한다는 것이다. 주소연산자 &도 마찬가지로 자료형을 맞춰주는 역할을 한다. 변수, 주소 연산자, 역참조 연산자, 포인터의 차이는 다음과 같다.
모니터에 하나의 문자를 출력할 때 일반적으로 사용하는 함수는 putchar() 과 fputc()가 있다.
#include <stdio.h>
int putchar(int c);
int fputc(int c, FILE * stream);
// 함수 호출 성공시 쓰여진 문자 정보, 실패시 EOF반환
putchar() 함수는 인자로 전달된 문자정보를 stdout 표준 출력 스트림으로 전송하여 모니터로 출력하는 함수이다.
fputc()함수는 문자를 전송한다는 측면은 putchar()함수와 동일하지만 문자를 전송할 스트림을 지정할 수 있어 파일을 대상으로도 데이터를 저장할 수 있다. fputc() 함수의 두 번째 매개변수 stream은 문자를 출력할 스트림을 지정하는데 사용된다.
문자 입력 함수
키보드로 부터 하나의 문자을 입력 받을 때 일반적으로 사용하는 함수는 getchar() 과 fgetc()가 있다.
#include <stdio.h>
int getchar(void)
int fgetc(FILE * stream);
// 파일의 끝에 도달하거나 함수 호출 실패 시 EOF반환
getchar() 함수는 stdin으로 표현되는 표준 입력 스트림으로부터 하나의 문자를 입력받아 반환하는 함수이다.
fgetc() 함수는 문자를 입력받을 스트림을 지정할 수 있다.
#include <stdio.h>
int main(void)
{
int ch1, ch2;
ch1 = getchar(); // 문자 입력
ch2 = fgetc(stdin); // 엔터키 입력
putchar(ch1); // 문자 출력
fputc(ch2, stdout); // 엔터키 출력
return 0;
}
위 코드에서 문자를 int 형 변수로 선언한 이유는 getchar() 함수와 fgetc()함수의 반환형이 int 이기 때문이다.
문자 입출력에서의 EOF
EOF는 End Of File의 약자로 파일의 끝을 표현하기위해 정의된 상수이다. 따라서 EOF가 반환되면 파일의 끝에 도달하여 더 이상 읽을 내용이 없음을 의미한다. 키보드를 대상으로 하느 fgetc() 와 getchar() 함수는 다음의 경우중 하나가 만족되었을 때 EOF를 반환한다.
함수 호출 실패
Windows에서 CTRL + Z, Linux에서 CTRL + D 키가 입력되었을 경우
#include <stdio.h>
int main(void)
{
int ch;
while (1)
{
ch = getchar();
if(ch == EOF)
break;
putchar(ch);
}
return 0;
}
위 코드를 실행하면 문자 입출력이 반복되고 EOF를 반환하는 CTRL + D 키를 입력하면 프로그램이 종료된다.
위 함수들의 반환형이 int 이고, int형 변수에 문자를 담는 이유는 EOF 는 -1로 정의된 상수인데, 반환형이 char 이라면 처리하는 컴파일러에 따라 char을 unsigned char로 처리하는 경우가 있어 -1을 양의 정수로 형변환 하는 경우가 발생 할 수 있다. 그래서 어떤 컴파일러라도 -1 을 유지하기 위해 반환형을 int 로 정의해둔것이고, 반환형이 int 이기 때문에 int 형 변수에 값을 저장해야 한다.
프로그램 실행시 main함수로 인자를 전달할 수 있고, main 함수도 인자를 전달받을 수 있도록 한 것이다.
#include <stdio.h>
int main(int argc, char * argv[])
{
int i = 0;
printf("전달된 문자열의 수 : %d\n", argc);
for(i = 0; i < argc; i++)
printf("%d 번째 문자열 : %s\n", i+1, argv[i]);
return 0;
}
/*
input :
# ./argcargv I love you
output:
전달된 문자열의 수 : 4
1 번째 문자열 : ./argcargv
2 번째 문자열 : I
3 번째 문자열 : love
4 번째 문자열 : you
*/
위 코드와 같이 main함수를 구성하면 프로그램을 실행할 때 인자를 전달 할 수 있다. argv는 char형 더블 포인터 변수이다.
인자 전달 과정에서 공백은 문자열을 나누는 기준이 된다. 인자로 전달된 문자열들이 배열에 묶여 main 함수의 두 번째 인자로 전달이되고, 첫 번째 인자는 문자열의 수가 전달이 된다.
#include <stdio.h>
int main(int argc, char * argv[])
{
int i = 0;
printf("전달된 문자열의 수 : %d\n", argc);
while(argv[i] != NULL)
{
printf("%d 번째 문자열 : %s\n", i+1, argv[i]);
i++;
}
return 0;
}
/*
input :
# ./argvNull "I Love You"
output :
전달된 문자열의 수 : 2
1 번째 문자열 : ./argvNull
2 번째 문자열 : I Love You
*/
위 코드를 통해 전달받은 배열의 마지막에 NULL이 삽입됨을 확인할 수 있다. 또한 큰 따옴표로 묶으면 공백을 포함하는 문자열을 인자로 전달할 수 있다.