目录
C++的类在什么都不写的情况下会有6个默认生成的成员函数。(构造函数,析构函数,拷贝构造函数,赋值重载函数,取地址以及const取地址操作符重载函数)
一.构造函数
构造函数是一个特殊的函数,作用是初始化对象,其特性如下:
1. 函数名与类名相同。2. 无返回值。3. 对象实例化时编译器 自动调用 对应的构造函数。4. 构造函数可以重载。5. 我们写了构造函数编译器就不会生成默认构造函数了。
#include<iostream>
using namespace std;
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;
};
int main()
{
Date d1; // 调用无参构造函数
Date d2(2023, 9, 10); // 调用带参的构造函数
}
注意:如果通过无参的构造函数初始化对象时,不能写成Date d1(),这样会被认为是函数声明!
class Date
{
public:
Date()
{}
int _year = 2023;//C++11新特性,可以在成员变量声明处给缺省值或者函数(比如栈的缺省值可以写malloc)
int _month = 9;
int _day = 10;
};
int main()
{
Date d1;
}
下面是构造函数比较重要的特性:
1.C++中有内置类型和自定义类型的分类,可以说除了class和struct以及union定义的类是自定义类型之外都是内置类型,指针也是内置类型。构造函数对内置类型不会处理(在新版的VS编译器中,会对内置类型进行一定处理,其他编译器还不会进行处理),对于自定义类型会调用对应的构造函数,所以对象的成员都是自定义类型可以不写构造函数。
2.无参的构造函数和全缺省的构造函数以及编译器生成的构造函数编译器都叫默认构造函数,默认构造函数只能存在一个(如果存在多个会出现调用歧义),可以认为不用传参的构造函数就叫默认构造函数。
二.析构函数
析构函数和构造函数类似,我们不写编译器会默认生成一个,作用是清理对象的资源,以下是析构函数的特性:
1. 析构函数名是在类名前加上字符 ~。
2. 无参数无返回值类型。
3. 一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。注意:析构函数不能重载
4. 对象生命周期结束时,编译器会 自动调用析构函数
class Date
{
public:
Date()
{
cout << "Date()" << endl;
}
~Date()
{
cout << "~Date()" << endl;
}
int _year;
int _month;
int _day;
};
int main()
{
Date d1;
}
析构函数的重要特性:
1.析构函数和构造函数一样,对内置类型不处理,对自定义类型就调用对应的析构函数。
2.构造函数的调用顺序是先定义的先调用构造函数,析构函数是先定义的后调用析构函数。
三.拷贝构造函数
拷贝构造函数的功能是把类的数据拷贝到另一个类里,比如类作为参数以值的方式传入函数时会自动调用拷贝构造函数。以下是特性:
1.拷贝构造函数是构造函数的重载形式
2.参数只有一个,就是类的类型的引用(建议加上const)
3.如果参数是类的类型但不是引用(值传递)就会发生无穷递归,原理是类作为参数以值传入函数会自动调用拷贝构造函数,然后一直这样直到栈溢出。
4.若不显示定义拷贝构造函数编译器会自动生成默认的拷贝构造函数
5.拷贝构造函数的调用场景:
(1).使用已存在的对象创建新对象
(2).函数参数类型为类的类型对象
(3).函数返回值类型为类的类型对象
#include<iostream>
using namespace std;
class Date
{
public:
Date(int year = 2023, int month = 9, int day = 11)
{
_year = year;
_month = month;
_day = day;
}
Date(const Date& d)//拷贝构造函数
{
_year = d._year;
_month = d._month;
_day = d._day;
}
int _year;
int _month;
int _day;
};
int main()
{
Date d1(2023, 9, 11);
Date d2(d1);//用创建好的对象初始化新创建的对象,自动调用拷贝构造
Date d3 = d1;//和上句同理
return 0;
}
拷贝构造函数的重要特性:
1.对于内置类型会处理(会拷贝),自定义类型会调用对应的拷贝构造函数。
2.默认的拷贝构造函数是浅拷贝(值拷贝)只会拷贝成员变量里面的数据。对于栈来说使用默认拷贝构造函数,拷贝对象和被拷贝对象所指向的是同一块空间,任意一方更改栈里面的数据另一方也会被更改。(传入函数拷贝的临时对象出函数时会调用析构函数释放指向的空间,在函数外的被拷贝对象所指向的空间自然也被释放了,所以程序结束时被拷贝对象自动调用析构函数的时候会出错(释放了已经释放过的空间))
四.运算符重载
对于自定义类型的运算符运算C++并没有定义,需要用户自己去实现,这就是运算符重载,
以下是运算符重载的特性:
函数原型为:返回值 operator操作符(参数列表)
{
//内容实现
}
注意:1.不能通过连接其他符号创建新的操作符,比如operator@
2.重载操作符必须有一个类的类型参数
3.用于运算内置类型的运算符的用意不能改变,比如两个int的+
4.作为类成员重载时,参数的个数是运算符操作数-1,原因是有隐藏的this指针
5.(.*)(::)(sizeof)(?:)(.)上述5个运算符不能被重载
#include<iostream>
using namespace std;
class Date
{
public:
Date(int year = 2023, int month = 9, int day = 11)
{
_year = year;
_month = month;
_day = day;
}
bool operator==(const Date& d)//重载运算符==,作为成员函数参数个数是运算符操作数-1
{
return _year == d._year && _month == d._month && _day == d._day;
}
int _year;
int _month;
int _day;
};
int main()
{
Date d1(2023, 9, 11);
Date d2(2023, 9, 12);
Date d3(2023, 9, 11);
cout << (d1 == d2) << endl;//输出结果为0
cout << (d1 == d3) << endl;//输出结果为1
return 0;
}
可以声明定义分离:
#include<iostream>
using namespace std;
class Date
{
public:
Date(int year = 2023, int month = 9, int day = 11)
{
_year = year;
_month = month;
_day = day;
}
bool operator==(const Date& d);//重载运算符==,作为成员函数参数个数是运算符操作数-1
int _year;
int _month;
int _day;
};
bool Date::operator==(const Date& d)//可以声明定义分离,但是此时类的成员变量要公有,或者是友元函数解决,或者干脆不要分离
{
return _year == d._year && _month == d._month && _day == d._day;
}
int main()
{
Date d1(2023, 9, 11);
Date d2(2023, 9, 12);
Date d3(2023, 9, 11);
cout << (d1 == d2) << endl;//输出结果为0
cout << (d1 == d3) << endl;//输出结果为1
return 0;
}
五.赋值运算符重载
赋值运算符用户不写,编译器会在类里生成一个默认赋值重载函数,用于字节序的拷贝,一定要写成成员函数,以下是赋值运算符重载的注意事项:
1.参数类型:const 类的类型& (提高传参效率)
2.返回值类型:类的类型& (提高效率以及支持连续赋值)
3.检测是否要自己给自己赋值
4.返回*this (支持连续赋值)
#include<iostream>
using namespace std;
class Date
{
public:
Date(int year = 2023, int month = 9, int day = 11)
{
_year = year;
_month = month;
_day = day;
}
Date& operator=(const Date& d)//重载运算符=,作为成员函数参数个数是运算符操作数-1
{
if (&d == this)//如果是自己给自己赋值就退出
return *this;
_year = d._year;
_month = d._month;
_day = d._day;
return *this;//支持连续赋值
}
void Print()//打印日期
{
cout << _year << " " << _month << " " << _day << endl;
}
int _year;
int _month;
int _day;
};
int main()
{
Date d1(2023, 9, 11);
Date d2(2023, 9, 12);
Date d3(2023, 9, 13);
d3 = d2 = d1;//连续赋值
d1.Print();
d2.Print();
d3.Print();
return 0;
}
下面是赋值重载函数写成全局函数:
#include<iostream>
using namespace std;
class Date
{
public:
Date(int year = 2023, int month = 9, int day = 11)
{
_year = year;
_month = month;
_day = day;
}
void Print()//打印日期
{
cout << _year << " " << _month << " " << _day << endl;
}
int _year;
int _month;
int _day;
};
Date& operator=(Date& left, const Date& right)//没有this指针所以是两个参数,赋值运算符重载写成全局函数,编译报错
{
if (&left != &right)
{
left._year = right._year;
left._month = right._month;
left._day = right._day;
}
return left;
}
int main()
{
Date d1(2023, 9, 11);
Date d2(2023, 9, 12);
Date d3(2023, 9, 13);
d3 = d2 = d1;//连续赋值
d1.Print();
d2.Print();
d3.Print();
return 0;
}
很显然编译会报错,原因是:赋值运算符重载函数写成全局,相当于Date类里面没有显示写,编译器会生成默认的赋值重载函数,这样在实际调用的过程中会发生冲突,不清楚调用哪个重载函数。总结就是类中未涉及到资源管理是否实现都可以,否则就要实现。
编译器生成的默认赋值重载函数是浅拷贝,也就是值拷贝,和拷贝构造函数类似,对于栈这种需要深拷贝的类来说是不能用的,对于不需要深拷贝的类是可以使用默认生成的赋值重载函数。
注意:拷贝构造函数是一个已存在的对象初始化另一个不存在的对象,而赋值是两个已存在的对象进行拷贝!
前置++和后置++的不同:
重载前置++的函数原型:返回值 operator++() {//实现内容}
重载后置++的函数原型:返回值 operator++(int){//实现内容}
后置++就是加了一个int进行占位,进行区分,编译器会进行特殊处理,显示调用可以随便传入一个int的值。
六.const成员
const是关键字,可以修饰类和成员函数(有两个地方可修饰)并且不止能修饰类和成员函数,而被const修饰的类不能调用非const的成员函数,会涉及到权限的放大。
以下是const修饰函数返回值的情况:
#include<iostream>
using namespace std;
class Date
{
public:
Date(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
const int GetYear()//const放在前面修饰返回值,对于这里因为是传值返回,所以会生成临时变量(具有常性)所以加const没有意义
{
return _year;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1(2023, 9, 11);
cout << d1.GetYear() << endl;
}
const修饰成员函数隐藏的this指针的情况下:
#include<iostream>
using namespace std;
class Date
{
public:
Date(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
int GetYear() const//const放在后面修饰隐藏的this指针,可以让const类对象调用,也可以让非const类对象调用
{
return _year;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
const Date d1(2023, 9, 11);//const类对象
cout << d1.GetYear() << endl;
}
总结:const修饰返回值对于传值返回一般没有意义,const修饰this指针时可以让const类对象非const对象都能调用。
注意:函数后加const修饰(const修饰this)是在这个函数不会修改类的情况可以加,否则就不能加,针对一些函数是需要写const版本和非const版本比如只读和修改的功能分离(重载[]就需要两个版本)。const修饰返回值的情况下一般是引用返回才看情况选择加或不加!
下面是返回值加const有意义的情况:(以及重载[]的const版本返回值需要const)
const int& Func()
{
static int a;
return a;
}
思考下面几个问题:×的意味着有权限的缩小
1. const 对象可以调用非 const 成员函数吗?(×)2. 非 const 对象可以调用 const 成员函数吗?(√)3. const 成员函数内可以调用其它的非 const 成员函数吗?(×)4. 非 const 成员函数内可以调用其它的 const 成员函数吗?(√)
七.取地址以及const取地址操作符重载
这两个是默认成员函数,功能就是返回对象的地址,一般情况下不需要显示写,编译器自动生成的够用,除非不想让别人知道对象的实际地址。(注意const对象需要初始化,笔者写这段代码const对象忘记初始化,编译器报错)
#include<iostream>
using namespace std;
class Date
{
public:
//两个默认取地址操作符重载的实现
Date* operator&()
{
return this;
}
const Date* operator&()const
{
return this;
}
private:
int _year = 2023;//初始化
int _month = 9;//初始化
int _day = 11;//初始化
};
int main()
{
Date d1;
const Date d2;//const对象没有初始化会报错
cout << &d1 << endl << &d2;//输出两个对象的地址
return 0;
}