수색…
통사론
- variable.member_var = 상수;
- variable.member_function ();
- variable_pointer-> member_var = 상수;
- variable_pointer-> member_function ();
비고
사이의 유일한 차이점합니다 struct
와 class
키워드는 기본, 멤버 변수, 멤버 함수, 그리고의 기본 클래스가 있다는 것입니다 struct
이다 public
A의 동안, class
그들이 private
. C ++ 프로그래머는 생성자와 소멸자가 있고 자체 불변성을 적용 할 수있는 경우 클래스라고 부르는 경향이 있습니다. 또는 단순한 값의 모음이라면 struct이지만 C ++ 언어 자체는 별다른 차이가 없습니다.
수업 기초
클래스 는 사용자 정의 유형입니다. 클래스는 class
, struct
또는 union
키워드와 함께 도입됩니다. 구어체 사용에서 "클래스"라는 용어는 일반적으로 비노 색 클래스만을 나타냅니다.
클래스는 다음과 같은 클래스 멤버 의 컬렉션입니다.
- 멤버 변수 ( "필드"라고도 함)
- 멤버 함수 ( "메소드"라고도 함)
- 멤버 유형 또는 typedef (예 : "중첩 클래스"),
- 멤버 템플릿 (모든 종류의 : 변수, 함수, 클래스 또는 별칭 템플릿)
class
및 struct
멤버와 기지에 대한 기본 액세스 지정이로 선언 된 클래스에 대해 "개인"이라는 점을 제외 클래스 키라는 키워드는 주로 상호 교환 class
로 선언 된 클래스의 키와 "공개" struct
또는 union
키 (참조 : 접근 수정 자 ).
예를 들어, 다음 코드 스니 j은 동일합니다.
struct Vector
{
int x;
int y;
int z;
};
// are equivalent to
class Vector
{
public:
int x;
int y;
int z;
};
클래스`를 선언함으로써 새로운 타입이 프로그램에 추가되고, 그 클래스의 객체를 다음과 같이 인스턴스화 할 수 있습니다.
Vector my_vector;
클래스의 멤버는 도트 구문을 사용하여 액세스됩니다.
my_vector.x = 10;
my_vector.y = my_vector.x + 1; // my_vector.y = 11;
my_vector.z = my_vector.y - 4; // my:vector.z = 7;
액세스 지정자
액세스 지정자의 역할을하는 세 가지 키워드 가 있습니다 . 이는 다른 지정자가 액세스 수준을 다시 변경할 때까지 지정자 다음의 클래스 멤버에 대한 액세스를 제한합니다.
예어 | 기술 |
---|---|
public | 누구나 액세스 할 수 있습니다. |
protected | 클래스 자체, 파생 클래스 및 친구들 만 액세스 할 수 있습니다. |
private | 클래스 자체 및 친구들 만 액세스 할 수 있습니다. |
class
키워드를 사용하여 유형을 정의하면 기본 액세스 지정자는 private
이지만 struct
키워드를 사용하여 유형을 정의하면 기본 액세스 지정자는 public
.
struct MyStruct { int x; };
class MyClass { int x; };
MyStruct s;
s.x = 9; // well formed, because x is public
MyClass c;
c.x = 9; // ill-formed, because x is private
액세스 지정자는 주로 내부 필드와 메소드에 대한 액세스를 제한하고 프로그래머가 변수를 직접 참조하는 대신 getter와 setter를 강제로 사용하도록 특정 인터페이스를 사용하도록합니다.
class MyClass {
public: /* Methods: */
int x() const noexcept { return m_x; }
void setX(int const x) noexcept { m_x = x; }
private: /* Fields: */
int m_x;
};
protected
사용하면 유형의 특정 기능을 파생 클래스에만 액세스 할 수 있습니다. 예를 들어 다음 코드에서 메소드 calculateValue()
는 기본 클래스 Plus2Base
에서 파생 된 클래스 (예 : FortyTwo
calculateValue()
에서만 액세스 할 수 있습니다.
struct Plus2Base {
int value() noexcept { return calculateValue() + 2; }
protected: /* Methods: */
virtual int calculateValue() noexcept = 0;
};
struct FortyTwo: Plus2Base {
protected: /* Methods: */
int calculateValue() noexcept final override { return 40; }
};
friend
키워드는 보호 된 멤버와 비공개 멤버에 액세스하는 함수 또는 형식에 대한 액세스 예외를 추가하는 데 사용할 수 있습니다.
public
, protected
및 private
키워드는 기본 클래스 하위 객체에 대한 액세스 권한을 부여하거나 제한하는 데 사용할 수도 있습니다. 상속 예제를 참조하십시오.
계승
클래스 / 구조체는 상속 관계를 가질 수 있습니다.
클래스 / 구조체 B
가 클래스 / 구조체 A
로부터 상속 받으면 B
는 부모 A
가짐을 의미합니다. 우리는 B
가 A
의 파생 클래스 / 구조체이고 A
가 기본 클래스 / 구조체라고 말합니다.
struct A
{
public:
int p1;
protected:
int p2;
private:
int p3;
};
//Make B inherit publicly (default) from A
struct B : A
{
};
클래스 / 구조체에는 3 가지 형태의 상속이 있습니다.
-
public
-
private
-
protected
기본 상속은 멤버의 기본 공개 설정과 동일합니다 ( public
: struct
키워드 사용) public
private
) 및 class
키워드 ( private
.
struct
에서 파생 된 class
를 가질 수도 있습니다 (또는 그 반대). 이 경우, 기본 상속은 자식에 의해 제어되는, 그래서 struct
A로부터 파생 class
공공 상속을 기본값으로하고, class
A로부터 파생 struct
기본적으로 개인 상속을해야합니다.
public
상속 :
struct B : public A // or just `struct B : A`
{
void foo()
{
p1 = 0; //well formed, p1 is public in B
p2 = 0; //well formed, p2 is protected in B
p3 = 0; //ill formed, p3 is private in A
}
};
B b;
b.p1 = 1; //well formed, p1 is public
b.p2 = 1; //ill formed, p2 is protected
b.p3 = 1; //ill formed, p3 is inaccessible
private
상속 :
struct B : private A
{
void foo()
{
p1 = 0; //well formed, p1 is private in B
p2 = 0; //well formed, p2 is private in B
p3 = 0; //ill formed, p3 is private in A
}
};
B b;
b.p1 = 1; //ill formed, p1 is private
b.p2 = 1; //ill formed, p2 is private
b.p3 = 1; //ill formed, p3 is inaccessible
protected
상속 :
struct B : protected A
{
void foo()
{
p1 = 0; //well formed, p1 is protected in B
p2 = 0; //well formed, p2 is protected in B
p3 = 0; //ill formed, p3 is private in A
}
};
B b;
b.p1 = 1; //ill formed, p1 is protected
b.p2 = 1; //ill formed, p2 is protected
b.p3 = 1; //ill formed, p3 is inaccessible
protected
상속은 허용되지만 실제 사용은 거의 없습니다. 응용 프로그램에서 protected
상속이 사용되는 방법 중 하나는 부분 기본 클래스 특수화 (일반적으로 "제어 된 다형성"이라고 함)입니다.
OOP이 비교적 새로운 경우, (공개) 상속은 종종 "IS-A"관계를 모델링한다고합니다. 즉, 파생 클래스 의 인스턴스가 기본 클래스 의 인스턴스 이기도 한 경우에만 공용 상속이 올바른 것입니다.
이것은 나중에 Liskov Substitution Principle 으로 정제되었다. public 상속은 파생 된 클래스의 인스턴스가 가능한 모든 상황에서 기본 클래스의 인스턴스로 대체 될 수있는 경우에만 사용되어야한다.
사적 상속은 일반적으로 완전하게 다른 관계를 구현한다고 말합니다 ( "HAS-A"관계라고도 함). 예를 들어, Stack
클래스는 Vector
클래스에서 개별적으로 상속받을 수 있습니다. 개인 상속은 공용 상속보다 집계와 훨씬 더 유사합니다.
보호 된 상속은 거의 사용되지 않으며 어떤 유형의 관계가 구현되는지에 대한 일반적인 합의가 없습니다.
가상 상속
상속을 사용할 때 virtual
키워드를 지정할 수 있습니다.
struct A{};
struct B: public virtual A{};
클래스 B
가 가상베이스 A
가지면, 상속 트리의 대부분의 파생 클래스 에 A
가 상주 할 것이므로 대부분의 파생 클래스가 해당 가상베이스를 초기화 할 책임이 있음을 의미합니다.
struct A
{
int member;
A(int param)
{
member = param;
}
};
struct B: virtual A
{
B(): A(5){}
};
struct C: B
{
C(): /*A(88)*/ {}
};
void f()
{
C object; //error since C is not initializing it's indirect virtual base `A`
}
우리가 /*A(88)*/
주석 해제하면 C
는 이제 간접 가상베이스 A
초기화하기 때문에 어떤 에러도 발생하지 않을 것입니다.
또한 변수 object
만들 때 대부분의 파생 클래스는 C
이므로 C
는 A
생성자를 생성하는 책임이 있으므로 A::member
값은 5
가 아니라 88
(우리가 B
유형의 오브젝트 생성).
다이아몬드 문제를 해결할 때 유용합니다.
A A A
/ \ | |
B C B C
\ / \ /
D D
virtual inheritance normal inheritance
B
와 C
모두 A
에서 상속 받고 D
는 B
와 C
에서 상속되므로 D
에 A
인스턴스가 2 개 있습니다! 이는 컴파일러가 어느 클래스에서 그 멤버 ( B
상속하는 클래스 또는 C
상속 된 클래스)에 액세스 할지를 알 수있는 방법이 없기 때문에 A
에서 D
멤버에 액세스 할 때 모호함을 초래합니다. .
가상 상속은이 문제를 해결합니다. 가상베이스는 대부분의 파생 된 객체에만 있기 때문에 D
에서 A
인스턴스는 하나만 존재합니다.
struct A
{
void foo() {}
};
struct B : public /*virtual*/ A {};
struct C : public /*virtual*/ A {};
struct D : public B, public C
{
void bar()
{
foo(); //Error, which foo? B::foo() or C::foo()? - Ambiguous
}
};
주석을 제거하면 모호성이 해결됩니다.
다중 상속
단일 상속을 제외하면 다음과 같습니다.
class A {};
class B : public A {};
다중 상속을 가질 수도 있습니다.
class A {};
class B {};
class C : public A, public B {};
C
는 이제 A
와 B
를 동시에 상속받습니다.
참고 : 여러 개의 상속 된 class
나 struct
에서 같은 이름을 사용하면 모호함이 발생할 수 있습니다. 조심해!
다중 상속의 모호함
특정 상속에서는 다중 상속이 도움이 될 수 있지만, 다중 상속을 사용하는 동안 때로는 이상한 종류의 문제가 발생할 수 있습니다.
예 : 두 개의 기본 클래스는 파생 클래스에서 재정의되지 않은 동일한 이름의 함수를 가지며 파생 클래스의 객체를 사용하여 해당 함수에 액세스하는 코드를 작성하면 호출 할 함수를 결정할 수 없기 때문에 컴파일러에 오류가 표시됩니다. 다음은 다중 상속에서 이러한 유형의 모호성에 대한 코드입니다.
class base1
{
public:
void funtion( )
{ //code for base1 function }
};
class base2
{
void function( )
{ // code for base2 function }
};
class derived : public base1, public base2
{
};
int main()
{
derived obj;
// Error because compiler can't figure out which function to call
//either function( ) of base1 or base2 .
obj.function( )
}
그러나이 문제는 scope resolution 함수를 사용하여 base1 또는 base2 클래스를 지정할 함수를 지정하여 해결할 수 있습니다.
int main()
{
obj.base1::function( ); // Function of class base1 is called.
obj.base2::function( ); // Function of class base2 is called.
}
반원들과 대화하기
클래스의 객체의 멤버 변수 및 멤버 함수에 액세스하려면 .
연산자가 사용됩니다.
struct SomeStruct {
int a;
int b;
void foo() {}
};
SomeStruct var;
// Accessing member variable a in var.
std::cout << var.a << std::endl;
// Assigning member variable b in var.
var.b = 1;
// Calling a member function.
var.foo();
포인터를 통해 클래스의 멤버에 액세스 할 때 ->
연산자가 일반적으로 사용됩니다. 또는 인스턴스를 참조 해제 할 수 있습니다 .
이것은 일반적이지 않지만 연산자가 사용됩니다.
struct SomeStruct {
int a;
int b;
void foo() {}
};
SomeStruct var;
SomeStruct *p = &var;
// Accessing member variable a in var via pointer.
std::cout << p->a << std::endl;
std::cout << (*p).a << std::endl;
// Assigning member variable b in var via pointer.
p->b = 1;
(*p).b = 1;
// Calling a member function via a pointer.
p->foo();
(*p).foo();
정적 클래스 멤버에 액세스 할 때 ::
연산자가 사용되지만 클래스의 인스턴스 대신 클래스 이름에 사용됩니다. 또는 정적 멤버는 인스턴스 또는 인스턴스를 가리키는 포인터에서를 사용하여 액세스 할 수 있습니다 .
또는 ->
연산자를 사용하여 비 정적 멤버에 액세스하는 구문과 동일한 구문을 사용합니다.
struct SomeStruct {
int a;
int b;
void foo() {}
static int c;
static void bar() {}
};
int SomeStruct::c;
SomeStruct var;
SomeStruct* p = &var;
// Assigning static member variable c in struct SomeStruct.
SomeStruct::c = 5;
// Accessing static member variable c in struct SomeStruct, through var and p.
var.a = var.c;
var.b = p->c;
// Calling a static member function.
SomeStruct::bar();
var.bar();
p->bar();
배경
멤버 액세스 연산자 때문에 ->
연산자가 필요합니다 .
역 참조 연산자 *
보다 우선합니다.
*pa
가 p
참조 해제하여 (객체 p
가 가리키는 p
를 얻음) 멤버 a
액세스하는 것으로 기대할 수 있습니다. 그러나 실제로 p
의 멤버 a
에 액세스 한 다음 역 참조를 시도합니다. 즉 *pa
는 *(pa)
와 같습니다. 첫째, 두 가지 사실들 때문에 위의 예에서,이 컴파일러 오류가 발생할 것 p
포인터이며, 회원이 없습니다 a
. 둘째, a
는 정수이므로 역 참조 할 수 없습니다.
이 문제에 대해 일반적으로 사용되는 솔루션은 우선 순위를 명시 적으로 제어하는 것입니다. (*p).a
대신, ->
연산자는 거의 항상 사용됩니다. 포인터를 처음 역 참조 한 다음 액세스하는 것은 짧은 명령입니다. 즉 (*p).a
는 p->a
와 정확히 같습니다.
::
연산자는 범위 연산자이며 네임 스페이스의 멤버에 액세스하는 것과 같은 방식으로 사용됩니다. 이는 정적 클래스 멤버가 해당 클래스의 범위에있는 것으로 간주되지만 해당 클래스의 인스턴스 멤버로 간주되지 않기 때문입니다. 정상적인 사용 .
와 ->
또한 그들이 역사적인 이유로 인스턴스 멤버를,없는에도 불구하고, 정적 멤버 허용됩니다; 이 함수는 호출자가 주어진 멤버 함수가 정적인지 비 정적인지 여부를 고려할 필요가 없으므로 템플릿에 일반 코드를 작성하는 데 사용됩니다.
개인 상속 : 기본 클래스 인터페이스 제한
개인 상속은 클래스의 공용 인터페이스를 제한해야 할 때 유용합니다.
class A {
public:
int move();
int turn();
};
class B : private A {
public:
using A::turn;
};
B b;
b.move(); // compile error
b.turn(); // OK
이 접근법은 A 포인터 또는 참조로 캐스팅하여 A 공용 메소드에 대한 액세스를 효율적으로 방지합니다.
B b;
A& a = static_cast<A&>(b); // compile error
공개 상속의 경우 이러한 캐스팅은 파생 된 B에서이를 방지하기위한 대체 방법에도 불구하고 숨기기와 같이 모든 A 공용 메소드에 대한 액세스를 제공합니다.
class B : public A {
private:
int move();
};
또는 개인용 :
class B : public A {
private:
using A::move;
};
두 경우 모두 가능합니다.
B b;
A& a = static_cast<A&>(b); // OK for public inheritance
a.move(); // OK
최종 클래스 및 구조체
클래스를 파생시키는 것은 final
지정자로 금지 될 수 있습니다. 최종 수업을 선언합시다.
class A final {
};
이제 하위 클래스를 만들려고하면 컴파일 오류가 발생합니다.
// Compilation error: cannot derive from final class:
class B : public A {
};
최종 클래스는 클래스 계층 구조의 아무 곳에 나 나타날 수 있습니다.
class A {
};
// OK.
class B final : public A {
};
// Compilation error: cannot derive from final class B.
class C : public B {
};
우정
friend
키워드 는 클래스의 범위 외부에서 정의 된 경우에도 클래스의 개인 및 보호 된 멤버에 대한 액세스를 다른 클래스 및 함수에 부여하는 데 사용됩니다.
class Animal{
private:
double weight;
double height;
public:
friend void printWeight(Animal animal);
friend class AnimalPrinter;
// A common use for a friend function is to overload the operator<< for streaming.
friend std::ostream& operator<<(std::ostream& os, Animal animal);
};
void printWeight(Animal animal)
{
std::cout << animal.weight << "\n";
}
class AnimalPrinter
{
public:
void print(const Animal& animal)
{
// Because of the `friend class AnimalPrinter;" declaration, we are
// allowed to access private members here.
std::cout << animal.weight << ", " << animal.height << std::endl;
}
}
std::ostream& operator<<(std::ostream& os, Animal animal)
{
os << "Animal height: " << animal.height << "\n";
return os;
}
int main() {
Animal animal = {10, 5};
printWeight(animal);
AnimalPrinter aPrinter;
aPrinter.print(animal);
std::cout << animal;
}
10
10, 5
Animal height: 5
중첩 클래스 / 구조체
class
나 struct
자체 내에 또 다른 class
/ struct
정의를 포함 할 수 있습니다.이 struct
"중첩 클래스"라고합니다. 이 상황에서 포함하는 클래스는 "둘러싸는 클래스"라고합니다. 중첩 된 클래스 정의는 포함하는 클래스의 멤버로 간주되지만 그렇지 않으면 분리됩니다.
struct Outer {
struct Inner { };
};
둘러싸는 클래스의 바깥 쪽에서 범위 연산자를 사용하여 중첩 된 클래스에 액세스합니다. 그러나 엔 클로징 클래스 내부에서 한정자없이 중첩 클래스를 사용할 수 있습니다.
struct Outer {
struct Inner { };
Inner in;
};
// ...
Outer o;
Outer::Inner i = o.in;
중첩되지 않은 class
/ struct
와 마찬가지로 멤버 함수와 정적 변수는 중첩 클래스 또는 포함하는 네임 스페이스에서 정의 할 수 있습니다. 그러나 중첩 클래스와 다른 클래스로 간주되므로 포함 클래스 내에서 정의 할 수 없습니다.
// Bad.
struct Outer {
struct Inner {
void do_something();
};
void Inner::do_something() {}
};
// Good.
struct Outer {
struct Inner {
void do_something();
};
};
void Outer::Inner::do_something() {}
중첩되지 않은 클래스와 마찬가지로 중첩 클래스는 직접 사용되기 전에 정의 된 경우 나중에 전달되고 나중에 정의 될 수 있습니다.
class Outer {
class Inner1;
class Inner2;
class Inner1 {};
Inner1 in1;
Inner2* in2p;
public:
Outer();
~Outer();
};
class Outer::Inner2 {};
Outer::Outer() : in1(Inner1()), in2p(new Inner2) {}
Outer::~Outer() {
if (in2p) { delete in2p; }
}
C ++ 11 이전에는 중첩 클래스는 그 클래스에서 오는 유형 이름, static
멤버 및 열거 자에만 액세스 할 수있었습니다. 동봉하는 클래스에 정의 된 다른 모든 멤버는 제한을 벗어났습니다.
C ++ 11에서 중첩 클래스와 그 멤버는 마치 둘러싸는 클래스의 friend
것처럼 처리되고 일반적인 액세스 규칙에 따라 모든 멤버에 액세스 할 수 있습니다. 중첩 클래스의 멤버가 둘러싸는 클래스의 하나 이상의 비 정적 멤버를 평가해야하는 경우 인스턴스를 전달해야합니다.
class Outer {
struct Inner {
int get_sizeof_x() {
return sizeof(x); // Legal (C++11): x is unevaluated, so no instance is required.
}
int get_x() {
return x; // Illegal: Can't access non-static member without an instance.
}
int get_x(Outer& o) {
return o.x; // Legal (C++11): As a member of Outer, Inner can access private members.
}
};
int x;
};
반대로, 바깥 쪽 클래스는 명시 적으로 부여되는 허가없이 개인 회원에 액세스 할 수 없습니다, 따라서 중첩 된 클래스의 친구로 취급하고 있지 않습니다.
class Outer {
class Inner {
// friend class Outer;
int x;
};
Inner in;
public:
int get_x() {
return in.x; // Error: int Outer::Inner::x is private.
// Uncomment "friend" line above to fix.
}
};
중첩 된 클래스의 친구는 자동으로 둘러싸는 클래스의 친구로 간주되지 않습니다. 그들이 동봉하는 수업의 친구가되어야 할 필요가 있다면, 이것은 별도로 선언해야합니다. 반대로 둘러싸는 클래스는 자동적으로 중첩 된 클래스의 친구로 간주되지 않으므로 둘러싸는 클래스의 친구도 중첩 된 클래스의 친구로 간주되지 않습니다.
class Outer {
friend void barge_out(Outer& out, Inner& in);
class Inner {
friend void barge_in(Outer& out, Inner& in);
int i;
};
int o;
};
void barge_in(Outer& out, Outer::Inner& in) {
int i = in.i; // Good.
int o = out.o; // Error: int Outer::o is private.
}
void barge_out(Outer& out, Outer::Inner& in) {
int i = in.i; // Error: int Outer::Inner::i is private.
int o = out.o; // Good.
}
다른 모든 클래스 멤버와 마찬가지로 중첩 클래스는 공용 액세스 권한이있는 경우에만 클래스 외부에서 이름을 지정할 수 있습니다. 그러나 명시 적으로 이름을 지정하지 않는 한 액세스 한정자와 상관없이 액세스 할 수 있습니다.
class Outer {
struct Inner {
void func() { std::cout << "I have no private taboo.\n"; }
};
public:
static Inner make_Inner() { return Inner(); }
};
// ...
Outer::Inner oi; // Error: Outer::Inner is private.
auto oi = Outer::make_Inner(); // Good.
oi.func(); // Good.
Outer::make_Inner().func(); // Good.
중첩 클래스에 대한 유형 별칭을 만들 수도 있습니다. 형식 별칭이 둘러싼 클래스에 포함되어 있으면 중첩 형식과 형식 별칭이 다른 액세스 한정자를 가질 수 있습니다. 형식 별칭이 둘러싼 클래스 외부에 있으면 중첩 클래스 또는 그 typedef
공개해야합니다.
class Outer {
class Inner_ {};
public:
typedef Inner_ Inner;
};
typedef Outer::Inner ImOut; // Good.
typedef Outer::Inner_ ImBad; // Error.
// ...
Outer::Inner oi; // Good.
Outer::Inner_ oi; // Error.
ImOut oi; // Good.
다른 클래스와 마찬가지로 중첩 클래스는 다른 클래스에서 파생되거나 파생 될 수 있습니다.
struct Base {};
struct Outer {
struct Inner : Base {};
};
struct Derived : Outer::Inner {};
이것은 프로그래머가 필요에 따라 중첩 된 클래스를 업데이트 할 수있게함으로써, 둘러싸는 클래스가 다른 클래스에 의해 파생되는 상황에서 유용 할 수 있습니다. 이것은 typedef와 결합되어 각각의 둘러싸는 클래스의 중첩 된 클래스에 대해 일관된 이름을 제공 할 수 있습니다 :
class BaseOuter {
struct BaseInner_ {
virtual void do_something() {}
virtual void do_something_else();
} b_in;
public:
typedef BaseInner_ Inner;
virtual ~BaseOuter() = default;
virtual Inner& getInner() { return b_in; }
};
void BaseOuter::BaseInner_::do_something_else() {}
// ---
class DerivedOuter : public BaseOuter {
// Note the use of the qualified typedef; BaseOuter::BaseInner_ is private.
struct DerivedInner_ : BaseOuter::Inner {
void do_something() override {}
void do_something_else() override;
} d_in;
public:
typedef DerivedInner_ Inner;
BaseOuter::Inner& getInner() override { return d_in; }
};
void DerivedOuter::DerivedInner_::do_something_else() {}
// ...
// Calls BaseOuter::BaseInner_::do_something();
BaseOuter* b = new BaseOuter;
BaseOuter::Inner& bin = b->getInner();
bin.do_something();
b->getInner().do_something();
// Calls DerivedOuter::DerivedInner_::do_something();
BaseOuter* d = new DerivedOuter;
BaseOuter::Inner& din = d->getInner();
din.do_something();
d->getInner().do_something();
상기의 경우, 두 BaseOuter
및 DerivedOuter
회원이 입력 공급 Inner
로서 BaseInner_
및 DerivedInner_
각각. 이렇게하면 중첩 된 유형이 둘러싸는 클래스의 인터페이스를 손상시키지 않고 파생 될 수 있으며 중첩 된 유형을 다형성으로 사용할 수 있습니다.
회원 유형 및 별칭
class
나 struct
는 클래스 자체에 포함되어 있으며 클래스 자체의 구성원으로 처리되는 유형 별칭 인 멤버 유형 별칭을 정의 할 수도 있습니다.
struct IHaveATypedef {
typedef int MyTypedef;
};
struct IHaveATemplateTypedef {
template<typename T>
using MyTemplateTypedef = std::vector<T>;
};
정적 멤버와 마찬가지로 이러한 typedef는 범위 연산자 인 ::
사용하여 액세스됩니다.
IHaveATypedef::MyTypedef i = 5; // i is an int.
IHaveATemplateTypedef::MyTemplateTypedef<int> v; // v is a std::vector<int>.
일반 유형 별명과 마찬가지로 각 구성원 유형 별명은 해당 정의 이전에 정의되었거나 별명이 지정된 모든 유형을 참조 할 수 있습니다. 마찬가지로 클래스 정의 외부에있는 typedef는 클래스 정의 뒤에 오는 경우 클래스 정의 내의 접근 가능한 typedef를 참조 할 수 있습니다.
template<typename T>
struct Helper {
T get() const { return static_cast<T>(42); }
};
struct IHaveTypedefs {
// typedef MyTypedef NonLinearTypedef; // Error if uncommented.
typedef int MyTypedef;
typedef Helper<MyTypedef> MyTypedefHelper;
};
IHaveTypedefs::MyTypedef i; // x_i is an int.
IHaveTypedefs::MyTypedefHelper hi; // x_hi is a Helper<int>.
typedef IHaveTypedefs::MyTypedef TypedefBeFree;
TypedefBeFree ii; // ii is an int.
멤버 유형 별칭은 모든 액세스 수준으로 선언 할 수 있으며 적절한 액세스 한정자를 존중합니다.
class TypedefAccessLevels {
typedef int PrvInt;
protected:
typedef int ProInt;
public:
typedef int PubInt;
};
TypedefAccessLevels::PrvInt prv_i; // Error: TypedefAccessLevels::PrvInt is private.
TypedefAccessLevels::ProInt pro_i; // Error: TypedefAccessLevels::ProInt is protected.
TypedefAccessLevels::PubInt pub_i; // Good.
class Derived : public TypedefAccessLevels {
PrvInt prv_i; // Error: TypedefAccessLevels::PrvInt is private.
ProInt pro_i; // Good.
PubInt pub_i; // Good.
};
이것은 추상화 수준을 제공하는 데 사용할 수 있으므로 클래스 설계자가 의존하는 코드를 중단하지 않고 내부 설계를 변경할 수 있습니다.
class Something {
friend class SomeComplexType;
short s;
// ...
public:
typedef SomeComplexType MyHelper;
MyHelper get_helper() const { return MyHelper(8, s, 19.5, "shoe", false); }
// ...
};
// ...
Something s;
Something::MyHelper hlp = s.get_helper();
이 상황에서 도우미 클래스가 SomeComplexType
에서 다른 유형으로 변경되면 typedef
및 friend
선언 만 수정해야합니다. 도우미 클래스가 동일한 기능을 제공하는 한, 이름으로 지정하는 대신 Something::MyHelper
로 사용하는 코드는 일반적으로 수정하지 않고도 작동합니다. 이러한 방식으로 기본 구현이 변경 될 때 수정해야하는 코드의 양을 최소화하므로 형식 이름은 한 위치에서만 변경하면됩니다.
이것은 원한다면 decltype
과 결합 될 수도 있습니다.
class SomethingElse {
AnotherComplexType<bool, int, SomeThirdClass> helper;
public:
typedef decltype(helper) MyHelper;
private:
InternalVariable<MyHelper> ivh;
// ...
public:
MyHelper& get_helper() const { return helper; }
// ...
};
이 상황에서 SomethingElse::helper
의 구현을 변경하면 decltype
때문에 자동으로 typedef가 변경됩니다. 이렇게하면 helper
를 변경하려는 경우 필요한 수정 작업이 최소화되므로 사람의 실수 위험을 최소화 할 수 있습니다.
그러나 모든 것과 마찬가지로 이것은 너무 오래 걸릴 수 있습니다. typename이 내부적으로 한 번 또는 두 번만 사용되고 외부 적으로 0 번 사용되는 경우 예를 들어 별칭을 제공 할 필요가 없습니다. 프로젝트 전체에서 수백 또는 수천 번 사용되었거나 이름이 충분히 긴 경우 항상 절대적으로 사용하는 대신 typedef로 제공하는 것이 유용 할 수 있습니다. 생성 된 불필요한 잡음의 양과 앞으로의 호환성 및 편의성의 균형을 맞추어야합니다.
또한 템플릿 클래스와 함께 사용하여 클래스 외부의 템플릿 매개 변수에 대한 액세스를 제공 할 수 있습니다.
template<typename T>
class SomeClass {
// ...
public:
typedef T MyParam;
MyParam getParam() { return static_cast<T>(42); }
};
template<typename T>
typename T::MyParam some_func(T& t) {
return t.getParam();
}
SomeClass<int> si;
int i = some_func(si);
일반적으로 컨테이너와 함께 사용되며 일반적으로 요소 유형 및 기타 도우미 유형을 구성원 유형 별칭으로 제공합니다. 예를 들어, C ++ 표준 라이브러리의 대부분의 컨테이너는 필요한 12 개의 다른 유형과 함께 다음과 같은 12 개의 헬퍼 유형을 제공합니다.
template<typename T>
class SomeContainer {
// ...
public:
// Let's provide the same helper types as most standard containers.
typedef T value_type;
typedef std::allocator<value_type> allocator_type;
typedef value_type& reference;
typedef const value_type& const_reference;
typedef value_type* pointer;
typedef const value_type* const_pointer;
typedef MyIterator<value_type> iterator;
typedef MyConstIterator<value_type> const_iterator;
typedef std::reverse_iterator<iterator> reverse_iterator;
typedef std::reverse_iterator<const_iterator> const_reverse_iterator;
typedef size_t size_type;
typedef ptrdiff_t difference_type;
};
C ++ 11 이전 버전에서는이 기능이 아직 제공되지 않았기 때문에 일반적으로 "템플릿 typedef
"를 제공하는 데 사용되었습니다. 이것들은 별칭 템플릿의 도입과는 다소 공통점이 없지만 일부 상황에서는 여전히 유용합니다 (다른 상황에서는 별칭 템플릿과 결합됩니다. 이는 함수 포인터와 같은 복합 유형의 개별 구성 요소를 얻는데 매우 유용 할 수 있습니다 ). 일반적으로 형식 별칭에 대해 이름 type
을 사용합니다.
template<typename T>
struct TemplateTypedef {
typedef T type;
}
TemplateTypedef<int>::type i; // i is an int.
이것은 하나 이상의 매개 변수를 정의하는 별칭을 제공하기 위해 여러 템플릿 매개 변수가있는 유형과 함께 사용되었습니다.
template<typename T, size_t SZ, size_t D>
class Array { /* ... */ };
template<typename T, size_t SZ>
struct OneDArray {
typedef Array<T, SZ, 1> type;
};
template<typename T, size_t SZ>
struct TwoDArray {
typedef Array<T, SZ, 2> type;
};
template<typename T>
struct MonoDisplayLine {
typedef Array<T, 80, 1> type;
};
OneDArray<int, 3>::type arr1i; // arr1i is an Array<int, 3, 1>.
TwoDArray<short, 5>::type arr2s; // arr2s is an Array<short, 5, 2>.
MonoDisplayLine<char>::type arr3c; // arr3c is an Array<char, 80, 1>.
정적 클래스 멤버
클래스는 변수 또는 함수가 될 수있는 static
멤버도 가질 수 있습니다. 이것들은 클래스 범위에있는 것으로 간주되지만 정상적인 멤버로 취급되지 않습니다. 그들은 정적 저장 기간 (프로그램 시작부터 끝까지 존재 함)을 가지며 클래스의 특정 인스턴스에 묶이지 않으며 전체 클래스에 대해 하나의 복사본 만 존재합니다.
class Example {
static int num_instances; // Static data member (static member variable).
int i; // Non-static member variable.
public:
static std::string static_str; // Static data member (static member variable).
static int static_func(); // Static member function.
// Non-static member functions can modify static member variables.
Example() { ++num_instances; }
void set_str(const std::string& str);
};
int Example::num_instances;
std::string Example::static_str = "Hello.";
// ...
Example one, two, three;
// Each Example has its own "i", such that:
// (&one.i != &two.i)
// (&one.i != &three.i)
// (&two.i != &three.i).
// All three Examples share "num_instances", such that:
// (&one.num_instances == &two.num_instances)
// (&one.num_instances == &three.num_instances)
// (&two.num_instances == &three.num_instances)
정적 멤버 변수는 선언 된 클래스 내에서 정의 된 것으로 간주되지 않으므로 정의가 클래스 정의 외부에 있습니다. 프로그래머는 자신의 정의에서 정적 변수를 초기화 할 수는 있지만 필수는 아닙니다. 멤버 변수를 정의 할 때 static
이라는 키워드는 생략됩니다.
class Example {
static int num_instances; // Declaration.
public:
static std::string static_str; // Declaration.
// ...
};
int Example::num_instances; // Definition. Zero-initialised.
std::string Example::static_str = "Hello."; // Definition.
이 때문에 정적 변수는 나중에 완전한 유형으로 정의되는 한 불완전한 유형 ( void
제외) 일 수 있습니다.
struct ForwardDeclared;
class ExIncomplete {
static ForwardDeclared fd;
static ExIncomplete i_contain_myself;
static int an_array[];
};
struct ForwardDeclared {};
ForwardDeclared ExIncomplete::fd;
ExIncomplete ExIncomplete::i_contain_myself;
int ExIncomplete::an_array[5];
정적 멤버 함수는 일반 멤버 함수와 마찬가지로 클래스 정의 내부 또는 외부에서 정의 할 수 있습니다. 정적 멤버 변수와 마찬가지로, 키워드 static
클래스 정의 외부 고정 멤버 함수를 정의 할 때 생략된다.
// For Example above, either...
class Example {
// ...
public:
static int static_func() { return num_instances; }
// ...
void set_str(const std::string& str) { static_str = str; }
};
// Or...
class Example { /* ... */ };
int Example::static_func() { return num_instances; }
void Example::set_str(const std::string& str) { static_str = str; }
정적 멤버 변수가 선언되면 const
아니라 volatile
및 통합 또는 열거 타입이다, 그것은 클래스 정의 내부에서 선언에서 초기화 할 수 있습니다.
enum E { VAL = 5 };
struct ExConst {
const static int ci = 5; // Good.
static const E ce = VAL; // Good.
const static double cd = 5; // Error.
static const volatile int cvi = 5; // Error.
const static double good_cd;
static const volatile int good_cvi;
};
const double ExConst::good_cd = 5; // Good.
const volatile int ExConst::good_cvi = 5; // Good.
C ++ 11에서 LiteralType
유형의 정적 멤버 변수 ( constexpr
규칙에 따라 컴파일 타임에 생성 할 수있는 유형)는 constexpr
로 선언 할 수도 있습니다. 그렇다면 클래스 정의 내에서 초기화되어야합니다.
struct ExConstexpr {
constexpr static int ci = 5; // Good.
static constexpr double cd = 5; // Good.
constexpr static int carr[] = { 1, 1, 2 }; // Good.
static constexpr ConstexprConstructibleClass c{}; // Good.
constexpr static int bad_ci; // Error.
};
constexpr int ExConstexpr::bad_ci = 5; // Still an error.
const
또는 constexpr
정적 멤버 변수가 odr-used 인 경우 (비공식적으로 주소가 지정되었거나 참조에 할당 된 경우) 클래스 정의 외부에 별도의 정의가 있어야합니다. 이 정의에는 초기화 프로그램이 포함될 수 없습니다.
struct ExODR {
static const int odr_used = 5;
};
// const int ExODR::odr_used;
const int* odr_user = & ExODR::odr_used; // Error; uncomment above line to resolve.
정적 멤버는 지정된 인스턴스에 연결되지 않으므로 범위 연산자 인 ::
사용하여 액세스 할 수 있습니다.
std::string str = Example::static_str;
그들은 마치 비 정적 인 멤버 인 것처럼 액세스 할 수도 있습니다. 이것은 역사적인 의미이지만, 멤버가 정적인지 비 정적인지에 대한 혼동을 방지하기 위해 범위 연산자보다 덜 일반적으로 사용됩니다.
Example ex;
std::string rts = ex.static_str;
클래스 멤버는 비 정적 클래스 멤버와 마찬가지로 범위를 한정하지 않고 정적 멤버에 액세스 할 수 있습니다.
class ExTwo {
static int num_instances;
int my_num;
public:
ExTwo() : my_num(num_instances++) {}
static int get_total_instances() { return num_instances; }
int get_instance_number() const { return my_num; }
};
int ExTwo::num_instances;
그것들은 mutable
될 수 없으며 필요가 없습니다. 특정 인스턴스에 묶여 있지 않기 때문에 인스턴스가 정적 멤버인지 정적 멤버인지는 정적 멤버에 영향을주지 않습니다.
struct ExDontNeedMutable {
int immuta;
mutable int muta;
static int i;
ExDontNeedMutable() : immuta(-5), muta(-5) {}
};
int ExDontNeedMutable::i;
// ...
const ExDontNeedMutable dnm;
dnm.immuta = 5; // Error: Can't modify read-only object.
dnm.muta = 5; // Good. Mutable fields of const objects can be written.
dnm.i = 5; // Good. Static members can be written regardless of an instance's const-ness.
정적 멤버는 정적 멤버가 아닌 액세스 수정자를 존중합니다.
class ExAccess {
static int prv_int;
protected:
static int pro_int;
public:
static int pub_int;
};
int ExAccess::prv_int;
int ExAccess::pro_int;
int ExAccess::pub_int;
// ...
int x1 = ExAccess::prv_int; // Error: int ExAccess::prv_int is private.
int x2 = ExAccess::pro_int; // Error: int ExAccess::pro_int is protected.
int x3 = ExAccess::pub_int; // Good.
주어진 인스턴스에 묶여 있지 않기 때문에 정적 멤버 함수에는 this
포인터가 없습니다. 이로 인해 인스턴스를 전달하지 않으면 비 정적 멤버 변수에 액세스 할 수 없습니다.
class ExInstanceRequired {
int i;
public:
ExInstanceRequired() : i(0) {}
static void bad_mutate() { ++i *= 5; } // Error.
static void good_mutate(ExInstanceRequired& e) { ++e.i *= 5; } // Good.
};
this
포인터를 가지지 않기 때문에, 그들의 주소는 포인터 - 투 - 멤버 함수에 저장 될 수없고 대신 함수에 대한 일반적인 포인터에 저장됩니다.
struct ExPointer {
void nsfunc() {}
static void sfunc() {}
};
typedef void (ExPointer::* mem_f_ptr)();
typedef void (*f_ptr)();
mem_f_ptr p_sf = &ExPointer::sfunc; // Error.
f_ptr p_sf = &ExPointer::sfunc; // Good.
this
포인터가 없기 때문에 const
또는 volatile
일 수 없으며 ref-qualifier도 가질 수 없습니다. 또한 가상 일 수도 없습니다.
struct ExCVQualifiersAndVirtual {
static void func() {} // Good.
static void cfunc() const {} // Error.
static void vfunc() volatile {} // Error.
static void cvfunc() const volatile {} // Error.
static void rfunc() & {} // Error.
static void rvfunc() && {} // Error.
virtual static void vsfunc() {} // Error.
static virtual void svfunc() {} // Error.
};
정적 인스턴스 변수는 주어진 인스턴스에 묶여 있지 않으므로 효과적으로 전역 변수로 취급됩니다. 프로그램이 시작될 때 만들어지며, 클래스가 실제로 존재하는지 여부에 관계없이 종료 될 때 파괴됩니다. 변수가 thread_local
(C ++ 11 이상)으로 선언되지 않는 한 각 정적 멤버 변수의 복사본 하나만 존재합니다.이 경우 스레드 당 하나의 복사본이 있습니다.
정적 멤버 변수는 클래스가 외부 또는 내부 링크를 가지고 있는지 여부와 관계없이 클래스와 동일한 연결을가집니다. 로컬 클래스 및 명명되지 않은 클래스는 정적 멤버를 가질 수 없습니다.
비 정적 멤버 함수
클래스는 클래스의 개별 인스턴스에서 작동하는 비 정적 멤버 함수를 가질 수 있습니다.
class CL {
public:
void member_function() {}
};
이러한 함수는 다음과 같이 클래스의 인스턴스에서 호출됩니다.
CL instance;
instance.member_function();
클래스 정의 내부 또는 외부에서 정의 할 수 있습니다. 외부에 정의 된 경우 클래스 범위에 속하도록 지정됩니다.
struct ST {
void defined_inside() {}
void defined_outside();
};
void ST::defined_outside() {}
CV 인증 및 / 또는 ref-qualified 일 수 있으며 호출 된 인스턴스를 보는 방법에 영향을 미칩니다. 이 함수는 인스턴스가 지정된 cv-qualifier를 갖는 것으로 간주합니다 (있는 경우). 호출되는 버전은 인스턴스의 cv 한정자를 기반으로합니다. 인스턴스와 동일한 cv-qualifier를 가진 버전이 없으면 사용 가능한 경우보다 많은 버전의 버전이 호출됩니다.
struct CVQualifiers {
void func() {} // 1: Instance is non-cv-qualified.
void func() const {} // 2: Instance is const.
void cv_only() const volatile {}
};
CVQualifiers non_cv_instance;
const CVQualifiers c_instance;
non_cv_instance.func(); // Calls #1.
c_instance.func(); // Calls #2.
non_cv_instance.cv_only(); // Calls const volatile version.
c_instance.cv_only(); // Calls const volatile version.
멤버 함수 ref-qualifiers는 rvalue 인스턴스에서 함수를 호출 할 것인지 여부를 나타내고 함수 cv-qualifiers와 동일한 구문을 사용합니다.
struct RefQualifiers {
void func() & {} // 1: Called on normal instances.
void func() && {} // 2: Called on rvalue (temporary) instances.
};
RefQualifiers rf;
rf.func(); // Calls #1.
RefQualifiers{}.func(); // Calls #2.
필요한 경우 CV 한정어와 ref 한정자가 결합 될 수 있습니다.
struct BothCVAndRef {
void func() const& {} // Called on normal instances. Sees instance as const.
void func() && {} // Called on temporary instances.
};
그들은 가상 일 수도 있습니다. 이것은 다형성의 기본이며, 하위 클래스가 자신의 기능을 제공하면서 상위 클래스와 동일한 인터페이스를 제공 할 수 있습니다.
struct Base {
virtual void func() {}
};
struct Derived {
virtual void func() {}
};
Base* bp = new Base;
Base* dp = new Derived;
bp.func(); // Calls Base::func().
dp.func(); // Calls Derived::func().
명명되지 않은 구조체 / 클래스
struct
가 허용됩니다 (유형은 이름 없음).
void foo()
{
struct /* No name */ {
float x;
float y;
} point;
point.x = 42;
}
또는
struct Circle
{
struct /* No name */ {
float x;
float y;
} center; // but a member name
float radius;
};
나중에
Circle circle;
circle.center.x = 42.f;
그러나 익명 struct
(이름이없는 유형 및 이름이없는 객체)
struct InvalidCircle
{
struct /* No name */ {
float centerX;
float centerY;
}; // No member either.
float radius;
};
참고 : 일부 컴파일러에서는 익명 struct
를 확장자 로 사용할 수 있습니다 .
lamdba 는 특별한 이름없는
struct
로 볼 수 있습니다.decltype
은 이름없는struct
유형을 검색 할 수 있습니다.decltype(circle.point) otherPoint;
이름없는
struct
인스턴스는 template 메소드의 매개 변수가 될 수 있습니다.void print_square_coordinates() { const struct {float x; float y;} points[] = { {-1, -1}, {-1, 1}, {1, -1}, {1, 1} }; // for range relies on `template <class T, std::size_t N> std::begin(T (&)[N])` for (const auto& point : points) { std::cout << "{" << point.x << ", " << point.y << "}\n"; } decltype(points[0]) topRightCorner{1, 1}; auto it = std::find(points, points + 4, topRightCorner); std::cout << "top right corner is the " << 1 + std::distance(points, it) << "th\n"; }