别名的定义、传递、返回对象
&的功能:
(1)取地址符
(2)引用符
一、定义别名
定义变量的别名
如,int n ;
int &m =n; //m是n 的别名 ,可以用int类型的m来表示int类型的n。且m与n的地址也一样。故m 和 n 是同一个东西!
定义对象的别名
如:Human Mike;
Human &rMike = Mike; //rMike是Mike的别名
二、空引用
指针进行删除之后,需要将它赋值为空,引用却不需要这样做。假如该对象存放在栈中,那么在对象超出作用域时别名会和对象一起消失;假如存放在堆中,由于对中内存只能通过指针来访问,因此用不着别名,即使再定义一个盖子真的别名,那么将指针删除并赋空之后,该指针的别名中的地址也相应的赋空了。
三、按别名传递
传递数值包括:按值传递,按址传递,按别名传递
关于变量:
(1)按值传递
#include <iostream> using namespace std; void swap(int a,int b) { int t; t = a; a = b; b = t; } int main() { int x,y; x = 3; y = 5; swap(x,y); cout<<"x:"<<x<<endl; cout<<"y:"<<y<<endl; return 0; }
输出:
x:3
y:5
不能成功交换x y的值!因为在swap函数里面交换的是x ,y 的副本,不是x y
按值传递在想函数传递一个变量/对象时,
(2)按址传递
#include <iostream> using namespace std; void swap(int *a,int *b) { int t; t = *a; *a = *b; *b = t; } int main() { int x,y; x = 3; y = 5; swap(&x,&y); cout<<"x:"<<x<<endl; cout<<"y:"<<y<<endl;
return 0;
}
输出:
x:5
y:3
可以成功交换x y的值!
(3)按别名传递
#include <iostream> using namespace std; void swap(int &a,int &b) { int t; t = a; a = b; b = t; } int main() { int x,y; x = 3; y = 5; swap(x,y); cout<<"x:"<<x<<endl; cout<<"y:"<<y<<endl; return 0; }
输出:
x:5
y:3
可以成功交换x y的值!
关于对象:
#include <iostream> using namespace std; class A { A(){cout<<"执行构造函数创建一个对象\n";} A(A&){cout<<"执行复制函数创建该对象的副本\n";} ~A(){cout<<"执行析构函数删除该对象\n";} }; A fun(A one) { return one; // 3 fun函数又将接收到的副本返回了,由于返回方式也是按值返回,所以又要调用一个副本构造函数 } int main() { A a; // 1 创建一个对象,调用一次构造函数 fun(a); // 2 对象 a 按值传递到fun函数中,调用fun函数的副本,创建一个对象a的副本,然后将副本传递到 fun函数值中去 return 0; } /*********************** 输出: 执行构造函数创建一个对象 //1 执行复制函数创建该对象的副本 //2 执行复制函数创建该对象的副本 //3 执行析构函数删除该对象 执行析构函数删除该对象 执行析构函数删除该对象 */
按值传递在向函数传递一个对象时,会向传递变量那样建立一个该对象的拷贝,而从函数返回一个对象时,也要建立这个被返回的对象的一个拷贝!这就导致了,当对象所占内存空间很大的时候,在传递过程中每次都要复制一个,虽然当值返回给调用程序之后会删除该对象的复制品,也会浪费巨大的空间!!!
因此,可以将程序改成按址传递:
#include <iostream> using namespace std; class A { public: A(){cout<<"执行构造函数创建一个对象\n";} A(A&){cout<<"执行复制函数创建该对象的副本\n";} ~A(){cout<<"执行析构函数删除该对象\n";} }; A* fun(A *one) { return one; //第二次使用按址传递。返回的是地址 } int main() { A a; fun(&a); //第一次使用按址传递 return 0; } /************************** 输出: 执行构造函数创建一个对象 执行析构函数删除该对象 ****************************/
但是问题来了:使用了指针去指向对象,那不是指针也可以修改对象啦!!要是它不小心用于非法怎么办?我们来用const避免他:
#include <iostream> using namespace std; class A { A(){cout<<"执行构造函数创建一个对象\n";} A(A&){cout<<"执行复制函数创建该对象的副本\n";} ~A(){cout<<"执行析构函数删除该对象\n";} }; const *const A fun(const A *const one) //保证传递进来的数据不被修改,又保证了返回的数据不被修改 { return one; //第二次使用按址传递。返回的是地址 } int main() { A a; const A *const p = fun(&a); //第一次使用按址传递 const 需要匹配 fun函数的输入 return 0; }
或者用别名传递
#include <iostream> using namespace std; class A { A(){cout<<"执行构造函数创建一个对象\n";} A(A&){cout<<"执行复制函数创建该对象的副本\n";} ~A(){cout<<"执行析构函数删除该对象\n";} }; const *const A fun(const A & one) //保证传递进来的数据不被修改,又保证了返回的数据不被修改 { return one; //第二次使用按址传递。返回的是地址 } int main() { A a; const A & b = fun(a); //第一次使用按址传递 const 需要匹配 fun函数的输入 return 0; }
此方法将函数的返回值和接收参数都定义为const,就可以保证函数内不可修改原始值,同时避免利用返回值对原始值进行修改。
******************************************************
使用指针还是使用别名呢?
(1)指针可以为空,但引用不能为空,指针可以被赋值,但引用只可以被初始化,不可被复位另一个对象的别名。如果需要使一个变量记录不同对象的地址,必须用指针!
(2)在堆中创建一块内存区域,必须要用指针才能指向该块区域!当然我们也可以用引用来引用指向内存空间的指针(没必要!!)...
如: int * &a =new int;
*r = 3;
这样的写法容易出错!!当机器虚拟内存太小,无法创建新空间的情况下,那么new int会自动返回一个空指针。 因此会导致一个无用 的别名。而使用 '*' 读取一个无用的别名则会引起系统奔溃!!
--->解决办法是,不要将引用初始化为新建内存区域的别名,而是要将 a 初始化为指向该区域的指针的别名。前提是首先判断该指针不为空。更多的时候,一般不给指针创建别名。
******************************************************
四、按别名返回堆中对象
需要改变对象中的数据时:
#include <iostream> using namespace std; class A { public: A(int i){cout<<"执行构造函数创建一个对象\n";x=i;} A(A&){cout<<"执行复制函数创建该对象的副本\n";} ~A(){cout<<"执行析构函数删除该对象\n";} void set(int i){x=i;} int get(){return x;} private: int x; }; A& fun(A&a) //返回值是A对象的别名 { cout<<"跳转到fun()函数中!\n"; a.set(66); return a; //返回的是A的对象的别名(按别名返回) } int main() { A *p = new A(99); //堆中创建了一个追踪对象A(99),用p指向它 fun(*p); //将这个追踪对象传递进去 cout<< p->get()<<endl; delete p; //删除了追踪对象指针 p return 0; } /************************************************* 输出: 执行构造函数创建一个对象 跳转到fun()函数中! 66 执行析构函数删除该对象 ***********************************************/
解决了空引用问题.