C++的函数使用值传递,以指针或对象作为参数时会有很大的差异,本文通过例子介绍这些差异。
以对象方式使用
下面的代码将创建一个vector对象,并通过for循环添加8个Button对象,
vector<Button> bs1;
for (auto i = 0; i < 8; i++) {
Button button {i, i}; // 在栈上创建对象
bs1.push_back(button); // 复制对象
// 超出button的作用域,button被自动删除
}
由于使用值传递,在调用push_back
方法时,Button
的复制构造函数会被调用,所以添加到bs1
里的实际是button
被复制后的新对象,原来的button
对象在本次循环结束时被自动删除。
同时由于vector内部使用类似数组的实现,添加元素时可能会调整数组大小,在调整时会复制原来所有的元素,所以在上面的循环里,生成的对象数量数量会远大于我们预期的数量。
除了默认复制对象外,我们也可以使用move
函数强制移动对象,
// 移动对象,会调用移动构造函数
bs1.push_back(move(button));
或者将构造函数作为右值使用,避免循环时的复制,
bs1.push_back(Button{i, i});
但后面两种方式仍然无法避免vector调整内部数据大小时的对象复制。
以指针方式使用
指针指向对象的地址。使用指针时需要确保指针指向的对象仍然有效。例如下面的使用方式就是错误的:
vector<Button *> bs0;
for (auto i = 0; i < 8; i++) {
// 错误使用方式!
Button button {i};
bs0.push_back(&button);
}
在上面的代码里,添加到bs0
的是栈对象的地址,但这些对象会在for的每个循环结束时被删除,结果是bs0
里指针指向的都是垃圾数据。
正确的方式是通过new
方式生成的对象,此时这些对象会被分配到堆内存上,付出的代价是我们必须在使用结束时自行删除这些对象,否则会出现内存泄漏问题。
vector<Button *> bs2;
for (auto i = 0; i < 8; i++) {
Button *button = new Button(i);
// 这里只需要复制指针
bs2.push_back(button);
}
// 不使用bs2时,需要删除其中指针指向的对象
for (auto p: bs2){
delete p;
}
bs2.clear();
关于C++的引用
C++的引用相当于对象的别名,
int a = 8;
int &b = a;
b = 9;
// a值为9
需要注意引用的声明与初始化方式,&
出现在表达式左侧,与出现在右侧时(表示对象内存地址)表示不同含义。