什么是类?类是拥有相同属性和行为的集合
类中有六个默认的成员函数分别是:
- 构造函数
- 析构函数
- 拷贝构造函数
- 赋值操作符重载函数
- 取地址操作符重载函数
- const修饰的取地址操作符重载函数
接下来对前四个函数进行具体的分析
我们先定义一个空类:
class A
{
};
在经过编译器处理之后它就不在为空,编译器会自动加入一些默认的成员函数,即使在这些函数中什么也不做。编译器处理之后的类相当于:
class A
{
public:
A(); //构造函数
A(const A& a); //拷贝构造函数
~A(); //析构函数
A& operator =(const A& a); //赋值运算符重载
A* operator &(); //取址运算符重载
const A* operator &() const; //取址运算符重载
};
注意:这些函数在我们没有显式给出时编译器会为我们自动合成。
一、构造函数
1.什么是构造函数?
构造函数是一个特殊的成员函数,名字与类名相同,创建类类型对象时,由编译器自动调用,在对象的生命周期内只且只调用一次,以保证每个数据成员都有一个合适的初值。作用:初始化对象所占的内存空间。
stdent()
{
}
class Time
{
public:
//构造函数
Time(int hour = 0, int minute = 0, int second = 0)
:_hour(hour),_minute(minute),_second(second)//初始化列表
{
doSomeThing...
}
private:
int _hour;
int _minute;
int _second;
};
2.初始化列表
用于对对象成员进行初始化,格式为在函数名和函数体之间,以一个冒号开始,后面跟着以逗号隔开的数据成员列表,在每个成员后接一个圆括号,括号中为初始化的内容。
3.数据成员初始化顺序
即使初始化列表中的成员顺序与定义顺序不同,初始化顺序实际也与数据成员定义的顺序一致。即先给hour赋值,接着minute,最后second。
4.总结:
- 可以重载,实参决定了调用那个构造函数。(对不同对象赋不同资源)
- 新对象被创建,由编译器自动调用,且在对象的生命期内仅调用一次。(不可以手动调动)
- 如果没有显式定义时,编译器会提供一个默认的构造函数。
- 不依赖对象调用(此时对象还没有生成)
- 无返回值
类中包含以下成员时必须要在初始化列表中初始化:
(1)引用数据成员:因为引用必须在定义时初始化,且不可重新赋值。
(2)const数据成员:因为它必须初始化,不能赋值。
(3)类类型成员(该类没有缺省的构造函数,有构造函数):因为使用初始化列表可以不必调用默认构造函数来初始化,而是直接调用拷贝构造函数初始化。
二、析构函数
析构函数(destructor) 与构造函数相反,当对象脱离其作用域时(例如对象所在的函数已调用完毕),系统自动执行析构函数。
作用:释放对象所占的资源
~student()
{
delete [] mname;
mname = NULL;
}
我们在创建对象时,给对象申请了空间,申请的空间必须手动去释放,所以我们在析构函数中去释放空间。
析构函数在对象生命周期结束前由系统自动调用。
总结:
- 不能重载
- 可以手动调用,此时析构函数的调用退化普通函数的调用
- 依赖对象调用
三、拷贝构造函数
1.作用:通过已经存在的对象来创建并初始化新对象。
函数名与类名相同,无返回值,有一个形参(常用const修饰),该参数是本类类型的引用。是构造函数的重载,
class Time
{
public:
//构造函数
Time(int hour = 0, int minute = 0, int second = 0)
:_hour(hour)
,_minute(minute)
,_second(second)
{
doSomeThing...
}
//拷贝构造函数
Time(const Time& t)
:_hour(t._hour)
,_minute(t._minute)
,_second(t._second)
{
doSomeThing...
}
private:
int _hour;
int _minute;
int _second;
};
1)使用已经存在的对象创建新的对象
Time t1(12,01,59);
Time t2(t1);12
2)传值方式作为函数的参数
void FunTest1(const Time t)
{}12
3)传值方式作为函数返回值
Time FunTest2()
{
Time t;
return t;
}
系统默认提供的为浅拷贝,类成员变量有指针,实现深拷贝。
像上面这种做法,只是简单的将s赋给 _pStr,即让 _pStr也指向字符串s,这样造成的后果是多个对象指向同一空间,析构(关于析构的概念在下面介绍)出错,这种拷贝方式叫做浅拷贝。
class String
{
public:
String()
{
mptr = new char[1]();
}
String(char* ptr)
{
mptr = new char[strlen(ptr) + 1]();
strcpy_s(mptr, strlen(ptr) + 1, ptr);
}
String(const String& rhs)
{
mptr = new char[strlen(rhs.mptr) + 1]();
strcpy_s(mptr, strlen(rhs.mptr) + 1, rhs.mptr);
}
String& operator=(const String& rhs)
{
if (this != &rhs) //自赋值判断
{
delete[] mptr;//释放旧资源
mptr = new char[strlen(rhs.mptr) + 1]();//开辟新资源
strcpy_s(mptr, strlen(rhs.mptr) + 1, rhs.mptr);//赋值
}
return *this;
}
~String()
{
delete[] mptr;
mptr = NULL;
}
private:
char* mptr;
};
int main()
{
String str1("hello");
String str2(str1);
str1 = str2;
return 0;
}
如上实现了深拷贝.像这样,给新创建的对象开辟一块独立的空间,再将旧对象的内容拷贝过来,这样就不会发生如上的错误了,这种拷贝方式叫作深拷贝。图解如下。
总结:
- 系统默认合成的为浅拷贝,在大多数情况下,我们应该自己写出拷贝构造函数,
- 它的参数必须使用同类型对象的引用传递。因为对象以值传递的方式进入函数体就会调用拷贝构造函数,这样就会形成无限递归。最终导致栈溢出.
四、赋值操作符(=)重载函数
1.作用:用已存在的对象给相同类型的已存在对象赋值
String& operator=(const String& rhs)
注意:此处的&引用可去除,不过或多生成一个临时对象,不建议。
普通类型之间的赋值通过简单的=完成
int a = 10;
int b = 20;
a = b;
对于类类型的对象我们需要对‘=’重载,以完成类类型对象之间的赋值。
默认的赋值运算符的重载函数为浅拷贝。
2.深拷贝的实现过程(参考上面的实现过程)
- 自赋值判断(防止越界访问)
- 释放旧的资源
- 生成新的资源
- 赋值
5、取址(&)运算符重载
String* operator&()
{
return this;
}
取址操作符重载函数返回值为该类型的指针,无参数。
6. const修饰的取址运算符重载
const String* operator&() const
{
return this;
}