몇 달 전에 어떤 사람이이 질문을했지만 자세히 설명 할 수 없었습니다. C #에서 참조 형식과 값 형식의 차이점은 무엇입니까?
나는 그 값 유형을 알고 int
, bool
, float
, 등 참조 유형은 delegate
, interface
, 등 또는도이 잘못?
전문적인 방식으로 설명해 주시겠습니까?
몇 달 전에 어떤 사람이이 질문을했지만 자세히 설명 할 수 없었습니다. C #에서 참조 형식과 값 형식의 차이점은 무엇입니까?
나는 그 값 유형을 알고 int
, bool
, float
, 등 참조 유형은 delegate
, interface
, 등 또는도이 잘못?
전문적인 방식으로 설명해 주시겠습니까?
답변:
귀하의 예 때문에 동안 약간의 홀수 int
, bool
및 float
특정 유형, 인터페이스 및 대의원이 있습니다 가지 유형의 - 단지 같은 struct
과 enum
값 형식의 종류입니다.
이 기사에서 참조 유형 및 값 유형 에 대한 설명을 작성 했습니다 . 혼란스러워하는 부분이 있으면 기꺼이 확장하겠습니다.
"TL; DR"버전은 특정 유형의 변수 / 표현식 값이 무엇인지 생각하는 것입니다. 값 유형의 경우 값은 정보 자체입니다. 참조 유형의 경우 값은 null 일 수있는 참조이거나 정보를 포함하는 개체를 탐색하는 방법 일 수 있습니다.
예를 들어, 변수를 종이처럼 생각하십시오. "5"또는 "false"라는 값이 적혀있을 수 있지만 내 집은 가질 수 없습니다 . 내 집 으로 가는 길 을 알려야합니다. 이러한 지침은 참조와 동일합니다. 특히 두 사람이 내 집으로가는 방향이 같은 다른 종이를 가지고있을 수 있습니다. 한 사람이 그 길을 따라 내 집을 빨간색으로 칠하면 두 번째 사람도 그 변화를 볼 수 있습니다. 둘 다 종이에 내 집 사진 을 따로 찍었 다면 한 사람이 종이에 색칠을하더라도 다른 사람의 종이를 전혀 바꾸지 않을 것입니다.
while int, bool and float are specific types, interfaces and delegates are kinds of type - just like struct and enum are kinds of value types
. int, bool이 특정 유형이라는 것은 무엇을 의미합니까? int, bool, float, class, interface, delegate 등 C #의 모든 것은 유형 (정확한 데이터 유형)입니다. 데이터 형식은 C #에서 '참조 형식'과 '값 형식'으로 구분됩니다. 그렇다면 int가 특정 유형이지만 인터페이스가 일종의 유형이라고 말하는 이유는 무엇입니까?
int
구조체, string
클래스, Action
대리자 등입니다. "int, bool, float, class, interface, delegate"목록은 "10, int"와 같은 방식으로 다른 종류의 항목을 포함하는 목록입니다. 다른 종류의 것들을 포함하는 목록.
메모리 주소가 아닌 일부 값을 보유합니다.
예:
구조
저장:
TL; DR : 변수의 값은 해제 될 때마다 저장됩니다. 지역 변수는 예를 들어 스택에 살고 있지만 구성원으로 클래스 내에 선언 때 단단히가 선언 된 클래스와 결합 된 힙에 살고있다.
긴 : 그들이 선언 된 곳 따라서 값 유형이 저장됩니다. 예 : int
지역 변수로서 함수 내부의의 값은 스택에 저장되는 반면 int
, 클래스의 멤버로 선언 된 in의 값은 선언 된 클래스와 함께 힙에 저장됩니다. 클래스에는 선언 된 클래스와 정확히 동일한 라이프 타입이 있으므로 가비지 수집기의 작업이 거의 필요하지 않습니다. 하지만 더 복잡합니다. @JonSkeet의 책 " C # In Depth "를 참조하겠습니다.보다 간결한 설명을 위해 .NET의 메모리 "
장점 :
값 유형에는 추가 가비지 콜렉션이 필요하지 않습니다. 그것은 존재하는 인스턴스와 함께 가비지 수집을 가져옵니다. 메서드의 지역 변수는 메서드를 떠날 때 정리됩니다.
단점 :
큰 값 집합이 메서드에 전달되면 수신 변수가 실제로 복사하므로 메모리에 두 개의 중복 값이 있습니다.
수업을 놓치면 모든 oop 혜택이 손실됩니다.
값이 아닌 값의 메모리 주소를 보유합니다.
예:
수업
저장:
힙에 저장
장점 :
참조 변수를 메소드에 전달하면 실제로 원래 값이 변경되는 반면 값 유형에서는 주어진 변수의 사본이 취해져 그 값이 변경됩니다.
변수의 크기가 클 때 참조 형이 좋다
클래스는 참조 유형 변수로 제공되므로 재사용 가능성을 제공하므로 객체 지향 프로그래밍에 도움이됩니다.
단점 :
값을 읽을 때 할당 및 역 참조시 더 많은 작업 참조 가비지 수집기에 대한 추가 오버로드
컴퓨터가 메모리에 물건을 할당하는 방법을 알고 포인터가 무엇인지 안다면 두 가지의 차이점을 더 쉽게 이해할 수있었습니다.
참조는 일반적으로 포인터와 연관됩니다. 변수가있는 메모리 주소가 실제로 다른 메모리 위치에있는 실제 객체의 다른 메모리 주소 를 보유하고 있음을 의미합니다 .
제가 드리려고하는 예는 지나치게 단순화되어 있으므로 소금 한 알과 함께 가져 가십시오.
컴퓨터 메모리가 내부에 무언가를 담을 수있는 한 줄의 사서함 (PO Box 0001부터 PO Box n으로 시작)이라고 상상해보십시오. 사서함이 당신을 위해 그것을하지 않으면 해시 테이블이나 사전 또는 배열 또는 유사한 것을 시도하십시오.
따라서 다음과 같은 작업을 수행 할 때 :
var a = "Hello";
컴퓨터는 다음을 수행합니다.
값 유형은 실제 사물을 메모리 위치에 보관합니다.
따라서 다음과 같은 작업을 수행 할 때 :
var a = 1;
컴퓨터는 다음을 수행합니다.
이것은 약 2 년 전 다른 포럼의 제 게시물입니다. 언어는 vb.net (C #과 반대)이지만 값 유형 대 참조 유형 개념은 .net 전체에서 동일하며 예제는 여전히 유지됩니다.
.net 내에서 모든 유형은 기술적으로 기본 유형 Object에서 파생된다는 사실을 기억하는 것도 중요합니다. 값 유형은 그렇게 작동하도록 설계되었지만 결국 기본 유형 Object의 기능도 상속합니다.
A. 값 유형은 메모리에서 개별 값이 저장되는 고유 한 영역을 나타냅니다. 값 유형은 고정 된 메모리 크기이며 고정 된 크기의 주소 모음 인 스택에 저장됩니다.
다음과 같은 진술을 할 때 :
Dim A as Integer
DIm B as Integer
A = 3
B = A
다음을 수행했습니다.
각 변수의 값은 각 메모리 위치에 개별적으로 존재합니다.
B. 참조 유형은 다양한 크기가 될 수 있습니다. 따라서 "스택"에 저장할 수 없습니다 (스택이 고정 된 크기의 메모리 할당 모음임을 기억하십니까?). 이들은 "관리되는 힙"에 저장됩니다. 관리되는 힙의 각 항목에 대한 포인터 (또는 "참조")는 스택 (예 : 주소)에서 유지됩니다. 코드는 스택에서 이러한 포인터를 사용하여 관리되는 힙에 저장된 개체에 액세스합니다. 따라서 코드에서 참조 변수를 사용할 때 실제로 포인터 (또는 관리되는 힙의 메모리 위치에 대한 "주소")를 사용합니다.
문자열 Property Person.Name을 사용하여 clsPerson이라는 클래스를 만들었다 고 가정 해 보겠습니다.
이 경우 다음과 같은 진술을 할 때 :
Dim p1 As clsPerson
p1 = New clsPerson
p1.Name = "Jim Morrison"
Dim p2 As Person
p2 = p1
위의 경우 p1.Name 속성은 예상대로 "Jim Morrison"을 반환합니다. p2.Name 속성은 직관적으로 예상하는 것처럼 "Jim Morrison"도 반환합니다. 나는 p1과 p2가 모두 스택에서 별개의 주소를 나타낸다고 생각합니다. 그러나 이제 p2에 p1 값을 지정 했으므로 p1과 p2는 모두 관리되는 힙의 동일한 위치를 가리 킵니다.
이제 다음 상황을 고려하십시오.
Dim p1 As clsPerson
Dim p2 As clsPerson
p1 = New clsPerson
p1.Name = "Jim Morrison"
p2 = p1
p2.Name = "Janis Joplin"
이 상황에서 개체를 참조하는 스택의 포인터 p1을 사용하여 Managed Heap에 사람 클래스의 새 인스턴스 하나를 만들고 개체 인스턴스의 Name 속성에 "Jim Morrison"값을 다시 할당했습니다. 다음으로 스택에 또 다른 포인터 p2를 생성하고 p1이 참조하는 것과 동일한 주소를 관리되는 힙에 지정했습니다 (할당 p2 = p1을 만들 때).
여기에 트위스트가 있습니다. p2의 Name 속성에 "Janis Joplin"값을 할당하면 다음 코드를 실행 한 경우 p1과 p2가 모두 참조하는 개체의 Name 속성이 변경됩니다.
MsgBox(P1.Name)
'Will return "Janis Joplin"
MsgBox(p2.Name)
'will ALSO return "Janis Joplin"Because both variables (Pointers on the Stack) reference the SAME OBJECT in memory (an Address on the Managed Heap).
그게 말이 되었나요?
마지막. 이렇게하면 :
DIm p1 As New clsPerson
Dim p2 As New clsPerson
p1.Name = "Jim Morrison"
p2.Name = "Janis Joplin"
이제 두 개의 별개의 Person Object가 있습니다. 그러나이 작업을 다시 수행하는 순간 :
p2 = p1
이제 두 가지 모두 "Jim Morrison"을 가리 켰습니다. (나는 p2가 참조하는 힙의 객체에 무슨 일이 일어 났는지 정확히 모르겠습니다... 나는 그것이 이제 범위를 벗어났다고 생각합니다. 이것은 누군가가 나를 바로 잡을 수있는 영역 중 하나입니다...). -편집 : 나는 이것이 새로운 할당을 만들기 전에 p2 = Nothing OR p2 = New clsPerson을 설정하는 이유라고 믿습니다.
다시 한 번, 지금 이렇게하면 :
p2.Name = "Jimi Hendrix"
MsgBox(p1.Name)
MsgBox(p2.Name)
이제 두 msgBox 모두 "Jimi Hendrix"를 반환합니다.
이것은 약간 혼란 스러울 수 있으며, 마지막으로 한 번 말씀 드리겠습니다. 세부 사항 중 일부가 잘못되었을 수 있습니다.
행운을 빕니다. 저보다 더 잘 아는 다른 사람들이이 중 일부를 명확히하는 데 도움이되기를 바랍니다. . .
값 데이터 유형 및 참조 데이터 유형
1) 값 (데이터를 직접 포함)하지만 참조 (데이터를 나타냄)
2)에 값 (모든 변수는 그 자신의 복사본이 있음)하지만
의 기준 (기타 변수가 어떤 오브젝트를 참조 할 수있는 것보다)
3) 값 (작업 변수는 다른 변수에 영향을 줄 수 없음 )이지만 참조 (변수는 다른 변수에 영향을 줄 수 있음)
4) 값 유형 은 (int, bool, float)이지만 참조 유형 은 (array, class objects, string)
"값 유형을 기반으로하는 변수는 값을 직접 포함합니다. 하나의 값 유형 변수를 다른 값에 할당하면 포함 된 값이 복사됩니다. 이는 객체 자체가 아닌 객체에 대한 참조를 복사하는 참조 유형 변수의 할당과 다릅니다." Microsoft의 라이브러리에서.
이것은 심오한 방식으로 잘못되었을 수 있지만 간단하게하기 위해 :
값 유형은 일반적으로 "값으로"전달되는 값입니다 (따라서 복사). 참조 유형은 "참조에 의해"전달됩니다 (따라서 원래 값에 대한 포인터 제공). .NET ECMA 표준에서는 이러한 "사물"이 저장되는 위치를 보장하지 않습니다. 스택이없는 .NET 구현 또는 힙이없는 구현을 빌드 할 수 있습니다 (두 번째는 매우 복잡하지만 파이버와 많은 스택을 사용하여 가능할 것입니다).
구조체는 값 유형 (int, bool ...은 구조체이거나 적어도 다음과 같이 시뮬레이션 됨)이고 클래스는 참조 유형입니다.
값 형식은 System.ValueType의 자손입니다. 참조 유형은 System.Object의 자손입니다.
이제 .. 결국 값 유형, "참조 된 객체"및 참조 (C ++에서는 객체에 대한 포인터라고합니다. .NET에서는 불투명합니다. 우리는 그것이 무엇인지 모릅니다. 우리의 관점에서 볼 때 그들은 개체에 대한 "핸들"). 이러한 마지막 항목은 값 유형과 유사합니다 (복사본으로 전달됨). 따라서 객체는 객체 (참조 유형)와 이에 대한 0 개 이상의 참조 (값 유형과 유사 함)로 구성됩니다. 참조가 0 개이면 GC가이를 수집 할 것입니다.
일반적으로 (.NET의 "기본"구현에서) Value 유형은 스택 (로컬 필드 인 경우) 또는 힙 (클래스의 필드 인 경우, 반복기 함수의 변수 인 경우)에서 이동할 수 있습니다. 클로저에 의해 참조되는 변수 인 경우, 비동기 함수에서 변수 인 경우 (최신 비동기 CTP 사용) ...) 참조 된 값은 힙으로 만 이동할 수 있습니다. 참조는 값 유형과 동일한 규칙을 사용합니다.
반복기 함수, 비동기 함수 또는 클로저에 의해 참조되기 때문에 힙에있는 Value Type의 경우 컴파일 된 파일을 보면 컴파일러가 이러한 변수를 넣는 클래스를 생성했음을 알 수 있습니다. , 클래스는 함수를 호출 할 때 빌드됩니다.
이제 나는 긴 글을 쓰는 법을 모르고 내 인생에서 더 좋은 일을 할 수 있습니다. "정확한" "학업 적" "올바른"버전을 원하면 다음을 읽으십시오.
http://blogs.msdn.com/b/ericlippert/archive/2010/09/30/the-truth-about-value-types.aspx
찾고 있어요 15 분이에요! 압축 된 "사용 준비 완료"기사이기 때문에 msdn 버전보다 낫습니다.
참조 유형을 생각하는 가장 간단한 방법은 "객체 ID"로 간주하는 것입니다. 객체 ID로 할 수있는 유일한 작업은 하나를 생성하고, 하나를 복사하고, 하나의 유형을 조회하거나 조작하거나, 둘이 같은지 비교하는 것입니다. object-ID로 다른 작업을 수행하려는 시도는 해당 ID가 참조하는 개체로 표시된 작업을 수행하는 약어로 간주됩니다.
참조 유형 인 Car 유형의 두 변수 X와 Y가 있다고 가정합니다. Y는 "개체 ID # 19531"을 보유합니다. "X = Y"라고 말하면 X는 "개체 ID # 19531"을 유지합니다. X도 Y도 차를 가지고 있지 않습니다. "개체 ID # 19531"이라고도하는 자동차는 다른 곳에 저장됩니다. Y를 X에 복사했을 때 ID 번호 만 복사했습니다. 이제 X.Color = Colors.Blue라고 가정하겠습니다. 이러한 진술은 "개체 ID # 19531"을 찾아 파란색으로 칠하라는 지시로 간주됩니다. X와 Y가 이제 노란색 자동차가 아닌 파란색 자동차를 참조하더라도이 명령문은 실제로 X 또는 Y에 영향을주지 않습니다. 둘 다 여전히 동일한 자동차 인 "개체 ID # 19531"을 참조하기 때문입니다. 항상 그랬습니다.
변수 유형 및 참조 값은 적용하기 쉽고 도메인 모델에 잘 적용되어 개발 프로세스를 용이하게합니다.
"값 유형"의 양에 대한 오해를 제거하기 위해 플랫폼에서 이것이 어떻게 처리되는지에 대해 설명하겠습니다. NET, 특히 C # (CSharp)에서 APIS를 호출하고 값, 참조, 메서드 및 함수에서 매개 변수를 전송하고 이러한 값의 전달을 올바르게 처리하는 방법
이 기사 읽기 C #의 변수 유형 값 및 참조
v
값 유형 표현식 / 변수이고 r
참조 유형 표현식 / 변수 라고 가정 합니다.
x = v
update(v) //x will not change value. x stores the old value of v
x = r
update(r) //x now refers to the updated r. x only stored a link to r,
//and r can change but the link to it doesn't .
따라서 값 유형 변수는 실제 값 (5 또는 "h")을 저장합니다. 참조 유형 변수는 값이있는 은유 적 상자에 대한 링크 만 저장합니다.
C #에서 사용할 수있는 다양한 데이터 유형을 설명하기 전에 C #이 강력한 유형의 언어라는 점을 언급하는 것이 중요합니다. 이는 각 변수, 상수, 입력 매개 변수, 반환 유형 및 일반적으로 값으로 평가되는 모든 표현식에 유형이 있음을 의미합니다.
각 유형에는 메모리를 할당하고 회수 할 때 유형 안전성을 보장하기 위해 CLR (공용 언어 런타임)에서 사용할 메타 데이터로 컴파일러가 실행 파일에 포함 할 정보가 포함되어 있습니다.
특정 유형이 할당하는 메모리 양을 알고 싶다면 다음과 같이 sizeof 연산자를 사용할 수 있습니다.
static void Main()
{
var size = sizeof(int);
Console.WriteLine($"int size:{size}");
size = sizeof(bool);
Console.WriteLine($"bool size:{size}");
size = sizeof(double);
Console.WriteLine($"double size:{size}");
size = sizeof(char);
Console.WriteLine($"char size:{size}");
}
출력은 각 변수에 의해 할당 된 바이트 수를 표시합니다.
int size:4
bool size:1
double size:8
char size:2
각 유형과 관련된 정보는 다음과 같습니다.
유형에 포함 된 구성원 (메서드, 필드, 이벤트 등)입니다. 예를 들어 int 유형의 정의를 확인하면 다음과 같은 구조체와 멤버를 찾을 수 있습니다.
namespace System
{
[ComVisible(true)]
public struct Int32 : IComparable, IFormattable, IConvertible, IComparable<Int32>, IEquatable<Int32>
{
public const Int32 MaxValue = 2147483647;
public const Int32 MinValue = -2147483648;
public static Int32 Parse(string s, NumberStyles style, IFormatProvider provider);
...
}
}
메모리 관리 운영 체제에서 여러 프로세스가 실행 중이고 RAM의 양이 모든 것을 수용하기에 충분하지 않은 경우 운영 체제는 하드 디스크의 일부를 RAM과 매핑하고 데이터를 하드 디스크에 저장하기 시작합니다. 운영 체제는 요청을 수행하기 위해 가상 주소가 해당 물리적 주소에 매핑되는 특정 테이블보다 사용합니다. 이러한 메모리 관리 기능을 가상 메모리라고합니다.
각 프로세스에서 사용 가능한 가상 메모리는 다음 6 개 섹션으로 구성되어 있지만이 항목의 관련성을 위해 스택과 힙에만 초점을 맞출 것입니다.
스택 스택은 LIFO (last in, first out) 데이터 구조이며 운영 체제에 따라 크기가 달라집니다 (기본적으로 ARM, x86 및 x64 시스템의 경우 Windows의 예약 1MB, Linux 예약에 따라 2MB에서 8MB까지) 버전).
이 메모리 섹션은 CPU에 의해 자동으로 관리됩니다. 함수가 새 변수를 선언 할 때마다 컴파일러는 스택에 크기만큼 큰 새 메모리 블록을 할당하고 함수가 종료되면 변수의 메모리 블록이 할당 해제됩니다.
힙 이 메모리 영역은 CPU에서 자동으로 관리되지 않으며 크기가 스택보다 큽니다. new 키워드가 호출되면 컴파일러는 요청 크기에 맞는 첫 번째 여유 메모리 블록을 찾기 시작합니다. 그리고 그것을 발견하면 내장 C 함수 malloc ()을 사용하여 예약 된 것으로 표시되고 해당 위치에 대한 포인터를 리턴합니다. 내장 된 C 함수 free ()를 사용하여 메모리 블록을 할당 해제 할 수도 있습니다. 이 메커니즘은 메모리 조각화를 일으키고 포인터를 사용하여 올바른 메모리 블록에 액세스해야합니다. 읽기 / 쓰기 작업을 수행하는 데 스택보다 느립니다.
사용자 지정 및 기본 제공 형식 C #은 정수, 부울, 텍스트 문자 등을 나타내는 표준 기본 제공 형식 집합을 제공하지만 struct, class, interface 및 enum과 같은 구문을 사용하여 고유 한 형식을 만들 수 있습니다.
struct 구문을 사용하는 사용자 정의 유형의 예는 다음과 같습니다.
struct Point
{
public int X;
public int Y;
};
값 및 참조 유형 C # 유형을 다음 범주로 분류 할 수 있습니다.
값 형식 값 형식은 System.ValueType 클래스에서 파생되며이 형식의 변수에는 스택의 메모리 할당 내에 해당 값이 포함됩니다. 값 유형의 두 가지 범주는 struct와 enum입니다.
다음 예제는 boolean 유형의 멤버를 보여줍니다. 보시다시피 System.ValueType 클래스에 대한 명시적인 참조가 없기 때문에이 클래스는 구조체에 의해 상속되기 때문에 발생합니다.
namespace System
{
[ComVisible(true)]
public struct Boolean : IComparable, IConvertible, IComparable<Boolean>, IEquatable<Boolean>
{
public static readonly string TrueString;
public static readonly string FalseString;
public static Boolean Parse(string value);
...
}
}
참조 유형 반면에 참조 유형에는 변수에 저장된 실제 데이터가 포함되지 않고 값이 저장된 힙의 메모리 주소가 포함됩니다. 참조 유형의 범주는 클래스, 대리자, 배열 및 인터페이스입니다.
런타임에 참조 유형 변수가 선언되면 new 키워드를 사용하여 생성 된 개체가 할당 될 때까지 null 값이 포함됩니다.
다음 예제는 일반 유형 List의 멤버를 보여줍니다.
namespace System.Collections.Generic
{
[DebuggerDisplay("Count = {Count}")]
[DebuggerTypeProxy(typeof(Generic.Mscorlib_CollectionDebugView<>))]
[DefaultMember("Item")]
public class List<T> : IList<T>, ICollection<T>, IEnumerable<T>, IEnumerable, IList, ICollection, IReadOnlyList<T>, IReadOnlyCollection<T>
{
...
public T this[int index] { get; set; }
public int Count { get; }
public int Capacity { get; set; }
public void Add(T item);
public void AddRange(IEnumerable<T> collection);
...
}
}
특정 개체의 메모리 주소를 찾으려는 경우 System.Runtime.InteropServices 클래스는 관리되지 않는 메모리에서 관리되는 개체에 액세스하는 방법을 제공합니다. 다음 예제에서는 정적 메서드 GCHandle.Alloc ()을 사용하여 핸들을 문자열에 할당 한 다음 AddrOfPinnedObject 메서드를 사용하여 주소를 검색합니다.
string s1 = "Hello World";
GCHandle gch = GCHandle.Alloc(s1, GCHandleType.Pinned);
IntPtr pObj = gch.AddrOfPinnedObject();
Console.WriteLine($"Memory address:{pObj.ToString()}");
출력은
Memory address:39723832
참조 공식 문서 : https://docs.microsoft.com/en-us/cpp/build/reference/stack-stack-allocations?view=vs-2019
표준에 의해 명시 적으로 명시된 값 유형과 참조 유형 간의 차이점에 대한 세부 사항이 많으며 그중 일부는 특히 초보자에게 이해하기 쉽지 않습니다.
ECMA 표준 33, 공용 언어 인프라 (CLI)를 참조하십시오 . CLI는 ISO에 의해 표준화되었습니다. 참조를 제공하지만 ECMA의 경우 PDF를 다운로드해야하며 해당 링크는 버전 번호에 따라 다릅니다. ISO 표준은 비용이 듭니다.
한 가지 차이점은 값 유형은 boxing 할 수 있지만 참조 유형은 일반적으로 할 수 없다는 것입니다. 예외가 있지만 상당히 기술적입니다.
값 유형은 매개 변수없는 인스턴스 생성자 또는 종료자를 가질 수 없으며 자신을 참조 할 수 없습니다. 자체 참조하면 값 유형이 있는지 것을 예 수단 노드 다음의 멤버 노드가 수없는 노드 . 사양에 다른 요구 사항 / 제한 사항이 있다고 생각하지만 그렇다면 한곳에 모이지 않습니다.