C++类与对象
空类:如果一个类中不存在任何成员,我们就称之为空类、
class Date{};```
但是空类不代表类中啥都没有,在一个类中,如果没有设置成员,会自动设置6个默认的成员函数
```c
1.初始化和清理
构造函数:完成初始化工作
析构函数:完成清理工作
2.拷贝复制
拷贝构造是使用同类对象初始化创建对象
赋值运算符重载:把一个对象赋值给另一个对象
3.取地址重载
主要是普通对象和const对象取地址
2.构造函数
1、概念
构造函数是特殊的成员函数,名字与类名相同,创建类类型对象时由编译器自动调用,并且保证每个数据成员都有合适的初始值,并在对象的生命周期内只调用一次。
2、特性
构造函数是特殊的成员函数,构造函数的名字虽然叫构造函数,但是并不代表他要开辟空间创建对象,而是初始化对象。
3、特性
1.函数名与类名相同
2.无返回值
3.对象实例化时自动调用对应的构造函数
4.构造函数可以重载
class Date
{
public:
//无参构造函数
Date()
{}
//带参构造函数
Date(int year,int month,int day)
{
_year = year;
_month = month;
_day = day;
}
//初始化列表
Date(int year,int month,int day):_year(year),
_month(month),
_day(day)
{
}
private:
int _year;
int _month;
int _day;
};
void TestDate()
{
Date d1; //调用无参构造函数
Date d2(2019,6,16); //调用带参构造函数
}
5.如果类中没有显式定义构造函数,C++编译器会自动生成一个无参的默认构造函数,一旦用户显式定义,编译器就不在生成
class Date
{
public:
//如果用户没有显式定义构造函数,编译器自动生成默认构造函数
Date();
{}
//无参构造函数和全缺省的构造函数不能同时存在
//全缺省的构造函数
Date(int year=2010,int month = 1,int day = 1)
{
_year = year;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
}
void TestDate()
{
Date d;
return 0;
}
6.无参构造函数和全缺省的构造函数都可以称为默认构造函数,且默认构造函数只有一个。注意:构造函数、全缺省构造函数、编译器生产的构造函数都可以认为是默认构造函数。
3.析构函数
- 概念
简单来说,一个对象是通过构造函数来的,通过析构函数没的
析构函数:与构造函数相反,析构函数不是完成对象的销毁,局部对象的销毁工作是由编译器完成的,而对象在销毁时会自动调用析构函数,完成类的一些资源清理工作。
- 特性
析构函数是特殊的成员函数
特征:
1.析构函数名是在类名前加上~
2.无参数无返回值且不能重载
3.一个类有且只有一个析构函数,若没有显式定义,系统会自动生成默认的析构函数
4.对象的生命周期结束时,C++编译系统会自动调用析构函数
#include <iostream>
#include <assert.h>
using namespace std;
typedef int DataType;
class SeqList
{
public:
SeqList(int capacity = 3)
{
cout << "SeqList(int):" << this << endl;
_array = (DataType*)malloc(sizeof(DataType)*capacity);
if (_array == nullptr)
{
assert(0);
return ;
}
_capacity = capacity;
_size = 0;
}
//析构函数没有参数
~SeqList()
{
cout << "~SeqList()" << this << endl;
if(_array)
{
free(_array);
_array = nullptr;
}
}
private:
DataType * _array;
size_t _size;
size_t _capacity;
};
void TestSeqList()
{
SeqList s;
}
int main()
{
TestSeqList();
system("pause");
return 0;
}
[外链图片转存失败(img-xJlWmsOB-1564716539323)(C:\Users\deorro\Desktop\111.png)]
4.拷贝构造函数
1.概念
拷贝构造函数:只有单个形参,该形参是对本类类型对象的引用(一般用const修饰),在用已经存在的类类型对象创建新对象时由编译器自动调用
2.特征
拷贝构造函数是特殊的成员函数
1、拷贝构造函数是构造函数的重载形式。
2、拷贝构造函数的参数只有一个,且必须用引用来传参,使用传值方式会导致无穷递归
#include <iostream>
using namespace std;
class Date
{
public:
Date(int year = 2010, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
Date(const Date& d)
{
this->_year = d._year;
this->_month = d._month;
this->_day = d._day;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1;
Date d2(d1);
return 0;
}
若拷贝构造函数采用传值的方式,会引发对对象的拷贝,然后层层传值引发对象拷贝的递归调用,最终导致代码崩溃
3.若没有显示定义拷贝构造函数,默认的拷贝构造函数对象按照内存存储,按字节序完成拷贝,这种拷贝也被称为浅拷贝
4.编译器生成的默认拷贝构造函数已经能够实现字节序的拷贝,但仅仅针对上述这种只需要传值的Date类,当类对象为string类时
#include <iostream>
#include <assert.h>
#include <malloc.h>
using namespace std;
class String
{
public:
String(const char* pstr = "")
{
_str = (char*)malloc(strlen(pstr) + 1);
if (_str == nullptr)
{
assert(0);
return;
}
strcpy(_str, pstr);
cout << "String()" << endl;
}
~String()
{
if(_str)
{
free(_str);
}
cout << "~String()" << endl;
}
private:
char* _str;
};
void TestString()
{
String s1("hello");
String s2(s1);
}
int main()
{
TestString();
return 0;
}
5.赋值运算符重载
1.运算符重载
C++为了增强代码的可读性,引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有返回值类型,函数名称以及参数列表,其返回值类型与参数列表与普通函数类似
函数名称:关键字operator后接需要重载的运算符符号
函数原型:返回值类型的 operator操作符
注意:
- 不能通过连接其他符号来创建新的操作符:operator@
- 重载操作符必须具有类类型或者枚举类型的操作数
- 用于内置类型的操作数,其含义不能改变,如内置的整形+,不能改变其含义
- 作为类成员的重载函数时,其形参看起来比操作数数目少一,操作符有默认的形参this,限定为第一个参数
- .*,::,sizeof,.,?:,这5个运算符不能重载
class Date
{
public:
Date(int year = 2010, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
//若将运算符重载成全局变量,需要将成员变量设置为public,就无法保证封装性
//这个问题可以用友元函数解决,也可以直接封装成成员函数
//bool operator==(const Date& d1,const Date& d2)
//{
//return d1._month==d2._month
// &&d1._year==d2._year
// &&d1._day==d1._day;
// }
//左操作数是this指着指向的调用函数对象
bool operator==(const Date& d2)
{
return this->_month == d2._month
&&(*this)._year == d2._year
&&(*this)._day == d2._day;
}
private:
int _year;
int _month;
int _day;
};
void test()
{
Date d1(2019,6,16);
Date d2(2019,6,16);
cout << (d1 == d2) << endl;
}
int main()
{
test();
system("pause");
return 0;
}
2.赋值运算符重载
特点
- 参数类型
- 返回值
- 检测是否是自己给自己赋值
- 返回*this
- 一个类如果没有显式定义赋值运算符重载,编译器会自动生成,完成对象按字节序的值拷贝
class Date
{
public:
Date(int year = 2010, 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;
}
Date& operator=(const Date& d)
{
if(*this != d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
return *this;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1(2019,6,16);
Date d2(2019,6,17);
Date d3(2019,6,18);
d1 = d2 = d3;
return 0;
}
这里我们要实现d3给d2赋值,再用d2给d1赋值,所以在赋值运算符重载时,d3给d2赋值的过程中需要返回的是d2,即当前的*this,为了保证地址的一致性,得到的是d2而不是它的一份临时拷贝,采用了返回引用,此处返回的值生命周期大于该赋值运算符重载函数,所以返回引用没有问题。
class String
{
public:
String(const char* pstr = "")
{
_str = (char*)malloc(strlen(pstr) + 1);
if (_str == nullptr)
{
assert(0);
return;
}
strcpy(_str,pstr);
cout << "String" << endl;
}
~String()
{
if (_str)
{
free(_str);
_str = nullptr;
}
cout << "~String()" << endl;
}
private:
char* _str;
};
int main()
{
String s1("hello");
String s2("world");
s1 = s2;
return 0;
}
这个程序会崩溃,因为同样是浅拷贝吗,却出现了资源多次释放的问题,赋值操作后,s1和s2指向了同一块内存空间,调用析构函数会导致同一块内存空间被释放多次,造成程序崩溃。
++运算符重载
前置++与后置++的区别在于先自增再运算还是先运算再自增,所以在前置++操作中,需要先进行+1操作再返回this指针,然而后置++需要创建临时变量temp来存放此时的this,再+1
class Date
{
public:
Date(int year = 2010, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
Date& operator++()
{
_day += 1;
return *this;
}
Date& operator++(int)
{
Date temp(*this);
_day += 1;
return temp;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1(2019, 6, 17);
Date d2;
d2 = ++d1;
d2 = d1++;
system("pause");
return 0;
}
6.const 成员
1.const修饰类的成员函数
通常将const修饰的类成员函数称为const成员函数,const修饰类成员函数,实际修饰该成员函数的this指针,表明在该成员函数中不能对类的任何成员函数进行修改。
void Display() const----->void Display(const Date* this)