정신과 시간의 방

전체 글 273

카테고리 설명
게임 프로그래머가 되기 위한 청춘의 기록
  • 1. 함수// 함수// 모듈화 -> 습관적으로 모듈화 하라. 기능에 대해 세부적으로 잘 나눠져있어야 한다.// 하나의 함수에 여러가지 기능이 섞여있어선 안된다.// 필요한 기능만 호출해서 바로 사용할 수 있어야 한다.// 이 기능을 다시 조합해서 새로운 기능을 만들 수 있다.int Add(int left, int right){ return left+right;}data = Add(10, 20); // 30이 할당된다.// 함수int Add(int a, int b){ return 0;}int main() // 프로그램 진입점{ int iData = Add(100, 200); return 0;}// 라이브러리 함수 사용 예시#include printf();printf("OUTPUT TEST");scan..

  • 비트 연산자// 비트 연산자// 시프트 >unsinged char btye = 13;byte >= 2; // 2^n으로 나눈 몫이 됨, 즉 2^2 = 4로 나눈 몫// 비트곱 (&), 합(|), XOR(^), 비트 반전(~)// 비트 단위로 연산을 진행함.// & : 둘 다 1인 경우 1// | : 둘 중 하나라도 1이면 1// ^(XOR) : 같으면 0, 다르면 1(홀수 개의 1이 있으면 1)// ~: 1은 0으로, 0은 1로 변환// 비트 연산은 게임에서도 사용된다.// EX. 툴, 프로그램에서 사용. Define과 함께 특정 상태 지정 시 유용하다. Define#define HUNGRY 1int iStatus = HUNGRY; (=1을 할당한 것과 같은 결과가 됨)// 전처리기 매크로 define을..

  • 1. 주석// 주석// 설명 역할, 코드로 인식되지 않는다. 존재하지만 존재하지 않는 것처럼 여기는 마치 깍두기 같은 존재.주석에 대한 자세한 설명은 이전에 작성한 글 (https://risehyun.tistory.com/37)의 5번 문항을 참고하자.2. 변수변수(variable)란 데이터를 일시적으로 저장할 목적으로 사용하는 메모리 공간이다.간단히 말하자면 구체화 하지 않았거나 앞으로 변경될 가능성이 있는 수를 변수라고 할 수 있다.변수는 데이터 값을 일시적으로 저장하는 역할을 하는 일종의 데이터 임시 저장용 상자로,이 상자는 컴퓨터 내부의 메모리 안에 만들어 진다. 변수가 필요한 이유크게 아래의 두 가지로 정리할 수 있다.1. 사용자에게서 받는 데이터를 저장할 수 있다.2. 프로그램 코드에 직접 값..

  • - C++이 다른 언어에 비해 가지고 있는 장점 중 하나는 실행 속도다. - 고수준 언어 중에서 작성한 스크립트가 가상머신을 통해 실행되는 다른 언어들과 달리 C++은 기계어와 가장 가까운 언어이기 때문에 속도가 빠르다. - 그렇기 때문에 가상머신이 메모리 관리를 대신해주는 Java, C#과 달리 C++은 개발자가 직접 메모리를 관리해주어야 하며 실수할 여지가 많다. 하지만 직접 메모리를 관리할 수 있다는 점이 최적화나 효율면에서 장점이 되기도 한다. (양날의 검)- C++을 사용하려면 이런 배경과 문제를 인지하고 디버깅할 수 있는 숙련도가 필요하다.

  • 보호되어 있는 글입니다.

  • 보호되어 있는 글입니다.

작성일
2022. 11. 24. 06:07
작성자
risehyun

1. 함수

// 함수
// 모듈화 -> 습관적으로 모듈화 하라. 기능에 대해 세부적으로 잘 나눠져있어야 한다.
// 하나의 함수에 여러가지 기능이 섞여있어선 안된다.
// 필요한 기능만 호출해서 바로 사용할 수 있어야 한다.
// 이 기능을 다시 조합해서 새로운 기능을 만들 수 있다.

int Add(int left, int right)
{
	return left+right;
}

data = Add(10, 20); // 30이 할당된다.

// 함수
int Add(int a, int b)
{
	return 0;
}

int main() // 프로그램 진입점
{
	int iData = Add(100, 200);
    return 0;
}

// 라이브러리 함수 사용 예시
#include <stdio.h>
printf();
printf("OUTPUT TEST");
scanf();

C언어는 프로그램 작성시 여러 함수를 만들거나 사용해 큰 흐름을 완성한다.
따라서 함수는 C언어 프로그램을 이루는 핵심 중의 핵심이라 할 수 있다.

  • 함수와 반복문
    함수와 반복문은 반복되는 코드를 처리하는 것과 관련되는 문법이라는 점에서 공통점이 있다.
    하지만 반복문은 연속적인 반복을 의미하고, 함수는 불연속적인 반복을 통한 코드의 재사용을 의미한다.

    한 프로그램 내부에서 같은 일을 수행하는 코드를 함수로 만들어 놓으면, 단지 호출 하는 것만으로도 같은 코드를 재사용할 수 있다. 또한 같은 일을 수행하는 코드의 중복을 회피할 수 있으므로 프로그램을 더욱 간결하게 만들 수 있다.


  • 사용자 정의 함수의 선언 및 정의
    사용자 정의 함수(user-defined function)은 기존의 표준 입/출력 함수들과 달리 이미 만들어진 것이 아니라 필요에 의해 개발자 스스로 만들어낸 함수를 의미한다.
    모든 C언어 프로그램에서 찾아볼 수 있는 main()함수 역시 이러한 사용자 정의 함수의 일종이다.
    프로그래머는 main() 함수라는 특정 사용자 정의 함수에 내부 절차적 흐름을 정의(=기술) 하여 프로그램을 작성한다.

<함수의 선언과 정의>

함수 정의 : 함수의 내부를 이루는 구문들을 실제로 기술하는 것

함수 선언 : 반환 자료형, 함수이름, 매개변수 등을 기술하여 함수의 외형적 특성을 기술한 문법
EX. int Add(int a, int b)


<호출자와 피호출자>

호출자(caller) : 어떤 함수를 호출하는 함수
피호출자(callee) : 어떤 함수에 의해 호출되는 함수
  • 매개변수
    매개변수(parameter)는 호출자와 피호출자 함수를 연결해주는 매개체이다.
    파라미터, 혹은 형식인수라고도 부르는데 보통 매개변수라는 말을 가장 일반적으로 사용한다.

    매개변수는 자동변수로, 지역변수와 같이 스택 영역을 사용한다.

    모든 호출자 함수는 피호출자 함수 매개변수의 초깃값을 '실인수'로 명시할 의무가 있다.
    이때 매개변수라는 것은 말 그대로 변수를 의미하는데, 피호출자 함수 내부에 선언된 지역변수이기 때문에

    아래와 같이 같은 이름을 가진 변수가 함수 몸체 스코프 안에 공존할 수 없다.
#include <stdio.h>

int Add(int a, int b)
{
    int nData = 0, a; // 매개변수로 사용되는 a를 다시 재정의 하는 것은 불가능하다.
    nData = a + b;
    a = b = 10;
    return nData;
}
  • 반환 자료형
    함수의 반환 자료형이란 호출자 함수가 피호출자 함수를 호출해서 얻을 수 있는 정보의 형식을 뜻한다.
    이때 함수가 반환한 자료는 연산의 '임시결과'와 마찬가지로 즉시 활용하거나 저장하지 않으면 유실된다.

    반환 자료를 즉시 사용한다는 것은 아래의 3가지 경우를 의미한다.

    1. 자료형이 일치하는 변수에 대입하여 정보를 보관
    2. 피연산자로 다른 연산에 참여
    3. 다른 함수를 호출하는 데 인수로 사용
#include <stdio.h>

// 정수 셋을 매개변수로 받고 최댓값을 반환하는 함수 선언 및 정의
int GetMax(int a, int b, int c)
{
	// GetMax() 함수의 지역 변수 선언 및 정의
	int nMax = a;
	if (b > nMax) nMax = b;
	if (c > nMax) nMax = c;

	return nMax;
}

int main(void)
{
	int nResult = 0;

	// 1. 다른 함수를 호출하는데 인수로 사용
	// 함수가 반환한 값을 %d 형식으로 출력한다.
	printf("MAX : %d\n", GetMax(1, 2, 3));

	// 2. 피연산자로 다른 연산에 참여
	// 함수가 반환한 값에 * 2 연산을 수행하고 %d 형식으로 출력한다.
	printf("MAX : %d\n", GetMax(2, 3, 1) * 2);
	
	// 3. 자료형이 일치하는 변수에 대입하여 정보를 보관
	// 함수가 반환한 값을 nResult 변수에 저장한 후
	// nResult에 저장한 값을 %d 형식으로 출력한다.
	printf("MAX : %d\n", nResult = GetMax(3, 1, 2));


	return 0;
}

 

  • 함수 설계의 두 가지 원칙
    함수를 설계할 때 다음의 두 가지 원칙을 고려하는 것이 좋다.

    1. 사용자 인터페이스(UI, 겉으로 드러나는 외형)과 내부기능은 반드시 분리할 것

    2. 하나의 단위 기능으로 규정할 수 있는 대상은 함수로 만들 것
    EX. 평균 계산하기, 최댓값 적기, 정렬 등


    - UI와 기능의 분리
    프로그램의 사용자 인터페이스(UI, User Interface)는 인간과 기계가 상호작용할 수 있도록 연결되는 형식을 의미한다.
    이 UI를 통해 사용자는 기계에 명령을 내리거나(입력) 기계가 생산한 정보를 인식(출력) 할 수 있다.

    따라서 거의 모든 프로그램에 UI와 기능으로 나뉘어진다.
    이때 이 둘은 적어도 함수단위로 구별되어야 한다. 

    예를 들어서 화면에 메뉴를 출력한 후 사용자로부터 메뉴 선택을 입력받아 메뉴에 대응하는 기능을 수행하는 코드가 반복되는 구조가 있다고 하면, 메뉴를 출력하고 사용자가 원하는 메뉴 선택을 받는 코드들은 PrintMenu() 함수에 구현하고 이 함수를 main() 함수에서 반복해서 호출하여 반환받은 사용자의 선택을 switch-case 문으로 분석해 메뉴에 대한 적절한 문자열을 출력할 수 있다.

    이러한 반복 구조는 이벤트 루프(event loop)라고도 부르며 GUI를 가진 프로그램을 개발할 때도 적용된다.


    - 재사용 가능한 단위 기능의 구현
    불연속적으로 반복되거나 앞으로 다시 사용될 가능성이 높은 코드는 함수로 만드는 것이 좋다.
    그래야 유지보수하기가 쉽기 때문이다.

    꼭 유지보수의 용이성만이 아니라, 프로그램이 제공하는 여러 기능 각각을 하나의 함수로 만들어버리면 코드를 관리하기도 좋고 구조도 깔끔한 프로그램이 될 수 있다.

    각 기능은 당장은 특정 상황에서만 필요한 기능일 수 있지만, 추후 프로그램의 다른 부분에서도 같은 기능이 필요할 가능성이 존재한다. 재사용 가능성이 없는 함수라고 해도 각 기능을 함수로 따로 떼어놓으면 소스코드에서 헤당 코드만 따로 식별하기에도 좋다.

    함수 설계 원칙보다 더 큰 범위의 설계 원칙으로 DRY(Don't repeat yourself) 원칙이 있다.
    이 원칙의 핵심은 "같은 일을 수행하는 코드가 중복(여러 곳에 존재)되지 않도록 하라"이다.
    이는 수시로 변하는 소프트웨어의 환경적 특성에 대응해 논리적 오류를 방지하고 유지보수를 용이하게 만들기 위함이다.


  • 코드 분할
    덩치가 너무 큰 코드는 변화에 대응하기 어렵다. 즉, 유지보수 하기가 어려워진다.
    따라서 자신 스스로 너무 큰 코드라고 판단되면 일정 수준으로 크기를 줄여 여러 함수로 코드를 나누는 것이 좋다.
    코드를 분할하고 연결하기 위해서는 다음과 같은 점들을 고려해야 한다.

    - 함수의 이름에서 기능이 무엇인지 직관적으로 알 수 있어야 한다.

    - 분할 된 코드가 들어 있는 피호출자 함수에 반드시 전달되어야 할 정보는 무엇인지 확정(매개변수)해야 한다.

    - 호출자 함수는 피호출 함수를 호출하는 것으로 끝나는 것인지 아니면 반드시 어떤 정보를 반환받아야 하는지 확정(반환 자료형)해야 한다.

    매개변수와 반환 자료형은 두 함수가 서로 만나는 접점이다.
    따라서 호출자 함수는 피호출자 함수 매개변수의 실인수를 기술할 의무가 있으며 피호출자 함수 역시 자신의 반환 자료형에 맞는 정보를 호출자 함수에게 반환(return) 해줄 의무가 있다. 즉, 코드가 둘로 나눠졌을 때 넘겨줄 정보와 받아 낼 정보가 무엇인지 형식 수준에서 구체화 해야 한다.

    따라서 매개변수와 반환 자료형을 명확히 선언할 수 있는 능력을 기르는 것이 중요하다.
    좋은 코드들을 많이 접하고 경험을 늘리도록 하자.

  • 함수의 원형 선언
    변수와 함수는 문법적으로 선언과 정의가 분리될 수 있다.
    변수는 선언만으로 끝나는 경우도 있지만, 함수는 늘 선언과 정의를 분리할 수 있다.

    경우에 따라서 함수의 선언이 반드시 별도로 존재해야 할 수도 있다.
    이 경우 함수 정의보다 함수를 호출하는 코드가 더 먼저 등장하는 경우이다.

    이럴 때는 함수의 '원형 선언(prototype)'을 소스코드 상단에 기술함으로써 컴파일러에 함수의 존재를 알려야 한다.
#include <stdio.h>


int Add(int, int);

int main()
{
	// add()함수가 존재한다는 사실을 컴파일러가 알고 있으므로
	// 컴파일 오류는 발생하지 않는다.
	printf("%d\n", Add(3, 4));

	return 0;
}

int Add(int _x, int _y)
{
	return _x + _y;
}

 

원형 선언을 하지 않아도 피호출자 함수를 main() 함수 위로 옮기면 컴파일러는 아무런 오류도 내지 않는다.
그것이 피호출자 함수의 선언 및 정의가 되기 때문이다.

하지만 원형 선언 없이 main()함수를 피호출자보다 먼저 호출하게 되면 아직 존재하지도 않는 함수를 호출하는 것이 되기 때문에 오류가 발생한다.

이러한 링크 오류는 있어야 할 정의가 없거나, 하나만 있어야 할 정의가 여러 개 있을 때 발생한다.


2. 반복문

반복문이란 일정구간의 코드를 연속적으로 반복해 실행하는 제어문을 뜻한다.
프로그램 시작 후 메인 함수가 반환되어 프로그램이 종료되는 것을 막기 위해 대기 상태를 유지할 때 사용되는 것도 바로 이 반복문이다.

반복문을 표현하는데 있어서 고려되는 점은 아래의 세가지이다.

1. 반복을 멈추기 위한 조건
2. 반복 횟수
3. 반복 조건

가장 중요한 것은 반복을 멈추기 위한 조건식이며, 횟수와 조건에 따라서 어떤 반복문을 사용할지가 결정된다.

  • while, 조건 기반 반복문
    while문은 조건에 기반을 두기 때문에 if문과 문법 구조가 동일하다.
    괄호 속에 조건식을 기술하는 문법이나 여러 구문을 스코프로 묶는 것도 동일하다.

    하지만 while문은 내부 구문이 끝나면 다음으로 넘어가는 if문과 달리,
    내부 구문의 종료와 동시에 다시 구문의 처음으로 돌아가 조건식을 확인하는 것을
    조건식이 불성립할 때까지 반복하는 반복문이라는 점에서 차이가 있다. 

    그렇기 때문에 조건이 참이면 계속해서 구문을 실행하고, 반복 하던 와중에 조건식의 결과가 거짓이 되면 그때 반복을 끝내게 된다.

  • 무한루프
    반복문을 멈추기 위한 조건식에 문제가 있어 반복이 끝나지 않는 문제를 무한루프라고 한다.
    반복문 내부 연산으로 반복문을 끝내는(혹은 탈출하는) 상태에 도달하지 않으면
    계속해서 똑같은 동작을 반복하게 되므로 프로그램이 죽어버리는 치명적인 오류가 발생할 수 있다.

  • 반복문 내부에 선언한 자동변수
    반복문 내부에 변수를 선언하는 것은 바람직하지 않다.

    지역 변수의 경우 스코프가 닫히면 그 내부에 선언 및 정의한 변수가 사라진다.
    그러므로 논리적 오류를 야기할 가능성이 높고, 불필요한 생성과 소멸을 반복하는 효율상의 문제도 발생한다.

    따라서 문제가 발생할 경우 스택영역 관리 방법에 대한 이해 없이는 해결할 수가 없으며 매우 비효율적이다.

  • for, 계수 기반 반복문
    for문과 while문은 반복문이라는 점에서 같지만,
    while문은 조건에 기반을 둔 측면이 강하고 for문은 계수에 기반을 둔 측면이 강하다.

    for문 처럼 계수에 기반을 둔 반복을 while문으로 기술할 경우, 반복 자체를 위한 코드가 세 부분임에도 문법상으로는 조건식만 기술하면 된다.

    때문에 중첩된 반복문을 구현할 때 계수기 초기화 코드를 빼먹거나,
    반복문 내부에 반드시 기술되어야 하는 계수기 증가식을 깜빡하는 실수가 벌어질 가능성이 있다. 

    하지만 for문은 그 세 가지 요소를 모두 한 행에서 기술하도록 강제하는 문법이다.
    반복횟수에 영향을 주는 계수기 초기화, 조건식, 계수기 증감 등의 코드를 모두 한 행에서 확인할 수 있다.
    따라서 단 한 줄의 코드만으로도 for문을 몇 회 반복 수행하는지 명확하게 알 수 있다.
	// 계수기 초기화; 조건식; 계수기 증가
	for (i = 0; i < 5; ++i)
	{
		printf("%dth\n", i);
	}


조건이 참일 때 반복문 내부를 실행한 뒤 다시 올라올 때는 계수기 증가 코드 ++i를 실행한 후 i < 5 조건을 비교한다.

이때 원한다면 for문의 오른쪽 괄호 내부 구문 중 일부를 생략할 수도 있다.
예를 들어 for( ; i < 5; ++i)와 같이 초기 수행식을 생략하거나 for( i = 0; i < 5; ) 처럼 계수기 증가 부분을 생략하거나, 심지어 조건식도 생략할 수 있다.

for( ; ; )의 경우 while(1)과 같은 의미이다. (무한 반복)

  • do while문
    do while문은 while문이나 for문과 달리 반복대상 단위코드를 조건과 상관없이 일단 한 번 실행한 후 조건을 비교한다.
    따라서 반복 대상 코드가 적어도 한 번은 반드시 실행되며, 자주 쓰이진 않지만 이런 특성이 반드시 필요한 반복을 구현하는데 적합하다.

    예를 들어, 사용자가 유효범위 값을 입력하지 않았을 때 다시 입력하는 코드를 작성하고자 할 때 do while문을 사용해 일단 입력을 받은 뒤 유효범위를 체크하고 넘어가면 올바른 값을 선택하지 않을 시 계속해서 입력을 해야하므로 간편하게 구현할 수 있다.

    이런식으로 사용자가 프로그램을 잘못 사용할 가능성 혹은 실수의 가능성 등을 봉쇄하는 기능은 프로그램의 설계상 아주 중요하다.

    do while문의 문법적인 특이점조건식 괄호 뒤에 세미콜론(;)이 붙는다는 점이다.
    이는 반복문 중에 유일하게 do while만 가진 특징이다. 
#include <stdio.h>

int main(void)
{
	char ch = 0;

	do 
	{
		// 조건을 나중에 비교하므로
		// 일단 한 번은 무조건 실행한다.

		ch = getchar();
		putchar(ch);

		// 조건식 오른쪽 끝에 ;이 있다는 점에 주의한다.
	} while (ch != '\n');

	return 0;
}

 

 

  • break과 continue


    break는 switch-case문이나 반복문(while, for, do while)문에서 사용되어 프로그램의 흐름 내부를 벗어나게 하는 탈출을 위한 제어문이다.

    continue문은 반복문 내부에만 사용할 수 있는 구문이며, 수행 즉시 나머지 구문들을 생략하고 다시 조건식을 비교하는 제어문이다. 만약 제어문이 참일 경우 반복문을 계속 수행한다.

    이 두 제어문이 사용되는 가장 흔한 이유는 반복문 내부에서 수행한 어떤 연산에서 예외가 발생했을 때 이에 대응하기 위함이다.

    일반적으로 break문과 continue문은 단독으로 사용되기 보다, if문과 결합하여 특정 조건일 때 작동하게끔 코드를 작성하는 것이 대부분이다.


    +)
// <반복문>
// 프로그램 시작 후 메인 함수가 끝나는 것을 방지하기 위해서 
// 대기 상태를 유지 할 때 반복문을 사용한다.

// 1. for() 
// for(횟수체크를 위한 변수(반복자)를 초기화; 반복자 조건 체크 EX. (A<B);반복자 변경 EX.++,--) {}
for(int i = 0; i < 10; ++i) // 0~9까지 돌면서 반복
{
	// 조건 체크 결과 참일 경우 이 영역 안의 코드가 실행 된다.
    printf("OUTPUT TEST");
}


// 2. while
int i = 0;
while(i < 2)
{
	printf("OUTPUT TEST\n");
    
    ++i;
}

// <반복문 관련 그 외 문법>
// 1. Continue; -> 반복 구문 수행 중, 컨티뉴를 만나면 
// 이후 작성된 뒷 부분을 수행하지 않고 바로 조건체크 부분으로 넘어간다.

// 2. break
// break문을 만나면 즉시 반복문을 빠져나간다.

for(int i = 0; i < 4; ++i)
{
	if(i % 2 == 1)				// i가 홀수이면
    {
    	continue;				// 출력하지 않고 바로 조건 체크로 넘어감
    }
    printf("OUTPUT TEST\n"); 	// i가 짝수면 정상 출력
}



'C,C++ > [강의] 어소트락 C++' 카테고리의 다른 글

함수와 재귀함수  (0) 2022.11.28
VS 단축키 및 편의사항  (0) 2022.11.25
define / 비트연산자  (0) 2022.11.23
주석, 변수, 자료형, 연산자  (0) 2022.11.22
C++을 쓰는 이유  (0) 2022.11.22
작성일
2022. 11. 23. 18:23
작성자
risehyun
  • 비트 연산자
// 비트 연산자
// 시프트 <<, >>

unsinged char btye = 13;
byte <<= 3; // 값이 2^n의 배수가 됨, 즉 2^3 = 8
byte >>= 2; // 2^n으로 나눈 몫이 됨, 즉 2^2 = 4로 나눈 몫

// 비트곱 (&), 합(|), XOR(^), 비트 반전(~)
// 비트 단위로 연산을 진행함.
// & : 둘 다 1인 경우 1
// | : 둘 중 하나라도 1이면 1
// ^(XOR) : 같으면 0, 다르면 1(홀수 개의 1이 있으면 1)
// ~: 1은 0으로, 0은 1로 변환

// 비트 연산은 게임에서도 사용된다.
// EX. 툴, 프로그램에서 사용. Define과 함께 특정 상태 지정 시 유용하다.

 

  • Define
#define HUNGRY 1
int iStatus = HUNGRY; (=1을 할당한 것과 같은 결과가 됨)

// 전처리기 매크로 define을 사용하는 이유?
// 캐릭터의 상태가 여러개 있다면(중독, 화상) 이것들이 중첩될 수 있는데, 
// define을 사용하면 이를 혼동하지 않고 쓸 수 있다.
// 또한 수정할이 필요할 때도 define을 사용하면 미리 정한 값을 수정하기만 하면 되므로 편리하다.
// 즉, 코드의 가독성을 높일 수 있으며 유지보수가 용이해진다.


unsinged int iStatus = 0;
// 합연산을 사용한 상태 추가
iStatus 1 |= HUNGRY;
iStatus 1 |= THIRSTY;

// 상태 확인
if(iStatus & THIRSTY) // 곱연산으로 둘이 서로 같으면 1(True)가 된다.
{

}

// 특정 자리 비트 제거
iStatus &= ~THIRSTY; 
// 비트 곱으로 특정 자리 비트를 삭제한다.
// 비트 삭제 전에 원하지 않는 값이 1로 활성화되는 것을 막기 위해 먼저 비트 반전 (~)을 사용한다.
#define HUNGRY 	1 	// 0x1 (대부분 상태 나열은 16진수로 나타낸다.)
#define THIRSTY 2	// 0x2 (2의 배수로 증가함)
#define TIRED 	4 	// 0x4
#define FIRE	8 	// 0x8
#define COLD	16	// 0x10 (16진수는 16개 비트가 1개이므로 16부터는 0x10이 된다.)
					// F가 15에 해당하고, F+1부터는 다음 자리로 넘어감
#define POISION 32  // 0x20 (32는 16의 2묶음이므로 20이 됨)
#define BLIND   64  // 0x40
#define	HUNGRY		0x001
#define	THIRSTY 	0x002
#define	TIRED		0x004
#define FIRE		0x008

#define COLD		0x010
#define POISION 	0x020
#define 임의상태1 	0x040
#define	임의상태2	0x080

#define	임의상태3	0x100
#define	임의상태4	0x200
#define	임의상태5	0x400
#define	임의상태6	0x800

// 16진수로 나타내면 4개씩 끊어지는 규칙이 보인다.

'C,C++ > [강의] 어소트락 C++' 카테고리의 다른 글

함수와 재귀함수  (0) 2022.11.28
VS 단축키 및 편의사항  (0) 2022.11.25
함수, 반복문  (0) 2022.11.24
주석, 변수, 자료형, 연산자  (0) 2022.11.22
C++을 쓰는 이유  (0) 2022.11.22
작성일
2022. 11. 22. 13:54
작성자
risehyun

1. 주석

// 주석
// 설명 역할, 코드로 인식되지 않는다. 존재하지만 존재하지 않는 것처럼 여기는 마치 깍두기 같은 존재.

주석에 대한 자세한 설명은 이전에 작성한 글 (https://risehyun.tistory.com/37)의 5번 문항을 참고하자.

2. 변수

변수(variable)란 데이터를 일시적으로 저장할 목적으로 사용하는 메모리 공간이다.
간단히 말하자면 구체화 하지 않았거나 앞으로 변경될 가능성이 있는 수를 변수라고 할 수 있다.

변수는 데이터 값을 일시적으로 저장하는 역할을 하는 일종의 데이터 임시 저장용 상자로,
이 상자는 컴퓨터 내부의 메모리 안에 만들어 진다.

 

  • 변수가 필요한 이유
    크게 아래의 두 가지로 정리할 수 있다.

    1. 사용자에게서 받는 데이터를 저장할 수 있다.
    2. 프로그램 코드에 직접 값을 넣는 것 보다 변수를 사용하는 것이 프로그램을 더 유연하게 만든다. (유지보수가 쉬워진다)



  • 변수의 종류
     다음과 같이 특징에 따라 4가지로 나눌 수 있다.

// 변수
// 1. 전역변수 -> 함수 밖에 선언된 변수
int global = 0;
int data = 0;

// 2. 지역변수 (함수 안에 선언된 변수) 
// 함수 내부의 스코프가 중첩되면서 지역 변수의 지역변수 생성 가능.
// 변수명 규칙 : 지역변수끼리는 변수명이 겹쳐도 스코프 밖에 가까운 변수 부터 우선 순위 부여함.
// 따라서 겹치는 변수명도 선언 가능. 하지만 완전히 같은 영역 안에서는 중복 되는 이름 줄 수 X
// 또한 전부 변수와 지역변수가 동시에 존재하는 경우 호출시 전역 변수가 인식 된다.

//3. 정적변수

//4. 외부변수

 

  • 변수에 대한 사실
    - 변수는 이름으로 식별한다.
    각각의 변수는 자신만의 이름을 가지고 있으므로 이름을 이용해 변수들을 구별한다.

    -변수는 그릇(자료를 담을 공간)이고 상수는 그릇에 담을 음료수(이미 상표가 정해진 물건)이다.

    예를 들어 종이컵(=그릇, 변수)에 어떤 음료가 담기냐에 따라 커피 한잔이 될 수 있고 주스 한잔이 될 수도 있다.

    - 변수의 개수는 적을 수록 좋다.
    변수가 많아지면 관리가 힘들고 프로그램을 복잡하게 만든다.

    - 변수는 메모리에서 구현되며, 메모리는 주소를 가진다.
    메모리 사용량이 적은 프로그램이 효율적인 프로그램이다.
    앞서 살펴본 많은 변수가 프로그램을 복잡하게 만드는 데는 이러한 이유도 있다.


  • 변수 선언
    - 변수를 사용하려면 먼저 선언을 해야 한다.

    변수 선언이란 컴파일러에게 지금부터 어떤 변수를 할 건지,
    어떤 타입의 데이터가 그 변수에 저장되는지 컴파일러에게 미리 알려주는 것이다.
int a;

int a, b, c; // 하나의 라인에서 여러 개의 변수를 선언

 

 

  • 변수 이름 짓기
    앞서 변수는 이름으로 식별한다고 배웠다. 따라서 반드시 변수에는 이름이 있어야 한다.
    변수의 이름과 함수의 이름은 모두 식별자의 일종이다. 

    식별자(identifer)란 변수 이름, 함수 이름 등에 사용되어 다른 것들과 식별할 수 있게 해주는 것이다. 
    변수의 이름은 프로그래머가 마음대로 지을 수 있지만 몇 가지 식별자를 만드는 규칙이 있어 이것을 따른다.
    식별자를 만드는 규칙은 다음과 같다.

    - 식별자는 영문자와 숫자, 밑줄 문자 _로 이루어진다.
    - 식별자의 중간에 공백이 들어가면 안 된다.
    - 식별자의 첫 글자는 반드시 영문자 또는 밑줄 기호 _ 이여야 한다. 식별자는 숫자로 시작 할 수 없다.
    - 대문자와 소문자는 구별된다. 따라서 변수 include와 Index, INDEX는 모두 서로 다른 변수이다.
    - C언어의 키워드(※)와 똑같은 식별자는 허용되지 않는다.

※ 키워드는 C언어에서 고유한 의미를 가지고 있는 특별한 단어이다.
키워드는 컴파일러가 지정한 예약어(reserved words)라고도 한다.
이러한 키워드는 사용자가 다시 정의하거나 사용하는 것이 금지 되어 있다.

 

  • 변수 초기화

- 변수가 선언되면 변수의 값은 아직 정의되지 않은 상태가 된다.
변수를 선언과 동시에 값을 넣는 방법은 변수 이름 뒤에 = 를 붙이고 초기값을 적는 것이다.
이것을 변수의 초기화(initialization)이라고 한다.

변수를 선언 동시에 초기화하는 것은, 먼저 선언 한 후에 값을 대입하는 것과 동일하게 작동한다.
동일 타입의 변수는 같은 줄에서 선언과 동시에 변수를 초기화 할 수 있다.

int width = 100, height = 200;

다음과 같은 초기화는 문법적인 오류는 아니지만 width는 초기화되지 않고 height만 초기화 되므로 피해야 한다.

※ 변수를 초기화 하지 않으면 변수 안에 어떤 값이 들어갈까?
변수가 어디에 선언되느냐에 따라 다르지만, 일반적으로 초기화 되지 않은 변수엔 
아무 의미가 없는 쓰레기 값이 들어가게 된다.


따라서 변수를 제대로 초기화 하지 않으면 연산 과정에서 의도하지 않은 값이 들어가는 등 문제가 발생할 수 있으므로,

변수는 가능한 선언과 동시에 초기화까지 진행해주는 것이 좋다.

 

  • 변수값 출력

    printf()와 같은 라이브러리 함수를 사용하려면 함수를 호출하면 된다.
    이를 이용해 변수값을 출력하려면 다음과 같은 문장을 사용하면 된다.
printf("두수의 합 : %d", sum);

 

여기서 "%d"는 %d를 출력하라는 의미가 아니고 출력 형식을 지정한다.

%d는 변수의 값을 10진수 형태로 출력하라는 것을 의미한다.

여기서 변수 sum의 값이 %d의 위치에서 정수로 출력 된다.

"%d"를 형식 지정자라고 한다. 기본적인 형식 지정자는 다음과 같은 것들이 있다.

 

형식 지정자 의미 실행 결과
%d 10진 정수로 출력 10
%f 실수로  출력 3.14
%c 문자로 출력 a
%s 문자열로 출력 Hello

 

형식 지정자와 변수들은 1개 이상일 수도 있고 중간에 문자열이 있는 등 섞어서 사용할 수 있다.

이 경우 형식 지정자의 자리에 각 변수들이 대응된다.

 

+) 변수의 반대되는 개념으로 상수라는 것이 존재한다.
상수는 구체적인 값이 확정되어 앞으로 변할 가능성이 없는 수를 의미한다.

프로그램에서 데이터를 저정하는 공간은 두 가지로 나눌 수 있는데,
변수의 경우 프로그램 실행 중에 저장된 값이 언제든지 변경될 수 있는 공간에 생성된다.

컴퓨터가 계산을 하기 위해서는 모든 데이터가 메모리에 존재해야 하므로 상수도 변수처럼  메모리에 저장된다.
하지만 상수의 경우 변경이 불가하다는 태그가 붙어있다는 차이점이 존재한다.
이렇듯 상수도 메모리에 존재하기 때문에 자료형이 존재한다.

 

  • 변수의 입력
    지정한 값을 출력하는 printf()와 반대로 키보드로부터 입력된 데이터를 지정된 형식으로 변환하여 저장하는 scanf() 함수가 있다.
scanf("%d", &x);

scanf()가 호출되면 컴퓨터는 사용자가 숫자 입력을 마칠 때까지 기다리게 된다.

사용자가 정수를 입력하고 엔터키를 누르면 그때 비로소 정수가 변수에 저장되어서 scanf() 함수의 호출이 끝나게 된다.

 

첫 번째 인수인 "%d"는 형식 지정자로서 정수형의 데이터를 지정된 형식으로 변환하여 변수에 저장하는 라이브러리 함수이다. scanf()에서 형식 지정자는 printf()에서의 형식 지정자와 그 의미가 같다. 두 번째 인수인 &x은 입력을 받을 변수의 주소를 나타낸다.

이때 변수 이름 앞에 &(앰퍼샌드) 기호가 있다는 점을 유의해야 한다.
변수는 메모리에 생성되기 때문에 각각의 변수는 고유한 주소를 가지고 있다.

변수 이름 앞에 &를 붙이면 해당 변수의 주소를 의미하게 된다.
예를 들어 &i라고 하면 i라는 변수의 주소를 의미하게 된다.

이렇듯 scanf() 함수는 단순히 변수의 값을 받아 출력하는 printf()와 달리 변수의 주소를 받는다는 특징이 있다.

 

3. 자료형

자료형(data type)이란 변수가 저장할 데이터가 정수인지 실수인지, 아니면 또 다른 데이터인지를 지정하는 것이다.

일정 크기(혹은 길이)의 메모리에 저장된 정보(혹은 자료)를 해석하는 방법,

즉  정수, 실수, 문자 등을 구별하기 위한 키워드로서 자료형이라는 하위분류가 존재한다고 할 수 있다.
기본적으로 컴퓨터라는 기계가 처리할 수 있는 정보는 오로지 숫자 뿐이다.

겉으로는 그림이나 글자의 모습을 하고 있어도 본질은 숫자라는 사실을 잊지 말아야 한다.

자료형에는 정수형, 실수형, 문자형 등이 있다.

// 자료형 (크기단위, byte, 형태를 설명해주는 역할 = Data Type)
// 정수형 : char(1), short(2), int(4), long(4), long long(8)
// 실수형 : float(4), double(8)

int main()
{
	int i = 0;
    


     // Q. 정수형 자료형이 1바이트이고 양수만 취급하고자 할 때
     // 0에서 최대 255까지 들어갈 수 있는 변수 c를 선언해보자.
     // 1 바이트로 양수만 표현
     // 256가지 -> 0 ~ 255
     // A. unsigned 형의 char 변수를 선언한다.
     unsigned char c = 0;
     c = 0;
     c = 255;
     
     // Q. 만약 자료형의 크기에 벗어나는 값을 할당하면 어떻게 될까?
     // A. 값을 할당할 공간이 더 이상 없기 때문에 오버플로우가 발생하여 변수에 0이 할당되게 된다.
     c = 256;
     
     // Q. 1바이트로 양수, 음수를 모두 표현하려면 어떻게 해야 할까?
     // -128 ~ 0 ~ 127
     char c1 = 0;
     c1 = -1;
     c2 = 255;
     
     // 음의 정수 찾기(2의 보수법)
     // 대응되는 양수의 부호를 반전 후, 1을 더한다.

    
	return 0;
}

 

  • C언어의 자료형
분류 자료형  크기 해석방법 혹은 특징
정수형 char 1바이트 (8비트) 1비트 부호비트와
나머지 전체를 데이터 비트로
해석하는 부호가 있는 정수형
short 2바이트 (16비트)
int 4바이트 (32비트)
long 4바이트 (32비트)
long long int 8바이트 (64비트)
unsigned 조합 - 부호 비트도 데이터 비트로
해석하는, 부호가 없는 정수형
실수형 float 4바이트 부호비트, 지수, 가수 등
세 부분으로 구성된 실수형.

지수부와 가수부를 연산한
결과로 값을 표현
double 8바이트
long double 8바이트 이상
유도형 * (에스터리스크) 4바이트 (32비트 App) 또는
8바이트 (64비트 App)
포인터 
[ ] - 배열, 동일 요소의 집합체
구조체 서로 다른 요소들의 집합체
공용체 동일 메모리에 다양한
해석방법을 부여하는 방법
함수형 - 포인터와 동일 반환 형식과 매개변수 구조를
가진 형식으로 호출 연산의
대상이 될 수 있는 방식
- void   무치형.
길이 및 해석방법 없는 자료형

 

  • short, wchar_t
    short형은 부호가 있는 16비트 정수형이다. 정수를 담기 위한 자료형이긴 하지만, 표현 범위가 작기 때문에 자주 사용되지 않는다. wchar_t는 윈도우 환경에서 short형과 같은 16비트 자료형으로 문자 하나를 저장하는 공간이다.

    정수를 표현하는 가장 보편적인 형식은 int형이지만 소스코드에서 상수를 표현할 때 int와 short는 별도로 구별하지 않는다. 또한 wchar_t형은 문자 상수는 char형과 달리 상수 앞에 L이 붙는다.
short s = 1234;
wchar_t w = L'A';

 

  • int와 long
    int형은 부호가 있는 32비트 정수형이며 가장 많이 사용되는 자료형 중 하나로
    일반적인 정수, 특히 상수를 표시할 때 int형을 사용한다/

    long도 int형과 동일하게 32비트를 가지는데, 둘의 표현 범위가 같기 때문에 서로 차이가 없는 것처럼 보이지만 분명히 다르다.

    int는 운영체제 환경과 상관없이 늘 동일한 4바이트 크기를 유지한다.
    즉 64비트 운영체제와 32비트 운영체제 중 어디에서 int 자료형을 사용하더라도 같은 값을 가진다.

    반면 long의 경우 32비트 운영체제에선 4바이트 크기를 가지지만 64비트 운영체제에서는 8바이트 크기를 가진다.
    따라서 둘의 차이는 운영체제 환경에 따라 크기가 달라지는지 여부에 있다.


  • unsigned, signed 수식자
    두 키워드는 정수형(short, int, long, long long) 앞에서 선언할 수 있다.

    unsigned는 음수가 아닌 값만을 나타낸다는 것을 의미한다.
    unsigned를 붙이면 표현 범위에서 음수가 제외 되기 때문에 더 넓은 범위의 양수를 나타낼 수 있다.
    printf()로 unsigned 값을 출력하고 싶을 때는 %u를 사용한다.

    signed는 반대로 해당 자료형이 음수도 가질 수 있음을 명백히 하는데 사용된다.
    따라서 signed int와 일반 int는 서로 같은 의미이며 보통의 경우 signed 수식자가 생략된다.


  • 오버플로우
    변수가 나타낼 수 있는 범위를 넘는 숫자를 저장하려 하면 그 결과가 정수형이 나타낼 수 있는 범위를 넘어갈 수 있다. 이러한 현상을 오버플로우(overflow)라고 한다.

    오버플로우가 발생하면 전체적으로 부정확한 결과가 계산될 수 있다.

    그러나 정수형에서 오버플로우가 발생하더라도 컴파일러는 아무런 경고를 하지 않으므로,
    오버플로우가 발생하지 않도록 프로그래머 스스로 주의해야 한다.

 

  • 이형자료 간의 연산과 형승격
    가능한 서로 자료형이 같은 자료형을 가진 변수끼리 계산하는 것이 바람직하지만,
    이형자료 간 연산을 진행하는데 문법적으로 전혀 문제가 없는 경우  이형자료를 가진 변수끼리 연산이 가능한 경우가 있다.

    예를 들어, double + int는 double 형식이 된다. 따라서 해당 결과 값은 실수가 되므로 %d 형식으로 출력할 수 없다.
    %d로 출력하려하면 실제 결과 값이 아닌 0이 출력된다.
    왜 실수를 정수로 출력할 수 없는지 헷갈린다면 아래 5번 항목에서 실수와 정수의 차이를 확인해보자.
    간략하게 여기서 이유만 언급하자면 실수와 정수는 비트 상에서의 표현 방식이 다르기 때문이다.

    다시 본론으로 돌아가서, 이처럼 서로 자료형 크기가 같은 int와 double뿐 만 아니라 자료형 크기가 다른 변수도 연산 가능하다.
    다만 이 케이스에서는 한 가지 유의해야 할 점이 있다. 

    임시 결과의 자료형은 연산자가 참여한 피연산자 중 정보 표현 범위가 더 넓은 자료형이 된다.

    EX. char형과 int형 간의 연산
    : char타입은 2비트 정수형 자료형이고 int는 4비트 정수형 자료형이다.
    따라서 연산의 결과로 얻는 임시 결과의 자료형은 표현 범위가 더 넓은 int이다.

    같은 이치로 float와 double형 간의 연산은 표현 범위가 더 넓은 double형을 결과로 얻게 된다.

    이렇게 연산 결과가 피연산자의 자료형보다 표현범위가 더 넓은 형식으로 변경되는 현상을 형승격(type promotion)이라 한다.


4. 비트와 바이트

1 Byte = 8 Bit

 

1bit => 더 이상 쪼갤 수 없는 메모리의 최소 단위로, 있다(1) 없다(0)의 상태값만 나타낸다.

 

2^10 = 1024개 묶음 = 1Kb

 

1024 Kb => 1MB (약 1천배)

 

1byte(=8bit) 자료형의 경우 최대 256개의 상태를 나타낼 수 있다.(2^8=256)

숫자로는 0~255까지의 수를 표현할 수 있다.

 

2진수에 이를 대입해보면, 모든 메모리가 0으로 채워진 경우가 있을 수 있다. 이를 정수 0이라고 한다.

전부 0인데 첫 번째 메모리에 1이 들어있는 경우 이를 정수 1이라고 한다.

 

1 바이트
(부호비트)
1 바이트
(자료비트)
1 바이트
(자료비트)
1 바이트
(자료비트)
1 바이트
(자료비트)
1 바이트
(자료비트)
1 바이트
(자료비트)
1 바이트
(자료비트)

 

양수의 경우 8bit로 이뤄진 메모리라고 할 때, 7개의 메모리인 2^7 = 128개의 값(0~127)을 나타내며,

맨 왼쪽 부호 비트에 0이 할당된다.

 

음수의 경우에도 동일하게 128개의 값(-1~-128)을 나타내나 부호 비트에 1이 할당된다. 

 

5. 정수와 실수의 차이

메모리가 작을 수록 비트가 작아지므로 그만큼 표현할 수 있는 수의 가짓수가 줄어든다.

실수의 경우, 0~1사이에 무한대에 가까운 많은 숫자가 존재할 수 있다.

따라서 존재할 수 있는 모든 실수를 메모리에 매칭한다는 것은 불가능하다.

한정된 메모리로 이러한 숫자를 나타내는데 한계가 있기 때문이다.

그러므로 실수를 표현할 때는 부동소수점이라는 새로운 체계를 따른다.
부동소수점이란 소수점 이하의 숫자 정보까지 표현하는 것을 말한다.

 

즉, 정수와 실수는 메모리를 다루는 방식, 즉 표현하는 방식 자체가 다르다.

그렇기 때문에 정수와 실수를 함께 연산할 때도 둘의 표현방식이 다르므로 어느 한쪽의 기준에 맞춰서 계산해야 한다.

// 정수와 실수를 함께 사용하는 예
int a = 4 + 4.0;

이 과정에서 형변환등에 실수가 있다면 연산 결과에 문제가 생길 수 있으므로

둘을 혼재해서 쓰는 경우는 특별한 의도가 있는 상황이 아니라면 가급적 피한다.

 

실수의 표현에서는 특정 실수에 딱 맞는 비트가 존재하기 보다는 해당 실수를

표현하기 위한 가장 근사한 값으로 비트를 잡아주는 것에 가깝다.

 

그렇기 때문에 실수를 다룰 때 중요한 것은 실수가 정밀하게 표현되는 타입이 아니므로

연산 전에 예상했던 결과값과 실제 연산 최종값이 다르게 나타날 수 있다.

따라서 특정 조건에 충족을 할때도 있고 아닐 때도 있게 된다.

결론적으로 조건식에 실수의 연산 결과값을 사용하면

이러한 실수의 정밀도 차이로 인한 문제가 발생할 수 있음을 인지해야 한다.

// 정수 표현 방식과 실수 표현 방식은 다르다.
// 실수 표현 방식은 정밀도에 의존한다.
// 따라서 double(8) 자료형이 flaot(4)보다 더 아래의 소수점까지 정확하게 표현이 가능하다.

// 가능한 정수는 정수끼리, 실수는 실수끼리 연산하되, 두 표현방식의 피 연산자가 연산될 경우
// 명시적으로 형을 변환하는 것이 좋다.

// EX) 형변환을 명시적으로 적어서 연산하기
float f = 10.2415f + (float) 20;

// 캐스팅을 하지 않아도 연산은 진행되지만 이 경우 도출된 결과가 의도를 한 것인지 아닌지 알 수 없으므로
// 명시적으로 확실하게 변환을 시켜주자.


위에서 서술했듯이 float는 유효 자릿수가 소수점 이하 6자리까지 이므로 정밀도가 비교적 낮은 편에 속해 오류가 발생할 위험성이 높다. 따라서 대부분 실수 자료는 float형 보다는 double형을 사용하는 것이 좋다.

실수상수를 표기할 때, f를 붙이지 않으면 자동으로 double형으로 처리된다.

 

5.5 상수

상수의 종류에는 정수 상수와 기호 상수가 있다.

  • 정수 상수
    12나 100과 같이 숫자로 표기한다.

    정수 상수는 기본적으로 int형으로 간주되지만 만약 int의 범위를 넘게 되면 컴파일러가 알아서 long형으로 취급한다.
    long형으로도 부족한 경우 unsigned long형으로 변경될 수도 있다.
    이처럼 컴파일러는 상수값을 처리할 때 가급적 자료형에서 가장 작은 자료형을 선택하는 특성이 있다.

    명시적으로 상수의 자료형을 지정하고 싶을 때는 접미사를 붙여 이를 나타낼 수 있다.
    예를 들어 아래와 같은 경우는 접미사 L을 붙여 123이라는 상수를 long으로 나타낸다.

    sum = 123L;

    123만 있는 경우 int형이 16비트인 시스템에서는 16비트의 123이 된다.
    하지만 123L이라고 하면 32비트의 123이 된다.

 

6. 대입 연산자

= 연산자를 대입 연산자라고 부른다.  = 의 좌변에는 항상 변수가 위치하고 우변에는 값이 위치한다.

= 연산자는 우변의 값을 좌변의 변수에 저장한다.

정확히 말하자면, 변수는 메모리 주소의 이름이므로 대입 연산자는 특정 주소에 정보를 담는(복사하는) 역할을 한다.
따라서 대입 연산자가 수행되면 왼쪽 피연산자의 정보에 새로운 정보를 덮어쓰게 되므로, 기존 정보는 유실된다.

대입 연산자의 왼쪽 피연산자를 l-value라고 하고, 오른쪽 피연산자를 r-value라고 한다.
l-value의 l은 Left의 L도 되지만 Locator(위치 지정자)라는 의미도 가지고 있다.

따라서 왼쪽 피연산자는 '정보를 담을 수 있는 변수'라는 뜻이다.
정보를 담을 수 있는 변수가 아니라면 l-value가 될 수 없다.

7. 산술 연산자

// 연산자
// 대입 연산자, =

// 산술 연산자
// +, -, *, /, %(모듈러스, 나머지, 피연산자가 모두 정수일 때만 가능
// 실수의 경우 어느 소수점에서 끊어서 계산할지 모호하기 때문에 연산이 성립되지 않음)
// ++, --
int data = 10 * 10;
//data = data + 20;
data += 20;

// data가 int형이므로 double 타입의 실수를 넣을 수 없음. int로 캐스팅 해주어야함
data = (10.f / 3.f);

// int로 결과값을 캐스팅 해준 경우
data = (int)(10.f / 3.f);

 

  • 나눗셈 연산에서 주의해야 할 점
    - 정수를 정수로 나눈 결과는 정수이다. 따라서 int / int 결과값은 소수점 이하가 절사된 채 정수 부분만 가지게 되므로,
    %f로 출력할 수 없다. (출력하려 하면 실제 결과값 대신 0이 출력된다. 정수와 실수의 표현 체계가 다르기 때문이다.)

    - 나눗셈 연산시 어떤 값이든 절대 0으로 나눌 수 없다. 만약 0으로 나누려고 시도하게 되면 어떤 연산 장치도 0으로 나눌 수 없게 되므로 값을 구할 수 없어진다. 결과적으로 연산이 영원히 끝나지 않게 되므로 프로그램이 죽게 된다.


  • 나머지 연산자에서 주의해야 할 점
    나머지 연산자 역시 앞서 살펴본 나눗셈 연산처럼 값을 0으로 나눌 수 없다.

    또한 나머지 연산자의 피연산자로 실수가 사용될 수 없다.
    따라서 '12.3 % 3' 혹은 '12.3F % 3' 같은 연산식은 문법적으로 허용되지 않는다. 

    나머지 연산자의 우선순위는 곱셈, 나눗셈 연산자와 동일하다.
    그러므로 사칙 연산 중에서 덧셈과 뺼셈보다 우선 연산된다.

    나머지 연산자는 보통의 연산자 이상의 의미를 가진다.
    나머지 연산의 결과는 제수(※)보다 클 수 없기 때문에 나머지 연산은 제수만큼 경우의 수를 갖는다.
    이를 활용하면 일정 범위로 경우의 수를 강제할 수 있다.
※  나눗셈에서의 제수와 피제수

- 제수 : 나눗셈에서 어떤 수를 나누는 수.
EX. 10 / 5 = 2 에서 '5'가 제수가 된다.

- 피제수 : 어떤 수나 식을 다른 수나 식으로 나눌 때 처음의 수나 식.
EX. 6 / 3 = 2에서 '6'이 피제수가 된다.

 

  • 복합 대입 연산자 (+=, -= 등)
    복합 대입 연산자란 기존의 단순 대입 연산자에 산술 연산자나 비트 연산자를 조합해 새로운 하나의 연산자로 만드는 것이다.
    따라서 연산을 두 번 실행한다.

    일종의 누적을 실행하는데, 이 때 누적할 변수는 누적 연산에 앞서 반드시 0으로 초기화를 해야 한다.

    그렇지 않으면 쓰레기 값이 들어가 오류가 발생하게 된다.


  • 단항 증감 연산자
    단항 증감 연산자는 '--'와 "++"로 표시되는 연산자를 뜻한다.
    피연산자에 저장된 정보를 1씩 증가 또는 감소시키는 역할을 하는데, 이때 기본 전제로 피연산자가 l-value여야 한다.
    즉, 단순 대입 연산자의 왼쪽에 올 수 있는 쓰기 가능한 변수여야 연산이 실행된다.

    단항 증감  연산에서 문법적으로 주의해야 할 부분은 전위식 표기(++x)인 경우에는 연산자 우선 순위가 높지만,
    후위식 표기(x++)일 때는 우선순위가 사실상 최하위가 된다는 것이다.

    따라서 단항 증감 연산자를 후위식으로 표현하면 대입 연산자보다도 나중에 실행되므로
    마치 그 단항 연산이 없는 것처럼 생각하는 것이 낫다.

    대입까지 모든 구문이 다 끝나야만 비로소 단항 증감 연산이 수행되기 때문이다.

    이 우선순위가 바뀜에 따라서 후에 클래스나 구조체의 오퍼레이터를 호출할 때 의도치 않은 문제가 발생할 수 있다.
    따라서 습관적으로 전위 연산자를 사용하는 것이 좋다.

 

< 단항 증감 연산과 복합 대입 연산>

단항 증감 연산은 기존의 복합 대입 연산자와 비슷해보이는 점이 있다.
실제로 x += 1 와 ++x는 의미와 실행 결과가 같다.
그러나 문법적으로는 단항 연산이란 점에서 다르며 연산자의 우선순위가 전혀 다르다.

또한 결과적으로 보면 ++x는 x에 1을  누적하는 연산이지만, 정확히 말하자면 누적이라기 보다는 계수(counting)다.
이 계수 역시 누적에 기반을 두기 때문에, 복합 대입 연산자로 누적을 구현할 때 그러했듯이
계수기 변수도 연산 전 반드시 변수를 0으로 초기화 해야만 쓰레기값으로 인한 오류가 발생하지 않는다.

 

 


8. 논리 연산자

// 논리 연산자
// !(역), &&(And), ||(Or)
// 참(True), 거짓(False)
// 참 : 1 (0을 제외한 값, 주로 1로 표기)
// 거짓 : 0


bool truefalse = false; // 디버깅 해보면 false이므로 0이 나옴
bool IsTrue = 100; // bool 자료형에 정수를 넣으면 이것을 true 상태로 받아들여 디버깅시 1이 된다.
// 따라서 bool 자료형은 0,1만 다루는 1byte 자료형이므로 숫자를 표현하기에는 적합하지 않다.

int iTrue = true; // 1이 할당됨
iTrue = !iTrue; // !(역 연산자)로 인해 1(true)의 반대인 0(false)이 할당된다.

// 원래 c에선 true, false를 전처리기에서 따로 처리했어야했는데 c++부터 기본 연산자가 되었다.


// 논리곱으로 연산해보기
// 둘 다 0이 아닌 수이기 때문에 참이므로 iTrue는 1(True)가 된다.
iTrue = 100 && 200;

// 앞의 숫자가 0이므로 거짓이기 때문에 iTrue는 0(false)가 된다.
iTrue = 0 && 200;

// 논리합으로 연산해보기
// ||(OR) 연산은 둘 중 하나만 참이면 참이 되므로 iTrue는 1(True)가 된다.
iTrue = 0 || 300;

// 두 개 모두 0(false)이므로 iTrue는 0(False)가 된다.
iTrue = 0 || 0;

// 주의! 1만 True가 아니라 연산자에서는 0을 제외한 모든 값을 True라고 본다. 
// bool 자료형은 값을 0 또는 1로만 구분하기 때문에 숫자 100, 200 등을 넣으면 모두 1로 간주한다.
// 이런 특성을 활용하는 경우가 많다. (조건문의 조건으로 쓴다던지)
// 이 때 값을 유지한 채로 값이 참인지 거짓인지를 즉시 활용할 수 있다.
// (700 등의 값을 넣으면서 0이 아니므로 참으로 판별한다던지)


// (비교) 구문
// if, else
// switch case -> if, else와 결은 다르지만 if else문과 같이 비교 구문이다.


// 삼항 연산자
// :?

 

  • 논리 연산의 특징

    1. 논리 연산은 왼쪽에서 오른쪽으로 진행한다.
    2. 경우에 따라 나중에 수행될 연산을 수행하지 않고도 전체 결과를 확정할 수 있다.

    이처럼 앞에서 연산 결과가 확정되면 뒤의 연산은 생략하여 논리식의 효율을 꾀하는 것을 쇼트서킷(short circuit)이라고 한다.
<쇼트서킷의 특징>

1. 논리 연산식은 반드시 왼쪽에서 오른쪽으로 수행한다.

2. OR 논리식은 조건에 만족하면 이후 연산을 생략한다.

3. AND 논리식은 조건에 부합하지 않으면 이후 연산은 생략한다.

4. 혼합 논리식에서 AND는 한 덩어리로 묶고 OR의 연속으로 살핀다.


9. 비트 연산자

  • 비트 연산자는 자료형을 근거로 해석된 정보가 아닌, 일정 길이의 메모리에 담긴 2진수 정보를 비트 단위로 계산하는 연산자를 뜻한다.
    만일 8비트 정보라면 여덟 개의 비트 각각에 대해 여덟 번 연산하는 것과 같다.

    C에서는 비트 연산자로 &(AND), OR(|), XOR(^), NOT(~), Shift Left (<<), Shift right(>>)가 있다.
    이중 NOT(!)만 단항 연산자이고, 나머지는 모두 이항 연산자이다.
    비트 단위의 상수를 기술할 땐 보통 16진수를 사용한다.

 

unsigned char byte = 13;

byte <<= 3; // 2^n 배수
byte >>= 2; // 2^n 나눈 몫

// 초기값이 홀수인 13이지만 비트 이동을 하고 나면 나머지는 소실되고 2의 배수인 몫만 남게 된다.

// 비트 곱(&), 합(|), xor(^), 반전(~)
// 비트 단위로 연산을 진행하며,
// 곱 연산(&)은 두 비트가 모두 1인 경우 1
// 합 연산(|)은 두 비트중 하나라도 1인 경우 1
// XOR 연산(^)은 두 비트가 서로 같으면 0이고 다르면 1
// 반전 연산 (~)은 0은 1로, 1은 0으로 변경

 

 

  • 마스크 연산
    상수 0과 변수의 특정 비트열에 대해 비트 AND 연산으로 일정 구간이 모두 0이 되도록 의도한 연산을 마스크(mask) 연산이라고 한다. 이 연산을 통해 불필요한 정보를 가리고 필요한 정보만 골라낼 수 있으므로 마스크라 표현한 것이다.

    만일 비트 AND 연산의 한쪽 피연산자의 모든 비트가 1이면, 다른 한쪽 변수의 값이 무엇이든 그 결과는 변수 값 그대로다. 하지만 상수 0인 경우에는 결과가 무조건 0이 된다.

    정보를 잘라내기 위해 시프트 연산을 사용할 수도 있지만, 꼭 필요한 정보만 걸러내거나 특정 스위치를 켜거나 끄기 위한 연산 등을 쉽게 해낼 수 있으므로 마스크 연산이 활용된다.

 

10. 형변환 연산자

int x = 5;

printf("%f\n", (double)(x/2)); // 결과값이 double이 된다.


형변환 연산자(type cast)는 피연산자의 자료형을 강제로 새로운 자료형으로 변경하는 단항 연산자이다.
이전에 배운 연산자들보다 우선 순위가 높은 연산이기도 하다.

따라서 덧셈 또는 곱셈과 같은 산술 연산자와 함께 사용하면 이 형변환 연산자가 먼저 수행된다.

형변환 연산자는 강제로 자료형을 변경하기 때문에 적절한 변환이 아닌 경우에도 연산이 수행되버린다.

따라서 심각한 논리 오류나 정보의 손실이 발생할 가능성이 있기 때문에 사용시 주의해야 한다.

11. 연산자 응용

앞서 살펴본 기본 연산자들처럼 직접적인 숫자 계산처럼 보이진 않지만 명백히 연산자가 맞는 연산자들이 있다.
이번에는 이러한 연산자들에 대해 알아보려 한다.

  • sizeof 연산자
    sizeof 연산자는 피연산자의 자료형에 대한 연산이다.
    즉, 피연산자가 자료형이라는 것이다.

    따라서 sizeof(5)라는 연산은 정수 5를 뜻하는 것이 아닌, sizeof(int)라는 연산을 의미한다.

    int형의 바이트 단위 메모리 크기가 4바이트이므로, sizeof(int)의 연산 결과는 4가 된다.
    그러므로 sizeof(5)의 연산 결과도 동일하게 4가 된다.

    또한 sizeof 연산자는 프로그램을 빌드하고 CPU가 실행하는 런타임 연산자가 아니라 컴파일러가 컴파일타임에 수행하는 연산자이다. 그렇기 때문에 아무리 많이 사용하더라도 프로그램의 수행능력이 떨어지지 않는다.

    프로그램을 작성하다 보면 변수의 메모리 크기를 생각하고 거기에 맞춰서 코드를 작성할 일이 많다.
    예를 들어 다음과 같은 코드가 있다고 가정하자.

int aList[5];
wchar_t wch;

 

int는 4바이트 크기를 가지므로 int 5개가 한 묶음인 aList는 20바이트 크기를 가진다.
그러므로 소스코드에서 aList 배열의 크기를 명시할 때 20이라는 숫자를 직접 기입할 수도 있다.

하지만 이는 매우 부적절한 코딩 방식이다.
배열 요소의 개수는 추후에 변경될 수 있는 여지가 있기 때문이다.
그러므로 20이라고 직접 숫자를 기입하기 보다 sizeof(aList)로 처리하는 것이 적절하다.

또한 wchar_t 역시 환경에 따라 2바이트 또는 4바이트가 될 수 있기 때문에, 바로 숫자로 크기 값을 표기하기보다

sizeof(wchar_t)로 쓰는 것이 다양한 환경에 대응할 수 있는 좋은 코드가 된다.

  • 관계 연산자
    관계 연산자는 내부적으로 상등/부등, 비교 연산자로 분류할 수 있으며,
    두 피연산자의 값을 비교해 참 또는 거짓의 결과를 내는 연산자이다.
    피연산자로 변수나 상수를 사용하는 것이 일반적이지만 연산식이 오는 경우도 많다.


    A == B : 상등 연산으로, A는 B와 같다. (즉, 둘의 뺄셈 결과가 0이다.)

    A != B : 부등 연산으로, A는 B와 같지 않다.

    A > B : 비교 연산으로, A는 B보다 크다.

    A < B : 비교 연산으로, A는 B보다 작다.

    A >= B : 비교 연산으로, A는 B보다 크거나 같다. (B 이상이다)

    A <= B : 비교 연산으로, A는 B보다 작거나 같다. (B 이하이다)
<C언어에서의 참의 정확한 의미>

참 (true)를 %d로 출력하면 1이 되고, 거짓 (false)를 출력하면 0이 된다.
그렇기 때문에 참 (true)는 1이라고 생각하기 마련이다.

하지만 c언어에서 '참'의 진정한 의미는 "0이 아닌 모든 값" 이다.
따라서 값이 1이든, 400이든 모두 0이 아니므로 bool 연산자로 받아보면 참으로 표시된다.

 

비교 관계 연산자에서는 두 값의 비교를 위해서 내부적으로 뺄셈을 실행한다.
정확한 값을 알 수 없는 임의의 두 숫자가 서로 같은 숫자인지 여부를 사칙연산으로 알아보려면
가장 직관적이고 명백한 방법이 바로 두 수를 빼서 결과가 0인지를 확인해보는 것이기 때문이다.

따라서 둘의 뺄셈을 진행하여 결과가 0이 아니면 다시 양수인지 음수인지를 확인한다.
이때 A - B 연산의 결과가 양수라면 A는 B보다 크다는 것을 알 수 있다.
이런 식으로 관계 연산은 뺄셈 연산이 응용된다.

만일 문자열처럼 배열 형태의 정보에 대해 관계 연산을 수행하고 싶다면 각 요소 하나하나를 일일이 비교해야 한다.
따라서 대부분 비교를 위한 전용 함수를 사용한다. 함수를 사용할 때도 연산자를 사용할 때와 마찬가지로,
두 대상이 같다면 함수가 0을 반환한다. 이런 함수로는 memcmp()와 strcmp() 함수가 있다.

이전 연산자들과 마찬가지로 관계 연산 또한 정수와 실수의 조합으로 상등 연산을 수행할 경우
전혀 엉뚱한 결과를 얻을 수 있기 때문에 피연산자의 자료형이 매우 중요하다.

 

12.  if / else 구문

// 비교 연산자
// ==, !=, <, >, <=, >=
// 참, 거짓

// 구문
// if, else

data = 0;

// 조건식이 참이므로 스코프 안의 구문이 실행 됨.
if (100 && 200)
{
	data = 100;
}

// 위 조건식이 참이고 스코프 안에서 data 변수에 100을 할당하였으므로 참이 된다.
// 하지만 조건식이 거짓이 되게 된다면 아래 조건식 역시 거짓이 된다.
if (data == 100)
{

}

// if, else, else if 문
if (data == 100)
{
	// if가 참인 경우 수행
}
else if()
{
	// 위의 조건이 거짓일 때 이 조건문 체크 후 참일 시 수행
    // 이 조건식이 거짓이 경우 아래의 else문 스코프 안에 있는 내용을 수행하도록 넘어감
}
else
{
	// 위의 조건에서 if가 다 거짓인 경우 수행
}

// else if문과 각자 따로 수행되는 여러개의 if문 중 어느 것을 사용할지 잘 생각해봐야한다.
// 예를 들어서 wasd로 캐릭터를 이동시키는 movement 로직을 만든다고 할 때,
// 키보드가 눌리는 순서를 w->a->s->d 순으로 else if문을 이용해 조건 검사를 하면
// w와 a를 동시에 누르는 경우 대각선으로 이동하는 것이 아니라
// 조건 검사를 위해 w 이동을 건너 뛰어버리기 때문에 위쪽 방향으로 이동할 수 없다.
// 이런 경우에는 else if문이 아닌 각각 4개의 if문을 사용하여 조건 검사를 개별적으로 하도록 해야 한다.

 

13. switch 구문 / 삼항 연산자

switch (10) // 원하는 특정 변수 또는 값을 넣어준다
{
// 위에서 사용한 변수와 케이스 안의 조건 값을 비교해서 해당 케이스와 일치하는 쪽 구문을 실행한다.
case 10: 
	// 여기서 변수 값과 조건 값이 일치하므로 break문을 만나기 전까지의 로직을 실행한다.
    break;

case 20:

	break;
    
// 여기까지 조건을 검사했을 때 조건에 맞는 경우가 없을 때 아래의 디폴트문을 수행하게 된다. 
default:
	break;
// 만약 케이스문에 break가 없는 경우 그 아래로 지정된 케이스문 로직도 함께 작동되므로
// break문의 사용에 유의해야한다. 이를 활용해 의도적으로 break문을 안 쓸 수도 있게 된다.
}

int iTest = 10;
if (iTest == 10)
{

}

else if (iTest == 20)
{

}

else
{

}

// 3항 연산자
// 조건식 ? 조건식이 참인 경우 실행되는 것 : 조건식이 거짓인 경우 실행되는 것
iTest == 20 ? iTest = 100 : iTest = 200;

// 삼항연산자로 나타낸 다음 식을 if else문으로 동일하게 나타내면 다음과 같다.
if (iTest == 10 || iTest == 20 || iTest == 30)
{

}

else
{

}

 

  • 조건 연산자 (= 삼항 연산자)
    조건 연산자는 C 언어에서의 유일한 삼항 연산자이다. 사용방법과 문법구조 면에서 이전에 배운 연산자들과 다르게 피연산자로 조건식을 필요로 한다. 또한 그 조건의 결과로 피연산자인 두 개 항에서 하나를 '선택' 한다.

    조건식 ? A : B

    조건식이 참이면 A를 선택하지만, 그렇지 않으면(거짓이면) B를 선택한다. 따라서 A와 B가 동시에 선택될 수 없다.

    또한 삼항 연산자의 두 번째, 세 번째 피연산자는 변수나 상수 하나(항)이어야 한다. 따라서 선택 대상 피연산자가 연산식이라면 아래의 예시처럼 반드시 괄호로 묶어야 한다.

nInput <= 10 ? (nSelect = 10) : (nSelect = 20);


sizeof()연산이나 주소 연산자(&)와 같은 컴파일 타임 연산을 제외하고, 연산에 참여하는 피연산자는 변수 그 자체가 아니라 그 안에 담긴 값이라는 점을 유의하도록 하자.

'C,C++ > [강의] 어소트락 C++' 카테고리의 다른 글

함수와 재귀함수  (0) 2022.11.28
VS 단축키 및 편의사항  (0) 2022.11.25
함수, 반복문  (0) 2022.11.24
define / 비트연산자  (0) 2022.11.23
C++을 쓰는 이유  (0) 2022.11.22
작성일
2022. 11. 22. 13:34
작성자
risehyun

 - C++이 다른 언어에 비해 가지고 있는 장점 중 하나는 실행 속도다.

 - 고수준 언어 중에서 작성한 스크립트가 가상머신을 통해 실행되는 다른 언어들과 달리 C++은 기계어와 가장 가까운 언어이기 때문에 속도가 빠르다.

 - 그렇기 때문에 가상머신이 메모리 관리를 대신해주는 Java, C#과 달리 C++은 개발자가 직접 메모리를 관리해주어야 하며 실수할 여지가 많다. 하지만 직접 메모리를 관리할 수 있다는 점이 최적화나 효율면에서 장점이 되기도 한다. (양날의 검)

- C++을 사용하려면 이런 배경과 문제를 인지하고 디버깅할 수 있는 숙련도가 필요하다.

'C,C++ > [강의] 어소트락 C++' 카테고리의 다른 글

함수와 재귀함수  (0) 2022.11.28
VS 단축키 및 편의사항  (0) 2022.11.25
함수, 반복문  (0) 2022.11.24
define / 비트연산자  (0) 2022.11.23
주석, 변수, 자료형, 연산자  (0) 2022.11.22
작성일
2022. 10. 14. 22:39
작성자
risehyun
작성일
2022. 10. 12. 22:22
작성자
risehyun
작성일
2022. 10. 10. 22:43
작성자
risehyun
작성일
2022. 10. 9. 21:16
작성자
risehyun