目录

类的6个默认成员函数

一、构造函数

二、析构函数

三、拷贝构造函数

四.赋值运算符的重载

4.1运算符重载

4.2赋值运算符的重载

五、const成员

5.1 const修饰类的成员函数

六、取地址以及const取地址操作符重载


类的6个默认成员函数

  如果一个类中什么成员都没有,简称为空类。空类中什么都没有吗?并不是的,任何一个类在我们不写的情况下,都会自动生成下面6个默认成员函数
class Date {}; //空类

注意:这是6个特殊的成员函数

一、构造函数

概念:构造函数虽然名字叫做构造,但是它并不是开空间创造对象,而是初始化对象

特征如下:

1、函数名和类名相同

2、无返回值,并不是void

3、对象实例化的时候自动调用对应的构造函数

4、构造函数可以构成重载

class Date
{
public:Date()//1、无参构造函数{_year = 2000;_mouth = 2;_day = 5;}Date(int year, int mouth,int day)//2、带参构造函数(可以是缺省的参数){_year = year;_mouth = mouth;_day = day;}
//以上两个函数名字相同,但是参数不同,构成了函数重载private:int _year;int _mouth;int _day;
};
int main()
{Date d1;//这里就是具体的对象实例化Date d2(2022,2,12);return 0;
}

5、tips:C++把我们的类型分为了两类

第一类内置类型:int/double/指针类型/内置类型数组等

第二类自定义类型:struct/class定义的类型

特殊的处理规则:

①我们不写编译器默认生成的构造函数,对于内置类型不做处理

②对于自定义类型的成员变量,会去调用它的默认构造函数(就是不用参数就可以调用的成员函数)进行初始化

③如果没有默认构造函数,编译器就会报错

默认构造函数有三类(不用参数就可以调用):全缺省的,无参的,我们不写编译器默认生成的

举例说明:

//A类
class A
{
public:A(){cout << "调用A()" << endl;_a = 0;}//如果是下面的带参的函数,就不是默认构造函数,编译器就会报错/*A(int a){cout << "调用A()" << endl;_a = 0;}*/
private:int _a;
};class Date
{
public://这里不写
private:int _year;int _mouth;int _day;A _aa;//这里对于自定义类型,会去调用A类型的默认构造函数
};
int main()
{Date d1;return 0;
}

运行结果:

说明:对于内置类型不做处理,对于自定义类型A里面_aa成员变量,回去调用A类的默认构造函数,如果没有默认构造函数就会报错

打开监视窗口观察:

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

例如:

class Date
{
public://这里我们不手动写,编译器会默认生成构造函数帮我们完成初始化操作
private:int _year;int _mouth;int _day;
};
int main()
{Date d1;return 0;
}

我们此时打开监视窗口,可以观察到初始化给对象的是随机值,这也印证了我们上面的结论:

需要注意:

class Date
{
public:Date()//1、无参构造函数{_year = 2000;_mouth = 2;_day = 5;}Date(int year=2000, int mouth=2, int day=5)//2、带参构造函数{_year = year;_mouth = mouth;_day = day;}private:int _year;int _mouth;int _day;
};
int main()
{Date d1;//这里就会报错//1、语法上无参和全缺省的可以同时存在//2、但是如果存在无参调用,就会存在歧义(编译器报错)Date d2(2022, 2, 12);return 0;
}

二、析构函数

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

例如:我们动态内存开辟的一些指针,我们需要对其进行释放,这就叫做资源的清理工作

特征:

1、析构函数名个类名相同,且在前加个 ~

2、无参数,无返回值

3、一个类有且只有一个析构函数,若没有显式定义,系统会自动生成默认的析构函数

4、对象生命周期结束时,C++编译系统会自动调用析构函数

class Date
{
public:~Date(){//Date类没有资源需要清理,所以Date不实现析构函数也是可以的cout << "调用 ~Date()" << endl;}
private:int _year;int _mouth;int _day;
};
int main()
{Date d1;return 0;
}

但是我们遇到某些情况下是需要资源清理的:(例如malloc的资源是需要处理的)

class stack
{
public:stack(int capacity = 4){_a = (int*)malloc(sizeof(int) * capacity);if (_a == nullptr){cout << "malloc fail" << endl;exit(-1);}}~stack(){free(_a);_a = nullptr;_top = _capacity = 0;}
private:int* _a;size_t _top;size_t _capacity;};
int main()
{stack s1;stack s2(10);return 0;
}

5、tips:

如果我们不写,默认生成的析构函数和构造函数类似

①对于内置类型的成员变量,不做处理

②对于自定义类型的成员变量,会去调用它的析构函数

我们这里举一个题,用两个栈实现队列,其中默认生成的构造和析构函数就会去调用它的构造和析构:

class stack
{
public:stack(int capacity = 4){_a = (int*)malloc(sizeof(int) * capacity);if (_a == nullptr){cout << "malloc fail" << endl;exit(-1);}}~stack(){free(_a);_a = nullptr;_top = _capacity = 0;}
private:int* _a;size_t _top;size_t _capacity;};//两个栈实现一个队列
class MyQueue
{
public:// 默认生成构造函数和析构函数会对自定义类型成员调用他的构造和析构void push(int x){}
private:stack pushST;stack popST;
};
int main()
{MyQueue q;return 0;
}

三、拷贝构造函数

概念:顾名思义,就是拷贝赋值一个和自己一模一样的对象出来。拷贝构造函数只有单个形参,改形参是对本类类型对象的引用(一般常用const修饰)在用已存在的类类型对象创建新对象时,由编译器自动调用。

#include <iostream>
using namespace std;
class Date
{
public:Date(int year = 1, int month = 1, int day = 1)// 构造函数{_year = year;_month = month;_day = day;}Date(const Date& d)// 拷贝构造函数,{_year = d._year;_month = d._month;_day = d._day;}
private:int _year;int _month;int _day;
};
int main()
{Date d1(2000, 1, 1);Date d2(d1); // 用已存在的对象d1创建对象d2
//用一个同类型对象初始化就是拷贝构造,将d1的内容复制给d2return 0;
}

特征:

1、拷贝构造函数是构造函数的一个重载形式

2、拷贝构造的函数参数只有一个,并且必须使用引用传参,使用传值的方式会引发无穷递归调用

class Date
{
public:Date(int year = 1, int month = 1, int day = 1){_year = year;_month = month;_day = day;}Date(const Date& d){_year = d._year;_month = d._month;_day = d._day;}~Date(){cout << "~Date()" << endl;}
private:int _year;int _month;int _day;
};
int main()
{Date d1;Date d2(d1);//将d1的内容复制给d2//用同一个类型的对象去初始化,就是拷贝构造return 0;
}

为什么这里是引用传参?

date就是d1的别名,避免传值时的拷贝引发无限递归

为什么这里建议加const?

这里是想复制一个与原对象一样的对象出来,我们是想保持原对象的内容不改变所以让原对象具有const常属性

3、如果没有显式定义,系统生成默认的拷贝构造函数

默认生成的拷贝构造函数:

1)内置类型的成员变量:会完成按字节序的拷贝(浅拷贝)

2)自定义类型的成员变量:会调用它的拷贝构造函数

class Date
{
public:Date(int year = 1, int month = 1, int day = 1){_year = year;_month = month;_day = day;}//我们这里不写拷贝构造函数,编译器会自动生成~Date(){cout << "~Date()" << endl;}
private:int _year;int _month;int _day;
};
int main()
{Date d1;Date d2(d1);//将d1的内容复制给d2return 0;
}

打开监视窗口观察两个对象:

可以发现发生了浅拷贝(值拷贝),拷贝出了一个内容完全相同的d2。

我们自己这里并没有定义拷贝构造函数,但是编译器自动生成的拷贝构造函数还是完成了对象的拷贝任务。

4、编译器自动生成的拷贝构造函数不能完成深拷贝

有些时候,我们不写,编译器自动生成的拷贝构造函数就足够用了,但是编译器自动生成的拷贝构造函数不能完成深拷贝,举个例子,当我们需要拷贝构造一个栈出来时,对于默认生成的拷贝构造函数就不能用

class Stack
{
public:Stack(int capacity = 4){_ps = (int*)malloc(sizeof(int)* capacity);_size = 0;_capacity = capacity;}void Print(){cout << _ps << endl;// 打印栈空间地址}
private:int* _ps;int _size;int _capacity;
};
int main()
{Stack stack1;stack1.Print();// 打印stack1栈空间的地址Stack stack2(stack1);// 用已存在的对象stack1创建对象stack2stack2.Print();// 打印s2栈空间的地址return 0;
}

解析:如果我们的拷贝构造还是按字节序的浅拷贝,拷贝出来就会将开辟栈1的地址复制给栈2,这样两个指针就会指向堆区的同一块区域,这样的话就用出现两个栈同时操作会相互影响,当stack2析构完后,stack1也对同一块区域析构,这是不允许的,会导致程序崩溃。正确的是,我们自己写栈的拷贝构造,当程序运行结束,stack2栈将先被析构,此时那块栈空间被释放,然后stack1栈也要被析构,再次对那一块空间进行释放。

四.赋值运算符的重载

4.1运算符重载

默认C++是不支持自定义类型对象使用运算符的,为了让他们可以运算(比大小之类的),提出来运算符重载,运算符重载是具有特殊函数名的函数,其目的就是让自定义类型可以像内置类型一样可以直接使用运算符进行操作

特征:

1、具有其返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似

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

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

4、参数:操作符有几个操作数,它就有几个参数

注意:

1)不能通过连接其他符号来创建新的操作符:比如operator@

2)重载操作符必须有一个类类型或者枚举类型的操作数

3)用于内置类型的操作符,其含义不能改变,例如:内置的整型+,不能改变其含义

作为类成员的重载函数时,其形参看起来比操作数数目少1成员函数的操作符有一个默认的形参this,限定为第一个形参

4).* 、:: 、sizeof 、?: 、. 注意以上5个运算符不能重载。这个经常在笔试选择题中出现。

我们要实现运算符重载,我们首先来举例看看全局的 operator>

#include<iostream>
using namespace std;
class Date
{
public:Date(int year = 1, int month = 1, int day = 1){_year = year;_month = month;_day = day;}
//private:     //这里需要放开成员变量,会破坏程序的封装性int _year;int _month;int _day;
};
bool operator>(const Date& d1, const Date& d2)
{if (d1._year > d2._year)return true;else if (d1._year == d2._year && d1._month > d2._month)return true;else if (d1._year == d2._year && d1._month == d2._month && d1._day > d2._day)return  true;elsereturn false;
}int main()
{Date d1(2022,2,1);Date d2(d1);Date d3(2000, 1, 1);cout <<(d1 > d3 )<< endl;cout << (operator>(d1,d3)) << endl;//将d1的内容复制给d2return 0;
}

这里会发现运算符重载成全局的就需要成员变量公有的,那么就会破坏程序的封装性了。

其实我们可以将重载写在类里面:

#include<iostream>
using namespace std;
//重载成员函数
class Date
{
public:Date(int year = 1, int month = 1, int day = 1){_year = year;_month = month;_day = day;}bool operator>(const Date& d)//成员函数默认有一个隐藏的this指针{if (_year > d._year)return true;else if (_year == d._year && _month > d._month)return true;else if (_year == d._year && _month == d._month && _day > d._day)return  true;elsereturn false;}
private:int _year;int _month;int _day;
};
int main()
{Date d1(2022, 2, 1);Date d2(d1);Date d3(2000, 1, 1);cout << (d1>d3) << endl;//这样写会被编译器转化为下面这行cout<< d1.operator>(d3)<<endl;return 0;
}

成员函数就会有一个隐藏的this指针

4.2赋值运算符的重载

我们知道:

一个已经存在的对象去初始化一个马上创建市级的对象——拷贝构造

两个已经存在的对象,之间进行赋值操作——赋值拷贝

class Date
{
public:Date(int year = 1, int month = 1, int day = 1){_year = year;_month = month;_day = day;}Date& operator=(const Date& d)//成员函数默认有一个隐藏的this指针,加const保持赋值的那个对象不变,具有常属性{if (this != &d)//预防自己给自己拷贝,没有这个必要{_year = d._year;_month = d._month;_day = d._day;}return *this;//引用返回,减少值拷贝}
private:int _year;int _month;int _day;
};
int main()
{Date d1(2022, 2, 1);Date d2(d1);Date d3(2000, 1, 1);d1 = d3;
//将d3的内容拷贝给d1,将d1的内容给覆盖了return 0;
}

赋值运算符主要特点:

1、参数类型为引用,并且用const修饰

右操作数是已经存在的对象了,我们一般不改变它,所以加个const修饰

2、使用引用返回,为了减少多次拷贝

我们若是d2=d1,这样赋值,完全没有必要有返回值,但是若是连续赋值d3=d2=d1,返回给d2的值要拷贝一份,再赋值给d3,这样最后再拷贝一份返回调用,这样形成了多重拷贝,为了避免不必要的拷贝我们使用引用返回

3、检测是否自己给自己赋值

若是出现d1=d1这种没有不要的赋值,我们可以避免不必要赋值操作

4、return *this

出了作用域*this还存在,*this就是d对象(对象并未销毁),用引用返回减少拷贝

5、一个类中如果没有显示定义赋值运算符重载,编译器会默认生成一个,会完成按对象的字节序拷贝 

编译器默认生成赋值重载,根拷贝构造函数做的事情类似

1)对于内置类型,会完成按字节序的浅拷贝

2)对于自定义类型,会去调用它自己的运算符重载

五、const成员

5.1 const修饰类的成员函数

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

class Date
{
public :void Display (){cout<<"Display ()" <<endl;cout<<"year:" <<_year<< endl;cout<<"month:" <<_month<< endl;cout<<"day:" <<_day<< endl<<endl ;}void Display () const{cout<<"Display () const" <<endl;cout<<"year:" <<_year<< endl;cout<<"month:" <<_month<< endl;cout<<"day:" <<_day<< endl<<endl;}
private :int _year ; // 年int _month ; // 月int _day ; // 日
};
void Test ()
{Date d1 ;//普通对象d1.Display ();const Date d2;//const修饰对象d2.Display ();
}

结论:成员函数加const是好的,建议能加的都加上。这样普通对象和const修饰的对象都可以调用,但是如果要修改成员变量的成员函数是不能加const的

六、取地址以及const取地址操作符重载

这两个默认成员函数一般不用重新定义,编译器默认会生成

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

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

谢谢观看!

C++类和对象(中)相关推荐

  1. 类Array对象中是否该直接使用Array的原型方法?

    题目来自于掘金一位分享者的分享 , 我经实验认证写篇文章记之 var obj={'2' : 3,'3' : 4,'length' : 2,'splice' : Array.prototype.spli ...

  2. 类与对象(中) 构造函数和析构函数

    目录 一.类的6个默认成员函数 二.构造函数 2.1定义 2.2特性 1. 函数名和类名相同: 2. 没有返回值: 3.对象实例化的时候编译器自动调用: 4.可以函数重载. 2.3 无参的构造函数和全 ...

  3. 《C++类和对象中的static、友元以及内存管理 总结》

    学习完前面有关类和对象的基础知识,这里再对其中的某些容易出现的错误及经常性会遇到的问题进行阐述,加深对类和对象的理解是学好C++的基础. 文章目录 1.再次谈及构造函数 2.static成员 3.友元 ...

  4. 【c++师傅领进门,修行靠个人】第五篇:C++类和对象中的一些小细节

    面向对象的总结 1 初始化列表 2 如何突破封装 3 了解静态成员 4 类也能套娃 5 面向对象总结 1 初始化列表 初始化列表:以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个"成 ...

  5. 【类和对象(中)】六大默认成员函数

    文章目录 前言 一.

  6. python的类和对象——类的静态字段番外篇

    什么是静态字段 在开始之前,先上图,解释一下什么是类的静态字段(我有的时候会叫它类的静态变量,总之说的都是它.后面大多数情况可能会简称为类变量.): 我们看上面的例子,这里的money就是静态字段,首 ...

  7. python基础19 -------面向对象终结篇(介绍python对象中各种内置命令)

    一.isinstance()和issubclass()命令 1.isinstance(对象,类型) 用来判定该对象是不是此类型或者说是该对象是不是此类的对象,返回结果为True和False,如图所示. ...

  8. day34-2 类和对象(重点)

    目录 类 定义类和对象 __dict__ 和__class__ 创建对象时的底层运作 定义对象独有的特征 init __slots__(了解) 给对象添加属性时的底层运作 类 分类/类别 上述的代码( ...

  9. Map对象与实体类Object对象转换

    方法一 fastjson 转换 <dependency><groupId>com.alibaba</groupId><artifactId>fastjs ...

  10. python基础----面向对象的程序设计(五个阶段、对小白的忠告、关于OOP常用术语)、类、对象...

    一.面向对象的软件开发有如下几个阶段                                              1.面向对象分析(object oriented analysis ,O ...

最新文章

  1. linux启动sql server数据库,SQL Server数据库启动过程详解及启动不起来的问题分析及解决方法...
  2. MongoDB for C#基础入门
  3. php展开收缩bom表_WordPress实现文章内容收缩和展开功能
  4. ubuntu14.04部署Ganglia
  5. html5贝塞尔,使用HTML5画布绘制贝塞尔曲线
  6. c#string类型探讨
  7. 机器学习十大算法实现代码汇总(python)----线性回归、逻辑回归、决策树、支持向量机、朴素贝叶斯、K邻近算法、K-均值算法、随机森林、降低维度算法、梯度增强算法
  8. Excel统一将小写的金额转为大写汉字金额的操作
  9. Linux驱动(并发):02---编译乱序、执行乱序(屏障API(bm、rmb、wmb、__iormb、__iowmb))
  10. python基础知识学习
  11. fiash星空动画制作_Flash8简单制作酷炫的星空飞越动画特效
  12. 【应用安全】“我的QQ被盗,请大家不要相信任何消息.......”
  13. Win10极简fliqlo时钟屏保(附下载链和安装教程)
  14. HTTP基础知识(仅介绍涉及R语言爬虫部分)
  15. Keil 编译器AC6中的LLVM编译原理
  16. 你永远不知道明天和意外哪个先到来
  17. MyBatis SQL里的大于号、小于号
  18. 数据结构题目收录(一)
  19. NewOJ Week 2题解
  20. U3D插件 CSVSerialize(CSV转Scriptable)

热门文章

  1. 你见过最奇葩的代码提交信息是什么?别再为写commit message头疼了!
  2. Raki的读paper小记:Image as a Foreign Language: BEIT Pretraining for All Vision and Vision-Language Tasks
  3. 计算机修改人类记忆曲线,遗忘曲线
  4. TCP重连重试机制——记一次线上故障排查思考
  5. 云之讯短信接口开发小白教程
  6. 如何查看电脑WIFI密码
  7. Vue-cli构建步骤
  8. r语言remarkdown展示图_为什么Markdown R有较大概率成为科技写作主流? ← 阳志平的个人网站::技术...
  9. Ix Chariot测试路由器流程
  10. mybatis、mysql使用函数实现英文模糊查询中文的功能