类成员函数分类

默认成员函数:当用户没有在类中显示定义时,编译器也会自动生成的成员函数。内置类型不处理,自定义类型调用它的默认构造。
构造函数
构造函数的名字与类名相同,创建类对象时由编译器自动调用。主要用于初始化对象(为各成员变量赋值)且在对象整个生命周期中只调用一次。
特点
1.由编译器自动调用
2.无返回值,也不写void
3.支持函数重载
4.函数名与类名相同
class Date
{
public:
// 1.无参构造函数
Date()
{}
// 2.带参构造函数
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(2015, 1, 1); // 调用带参的构造函数
}
当用户没有显示定义构造函数时,编译器会自动生成一个默认的构造函数,进行初始化。默认构造函数生成时,对内置类型的类成员变量进行初始化时,其值为随机值;对自定义类型的类成员变量初始化时,编译器会调用该自定义类型成员的构造函数。
析构函数
析构函数的名字组成为~类名(),在对象销毁前由编译器自动调用。主要进行对象的清理工作。例如,free动态开辟的内存,防止内存泄漏等等。
特点
1.由编译器自动调用
2.无返回值,也不写void
3.不可以有参数,因此不支持函数重载
4.函数名~类名
class Date
{
public:
~Date()
{
//析构函数
}
};
一个类只能有一个析构函数,若未显示定义,系统会自动生成默认析构函数,析构时,内置类型成员变量不处理,自定义类型成员变量会自动调用它的析构函数。
注:如果类中没有申请资源时,析构函数可以不写,直接使用编译器生成的默认析构函数,比如
Date类;有资源申请时,一定要写,否则会造成资源泄漏,比如Stack类。
拷贝构造函数
拷贝构造函数是函数名与类名相同,形参只有一个,是对类对象的引用(const修饰)。一般用于通过已存在的类对象初始化新的类对象、值传递的方式给函数参数传值、以值方式返回局部对象。
特征:
1. 拷贝构造函数是 构造函数的一个重载形式。
2. 拷贝构造函数的 参数只有一个且必须是类类型对象的引用,使用传值方式编译器直接报错,
因为 会引发无穷递归调用。
3. 若未显式定义,编译器会生成 默认的拷贝构造函数。 默认的拷贝构造函数对象按内存存储按
字节序完成拷贝,这种拷贝叫做浅拷贝,或者 值拷贝。

浅拷贝和深拷贝
浅拷贝:进行简单的赋值操作
深拷贝:在堆中重新申请空间,进行拷贝操作
在没有手动定义拷贝构造函数时,编译器会默认生成拷贝构造函数,将类成员属性进行一个浅拷贝。而对于某些类,实现浅拷贝时会出现问题,如下:
class Person
{
public:
Person()
{
cout << "Person 默认构造函数调用" << endl;
}
~Person()
{
if (m_height)
{
delete m_height;
m_height = NULL;
}
cout << "Person 析构函数调用" << endl;
}
Person(int age,int height)
{
m_age = age;
m_height = new int(height);
cout << "Person 有参构造函数调用" << endl;
}
int m_age;
int* m_height;
};
//调用后,无法正常运行
void test1()
{
Person m1(18,160);
Person m2(m1);
cout << "m2的身高是 " << *m2.m_height << endl;
}

因此我们需要手动实现构造函数。
Person(const Person& p)
{
m_age = p.m_age;
m_height = new int(*p.m_height);
}
总结:类中如果没有涉及资源申请时,拷贝构造函数是否写都可以;一旦涉及到资源申请
时,则拷贝构造函数是一定要写的,否则就是浅拷贝。
构造函数调用的典型场景
用一个定义好的类对象创建另一个类对象
class Person
{
public:
Person()
{
cout << "Person 默认构造函数调用" << endl;
}
~Person()
{
cout << "Person 析构函数调用" << endl;
}
Person(int age)
{
m_age = age;
cout << "Person 有参构造函数调用" << endl;
}
Person(const Person& p)
{
m_age = p.m_age;
cout << "Person 拷贝构造函数调用" << endl;
}
int m_age;
};
void test1()
{
Person m1(18);
Person m2(m1);
cout << "m2的年龄是 " << m2.m_age << endl;
}

值传递方式给函数参数传值
void Play(Person p)
{
}
void test2()
{
Person m1;
Play(m1);
}

值方式返回局部对象
Person test3()
{
Person m;
return m;
}
int main()
{
Person p=test3();
return 0;
}

赋值重载函数
C++中用户可以重定义或重载大部分内置的运算符。就能使用运算符进行自定义类型的运算。
函数名字为:关键字operator后面接需要重载的运算符符号。
函数原型:返回值类型 operator操作符(参数列表)
注意:
1.不能通过连接其他符号来创建新的操作符:比如operator@
2.重载操作符必须有一个类类型参数
3.用于内置类型的运算符,其含义不能改变,例如:内置的整型+,不 能改变其含义
4.作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐
藏的this
5. .* :: sizeof ?: . 注意以上5个运算符不能重载。(常用)

加法运算符重载
重载运算符一般可以有两种定义方式,可通过类成员函数/全局函数进行重载,以加号为例:
class Date
{
Date operator+(const Date& d) //类成员函数进行重载
{
Date tmp;
tmp.year=this->year+d.year;
....
return tmp;
}
private:
int year;
...
};
Date operator+(const Date& d1,const Date& d2) //全局函数进行重载
{
....
}
int main()
{
Date d1;
Date d2;
Date d3=d1+d2;
//Date d3=d1.operator(d2) 类成员函数进行重载本质
//Date d3=operator(d1,d2) 全局函数进行重载本质
}
自增/自减运算符重载
分为前置和后置两种,区别是前置返回++/--之后的值,后置是++/--之后返回原来的值
// 前置++:返回+1之后的结果
// this指向的对象函数结束后不会销毁,故以引用方式返回提高效率
Date& operator++()
{
_day += 1;
return *this;
}
// 后置++:
// C++规定:后置++重载时多增加一个int类型的参数,但调用函数时该参数不用传递,编译器自动传递
// 后置++是先使用后+1,因此需要返回+1之前的旧值,故需在实现时需要先将this保存
一份,然后给this+1
// 而temp是临时对象,因此只能以值的方式返回,不能返回引用
Date operator++(int)
{
Date temp(*this);
_day += 1;
return temp;
}
赋值运算符重载(=)
参数类型:const T&,传递引用可以提高传参效率
返回值类型:T&,返回引用可以提高返回的效率,有返回值目的是为了支持连续赋值
不支持通过全局函数进行运算符重载,会与编译器在类中生成的 默认赋值运算符重载冲突了
Date& operator=(const Date& d)
{
if(this != &d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
return *this;
}
和拷贝构造函数的原理类似:如果类中未涉及到资源管理,赋值运算符是否实现都可以;一旦涉及到资源管理则必须要实现。
关系运算符重载
C++ 语言支持各种关系运算符( < 、 > 、 <= 、 >= 、 == 等等),它们可用于比较 C++ 内置的数据类型。
返回值类型:bool