저는 C ++ 프로그래밍을 처음 사용하지만 Java에 경험이 있습니다. C ++에서 객체를 함수에 전달하는 방법에 대한 지침이 필요합니다.
포인터, 참조 또는 비 포인터 및 비 참조 값을 전달해야합니까? Java에서는 객체에 대한 참조를 보유하는 변수 만 전달하기 때문에 그러한 문제가 없음을 기억합니다.
각 옵션을 사용할 위치를 설명 할 수 있다면 좋을 것입니다.
저는 C ++ 프로그래밍을 처음 사용하지만 Java에 경험이 있습니다. C ++에서 객체를 함수에 전달하는 방법에 대한 지침이 필요합니다.
포인터, 참조 또는 비 포인터 및 비 참조 값을 전달해야합니까? Java에서는 객체에 대한 참조를 보유하는 변수 만 전달하기 때문에 그러한 문제가 없음을 기억합니다.
각 옵션을 사용할 위치를 설명 할 수 있다면 좋을 것입니다.
답변:
패스 값으로 , 경우를 제외하고
const
.const
참조로 전달을 사용 하십시오 .const
참조 로 전달할지 여부를 결정하십시오 .)포인터로 전달하는 것은 사실상 권장되지 않습니다. 선택적 매개 변수는 std::optional
( boost::optional
이전 std 라이브러리의 경우) 로 가장 잘 표현되며 앨리어싱은 참조로 잘 수행됩니다.
C ++ 11의 이동 의미론은 복잡한 객체에 대해서도 가치에 의한 전달 및 반환을 훨씬 더 매력적으로 만듭니다.
인수를 전달 하여 const
참조 , 경우를 제외하고
const
기준NULL
/ 0
/를 nullptr
대신 전달할 수 있습니다 . 인수 에 대한 포인터로 전달 해야하는지 여부를 판별하기 위해 이전 규칙을 적용하십시오.const
(여기서 "값으로 전달"은 "복사로 전달"이라고합니다. 값으로 전달하면 항상 C ++ 03에서 사본이 작성되므로)
이것에 더 많은 것이 있지만,이 소수의 초보자 규칙은 당신을 꽤 멀리 이끌어 줄 것입니다.
C ++ 및 Java의 호출 규칙에는 몇 가지 차이점이 있습니다. C ++에는 기술적으로 값으로 전달 및 참조로 전달의 두 가지 규칙 만 있습니다. 일부 문헌에서는 포인터로 세 번째 전달 규칙 (실제로 포인터 유형의 값으로 전달)을 포함합니다. 또한 인수의 유형에 constness를 추가하여 의미를 향상시킬 수 있습니다.
참조로 전달
참조로 전달한다는 것은 함수가 객체 인스턴스를 개념적으로 수신하지만 사본이 아니라는 것을 의미합니다. 참조는 개념적으로 호출 컨텍스트에서 사용 된 오브젝트의 별명이며 널이 될 수 없습니다. 함수 내부에서 수행 된 모든 작업은 함수 외부의 개체에 적용됩니다. 이 규칙은 Java 또는 C에서 사용할 수 없습니다.
값으로 전달 (및 포인터로 전달)
컴파일러는 호출 컨텍스트에서 객체의 복사본을 생성하고 해당 복사본을 함수 내에서 사용합니다. 함수 내부에서 수행되는 모든 작업은 외부 요소가 아닌 복사본에 수행됩니다. 이것은 Java의 기본 유형에 대한 규칙입니다.
특별한 버전은 포인터를 (객체의 주소) 함수에 전달합니다. 이 함수는 포인터를 수신하고 포인터 자체에 적용된 모든 작업은 복사 (포인터)에 적용되며, 역 참조 된 포인터에 적용된 작업은 해당 메모리 위치의 객체 인스턴스에 적용됩니다. 부작용이있을 수 있습니다. 객체에 대한 포인터의 값별 전달을 사용하면 내부 함수가 참조 별과 같이 외부 값을 수정하고 선택적 값을 허용 할 수 있습니다 (널 포인터 전달).
이것은 함수가 외부 변수를 수정해야 할 때 C에서 사용되는 규칙이며 Java에서 참조 유형으로 사용되는 규칙입니다. 참조는 복사되지만 참조 된 오브젝트는 동일합니다. 기능이지만 지정된 메모리로 변경됩니다.
방정식에 const 추가
C ++에서는 변수, 포인터 및 참조를 다른 수준으로 정의 할 때 객체에 상수를 할당 할 수 있습니다. 변수를 상수로 선언하고 상수 인스턴스에 대한 참조를 선언 할 수 있으며 상수 객체에 대한 모든 포인터, 가변 객체에 대한 상수 포인터 및 상수 요소에 대한 상수 포인터를 정의 할 수 있습니다. 반대로 Java에서는 한 수준의 상수 (최종 키워드) 만 정의 할 수 있습니다. 변수의 레벨 (기본 유형의 인스턴스, 참조 유형의 참조)은 변경할 수 없습니다 (클래스 자체가 아닌 한) 불변).
이것은 C ++ 호출 규칙에서 광범위하게 사용됩니다. 객체가 작 으면 값으로 객체를 전달할 수 있습니다. 컴파일러는 사본을 생성하지만 그 사본은 값 비싼 작업이 아닙니다. 다른 유형의 경우 함수가 객체를 변경하지 않으면 유형의 상수 인스턴스 (일반적으로 상수 참조)에 대한 참조를 전달할 수 있습니다. 이것은 객체를 복사하지 않지만 함수에 전달합니다. 그러나 동시에 컴파일러는 객체가 함수 내에서 변경되지 않도록 보장합니다.
엄지 손가락의 규칙
다음은 몇 가지 기본 규칙입니다.
이 규칙과 다른 작은 편차가 있는데, 첫 번째는 객체의 소유권을 처리하는 것입니다. 객체에 new를 동적으로 할당 할 때는 delete (또는 [] 버전)로 할당을 해제해야합니다. 객체의 파괴를 담당하는 객체 또는 기능은 리소스의 소유자로 간주됩니다. 동적으로 할당 된 객체가 코드 조각으로 생성되지만 소유권이 다른 요소로 전송되는 경우 일반적으로 포인터 별 의미 체계 또는 가능한 경우 스마트 포인터로 수행됩니다.
사이드 노트
C ++과 Java 참조의 차이점의 중요성을 주장하는 것이 중요합니다. C ++에서 참조는 개념적으로 개체에 대한 접근자가 아니라 개체의 인스턴스입니다. 가장 간단한 예는 스왑 기능을 구현하는 것입니다.
// C++
class Type; // defined somewhere before, with the appropriate operations
void swap( Type & a, Type & b ) {
Type tmp = a;
a = b;
b = tmp;
}
int main() {
Type a, b;
Type old_a = a, old_b = b;
swap( a, b );
assert( a == old_b );
assert( b == old_a );
}
위의 swap 함수 는 참조를 사용하여 두 인수를 모두 변경 합니다. 자바에서 가장 가까운 코드 :
public class C {
// ...
public static void swap( C a, C b ) {
C tmp = a;
a = b;
b = tmp;
}
public static void main( String args[] ) {
C a = new C();
C b = new C();
C old_a = a;
C old_b = b;
swap( a, b );
// a and b remain unchanged a==old_a, and b==old_b
}
}
코드의 Java 버전은 참조 사본을 내부적으로 수정하지만 실제 오브젝트를 외부 적으로 수정하지는 않습니다. Java 참조는 값으로 함수에 전달되는 포인터 산술이없는 C 포인터입니다.
고려해야 할 몇 가지 경우가 있습니다.
void modifies(T ¶m);
// vs
void modifies(T *param);
이 경우는 주로 스타일에 관한 것입니다. 코드를 call (obj) 또는 call (& obj) 처럼 보이게 하시겠습니까? 그러나 차이점이 중요한 두 가지 점이 있습니다. 아래의 선택적 경우와 연산자를 오버로드 할 때 참조를 사용하려고합니다.
void modifies(T *param=0); // default value optional, too
// vs
void modifies();
void modifies(T ¶m);
void uses(T const ¶m);
// vs
void uses(T param);
흥미로운 사례입니다. 경험상 "복사 가능"유형은 값으로 전달됩니다. 일반적으로 작은 유형이지만 항상 그렇지는 않습니다. 반면에 다른 유형은 const ref로 전달됩니다. 그러나 함수 내에서 사본을 복사해야 할 경우 value로 전달해야합니다 . (예, 이것은 약간의 구현 세부 사항을 노출시킵니다. C'est le C ++. )
void uses(T const *param=0); // default value optional, too
// vs
void uses();
void uses(T const ¶m); // or optional(T param)
여기에는 모든 상황에서 가장 작은 차이가 있으므로 인생을 가장 편하게 만드는 것을 선택하십시오.
void f(T);
void f(T const);
이 선언은 실제로 정확히 동일한 기능입니다! 값으로 전달할 때 const는 순전히 구현 세부 사항입니다. 사용해보십시오 :
void f(int);
void f(int const) { /* implements above function, not an overload */ }
typedef void NC(int); // typedefing function types
typedef void C(int const);
NC *nc = &f; // nc is a function pointer
C *c = nc; // C and NC are identical types
const
가치를 지날 때 구현되는 것에 대해 몰랐습니다 .
void func (vector v)
함수가 환경으로부터 완전히 분리되어야하는 경우, 즉 함수가 원래 변수를 수정하지 못하게하고 함수가 실행되는 동안 다른 스레드가 값을 수정하지 못하도록 값으로 변수를 전달하십시오.
단점은 CPU주기와 객체를 복사하는 데 사용 된 추가 메모리입니다.
void func (const vector& v);
이 양식은 복사 오버 헤드를 제거하면서 값별 동작을 에뮬레이트합니다. 이 함수는 원래 객체에 대한 읽기 권한을 갖지만 값을 수정할 수는 없습니다.
단점은 스레드 안전성입니다. 다른 스레드에 의해 원래 객체에 대한 모든 변경 사항은 여전히 실행되는 동안 함수 내에 표시됩니다.
void func (vector& v)
함수가 변수에 값을 다시 써야 할 때 이것을 사용하십시오. 이는 궁극적으로 호출자가 사용합니다.
const 참조 사례와 마찬가지로 스레드 안전하지 않습니다.
void func (const vector* vp);
다른 구문을 제외하고는 const-reference에 의한 전달과 기능적으로 동일하며, 호출하는 함수가 전달할 유효한 데이터가 없음을 표시하기 위해 NULL 포인터를 전달할 수 있다는 사실.
스레드 안전하지 않습니다.
void func (vector* vp);
비 const 참조와 유사합니다. 호출자는 일반적으로 함수가 값을 다시 쓰지 않을 때 변수를 NULL로 설정합니다. 이 규칙은 많은 glibc API에서 볼 수 있습니다. 예:
void func (string* str, /* ... */) {
if (str != NULL) {
*str = some_value; // assign to *str only if it's non-null
}
}
모든 스레드가 안전하지 않은 참조 / 포인터를 통과하는 것처럼.
아무도 언급하지 않았으므로 객체를 c ++의 함수에 전달할 때 객체의 복제본을 생성 한 다음 객체에 복제본이없는 경우 객체의 기본 복사 생성자가 호출됩니다. 원본 객체 대신 객체의 사본에 반영되는 객체 값을 변경하면 c ++의 문제입니다. 따라서 모든 클래스 속성을 포인터로 만들면 복사 생성자가 주소를 복사합니다. 포인터 속성, 포인터 속성 주소에 저장된 값을 조작하는 객체의 메소드 호출시 변경 사항도 매개 변수로 전달되는 원래 객체에 반영되므로 동일한 Java로 작동 할 수 있지만 모든 클래스를 잊지 마십시오. 속성은 포인터 여야하며 포인터 값을 변경해야합니다.코드 설명으로 훨씬 명확해질 것입니다.
Class CPlusPlusJavaFunctionality {
public:
CPlusPlusJavaFunctionality(){
attribute = new int;
*attribute = value;
}
void setValue(int value){
*attribute = value;
}
void getValue(){
return *attribute;
}
~ CPlusPlusJavaFuncitonality(){
delete(attribute);
}
private:
int *attribute;
}
void changeObjectAttribute(CPlusPlusJavaFunctionality obj, int value){
int* prt = obj.attribute;
*ptr = value;
}
int main(){
CPlusPlusJavaFunctionality obj;
obj.setValue(10);
cout<< obj.getValue(); //output: 10
changeObjectAttribute(obj, 15);
cout<< obj.getValue(); //output: 15
}
그러나 메모리 누수가 발생하고 소멸자를 호출하는 것을 잊지 않는 포인터와 관련된 많은 코드 작성을 끝내기 때문에 이것은 좋은 생각이 아닙니다. 그리고이 C ++에는 포인터를 포함하는 객체가 함수 인수에 전달되어 다른 객체 데이터 조작을 중지 할 때 새 메모리를 생성하는 복사 생성자가 있습니다 .Java는 값으로 전달하고 값은 참조이므로 복사 생성자가 필요하지 않습니다.
객체를 매개 변수로 함수에 전달하는 방법에는 세 가지가 있습니다.
다음 예를 살펴보십시오.
class Sample
{
public:
int *ptr;
int mVar;
Sample(int i)
{
mVar = 4;
ptr = new int(i);
}
~Sample()
{
delete ptr;
}
void PrintVal()
{
cout << "The value of the pointer is " << *ptr << endl
<< "The value of the variable is " << mVar;
}
};
void SomeFunc(Sample x)
{
cout << "Say i am in someFunc " << endl;
}
int main()
{
Sample s1= 10;
SomeFunc(s1);
s1.PrintVal();
char ch;
cin >> ch;
}
산출:
내가 someFunc
에 있다고 포인터
의 값 은 -17891602 입니다. 변수의 값은 4입니다.
다음은 C ++에서 작동하도록 인수 / 매개 변수를 전달하는 방법입니다.
1. 가치.
// passing parameters by value . . .
void foo(int x)
{
x = 6;
}
2. 참고로.
// passing parameters by reference . . .
void foo(const int &x) // x is a const reference
{
x = 6;
}
// passing parameters by const reference . . .
void foo(const int &x) // x is a const reference
{
x = 6; // compile error: a const reference cannot have its value changed!
}
3. 개체 별.
class abc
{
display()
{
cout<<"Class abc";
}
}
// pass object by value
void show(abc S)
{
cout<<S.display();
}
// pass object by reference
void show(abc& S)
{
cout<<S.display();
}