当前位置: 首页 > news >正文

【C++】类和对象(中)

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

文章目录

  • 前言
  • 一、pandas是什么?
  • 二、使用步骤
    • 1.引入库
    • 2.读入数据
  • 总结


前言

我们前面了解了类的基本知识,其中提到了成员函数,现在我们需要具体了解类中默认成员函数。


一、类的6个默认成员函数

如果一个类中一个成员都没有,简称为空类。

那么空类中真的什么都没有吗?并不是,任何类在什么都不写时,编译器会自动生成以下六个默认成员函数。

默认成员函数:用户没有显示实现,编译器会生成的成员函数称为默认成员函数。

二、构造函数

2.1 概念

 对于下面这个Date类:

class Date
{
public:void Init(int year, int month, int day){_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.Init(2022, 7, 5);d1.Print();Date d2;d2.Init(2022, 7, 6);d2.Print();return 0;
}

我们可以通过Init函数去给对象设置日期,但如果每次创建对象时都调用该方法设置日期,未免有点太麻烦了,那能否在对象创建时,就将信息设置进去呢?构造函数就是解决这个问题的。

构造函数是一个特殊的成员函数名字与类名相同,创建类类型对象时由编译器自动调用,以保证每个数据成员都有一个合适的初始值,并且在对象整个生命周期内只调用一次

 2.2 特性

构造函数是特殊的成员函数,需要注意的是,构造函数名称虽然叫做构造,但是构造函数的主要任务并不是开辟空间创建对象,而是初始化对象

其特性如下:

  1. 函数名和类名相同
  2. 无返回值(连void也没有,返回值处无用写任何东西)
  3. 对象实例化时编译器自动调用对应的构造函数
  4. 构造函数可以重载

 

class Date
{
public:// 构造函数只有函数名,参数,函数体,没有返回值// 无参的构造函数Date(){}// 有参数的构造函数Date(int year, int month, int day){_year = year;_month = month;_day = day;}void Print(){cout << _year << "-" << _month << "-" << _day << endl;}
private:int _year;int _month;int _day;
};int main()
{Date d1; // 调用无参的构造函数Date d2(2025, 7, 24); // 调用有参数的构造函数// 注意:如果通过无参构造函数创建对象时,对象后面不用跟括号,否则就成了函数声明// 下面这个代码,声明了d3函数,该函数无参,返回一个Date类型的对象Date d3();return 0;
}

 

5. 如果类中没有显示定义构造函数,则C++编译器会自动成成一个无参的默认构造函数,一旦用户显示定义编译器就不再生成。

class Date
{
public:// 这里显示生成了含参的构造函数,编译器不再生成默认构造函数Date(int year, int month, int day){_year = year;_month = month;_day = day;}void Print(){cout << _year << "-" << _month << "-" << _day << endl;}
private:int _year;int _month;int _day;
};int main()
{// 编译器没有生成默认构造函数,也就没有无参的构造函数,d1这里就会报错Date d1; Date d2(2025, 7, 24); return 0;
}

 

 

6. C++把类型分成内置类型(基本类型)和自定义类型。内置类型就是语言提供的数据类型,如:int / char...,自定义类型就是我们使用class / struct / union等自己定义的类型,看看下面的程序,就会发现编译器生成默认的构造函数会对自定类型成员_t调用的它的默认成员函数。 

class Time
{
public:Time(){cout << "Time()" << endl;_hour = 0;_minute = 0;_second = 0;}
private:int _hour;int _minute;int _second;
};
class Date
{
private:// 基本类型(内置类型)int _year;int _month;int _day;// 自定义类型Time _t;
};
int main()
{Date d;return 0;
}

 

注意:C++11中针对内置类型成员不初始化的缺陷,打了个补丁,即:内置类型成员变量在类中声明时可以给默认值。 

 

class Date
{
private:// 基本类型(内置类型)int _year = 2025;int _month = 7;int _day = 34;// 自定义类型Time _t;
};

7. 无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个。注意:无参构造函数,全缺省构造函数和我们没写有编译器默认生成的构造函数,都可以认为是默认构造函数。

class Time
{
public:// 无参构造函数Time(){cout << "Time()" << endl;_hour = 0;_minute = 0;_second = 0;}// 全缺省构造函数Time(int hour = 19, int minute = 44, int second = 33){_hour = hour;_minute = minute;_second = second;}
private:int _hour;int _minute;int _second;
};
class Date
{
private:// 基本类型(内置类型)int _year = 2025;int _month = 7;int _day = 34;// 自定义类型Time _t;
};
int main()
{Date d;return 0;
}

 

三、析构函数

3.1 概念

 通过前面构造函数的学习,我们知道了一个对象是怎么来的,那一个对象又是怎么没的呢?

析构函数:与构造函数功能相反,析构函数不是完成对对象本身的销毁,局部对象销毁工作是由编译器完成的。而对象销毁时会自动调用析构函数,完成对象中资源的清理工作

3.2 特性

析构函数也是特殊的成员函数,其特性如下:

1. 析构函数名是在类名前面加上符号~

2. 无参数无返回值类型

3. 一个类只能有一个析构函数。若未显示定义,编译器会自动生成默认的析构函数。注意:析构函数不能重载。

4. 对象生命周期结束时,C++编译器自动调用析构函数 

typedef int DataType;
class Stack
{
public:Stack(size_t capacity = 3){_array = (DataType*)malloc(sizeof(DataType) * capacity);if (NULL == _array){perror("malloc申请空间失败!!!");return;}_capacity = capacity;_size = 0;}void Push(DataType data){// CheckCapacity();_array[_size] = data;_size++;}// 其他方法...~Stack(){cout << "~Stack()" << endl;if (_array){free(_array);_array = NULL;_capacity = 0;_size = 0;}}
private:DataType* _array;int _capacity;int _size;
};
int main()
{Stack s;s.Push(1);s.Push(2);return 0;
}

 

5. 关于编译器自动生成的析构函数,是否会完成一些事情呢?下面的程序我们会看到,编译器生成的默认析构函数,对自定义类型成员调用它的析构函数。

class Time
{
public:// 全缺省构造函数Time(int hour = 19, int minute = 44, int second = 33){_hour = hour;_minute = minute;_second = second;}~Time() {cout << "~Time()" << endl;}
private:int _hour;int _minute;int _second;
};
class Date
{
private:// 基本类型(内置类型)int _year = 2025;int _month = 7;int _day = 34;// 自定义类型Time _t;
};
int main()
{// Date生成默认的析构函数,这个析构函数调用了Time的析构函数Date d;return 0;
}

 

6. 如果类中没有申请资源时,析构函数可以不写,直接使用编译器生成的默认析构函数,比如Date类;有资源申请时一定要写,否则会造成资源泄漏,比如Stack类。 

四、拷贝构造函数

4.1 概念

在现实生活,会存在两个人十分相似,我们一般称其为双胞胎。 

那么在创建对象时,可否创建一个与已存在的对象一模一样的新对象呢?

拷贝构造函数:只有一个形参,该形参是对本类类型对象的引用(一般常用const修饰),在用已存在的类类型对象创建新对象时有编译器自动调用。

4.2 特性

其特性如下:

1. 拷贝构造函数是构造函数的一种重载形式

2. 拷贝构造函数的参数只有一个且必须是类类型对象的引用,使用传值方式编译器直接报错,因为会引发无穷递归调用

class Date
{
public:Date(int year = 2025, int month = 7, int day = 24){_year = year;_month = month;_day = day;}// Date(const Date d)   // 错误写法:编译报错,会引发无穷递归Date(const Date& d)   // 正确写法{_year = d._year;_month = d._month;_day = d._day;}
private:// 基本类型(内置类型)int _year;int _month;int _day;// 自定义类型Time _t;
};

 

 3. 若未显示定义,编译器会生成默认的拷贝构造函数。默认的拷贝构造函数对象按内存存储按字节序完成拷贝,这种拷贝叫做浅拷贝,也叫做值拷贝

class Time
{
public:// 全缺省构造函数Time(int hour = 19, int minute = 44, int second = 33){_hour = hour;_minute = minute;_second = second;}Time(const Time& t){_hour = t._hour;_minute = t._minute;_second = t._second;cout << "Time(const Time& t)" << endl;}~Time() {cout << "~Time()" << endl;}
private:int _hour;int _minute;int _second;
};
class Date
{
public:Date(int year = 2025, int month = 7, int day = 24){_year = year;_month = month;_day = day;}
private:// 基本类型(内置类型)int _year;int _month;int _day;// 自定义类型Time _t;
};
int main()
{Date d1;// Date没有显示定义拷贝构造函数,编译器自动生成默认拷贝构造函数// 默认拷贝构造函数对于自定义类型会去调用它的拷贝构造函数Date d2(d1);return 0;
}

 

4. 编译器生成的默认拷贝构造函数已经可以完成字节序的值拷贝了,还需要自己显示实现吗?当然,对于日期类的不需要显示实现,但是下面这个呢?

typedef int DataType;
class Stack
{
public:Stack(size_t capacity = 3){_array = (DataType*)malloc(sizeof(DataType) * capacity);if (NULL == _array){perror("malloc申请空间失败!!!");return;}_capacity = capacity;_size = 0;}void Push(DataType data){// CheckCapacity();_array[_size] = data;_size++;}// 其他方法...~Stack(){cout << "~Stack()" << endl;if (_array){free(_array);_array = NULL;_capacity = 0;_size = 0;}}
private:DataType* _array;int _capacity;int _size;
};
int main()
{Stack s;s.Push(1);s.Push(2);Stack s1(s);// 这里我们想要s1后面有数字3,s后面只有数字1,2// 但实际会是这样吗s1.Push(3);return 0;
}

 

我们能看到s和s1的_array都指向了同一块空间,所以只要发生数据修改就会影响到其他对象,这不符合我们的需求。

注意:类中如果没有涉及资源申请时,拷贝构造函数是否写都可以;一旦涉及到资源申请时,则拷贝构造函数是一定要写的,否则就是浅拷贝。 

五、赋值运算符重载

5.1 运算符重载

C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有其返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。 

函数名字为:关键字operator后面接需要重载的运算符符号

函数原型:返回值类型 operator操作符(参数列表)

注意:

  • 不能通过连接其他符号来创建新的操作符,比如:operator#
  • 重载操作符必须有一个类类型参数
  • 用于内置类型的运算符,其含义不能改变,例如:内置的整型+,不能改变其含义
  • 作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐藏的指针this
  • .*   ::   sizeof   ?:   .   注意以上5个运算符不能重载
class Date
{
public:Date(int year = 2025, int month = 7, int day = 24){_year = year;_month = month;_day = day;}bool operator==(const Date& d){if (_year == d._year && _month == d._month && _day == d._day)return true;else return false;}
private:// 基本类型(内置类型)int _year;int _month;int _day;// 自定义类型Time _t;
};
int main()
{Date d1;Date d2(d1);cout << d1.operator==(d2) << endl;return 0;
}

 

5.2 赋值运算符重载

1. 赋值运算符重载格式

  • 返回值类型:T&,返回引用可以提高返回的效率,有返回值目的是为了支持连续赋值
  • 参数类型:const T&,传递引用可以提高传递效率
  • 检测是否自己给自己赋值
  • 返回*this:要符合连续赋值的含义 
class Date
{
public:Date(int year = 2025, int month = 7, int day = 24){_year = year;_month = month;_day = day;}bool operator==(const Date& d){if (_year == d._year && _month == d._month && _day == d._day)return true;else return false;}Date& operator=(const Date& d){if (this != &d) {_year = d._year;_month = d._month;_day = d._day;}return *this;}
private:// 基本类型(内置类型)int _year;int _month;int _day;// 自定义类型Time _t;
};

 2. 赋值运算符只能重载成类的成员函数,不能重载成全局函数

// 定义为全局函数,没有了this指针,需要给两个参数
Date& operator=(Date& d1, Date& d2)
{if (&d1 != &d2) {d1._year = d2._year;d1._month = d2._month;d1._day = d2._day;}return d1;
}

 

原因:赋值运算符如果不显式实现,编译器会生成一个默认的。此时用户再在类外自己实现一个全局的赋值运算符重载,就和编译器在类中生成的默认赋值运算符重载冲突了,故赋值运算符重载只能是类的成员函数。 

3. 用户没有显示实现时,编译器会生成一个默认的,以值得方式逐字节拷贝。 注意:内置类型成员变量是直接赋值的,而自定义类型成员变量需要调用对应类的赋值运算符重载完成赋值。

class Time
{
public:// 全缺省构造函数Time(int hour = 19, int minute = 44, int second = 33){_hour = hour;_minute = minute;_second = second;}Time(const Time& t){_hour = t._hour;_minute = t._minute;_second = t._second;cout << "Time(const Time& t)" << endl;}Time& operator=(const Time& t){cout << "Time& operator=(const Time& t)" << endl;if (this != &t) {_hour = t._hour;_minute = t._minute;_second = t._second;}return *this;}~Time() {cout << "~Time()" << endl;}
private:int _hour;int _minute;int _second;
};
class Date
{
public:Date(int year = 2025, int month = 7, int day = 24){_year = year;_month = month;_day = day;}bool operator==(const Date& d){if (_year == d._year && _month == d._month && _day == d._day)return true;else return false;}
//private:// 基本类型(内置类型)int _year;int _month;int _day;// 自定义类型Time _t;
};
int main()
{Date d1;Date d2;d2 = d1;return 0;
}

 

默认的赋值运算符重载也是逐字节,跟拷贝构造函数一样,要注意的也一样。

注意:类中如果没有涉及资源申请时,赋值运算符重载是否写都可以;一旦涉及到资源申请时,则赋值运算符重载是一定要写的,否则就是浅拷贝。 

5.3 前置++和后置++重载 

class Date
{
public:Date(int year = 2025, int month = 7, int day = 24){_year = year;_month = month;_day = day;}bool operator==(const Date& d){if (_year == d._year && _month == d._month && _day == d._day)return true;else return false;}// 前置++// this指针指向的对象不会销毁,这里用引用增加效率Date& operator++(){_day++;return *this;}// 后置++// 前置++和后置++都是一元运算符,为了能让这两个正确的重载// C++规定:后置++重载多增加一个int参数,但调用函数时该参数不用传递,编译器自动传递// 后置++是先使用再+1,所以需要传递旧值,这就需要先保存旧值,然后再+1,这里就不能直接返回*thisDate& operator++(int){Date tmp(*this);_day++;return tmp;}
//private:// 基本类型(内置类型)int _year;int _month;int _day;// 自定义类型Time _t;
};
int main()
{Date d;Date d1(2025, 7, 24);d = d1++;   // d: 2025,7,24   d1:2025,7,25d = ++d1;   // d: 2025,7,26   d1:2025,7,26return 0;
}

六、const成员函数

const修饰的“成员函数”称为const成员函数, const修饰类成员函数,实际修饰该成员函数隐含的this指针,表明在该成员函数中不能对类的任何成员进行修改

 

我们来看看这个代码:

class Date
{
public:Date(int year, int month, int day){_year = year;_month = month;_day = day;}void Print(){cout << "Print()" << endl;cout << "year:" << _year << endl;cout << "month:" << _month << endl;cout << "day:" << _day << endl << endl;}void ConstPrint() const{cout << "Print()const" << endl;cout << "year:" << _year << endl;cout << "month:" << _month << endl;cout << "day:" << _day << endl << endl;}void test(){ConstPrint();}void consttest() const{Print();}
private:int _year; // 年int _month; // 月int _day; // 日
};
int main()
{Date d1(2025, 7, 24);d1.ConstPrint();const Date d2(2025, 7, 24);d2.Print();
}

 这里有几个问题:

  1. const对象可以调用非const成员函数吗?
  2. 非const对象可以调用const成员函数吗?
  3. const成员函数内可以调用其他非const成员函数吗?
  4. 非const成员函数内可以调用其他const成员函数吗?

 1和3是可以的,2和4不可以。这就跟const变量可以被非const变量赋值,但非const变量不能被const变量赋值一样。

七、取地址及const取地址操作符重载

class Date
{
public:Date(int year, int month, int day){_year = year;_month = month;_day = day;}Date* operator&(){return this;}const Date* operator&() const{return this;}
private:int _year; // 年int _month; // 月int _day; // 日
};

这两个运算符一般不需要重载,使用编译器生成的默认取地址的重载即可,只有特殊情况才需要重载,比如想让别人获取到指定的内容。 


总结

这篇文章我们了解了六大默认成员函数中最常用的4个:构造函数,析构函数,拷贝构造函数,赋值运算符重载,并了解了const在类中的一些用处,希望对大家有所帮助。

http://www.dtcms.com/a/296515.html

相关文章:

  • 【三桥君】Windows系统如何临时关闭“Windows安全中心实时保护”?解析Win10和Win11关闭方法
  • C++中std::string和std::string_view使用详解和示例
  • Lua(字符串)
  • 生成式人工智能展望报告-欧盟-03-经济影响
  • pyautogui 置信度问题
  • 拖拽同时支持Y轴滚动
  • 开立医疗2026年校园招聘
  • openbmc 日志系统继续分析
  • 行为型模式-协作与交互机制
  • 华为仓颉编程语言的表达式及其特点
  • mac llama_index agent算术式子计算示例
  • 力扣刷题(第九十七天)
  • 强化学习入门三(SARSA)
  • 专题:2025微短剧行业生态构建与跨界融合研究报告|附100+份报告PDF汇总下载
  • LeetCode 1695.删除子数组的最大得分:滑动窗口(哈希表)
  • 07 RK3568 Debian11 网络优先级
  • “抓了个寂寞”:一次实时信息采集的意外和修复
  • 网络基础19--OSPF路由协议(上)
  • 基于QT(C++)实现(图形界面)通讯录系统
  • JavaJSP
  • 【SpringAI实战】FunctionCalling实现企业级自定义智能客服
  • Qt 调用ocx的详细步骤
  • 单片机学习课程
  • 数据推荐丨海天瑞声7月数据集上新啦!
  • 海外红人营销的下一站:APP出海如何布局虚拟网红与UGC生态?
  • idea监控本地堆栈
  • Redis分布式锁的学习(八)
  • 无源域自适应综合研究【2】
  • Qt连接MySql数据库
  • SAP B1 DTW成功登录后点击下一步提示没有权限读取清单