240 likes | 458 Views
面向对象程序设计与 C++. 第六章 引 用. 教 师: 王 涛 电 话 : 51688243 办公室 : 九教北 512 E-mail: twang@bjtu.edu.cn. 第六章 引用. 引用的定义与使用 引用参数 引用返回值 拷贝构造函数. 1 引用的定义与使用. 在同一个域中,两个对象不能有相同的名字;但通过定义引用,同一个对象可以有不同的名字 . 引用就是别名,与被引用的 “ 名字 ” 指向同一个对象。 定义引用的方法:数据类型 & 变量名; int a = 0; // 定义了一个整型变量 a
E N D
面向对象程序设计与C++ 第六章 引 用 教 师: 王 涛 电 话: 51688243 办公室: 九教北512 E-mail: twang@bjtu.edu.cn
第六章 引用 • 引用的定义与使用 • 引用参数 • 引用返回值 • 拷贝构造函数
1 引用的定义与使用 • 在同一个域中,两个对象不能有相同的名字;但通过定义引用,同一个对象可以有不同的名字. • 引用就是别名,与被引用的“名字”指向同一个对象。 • 定义引用的方法:数据类型 &变量名; int a = 0; // 定义了一个整型变量a int *p = &a; // 定义了一个整型指针,指向a int& b = a; // 定义了一个整型引用, // b与a表示同一个变量 注:p与a是不同的对象,他们有自己不同的值,只不过可以通过*p来访问a的内容;而b和a则表示同一个对象,他们的使用完全相同。
引用的定义与使用 main(){ int a = 0; int& b = a; // 引用 printf(“\n a = %d, b = %d”, a, b); a = 10; printf(“\n a = %d, b = %d”, a, b); b = 20; printf(“\n a = %d, b = %d”, a, b); } 输出结果:a = 0, b = 0 a = 10, b = 10 a = 20, b = 20
引用的定义与使用 • 引用的定义与使用需要注意: • 引用定义的时必须初始化(必须指向已存在的对象) • 引用定义后可以修改(可以修改指向别的对象) int i = 0, j = 0; int & ri; // error,必须初始化 int & ri = i; // ok ri = &j; // error, 类型不匹配 ri = j; // ok, 定义后可以修改 int *pi; // ok, 指针可以不初始化(但不安全) pi = &i; pi = &j; // ok, 指针可以修改指向别的对象
2 引用参数 void swap1(int a, int b){ int temp = a; a = b; b = temp; } void swap2(int& a, int& b){ int temp = a; a = b; b = temp; } main(){ int a = 10, b = 20; swap1(a, b); printf(“\n a = %d, b = %d”, a, b); swap2(a, b); printf(“\n a = %d, b = %d”, a, b); } 输出结果: a = 10, b = 20 a = 20, b = 10
swap1函数 void swap1(int a, int b){ int temp = a; a = b; b = temp;} swap1(a, b); a b b a 调用完swap1: …… 10 20 20 10 temp a b b a 执行完swap1: …… 10 20 10 20 10 b a swap1返回后: 20 10
swap2函数 void swap2(int& a, int& b){ int temp = a; a = b; b = temp;} swap2(a, b); a b b a 调用完swap2: …… 引用 引用 20 10 temp a b b a 执行完swap2: …… 10 引用 引用 10 20 b a swap2返回后: 10 20
引用参数 • 不同类型作为函数参数: • 普通对象类型作为函数参数时,创建了一个新的对象,并将传入参数的对象数据复制给新对象。(传入的参数形式是对象) • 指针类型作为参数时,没有创建新对象,只是创建了一个指针(4字节整数),并将传入的地址复制给该指针。(传入的参数形式是对象的地址) • 引用类型作为参数时,没有创建新对象,只是创建了一个引用(别名)指向传入的参数对象.(传入的参数形式是对象) • 示例程序:06_01
引用参数 • 在使用引用参数时,为防止传入的对象被改变,可声明为const类型. class point{ int x, y; } p1; void print(point& pt){ pt.x = pt.y = 10; // 编译通过,可以修改 printf(“\n x = %d, y = %d”, pt.x, pt.y); } void print(const point& pt){ pt.x = pt.y = 10; // 编译不通过,不能修改 printf(“\n x = %d, y = %d”, pt.x, pt.y); } p1.x = p1.y = 20; print(p1);
3 缺省拷贝构造函数与‘=’ • 每定义一个类时,系统会隐式为之重载缺省的拷贝构造函数及赋值操作符‘=’,进行对象复制工作. class A{}; 相当于: class A{ A(); // 缺省构造函数 ~A(); // 缺省析构函数 A(const A&); // 缺省拷贝构造函数 void operator = (const A&); // 缺省赋值操作符 }
缺省拷贝构造函数与‘=’ • 缺省的拷贝构造函数和‘=’操作符都进行了对象的数据复制工作. class point{ public: int x,y;}; point p1, p3; p1.x = p1.y = 20; point p2(p1); // 调用缺省拷贝构造函数 p3 = p1; // 调用缺省‘=’操作符 printf(“\n p2.x = %d, p2.y = %d”, p2.x,p2.y); printf(“\n p3.x = %d, p3.y = %d”, p3.x, p3.y); 输出结果: p2.x = 20, p2.y = 20 p3.x = 20, p3.y = 20
缺省拷贝构造函数与‘=’ • 可以认为缺省拷贝构造函数与‘=’操作符函数中进行了如下操作: class point{ //… }; point::point(const point& pt){ // 复制对象所有数据 memcpy( this, &pt, sizeof(point) ); } void point::operator = (const point& pt){ // 复制对象所有数据 memcpy( this, &pt, sizeof(point) ); }
拷贝构造函数与‘=’ • 只有在用‘=’进行对象赋值时,才会调用重载的‘=’操作符函数. • 在如下情况创建对象时,调用拷贝构造函数: • 用一个类对象显示地初始化另一个对 A newA(oldA); • 把一个类对象作为实参传递给函数 A f1(A a); …… f1(oldA); • 把一个类对象作为函数返回值传递 A f1(){ A a; ……. return a; } • 容器类(vector,list等)的一些操作
缺省拷贝构造函数和‘=’的缺陷 • 一般情况下,使用缺省拷贝构造函数和‘=’操作符能够满足应用要求。 • 但若class或struct中包含非静态指针成员时,使用缺省拷贝构造函数及‘=’操作符经常会导致错误. • 此时需要程序员显式重载拷贝构造函数和‘=’操作符,显示重载后,系统改调用显式重载的拷贝构造函数和‘=’操作符。
一次调用PrintMatrix: m.elems a.elems 0,0,… 返回,删除m(delete[] m.elems): a.elems 内存被释放 自此,a.elems为野指针! 二次调用PrintMatrix: m.elems a.elems 内存被释放 返回,删除m(delete[] m.elems): Delete不是该程序的内存,出错! 缺省拷贝构造函数和‘=’的缺陷 • 示例程序:06_02
一次调用PrintMatrix: m.elems a.elems 0,0,… 0,0,… 返回,删除m(delete[] m.elems): a.elems 0,0,… 二次调用PrintMatrix: m.elems a.elems 1,2,… 1,2,… 返回,删除m(delete[] m.elems): a.elems 1,2,… 显示重载 • 显式重载拷贝构造函数后:
4 引用返回值 • 除了可以将函数参数定义为引用类型外,还可以将引用类型作为函数返回值类型。 int max(int a, int b){ return (a>b)?a:b; } int& max(int& a, int& b){ return (a>b)?a:b; }
函数返回作了什么? • 普通非引用类型返回值的函数,系统要为每个返回值创建一个临时对象。 • 使用引用类型作为返回值,则系统不需为返回值创建新对象。 • 示例程序:06_03
左值问题 • 普通非引用类型的函数返回值不能作为左值, 而引用类型的函数返回值可以作为左值使用。 int max1(int a, int b){ return a>b?a:b; } int& max2(int& a, int&b){ return a>b?a:b; } int a = 10, b = 20; max1(a, b) = 0; // error C2106: '=' : left operand must be l-value max2(a, b) = 0; // ok. 之后b = 0
警惕: 返回局部变量的引用 • 函数返回局部变量的引用,编译不会出错,但容易导致程序逻辑错误。 int& max(int a, int b){ int value = a; // value局部变量 if( b > a) value = b; return value; // 函数返回后该变量已释放 } • 示例程序:06_04
返回堆内存的引用 • 函数也可以返回堆内存的引用,但在使用完毕之后需释放内存,避免内存泄漏。(建议不要使用) set& operator * (set& set1, set& set2){ set* res = new set; //… return *res; // 返回引用 } • 示例程序:06_05
5 引用 vs 指针 • 很多编译器中,引用是通过指针来实现的。 • 很多情况下,引用和指针可以互换: • 引用参数/指针参数 • 引用返回值/指针返回值 • 引用比指针相对安全:定义引用时必须初始化;定义指针时可以不初始化。 • 使用指针参数,一般需要检查传入参数是否为空;而引用参数不必担心。 • 指针比引用相对灵活:引用初始化之后不能更改;指针初始化后可以修改指向别的地址。 • 可以用指针来实现链表;引用不行。
引用使用小结 • 尽量使用引用参数代替指针参数和实参(除非参数传入的地址会改变指向另外的对象,此时用指针参数). • 尽量使用引用返回值,但要警惕返回栈内存。 • 如需避免引用对象的内容被修改,将引用申明为const类型。 • class或struct的成员变量也可申明为引用,但尽量不要如此申明. class A{ int& a; public: A(int value):a(value){} };