如何将对象传递给 C++ 中的函数?
Posted
技术标签:
【中文标题】如何将对象传递给 C++ 中的函数?【英文标题】:How to pass objects to functions in C++? 【发布时间】:2011-01-09 12:00:50 【问题描述】:我是 C++ 编程新手,但我有 Java 方面的经验。我需要有关如何将对象传递给 C++ 中的函数的指导。
我是否需要传递指针、引用或非指针和非引用值?我记得在 Java 中没有这样的问题,因为我们只传递了持有对对象的引用的变量。
如果您还可以解释在哪里使用这些选项,那就太好了。
【问题讨论】:
你从哪本书学习 C++? 强烈不推荐这本书。阅读 Stan Lippman 的 C++ Primer。 嗯,有你的问题。 Schildt 基本上是 cr*p - 由 Koenig 和 Moo 获得 Accelerated C++。 我想知道为什么没有人提到 Bjarne Stroustrup 的 C++ 编程语言。 Bjarne Stroustrup 是 C++ 的创造者。一本非常好的学习 C++ 的书。 @George:TC++PL 不适合初学者,但它被认为是 C++.xD 的圣经 【参考方案1】:C++11 的经验法则:
按值传递,除非
-
您不需要对象的所有权,只需一个简单的别名即可,在这种情况下,您通过
const
参考,
您必须改变对象,在这种情况下,请使用通过非const
左值引用传递,
您将派生类的对象作为基类传递,在这种情况下,您需要通过引用传递。 (使用前面的规则来判断是否通过const
引用。)
实际上从不建议通过指针传递。可选参数最好表示为 std::optional
(boost::optional
用于较旧的标准库),并且通过引用可以很好地完成别名。
C++11 的移动语义使得按值传递和返回更有吸引力,即使对于复杂对象也是如此。
C++03 的经验法则:
通过const
参考传递参数,除非
-
它们将在函数内部进行更改,并且此类更改应反映在外部,在这种情况下,您通过非
const
引用
该函数应该可以不带任何参数调用,在这种情况下你通过指针传递,以便用户可以传递NULL
/0
/nullptr
代替;应用前面的规则来确定您是否应该通过指向const
参数的指针传递
它们是内置类型,可以复制传递
它们将在函数内部进行更改,并且此类更改不应反映在外部,在这种情况下您可以通过副本传递 strong>(另一种方法是根据前面的规则传递并在函数内部进行复制)
(这里,“按值传递”称为“按副本传递”,因为在 C++03 中按值传递总是会创建副本)
还有更多内容,但是这几条初学者的规则会让你走得更远。
【讨论】:
+1 - 我还要注意一些(即 Google)认为将在函数内更改的对象应该通过指针而不是非常量引用传递。推理是当一个对象的地址被传递给一个函数时,更明显的是该函数可能会改变它。示例:使用引用,调用是 foo(bar);引用是否为 const,指针为 foo(&bar);并且更明显的是 foo 被传递了一个可变对象。 @RC 仍然不会告诉您指针是否为 const。 Google 的指导方针在各种 C++ 在线社区中引起了很多批评——恕我直言,这是有道理的。 虽然在其他情况下 google 可能处于领先地位,但在 C++ 中,他们的风格指南并不是那么好。 @ArunSaha:作为纯粹的风格指南,Stroustrup 有一个为航空航天公司开发的guide。我浏览了谷歌指南,但出于几个原因不喜欢它。 Sutter & Alexandrescu C++ Coding Standards 是一本值得阅读的好书,您可以获得很多好的建议,但它并不是真正的风格指南。除了人类和常识,我不知道有任何 style 自动检查器。 @anon 但是,您确实可以保证,如果参数未通过指针传递,则不会更改。恕我直言,这非常有价值,否则在尝试跟踪函数中的变量发生了什么时,您必须检查传递给它的所有函数的头文件以确定它是否已更改。这样,您只需查看通过指针传递的那些。【参考方案2】:C++ 和 Java 中的调用约定存在一些差异。在 C++ 中,从技术上讲只有两种约定:按值传递和按引用传递,一些文献包括第三种按指针传递约定(实际上是指针类型的按值传递)。最重要的是,您可以将 const-ness 添加到参数的类型中,从而增强语义。
通过引用传递
通过引用传递意味着该函数将在概念上接收您的对象实例,而不是它的副本。引用在概念上是调用上下文中使用的对象的别名,不能为空。函数内部执行的所有操作都适用于函数外部的对象。此约定在 Java 或 C 中不可用。
按值传递(和按指针传递)
编译器将在调用上下文中生成对象的副本,并在函数内使用该副本。在函数内部执行的所有操作都是针对副本完成的,而不是外部元素。这是 Java 中原始类型的约定。
它的一个特殊版本是将指针(对象的地址)传递给函数。该函数接收指针,并且应用于指针本身的任何和所有操作都应用于副本(指针),另一方面,应用于取消引用指针的操作将应用于该内存位置的对象实例,因此该函数可能有副作用。使用指向对象的指针传递值的效果将允许内部函数修改外部值,就像传递引用一样,并且还允许可选值(传递空指针)。
这是 C 中函数需要修改外部变量时使用的约定,Java 中使用引用类型的约定:引用被复制,但被引用的对象是相同的:对引用/指针的更改是在函数外不可见,但指向内存的更改是可见的。
将 const 添加到方程中
在 C++ 中,您可以在不同级别定义变量、指针和引用时为对象分配常量。可以将变量声明为常量,可以声明对常量实例的引用,还可以定义所有指向常量对象的指针、指向可变对象的常量指针和指向常量元素的常量指针。相反,在 Java 中,您只能定义一个级别的常量(final 关键字):变量的级别(原始类型的实例,引用类型的引用),但您不能定义对不可变元素的引用(除非类本身是不可变)。
这在 C++ 调用约定中广泛使用。当对象很小时,您可以按值传递对象。编译器将生成一个副本,但该副本不是昂贵的操作。对于任何其他类型,如果函数不会改变对象,则可以传递对该类型的常量实例(通常称为常量引用)的引用。这不会复制对象,而是将其传递给函数。但同时编译器会保证对象在函数内部不会发生变化。
经验法则
这是一些需要遵循的基本规则:
首选基本类型的按值传递 更喜欢通过引用传递其他类型的常量 如果函数需要修改参数,请使用 pass-by-reference 如果参数是可选的,则使用指针传递(如果不应修改可选值,则传递给常量)这些规则还有其他一些小的偏差,首先是处理对象的所有权。当一个对象用 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 );
上面的交换函数通过使用引用改变它的两个参数。 Java中最接近的代码:
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 指针,它按值传递给函数。
【讨论】:
@david-rodriguez-dribeas 我喜欢经验法则部分,特别是“首选原始类型的值传递” 在我看来,这是对这个问题的更好回答。【参考方案3】:有几种情况需要考虑。
参数修改(“out”和“in/out”参数)
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 传递。但是,如果您无论如何都需要在您的函数中制作副本,请should pass by 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 纯粹是一个实现细节。 Try it out:
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
【讨论】:
+1 我不知道const
是按值传递时的实现。【参考方案4】:
将对象作为参数传递给函数的三种方法:
-
通过引用传递
按值传递
在参数中添加常量
通过以下示例:
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
【讨论】:
只有两种方法(你提到的前两种)。不知道“在参数中传递常量”是什么意思【参考方案5】:按值传递:
void func (vector v)
当函数需要与环境完全隔离时,按值传递变量,即防止函数修改原始变量以及防止其他线程在函数执行时修改其值。
缺点是复制对象所花费的 CPU 周期和额外内存。
通过 const 引用传递:
void func (const vector& v);
这种形式模拟了按值传递的行为,同时消除了复制开销。该函数获得对原始对象的读取权限,但不能修改其值。
缺点是线程安全:另一个线程对原始对象所做的任何更改都会在函数仍在执行时显示在函数内部。
通过非常量引用传递:
void func (vector& v)
当函数必须向变量写回一些值时使用它,最终将被调用者使用。
就像 const 参考案例一样,这不是线程安全的。
通过 const 指针传递:
void func (const vector* vp);
除了语法不同外,功能与通过 const-reference 传递相同,而且调用函数可以传递 NULL 指针以指示它没有要传递的有效数据。
不是线程安全的。
通过非常量指针传递:
void func (vector* vp);
类似于非常量引用。当函数不应该写回一个值时,调用者通常将变量设置为 NULL。在许多 glibc API 中都可以看到这种约定。示例:
void func (string* str, /* ... */)
if (str != NULL)
*str = some_value; // assign to *str only if it's non-null
就像所有通过引用/指针传递,不是线程安全的。
【讨论】:
【参考方案6】:由于没有人提到我正在添加它,当您将对象传递给 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 确实按值传递并且值是引用,因此它不需要复制构造函数。
【讨论】:
【参考方案7】:以下是将参数/参数传递给 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();
【讨论】:
“通过对象”不是一回事。只有值传递和引用传递。您的“案例 3”实际上显示了一种按值传递的情况和一种按引用传递的情况。【参考方案8】:我需要传递指针、引用还是非指针和非引用值?
这是一个在编写函数和选择参数类型时很重要的问题。该选择将影响函数的调用方式,这取决于几件事。
最简单的选择是按值传递对象。这基本上在函数中创建了对象的副本,这有很多优点。但有时复制成本很高,在这种情况下,经常引用const&
通常是最好的。有时你需要你的对象被函数改变。然后需要一个非常量引用&
。
有关参数类型选择的指导,请参阅the Functions section of the C++ Core Guidelines,以F.15 开头。作为一般规则,尽量避免使用原始指针,*
。
【讨论】:
以上是关于如何将对象传递给 C++ 中的函数?的主要内容,如果未能解决你的问题,请参考以下文章