C++_三种构造函数以及explicit和friend关键字

1.缺省构造函数(无参构造函数):缺省构造函数,初始化一个类之后,如果没用成员函数,编译器会运行一个没有参数的构造函数。如果自己不自定义,编译器会自动生成。

2.转换类型构造函数(单参构造函数):

转换构造函数(conversion constructor function) 的作用是将一个其他类型的数据转换成一个类的对象

  当一个构造函数只有一个参数,而且该参数又不是本类的const引用时,这种构造函数称为转换构造函数。

  转换构造函数是对构造函数的重载。在分析下面的例子代码前:讲解友元和explicit


1)友元:友元函数是可以直接访问类的私有成员的非成员函数。它是定义在类外的普通函数,它不属于任何类,但需要在类的定义中加以声明,声明时只需在友元的名称前加上关键字friend。
    友元是一种定义在类外部的普通函数,但它需要在类体内进行说明,为了与该类的成员函数加以区别,在说明时前面加以关键字friend。友元不是成员函数,但是它可以访问类中的私有成员。友元的作用在于提高程序的运行效率(即减少了类型检查和安全性检查等都需要的时间开销),但是,它破坏了类的封装性和隐藏性,使得非成员函数可以访问类的私有成员。
友元可以是一个函数,该函数被称为友元函数;友元也可以是一个类,该类被称为友元类。

2)关键字explicit:

可以阻止不应该允许的经过转换构造函数进行的隐式转换的发生。声明为explicit的构造函数不能在隐式转换中使用。


using namespace std;
//声明Point2d类
class Point2d;
class Point3d
{
private:
    int m_x;
    int m_y;
    int m_z;
public:
    //在函数前加explict表示要求该构造函数不能做隐式类型转换
    explicit Point3d(int x, int y = 0, int z = 0):m_x(x),m_y(y),m_z(z){}
    //支持自定义类型转换的构造函数
    Point3d(const Point2d& pt2);
    void print(void)
    {
        cout << '(' << m_x << ',' << m_y << ',' << m_z << ')' << endl;
    }
};
class Point2d
{
private:
    int m_x;
    int m_y;
public:
    //自定义有参构造函数
    Point2d(int x,int y):m_x(x),m_y(y){}
    //自定义函数打印成员变量
    void print(void)
    {
        cout << '(' << m_x << ',' << m_y << ')' << endl;
    }
    //设置Point3d为Point2d的友元类
    friend class Point3d;
};
//支持自定义类型转换的构造函数
Point3d::Point3d(const Point2d& pt2):m_x(pt2.m_x),m_y(pt2.m_y),m_z(300){}
int main(void)
{
    Point3d pt3(10,20,30);
    pt3.print(); //(10,20,30)
 
Point2d pt2(100,200);
    pt2.print(); //(100,200)以上都是调用各自的构造函数初始化
  cout << "---------------------" << endl;
    Point3d pt4 = pt2;
    pt4.print(); //(100,200,300)调用了类型转换的构造函数
 cout << "---------------------" << endl;
    //Point3d pt5 = 300; error //由于上面的Point3d构造函数前加了explict,所以这里不能做隐式转换
    Point3d pt5 = static_cast<Point3d>(300);
    pt5.print(); //(300,0,0)
    return 0;

3.拷贝构造函数

3.1拷贝构造函数概念

    拷贝构造函数就是指以拷贝/复制的方式来构造一个新对象的构造函数 ;

如:

如:
   class Student
   {
   private:
       string m_name;
   };
   int main(void)
   {
      Student s1; // 调用缺省/无参构造函数
      Student s2("张飞"); // 调用有参构造函数
      Student s3(s1); // 调用拷贝构造函数
      Student s4 = s1; // 调用拷贝构造函数;
      Student* ps = new Student(s1); //调用拷贝构造函数
      return 0;
   }

1.2 拷贝构造函数的语法形式
   class 类名
   {
       类名(const 类名& 对象引用)
       {
           拷贝构造函数体;
       }
   };


注意:

   (1)当类中没有自定义拷贝构造函数时,系统会自动提供一个缺省拷贝构造函数,而缺省方式的拷贝构造函数就是按字节复制一份和拷贝源一模一样的副本;

这种拷贝方法叫做位拷贝;位拷贝又称浅拷贝;自定义拷贝构造函数是一种良好的编程风格,它可以阻止编译器形成默认的拷贝构造函数,提高源码效率。

   (2)缺省方式的字节复制往往不能实现对象间的深拷贝,这时就需要自定义拷贝构造函数以获得真正意义上的副本;

String (String const& that):m_str(that.m_str){}   //编译器中的缺省构造函数,缺省拷贝构造会出现double free的段错误。

String(String const& that):m_str(strcpy(new char[strlen(that.m_str)+1],that.m_str)){}   //自定义的深拷贝

3.2深拷贝:

          在某些状况下,类内成员变量需要动态开辟堆内存,如果实行位拷贝,也就是把对象里的值完全复制给另一个对象,如A=B。这时,如果B中有一个成员变量指针已经申请了内存,那A中的那个成员变量也指向同一块内存。这就出现了问题:当B把内存释放了(如:析构),这时A内的指针就是野指针了,出现运行错误。

        也就是,将第二个对象的成员变量,复制到第一个对象中。如果有指针,指针的指向相同,都指向同一块内存区域,所以在第一个对象被释放时,第二个对象的内存对象的内存区域也被释放了。

//自定义拷贝构造函数的时机
#include <iostream>
#include <cstring>
using namespace std;


class Student
{
private:
    char* m_name;
    int m_age;
public:
    //实现有参的构造函数
    Student(const char* name,int age):m_name(NULL),m_age(age)
    {
        //申请一块动态内存给成员变量m_name
        m_name = new char[strlen(name)+1];
        strcpy(m_name,name);
        //m_age = age;
    }
    //自定义析构函数释放动态内存
    ~Student(void)
    {
        delete[] m_name;
        m_name = NULL;
    }
    //打印所有属性信息
    void show(void)
    {
        cout << "我是" << m_name << ",今年" << m_age << "岁了" << endl;
    }
    //自定义拷贝构造函数实现深拷贝
    Student(const Student& rs):m_name(NULL),m_age(rs.m_age)
    {
        m_name =new char[strlen(rs.m_name)+1];
        strcpy(m_name,rs.m_name);
    }
};


int main(void)
{
    Student s("张飞",30);
    s.show();


    Student s2(s); // 调用拷贝构造函数
    s2.show();
    return 0;
}

3.3拷贝赋值

拷贝赋值:类的缺省拷贝赋值和缺省的拷贝构造一样,是浅拷贝。
  为了得到深拷贝效果,需要自己定义一个支持深拷贝
  赋值的运算符函数。
String s1(“hello”);
String s2 ;
s2=s1 ;//相当于s2.operator=(s1);


String& operator=(String const& that)
{
//缺省拷贝赋值运算符函数----浅拷贝
m_str = that.m_str ;
return *this ;
}
void operator=(String const& that)
{
//自定义拷贝赋值运算符(菜鸟版不建议使用)
m_str = new char[strlen(that.m_str)+1] ;
strcpy(m_str , that.m_str) ;
}


String& operator=(String const& that)
{
//这个版本仍有一丝缺陷,分配新资源的new有可能失败
if(&that!=this)//为了防范出现自己赋值给自己的情况s1=s1
{
delete[] m_str ;//释放旧资源
m_str = new char[strlen(that.m_str)+1] ;//分配新资源
strcpy(m_str , that.m_str) ;//复制新内容
}
return *this ;//返回自引用
}


String& operator=(String const& that)
{
//这个版本把以上的缺陷弥补。先分配一个新资源,后释放旧资源
if(&that!=this)//为了防范出现自己赋值给自己的情况s1=s1
{
char* str = new char[strlen(that.m_str)+1] ;//分配新资源
delete[] m_str ;//释放旧资源
m_str = strcpy(str , that.m_str) ;//复制新内容
}
return *this ;//返回自引用
}


String& operator=(String const& that)
{
//这个版本是终极版
if(&that!=this)//为了防范出现自己赋值给自己的情况s1=s1
{
String str = that ;//利用了深拷贝构造和析构函数,减少了代码的冗余
swap(m_str ,str.m_str) ;//交换两个参数的值
}
return *this ;//返回自引用
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值