类和对象(中)

**[本节目标]

  1. 类的6个默认成员函数
  2. 构造函数
  3. 析构函数
  4. 拷贝构造函数
  5. 赋值操作符重载
  6. 默认拷贝构造与赋值运算符重载的问题
  7. const成员函数
  8. 取地址及const取地址操作符重载**
    1.类的六个默认构造函数
    任何一个类在我们不写的情况下,都会自动生成下面6个默认函数会:
    1>初始化和清理:构造函数和析构函数
    (构造函数主要完成初始化工作,析构函数主要完成清理工作)
    2>拷贝复制:拷贝构造和赋值重载
    (拷贝构造是使用同类对象初始化创建对象,赋值重载主要是把一个对象赋值给另一个对象)
    3>取地址重载(普通对象和const对象取地址)

2.构造函数
2.1概念:
构造函数是特殊的成员函数,名字与类名相同,创建类类型对象时由编译器自动调用,保证每个数据成员都有一个合适的值,并且在对象的生命周期只调用一次。
主要任务并不是开空间创建对象,而是初始化对象。

class Date
{
public:
//1.无参的构造函数
 Date()
 {
  _year = 1900;
  _month = 1l;
  _day = 1;
 }
 //2.带参的构造函数
 Date(int year,int month,int day)
 {
  _year = year;
  _month = month;
  _day = day;
  cout << "Data(int,int,int):" << this << endl;
 }
 //构造函数可以重载
 void SetDate(int year, int month, int day)
 {
  _year = year;
  _month = month;
  _day = day;
 }
 void Display()
 {
  cout << _year << "-" << _month << "-" << _day << endl;
 }
private:
 int _year;
 int _month;
 int _day;
};
void TestDate()
{
 Date d1; // 调用无参构造函数
 Date d2 (2015, 1, 1); // 调用带参的构造函数
  // 注意:如果通过无参构造函数创建对象时,对象后面不用跟括号,否则就成了函数声明
 Date d3(); 
 // 声明了d3函数,该函数无参,返回一个日期类型的对象
}

特征:
1.函数名与类名相同。
2.无返回值。(void 也不行)
3.对象实例化时比那一起自动调用对应的构造函数。在对象的生命周期只调用一次。
4.构造函数可以重载。
5.如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦用户显示定义编译器将不会生成。
6.无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个。
注意:无参的构造函数,全缺省的构造函数,我们没写的,编译器默认生成的构造函数都可以认为是默认成员函数。

//类中没有显示(用户没有显示提供构造)任何构造函数
//编译器将自动生成无参的构造函数
class Date
{
public:
 /*Date()
 {
  _year = 1900;
  _month = 1l;
  _day = 1;
 }*/
 Date(int year=1900, int month=1, int day=1)
 {
  _year = year;
  _month = month;
  _day = day;
  cout << "Data(int,int,int):" << this << endl;
 }
 //无参的构造函数和全缺省函数都是默认构造函数
 //默认构造函数只能有一个。
 int _year;
 int _month;
 int _day;
};

7.编译器生成的默认无参构造函数,对象里面的值依旧是随机值,那编译器生成的默认构造函数有什么作用?
在一个类中定义另一个类的对象,编译器生成的默认构造函数会对自定义类型成员_t调用它的默认成员函数。

class Time
{
public:
 Time(int hour=0, int minute=0,int second = 0)
 {
  cout << "Time(int,int,int)" << endl;
  _hour = hour;
  _minute = minute;
  _second = second;
 }
private:
 int _hour;
 int _minute;
 int _second;
};
class Date
{
public:
 void SetDate(int year, int month, int day)
 {
  _year = year;
  _month = month;
  _day = day;
 }
 void Display()
 {
  cout << _year << "-" << _month << "-" << _day << endl;
 }
private:
//基本类型
 int _year;
 int _month;
 int _day;
 //自定义类型
 Time _t;//编译器生成的默认构造函数会对自定义类型成员_t调用它的默认成员函数
};
int main()
{
 Date d1;
 system("pause");
 return 0;
}

2.2构造函数赋值和初始化

class Date
{
 //初始化列表作用:初始化类中成员变量,并且每个成员变量只能在初始化列表出现一次
public:
//初始化
 Date(int year, int month, int day)//初始化中没有this指针,没有对象
  :_year(year)
  , _month(month)
  , _day(day)
  , _a(10)
  , b(_year)
 {}
 //赋值
 //Date(int year, int month, int day)
 //{
 // _year = year;
 // _month = month;
 // _day = day;
 // //_a = 10;//错误
 //}
private:
 int _year;
 int _month;
 int _day;
 const int _a;
 int &b;
};

初始化注意:、
1.每个成员变量在初始列表中只能出现一次(初始化只能初始化一次)。
2.类中包含以下成员,必须放在初始化列表位置进行初始化:
引用成员变量
const成员变量
类类型成员(该类没有默认构造函数)
3.尽量使用初始化列表初始化。
4.成员变量在类中声明次序就是其在初始化列表中的初始化顺序,与其在初始化列表中的先后次序无关。(建议:尽量不要使用成员初始化成员,初始化列表中成员的出现次序最好与类中成员声明顺序一样。)

class Array
{
public:
 Array(int size)
  :_size(size)
  ,_array((int *)malloc(sizeof(int)*_size))
 {}
private:
 int *_array;
 int _size;
};

2.3explicit关键字
构造函数不仅可以构造与初始化对象,对于单个参数的构造函数,还有类型转换的作用。

class Date
{
public:
 explicit Date(int year)
  :_year(year)
 {}
  Date &operator=(const Date &d)
  {
   if (this != &d)
   {
    _year = d._year;
    _month = d._month;
    _day = d._day;
   }
   return *this;
  }
private:
 int _year;
 int _day;
 int _month;
};
int main()
{
 Date d1(1999);
 //用一个整形变量给日期类型对象赋值
 //实际编译器背后会用2000构造一个无名对象,最后用无名对象给d1对象赋值
 d1 = 2000;//调赋值运算符的重载:形参必须是两个类类型
 //把2000隐式转换Date类//先调了构造函数,然后进行复制
 //用2000创造了临时变量然后进行赋值
 return 0;
}
#endif

上述代码可读性不好,用explicit修饰单参构造函数,将会禁止单参构造函数的隐式转换。

3.析构函数
概念:与构造函数功能相反,析构函数不是完成对象的销毁,局部对象销毁工作是由编译器完成的。而对象在销毁时会自动调用析构函数,完成类的一些清理工作。

#include<assert.h>
#include<malloc.h>

typedef int DataType;
class SeqList
{
public:
 SeqList(size_t capacity = 10)
 {
  cout << "SeqList(size_t)" << endl;
  _array = (DataType *)malloc(capacity*sizeof(DataType));
  if (nullptr == _array)
  {
   assert(0);
      return;
     }
     
  _size = 0;
  _capacity = capacity;
 }
 
 ~SeqList()//无返回值无参数--不能重载
 {
  if (_array)
  {
   cout << "~SeqList()" << endl;
   free(_array);
   _array = nullptr;
   _capacity = 0;
   _size = 0;
  }
 }
private:
 DataType* _array;
 size_t _size;
 size_t _capacity;
};

void TestSeqList()
{
 SeqList s;
}
int main()
{
 TestSeqList();
 //_CrtDumpMemoryLeaks();//检测内存泄漏
 return 0;
}

特性:
1.析构函数是在类名前加~。
2.无参数无返回值类型。(无重载)
3.一个类有且只有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。
4.对象生命周期结束时,C++编译系统自动调用析构函数。
5.编译器默认生成析构函数,对会自定义类型成员调用它的析构函数。

class String
{
public:
 String(const char*str = "")
 {
  if (nullptr == str)
   str = "";
  _str = (char *)malloc(strlen(str) + 1);
  strcpy(_str, str);
 }
 
 ~String()
 {
  if (_str)
  {
   free(_str);
  }
 }
 
private:
 char *_str;
};
class Person
{
private:
 String _name;
 String _gender;
 int _age;
};
void TestPerson()
{
 Person p;
}
int main()
{
 TestPerson();
 return 0;
}

4.拷贝构造函数
概念:只有单个形参,该形参是对类类型对象的引用(一般常用const修饰),在用以存在的类类型对象创建新的对象时由编译器自动调用。

class Date
{
public:
 Date(int year = 1900, int month = 1, int day = 1)
 {
 _year = year;
 _month = month;
 _day = day;
 }
 
Date(const Date& d)
 {
 _year = d._year;
 _month = d._month;
 _day = d._day;
 }
private:
 int _year;
 int _month;
 int _day;
};

int main()
{
 Date d1;
 Date d2(d1);
return 0;
}

特性:
1.拷贝构造函数是构造函数的一个重载形式。
2.拷贝构造函数的参数只有一个且必须使用引用传参,使用传值方式会引发无穷递归调用。(传值会一直引发对象的拷贝,层层传值会引发对象的拷贝的递归调用)
3.若为显示定义,系统生成默认的拷贝构造函数。
默认的拷贝构造函数对象按内存存储按字节完成拷贝,这种拷贝叫浅拷贝,或者值拷贝。

class Date
{
public:
 Date(int year = 1900, int month = 1, int day = 1)
 {
 _year = year;
 _month = month;
 _day = day;
 }
private:
 int _year;
 int _month;
 int _day;
};

int main()
{
 Date d1;
 // 这里d2调用的默认拷贝构造完成拷贝,d2和d1的值也是一样的。
 Date d2(d1);
 return 0;
}

4.那么编译器生成的默认拷贝构造函数已经可以完成字节序的值拷贝了,我们还需要自己实现吗?当然像日期类这样的类是没必要的。那么下面的类呢?

// 这里会发现下面的程序会崩溃掉?这里就需要我们以后讲的深拷贝去解决。
class String
{
public:
 String(const char* str = "jack")
 {
 _str = (char*)malloc(strlen(str) + 1);
 strcpy(_str, str);
 }
~String()
 {
 cout << "~String()" << endl;
 free(_str);
 }
private:
 char* _str;
};

int main()
{
 String s1("hello");
 String s2(s1);
}

导致后果:多个对象公用同一块空间,在销毁对象时,一份空间会释放多次而导致程序崩溃。
如果类中管理资源,该类的拷贝构造函数,必须由用户显示提供。(String类需要显示构造,Date类不需要)

5.运算符重载
为什么?自定义类型不能直接比较。
C++为了增强可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有返回值类型,函数名字及参数列表,其返回值类型与参数列表与普通函数类似。

函数名:关键字operator后面接需要重载的运算符符号
函数原型:返回值类型 operator操作符(参数列表)

注意:
1.不能通过连接其他符号来创建新的操作符:比如operator@
2.5个运算符不能重载:: : , .* , sizeof , ?: , , 。
3.用于内置类型的操作符,其含义不能改变。例如:内置类型+,不能改变其含义。
4.作为类成员的重载函数时,其形参看起来比操作数数数目少1。
成员函数的操作符有一个默认的形参this,限定为第一个形象。

class Date
{
public:
 Date(int year = 1900, int month = 1, int day = 1)
 {
  _year = year;
  _month = month;
  _day = day;
 }
 Date(const Date& d)
 {
  _year = d._year;
  _month = d._month;
  _day = d._day;
 }

 bool operator>(const Date&d)
 {
  if (_year > d._year ||
   _year == d._year&&_month > d._month ||
   _year == d._year&&_month == d._month&&_day > d._day)
  {
   return true;
  }
  else
  {
   return false;
  }
 }
 
 // bool operator==(Date* this, const Date& d2)
 // 这里需要注意的是,左操作数是this指向的调用函数的对象
 bool operator==(const Date&d)
 {
  return _year == d._year&&_month == d._month
   &&_day == _day;
 }
 bool operator!=(const Date&d)
 {
  return !(*this == d);
 }
  //赋值运算符
Date& operator=(const Date&d)
 {
  if (this != &d)//检测是否自己给自己赋值
  {
   _year = d._year;
   _month = d._month;
   _day = d._day;
  }
  return *this;//返回*this
 }

 //前置++
 Date&  operator++()
 {
  _day += 1;
  return *this;
 }
 //后置++
 Date  operator++(int)
 {
  Date tmp(*this);
  _day += 1;
  return tmp;//旧值在栈上//引用不能反悔栈上的值
 }
 //前置--
 Date& operator--()
 {
  _day -= 1;
  return *this;
 }
 //后置--
 Date operator--(int)
 {
  Date temp(*this);
  _day -= 1;
  return temp;
 }
bool IsGreater(const Date& d)
 {
  if (_year > d._year ||
   _year == d._year&&_month > d._month ||
   _year == d._year&&_month == d._month&&_day > d._day)
  {
   return true;
  }
  else
  {
   return false;
  }
 }
private:
 int _year;
 int _month;
 int _day;
};
int main()
{
 int a = 1;
 int b = 2;
 int c = 3;
a = b = c;
 c++;
 ++b;
Date d1(2019, 9, 14);
 Date d2(2019, 9, 15);
 Date d3(2019, 9, 13);
 d2=d3++;
 d2.operator=(d3.operator++(5));//随便传一个参数

d2=++d3;
 d2.operator=(d3.operator++());
 d3 = d3;
 d2 = d1;
 d2.operator=(d1);

d3 = (d2 = d1);
 d3.operator=(d2.operator=(d1));//赋值运算符重载必须给返回值
 return 0;
}

运算符重载有4点注意:
1.参数类型(const)
2.返回值Date& (引用)
3.检测是否自己给自己赋值。
4.返回*this
5.一个类如果没有显示定义赋值运算符重载,编译器也会生成一个,完成对象按字节的值拷贝。

class Date
{
public:
 Date(int year = 1900, int month = 1, int day = 1)
 {
 _year = year;
 _month = month;
 _day = day;
 }
private:
 int _year;
 int _month;
 int _day;
};

int main()
{
 Date d1;
 Date d2(2018,10,2);
 // 这里d1调用的编译器生成operator=完成拷贝,d2和d1的值也是一样的。
 d1 = d2;
 return 0;
 }

7.const成员
概念:将const修饰的类成员函数称为const成员函数,const修饰类成员函数,实际修饰该成员函数隐含的this指针,表明在该成员函数中不能对类的任何成员进行修改。(mutable修饰的成员可以修改)

//被const修饰的成员函数,称作const类型的成员函数
class Date
{
public:
 Date(int year = 1900, int month = 1, int day = 1)
 {
  _year = year;
  _month = month;
  _day = day;
 }
//在const成员函数中,不能修改类的任何成员变量
 void PrintDate() const
 // void PrintDate(const Date* this) //编译器对const成员函数的修改
 {
  _day++;
   //const Date*const
   //this._month++;
  cout << _year << "-" << _month << "-" << _day << endl;
 }
private:
 int _year;
 int _month;
 mutable int _day;
};

注意:
1.const对象不能调用非const成员函数
2.非const对象可以调用const成员函数
//const类型的对象不能调用普通类型的成员变量
//原因:const类型对象中的成员不允许被修改
//在普通类型成员函数可以修改成员变量
//如果编译器允许const类型对象调用普通类型成员,在普通类型成员函数中可能会修改onst类型对象中的内容。
//操作不安全
3.const成员函数内不能调用其他的非const成员函数。
4非const成员函数内可以调用其他的const成员函数。

8.取地址及const取地址操作符
这两个默认函数一般不用重新定义,编译器默认生成。只有特殊情况,才需要重载,比如想让别人获取到指定内容。

class Date
{
public :
 Date* operator&()
 {
 return this ;
 }

const Date* operator&()const
 {
 return this ;
 }
private :
 int _year ; // 年
 int _month ; // 月
 int _day ; // 日
};

int main()
{
 Date d1;
 cout << &d1 << endl;
 const Date d2;
 cout << &d2 << endl;
 return 0;
}

猜你喜欢

转载自blog.csdn.net/unique_IT/article/details/100938908