프로그래밍 언어에는 reserved keywords가 있다. 프로그래머가 변수명이나 함수명으로 사용할 수 없도록 미리 예약을 해둔 것들로 int, double, main과 같은 기본적인 단어들이 keyword가 된다.
이처럼 이미 예약된 키워드들은 변수명으로 사용할 수 없도록 컴파일 이전부터 에러임을 알려준다.
1.4.2 식별자(identifier)
식별자(identifier)란 변수의 이름, 함수의 이름, 객체의 이름처럼 식별할 수 있게 만들어준 것을 의미한다. 정확하게는 메모리 주소를 식별하기 위한 것으로 프로그래머가 해당 식별자를 통해 메모리를 인식할 수 있게 된다. 만약 int 자료형에 대한 초기화가 이루어지면 해당 변수가 4 bytes 크기의 메모리를 지닐 수 있음을 우리는 알 수 있게 된다. 실무에서 그렇게 많이 사용하는 단어가 아니므로 중요하게 알고 있을 필요는 없다.
1.4.3 변수명을 짓는 방법
변수명을 짓는 방법은 이전부터 여러가지 방법이 있었는데, 정확한 답은 없다. 한가지 예시를 보자.
위처럼 많은 방법이 있는데, 혼자 작업할 경우 변수명은 자신이 알아보기 쉽도록 자신만의 규칙으로 짓는 것이다. 여러명이 협업하는 경우에도 크게 고민할 필요가 없다. 어느 기업에 취직을 해서 해당 기업의 프로그램을 보면 그 기업에서 관습적으로 사용하는 변수명에 대한 패턴이 있다. 그 패턴을 따르면 되겠다.
위처럼 상황에 맞게 변수명을 짓는 것과는 다르게 대부분의 프로그래머들이 관습적으로 지키는 것들이 몇가지 있다. 그러한 것들만 관습으로 지키도록 하자.
띄어쓰기 대신 under bar를 쓴다. 대문자는 변수명에서 되도록 지양하며, 매크로에서 많이 쓴다. 함수명은 일반적으로 동사명사 구조로 작성하며, 이를 어기고 명사만으로 지을 수도 있다.
큰 프로그램을 만들다 보면 같은 계산을 반복하는 경우가 생길 수 있다. 이러한 계산이 만약 수정될 부분이 생겼다면 어떻게 해야 할까? 함수로 작성하지 않았다면 모든 부분을 찾아가서 하나하나 직접 수정해야 할 것이다. 하지만 수정 작업은 사람이라면 한두 개씩 빼먹고 수정을 하지 않는 경우도 생길 것이며, 이러한 실수는 치명적일 수 있다.
같은 작업을 하는 영역이 있다면 이를 하나로 묶어서 함수로 표현하면 좋다. 하나의 함수를 만들어서 이 함수 계산이 추후에 쓰인다면 이 함수를 호출해서 사용하면 그만이고 수정 작업이 필요할 때는 함수가 선언된 부분만 수정하면 될 것이다. 이렇게 다시 사용할 수 있도록 해주는 것을 우리는 재사용성이라고 부른다.
#include <iostream>
using namespace std;
int multiplyTwoNumbers(int num_a, int num_b)
{
int mul = num_a * num_b;
return mul;
}
int main()
{
cout << multiplyTwoNumbers(7, 2) << endl;
cout << multiplyTwoNumbers(3, 4) << endl;
cout << multiplyTwoNumbers(8, 13) << endl;//함수 재사용이 가능하다
return 0;
}
곱셈을 수행하는 함수이다. 이렇게 곱셈을 여러번 수행하고 싶다면 함수로 만들어서 프로그램을 구현할 수 있다. 또한 곱셈이 아닌 덧셈으로 수행하고자 하는 경우 함수 내부에 계산되는 부분만 수정하면 되겠다.
위처럼 함수를 마우스 오른쪽 클릭과 Rename으로 진입하면 함수 자체 이름과 해당 함수를 호출하는 부분에서의 이름까지 한 번에 수정할 수 있다.
1.3.2 반환값(Return Values)
반환값이반환 값이 있느냐에 따라서 함수의 표현이 달라진다. multiplyTwoNumbers의 경우 int 자료형을 반환해주기 때문에 함수명 앞에 int가 붙어 있고 printHelloWorld의 경우엔 반환 값이 없기 때문에 void를 사용하였다.
반환 값 여부에 대해서는 함수명 앞에 표기해줄 수 있지만 return 명령어를 사용할 수 없다는 것은 아니다. 두 번째 함수의 경우 return 명령어를 통해 Hello World2 출력을 못하도록 막았다. return에 도달하면서 해당 함수를 호출한 함수로 다시 넘어갔기 때문이다.
참고로 반환값이 void인 함수를 다른 자료형 메모리에 대입하는 형태로 코드를 구현할 경우 컴파일이 되지 않는다.
1.3.3 매개변수(Parameters)와 인자(Arguments)
많이 혼동하는 개념 중 하나가 매개변수와 인자라고 생각된다.
여기서 주의 깊게 볼 것은 addTwoNumbers 함수이다. main 함수 내부에서 addTwoNumbers(1, 2)로 호출을 진행하는데, 여기에 입력된 1과 2가 인자가 된다. 즉, 함수 호출 시에 입력으로 넣는 값이 인자이다. 함수 정의가 되어 있는 부분을 살펴보자. num_a와 num_b가 존재하는데, 프로그램을 실행해보면 알겠지만 1과 2를 받아서 덧셈을 수행하고 3을 반환한다. 즉, num_a와 num_b가 1과 2를 각각 받아서 사용했다는 의미인데, 여기 있는 num_a와 num_b를 매개변수라고 한다. 인자와 다를 것이 없다고 생각할 수 있지만, 인자는 값으로 함수가 받아오며, 이를 저장하고 사용할 공간이 따로 필요하다. 이러한 공간이 매개변수가 되며, 결과적으로 인자로 들어온 값을 복사해서 매개변수에 저장한 뒤 함수가 수행된다. 즉, 매개변수는 함수의 인자를 저장하는 변수가 되겠다.
1.3.4 디버깅(debugging)
앞에서 언급한 인자와 매개변수의 동작 방식에 대해서 알기 위해 디버깅을 수행할 필요가 있다.
visual studio 상단에 debug 모드로 실행한 뒤에 함수 호출 부분에 디버깅을 위한 표식을 남기자. 코드 줄 숫자 표시 옆 회색 부분을 클릭하면 위의 사진처럼 디버깅을 위한 점이 생긴다. 그리고 F5를 클릭하여 디버깅을 시작해보자.
디버깅을 시작하고서 아래 Locals 파트를 보자. sum이라는 변수가 아직 값이 제대로 저장되어 있지 않아서 의미 없는 쓰레기 값이 저장된 것을 볼 수가 있다. F11을 눌러서 함수 내부 동작을 들여다보자.
함수 내부로 들어왔다. Locals를 확인해보면 num_a와 num_b가 쓰레기 값으로 들어 있는 것을 알 수가 있다. 분명 인자로 7과 2를 주었는데, 다른 값이 저장되어 있다. 즉, 매개변수는 현재 생성되어 있기만 하고 인자에 들어 있는 값을 복사받지 못했다는 의미가 된다. 이렇게 매개변수와 인자는 다르다는 것을 알 수가 있다. F11을 계속 눌러주면서 해당 값들이 어떻게 변하는지 확인하면 좋은 공부가 된다.
변수(variables)는 가리키게 되는 메모리 공간의 또 다른 이름이라고 볼 수 있다. 조금은 어려운 정의로 언급하였지만 C++처럼 메모리 공간에 굉장히 가깝게 프로그램을 작성하는 언어에 한정해서는 이렇게 이해하는게 좋다. 코드를 먼저 보고 등장하는 것들에 대해 설명을 이어가도록 하겠다.
#include <iostream>
int main()
{
int x = 123;
int y;
y = 123; // assignment operator 대입 연산자
std::cout << y << std::endl;
std::cout << &y << std::endl; // y의 메모리 주소가 출력된다.
return 0;
}
변수 x에 대해서는 선언과 동시에 초기화를 해준다. y는 선언과 할당이 이루어지고, 대입 연산자(assignment operator)를 통해 값을 넣어준다.
int y;
y = 123; // assignment operator 대입 연산자
대입 연산자의 정확한 의미는 다음과 같다. y라는 변수 이름이 가리키고 있는 메모리 공간에 123이라는 정수를 저장해둔다.
&(ampersand) 연산자를 통해서 현재 y가 어느 메모리 공간에 저장되어 있는지 주소를 볼 수 있다. x86과 x64에 따라서 주소의 길이는 다르게 표현된다.
x = x + 2;
l-value와 r-value에 대한 정의를 간단히 이야기 하면 다음과 같다. l-value는 메모리 주소를 가지고 있다.(대입 연산자 기준 왼쪽의 x) r-value는 임시적으로 저장한 뒤 사라진다.(대입 연산자 기준 오른쪽의 x + 2) x = x + 2;라고 할지라도 오른쪽의 x는 임시적으로 다른 메모리 공간에 x에 담긴 값을 가져온 뒤 2를 더하는 연산이 수행되기 때문에 l-value가 아닌 r-value이다.
1.2.2. 초기화의 중요성 및 대입 연산자와의 차이점
추후 생성자 및 소멸자 개념처럼 객체 지향에 대한 개념으로 들어가게 되었을 때, 초기화와 대입을 구분하는 것은 굉장히 중요해진다. 이후 포스팅에서도 언급하겠지만 미리 한번 정리해보도록 하자.
#include <iostream>
int main()
{
int x; // error C4700: uninitialized local variable 'x' used
int y = 123; // initialization
//int y(123); 효율적인 차이는 없다.
int z;
z = 5; // assignment
std::cout << x << std::endl;
return 0;
}
y는 초기화를 해준 것이다. z는 쓰레기 값이 들어가 있는 상태로 할당이 된 뒤에 5라는 값이 대입이 되었다. 이처럼 선언과 동시에 의도한 값이 들어가는지에 대한 여부로 초기화와 대입을 구분할 수 있겠다.
1.2.3. 초기화와 관련된 debug 모드와 release 모드의 치명적인 차이점
debug 모드에서는 앞에서 소개한 코드를 빌드할 경우 위와 같이 x가 초기화되지 않았다는 경고 메시지가 뜬다. 무시하고서 프로그램을 실행시 runtime error가 뜬다.
release 모드에서는 실행시 자동으로 초기화시켜주는데 기본적인 값을 넣어준다. 여기서 기본적인 값은 어느정도 규칙은 있겠으나 큰 프로그램을 다루는 경우 개발자의 의도와는 다른 값이 들어가서 방해를 할 수도 있다.
변수는 항상 초기화를 해주자!!
이전 프로그램이 해당 메모리 주소를 사용하다가 쓰레기 값을 넣어두고 가버리기 때문에 항상 초기화로 바로 잡아야한다.
1.2.4. iostream 입출력 스트림의 사용
프로그램을 만들면서 가장 많이 사용하는 것이 바로 입출력 스트림이다. 다음의 코드와 함께 기본적인 용어를 정리해보자!
#include <iostream>
#include <cstdio>
int main()
{
using namespace std;
int x = 1024;
double pi = 3.141592;
std::cout << "I love this lecture!\n"; // << std::endl;
// <<라는 스트림을 타고 cout으로 들어간다.
std::cout << "x is " << x << " pi is " << pi << std::endl;
// pi는 일부가 잘린다. 출력 정밀도 조정이 필요하다!
std::cout << "abc" << "\t" << "def" << std::endl;
std::cout << "ab" << "\t" << "cdef" << std::endl;
// \t는 탭을 의미한다. \t은 빈공간을 주면서 줄맞춤까지 하고 싶을 때 사용하면 좋다.
// \t는 하나의 기능으로 작동하며 두 글자가 아니다.
cout << "\a"; // audio의 약자. 소리를 출력해준다.
//printf("I love this lecture!\n");
int z = 1;
cout << "Before your input, z was " << z << endl;
// z로 input이 흘러들어가는 형태
cin >> z; //100000000000000000000000과 같은 큰 수를 넣는 경우를 생각하면 입력 유효성 검증이 필요
cout << "Your input is " << z << endl;
return 0;
}
#include <iostream>를 통해 입출력 스트림을 사용할 수가 있다. stream은 흐르는 느낌의 단어인데, 입력과 출력을 사용해보면 마치 그런 느낌이 들도록 구현이 되어 있다. iostream은 input output stream을 축약해서 사용한 것이다.
입출력 스트림을 사용하면 기본적으로 아래의 3가지를 많이 다룬다.
std::cin은 console in을 줄인 명령어로 무언가를 입력할 때 사용한다. std::cout은 console out을 줄인 명령어로 무언가를 출력할 때 사용한다. std::endl은 end line을 의미하는 명령어로 줄바꿈을 수행한다.
1.2.5. namespace
C++를 시작하면서 처음 접하게 되는 개념이 namespace였다. namespace는 이름공간, 명칭공간이라고도 하며, 입출력 스트림을 사용할 때마다 앞에 붙여준 std::가 바로 namespace이다. namespace가 다르다면 이름이 같은 함수라도 다른 함수로 인식할 수 있다. cout, cin, endl은 std라는 namespace 안에 정의되어 있다.
using namespace std;
이 명령문이 입력된 코드 영역에서는 이제 std::을 생략해도 된다. 컴파일러가 cout이나 endl을 만났을 때, std라는 namespace 안에서 컴파일러가 직접 찾고 컴파일을 해준다.
프로그램을 실행시킨다는 것은 운영체제(Operating System, OS)에게 요청을 하는 것이다. 내가 작성한 코드가 컴파일되어 실행 파일이 되고 그 파일을 다루는 것은 OS가 된다. 아래의 코드에 나오는 것들을 간단간단하게 알아보자. 추후에 계속 다룰 용어들이니 용어에 친숙해지는 것을 목표로 가볍게 보면 좋겠다.
#include <iostream>
int main(void)
{
int x = 2;
x = 5;
int y = x + 3;
std::cout << y << std::endl;
int sight = 10;
// 마법의 물약을 먹어서 시야 거리가 0
sight = 0;
return 0;
}
#include와 같이 앞에 #이 붙은 것들을 전처리기(preprocessor directive)라고 한다.
코드 상에서 빈칸과 빈 줄은 컴파일러가 무시를 한다. 그러니 사람이 보기에 가독성이 좋도록 여백을 배치하는 것이 좋다. 주석 또한 컴파일러가 무시한다.
int는 출력 부분에 해당하며, 여기서는 정수의 약자인 int를 사용하였다. int main(void), 즉, main 함수는 중요하다. OS는 프로그램을 받았을 때 main 함수를 찾는다. main 함수의 몸체는 중괄호로 묶인다.
세미 콜론(;)으로 완전한 문장, statement가 된다. 기능을 수행하는 줄을 statement라고 하며, statement는 관습적으로 명령문이라고 말한다.
변수, int x = 2;의 경우 정수형의 데이터를 받을 수 있는 메모리 공간을 할당받는 것이다
std namespace 이름이 겹칠 수 있는 함수나 변수를 구분하기 위한 공간
return 0; 명령문을 통해 OS에게 출력 값을 반환한다.
error C2143: syntax error: missing ';' before '}' 에러가 있다면 항상 구글에 검색해보자.
1.1.2 주석 잘 다는 법
요즘은 코드 자체를 문서처럼 다룬다. 그렇기에 코드 자체에서 주석(Comments)을 잘 다는 습관을 지니는 것이 좋다. 주석은 내가 왜 이 프로그램을 만드는지, 무엇을 하고 있는지 기록하는 것이다.
// 명령문 위에 주석 달기 명령문의 세미콜론 옆에 주석을 달면 한 페이지를 넘어가는 경우가 많다. 명령문의 위에 적절한 길이로 주석을 달아서 가독성을 높여보자! 또한 명령문을 보고서 직관적으로 알 수 있는 내용은 주석으로 달지 않는다.
/*주석 단축키*/
visual studio 상단 중앙에 주석을 달 수 있는 버튼이 있다. 주로 단축키를 많이 사용하니 외워두도록 하자!
1.1.3 기본적인 서식 맞추기
코드를 작성하는 것에 있어서 모두가 따라야 하는 규칙은 없고 사람이 보기 편하면 된다. 많은 사람이 각자의 규칙이 있겠지만 협업한다면 적절하게 타협하여 같은 규칙을 가져야 협업하기가 좋다.
일반적으로 컴파일러는 리터럴이 아닌 빈칸과 빈 줄은 무시하기 때문에 사람이 보기 편하도록 빈칸과 빈줄을 넣어주면 되겠다.
#include <iostream>
using namespace std;
// 여러가지 함수 작성 서식
int add(int x, int y) { return x + y; }
int add(int x, int y) {
return x + y; }
int add(int x, int y) {
return x + y;
}
int add(int x, int y)
{
return x + y;
}
int main()
{
// 이처럼 줄을 바꾸더라도 컴파일러는 무시한다.
// string 안의 빈칸은 컴파일러가 중요시 여긴다.
cout << "Hello, World" << // 줄바꿈을 행했다면 operator를 뒤에 남겨두는 것이 좋다.
" Hello Home" << endl;
// 앞의 빈칸(tab)을 만드는 것을 indenting이라 한다.
// 프로그래머가 반드시 해줘야하는 부분.
// 변수 대입 연산자 줄맞춤
int x = 1;
int my_value = 123;
}
위처럼 함수 작성의 서식은 여러가지가 있는데 하나로 통일하는 것이 협업에 좋다. 정답은 없으니 같이 일하는 사람들과 정하자.
프로그래머가 반드시 지켜줘야 하는 것 중 하나가 바로 indenting인데, 같은 블럭에 있는 코드 줄은 같은 수준의 들여 쓰기를 써야 한다는 점이다. 이를 안 지키는 것은 프로그래머의 관습을 무시하는 것이므로 반드시 지켜주자! 특히 파이썬에서는 이 indenting으로 코드 블럭을 구분하기 때문에 특히 주의해야 한다.