文章目录
1.类的默认成员函数有哪些
- 默认构造函数是我们不去显示写时,编译器会自动生成的函数。其中一个类中,编译器有6个默认成员函数,分别是:构造函数、析构函数、拷贝函数、赋值运算符重载函数、普通取地址运算符重载函数和const取地址运算符重载函数。
- 我们主要对前四个进行认识,后两个取地址重载函数一般编译器自动生成的即可用。以下的内容主要对默认成员函数的一个认识。
2.构造函数
- 构造函数虽名字叫构造,但它并不是完成开辟空间创建对象的任务,因为调用函数时,在开辟函数栈帧的同时,局部对象的地址也就随着开辟好了。故构造函数是完成实例化对象时对对象的初始化工作。
构造函数的特点: - 1.函数名与对象名相同。
- 2.无返回值类型,但可以有参数。(这里的无返回值类型不是指它是void类型,而是指返回值啥都不要给)
- 3.对象实例化时会自动调用对应的构造函数。
- 4.构造函数可以重载。
- 5.如果类中没有显式定义构造函数,则C++编译器会自动生成⼀个无参的默认构造函数,一旦用户显式定义编译器将不再生成。
- 6.无参构造函数、全缺省构造函数、我们不写构造时编译器默认生成的构造函数,都叫做默认构造函数。但是这三个函数有且只有⼀个存在,不能同时存在。总结一下就是不传实参就可以调用的构造就叫默认构造。其中默认构造函数是包含在构造函数这个范围之类的。
- 7.注意,我们不写,编译器默认生成的构造,对内置类型成员变量的初始化没有要求,也就是说是否初始化是不确定的,看编译器。对于自定义类型成员变量,要求调用这个成员变量的默认构造函数初始化。如果这个成员变量,没有默认构造函数,那么就会报错。
下图是对默认构造函数和构造函数的区分。
#include<iostream>
using namespace std;
class Data
{
public:
Data()//默认构造函数
{
_year = 2024;
_month = 10;
_day = 13;
}
Data(int year, int month, int day)//是默认构造函数的重载,但需要传参,所以属于构造函数的范畴
{
_year = year;
_month = month;
_day = day;
}
/*Data(int year = 2024,int month = 10,int day = 13)//默认构造函数,与第一个不能同时存在
{
_year = year;
_month = month;
_day = day;
}*/
void Print()
{
cout << _year << "/" << _month << "/" << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Data d1;//调用默认构造函数
d1.Print();
Data d2(2024,4,4);//调用传参构造函数
d2.Print();
return 0;
}
3.析构函数
- 析构函数不是完成对象的销毁功能,因为对象在函数栈帧销毁后,对象也跟着销毁了,它主要完成的是对销毁后的对象中未释放的资源进行释放,对于没有资源消耗的类中,自然析构函数可以使用默认生成的,但对于有资源申请的类中,一般都是要自我显示实现析构函数,以防资源泄露。
析构函数的特点: - 1.函数名是在类名前加上字符~。
- 2.无参数也无返回值。(这里跟构造类似,也不需要加void)
- 3.⼀个类只能有⼀个析构函数。若未显式定义,系统会自动生成默认的析构函数。
- 4.对象命周期结束时,系统会自动调用析构函数。
- 5.跟构造函数类似,我们不写编译器自动生成的析构函数对内置类型成员不做处理,自定义类型成员会调用他的析构函数。
- 6.还需要注意的是我们显示写析构函数,对于自定义类型成员也会调用他的析构,也就是说自定义类型成员无论什么情况都会自动调用析构函数。
- 7.如果类中没有申请资源时,析构函数可以不写,直接使用编译器生成的默认析构函数即可,但是有资源申请时,⼀定要自己写析构,否则会造成资源泄漏。
- 8.⼀个局部域的多个对象,C++规定后定义的先析构。参考栈帧,后进先出原则。
#include<iostream>
using namespace std;
class Stack
{
public:
Stack(int capacity = 4)
{
_a = (int*)malloc(sizeof(int) * capacity);
if (nullptr == _a)
{
perror("malloc申请空间失败");
return;
}
_capacity = capacity;
_top = 0;
cout << "调用了Stack(int capacity = 4)" << endl;
}
~Stack()
{
free(_a);
_a = nullptr;
_top = _capacity = 0;
cout << "调用了~Stack()" << endl;
}
private:
int* _a;
int _top;
int _capacity;
};
class MyQueue
{
public:
MyQueue()
{
cout << "调用了MyQueue()" << endl;
}
~MyQueue()
{
cout << "调用了~MyQueue()" << endl;
}
private:
Stack pushst;
Stack popst;
};
int main()
{
Stack st;
MyQueue q;
return 0;
}
附上运行结果图
4.拷贝构造函数
- 如果一个构造函数的第一个参数是自身类类型的引用,且其他额外的参数都有缺省值,则此构造函数即为拷贝构造函数,可以说,拷贝构造函数是一种特殊的构造函数。
拷贝构造的特点: - 1.拷贝构造函数是构造函数的⼀个重载。表明拷贝构造的显示出现必须要有构造函数的显示出现。且拷贝构造用于一个已经创建的对象,去对另一个要创建的对象进行拷贝初始化。
- 2.C++规定自定义类型对象进行拷贝行为(如传值传参时要进行拷贝行为)必须调用拷贝构造,所以自定义类型的传值传参和传值返回都会调用拷贝构造完成。
- 3.拷贝构造函数的第⼀个参数必须是类类型对象的引用,使用传值方式编译器直接报错,因为传值方式是一种拷贝行为,语法逻辑上会引发无穷递归调用。
- 4.若未显式定义拷贝构造,编译器会自动生成拷贝构造函数。自动生成的拷贝构造对内置类型成员变量会完成值拷贝/浅拷贝(⼀个字节⼀个字节的拷贝),对自定义类型成员变量会调用他的拷贝构造。
- 5.没有指向什么资源的类,编译器自动生成的拷贝构造就可以完成需要的拷贝,但如果有指向了资源,我们自己就要显示实现深拷贝(对指向的资源也进行拷贝)。对于自定义类型的成员变量,编译器会调用其自定义类型内部的拷贝构造。⼀个小技巧,如果⼀个类显示实现了析构并释放资源,那么他就需要显示写拷贝构造,否则就不需要。
- 6.传值返回会产生⼀个临时对象去调用拷贝构造,传值引用返回,会返回对象的别名(引用),没有产生拷贝。但是如果返回对象是⼀个当前函数局部域的局部对象,函数结束就销毁了,这时的引用相当于⼀个野引用,类似⼀个野指针⼀样。传引用返回可以减少拷贝,但是⼀定要确保返回对象在当前函数结束后还在,才能用引用返回。
#include<iostream>
using namespace std;
class Data
{
public:
Data()//构造
{
_year = 2024;
_month = 10;
_day = 13;
}
Data(const Data& d)//拷贝构造
{
_year = d._year;
_month = d._month;
_day = d._day;
}
void Print()
{
cout << _year << "/" << _month << "/" << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Data d1;//先创建d1
d1.Print();
Data d2(d1);//再调用拷贝构造,用d1去初始化d2,乃至初始化后面的d3,d4
Data d3 = d1;
Data d4;
d4 = d1;
d2.Print();
d3.Print();
d4.Print();
return 0;
}
5.赋值运算符重载函数
5.1运算符重载
- 1.无论哪种编程语言,运算符对于内置类型都是支持的,都是可以直接使用的。在C++中引入了类的概念,那么对于像类这样的自定义类型能否采用运算符去进行一系列的运算呢?当然是可以的,当运算符被用于类类型的对象时,C++语言允许我们通过运算符重载的形式为运算符指定新的含义。C++规定类类型对象使用运算符时,必须转换成调用对应运算符重载,若没有对应的运算符重载,则会编译报错。
- 2.运算符重载函数的定义形式是:operator(关键字)和后面要定义的运算符共同构成,其终究是函数,故其也有自己的参数列表和返回值。且要求重载运算符函数至少有一个类类型的参数。
- 3.运算符重载函数的参数个数和该运算符作用的运算对象数量⼀样多。一元则有一个参数,二元就有两个参数,且左侧运算对象传给第⼀个参数,右侧运算对象传给第⼆个参数。当⼀个运算符重载函数是成员函数时,它的参数个数比运算对象少一个,因为它的第⼀个运算对象默认传给隐式的this指针。且重载后优先级和结合性与对应的内置类型运算符保持⼀致
- 4.有5个运算符不能重载:.* :: . sizeof ? :
- 5.重载++运算符时,有前置++和后置++,运算符重载函数名都是operator++,无法很好的区分。C++规定,后置++重载时,增加⼀个int形参,跟前置++构成函数重载,方便区分。 重载<<和>>时,需要重载为全局函数,因为重载为成员函数,this指针默认抢占了第⼀个形参位置,第⼀个形参位置是左侧运算对象,调用时就变成了对象<<cout,不符合使用习惯和可读性。重载为全局函数把ostream/istream放到第⼀个形参位置就可以了,第⼆个形参位置当类类型对象。且在类中的声明要加上friend(关键字)表示声明和定义是友元,这样全局才能访问类中私有的成员变量。
#include<iostream>
using namespace std;
class Data
{
friend ostream& operator<<(ostream& out, const Data& d);//声明放在类中,用friend连接,且为了支持运算符的连续性,返回值应为ostream&
friend istream& operator>>(istream& in, Data& d);
public:
Data(int year=2024,int month=10,int day=14)
{
_year = year;
_month = month;
_day = day;
}
Data(const Data& d)//拷贝构造
{
_year = d._year;
_month = d._month;
_day = d._day;
}
void Print()
{
cout << _year << '/' << _month << '/' << _day << endl;
}
bool operator==(const Data& d)//对==的重载
{
return _year == d._year &&
_month == d._month &&
_day == d._day;
}
Data& operator++()//对前置++的重载
{
cout << "前置++" << endl;
++_day;//这里只是简单对_day前置++,后面会有更具体的日期类的运算符重载函数实现
return *this;
}
Data operator++(int)//对后置++的重载
{
cout << "后置++" << endl;
Data tmp = _day++;//同理,只是简单实现
return tmp;
}
private:
int _year;
int _month;
int _day;
};
ostream& operator<<(ostream& out, const Data& d)//用friend连接来就不要用::指定类域了
{
out << d._year << "年/" << d._month << "月/" << d._day << "日" << endl;
return out;
}
istream& operator>>(istream& in, Data& d)
{
cout << "请依次输入合法的年/月/日" << endl;
cin >> d._year >> d._month >> d._day;
return cin;
}
int main()
{
Data d1;
Data d2(2024, 4, 4);
Data d3(2023, 4, 4);
Data d4;
cout << (d1.operator==(d2)) << endl;//1.为==的显示调用
cout << (d1 == d2) << endl;//2.为==的隐式调用,编译器会转化成 d1.operator==(d2),由于d1与d2不同所以输出0
++d2; //隐式调用前置++,其显示为d2.operator++()
cout << "前置++的结果为:" << d2 << endl;//3.展示前置++
d3++; //隐示调用后置++,其显示为d3.operator++(0);
cout << "后置++的结果为:" << d3 << endl;//4.展示后置++
cin >> d4; //5.为 >> 的隐示调用,其显示为d4.operator >> (cin,d4)
cout << d1 << d2 << d3 << d4 << endl;//6.为 << 的隐式调用,其实3和4也有 << 隐式调用的体现
return 0;
}
5.2赋值运算符重载函数
- 赋值运算符重载是⼀个默认成员函数,用于完成两个已经存在的对象直接的拷贝赋值,这里要注意跟
拷贝构造区分,拷贝构造用于⼀个对象拷贝初始化给另⼀个要创建的对象。
赋值运算符重载函数的特点: - 1.赋值运算符重载是⼀个运算符重载,规定必须重载为成员函数。赋值运算重载的参数建议写成const当前类类型引用,否则会传值传参会有拷贝。
- 2.有返回值,且建议写成当前类类型引用,引用返回可以提高效率,有返回值目的是为了支持连续赋值的场景。
- 3.没有显式实现时,编译器会自动生成⼀个默认赋值运算符重载函数,默认赋值运算符重载行为跟默认拷贝构造函数类似,对内置类型成员变量会完成值拷贝/浅拷贝(⼀个字节⼀个字节的拷贝),对自定义类型成员变量会调用他的赋值运算符重载函数。
- 4.同拷贝构造一样,当一个类中没有指向什么资源的时候,默认生成的赋值运算符重载函数就可以使用,当指向有资源,则就需要我们显示实现。小技巧:如果⼀个类显示实现了析构并释放资源,那么他就需要显示写赋值运算符重载,否则就不需要。
#include<iostream>
using namespace std;
class Data
{
friend ostream& operator<<(ostream& out, const Data& d);
public:
Data(int year = 2024, int month = 10, int day = 14)//默认构造函数
{
cout << "普通构造" <<endl;
_year = year;
_month = month;
_day = day;
}
Data(Data& d)//拷贝构造函数
{
cout << "拷贝构造" << endl;
_year = d._year;
_month = d._month;
_day = d._day;
}
//d1=d2
//Data& operator=(Data* const this,const Data& d)---this是左操作数d1,d是右操作数d2,将d2赋值给d1
Data& operator=(const Data& d)//赋值运算符重载函数
{
//this是指向左操作数(d1),d是指向右操作数(d2)
cout << "赋值运算符重载" << endl;
this->_year = d._year;
this->_month = d._month;
this->_day = d._day;
return *this;
}
private:
int _year;
int _month;
int _day;
};
ostream& operator<<(ostream& out, const Data& d)
{
out << d._year << "年/" << d._month << "月/" << d._day << "日" << endl;
return out;
}
int main()
{
Data d1;
cout << d1 << endl;
Data d2(2024, 4, 4);
cout << d2 << endl;
Data d3(d1); //拷贝构造,等价于Data d3 = d1;其中d3还未被创建
cout << d3 << d1 << endl;
d1 = d2; //赋值运算符重载,其中d1与d2都是已经被创建了的
cout << d1 << d2 << endl;
return 0;
}
6.取地址运算符重载函数
6.1const成员函数
- 1.将const修饰的成员函数称之为const成员函数,const修饰成员函数放到成员函数参数列表的后面。
- 2.const实际修饰该成员函数隐含的this指针,表明在该成员函数中不能对类的任何成员进行修改。举例来说:当const修饰Date类的Print成员函数时,Print隐含的this指针由 Date* const this 变为 const Date* const this。
- 3.如果某个成员函数中不会对类的任何成员进行修改,一般建议加上const修饰。
#include<iostream>
using namespace std;
class student
{
public:
student()
{
year = 18;
}
//void Print() const 等价于 void Print(const student* const this)
void Print() const
{
//this->year = 20;//error 由于正在通过常量对象访问“year”,因此无法对其进行修改
cout << year << endl;
}
private:
int year;
};
int main()
{
student hl;
hl.Print();
return 0;
}
6.2普通取地址重载函数与const取地址运算符重载函数
- ⼀般来说这两个函数编译器自动生成的就可以够我们用了,不需要去显示实现。除非⼀些很特殊的场景,比如我们不想让别人取到当前类对象的地址,就可以自己显示实现⼀份,胡乱返回⼀个地址。
class Data
{
public:
Data* operator&()//普通取地址重载函数,返回值是指针
{
return this;//返回的是Data*类型
}
const Data* operator&() const
{
return this;//const取地址重载函数,返回的是const Data*类型
//return nullptr;//也可返回任意地址
}
private:
int _year;
int _month;
int _day;
};
标签:函数,对象,C++,运算符,重载,之类,拷贝,Data,构造函数
From: https://blog.csdn.net/2401_84420653/article/details/142895858