#include #include "complex.h"
네임스페이스 std 사용;
ostream& 연산자 << (ostream& os, const complex& x) { return os << '(' << real (x) << ',' << imag (x) << ')' }
int main() { 복합 c1(2, 1); 복합 c2(4, 0);
cout << c1 << cout << c2 << endl;
cout << c1+c2 << endl; cout << c1-c2 << cout << c1*c2 << endl;
cout << conj(c1) << endl; cout << nor(c1) << cout << polar(10,4) << endl;
cout << (c1 += c2) << endl;
cout << (c1 == c2) << endl; cout << (c1 != c2) << cout << +c2 << endl;
cout << (c2 - 2) << cout << (5 + c2) << endl;
0을 반환합니다.
위 그림과 같이 "=" 할당 연산자를 오버로드할 때 자체 할당을 확인해야 하는 이유는 다음과 같습니다.
자체 할당 감지가 없으면 자체 객체의 m_data가 해제되고 m_data가 가리키는 내용이 존재하지 않으므로 복사에 문제가 발생합니다.
설명을 위한 두 가지 클래스는 다음과 같습니다.
class complex
{
public:
complex (double r = 0, double i = 0)
: re (r), im (i)
{ }
complex& operator += (const complex&);
double real () const { return re; }
double imag () const { return im; }
private:
double re, im;
friend complex& __doapl (complex*,
const complex&);
};
class String
{
public:
String(const char* cstr = 0);
String(const String& str);
String& operator=(const String& str);
~String();
char* get_c_str() const { return m_data; }
private:
char* m_data;
};
이 두 객체를 생성한 후 컴파일러(VC)는 다음과 같이 두 객체에 메모리를 할당합니다.
왼쪽 두 개는 디버그 모드와 릴리스 모드에서 클래스 콤플렉스의 컴파일러 메모리 할당입니다. 디버그 모드에서 컴파일러는 머리와 꼬리(빨간색 부분), 4*8 + 4 크기 정보 부분(회색 부분)을 복합 개체 메모리에 삽입합니다. 녹색 부분은 계산 후 실제로 차지하는 공간입니다. 섹션은 52개 단어밖에 없지만 VC는 16바이트로 정렬되어 있으므로 52의 가장 가까운 16배수는 64이고 12바이트 간격을 채워야 합니다(청록색 패드 부분). 릴리스 부분의 복합 객체에 대해서는 정보 헤더와 헤더 부분만 추가됩니다. 문자열 클래스의 분석은 기본적으로 동일합니다.
다음으로 VC 컴파일러가 배열 객체를 할당하는 방법을 살펴보겠습니다.
마찬가지로, 컴파일러는 객체에 몇 가지 중복 정보를 추가합니다. 배열에 객체가 세 개 있으므로 8개의 double이 있습니다. 그런 다음 컴파일러는 개별 객체를 표시하기 위해 세 개의 객체 앞에 "3"을 삽입합니다. . String 클래스의 분석 방법도 유사하다.
아래 그림은 배열 객체를 삭제하는 데 delete[] 메서드가 필요한 이유를 보여줍니다.
문자열.h
#ifndef __MYSTRING__
#define __MYSTRING__
class String
{
public:
String(const char* cstr=0);
String(const String& str);
String& operator=(const String& str);
~String();
char* get_c_str() const { return m_data; }
private:
char* m_data;
};
#include <cstring>
inline
String::String(const char* cstr)
{
if (cstr) {
m_data = new char[strlen(cstr)+1];
strcpy(m_data, cstr);
}
else {
m_data = new char[1];
*m_data = ' ';
}
}
inline
String::~String()
{
delete[] m_data;
}
inline
String& String::operator=(const String& str)
{
if (this == &str)
return *this;
delete[] m_data;
m_data = new char[ strlen(str.m_data) + 1 ];
strcpy(m_data, str.m_data);
return *this;
}
inline
String::String(const String& str)
{
m_data = new char[ strlen(str.m_data) + 1 ];
strcpy(m_data, str.m_data);
}
#include <iostream>
using namespace std;
ostream& operator<<(ostream& os, const String& str)
{
os << str.get_c_str();
return os;
}
#endif
문자열_테스트.cpp
#include "string.h"
#include <iostream>
using namespace std;
int main()
{
String s1("hello");
String s2("world");
String s3(s2);
cout << s3 << endl;
s3 = s1;
cout << s3 << endl;
cout << s2 << endl;
cout << s1 << endl;
}
C++에서는 단일 매개변수 생성자(또는 첫 번째 매개변수를 제외한 모든 매개변수에 대해 기본값을 갖는 다중 매개변수 생성자)가 두 가지 역할을 합니다. 1은 생성자이고, 2는 기본 및 암시적 유형 변환 연산자입니다.
따라서 때로는 AAA = XXX와 같은 코드를 작성할 때 XXX 유형이 AAA 단일 매개변수 생성자의 매개변수 유형이 되는 경우 컴파일러는 자동으로 이 생성자를 호출하여 AAA 개체를 생성합니다.
이거 정말 시원하고 편해보이네요. 그러나 어떤 경우에는(아래의 권위 있는 예 참조) 이는 우리(프로그래머)의 원래 의도에 어긋납니다. 이때 이 생성자는 명시적으로만 호출/사용할 수 있고 암시적으로 형식 변환 연산자로 사용할 수 없도록 지정하려면 생성자 앞에 명시적 수정을 추가해야 합니다.
명시적 생성자는 암시적 변환을 방지하는 데 사용됩니다. 아래 코드를 살펴보십시오.
class Test1
{
public:
Test1(int n)
{
num=n;
}//普通构造函数
private:
int num;
};
class Test2
{
public:
explicit Test2(int n)
{
num=n;
}//explicit(显式)构造函数
private:
int num;
};
int main()
{
Test1 t1=12;//隐式调用其构造函数,成功
Test2 t2=12;//编译错误,不能隐式调用其构造函数
Test2 t2(12);//显式调用成功
return 0;
}
Test1의 생성자는 int 매개변수를 사용하며 코드의 23행은 암시적으로 Test1의 생성자를 호출하도록 변환됩니다. Test2의 생성자는 명시적으로 선언됩니다. 즉, 암시적 변환을 통해 생성자를 호출할 수 없으므로 코드 24행에서 컴파일 오류가 발생합니다.
일반 생성자는 암시적으로 호출될 수 있습니다. 명시적 생성자는 명시적으로만 호출할 수 있습니다.
Fraction을 double 유형으로 변환해야 하는 경우 변환을 위해 double() 함수가 자동으로 호출됩니다. 위 그림에서 볼 수 있듯이 컴파일러는 double d = 4 + f를 분석하는 동안 4가 정수라고 판단한 후 계속해서 f를 판단합니다. f가 double() 함수를 제공한 다음 double을 수행하는 것이 관찰됩니다. () 연산을 f에 수행하고 0.6을 계산한 다음 이를 4에 더하고 마지막으로 double 유형 4.6을 얻습니다.
위 그림에는 Fraction이라는 클래스가 정의되어 있습니다. "+" 연산자는 클래스에 오버로드됩니다. f+4 연산 중에 "4"는 컴파일러에 의해 암시적으로 Fraction 객체로 변환(생성자)된 다음 전달됩니다. through Fraction 오버로드된 "+" 연산자가 작업에 참여합니다.
위 그림과 같이 Fraction에 double() 함수가 추가되어 있는데, 이는 Fraction의 두 멤버 변수를 나눈 후 이를 강제로 double 유형으로 변환하고 결과를 반환하는 것입니다. f+4 오버로딩 과정에서 컴파일러는 다음과 같이 합니다. 오류를 보고하려면 다음과 같은 분석을 수행할 수 있습니다.
1. 먼저 4를 암시적으로 Fraction 객체로 변환(생성자)한 다음 오버로드된 "+" 연산자를 "f"와 함께 연산하여 Fraction 객체를 반환합니다.
2. 먼저 4를 암시적으로 Fraction 객체로 변환(생성자)한 다음 오버로드된 "+" 연산자와 "f" 연산을 통해 해당 쌍에 대해 이중 연산을 수행하고 마지막으로 Fraction 객체를 반환합니다.
3. . .
따라서 컴파일러는 모호성을 생성하고 오류를 보고하는 최소한 두 가지 방법을 가지고 있습니다. 위 그림과 같이 생성자 Franction 앞에 명시적 키워드를 추가하면 암시적 변환이 취소되므로 d2 = f + 4 실행 중에 f는 double 함수를 호출하여 0.6으로 변환한 다음 4.6이 되도록 4에 추가합니다. 생성자가 암시적 수식 변환을 취소하므로 4.6을 분수로 변환할 수 없으므로 오류가 보고됩니다.
다음 그림은 C++ stl의 연산자 오버로딩 및 변환 함수의 적용을 보여줍니다.
아래 그림은 스마트 포인터의 내부 구조와 사용법을 잘 보여줍니다. 스마트 포인터 구문에는 세 가지 핵심 사항이 있습니다. 첫 번째는 위 그림의 T* px에 해당하는 저장된 외부 포인터입니다. 들어오는 포인터와 관련된 작업은 들어오는 포인터 대신 수행됩니다. 두 번째는 "*" 연산자를 오버로드하고 역참조하고 포인터가 가리키는 객체를 반환하는 것입니다. 세 번째는 "->" 연산자를 오버로드하는 것입니다. return 위 그림에 해당하는 포인터는 px입니다.
반복자(Iterator)도 스마트 포인터의 일종입니다. 위에서 언급한 스마트 포인터의 세 가지 요소는 아래 그림의 빨간색 글꼴과 노란색으로 표시된 부분에 해당합니다.
반복자 오버로드의 "*" 및 "->" 오버로드 문자는 아래에서 주의 깊게 분석됩니다.
목록 반복자 객체인 list::iterator ite를 만듭니다. 여기 목록은 목록 템플릿 정의의 클래스인 Foo 객체를 저장하는 데 사용됩니다. T, Operator*()는 (*node).data 객체를 반환하고 node는 __link_type 유형이지만 __link_type은 __list_node<T>* 유형입니다. 여기서 T는 Foo이므로 node는 __list_node<Foo >* 유형입니다. *node).data는 Foo 유형의 객체를 가져오고 &(operator*())는 최종적으로 Foo 객체의 주소를 가져옵니다. 즉, Foo*를 반환합니다. 유형에 대한 포인터입니다.
위 그림에서 볼 수 있듯이 각 functor는 "()" 연산자를 오버로드한 후 "functor"가 되는 클래스입니다. 실제로는 클래스이지만 함수의 속성을 갖고 있는 것처럼 보입니다. 아래 그림에 표시된 것처럼 각 펑터는 실제로 그 뒤에 이상한 클래스를 통합합니다. 이 클래스는 프로그래머가 수동으로 명시적으로 선언할 필요가 없습니다. 표준 라이브러리의 펑터도 이상한 클래스를 상속받습니다. 이 클래스의 내용은 아래 그림에 나와 있습니다. 이는 단지 몇 가지 사항을 선언할 뿐 실제 변수나 함수는 없습니다. 구체적인 내용은 STL에서 설명합니다.
클래스 템플릿과 달리 함수 템플릿은 사용 시 들어오는 매개변수의 유형을 명시적으로 선언할 필요가 없으며 컴파일러가 자동으로 유형을 추론합니다.
멤버 템플릿은 일반 프로그래밍에서 자주 사용됩니다. 확장성을 높이기 위해 위 그림을 예로 들면 T1이 U1의 기본 클래스인 경우가 많고 다음 예를 볼 수 있습니다. 이를 통해 이렇게 전달된 U1과 U2의 부모 또는 조상 클래스가 T1과 T2인 한 이 방법을 통해 상속과 다형성을 교묘하게 활용할 수 있지만 그 반대는 불가능하다. 이 방법은 STL에서 많이 사용됩니다.
이름에서 알 수 있듯이 템플릿 부분화는 템플릿에 특정 데이터 유형을 지정하는 것을 의미하며 이는 일반화와 다릅니다. 물론 템플릿 부분화에도 등급이 있으며 부분 유형을 지정할 수 있는데 이를 부분 특수화라고 합니다.
C++11 강좌에서는 설명할 내용이 너무 많아 일단 여기서만 소개하겠습니다.
C++11 강좌에서는 너무 많은 내용을 설명하기 때문에 지금은 여기서만 소개하겠습니다.
참조는 참조된 변수의 별칭으로 볼 수 있습니다.
위 그림과 같이 A, B, C 세 개의 클래스가 정의되어 있습니다. B는 A를 상속하고 C는 B를 상속합니다. A에는 두 개의 가상 함수가 있고 B에는 하나, C에는 하나가 있습니다. 컴파일러는 위 그림과 같이 A의 객체 a를 메모리에 할당합니다. 동시에 두 개의 멤버 변수 m_data1과 m_data2가 있습니다. 동시에 클래스 A에는 가상 함수가 있으므로 컴파일러는 객체 a에 공간을 할당합니다. 가상 함수 테이블을 저장하기 위해 이 테이블은 이 클래스의 가상 함수 주소(동적 함수)를 유지합니다. 상태 저장 바인딩), 클래스 A에는 두 개의 가상 함수가 있으므로 A::vfunc1() 및 A::vfunc2()를 각각 가리키는 가상 함수 테이블에 두 개의 공백(노란색 및 파란색 공백)이 있습니다. b It; 클래스 B가 클래스 A의 vfunc1() 함수를 재정의하므로 클래스 B의 객체입니다. 따라서 B의 가상 함수 테이블(청록색 부분)은 B::vfunc1()을 가리키고 B는 클래스 A의 vfunc2()를 상속하므로 B의 가상 함수 테이블(파란색 부분)은 상위 클래스 A의 A: :vfunc2를 가리킵니다. () 함수와 유사하게 c는 다음과 같이 표현되는 클래스 C의 객체입니다. 클래스 C는 상위 클래스의 vfunc1() 함수를 다시 작성하므로 C의 가상 함수 테이블(노란색 부분)은 C::vfunc1()을 가리키게 됩니다. 동시에 C는 슈퍼클래스 A의 vfunc2()를 상속하므로 B의 가상 함수는 다음과 같습니다. 테이블(파란색 부분)은 A::vfunc2() 함수를 가리킵니다. 동시에 위 그림은 C 언어 코드를 사용하여 컴파일러의 하위 계층이 이러한 함수를 호출하는 방법을 보여줍니다. 이것이 객체 지향 상속 다형성의 본질입니다.
this 포인터는 실제로는 현재 객체의 메모리 주소를 가리키는 포인터라고 볼 수 있습니다. 위 그림과 같이 기본 클래스와 하위 클래스에 가상 함수가 있으므로 this->Serialize()가 동적으로 바인딩됩니다. 이는 (*(this->vptr)[n])(this)와 동일합니다. 앞선 절의 가상 포인터와 가상 함수 테이블을 결합하면 이해할 수 있다. 마지막에 이렇게 작성하는 것이 올바른 이유는 다음에서 요약해서 설명하겠다.