배움 저장소

[홍정모의 따라하며 배우는 C++] 1.C++의 기초적인 사용법 본문

Programming Language/C++

[홍정모의 따라하며 배우는 C++] 1.C++의 기초적인 사용법

시옷지읏 2021. 12. 11. 03:01

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 파일 내부에서 존재하고 있다.

Comments