헤더파일

C++ 정리 본문

C++

C++ 정리

헤더파일 2019. 10. 29. 15:08

 

 

C++ 컴파일러에서 함수를 오버로딩하는 과정은 다음과 같습니다.

 

1 단계

자신과 타입이 정확히 일치하는 함수를 찾는다.

2 단계

정확히 일치하는 타입이 없는 경우 아래와 같은 형변환을 통해서 일치하는 함수를 찾아본다.

  • Char, unsigned char, short 는 int 로 변환된다.

  • Unsigned short 는 int 의 크기에 따라 int 혹은 unsigned int 로 변환된다.

  • Float 은 double 로 변환된다.

  • Enum 은 int 로 변환된다.

3 단계

위와 같이 변환해도 일치하는 것이 없다면 아래의 좀더 포괄적인 형변환을 통해 일치하는 함수를 찾는다.

  • 임의의 숫자(numeric) 타입은 다른 숫자 타입으로 변환된다. (예를 들어 float -> int)

  • Enum 도 임의의 숫자 타입으로 변환된다 (예를 들어 Enum -> double)

  • 0 은 포인터 타입이나 숫자 타입으로 변환된 0 은 포인터 타입이나 숫자 타입으로 변환된다

  • 포인터는 void 포인터로 변환된다.

4 단계

유저 정의된 타입 변환으로 일치하는 것을 찾는다 (이 부분에 대해선 나중에 설명!)

void display_balance(const Money balance)
{
    std::cout << "The balance is: " << balance.amount << std::endl;
}

Money(double _amount) : amount{ _amount } {}; 이런 생성자가 있을 때 double인자를 Money 클래스로 생각하여 오버로딩

 

 

 

변환 생성자

explicit Money(double _amount) : amount{ _amount } {}; - 묵시적 변환이 허용되지 않는다.

 

display_balance(payable); // Legal: no conversion required
display_balance(49.95); // Error: no suitable conversion exists to convert from double to Money.
display_balance((Money)9.99f); // Legal: explicit cast to Money

 

변환함수

변환 함수는 사용자 정의 형식에서 다른 형식으로의 변환을 정의합니다. 변환 생성자와 함께 이러한 함수는 값이 다른 형식으로 캐스트될 때 호출되기 때문에 종종 "캐스트 연산자"라고 합니다. 다음 예제에서는 사용자 정의 형식에서 변환 하는 변환 함수 Money, 기본 제공 형식 double:

class Money
{
public:
	Money() : amount{ 0.0 } {};
	Money(double _amount) : amount{ _amount } {};

	operator double() const { return amount; }
private:
	double amount;
};

void display_balance(const Money balance)
{
    std::cout << "The balance is: " << balance << std::endl;
}

또는 

Money m;
double a = m; 

이런식으로 사용할 수 있습니다.

 

operator double() const { return amount; } 에 explicit을 붙인다면 (double)로 명시적으로 표시를 해줘야 쓸 수 있습니다.

 

 

산술 표현식 평가하기 (Evaluating arithmetic expressions)

표현식을 평가할 때, 컴파일러는 각 표현식을 개별 하위 표현식으로 나눈다. 산술 연산자의 피연산자는 모두 같은 자료형이어야 하므로 컴파일러는 다음과 같은 규칙을 사용한다.

  • 피연산자의 자료형이 int보다 작은 정수인 경우, int또는 unsigned int로 승격된다.
  • 피연산자의 자료형이 여전히 같지 않으면, 컴파일러는 가장 높은 우선순위 피연산자를 찾고 다른 피연산자를 암시적 형 변환을 통해 일치시킨다.

피연산자의 우선순위는 다음과 같다.:

  • long double (highest)
  • double
  • float
  • unsigned long long
  • long long
  • unsigned long
  • long
  • unsigned int
  • int (lowest)
  •  

예제 3

문제를 일으킬 수 있다.

std::cout << 5u - 10; // 5u means treat 5 as an unsigned integer // 4294967291

이같은 경우 우선순위에 의해서 부호없는 정수(unsigned int)로 승격되고 결과값의 오버플로로 인해 예상치 못한 결과를 얻을 수도 있다.

위 예제는 부호 없는 정수를 피하는 많은 이유 중 하나다.

 


 

__super::member_function();

 

재정의하는 함수에 대한 기본 클래스 구현을 호출하고 있음을 명시적으로 나타낼 수 있도록 합니다.

 

정적멤버

클래스는 정적 멤버 데이터와 멤버 함수를 포함할 수 있습니다. 데이터 멤버는 선언 하는 경우 정적, 클래스의 모든 개체에 대 한 데이터의 복사본을 하나만 유지 됩니다.

정적 데이터 멤버는 지정된 클래스 형식의 개체에 속하지 않습니다. 결과적으로, 정적 데이터 멤버 선언은 정의로 간주되지 않습니다. 데이터 멤버는 클래스 범위에서 선언되지만 정의는 파일 범위에서 수행됩니다. 이러한 정적 멤버에는 외부 링크가 있습니다. 다음 예제는 이러한 과정을 보여 줍니다.

 

// static_data_members.cpp
class BufferedOutput
{
public:
   // Return number of bytes written by any object of this class.
   short BytesWritten()
   {
      return bytecount;
   }

   // Reset the counter.
   static void ResetCount()
   {
      bytecount = 0;
   }

   // Static member declaration.
   static long bytecount;
};

// Define bytecount in file scope.
long BufferedOutput::bytecount;

int main()
{
}

 

앞의 코드에서 bytecount 멤버는 BufferedOutput 클래스에서 선언되었지만 클래스 선언 밖에서 정의되어야 합니다.

 

클래스 형식의 개체를 참조하지 않고 정적 데이터 멤버를 참조할 수 있습니다. BufferedOutput 개체를 사용하여 쓴 바이트 수는 다음과 같이 확인할 수 있습니다.

long nBytes = BufferedOutput::bytecount;

정적 멤버가 존재하기 위해 클래스 형식의 개체가 있어야 하는 것은 아닙니다. 멤버 선택 영역을 사용 하 여 정적 멤버 액세스할 수도 있습니다 (합니다.  ->) 연산자. 예를 들어:

 

BufferedOutput Console;

long nBytes = Console.bytecount;

 

앞의 경우에서 개체(Console)에 대한 참조는 평가되지 않습니다. 반환된 값은 정적 개체 bytecount의 값입니다.

정적 데이터 멤버에는 클래스 멤버 액세스 규칙이 적용되므로 정적 데이터 멤버에 대한 전용 액세스는 클래스 멤버 함수 및 friend에만 허용됩니다. 

 

 

 

Static

 

① 전역(global) 변수는 다른 파일에서도 가져다 쓸수 있지만 정적(static) 변수는 해당 파일의 scope안에서만 접근이 가능하다.

 

② 초기화 하지 않은 정적(static) 변수의 경우 본문에서 사용하지 않으면 아예 메모리 상에 올라오지 않는다.

 

③ 정적(static) 객체의 경우 처음 구문이 수행되는 시점에 처음 생성자를 호출하도록 할 수 있다.. 이를 함수화하여 호출을하면 생성자의 호출 시점을 조정하는게 가능해진다. 

 

 

초기화리스트에서 수행 내역 차이

 

왜냐하면, 초기화 리스트를 사용한 버전의 경우 생성과 초기화를 동시에 하게 됩니다. 반면에 초기화 리스트를 사용하지 않는다면 생성을 먼저 하고 그 다음에 대입 을 수행하게 됩니다. 쉽게 말하면 초기화 리스트를 사용하는 것은

int a = 10;

이라 하는 것과 같고, 그냥 예전 버전의 생성자를 사용하는 것은

int a; a = 10;

이 키워드는 클래스에서 static도 const도 아닌 데이터 멤버에만 적용할 수 있습니다. 데이터 멤버를 mutable로 선언하면, const 멤버 함수에서 이 데이터 멤버에 대한 값 할당 작업이 허용됩니다.

 

 


 

다음 예제에서는 예외가 throw되면 어떻게 스택이 해제되는지 보여 줍니다. 스레드에서의 실행은 방식에 따라 각 함수를 해제하면서 C의 throw 문에서 main의 catch 문으로 점프합니다. Dummy 개체가 만들어진 다음 범위에서 벗어날 때 제거되는 순서를 살펴보십시오. 또한 catch 문이 포함된 main을 제외하고는 어떤 함수도 완료할 수 없습니다. 함수 A는 B() 호출에서 반환되지 않으며 B도 C() 호출에서 반환되지 않습니다. Dummy 포인터의 정의 및 해당 delete 문에 대한 주석 처리를 제거한 다음 프로그램을 실행하면 포인터가 삭제되지 않습니다. 이는 함수가 예외 보장을 제공하지 않는 경우 발생할 수 있음을 보여 줍니다. 

 

void A(Dummy d, int i)
{
cout << "Entering FunctionA" << endl;
d.MyName = " A";
// Dummy* pd = new Dummy("new Dummy"); //Not exception safe!!!
B(d, i + 1);
// delete pd;
cout << "Exiting FunctionA" << endl;
}

 

 


특수 멤버 함수

 

특수 멤버 함수 클래스의 멤버함수는 특정 경우에 컴파일어를 자동으로 생성하면 됩니다. 

이 함수에는 기본 생성자의 소멸자의 복사 생성자 및 복사 할당 연산자, 및 이동 생성자 및 이동 할당 연산자가 있습니다.

= default(기본 생성자 생성), = delete : 

매개 변수를 사용 하는 생성자를 선언한 경우 기본 생성자를 호출 하는 코드 컴파일러에서 오류 메시지를 생성 합니다. 

 

기본 복사 및 이동 생성 및 할당 작업을 수행할 비트 패턴을 자동으로 멤버 단위 복사 또는 비정적 데이터 멤버의 이동 합니다. 이동 또는 복사 작업 없거나 소멸자가 선언 된 경우에 작업 생성은 이동 합니다. 기본 복사 생성자가 복사 생성자가 선언 된 경우에 생성 됩니다. 이동 작업을 선언 하는 경우 암시적으로 삭제 됩니다. 기본 복사 할당 연산자 복사 할당 연산자를 명시적으로 선언 하는 경우에 생성 됩니다. 이동 작업을 선언 하는 경우 암시적으로 삭제 됩니다.

 

기본 초기화

 

기본 생성자를 사용하는 클래스, 구조체 및 공용 구조체에 대한 기본 초기화입니다. 기본 생성자는 초기화 식이나 new 키워드를 사용 하 여 호출할 수 있습니다.

 

MyClass mc1; // 초기화가 안됨.

MyClass* mc3 = new MyClass;//초기화 안됨

뒤에 {}를 붙여줘야 기본 생성자가 호출되 기본값(보통 0)으로 초기화 됨.

 

클래스, 구조체 또는 공용 구조체에 기본 생성자가 있으면 컴파일러가 오류를 내보냅니다.

스칼라 변수는 초기화 식 없이 정의할 경우 기본값으로 초기화됩니다. 비활성화 상태 값입니다.

 

int i1;

float f;

char c;

 

int int_arr[3];

 

클래스, 구조체 또는 공용 구조체에 기본 생성자가 있으면 컴파일러가 오류를 내보냅니다.

스칼라 변수는 초기화 식 없이 정의할 경우 기본값으로 초기화됩니다. 비활성화 상태 값입니다.

 

배열은 초기화 식 없이 정의할 경우 기본값으로 초기화됩니다. 배열이 기본값으로 초기화되면 다음 예제와 같이 멤버가 기본값으로 초기화되고 비활성화 상태 값을 가집니다.

 

배열에 기본 생성자가 없는 경우, 컴파일러가 오류를 내보냅니다.

 

 

상수 변수의 기본 초기화

 

상수 변수는 이니셜라이저와 함께 선언해야 합니다. 스칼라 형식인 경우 컴파일러 오류가 발생되고 기본 생성자가 있는 클래스 형식인 경우 경고가 발생됩니다.

 

정적 변수의 기본 초기화

 

이니셜라이저 없이 선언된 정적 변수는 0으로 초기화됩니다(형식으로 암시적으로 변환).

class MyClass {
private:
    int m_int;
    char m_char;
};

int main() {
    static int int1;       // 0
    static char char1;     // '\0'
    static bool bool1;   // false
    static MyClass mc1;     // {0, '\0'}
}

전역 정적 변수

main함수가 호출되기 전에 다음 초기화 작업이 수행됩니다.

  • 기본적으로 정적 데이터를 0으로 설정합니다. 명시적으로 초기화 코드가 없는 모든 정적 데이터는 런타임 초기화를 포함하여 다른 코드가 실행되기 전에 0으로 설정됩니다. 정적 데이터 멤버는 명시적으로 정의되어야 합니다.

  • 변환에서 전역 정적 개체를 초기화합니다. main 함수에 진입하기 전이나 개체 변환 단위의 함수나 개체를 처음 사용하기 전에 실행할 수 있습니다.

Microsoft 전용

Microsoft C++에서 전역 정적 개체는 main에 진입하기 전에 초기화됩니다

 


 

초기화 종류

초기화는 여러 종류가 있으며 프로그램 실행의 여러 지점에서 발생할 수 있습니다. 다양한 종류의 초기화는 상호 배타적이지 않습니다. 예를 들어 목록 초기화는 값 초기화를 트리거할 수 있고 다른 상황에서는 집합체 초기화를 트리거할 수 있습니다.

0 초기화

0 초기화는 변수를 암시적으로 형식으로 변환된 0으로 설정하는 것입니다.

  • 숫자 변수는 0(또는 0.0 또는 0.0000000000 등)으로 초기화됩니다.

  • Char 변수는로 '\0'초기화 됩니다.

  • 포인터는 nullptr로 초기화 됩니다.

  • 배열, POD 클래스, 구조체 및 공용 구조체의 멤버는 0 값으로 초기화 됩니다.

0 초기화는 다음과 같은 다양한 시간에 수행됩니다.

  • 프로그램 시작 시 - 정적 지속 시간이 있는 명명된 모든 변수 이러한 변수는 나중에 다시 초기화할 수 있습니다.

  • 값을 초기화하는 동안 - 빈 중괄호를 사용하여 초기화된 스칼라 형식 및 POD 클래스 형식

  • 멤버의 하위 집합만 초기화된 어레이

0 초기화의 몇 가지 예제는 다음과 같습니다.

struct my_struct{
    int i;
    char c;
};

int i0;              // zero-initialized to 0
int main() {
    static float f1;  // zero-initialized to 0.000000000
    double d{};     // zero-initialized to 0.00000000000000000
    int* ptr{};     // initialized to nullptr
    char s_array[3]{'a', 'b'};  // the third char is initialized to '\0'
    int int_array[5] = { 8, 9, 10 };  // the fourth and fifth ints are initialized to 0
    my_struct a_struct{};   // i = 0, c = '\0'
}

 

 

기본 초기화

 

기본 생성자를 사용하는 클래스, 구조체 및 공용 구조체에 대한 기본 초기화입니다. 기본 생성자는 초기화 식이나 new 키워드를 사용 하 여 호출할 수 있습니다.

MyClass mc1;
MyClass* mc3 = new MyClass;

int i1;
float f;
char c;


int int_arr[3];

클래스, 구조체 또는 공용 구조체에 기본 생성자가 있으면 컴파일러가 오류를 내보냅니다.

스칼라 변수는 초기화 식 없이 정의할 경우 기본값으로 초기화됩니다. 비활성화 상태 값입니다.

 

배열은 초기화 식 없이 정의할 경우 기본값으로 초기화됩니다. 배열이 기본값으로 초기화되면 다음 예제와 같이 멤버가 기본값으로 초기화되고 비활성화 상태 값을 가집니다.

배열에 기본 생성자가 없는 경우, 컴파일러가 오류를 내보냅니다.

 

값 초기화

 

값 초기화는 다음과 같은 경우에 발생합니다.

  • 명명된 값이 빈 중괄호 초기화를 사용하여 초기화됩니다.

  • 익명의 임시 개체가 빈 괄호나 중괄호를 사용하여 초기화됩니다.

  •  키워드와 빈 괄호 또는 중괄호를 사용 하 여 개체를 초기화 합니다.

값 초기화가 수행하는 작업은 다음과 같습니다.

  • 하나 이상의 공용 생성자가 있는 클래스의 경우 기본 생성자가 호출됩니다.

  • 선언된 생성자가 없는 공용 구조체가 아닌 클래스의 경우 개체가 0으로 초기화되고 기본 생성자가 호출됩니다.

  • 배열의 경우 모든 요소의 값이 초기화됩니다.

  • 다른 모든 경우에는 변수가 0으로 초기화됩니다.

class BaseClass {
private:
    int m_int;
};

int main() {
    BaseClass bc{};     // class is initialized
    BaseClass*  bc2 = new BaseClass();  // class is initialized, m_int value is 0
    int int_arr[3]{};  // value of all members is 0
    int a{};     // value of a is 0
    double b{};  // value of b is 0.00000000000000000
}

 


복사 초기화

 

복사 초기화는 하나의 개체를 다른 개체로 초기화하는 것입니다. 다음과 같은 경우에 발생합니다.

  • 변수가 등호를 사용하여 초기화됩니다.

  • 인수가 함수에 전달됩니다.

  • 개체가 함수에서 반환됩니다.

  • 예외가 발생하거나 catch됩니다.

  • 비정적 데이터 멤버가 등호를 사용하여 초기화됩니다.

  • 클래스, 구조체 및 공용 구조체 멤버가 집합체 초기화 중에 복사 초기화로 초기화됩니다. 예제는 집합체 초기화 를 참조 하세요.

다음 코드는 복사 초기화의 몇 가지 예를 보여 줍니다.

#include <iostream>
using namespace std;

class MyClass{
public:
    MyClass(int myInt) {}
    void set_int(int myInt) { m_int = myInt; }
    int get_int() const { return m_int; }
private:
    int m_int = 7; // copy initialization of m_int

};
class MyException : public exception{};
int main() {
    int i = 5;              // copy initialization of i
    MyClass mc1{ i };
    MyClass mc2 = mc1;      // copy initialization of mc2 from mc1
    MyClass mc1.set_int(i);    // copy initialization of parameter from i
    int i2 = mc2.get_int(); // copy initialization of i2 from return value of get_int()

    try{
        throw MyException();
    }
    catch (MyException ex){ // copy initialization of ex
        cout << ex.what();
    }
}

복사 초기화는 명시적 생성자를 호출할 수 없습니다.

vector<int> v = 10; // the constructor is explicit; compiler error C2440: cannot convert from 'int' to 'std::vector<int,std::allocator<_Ty>>'
regex r = "a.*b"; // the constructor is explicit; same error
shared_ptr<int> sp = new int(1729); // the constructor is explicit; same error

경우에 따라 클래스의 복사 생성자가 삭제되거나 액세스할 수 없는 경우 복사 초기화로 컴파일러 오류가 발생합니다.

 


직접 초기화

 

직접 초기화는 (비어 있지 않은) 중괄호 또는 괄호를 사용한 초기화입니다. 복사 초기화와는 달리 명시적 생성자를 호출할 수 있습니다. 다음과 같은 경우에 발생합니다.

  • 변수가 비어 있지 않은 중괄호 또는 괄호를 사용하여 초기화됩니다.

  • 변수가 new 키워드와 비어 있지 않은 중괄호 또는 괄호를 사용 하 여 초기화 됩니다.

  • 변수는 static_cast 를 사용 하 여 초기화 됩니다.

  • 생성자에서 기본 클래스 및 비정적 멤버가 이니셜라이저 목록을 사용하여 초기화됩니다.

  • 람다 식 내의 캡처된 변수 복사본에서

다음 코드는 직접 초기화의 몇 가지 예를 보여 줍니다.

 


목록 초기화

 

목록 초기화는 변수가 중괄호로 묶인 이니셜라이저 목록을 사용하여 초기화될 때 발생합니다. 중괄호로 묶인 이니셜라이저 목록이 사용되는 경우는 다음과 같습니다.

  • 변수가 초기화됩니다.

  • 클래스가 new 키워드를 사용 하 여 초기화 됩니다.

  • 개체가 함수에서 반환됩니다.

  • 인수가 함수에 전달됩니다.

  • 직접 초기화의 인수 중 하나

  • 비정적 데이터 멤버 이니셜라이저에서

  • 생성자 이니셜라이저 목록에서

다음 코드는 목록 초기화의 몇 가지 예를 보여 줍니다.

 

class MyClass {
public:
    MyClass(int myInt, char myChar) {}
private:
    int m_int[]{ 3 };
    char m_char;
};
class MyClassConsumer{
public:
    void set_class(MyClass c) {}
    MyClass get_class() { return MyClass{ 0, '\0' }; }
};
struct MyStruct{
    int my_int;
    char my_char;
    MyClass my_class;
};
int main() {
    MyClass mc1{ 1, 'a' };
    MyClass* mc2 = new MyClass{ 2, 'b' };
    MyClass mc3 = { 3, 'c' };

    MyClassConsumer mcc;
    mcc.set_class(MyClass{ 3, 'c' });
    mcc.set_class({ 4, 'd' });

    MyStruct ms1{ 1, 'a', { 2, 'b' } };
}

 


집합체 초기화

 

집합체 초기화는 다음과 같은 일종의 배열 또는 클래스 형식(대개 구조체 또는 공용 구조체) 목록 초기화입니다.

  • 전용 또는 보호된 멤버 없음

  • 명시적으로 기본값으로 설정되었거나 삭제된 생성자를 제외하고 사용자 제공 생성자 없음

  • 기본 클래스 없음

  • 가상 멤버 함수 없음

집합체 이니셜라이저는 다음 예제와 같이 중괄호로 묶은 초기화 목록으로 구성되며, 등호가 있을 수도 있고 없을 수도 있습니다.

 

#include <iostream>
using namespace std;

struct MyAggregate{
    int myInt;
    char myChar;
};

struct MyAggregate2{
    int myInt;
    char myChar = 'Z'; // member-initializer OK in C++14
};

int main() {
    MyAggregate agg1{ 1, 'c' };
    MyAggregate2 agg2{2};
    cout << "agg1: " << agg1.myChar << ": " << agg1.myInt << endl;
    cout << "agg2: " << agg2.myChar << ": " << agg2.myInt << endl;

    int myArr1[]{ 1, 2, 3, 4 };
    int myArr2[3] = { 5, 6, 7 };
    int myArr3[5] = { 8, 9, 10 };

    cout << "myArr1: ";
    for (int i : myArr1){
        cout << i << " ";
    }
    cout << endl;

    cout << "myArr3: ";
    for (auto const &i : myArr3) {
        cout << i << " ";
    }
    cout << endl;
}

다음과 같은 내용이 출력됩니다.

agg1: c: 1
agg2: Z: 2
myArr1: 1 2 3 4
myArr3: 8 9 10 0 0

 

공용 구조체 및 구조체 초기화

 

공용 구조체에 생성자가 없는 경우 단일 값을 사용하여(또는 공용 구조체의 다른 인스턴스를 사용하여) 초기화할 수 있습니다. 값을 사용하여 첫 번째 비정적 필드를 초기화합니다. 이는 구조체 초기화와는 다릅니다. 구조체 초기화는 이니셜라이저의 첫 번째 값을 사용하여 첫 번째 필드를 초기화하고, 두 번째 값을 사용하여 두 번째 필드를 초기화하는 식으로 이루어집니다. 다음 예제에서 구조체와 공용 구조체 초기화를 비교해 보세요.

 

 

struct MyStruct {
    int myInt;
    char myChar;
};
union MyUnion {
    int my_int;
    char my_char;
    bool my_bool;
    MyStruct my_struct;
};

int main() {
    MyUnion mu1{ 'a' };  // my_int = 97, my_char = 'a', my_bool = true, {myInt = 97, myChar = '\0'}
    MyUnion mu2{ 1 };   // my_int = 1, my_char = 'x1', my_bool = true, {myInt = 1, myChar = '\0'}
    MyUnion mu3{};      // my_int = 0, my_char = '\0', my_bool = false, {myInt = 0, myChar = '\0'}
    MyUnion mu4 = mu3;  // my_int = 0, my_char = '\0', my_bool = false, {myInt = 0, myChar = '\0'}
    //MyUnion mu5{ 1, 'a', true };  // compiler error: C2078: too many initializers
    //MyUnion mu6 = 'a';            // compiler error: C2440: cannot convert from 'char' to 'MyUnion'
    //MyUnion mu7 = 1;              // compiler error: C2440: cannot convert from 'int' to 'MyUnion'

    MyStruct ms1{ 'a' };            // myInt = 97, myChar = '\0'
    MyStruct ms2{ 1 };              // myInt = 1, myChar = '\0'
    MyStruct ms3{};                 // myInt = 0, myChar = '\0'
    MyStruct ms4{1, 'a'};           // myInt = 1, myChar = 'a'
    MyStruct ms5 = { 2, 'b' };      // myInt = 2, myChar = 'b'
}

집합체가 포함된 집합체 초기화

 

집합체 형식은 배열의 배열, 구조체의 배열 등 다른 집합체 형식을 포함할 수 있습니다. 이러한 형식은 중첩된 중괄호 집합을 사용하여 초기화됩니다.

struct MyStruct {
    int myInt;
    char myChar;
};
int main() {
    int intArr1[2][2]{{ 1, 2 }, { 3, 4 }};
    int intArr3[2][2] = {1, 2, 3, 4};
    MyStruct structArr[]{ { 1, 'a' }, { 2, 'b' }, {3, 'c'} };
}

참조 초기화

 

참조 형식의 변수는 참조 형식이 파생된 형식의 개체 또는 참조 형식이 파생된 형식으로 변환될 수 있는 형식의 개체를 사용하여 초기화되어야 합니다. 예를 들어:

// initializing_references.cpp
int iVar;
long lVar;
int main()
{
    long& LongRef1 = lVar;        // No conversion required.
    long& LongRef2 = iVar;        // Error C2440
    const long& LongRef3 = iVar;  // OK
    LongRef1 = 23L;               // Change lVar through a reference.
    LongRef2 = 11L;               // Change iVar through a reference.
    LongRef3 = 11L;               // Error C3892
}

임시 개체를 사용하여 참조를 초기화하는 유일한 방법은 상수 임시 개체를 초기화하는 것입니다. 참조 형식 변수는 초기화되면 항상 동일한 개체를 가리키며, 다른 개체를 가리키도록 수정될 수 없습니다. 아닌 경우에는 자료형이 일치해야 합니다.

위에가 할당. 아래가 초기화. 초기화인 경우 자료형이 달라도 초기화 가능.

 

구문은 동일할 수 있지만 참조 형식 변수의 초기화와 참조 형식 변수에 대한 할당은 의미 체계가 서로 다릅니다. 위의 예제에서 iVar  lVar을 변경하는 할당은 초기화와 유사해 보이지만 결과가 다릅니다. 초기화는 참조 형식 변수가 가리키는 개체를 지정하고, 할당은 참조를 통해 참조된 개체에 할당합니다.

 

참조 형식의 인수를 함수에 전달하는 것과 함수에서 참조 형식의 값을 반환하는 것은 둘 다 초기화이기 때문에 함수에 대한 형식 인수는 참조가 반환됨에 따라 올바르게 초기화됩니다. 참조 형식 변수는 다음에서만 이니셜라이저 없이 선언될 수 있습니다.

 

함수 선언(프로토타입). 예를 들어:
int func( int& );

함수 반환 형식 선언. 예:
int& func( int& );

참조 형식 클래스 멤버의 선언. 예:
class c {public:   int& i;};

Extern으로 명시적으로 지정 된 변수 선언입니다. 예:
extern int& iVal;

 

휘발성 형식 에 대 한 참조 & ( volatile 형식 식별자로 선언 됨)는 동일한 형식의 volatile 개체 또는 volatile 로 선언 되지 않은 개체를 사용 하 여 초기화할 수 있습니다. . 그러나 해당 형식의 const 개체를 사용 하 여 초기화할 수는 없습니다. 마찬가지로 const 형식 ( const typename & identifier로 선언 됨 )에 대 한 참조는 동일한 형식의 const 개체를 사용 하 여 초기화 될 수 있습니다 (또는 해당 형식이 나 개체로의 변환이 있는 모든 항목). const로 선언 되지 않았습니다. 그러나 해당 형식의 volatile 개체를 사용 하 여 초기화할 수는 없습니다. Const 또는 volatile 키워드를 사용 하 여 정규화 되지 않은 참조는 const 또는 volatile이 아닌 개체로 선언 된 개체만 초기화할 수 있습니다.

 

 


변경할 수 있는 데이터 멤버

 

이 키워드는 클래스에서 static도 const도 아닌 데이터 멤버에만 적용할 수 있습니다. 데이터 멤버를 mutable로 선언하면, const 멤버 함수에서 이 데이터 멤버에 대한 값 할당 작업이 허용됩니다.

 

예를 들어, 다음 코드는 m_accessCount mutable로 선언되었기 때문에 오류 없이 컴파일되고, 따라서 GetFlag가 const 멤버 함수라도 GetFlag에서 이를 변경할 수 있습니다.

 

// mutable.cpp
class X
{
public:
   bool GetFlag() const
   {
      m_accessCount++;
      return m_flag;
   }
private:
   bool m_flag;
   mutable int m_accessCount;
};

int main()
{
}

 


중첩 클래스

 

클래스는 다른 클래스의 범위 내에서 선언될 수 있습니다. 이러한 클래스를 "중첩 클래스"라고 합니다. 중첩 클래스는 바깥쪽 클래스의 범위 내에 있는 것으로 간주되고 해당 범위 내에서 사용할 수 있습니다. 바로 바깥쪽 범위 이외의 범위에서 중첩 클래스를 참조하려면 정규화된 이름을 사용해야 합니다.

 

다음 예제에서는 중첩 클래스를 선언하는 방법을 보여 줍니다.

// nested_class_declarations.cpp
class BufferedIO
{
public:
   enum IOError { None, Access, General };

   // Declare nested class BufferedInput.
   class BufferedInput
   {
   public:
      int read();
      int good()
      {
         return _inputerror == None;
      }
   private:
       IOError _inputerror;
   };

   // Declare nested class BufferedOutput.
   class BufferedOutput
   {
      // Member list
   };
};

int main()
{
}

 

 

BufferedIO::BufferedInput  BufferedIO::BufferedOutput은 BufferedIO 내에 선언됩니다. 이러한 클래스 이름은 

BufferedIO 클래스의 범위 외부에서 표시되지 않습니다. 그러나 BufferedIO 형식의 개체에는 BufferedInput 또는 BufferedOutput 형식의 개체가 포함되지 않습니다.

 

중첩 클래스는 바깥쪽 클래스에서만 제공된 이름, 형식 이름, 정적 멤버의 이름 및 열거자를 직접 사용할 수 있습니다. 다른 클래스 멤버의 이름을 사용하려면 포인터, 참조 또는 개체 이름을 사용해야 합니다. 위의 BufferedIO 예제에서

IOError 함수에 표시된 것과 같이 중첩 클래스 BufferedIO::BufferedInput 또는 BufferedIO::BufferedOutput의 멤버 함수에서 good 열거형에 직접 액세스할 수 있습니다.

 


중첩 클래스 선언의 범위 표시 유형에 대한 예외는 형식 이름이 정방향 선언과 함께 선언된 경우입니다. 이 경우 정방향 선언에서 선언된 클래스 이름은 바깥쪽 클래스 외부에 표시되며 해당 범위는 가장 작은 바깥쪽 비클래스 범위로 정의됩니다. 예를 들어:

// nested_class_declarations_2.cpp
class C
{
public:
    typedef class U u_t; // class U visible outside class C scope
    typedef class V {} v_t; // class V not visible outside class C
};

int main()
{
    // okay, forward declaration used above so file scope is used
    U* pu;

    // error, type name only exists in class C scope
    u_t* pu2; // C2065

    // error, class defined above so class C scope
    V* pv; // C2065

    // okay, fully qualified name
    C::V* pv2;
}

U는 C내부에서 정의되지 않고 정방향 선언 되었기 때문에 U는 그냥 쓸 수 있습니다. 하지만 V는 C안에서 정의 되었기 때문에 C밖에서 사용할 수 없습니다.

 

중첩 클래스에 대한 액세스 권한

 

클래스가 다른 클래스 안에 중첩되면 중첩된 클래스의 멤버 함수에 대해 특별한 액세스 권한이 부여되지 않습니다. 마찬가지로 바깥쪽 클래스의 멤버 함수는 중첩된 클래스의 멤버에 대해 특별한 액세스 권한이 없습니다.

 

중첩 클래스의 멤버 함수

 

중첩 클래스에서 선언된 멤버 함수는 파일 범위에서 정의될 수 있습니다. 앞의 예제는 다음과 같이 작성될 수도 있습니다.

 

int BufferedIO::BufferedInput::read()
{
   return(1);
}

int BufferedIO::BufferedInput::good()
{
    return _inputerror == None;
}
typedef BufferedIO::BufferedInput BIO_INPUT;

int BIO_INPUT::read()

 

중첩 클래스의 Friend 함수

 

중첩 클래스에 선언된 friend 함수는 바깥쪽 클래스가 아닌 중첩 클래스 범위에 있다고 간주됩니다. 따라서 friend 함수는 바깥쪽 클래스의 멤버 또는 멤버 함수에 대해 특별한 액세스 권한이 없습니다.

 

class BufferedIO
{
private:
	const static int base_t = 0;
public:
	class BufferedInput
	{
	public:
		friend int GetExtendedErrorStatus(BufferedInput&);
	private:
		int t;
	};
};

int GetExtendedErrorStatus(BufferedIO::BufferedInput& b)
{
	cout << BufferedIO::base_t; // 상위 클래스 정적 멤버는 접근 불가능!
	cout << b.t;//Friend인 하위클래스 멤버는 private이여도 접근 가능
	return 0;
}

 

'C++' 카테고리의 다른 글

스레드 프로그래밍  (0) 2019.11.06
C++ 정리2  (0) 2019.10.30
문자열 함수  (0) 2018.06.11
알고리즘 함수  (0) 2018.05.31
반복자  (0) 2018.05.21
Comments