十一、引用与拷贝函数(References & the Copy-Constructor)
11.1 指针回顾(Review of pointers)
-
指针可以保存一个地址。
-
当你定义一个指针时,必须指定它所指向变量的类型 ,并且应该初始化它。
示例:
c++int a = 0,b =0 ; int* ipa = &a; int* ipb = &b;
-
有了一个已经初始化的指针,最基本的操作是使用它修改它所指向的值。
-
C++允许将 任何类型的指针 赋值给一个
void*
类型的指针,但不允许一个void*
指针直接赋值给其他类型的指针。示例:
c++int i = 10; int* p = &i; void* vp = p;//OK //int* ip = vp;//error
11.2 引用(References)
概述
-
引用(
&
) 通常用于函数参数列表 和函数返回值 ,但你也可以创建一个独立存在的引用。 -
引用在创建时必须被初始化(指针可以先创建,然后再初始化)。
-
一旦引用被初始化为指向某个对象,它就不能被更改为去引用另一个对象。(指针可以重新指向另一个对象)
-
引用始终不能是NULL。必须始终能够假设引用连接到了一个合法的存储区域。
示例
c++int a = 3,b = 5; int& m=a; //ok, int& q;//error int& a = b;//error int& k = m; int n = m; int* p = &m; int* & ref = p; m = m + 5;
函数中的引用
-
引用作为函数参数:函数内部对引用的修改会直接影响外部实参。
-
返回引用时:必须保证返回的引用所指向的对象再函数外仍然存在(类似返回指针)。
错误示例:
c++int* f(){ int*p = 9; (*p) ++; return p;//error:因为p在函数结束时就会被销毁 } int& wrongFunction() { int x = 10; // x是局部变量 return x; // 错误!x在函数结束时被销毁 } int main() { int& r = wrongFunction(); // r指向无效内存 cout << r << endl; // 未定义行为 }
因为p,x都是函数内的局部变量,因此返回之后,函数内部的局部变量就被销毁了,那么p,x就会使无定义。
正确示例:
c++//C11:Reference.cpp #include <iostream> using namespace std; int* f(int* p){ (*p) ++; return p;//安全,因为p是这个函数之外的,不会随着函数结束而被销毁 } int& g(int& x){ x++; return x;//安全,因为x是这个函数之外的,不会随着函数结束而被销毁 } int&h (){ int q = 0; //return q;//错误 static int x = 1; return x;//安全,因为x的生命周期使整个程序 } void main() { int a = 0; f(&a); g(a); }
Const使用
-
使用
const
修饰引用参数,可以兼容更多调用情况:c++#include <iostream> using namespace std; void f(int& i){i++;} void g(const int& j){cout << j;} void main(){ int a = 1; //f(1);//error,int& i = 1; f(a);//ok int& i = a;i++; g(1);// ok,1 g(a); //ok,2 }
因为:
-
g
的参数是cosnt int& j
-
const int&
可以绑定到一个临时量(比如1)上 -
C++明确规定了:
只要是
const
引用,可以直接引用常量、临时对象,编译器会在内部自动生成一个临时对象来绑定。
const引用可以接住临时值 ,因为它承诺不会修改这个值,所以是安全的。
-
指针的引用
-
当我们想要修改指针本身,而不是指针指向的内容,可以使用指针引用(int*&)更清晰
c++//C11:ReferenceToPointer.cpp #include <iostream> using namespace std; void increment(int*& i){i++;} void main(){ int a = 2; int* p = &a; cout << "p = " << p << ":" << *p << endl; increment(p); cout << "p = " << p << ":" << *p << endl; }
输出:
c++p = 000000456B8FFA24:2 p = 000000456B8FFA28:-858993460
11.3 拷贝构造函数
c++
int i(5);
int j = i; //通过拷贝初始化
Date today(2002,10,10);
Date tomorrow = today;//通过拷贝初始化
想要像Date tomorrow = today
这样去初始化就需要拷贝函数。
概述
- 拷贝构造函数的目的:创建一个类对象时,可以用另一个已创建的同类对象的副本来初始化
- 如果用户已经声明了一个拷贝构造函数,那么就会使用它。如果没有,编译器会尝试生成一个公共的拷贝赋值函数。
- 默认的拷贝语义是成员逐一复制。
- 格式:
X::X(const X&)
示例:
c++
#include <iostream>
using namespace std;
class Date{
int year,month,day;
public:
Date(int y = 0,int m = 0,int d = 0)
{
year = y;month = m;day = d;
cout << "Constructor called." << endl;
}
Date(const Date& r){
year = r.year;month = r.month;day = r.day;
cout << "Copy construtor called." << endl;
}
~Date(){cout << "Destructor called." << endl;}
};
void main(){
Date d1(2003,9,20);
Date d2 = d1;//调用拷贝构造函数
}
输出:
c++
Constructor called.
Copy construtor called.
Destructor called.
Destructor called.
类的成员函数(包括构造函数、拷贝构造函数、析构函数、普通成员函数) , 可以访问自己类对象的 private 和 protected 成员 。这里虽然
r
是一个参数对象 ,但是r 也是 Date 类的对象 ,而Date
的成员函数是允许访问同类对象的私有成员 的。简单来说,一个类的成员函数可以随意访问自己类中的其他对象的私有数据。
什么时候需要拷贝构造函数
-
拷贝构造函数(Copy Constructor) 会在以下情况被调用:
-
当用一个已创建的同类对象去初始化一个新的类对象时。
c++Date d1(2003,9,20); Date d2 = d1;
-
当以值传递方式传递参数时。
-
当一个函数返回一个对象值时。
-
-
拷贝构造函数不会被调用的情况是:
- 当以引用传递方式传递参数时,因为没有创建新的对象。
示例:
tpoint.h
c++
//C11:tpoint.h
class TPoint{
public:
TPoint(int x,int y)
{X = x;Y = y;cout << "Constructor called." << X << endl;}
TPoint(const TPoint& p); //copy constructor
~TPoint(){cout << "Destructor called." << X << endl;}
int Xcoord() {return X;}
int Ycoord() {return Y;}
private:
int X,Y;
};
TPoint::TPoint(const TPoint& P){
X = P.X;
Y = P.X;
cout << "Copy Constructor called." << X << endl;
}
tpoint.cpp
c++
#include <iostream>
#include "tpoint.h"
using namespace std;
TPoint f(TPoint Q)//值传递参数,调用拷贝构造函数
{
cout << "OK!" << endl;
int x,y;
x = Q.Xcoord() + 10;
y = Q.Ycoord() + 20;
TPoint R(x,y);
return R;// 返回(值传递),调用拷贝构造函数
}
void main(){
TPoint M(20,35),P(0,0);
TPoint N = M; //调用拷贝构造函数
P = f(N);//调用拷贝构造函数
cout << "P = " << P.Xcoord() << "," << P.Ycoord() << endl;
}
输出:
c++
Constructor called.20 //M
Constructor called.0 //P
Copy Constructor called.20 //TPoint N = M
Copy Constructor called.20 //TPoint Q = M
OK!
Constructor called.30 // return R时拷贝的
Destructor called.20 //函数结束时 销毁这个TPoint Q = M
Destructor called.30 //函数退出时 销毁R
P = 30,40
Destructor called.20 //N.Destructor()
Destructor called.30 //P.Destructor()
Destructor called.20 //M.Destructor()
1.4 成员指针
-
指向数据成员的指针
c++//C11.PonterToMemberData.cpp #include <iostream> using namespace std; class Data { public: int a, b, c; void print() const { cout << a << "," << b << "," << c << endl; } }; void main() { Data d; Data* dp = &d; int Data::* pmInt = &Data::a; dp->*pmInt = 47; pmInt = &Data::b; d.*pmInt = 48; pmInt = &Data::c; dp->*pmInt = 49; dp->print(); }
输出:
c++47,48,49
-
执行成员函数的指针
c++//C11:PointerToMemberFunction.cpp #include <iostream> using namespace std; class Widget{ public: void f(int) const { cout << "Widget::f()" << endl; } void h(int) const { cout << "Widget::h()" << endl; } }; void main() { Widget w; Widget* wp = &w; void(Widget:: * pmem)(int) const = &Widget::h; (w.*pmem)(1); (wp->*pmem)(2); }
输出:
c++Widget::h() Widget::h()
函数指针:
int (*fp)(float)
:说明fp
指向一个参数为float
,返回值为int
的函数
11.5总结
- 指针和引用的基本回顾
const
引用与指针引用的使用- 拷贝构造函数的重要性与使用场景
- 如何使用指向数据成员和成员函数的指针