#include <iostream>
using namespace std;
int main()
{
// Assignment operator =
int x = 2; // x is a variable, 2 is a literal.
cout << 1 + 2 << endl; // 1 and 2 are literal.
// Unary operator -
cout << -x << endl;
// Binary operator +
cout << 1 + 2 << endl;
// Ternary operator ? :
int y = (x > 0) ? 1 : 2;
cout << y << endl;
return 0;
}
위의 코드에서 x를 2로 초기화하는데, 여기서 입력하게 되는 2를 리터럴이라고 한다. 저 2라는 값에 3이라는 값을 넣을 방법이 없는 것처럼 바꿀 수 없는 것을 리터럴이라고 한다.
1.6.2 연산자(Operator)와 피연산자(Operand)
연산자는 우리가 평소에 다루던 +, -, *를 의미하고 피연산자는 연산자가 행하는 연산 수행에 사용되는 것들을 의미한다. 연산자는 단항 연산자(unary operator), 이항 연산자(binary operator), 삼항 연산자(ternary operator)가 있다.
-의 경우 숫자 앞에 쓰이므로 하나의 피연산자로 행해지는 연산이다. 이처럼 피연산자가 하나만 사용될 경우 단항 연산자라 한다. +와 *는 연산자의 양쪽에 피연산자가 필요하므로 이항 연산자가 된다.
C++에서 정의된 삼항 연산자는 조건문을 비교하여 그 결과에 따라 값을 선택하는 연산을 수행한다. 위의 코드에서는 x가 0보다 큰지에 대한 판단을 하고 참이면 1을 거짓이면 2를 반환하도록 구현되어 있다.
변수가 생성되고 없어지는 것엔 영역이 존재한다. 메모리에 할당된 지역 변수는 할당이 이루어진 곳을 묶고 있는 중괄호가 끝나는 지점까지만 존재한다. 이러한 영역을 지역 영역(local scope)이라고 한다. 다음의 예제를 보자.
#include <iostream>
using namespace std;
int main()
{
// 구분을 하기 위한 식별자인데 같으면 문제가 생김
int x = 0;
//int x = 1;
cout << x << " " << &x << endl;
{
// 앞의 x와 다르다.
// 다른 메모리에 대한 식별자 공간 자체가 다르다.
//int x = 0;
x = 1;
cout << x << " " << &x << endl;
}
cout << x << " " << &x << endl;
{
int x = 2;
cout << x << " " << &x << endl;
}
return 0;
}
우선 같은 중괄호에 같은 이름(같은 식별자)으로 생성된 지역 변수를 보자.
redefinition
같은 중괄호에 같은 식별자의 변수는 존재할 수 없다. 애초에 식별자가 식별을 위해 존재하는 것이기 때문에 다른 메모리에 같은 식별자로 두는 것은 허용되지 않는다.
다른 중괄호에 묶인 경우 같은 식별자의 변수를 사용할 수 있다. 이것이 가능한 이유가 지역 범위가 있기 때문이다.
local variables and local scope
메모리 주소를 출력한 결과 전부 다르다는 것을 알 수 있다. 첫번째와 세번째 출력 결과가 같은 이유는 같은 지역 변수의 메모리 주소를 출력하기 때문이다.
이번에는 새로 지역 변수를 할당하는 것이 아닌 값만 바꾸는 행위를 하는 코드의 결과를 보자.
x 값 변경
첫 중괄호에서 새로운 지역 변수 x를 할당하는 것이 아닌 x를 1로 만들고서 메모리 주소를 출력하기만 했다. 메모리 주소는 변하지 않았고 기존의 x의 값만 바꾼 것을 알 수가 있다.
지역 변수는 영역을 벗어나면 사용할 수 없게 된다. 지역 변수가 차지하고 있던 메모리는 그 지역 변수가 영역을 벗어날 때 "스택(stack)" 메모리로 반납된다. 반납된 메모리는 다음 지역 변수가 사용할 수 있도록 대기한다.
1.5.2 지역 변수로 보는 매개변수와 인자의 차이
지역 변수 예제를 통해 매개변수와 인자의 차이를 알 수가 있다. 다음 코드의 결과를 살펴보자.
#include <iostream>
using namespace std;
void doSomething(int x)
{
x = 123;
cout << x << "\t" << &x << endl; // #2
}
int main()
{
int x = 0;
cout << x << "\t" << &x << endl; // #1
doSomething(x);
cout << x << "\t" << &x << endl; // #3
return 0;
}
arguments and parameters
main 함수에서 생성한 x를 인자로 사용하는 doSometing 함수이다. 인자의 주소는 끝이 A54이지만 doSomething 함수에서 사용한 x의 메모리 주소는 A30으로 끝난다. 이를 통해 인자의 메모리 주소와 매개변수의 메모리 주소는 다르다는 것을 알 수 있다. 그렇기 때문에 x의 값을 123으로 변경했어도 main 함수에서 할당한 x의 값은 변하지 않음을 알 수 있다.
프로그래밍 언어에는 reserved keywords가 있다. 프로그래머가 변수명이나 함수명으로 사용할 수 없도록 미리 예약을 해둔 것들로 int, double, main과 같은 기본적인 단어들이 keyword가 된다.
int double
이처럼 이미 예약된 키워드들은 변수명으로 사용할 수 없도록 컴파일 이전부터 에러임을 알려준다.
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;
}
곱셈을 수행하는 함수이다. 이렇게 곱셈을 여러번 수행하고 싶다면 함수로 만들어서 프로그램을 구현할 수 있다. 또한 곱셈이 아닌 덧셈으로 수행하고자 하는 경우 함수 내부에 계산되는 부분만 수정하면 되겠다.
Right click -> Rename
위처럼 함수를 마우스 오른쪽 클릭과 Rename으로 진입하면 함수 자체 이름과 해당 함수를 호출하는 부분에서의 이름까지 한 번에 수정할 수 있다.
1.3.2 반환값(Return Values)
return values
반환값이반환 값이 있느냐에 따라서 함수의 표현이 달라진다. multiplyTwoNumbers의 경우 int 자료형을 반환해주기 때문에 함수명 앞에 int가 붙어 있고 printHelloWorld의 경우엔 반환 값이 없기 때문에 void를 사용하였다.
반환 값 여부에 대해서는 함수명 앞에 표기해줄 수 있지만 return 명령어를 사용할 수 없다는 것은 아니다. 두 번째 함수의 경우 return 명령어를 통해 Hello World2 출력을 못하도록 막았다. return에 도달하면서 해당 함수를 호출한 함수로 다시 넘어갔기 때문이다.
참고로 반환값이 void인 함수를 다른 자료형 메모리에 대입하는 형태로 코드를 구현할 경우 컴파일이 되지 않는다.
1.3.3 매개변수(Parameters)와 인자(Arguments)
많이 혼동하는 개념 중 하나가 매개변수와 인자라고 생각된다.
parameters and 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)
앞에서 언급한 인자와 매개변수의 동작 방식에 대해서 알기 위해 디버깅을 수행할 필요가 있다.
debug mode
visual studio 상단에 debug 모드로 실행한 뒤에 함수 호출 부분에 디버깅을 위한 표식을 남기자. 코드 줄 숫자 표시 옆 회색 부분을 클릭하면 위의 사진처럼 디버깅을 위한 점이 생긴다. 그리고 F5를 클릭하여 디버깅을 시작해보자.
start debug mode
디버깅을 시작하고서 아래 Locals 파트를 보자. sum이라는 변수가 아직 값이 제대로 저장되어 있지 않아서 의미 없는 쓰레기 값이 저장된 것을 볼 수가 있다. F11을 눌러서 함수 내부 동작을 들여다보자.
F11을 통한 함수 내부 동작 확인
함수 내부로 들어왔다. Locals를 확인해보면 num_a와 num_b가 쓰레기 값으로 들어 있는 것을 알 수가 있다. 분명 인자로 7과 2를 주었는데, 다른 값이 저장되어 있다. 즉, 매개변수는 현재 생성되어 있기만 하고 인자에 들어 있는 값을 복사받지 못했다는 의미가 된다. 이렇게 매개변수와 인자는 다르다는 것을 알 수가 있다. F11을 계속 눌러주면서 해당 값들이 어떻게 변하는지 확인하면 좋은 공부가 된다.