본문 바로가기
프로그램 개발/C++ 개발

C++언어 간략한 문법 2

by minchel1128 2021. 5. 14.

이전 게시글에서 이어서 진행됩니다.

1. 연산자

변수들을 사용하는 경우 해당 변수들의 값을 변경할 필요성이 있으며 그 값들을 변경하기 위해서 변수를 사용하게 됩니다. 그리고 변수들에 저장된 값을 더하거나 빼는 등의 연산을 하여 값을 바꾸는데 그때 사용하는 것을 연산자라고 합니다.

연산자는 단항 연산자, 이항 연산자, 삼항 연산자로 되어 있으며 단항 연산자는 하나의 표현식만 사용하고 이항 연산자는 두 개의 표현식을 계산하고 삼항 연산자는 현재 C++에서는 딱 한 개만 있으며 이후 조건문에서 추가로 설명하겠습니다. 그리고 주로 사용하는 연산자의 예는 다음과 같습니다.

연산자 설명 사용 예
= 오른쪽의 값을 왼쪽의 표현식에 대입하는 이항 연산자
주의할 점으로 같다는 의미로 사용하지 않습니다.
int i;
i=3;
! 표현식의 참/거짓(또는 0이 아닌 값과 0)을 반전시키는 단항 연산자 bool b = !true;
bool b2 = !b;
+
-
*
/
덧셈, 뺄셈, 곱셈, 나눗셈을 나타내는 이항 연산자
나누기 연산을 하는 경우 0으로 나누면 0나누기 에러가 발생하니 조심해야 합니다.
int i = 3 + 2;
int j = i + 5;
int k = i + j;
% 나머지 연산자로 부르며 다른말로 모드연산자 라고도 부름, 나눗셈의 나머지를 계산하는 이항 연산자 int i = 5 % 2;
++ 표현식의 값에 1을 더하는 단항 연산자. 이 연산자가 표현식 뒤에 오면 그 표현식의 결과 자체는 바뀌지 않고 다음 문장부터 1이 더해진 값이 적용(사후 증가) 앞에 오면 표현식의 결과에 곧바로 1을 더한 값이 반영(사전 증가) i++;
++i;
-- 표현식의 값에 1을 빼는 단항 연산자. 이 연산자가 표현식 뒤에 오면 그 표현식의 결과 자체는 바뀌지 않고 다음 문장부터 1이 빠진 값이 적용(사후 감소) 앞에 오면 표현식의 결과에 곧바로 1을 뺀 값이 반영(사전 감소) i--;
--i;
+=
-=
*=
/=
%=
i와 j가 임의의 변수일때 각각
i = i + j;
i = i - j;
i = i * j;
i = i / j;
i = i % j;

의 축약
i += j;
i -= j;
&
&=
양쪽에 나온 표현식끼리 비트 단위 AND 연산을 수행한다.
정수인 경우 각 정수를 2진수로 변환하여 AND연산을 한다고 보시면 됩니다.
i = j & k;
j &= k;
|
|=
양쪽에 나온 표현식끼리 비트 단위 OR 연산을 수행한다.
해당 키는 일반 두벌식 키보드 기준 시프트 원화 키(역슬래시 키) 입니다.
i = j | k;

<<
>>
<<=
>>=
왼쪽에 나온 표현식의 비트값을 오른쪽에 나온 수만큼 왼쪽 또는 오른쪽으로 시프트 연산을 수행
1번 수행이 *2혹은 /2와 비슷한 형태로 진행됩니다.
i = i << 1;
i = i>> 4;
^
^=
양쪽 표현식에 대해 비트단위 XOR연산을 수행한다. i = i ^ j;

C++에서는 해당 연산자의 우선순위 별로 진행을 하게 되는데 일반적으로 /(나눗셈),*(곱셈),%(나머지) 연산을 먼저 수행하고 그다음 +(덧셈), -(뺄셈) 연산을  마지막으로 비트 연산을 수행하고 같은 우선순위인 경우 왼쪽을 우선으로 연산을 합니다. 다만 괄호를 최우선으로 연산을 하므로 그냥 먼저 했으면 하는 부분에 괄호를 치면 됩니다.

2. 열거 타입

C++에서는 기본 타입인 int나 double, bool 등을 조합해서 더 복잡한 타입을 정의를 할 수 있고 C에서 사용하던 열거 타입과 구조체 또한 그대로 사용 가능합니다. 다만 열거 타임과 구조체는 클래스로 대신하여 사용하는 것이 일반적입니다.

열거 타입은 연속적으로 나열한 숫자 중 하나를 표현한 것이고 숫자를 나열하는 방식과 범위를 마음대로 정의해서 변수를 선언하는데 활용할 수 있습니다. 예를 들어 int형으로 상수를 표현한다고 하였을 때 const 키워드를 붙여 정의를 할 수 있습니다. 다만 이 경우 다른 변수로서 해당 값을 가져온다고 하였을 때 그 변수의 값을 바꾸게 되면 다른 상수값을 가져오는 경우와 같게 되므로 의도한 대로 프로그램이 작동하지 않을 수 있습니다. 따라서 변수에 지정할 수 있는 값을 제한하는 enum타입을 이용해 제한을 할 수 있습니다.

const int A = 0;
const int B = 1;

int num = A;
//위 대신 아래 사용
enum num { A, B};

enum타입을 사용하게 되면 타입을 구성하는 멤버는 내부적으로 정수 값을 지니게 되고 해당하는 변수에 사칙연산을 수행하거나 다른 정수 값처럼 다루는 코드를 작성하게 되면 컴파일 에러가 발생하게 된다.

또한 각 멤버에 해당하는 정수 값을 직접 지정도 가능하다.

enum number { A = 1, B, C = 10, D };

위와 같이 지정하게 되면 a는 1을 같고 B는 2의 값을 가지고 C는 10을 D는 11의 값을 가지게 됩니다. enum타입에서는 직접 값을 지정하지 않는다면 이전 값에서 1을 더한 값을 가지게 되고 만약 지정되지 않은 값이 첫 번째(예시에서는 A) 값인 경우 0을 대입하게 됩니다.

기존의 enum에서는 타입을 엄격하게 따지지는 않기 때문에 모든 enum타입은 정수로 해석되어 서로 비교할 수 있게 됩니다. 이를 변형하여 타입을 엄격히 따지는 즉 스트롱 타입 혹은 타입에 안전하다(타입 세이프하다)는 의미를 더한 enum class가 추가되었습니다.

enum class로 정의한 열거 타입 값들의 이름은 유효 범위가 자동으로 확장되지 않으며 enum class안에서만 유효합니다. 따라서 열거 타입 값을 사용할 때마다 스코프(유효 범위) 지정 연산자를 붙여야 합니다.

//값을 지정
enum class number
{
    A = 1,
    B,
    C = 10,
    D
};

//값을 가져올 때
number num = number::A;

이렇게 지정한 경우 열거 타입 값의 이름을 짧게 부를 수 있다는 것으로 위의 예시에서 number::A 대신 num만 사용해도 되게 됩니다. 또한 enum class로 지정한 열거 타입 값은 자동으로 정수로 변환 대지 않아 일반적인 비교문을 사용할 수 없습니다. 다만 강제형 변환 즉 static_cast <int>()를 사용한다면 표현이 가능합니다.

그리고 기본적으로 열거 타입의 값은 정수 타입으로 지정되지만 타입을 바꿀 수 있습니다.

enum class number : unsigned long
{
    A = 1,
    B
};

위와 같이 다른 타입을 지정 가능하고 열거 타입을 이용할 때는 타입에 안전하지 않은 enum보다는 enum class를 사용하는 것을 권장합니다.

3. 구조체

구조체를 사용하면 기존에 정의된 타입을 한 개 이상 묶어서 새로운 타입으로 정의를 할 수 있습니다. 주로 사용하는 예시로는 데이터베이스 레코드가 있습니다. 대표적인 예로 사용하는 직원 정보 관리 시스템인데 이 경우 직원의 이름, 사원번호, 급여정보 등을 저장하게 됩니다. 이렇게 구축하는 경우에는 다음과 같이 정의가 가능합니다.

//직원 구조체 정의
struct Employee {
    char firstInitial; // 문자 타입 이름
    char lastInitial; // 문자 타입 성
    int employeeNumber; // 정수 타입 사원 번호
    int salary; // 정수 타입 봉급
};

구조체는 주로 헤더 파일에 정의를 하게 되며 각 타입으로 선언한 변수는 이 구조체에 있는 모든 필드를 가지게 됩니다. 구조체를 구성하는 각 필드는 도트 연산자(.)로 접근을 합니다. 그리고 구조체는 기본적으로 매개 함수가 없는 class와 유사하며 내부 매개변수들은 public으로 선언한 것과 같은 역할을 담당합니다. 해당 코드를 사용한 예시는 다음과 같습니다.

#include <iostream>
#include "employeestruct.h"

using namespace std;

int main(void)
{
    //직원 레코드 생성 및 값 채우기
    Employee anEmployee;
    anEmployee.firstInitial = 'M';
    anEmployee.lastInitial = 'G';
    anEmployee.employeeNumber = 42;
    anEmployee.salary = 80000;
    
    //직원 레코드에 저장된 값 출력
    cout << "Employee: " << anEmployee.firstInitial << anEmployee.lastInitial << endl;
    cout << "Number : " << anEmployee.employeeNumber << endl;
    cout << "Salary : $" << anEmployee.salary << endl;
    return 0;
}

해당 코드를 직접 실행해 보면 다음과 같이 나옵니다.

012
VisualStudio에서 적어서 실행

4. 조건문

조건문은 참과 거짓의 조건에 따라 주어진 코드를 실행할지 결정하는 역할을 담당합니다. 주로 사용하는 예시로는 if-else문, switch문, 조건 연산자가 있습니다.

가장 많이 사용하며 대표적인 조건문으로는 if-else문이 있습니다. if-else문은 영어 뜻 그대로 만약-또 다른 의미로 사용합니다. 대표적인 구성 방식으로는 다음과 같습니다.

// 기본형 1
if ( /* 조건 */ )
{
    // 작업 1
}

// 기본형 2
if ( /* 조건 */ )
{
    // 작업 1
}
else
{
    // 작업 2
}

// 중첩형
if ( /* 조건 1 */ )
{
    // 작업 1
}
else if (/* 조건 2 */)
{
    // 작업 2
}
else
{
    // 작업 3
}

조건 부분에서는 부울형 혹은 결과가 부울 값인 표현식을 넣어야 하며 C++에서는 정수 0과 false랑 같은 의미로 동작으로 하고 0이 아닌 정수가 true랑 같은 역할을 담당합니다.

조건이 참인 경우 if안의 작업을 수행하고 조건이 거짓이 되는 경우 else 부분을 실행합니다. 중첩되어 있는 경우에는 조건을 여러 번 확인을 거치는데 else구문은 가까이에 있는 if구문과 같이 연결이 되므로 이 점 주의하시기 바랍니다.

C++17부터는 if문 안에 이니셜 라이저(초기자)를 넣는 기능이 추가되었습니다. 

if (<이니셜라이저> ; <조건문>) { <본문> }

위와 같은 문법을 가지고 있으며 이니셜 라이저에서 정의한 변수는 조건문과 본문 안에서만 사용 가능하며 if문 밖에서는 사용이 불가능합니다.

사용하는 예로는 다음과 같습니다.

if ( Employee employee = GetEmployee() ; employee.salary > 1000)
{
    // 본문 내용
}

 

switch문은 조건에 따라 수행할 동작을 선택합니다. C++에서는 switch문에서 지정할 수 있는 표현식은 결괏값이 반드시 정수 타입, 정수로 변환이 가능한 타입(실수나 문자), 열거형, 엄격한 열거형이 들어가며 상수값과 비교가 가능해야 합니다. switch문에서는 다양한 경우를 가지는 상수값을 case문으로 지정하여 일치하면 case가 있는 코드로 이동하여 brake를 만날 때까지 코드를 실행합니다. 만약 일치하는 구문이 없을 경우 default문으로 이동하게 됩니다. 따라서 예시로는 다음과 같습니다.

switch (<조건>) {
    case <상수값1>:
        //내용 1
        break;
    case <상수값2>:
        //내용 2
        break;
    default:
        //내용 3
        break;
}

switch문은 if-else문으로 변형이 가능하며 파이썬 등에서는 switch문이 없이 if-else만 있는 경우도 있습니다.

if ( < 조건 >)
{
    //내용 1
}
else if (<조건 2>)
{
    //내용 2
}
else
{
    //내용 3
}

switch문의 경우 테스트한 결과가 아닌 하나의 표현식에서 나오는 여러 값에 따라 동작을 결정하는 용도로 사용해 if-else구문을 연달아 쓰기보다는 switch문을 사용하는 것이 더 간편할 수 있습니다.

그리고 switch문에서는 case를 수행할 때 break를 만나면 조건을 벗어나게 되는데 만약 break를 지정하지 않는다면 폴스로(흘러 보내기)가 발생하고 계속해서 진행하게 됩니다. 즉 원하지 않는 코드가 실행될 문제가 발생을 하게 됩니다.

그래서 C++17부터는 중간에 break문이 없는 경우에는 컴파일러가 경고 메시지를 발생하고 [[fallthrough]]속성(visual studio 2017에서는 [[__fallthrough]]도 사용 가능합니다.)을 지정해서 컴파일러에게 의도적으로 break문을 지정하지 않았다고 알려줄 수 있습니다.

또한 switch문도 if-else처럼 C++17부터는 이니셜 라이저를 지정할 수 있습니다.

switch (<이니셜라이저> ; <표현식>){<본문>}

5. 조건 연산자

조건 연산자는 C++에서 인수를 3개를 받는 유일한 삼항 연산자로 조건을 만족하면 동작 1을 수행하고 그렇지 않으면 동작 2를 수행해라고 지정할 때 사용합니다.

따라서 문법은 다음과 같습니다.

[조건]? [동작 1] : [동작 2]

사용 예시로는 다음과 같이 작성합니다.

std::cout << ((i > 2) ? "yes" : "No");

//조건의소괄호는 선택사항이므로 제외해도 같은 결과가 나옵니다.
std::cout << (i > 2 ? "yes" : "No");

조건 연산자는 코드 블록을 실행할 수 없는 단점이 있습니다. 다만 코드 안 아무데서나 조건문을 표현할 수 있는 장점이 있으며 조건문으로의 역할보다는 연산자의 일종으로 보는 것이 맞습니다.

6. 논리 연산자

조건을 따지기 위해서 논리 연산자를 사용하는 경우가 많습니다. 논리 연산자는 입력받은 두 인수를 비교해서 참과 거짓의 값을 내놓는 연산자로 최종 결과는 true 혹은 false가 나오게 됩니다. 보통은 숫자를 비교하지만 포인터나 문자열 쪽에서도 사용하기도 합니다.

주로 사용하는 논리 연산자로는 다음과 같습니다.

연산자 설명 사용 예
<
<=
>
>=
이상 이하 초과 미만으로 왼쪽값과 오른쪽 값을 비교하는데 사용됩니다.
< : 왼쪽이 더 작은지, <= : 왼쪽이 더 작거나 같은지, > : 왼쪽이 더 큰지, >= : 왼쪽이 더 크거나 같은지 
if ( i < 0 ) 
{
    std::cout << "i는 음수";
}
== 왼쪽 값과 오른쪽 값이 같은지를 비교 '='는 일반 수학에서 등호로 사용하지만 C등의 컴퓨터 언어에서는 오른쪽 값을 왼쪽에 할당하라는 의미로 사용되 주의가 필요합니다. if ( i == 3)
{
    std::cout << "i는 3입니다."
}
!= 왼쪽과 오른쪽 값이 서로 다른지 비교합니다. if ( i != 3)
{
    std::cout << "i는 3이 아닙니다."
}
! 부울 표현식의 값에 대한 반대(보수)를 구하는 논리부정(NOT)에 대한 단항 연산자 if (!boolean)
{
    std::cout << "bolean은 false입니다."
}
&& 논리곱(AND)에 대한 연산자로 양쪽이 모두 참이어야 참이 된다. if ( A && B )
{
    std::cout << "A와 B모두 참 입니다."
}
|| 논리합(OR)에 대한 연산자로 양쪽 중 하나가 참이면 참이 된다 if ( A || B )
{
    std::cout << "A와 B둘 중 하나가 참 입니다."
}

|키는 한국 두벌식 키보드 기준으로 shift를 누르고 원화 키를 누르면 됩니다. 그리고 원화 키는 한국 언어팩에서만 원화로 표시되고 그 이외의 언어(일본은 엔화)에서는 역슬래시로 표현됩니다.

C++에서는 논리 표현식을 단락 논리 방식으로 평가합니다. 다르게 표현하면 한 줄에 여러 인수값을 비교하는 도중에 최종 결과가 결정되면(AND일 때 하나가 false라던가 OR일 때 하나가 true인 경우 등) 남은 뒷부분은 전부 무시하고 바로 넘어갑니다. 이렇게 작업을 하게 되므로 C++은 불필요한 처리를 하지 않으므로 더 빠르게 동작을 할 수 있습니다. 하지만 만약에 뒤에 반드시 수행되어야 하는 계산식 혹은 함수가 들어가 있는 경우라면 특정 조건에서만 해당 코드가 실행이 되므로 버그가 발생하고 이를 찾아내기는 어렵게 됩니다.

bool result = bool1 || bool2 || (i > 7) || (27 / 13 % i + 1) < 2;

위의 예시와 같은 코드인 경우에서 만약 bool1이 참이 된다면 뒤의 값이 모두 거짓이어도 참이 되므로 result는 참값을 가지고 넘어가게 됩니다. 위와 같은 경우에서는 딱히 큰 문제가 발생하지는 않습니다. 다만

bool result = bool1 || bool2 || (i++ > 7) || (27 / 13 % i + 1) < 2 || i > 3 ? print(i--) : print(i);

만약 이런 식으로 함수가 있거나 연산자가 끼어있는 경우가 된 경우 bool1이 참이 되었을 때 뒤에 i값을 변경하는 모든 코드들은 실행이 되지 않아 나중에 이를 실행한다는 가정하에 작업된 모든 코드들이 논리적 에러를 발생하게 되고 이를 찾아내기는 어렵게 됩니다.

단락 기능은 프로그램 성능을 높이는데 도움이 됩니다. 따라서 단락 되는 논리식을 작성하는 경우에는 가볍게 검사 가능한 부분은 앞에 두고 시간이 걸리는 부분은 뒤에 두게 됩니다. 또한 포인터를 이용하는 경우 포인터 값이 올바르지 않은 경우 실행하지 않게 하는 데에도 사용됩니다.

728x90
반응형