引用就是别名
定义引用时一定要同时对该引用进行初始化,如:
int a;
int &ra=a;
而不能写成:
int a;
int &ra;
ra=a;
按址传递
void swap(int *pa,int *pb); //swap函数声明
int a=3,b=4; //变量a和b
swap(&a,&b); //传递a和b
按别名传递
void swap(int &pa,int &pb); //swap函数声明
int a=3,b=4; //变量a和b
swap(a,b); //传递a和b
指针是间接引用,别名时直接引用
传递对象
按址传递可以修改原始变量的值,有雨按值传递的是原始变量的副本对象,因此它不会修改原始变量的值。
假如仅仅是传递变量的话,采用指针或者引用这种按址传递的优势不是很明显,但是假如是传递较大的对象的话,这种优势是比较明显的。这是因为,按值传递在向函数传递一个对象时,会像传递变量那样建立一个该对象的副本,而从函数返回一个对象时,也要建立这个被返回的对象的一个副本。假如对象的数据非常多,这种副本对象带来的内存开销是非常客观的。
按值传递:
#include <iostream>
using namespace std;
class A
{
public:
A();
A(A&);
~A();
};
A::A()
{
cout<<"执行构造函数创建一个对象\n";
}
A::A(A&)
{
cout<<"执行复制构造函数创建该对象的副本\n";
}
A::~A()
{
cout<<"执行析构函数删除对象\n";
}
A func(A one);
int main()
{
A a;
func(a);
return 0;
}
A func(A one)
{
return one;
}
按址传递
#include <iostream>
using namespace std;
class A
{
public:
A();
A(A&);
~A();
};
A::A()
{
cout<<"执行构造函数创建一个对象\n";
}
A::A(A&)
{
cout<<"执行复制构造函数创建该对象的副本\n";
}
A::~A()
{
cout<<"执行析构函数删除对象\n";
}
A *func(A *one);
int main()
{
A a;
func(&a);
return 0;
}
A *func(A *one)
{
return one;
}
利用const指针来传递对象
利用指针来传递对象,虽然可以避免调用复制构造函数和析构函数,但是由于它得到该对象的内存地址,可以随时修改数据对象,因此它实际上破坏了按值传递的保护机制。 用const指针来传递对象,这样就可以防止任何试图对该对象所进行的操作行为,并且保证返回一个不被修改的对象。
const A *const func(const A *const one)
{
return one;
}
指针常见的错误
引用是它所引用的对象的别名,假如这个对象不存在了,使用该对象就会出现随机错误
在哪里创建就在哪里释放
只要在堆中创建一块内存空间,就会返回一个指向该空间的指针,我们一定不要把该指针弄丢,假如指针丢失,那么它指向的空间就会成为一块不可访问的区域,也就是内存泄漏。假如我们将存储在堆中的对象初始化给一个引用,那么当该对象被删除的时候,这个引用会成为空引用,假如无意中使用了空引用,会令程序出错。
我们在main函数中创建一个堆中对象,然后按引用的方式传递到func()函数中,在函数中对该对象操作完毕后返回该对象,然后在main函数中释放该对象。这样就实现了在哪里创建就在哪里释放。
#include <iostream>
using namespace std;
class A
{
public:
A(int i){x=i;cout<<"执行构造函数创建一个对象\n";}
A(const A&a){a=a.x;cout<<"执行复制构造函数创建一个对象\n";}
int get(){return x;}
void set(int i){x=i;}
private:
int x;
};
A &func(A&a);
int main()
{
A *p=new A(32);
cout<<p->get();
func(*p);
cout<<p->get();
delete p;
return 0;
}
A &func(A&a)
{
a.set(66);
return a;
}
总结
在函数中按地址返回一个栈中对象时,由于该对象是从函数中创建的,函数返回后,该函数的栈中对象也被销毁了,因此不要对不存在的对象进行任何操作。
但当函数按值返回在该函数中创建的栈中对象时,会把该对象复制到执行调用该函数的作用域中。在本例中,在main函数中执行调用的该函数,所以有爱哦吧栈中对象复制到main函数的作用域中,复制对象的工作结束后,接着会调用析构函数销毁掉复制的新对象,但是由于引用会使临时变量寿命延长到与自身寿命相同,因此复制到mian函数中的对象直到引用的寿命结束,也就是main函数,结束时,才释放。但是指针没有这个特例,函数返回后,对象和它的副本都先后被析构了。不过,有雨副本的内存地址已经返回给指针,因此指针可以根据这个地址访问副本的数据,我们知道析构一个对象只是告诉表一切该对象所占用的内存不再为该对象锁独享,可以分配给其他对象或者变量,因此存储在该内存区域中的数据在没有被其他数据覆盖之前仍然存在,所以指针还可以访问到该数据。
假如函数按值返回一个堆中对象,由于指向堆中对象的指针超出函数的作用域而被自动释放了,同时返回的是该对象的副本,因此函数中创建的堆中对象便成了一个不可访问的对象,你无法找到它,因为指向它的指针被销毁了,这就造成了内存泄漏,另外,返回的副本是在栈中创建的,引用可以延缓它的析构到引用释放后,但是指针却没有这个特性,副本的内存地址返回给指针就会立即销毁自身。假如其他变量或者对象没有占用该副本的内存空间,那么该副本的数据将一直存在。
假如按地址返回一个堆中对象而由引用接收,那么就需要定义一个指针来存储引用的地址,这样删除指针就可以删除堆中对象。但是要注意,删除该指针所指向的对象后,该对象的引用就成了空引用。他们也可以用指针来代替引用,但是缴入我们无法确定指向堆中对象的指针是哪一个,那么就有可能该指针删除了两次,或者忘记删除指针。解决的方法是在哪里创建对象,就在哪里释放。比如说,在main函数中创建一个堆中对象,然后通过引用的方式传递到func()函数中去,在func()函数执行完对该对象的操作并返回该对象后,main函数再根据自己的需要对其进行释放。这样可避免混淆指向堆中对象的指针,同时按引用的方式传递堆中对象,不用生成该对象的副本。另外,由于引用可使临时变量寿命延长,因此还可避免指针容易导致的临时变量丢失的危险。