;
代码示例2引用作为返回值
1 double temp; //全局变量
2 double fsqr1(double a)
4 temp=a*a ; return temp;
7 double & fsqr2(double a)
9 temp=a*a ; return temp;
10 }
12 int main()
13 {
14 double x=fsqr1(5.5); //第一种情况
15 double y=fsqr2(5.5); //第二种情况
16 cout<<"x="<<x<<'\t‘<<"y="<<y<<endl;
17 return 0;
18 }
fsqr1先是将temp值赋值给内存中建立的无名临时变量,回到主函数后,赋值表达式x=fsqr1(5.5)把临时变量的值赋给x,无名临时变量的生命期结束。
fsqr2没有临时变量的过渡,而是直接返回temp本身赋值给y。不产生副本,效率提高了,但返回值不再是表达式。
返回值为引用的函数作为左值:
统计学生成绩,分数在80分以上的为A类,60分以上80分以下的为B类,60分以下的为C类
1 int& level(int grade ,int& typeA ,int& typeB ,int& typeC)
3 if(grade>=80) return typeA ;
4 else if(grade>=60) return typeB;
5 else return typeC;
8 void main( )
10 int typeA=0,typeB=0,typeC=0,student=9 ;
11 int array[9]={90 , 75 , 83 , 66 , 58 , 40 , 80 , 85 , 71} ;
12 for (int i=0 ; i<student ; i++)
13 level(array[i], typeA, typeB, typeC)++ ; //返回值为引用的函数作为左值
14 cout<<"A类学生数:"<<typeA<<endl ;
15 cout<<"B类学生数:"<<typeB<<endl ;
16 cout<<"C类学生数:"<<typeC<<endl ;
三、 复制构造函数(拷贝构造函数)-------------用于根据一个已存在的对象复制出一个新的该类对象,一般在函数中会将已经存在的对象的数据成员的值复制一份到新创建的对象中。
1.复制构造函数的引入:
同一个类的对象在内存中有完全相同的结构,如果作为一个整体进行复制是完全可能的。这个复制过程只需要复制数据成员,因为函数成员是共用的(在内存中只有一份代码)。在建立对象时可以用同一个类的另一个对象来初始化该对象,这时所用到的构造函数成为复制构造函数。
若没有显示的写复制构造函数,系统会默认创建一个复制构造函数但当类中有指针成员时,由系统创建的该复制构造函数就会存在风险。具体请看下边深复制与浅复制。
2.复制构造函数的参数必须采用引用
在C++中按值传递一个参数时,会在函数中重新分配一块内存建立与参数同类型的变量或对象,再把参数的数据成员赋值给新的变量或对象。在建立这个对象时,编译器就会自动为这个对象调用复制构造函数。如果其参数是真实的对象而不是引用,则又会引入新的一轮调用复制构造函数,出现了无穷递归。
3.复制构造函数被调用的时机:
当用一个类的对象去初始化该类的另一个对象(或引用)时系统自动调用拷贝构造函数拷贝赋值。
若函数的形参为类对象,调用函数时,实参赋值给形参,系统会自动调用复制构造函数
当函数的返回值是类对象时,系统自动调用复制构造函数
11 CGoods Car1("夏利2000",30,98000.0)
12 //调用三个参数的复制构造函数
13 CGoods Car2=Car1;//调用复制构造函数
14 CGoods Car3(Car1);
15 //调用复制构造函数,Car1为实参
16 这三个对象的初始化结果完全一样
示例代码:
1 class A
3 public: 5 A(const A& a){
6 data=a.data;
7 cout<<"拷贝构造函数调用\n";
9 A& operator=(const A&a){
10 data=a.data;
11 cout<<"调用赋值函数\n";
12 return *this;
13 }
15 int data;
16 };
18 void fun1(A a)
19 {
20 return ;
21 }
23 A fun2()
24 {
25 A a;
26 return a;
27 }
29 int _tmain(int argc, _TCHAR* argv[])
30 {
31 A a;
32 A b(a); //用类的一个对象a去初始化另一个对象b
33 A c=a; //用类的一个对象a去初始化另一个对象c,注意这里是初始化,不是赋值
34 fun1(a); //形参为类对象,实参初始化形参,调用拷贝构造函数。
35 A d=fun2(); //函数返回一个类对象时
36 d=a; //d已经初始化过了,这里是赋值,调用赋值函数
38 return 0;
39 }
40 //A c=a; d=a;前一个是初始化,后一个是赋值,两者不同
当对一个对象进行拷贝时,编译系统会自动调用一种构造函数--复制(拷贝)构造函数,如果用户未定义拷贝构造函数则会调用系统默认的拷贝构造函数。
在未自定义拷贝构造函数时,在复制对象时调用默认的拷贝构造函数时,进行的时浅拷贝。容易发生内存泄漏
深拷贝即调用的自定义的拷贝构造函数,不但对指针进行拷贝,还对指针指向的内容进行拷贝,拷贝后的指针是指向两个不同地址的指针。
1.对类对象进行复制的时候即把对象各数据成员的值原样复制到目标对象中时。当类中涉及到指针类型的数据成员的时候,往往会产生指针悬挂问题。看个简单地例子:
1 class A
3 public:
4 int *a;
6 A a1;
7 A b1=a1;
b1=a1;执行的是浅复制,此时b1.a与a1.a指向的是同一个内存地址,如果在析构函数里有对内存的释放就会出现内存访问异常。因为一块内存空间被释放了两次。
2.看一个例子:有一个学生类,数据成员为学生的人数和名字
1 #include <iostream>
2 using namespace std;
4 class Student
6 private:
8 int num;
9 char *name;
10 public:
11 Student();
12 ~Student();
13 };
15 Student::Student()
16 {
18 name=new char(20);
19 cout<<"Student"<<endl;
20 }
22 Studet::~Student()
23 {
24 cout<<"~Student"<<endl;
25 delete name;
26 name=NULL;
27 }
29 int main()
30 {
31 {//花括号让s1,s2变成局部对象,方便测试
32 Student s1;
33 Student s2(s1);//复制对象
34 sysem("ause");
35 return 0;
36 }
执行结果是:调用一次构造函数一次复制构造函数调用两次析构函数。两个对象的指针成员所指向的内存相同。name指针被分配一次内存,但是在程序结束时却被释放了两次,出现错误。
3.所以在对含有指针成员的对象进行拷贝时,必须要自定义拷贝构造函数使拷贝后的对象指针成员有自己的内存空间,即进行深拷贝,这样就避免了内存泄漏的发生。
下边是添加了自定义拷贝函数的例子:
1 #include <iostream>
2 using namespace std;
4 class Student
6 private:
7 int num;
8 char *name;
9 public:
10 Student();
11 ~Student();
12 Student(const Student &s);//拷贝构造函数,const防止对象被改变
13 };
15 Student::Student()
16 {
17 name = new char(20);
18 cout << "Student" << endl;
20 }
21 Student::~Student()
22 {
23 cout << "~Student " << (int)name << endl;
24 delete name;
25 name = NULL;
26 }
27 Student::Student(const Student &s)
28 {
29 name = new char(20);
30 memcpy(name, s.name, strlen(s.name));
31 cout << "copy Student" << endl;
32 }
34 int main()
35 {
36 {// 花括号让s1和s2变成局部对象,方便测试
37 Student s1;
38 Student s2(s1);// 复制对象
39 }
40 system("pause");
41 return 0;
执行过程:调用一次构造函数,一次自定义复制构造函数,两次析构函数。两个对象的指针成员所指内存不同
浅拷贝只是对指针的拷贝,拷贝后两个指针指向同一个内存空间。
深拷贝不仅对指针进行拷贝还对指针指向的内容进行拷贝,经过深拷贝后的指针是指向两个不同地址的指针。
要考虑自定义拷贝构造函数的情况有以下三种
- 复制含指针成员的对象时即用一个类的对象去初始化该类的另一个对象的时候
- 函数的形参为类对象时。调用函数时会有实参到形参的拷贝此时会调用拷贝构造函数
- 当函数的返回为类对象时。在函数和返回时会建立一个和类对象一样的临时类对象变量,并将返回的类对象赋值给新创建的类对象,此时会调用拷贝构造函数