C++中类,this指针,构造函数,析构函数。拷贝构造函数,初步理解运算符重载,初步理解赋值运算符重载
文章目录
- C++中类和对象
- 类的定义
- 类定义格式
- 类中访问修饰限定符
- 1.public(公共的)
- 2.private(私密的)
- 3.protected(保护的)
- this指针
- 由问题深入理解this指针
- 构造函数
- 析构函数
- 拷贝构造函数
- 初步理解运算符重载
- 初步理解赋值运算符重载
- 取地址运算符重载
C++中类和对象
类的定义
类定义格式
class为定义类的关键字。
定义在类面的成员函数默认为inline。
class Date
{//默认限制符是私有的
public:void Init(int year, int month, int day) {_year = year;_month = month;_day = day;}void print();
private:int _year;int _month;int _day;
};//声明类的函数
void Date::print() {cout << _year << endl;
}
类中访问修饰限定符
访问修饰限定符有三类。
class定义成员没有被访问限定修饰符默认为private,struct默认为public。
1.public(公共的)
public修饰的成员可以在类外直接被访问。
2.private(私密的)
private修饰的成员不可以在类外被访问,只可以在类中进行访问。
3.protected(保护的)
它介于 public(公有)和 private(私有)之间,专门为继承体系设计。(继承在后续博客中阐述)。
this指针
在本文最开始的代码中,Date类中有Init与Print两个成员函数。我们现在实例化出两个对象d1,d2。
int main(){//Date类实例化出对象d1和d2Date d1;Date d2;d1.Init(2025, 8, 8);d1.print();d2.Init(2025, 8, 12);d2.print();return 0;
}
提出一个问题,d1和d2在调用成员函数的时候,那么如何知道我们访问的是d1的对象,还是d2的对象呢?在这里我们提出this指针。
在成员函数的默认的第一个形参,会增加一个当前类类型的指针,叫this指针。
class Date
{//默认限制符是私有的
public://void Init(Date* const this, int year, int month, int day)void Init(int year, int month, int day) {_year = year;_month = month;_day = day;}void print();
private:int _year;int _month;int _day;
};
类的成员函数中访问成员变量,本质都是通过this指针访问的,this->_year = year;
C++规定不能在实参和形参的位置显示的写this指针(编译时编译器会处理),但是可以在函数体内显示的使用this指针。
由问题深入理解this指针
一、思考一下代码可以正常运行吗?
#include<iostream>
using namespace std;
class A
{
public:void Print(){cout << "A::Print()" << endl;}
private:int _a;
};int main()
{A* p = nullptr;p->Print();return 0;
}
答:可以正常运行!!!
1.在此代码中,指针p被初始化为nullptr(空指针),但是在p->Print()的时候却可以正常运行。
2.C++中的成员函数在编译时被处理成普通函数,并且隐式添加一个this指针参数,那么调用p->Print()实际上就等于A::Print( p );
3.Print()函数逻辑独立于对象实例(没有访问成员变量)。
4.成员函数的调用过程仅传递了this指针(值为nullptr),但函数内未使用它。
二、思考一下代码可以正常运行吗?
#include<iostream>
using namespace std;
class A
{
public:void Print(){cout << "A::Print()" << endl;cout << _a << endl;}
private:int _a;
};int main()
{A* p = nullptr;p->Print();return 0;
}
答:运行崩溃!!!
在次代码中,p虽然也是指向nullptr(空指针),但是问题不是出现在这里,而是在成员函数Print()中进行了成员变量 _a的访问。因为this->_a解引用了空指针。
构造函数
构造函数是特殊的成员函数。
1.函数名和类名相同。
2.无返回值。(返回值什么都不需要写,也不需要写void)。
3.对象实例化时系统会自动调用对应的构造函数。
4.构造函数可以重载。
5.如果在类中没有自己定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,但是一旦人为定义了构造函数,编译器将不再生成。
6.默认构造函数有三个:无参构造函数,全缺省构造函数,我们不写构造时编译器默认生成的构造函数。这三个函数有且只有一个存在,不能同时存在。
以下代码需要屏蔽无参构造函数运行。
#include<iostream>
using namespace std;
//构造函数
class Date
{
public://1.无参构造函数Date() {_year = 1;_month = 1;_day = 1;}//2.带参构造函数Date(int year, int month, int day) {_year = year;_month = month;_day = day;}//3.全缺省构造函数//全缺省和无参不能同时存在Date(int year = 1, int month = 1, int day = 1) {_year = year;_month = month;_day = day;}void print() {cout << _year << "/" << _month << "/" << _day << endl;;}
private:int _year;int _month;int _day;
};
int main(){Date d1;//会自动调用无参构造函数d1.print();//调用有参数构造函数Date d2(2025, 7, 31);d2.print();//调用全缺省Date d3(2025);d3.print();return 0;
}
运行结果
析构函数
析构函数不是完成对对象本身的销毁,比如局部对象时存在在栈帧的,函数结束栈帧销毁,它就释放了,不需要我们管。C++规定对象在销毁时自动调用析构函数,完成对对象中资源的清理与释放。
1.析构函数名是在类名前加上字符 ~。
2.无参数无返回值。(同构造函数一样)
3.一个类只能有一个析构函数。若未显示定义,系统会自动生成默认的析构函数。
4.对象生命周期结束时,系统会自动调用析构函数。
5.跟构造函数类似,我们不写编译器自动生成的析构函数对内置类型成员不做处理,自定义类型成员会调用自己的析构函数。
6.在显示写析构函数,对于自定义类型成员也会调用自己的析构函数,总而言之,自定义类型不论当我们有没有显示定义析构函数,自定义类型只会调用自己的析构函数。
拷贝构造函数
如果一个构造函数的第一个参数是自身类类型的引用,且任何额外的参数都有默认值,则次构造函数也叫拷贝构造函数,也就是说拷贝构造函数就是一个特殊的构造函数。(在以下代码体现)
拷贝构造用于一个对象拷贝给另一个要创建的对象(在下下个代码中的d2体现)。
#include<iostream>
using namespace std;
class Date {
public:Date(int year,int month,int day) {_year = year;_month = month;_day = day;}//拷贝构造 //加上const不改变外面的实参Date(const Date& d) {_year = d._year;_month = d._month;_day = d._day;}void print() {cout << _year << "/" << _month << "/" << _day << endl;}
private:int _year;int _month;int _day;};
1.拷贝构造函数是构造函数的一个重载。
2.拷贝构造函数的第一个参数必须是类类型对象的引用,使用传值方式编译器直接报错,因为语法逻辑上会引发无穷递归调用。拷贝构造函数也可以多个参数,但是第一个参数必须是类类型对象的引用,后面的参数必须有缺省值。
每次要调用拷贝构造函数之前要先传值传参,而传值传参是一种拷贝,又形成一个新的拷贝构造,就形成了无穷的递归
3.C++规定自定义类型对象进行拷贝必须调用拷贝构造,所以这里自定义类型传值传参和传值返回都会调用拷贝构造完成。
4.若未显示定义拷贝构造,编译器会自动生成拷贝构造函数。自动生成的拷贝构造对内置类型成员变量会完成值拷贝(浅拷贝)(一个字节一个字节的拷贝),对自定义类型成员变量会调用他的拷贝构造。
5.如果一个类显示实现了析构并释放资源,那么就需要写拷贝构造,否则就不需要。
6.传值返回会产生一个临时对象调用拷贝构造,传值引用返回,返回的是返回对象的别名,没有产生拷贝。传引用返回可以减少拷贝。
#include<iostream>
using namespace std;
class Date {
public:Date(int year,int month,int day) {_year = year;_month = month;_day = day;}//拷贝构造 //加上const不改变外面的实参Date(const Date&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() {Date d1(2025, 8, 8);d1.print();//拷贝构造 c++的规定,传值传参要调用拷贝构造Date d2(d1);Date d3 = d1; //注意区分d2.print();return 0;
}
注意:拷贝构造用于一个对象拷贝给另一个要创建的对象。
初步理解运算符重载
1.当运算符被用于类类型的对象时,必须转换成调用对应运算符重载。否则,编译报错。
2.运算符重载的名字由operator和后面要定义的运算符共同构成。和其他函数一样,它也具有返回类型和参数列表以及函数体。例如:operator+
3.重载运算符函数的参数个数和该运算符作用的预算对象数量一样多。
4.如果一个重载运算符函数是成员函数,则它的第一个运算对象默认传给隐式的this指针,因此运算符重载作为成员函数时,参数比运算对象少一个。
5. 运算符重载以后,其优先级和结合性与对应的内置类型运算符保持一致。
6.不能通过连接语法中没有的运算符。
7.(.*) (:: ) (sizeof) (?: ) (.),以上5个运算符不能重载。
解释 (.*)运算符。
class A {
public:void func() {cout << "A::func()" << endl;}
};
//成员函数指针类型
typedef void(A::*PF)();
/*typedef 创建类型别名void 指向函数的返回类型(A::*) 核心标识:表示这是指向类A的成员函数的指针类型PF 定义的新类型名称() 函数参数列表(空括号表示无参数)
*/
int main() {//void(A::*pf)() = nullptr;//PF 类型只能指向 A 类的成员函数PF pf = nullptr;//C++规定成员函数要加&才能取到函数指针pf = &A::func;A aa;//回调成员函数的指针.*(aa.*pf)();return 0;
}
8.重载++运算符时,有前置++和后置++,运算符重载的函数名都是operator++,无法很好的区分,C++规定,后置++重载时,增加一个int形参,和前置++构成函数重载,方便区分。
9.重载<<和>>时,需要重载为全局函数,因为重载为成员函数,this指针默认抢占了第一个形参位置,第一个形参位置是左侧运算对象,调用时就变成了对象<<cout,不符合使用习惯。重载为全局函数把ostream/istream放到第⼀个形参位置就可以了,第⼆个形参位置当类类型对象。
初步理解赋值运算符重载
赋值运算符重载是一个默认成员函数,用于完成两个已经存在的对象直接的拷贝赋值。
1.赋值运算符重载是一个运算符重载,规定必须重载为成员函数。赋值运算重载的参数建议写成const当前类类型的引用,否则会传值传参会有拷贝。
2.有返回值,建议写成当前类类型引用,提高效率,有返回值的目的是为了支持连续赋值场景。
3.没有显示实现时,编译器会自动生成一个默认赋值运算符重载,默认赋值运算符重载行为跟默认拷贝构造函数类似,对内置类型成员变量会完成值拷贝/浅拷贝(一个字节一个字节的拷贝),对自定义类型成员变量会调用他的赋值重载函数。
在以下代码中,实现了operator=(),可以让d1对象等于d2对象。但是仔细观察以下代码,就能得到只能让d1=d2,无法做到d1=d2=d3。原因:赋值操作返回 void,而连续赋值需要将第一次赋值的结果(d1 = d2 的返回值)作为第二次赋值(d3 = …)的右值。void 类型无法作为右值使用。
//d1=d2 赋值重载拷贝 这个只能让 d3 = d1 无法做到d3 = d1 = d2void operator = (const Date& d){_year = d._year;_month = d._month;_day = d._day;}
#include<iostream>
using namespace std;
class Date {
public:Date(int year,int month,int day) {_year = year;_month = month;_day = day;}//拷贝构造 //加上const不改变外面的实参Date(const Date&d) {_year = d._year;_month = d._month;_day = d._day;}//d1=d2 赋值重载拷贝 这个只能让 d3 = d1 无法做到d3 = d1 = d2void operator = (const Date& 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() {Date d4(2025, 8, 9);Date d5(2025, 8, 10);Date d6(2025, 8, 11);d4 = d5;d4 = d5 = d6;//赋值重载拷贝d4.print();d5.print();return 0;
}
升级代码!
返回值:通过 return *this 返回当前对象的引用。
连续赋值原理:
d1 = d2 执行后,返回 d1 的引用。
返回值作为 d3 = … 的右值,等价于 d3 = d1(此时 d1 已是赋值后的新值)。
//d3 = d1 = d2 = ......
Date& operator = (const Date& d) {_year = d._year;_month = d._month;_day = d._day;return *this;
}
#include<iostream>
using namespace std;
class Date {
public:Date(int year,int month,int day) {_year = year;_month = month;_day = day;}//拷贝构造 //加上const不改变外面的实参Date(const Date&d) {_year = d._year;_month = d._month;_day = d._day;}//d3 = d1 = d2 = ......Date& operator = (const Date& d) {_year = d._year;_month = d._month;_day = d._day;return *this;}void print() {cout << _year << "/" << _month << "/" << _day << endl;}
private:int _year;int _month;int _day;};
int main() {Date d4(2025, 8, 9);Date d5(2025, 8, 10);Date d6(2025, 8, 11);d4 = d5;d4 = d5 = d6;//赋值重载拷贝d4.print();d5.print();return 0;
}
取地址运算符重载
1.将const修饰的成员函数称之为const成员函数,const修饰成员函数参数列表的后面。
2.const实际修饰该成员函数隐含的this指针,表面在该成员函数中不能对类的任何成员进行修改。const修饰Date类中的Print成员函数,Print隐含的this指针由Date* const this 变为 const Date* const this
#include<iostream>
using namespace std;
class Date{
public:Date(int year,int month,int day) {_year = year;_month = month;_day = day;}// void Print(const Date* const this) constvoid print() const {cout << _year << "/" << _month << "/" << _day << endl;}private:int _year;int _month;int _day;};
int main(){// 这⾥⾮const对象也可以调⽤const成员函数是⼀种权限的缩⼩Date d1(2024, 7, 5);d1.Print();const Date d2(2024, 8, 5);d2.Print();return 0;}
3.取地址运算符重载分为普通取地址运算符和const取地址运算符重载,一般这两个函数编译器会自动生成。
class Date{
public :Date* operator&(){return this;// return nullptr;}const Date* operator&()const{return this;// return nullptr;}private :int _year ; // 年 int _month ; // ⽉int _day ; // ⽇
};
觉得我回答有用的话,记得点个关注哟!谢谢支持!