배움 저장소
[홍정모의 따라하며 배우는 C++] 1.C++의 기초적인 사용법 본문
1.1 프로그램의 구조
프로그램이 실행되는 원리
(1) 사용자가 운영체제에게 프로그램 사용을 요청한다. 운영체제는 CPU(연산)와 RAM(저장) 자원을 할당하여 프로그램
데이터를 가져와 실행시킨다.
(2) OS는 프로그램 시작 시 main( ) 함수를 찾아 실행한다.
Statement와 Expression
- statement(명령문) : semicolon( ; )이 들어간 문장으로 컴퓨터가 명령을 수행하는 단위이다.
- expression(표현식): 값을 계산하기위해 +,- 와 같이 연산자와 숫자로 구성된 기호들로 결과 값이 존재한다.
저장공간 할당받기
- 아래 코드를 통해 int 자료형의 저장공간을 할당받았다.
int x;
1.2 주석 comments
1.3 변수와의 첫 만남
Object(객체) : 컴퓨터는 존재 여부를 메모리 저장 유무로 구분한다.
variables(변수) : 저장공간을 구분하고 불러오는 단위로 Identifier(식별자)로 등록해 사용한다.
L-Value와 R-Value : 메모리에 접근하여 값을 저장할 수 있다면 L-Value 아니면 R-Value이다.
int x = 2; // copy initialization : declare x and copy value 2 on x addresses
int y(2); // direct initialization
int z{2}; // uniform initalization
x = x + 2; // assignment : copy value x + 2 on addresses
첫 번째 줄에서 x라고 부를 수 있는 Identifier(식별자) 주소에 int 자료형 크기만큼 저장공간 를 부여한다. 여기에 값 2를 저장한다. 이를 assignment라고 한다. 이 때 x는 L-Value이다.
다섯 번째 줄에서 왼쪽에 있는 x는 L-Value이고 오른쪽에 있는 x는 R-Value이다. 오른쪽 x는 x에 있는 값을 가져와 복사/붙여넣기 한 값으로 R-Value이다.
1.4 입출력 스트림과의 첫 만남 cin, cout
- cout은 control out의 줄임말이다.
- printf에서 print format의 줄임말이다.
- < : angle bracket, carot
using namespace std
- 코드 using namespace std;를 주목해보자
- standard library는 충돌을 막기 위해 namespace std 내부에 정의되어 있다. 해당 namespace에 접근하기 위하여 resolution operator ( :: )를 사용해주어야 한다. 이 때 using keyword를 사용하면 resolution 없이도 사용가능하다.
- using namespace std;는 헤더파일 전역변수 영역에서 사용하지 말자. 헤더파일을 포함할 때마다 자동으로 해당코드가 실행된다. 함수 내부나 해당 객체 내부에 선언해주자. 다른 파일에서 #include 한 이후에도 using namesapce std 영향없이 안전하게 사용할 수 있다.
#include <iostream> // cout, cin, endl, ...
#include <cstdio> // printf
int main()
{
/* Output */
using namespace std;
cout << "Hello World" << endl;
//std::cout << "Hello World!" << std::endl;
cout << "\a"; // short form for "audio", you can listen some sound.
/* Input */
int x;
cin >> x;
// >> called input operator
cout << "Input value saved in x : " << x << endl;
return 0;
}
1.5 함수와의 첫 만남
함수가 실행되는 순서를 알아보자
(1) main( ) 함수에서 multiplyTwoNumbers 함수가 호출된다.
(2) call stack에 새로운 scope가 만들어진다. 이 scope 내부에서 아래가 실행된다.
(3) 함수의 파라미터 num_A와 num_B가 값 1과 5로 짝지어 초기화된다.
(4) expression, num_A * num_B 이 계산되고 반환된다.
(5) call stack에서 맨 위 scope가 pop되고 해당 함수가 호출된 위치에서 return 값을 받는다.
int multiplyTwoNumbers(int num_A, int num_B)
{
return num_A * num_B;
}
int main()
{
cout << multiplyTwoNumbers(1, 5) << endl;
}
while 반복문을 사용하여 입력값 받아보기
int multiplyTwoNumbers(int num_A, int num_B)
{
return num_A * num_B;
}
int main()
{
int A, B;
while (cin >> A >> B)
{
cout << A << " * " << B << "=" << multiplyTwoNumbers(A, B) << endl;
}
}
1.6 키워드와 식별자 이름짓기
Identifier(식별자)는 메모리 주소를 프로그래머가 기억하기 쉬운 이름으로 바꾸어준다. Macro 식별자를 지을 때 모든 문자를 대문자로 작성한다. 변수명을 지을 때 모두 대문자로 사용하지 말자.
Keyword : 변수명을 지을 때 아래 keyword를 피하자.
https://en.cppreference.com/w/cpp/keyword
C++ keywords - cppreference.com
This is a list of reserved keywords in C++. Since they are used by the language, these keywords are not available for re-definition or overloading. (1) — meaning changed or new meaning added in C++11. (2) — meaning changed in C++17. (3) — meaning cha
en.cppreference.com
1.7 지역 범위
아래 x는 다음과 같은 에러를 발생시킨다. error C2374: 'x': redefinition; multiple initialization
int main()
{
int x = 0;
int x = 1;
}
이 때 새로운 Scope(영역)을 만든 다음 그 안에 x를 Initialization(초기화) 해보자. { } 내부에 x를 초기화 하면 된다. 아래 코드는 에러가 없다. 새로운 영역을 만들면 영역 내에서 상위 영역에서 존재하는 식별자를 사용할 수 있다. 식별자는 동일하지만 다른 주소 값을 가진다.
int main()
{
int x = 0;
{
int x = 1;
}
}
이 때 main 내부에 정의된 영역{ }에서 정의된 중복된 식별자 x를 보자. 이 식별자 x는 해당 영역 외부에서 사용할 수 없다. 컴파일러는 동일한 식별자가 존재한다면 해당 영역에서 정의된 식별자를 사용한다. 이는 스택에서 가장 나중에 선언 되는 변수이기도 하다.
1.8 연산자와의 첫 만남
Literal(리터럴)
- 변하지 않는 값이다. 임의로 바꿀 수 없다. 값 자체로 자료형이 무엇인지 알 수 있다.
- 첫 줄 처럼 변수에 Literal 값을 assignment(대입) 할 수 있으나 두 번째 줄과 같이 Literal 값을 바꿀 수 없다.
int x = 5;
5 = 'A'
연산자-피연산자
ㄴ 단항 : -1의 ' - ' 이다. 피연산자가 1개이다
ㄴ 이항 : 1+2의 '+' 이다. 피연산자가 2개이다
ㄴ 삼항 : x = (2 > 0) ? 1 : 2 의 ?이다. 피연산자가 3개이다. Conditional Operator라고도 부른다.
int x = -2; // x is a variable, 2 is a literal.
// '-' is unary operator
char str[] = "Hello World!"; // str is a variable, "Hello World!" is string literal
1 + 2; // 1 and 2 are literal, (1+2) are expression
// operand (+) is binary operator
bool trenary = 1 < 2 ? true: false; // (condition) ? (exp) : (exp)
1.9 기본적인 서식 맞추기
- 컴파일러는 코드에 입력된 Spacebar, Tap을 모두 무시한다. 프로그래머가 코드를 쉽게 읽을 수 있도록 잘 사용해보자.
Tap을 Indenting이라 부른다.
Hard Coding vs Soft Coding
ㄴHard Coding : 프로그래머가 직접 리터럴 값을 변수에 대입한다.
ㄴSoft Coding : 프로그램 실행 중 User 입력, 외부파일, 인터넷에서 데이터를 가져와 변수에 대입한다.
1.10 선언과 정의의 분리
forward declaration(전방선언)이 필요한 이유
- 아래에서 함수의 전방선언(forward declaration)이 없다면 main( ) 함수가 실행될 때 add( ) 함수를 호출할 수 없다.
- 함수의 전방선언이 있으면 컴파일 때 반환값, 입력값의 자료형을 확인하여 함수를 호출하도록 프로그램을 만든다. 이후 링커가 함수의 선언과 함수의 정의를 이어준다.
int add(int a, int b); // Forward declaration, Prototype(C-style)
int main(){
cout << add(1, 2) << endl;
}
int add(int a, int b) // Definition{
return a + b;
}
1.11 헤더파일 만들기
헤더 파일을 만들면 다른 파일에서 #include를 이용하여 재사용할 수 있다
// add.h
int add(int a, int b); // Declaration
// add.cpp
int add(int a, int b) // Definition{
return a + b;
}
// main.cpp
#include <iostream> // carot(angle bracket) for std
#inlcude "add.h" // double quatation for manual lib
int main()
{
cout << add(1, 2) << endl;
}
1.12 헤더 가드가 필요한 이유
- 함수를 헤더에서 만들 경우 선언은 header 파일에서 정의는 cpp 파일에서 한다.
header파일에서 정의를 하면 어떻게 될까?
(1) 여러 파일이 특정 header 파일을 #include 한다. #include 코드가 있는 곳에 header 파일의 코드가 복사/붙여넣기가 된다. 여러 파일에 동일한 이름의 함수가 여러번 정의되어 에러가 발생한다.
(2) 함수의 선언은 여러 번 반복되어도 에러가 발생하지 않는다. 함수의 선언은 컴파일될 때 링커가 해당 함수의 정의와 이어준다.
실험해보기
- 복사/붙여넣기를 막아주는 #pragma once, #ifndef-#define-#endif를 사용하면 Header내에서 함수의 Body를 만들어도 되지 않을까? 실험을 해보았다.
// add.h
#pragma once
#ifndef __ADD
#define __ADD
int add(int a, int b); // Forward declaration, Prototype
#endif
// add.cpp
#include "global.h"
int add(int a, int b) // Definition
{
print_global();
return a + b;
}
// global.h
#pragma once
#ifndef __GLOBAL
#define __GLOBAL
#include <iostream>
void print_global()
{
std::cout << __func__ << "is executed!" << std::endl;
}
#endif // !__GLOBAL
// main.cpp
#include "add.h"
#include "global.h"
int main()
{
print_global();
std::cout << add(1, 2) << std::endl;
}
에러가 발생한다.
" main.obj : error LNK2005: "void __cdecl print_global(void)" (?print_global@@YAXXZ) already defined in add.obj "
add.cpp 파일을 제거하고 add.h에 함수의 Body를 정의하면 에러가 발생하지 않는다.
1.13 네임스페이스 (명칭 공간)
- 같은 변수명끼리 충돌을 막기 위하여 namespace를 사용한다.
- Scope resolution Operator ( :: )를 활용하여 namespace 내부 변수를 불러온다.
뜻: 충돌시 해결해준다
예제
namespace First{
void duplicatedFunc()
{ std::cout << __func__ << " is executed in First" << std::endl; }
namespace Inner{
void duplicatedFunc()
{ std::cout << __func__ << " is executed in First::Inner" << std::endl; }
}
}
namespace Second{
void duplicatedFunc()
{ std::cout << __func__ << " is executed in Second" << std::endl; }
}
void duplicatedFunc()
{ std::cout << __func__ << " is executed in Global" << std::endl; }
int main(){
First::duplicatedFunc(); // >> duplicatedFunc is executed in First
First::Inner::duplicatedFunc();// >> duplicatedFunc is executed in First::Inner
Second::duplicatedFunc(); // >> duplicatedFunc is executed in Second
duplicatedFunc(); // >> duplicatedFunc is executed in Global
}
특정 영역 내에서 using keyword를 사용하여 해당 namespace를 사용해준다면 {namespace}:: 코드를 생략할 수 있다. 이 때 중복되는 함수명이 있다면 에러가 발생한다. main 함수 내부에 아래 코드를 삽입하여 실행해보자.
using namespace First;
duplicatedFunc();
에러가 발생한다
cout, cin, endl은 namespace 내부에 정의되어있다
- iostream 내부에 _STD_BEGIN의 정의를 확인해보자.
// iostream
#pragma once
#ifndef _IOSTREAM_
#define _IOSTREAM_
.
.
_STD_BEGIN
.
.
_STD_END
.
#endif // _IOSTREAM_
// NAMESPACE
#define _STD_BEGIN namespace std {
#define _STD_END }
1.14 전처리기와의 첫 만남
전처리기는 컴파일 전에 preprocessor directives(전치리기 지시자) 명령어를 수행한다
#define LENGTH 10 // Macro
#define STR "Hello World!" // replace keyword to value
#define ISFIRSTBIGGER_BAD(A,B) ( (A>B) ? "YES":"NO" ) // avoid it
// ISFIRSTBIGGER(1+2,2) ( (1+2>2) ) could makes undefined behavior
#define ISFIRSTBIGGER_GOOD(A,B) ( ((A)>(B)) ? "YES":"NO" ) // much safer
int main(){
std::cout << LENGTH << std::endl; // >> 10
std::cout << STR << std::endl; // >> Hello World!
std::cout << ISFIRSTBIGGER_GOOD(1,-1) << std::endl;// >> YES
}
전처리기를 활용하면 멀티 플랫폼 프로그램을 제작할 수 있다
- 전처리기는 매크로 이름을 기억해 아래와 같은 조건문을 실행한다. 조건에 따라 다르게 컴파일한다.
#define TURN_ON
int main(){
#ifdef TURN_ON
std::cout << "Switch is On!" << std::endl;
#else
std::cout << "Switch is OFF!!" << std::endl;
#endif
#ifndef TURN_ON
std::cout << "No defined!" << std::endl;
#endif
}
전처리 지시자 변수의 Lifecycle
- main( ) 함수 밖에 정의된 #define 전처리지시자는 해당 파일에서만 존재한다.
- 함수 내부에서 정의될 때는 일반 변수와 동일하게 해당 영역에서만 존재한다.
// Define.h
#define TURN_ON
// main.cpp
#include <iostream>
#include "Define.h"
int main(){
#ifdef TURN_ON
std::cout << "Switch is On!" << std::endl;
#else
std::cout << "Switch is OFF!!" << std::endl;
#endif
#ifndef TURN_ON
std::cout << "No defined!" << std::endl;
#endif
}
Header 파일의 코드가 #include 명령어 위치에 모두 복사/붙여넣기 때문에 #define TURN_ON은 main.cpp 파일 내부에서 존재하고 있다.
'Programming Language > C++' 카테고리의 다른 글
[홍정모의 따라하며 배우는 C++] 3. 연산자들 (0) | 2021.12.14 |
---|---|
[홍정모의 따라하며 배우는 C++] 2.변수와 기본적인 자료형 (0) | 2021.12.12 |
[홍정모의 따라하며 배우는 C++] 0. 시작해봅시다. (0) | 2021.12.10 |
[C++] Template (0) | 2021.11.13 |
[C/C++] Left Shift and Right Shift (0) | 2021.11.11 |