C++的简单总结(复制构造函数,深拷贝,前拷贝,默认属性)

类的三大属性:

private,public,protected


1,对于类的成员变量或者函数,缺省即为私有

#include <iostream>
using namespace std;

class A
{
        int y;                      //私有成员    
        int x;                      //私有成员
        public:
                A(int xx, int yy)     {x = xx; y = yy;}
                void setx(int m)      {x = m;}
                void sety(int n)      {y = n;}
};

int main()
{
        A a(1,2);                                                                                                                               
}

2,对于类的继承而言,缺省也是私有继承!!!!!


#include <iostream>                                                                                                                             
using namespace std;

class A
{
        int y;
        int x;
        public:
                A(int xx, int yy)     {x = xx; y = yy;}
                void setx(int m)      {x = m;}
                void sety(int n)      {y = n;}
};

class B:A
{
        int x;
        public:
        B(int xx, int yy,int xxx):A(xx,yy)
        {       x = xxx;             }
};

int main()
{
        B b(1,2,3);
}
如上,B也是同样的私有继承A了。。。。



3,内联函数

      内联函数是一种特殊的函数,本身比较少,运行起来也特别快,,由于函数调用会产生一定的调用语句,参数的

      传递,入栈出栈,那么当这个函数被反复调用的时候,就会产生很大的开销。于是就产生了内联函数。

      当我们的编译器看到了关键字:inline的时候,那么编译器会把函数的实现全部插入到调用内联函数出,这样就

      不会产生额外的调用开销了


      内联成员函数:

      1,inline + 成员函数

      2,整个函数体出现在定义类的内部


#include <iostream>
using namespace std;

class A
{
        int y;          
        int x;
        public:
                A(int xx, int yy)     {x = xx; y = yy;}
                void setx(int m)      {x = m;}                    //为内联函数,函数的定义在class中
                inline void sety(int n);                            //同样的为内联函数,通过inline声明
};

void A::sety(int n)
{
        y = n;  
}

int main()
{
        A a(1,2);                                                                                                                               
}

          内联函数,从代码层看,有函数的结构,而编译后却没有函数的性质,不是在调用时候发生控制转移,而是

          在编译的时候,将函数体嵌入到每一个调用之处。


3,成员函数的重载以及参数缺省

      我们知道函数是可以重载的,不同的返回值类型或者函数的个数是不相同的,那么就是函数的重载,同样:成员

      函数也是可以重载的。成员函数也是可以带有缺省的参数的。C++语法规定,函数在写的时候可以有缺省值,那么

      在调用的时候,如果我们没有给参数,那么参数就是缺省值。


#include <iostream>
using namespace std;

class A
{
        int y;          
        int x;
        public:
                void init(int x = 0, int y = 0);          //缺省参数
                void setx(int m)      {x = m;}           //下面这两个函数为setx函数的重载
                int  setx()     {return x;}
};
                                                                                                                                                
int main()
{
        A a(1,2);
}
       这里我们需要注意的就是使用:缺省参数时和函数重载时的二异行,,,,,

       如下:程序


#include <iostream>
using namespace std;

class A
{
        int y;          
        int x;
        public:
                void init(int xx = 0, int yy = 0);          //缺省参数
                void setx(int m =0)      {x = m;}
                int  setx()     {return x;}                                                                                                     
};

int main()
{
        A a(1,2);
        a.setx();
}
如上的setx函数,a调用setx函数,就不知道调用的是哪一个,就会产生编译出错,,,,:



5,构造函数

      一:成员函数的一种,名字与类名相同,可以有参数,不能有返回值(任何类型都不行,包括void)

      二:作用是对类对象进行初始化,如:给成员变量赋初值

      三:如果定义类的时候没有写构造函数,则编译器生成一个默认的无参构造函数(不做任何操作)

      四:如果我们定义了构造函数,那么编译器不会生成默认的无参构造函数

      五:对象生成时构造函数自动被调用(一定),对象一旦生成,就再也不能在其上执行构造函数

      六:一个类中可以有多个构造函数,构造函数的重载,那么如果要调用那个构造函数,需要看我们的对象是如何

             调用的


      那么为什么需要构造函数呢????

      构造函数执行必要的初始化工作,有了构造函数就不必在专门写必要的初始化函数了,也不用担心忘记了写

      初始化函数了,有时候对象没有被初始化就使用,那么就会导致编译错误了。。。。


      

#include <iostream>
using namespace std;

class A
{
        int y;          
        int x;
        public:
                void init(int xx = 0, int yy = 0);          //缺省参数
                void setx(int m =0)      {x = m;}
};
//编译器自动的调用默认构造函数
int main()
{
        A a;            //默认的构造函数被调用
        A *pc = new A;  //默认的构造函数被调用
        pc->init(1,2);                                                                                                                          
}

如上,只要有对象生成,就一定会调用构造函数


        构造函数的重载及其调用问题:

       

#include <iostream>
using namespace std;

class A
{
        double y;          
        double x;
        public:
                A(double xx, double yy)
                {    x = xx; y = yy;}
                A(double xxx)
                {    x = xxx;}
                void setx(int m =0)      {x = m;}
};
//构造函数的重载
int main()
{
        A a(2,3);           //调用的是第一个构造函数,整形被转化成了double类型
        A b(2);             //调用的是第二个构造函数,同样的被转化成了double                                                                    
}

            构造函数在数组(对象数组)中的使用:

#include <iostream>
using namespace std;

class A
{
        double x;
        public:
                A()
                {     cout<<"Construction 1"<<endl; }
                A(double xx)
                {     cout<<"Construction 2"<<endl; x = xx;}
                void setx(int m =0)      {x = m;}
};

int main()
{
        A a1[2];     //调用无参的构造函数
        cout<<"step1"<<endl;
        A a2[2] = {4,5};  //调用有参的构造函数初始化
        cout<<"step2"<<endl;    
        A a3[2] = {3};    //一个调用有参的构造函数,一个调用无参的构造函数
        A *p = new A[2];  //调用无参的构造函数                                                                                                  

        delete[] p;
}

运行结果:


           如上:这里还是有很多不同的,,,,,,生成一个对象就一定要调用构造函数


还有一种特殊的例子,我们来看一下:

#include <iostream>
using namespace std;

class A
{
        double x;
        double y;
        public:
                A()                                            //无参构造函数
                {     cout<<"Construction 1"<<endl; }
                A(double xxx)                        //一个参数构造函数
                {     cout<<"Construction 2"<<endl; x = xxx;}
                A(double xx, double yy)      //两个参数构造函数
                {     cout<<"Construction 3"<<endl; x = xx; y = yy;}
                void setx(int m =0)      {x = m;}
};

int main()
{
        A a[3] = {1, A(2,3)};           //定义了一个对象数组,第一个一个元素初始化,第二个两个元素,第三个没有初始化
        A *p[3] = {new A(3), new A, new A(4,5)};
 }


运行结果:


从上面可以看出,我们的对象数组是这个样子进行初始化的。。。。。。。。。。。并且也是这个样子调用new的。


其实总的来说:

对于对象数组的初始化:嗯嗯,好像只能用这种方式:

A a[3] = {A(1), A(2,3)};
A *p[3] = {new A(3), new A, new A(4,5)};

嗯嗯,就是,这种方式:上面我们实现的方式是不一样的,也就是说:当只有一个参数的时候

A(1)和1是一样的,,,,,,,,,,,

即就是:

A a = {1},A a(1),A a = {A(1)};      //三者是一样的!!!!!

6,复制构造函数(Copy  Constructor)

              我们经常使用函数,传递各种各样的参数,而我们今天要说的复制构造函数:就是把对象(注意是对象,不是

       对象的指针或者对象的引用)作为一个参数,并且传递给函数

              我们也知道,把参数传递给函数有三种方法:值传递,地址传递,传引用。

              值传递时,传送的是作为实际参数的对象的副本而不是实际对象本身。而且这个副本的内容是按位从原始参数

       那里复制过来的,内容是相同的。

       当传递过来的时候,如果是类的对象呢???会不会触发类的构造函数呢,,调用结束,会不会触发析构呢???

        从程序看:

#include <iostream>
using namespace std;

class A
{
        int x;
        public:
                A(int x1)
                {     x = x1;    cout<<"Constructor"<<endl; }       //构造函数的实现
                ~A()
                {     cout<<"Destructor"<<endl;             }       //析构函数的实现    
                void print()
                {     cout<<x<<endl;                        }
};

void f(A a)
{
        a.print();      
}

int main()
{
        A a(10);
        f(a);
        a.print();

        return 0;
}
运行结果:

从运行结果可以看到:A类的构造函数只被调用了一次,发生在创建对象a的时候,而A类的析构函数却被调用了两次

这是为什么呢???

        查阅书籍后发现:如同我们上面所提到的,把一个对象作为参数传递给函数时,同时创建了该对象的副本(这

        个副本将作为函数的参数)。也就是说:创建了一个新的对象。当函数结束时,作为函数的实际参数的副本将被

        销毁,这产生了两个有趣的问题。。。。。。。

        1,在创建对象的副本时有没有调用构造函数

        2,在销毁对象的副本时有没有调用了析构函数

        首先,在调用函数时,程序创建了一个对象的副本作为形式参数,此时普通的构造函数并没有被调用,而是调用

        了复制构造函数,复制构造函数定义了如何创建了一个对象的副本。。。。。如果一个类中没有显示的地定义类

        的复制构造函数,那么C++将提供一个默认的复制构造函数。默认的复制构造函数将以按位复制的形式创建一个

        对象的副本,即创建一个与原对象一样的副本。

        由于普通的构造函数通常用于初始化对象的某些成员,因此就不能调用普通的构造函数创建对象的副本,因为

        这样产生的对象可能与现有的对象的状态属性不完全相同。很简单,只是因为当把一个对象传递给函数时,需要

        使用的是对象的当前状态,而不是初始状态。。。。


        其次,当函数结束时,由于作为参数的对象副本超出了作用域,因此它将被销毁,从而调用了析构函数,这就是

        为什么前面的程序中调用了两次析构函数的原因,第一次函数f()参数超出了作用域,第二次是因为main函数

        中的对象a在程序结束时被销毁。。。。

        综上所述:当创建一个对象的副本作为函数参数时,普通的构造函数没有被调用,所调用的构造函数是按位复制

        的默认复制构造函数。但是,当对象的副本被销毁时(通常因为,函数返回而超出了作用域),析构函数被调用

        ,如果析构函数是空的话,通常不会发生什么问题,但是一般情况下:析构函数都要完成一些清理工作(释放

        内存。。。。)

        

        假如,我们真的在复制构造函数里面为一个指针变量分配了内存,在析构函数里释放了分配给这个指针所指向的

        空间,那么我们可以想想,在把对象传递给函数到整个main函数返回的时候,发生了什么???

       

        首先,有一个对象的副本产生了,这个副本也有一个指针,,,它和原始对象的指针指向同块内存空间,,函数

        返回时,副本对象的析构函数被执行了,,,释放了对象副本里指针所执行的内存空间,但是这个内存空间对于

        原始对象来说,还是有用的啊,,,,(干嘛就直接给释放了呢???),就程序本身而言,这是一个错误,

        但是错误还是没有停止,,,等到main函数结束的时候,原始对象的析构函数继续被执行,,,对同一块内存

        释放两次,这有可能加深错误!!!


        那么,如何解决上面的问题呢???

        嗯嗯,可以用传地址,传引用,这样却是可以合理的解决这样一个问题,但是,有的时候,我们不需要直接的改

        变外部变量啊,,,那如何处理呢????

        其实,这个时候,我们可以用复制构造函数来解决这个问题,复制构造函数是在产生对象副本的时候执行的,

        我们可以调用自己的复制构造函数。在自己的复制构造函数里,我们可以申请一个新的内存空间来保存那个

        构造函数所指向的内容。。。。这样,在执行对象副本的时候,执行的就是刚刚申请的那个空间了。。。


       复制构造函数(拷贝构造函数)

      

X&
const X&
volatile X&
const volatile X&
//后面也可以跟其他的参数。。。。。。。。。。。。。

       如上,就是复制构造函数的几种类型。。。。。。。。。

1, 当一个对象副本被作为参数传递给函数时              

display(y);

2, 当一个对象被另一个对象显式的初始化时

my_string x = y;

3, 当创建一个临时对象时(最常见的是:作为函数的返回值)

y = func2();

          前面2中情况,对象y的引用将被传递给复制构造函数;在第三种情况下,函数func2()返回的对象引用将被传递给

        y的复制构造函数。。。。

        关于对象之间的显示初始化。。。

#include <iostream>
using namespace std;

class A
{
        int x;
        public:
                A(int x1)
                {     x = x1;    cout<<"Constructor"<<endl; }       //构造函数的实现
                ~A()
                {     cout<<"Destructor"<<endl;             }       //析构函数的实现    
                void print()
                {     cout<<x<<endl;                        }
};

int main()
{
        A a(10);
        A b = a; //  可以这样初始化
        A c(a);   //  也可以这样初始化
        return 0;
} 
        运行结果:

         其实后面两个的初始化调用的是复制构造函数,只是在当前类中我们没写,调用的是默认,析构调用了 ,,,,


程序:

#include <iostream>                                                                                                                             
using namespace std;

class A
{
        int x;
        public:
                A(int x1)
                {     x = x1;    cout<<"Constructor"<<endl; }       //构造函数的实现
                A(A & a)                                            //自定义的复制构造函数
                {     x = a.x;   cout<<"自定义的 Constructor"<<endl;}
                ~A()
                {     cout<<"Destructor"<<endl;             }       //析构函数的实现    
                void print()
                {     cout<<x<<endl;                        }
};

int main()
{
        A a(10);
        A b = a;
        A c(a); 
        return 0; 
}
         程序如上:我们写了自定义的复制构造函数

运行结果:

为了明显的区分开跟赋值的区别,我们重载了一个赋值运算符:

#include <iostream>
using namespace std;

class A
{
        int x;
        public:
                A(int x1)
                {     x = x1;    cout<<"Constructor"<<endl; }       //构造函数的实现
                A(A & a)                                            //自定义的复制构造函数
                {     x = a.x;   cout<<"自定义的 Constructor"<<endl;}
                
                A &operator = (A & a)                               //重载了一个赋值运算符                                                      
                {     this->x = a.x;  cout<<"赋值运算符的重载"<<endl;}
                ~A()
                {     cout<<"Destructor"<<endl;             }       //析构函数的实现    
                void print()
                {     cout<<x<<endl;                        }
};

int main()
{
        A a(10);
        A b(2); 
        b = a;
        return 0;
}
运行结果:


           1,上面我们知道了一些关于复制构造函数里面的一些东西。。。。那么我们有一些疑惑???

               复制构造函数可以重载吗????

               可以的,因为上面我们提到了,复制构造函数的类型;;;;;;;;

              

#include <iostream>                                                                                                                             
using namespace std;

class A
{
        int x;
        public:
                A(int x1)
                {     x = x1;    cout<<"Constructor"<<endl; }       //构造函数的实现
                A(A & a)                                            //自定义的复制构造函数
                {     x = a.x;   cout<<"自定义的 Constructor"<<endl;}
                A(A & a, int b)
                {     a.x = b;   cout<<"重载的复制构造函数"<<endl;  }
                ~A()
                {     cout<<"Destructor"<<endl;             }       //析构函数的实现    
                void print()
                {     cout<<x<<endl;                        }
};

int main()
{ 
        A a(2); 
        A b(a, 3);
        return 0; 
}

          如上:我们重载了复制构造函数....

          运行结果:

          2,拷贝构造函数中可以调用private成员变量吗???

                从上面的程序中可以看到,这里的调用无压力,,,,可以直接调用,,,因为是一种特殊的构造函数

                操作的是自己的成员变量,不用考虑private。。。。。



          关于深拷贝,浅拷贝


          其实说白了:编译器默认的拷贝构造函数其实就是一个浅拷贝,如同我们上面所提到的那样,如果有指针变量的

话,那么就会出错,因为拷贝后两个指针指向了同一块空间,释放两次自然就会出错。。。。而深拷贝,不但对指针

进行拷贝,也对指向的内容进行拷贝。。。。。。。拷贝后,是两个指向不同地址的指针。。。。。。

1,那么浅拷贝会出现什么问题呢????

     

int main()
{
        A a(2);
        A b = a;   
//      A c(a);   上面这两种情况都是可以的                                                                                                      
        return 0; 
}

       假设类有一个成员变量指针:char *p;

       1,浅拷贝只是拷贝了指针,使得两个指针指向同一个地址,这样在对象块结束,调用函数析构的时,会造成同一

              份资源析构2次,即delete同一块内存2次,造成程序崩溃。

       2,浅拷贝使得b.p和a.p指向同一块内存,任何一方的变动都会影响到另一方

       3,结果是会调用两次析构函数,造成同一块内存释放两次,明显的错误


如下:

#include <string.h>
#include <iostream>
using namespace std;

class A
{
        char *p;
        public:
                A(char *s)             //构造函数
                {
                        cout<<"Constructor"<<endl;      
                        p = new char[strlen(s) + 1];
                        strcpy(p, s);
                }
                ~A()                   //析构函数
                {
                        if(p != NULL)
                        {
                                delete p;
                                p = NULL;       
                                cout<<"析构函数"<<endl; 
                        }
                }                                                                                                                               
};

int main()
{
        char p[] = "abc";
        A a(p);
        return 0;
}

运行结果:

一块空间的多次释放,自然而然的造成了错误。 ,,,,,,,,,,,,,,


深拷贝采用了在堆内存中申请新的空间来存储数据,在我们重新书写的复制构造函数里我们重新为这个新的指针

        分配了一块存储空间了,,,,,,,,,,


#include <string.h>
#include <iostream>
using namespace std;

class A
{
        char *p;
        public:
                A(char *s)             //构造函数
                {
                        cout<<"Constructor"<<endl;      
                        p = new char[strlen(s) + 1];
                        strcpy(p, s);
                }
                A(A & a)
                {
                        cout<<"复制Constructor"<<endl;
                        int length = strlen(a.p);
                        p = new char[length + 1];
                        strcpy(p, a.p); 
                }
                ~A()                   //析构函数
                {
                        if(p != NULL)
                        {
                                delete p;
                                p = NULL;       
                                cout<<"析构函数"<<endl; 
                        }
                }
};

int main()
{
        char p[] = "abc";
        A a(p);
        A b = a;
        return 0;
}

可以看到:我们重新写了复制构造函数,并且在其中重新分配了内存空间了。。。。


运行结果:


  • 3
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值