2024年三部曲深剖C++类与对象——中篇(1),2024年最新阿里专家原创

img
img

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

}

private:
int a;
};
int main()
{
Data* n = nullptr;
n->print();//访问函数
n->printa();//访问成员变量
return 0;
}


结果如图:  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/fd71448d6f9e4cb0b4233f48eb576768.png#pic_center)  
 没错,程序崩溃辣!但是很明显,hello 打印出来了就说明函数的访问是没有问题的,但是是没有办法访问成员变量的。


我们说类里面用空指针访问函数,成员变量结果会不同,原因就是函数在公共代码区,不需要解引用,直接找到函数地址变成 call 地址即可,而成员变量的访问需要解引用自然空指针就会寄。


空指针 nullptr 其实并不是真的“空”,实际上是真实存在的,他指向虚拟进程空间里面地址为 0 的地方,这个 0 地址处是用来程序初始化的,是预留出来的,并不是用来存储数据的。因此空指针一旦指向数据,这个数据就是不被认可的,没有意义的。  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/ade0e63f70b34821ab3bba74bf8a8368.png#pic_center)


## 类的默认成员函数😎


类里面什么都没有,就称它为空类,实际上空类中真的什么都没有吗?答案是 NO!任何一个类在默认情况下都会生成 6 个成员函数。


![在这里插入图片描述](https://img-blog.csdnimg.cn/4881e6ac1ced46a09a437c84f87ff1e5.png#pic_center)


### 构造函数🤔


构造函数是特殊的成员函数,需要注意的是,构造函数虽然名叫构造,但他的主要任务并不是开辟空间或者创建对象,而是将对象初始化。我们不写,编译器也会生成一个默认无参数的构造函数,但这个默认的构造函数不一定有用,而 C++11打的补丁,针对编辑器自己生成的默认成员函数不初始化的问题,给了缺省值来供默认构造函数使用。


需要注意的是:


1. 类名与函数名保持一致
2. 可以不用传参,没有返回值
3. 对象实例化时编辑器自动调用对应的构造函数
4. 构造函数支持函数重载
5. 如果类中没有显式定义构造函数,C++编译器会自动生成一个无参的默认构造函数,如果我们自己显式定义了就不会给出了
6. 无参的构造函数和全缺省的构造函数都被称为默认构造函数,并且默认构造函数只能有一个


### 意义🤔


C++将变量分为两种:内置类型(int,char,指针类型等等)和自定义类型(struct/class 去定义的类型对象),默认生成的构造函数对于内置类型成员变量不做处理,对自定义类型才进行处理。而这正好就是 C++ 语法设计的一个败笔,他会导致


如果有内置类型的成员就得自己写构造函数,比如:



class Stack
{
public:
void push(int x){
}
void pop(){
}
private:
Stack stackpush;
Stack stackpop;
}


该类里面只有自定义类型成员变量,就不需要去写构造函数,默认的构造函数就可以完成了。总结一下就是如果类里面只有自义定类型,就可以用默认构造函数,如果存在内置类型或者需要显示传参初始化就需要自己写构造函数。


### 析构函数🤔


如果构造函数高速了我们对象是怎么来的,那么析构函数就是在告诉我们对象是怎么走的。析构函数与构造函数功能相反,他并不是完成了对象的销毁,因为局部对象销毁是由编译器来完成,而析构函数是完成对象的一些资源清理工作,他是在对象销毁时自动调用。  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/309221bf2a514b45b79c4a4cd0cac450.png#pic_center)


再三强调不是销毁对象本身!不是销毁对象本身!所谓的资源清理针对的对象是 malloc,new 或者 fopen 这类的操作进行清理收尾,其实本质上就相当于我们之前的 destroy 函数。


其特征如下:


1. 析构函数名是在类名前面加上字符 -
2. 无参数也无返回值
3. 一个类有且仅有一个析构函数,如果没有显式定义,系统会自动生成析构函数
4. 对象声明周期结束时,C++编译系统自动调用析构函数


但是注意一个顺序问题:



int main()
{
Stack st1;
Stack st2;

return 0;
}


st1 相比 st2 先构造这没什么问题,但 st2 却比 st1 先析构,析构和构造的顺序是相反的。但他和构造函数一样,对内置类型不处理但是对于自定义类型会去调用。


### 拷贝构造🤔


有没有可能你会想搞一个和自己一样的对象出来呢?如果想那就该我拷贝构造登场辣!



int main()
{
Date d1(2022,5,16);
Date d2(d1);

 return 0;

}


这里的 d2 就是拿 d1 来初始化,所以拷贝构造只有单个形参,该形参是对类类型对象的引用,一般由 const 修饰,再用已存在的类类型对象创建新对象时由编译器自动调用。他实际上是构造函数的一种函数重载,他的参数只要一个,而且必须使用引用传参,因为使用传值会引发无穷递归调用。


在什么情况下系统会调用拷贝构造函数:


(1)用类的一个对象去初始化另一个对象时


(2)当函数的形参是类的对象时(也就是值传递时),如果是引用传递则不会调用


(3)当函数的返回值是类的对象或引用时


C++规定了自义定类型对象,拷贝初始化要调用拷贝构造完成。这就引出来一个问题,比如我们拷贝栈,定义了两个对象 st1 和 st2,假如我写成下面的样子就会出乱子:



Stack st1(1);
Stack st2(st1);


因为是拷贝构造, st2 指向地址和 st1 是一样的,这并不是我们想要的结果,我们传统拷贝是指向同一块空间,而且这里拷贝构造会崩,原因很简单,这里原理上进行的是浅拷贝,默认构造函数会对类里面内置类型进行浅拷贝,值拷贝,对于自义定类型,编译器不知道自义定类型的行为,如何拷贝,什么规则,像 stack 这种类型就需要深拷贝实现,我们后面再去学习。



> 
> 浅拷贝是按位拷贝对象,它会创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值;如果属性是内存地址(引用类型),拷贝的就是内存地址 ,因此如果其中一个对象改变了这个地址,就会影响到另一个对象。
> 
> 
> 


st1 构建完成变成 st2 参数去初始化 st2,st2 一旦拷贝完成就要进行析构,而析构会先执行 st2 ,st2 一旦被清理这块空间就会被销毁,而此时 st1 还在使用这块空间,就会导致内存错误。


![在这里插入图片描述](https://img-blog.csdnimg.cn/9a7d505b3722472a8a0b94679e799b4b.png#pic_center)


### 运算符重载🤔


说到运算符重载,这里面有几分门道嗷。他是个啥呢?



> 
> 您可以重定义或重载大部分 C++ 内置的运算符,这样就能使用自定义类型的运算符。重载的运算符是带有特殊名称的函数,函数名是由关键字 operator 和其后要重载的运算符符号构成的。与其他函数一样,重载运算符有一个返回类型和一个参数列表。
> 
> 
> 


说的比较玄学,其实你设想一下这个场景,假如我们给出的日期类有两个成员 d1,d2,我们如果去比较他们能用运算符 ==,<,> 来比较吗,或者我给日期 +、- 一个数可以吗,很明显不行,因为内置类型可以直接使用运算符,但是自义定类型不可以,因此就引入了我们的运算符重载。


运算符重载——函数,函数名:operator +运算符,参数是运算符的操作数,如下:



operator==(Date d1,Date d2)
{
return d1.year == d2.year
&& d1.month == d2.month
&& d1.day == d2.day
}


细心的你可能会问,内置类型我定义在私有域里面该怎么访问呢?这就有三种方法:


1. 从根源解决,改成公有类型public(尬活了属于是)
2. 再写一个公有域的函数来调用私有域的内置类型成员;
3. C++ 友元(后续会学习到)


先别觉得ez,因为这里他会报错  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/80ebdf86435d4fb39c3cb3b0b293296d.png#pic_center)


==是要求的两个参数,我这里也是两个参数没错啊,为什么会参数太多啊?别忘了,操作符还有 默认的形参 this,他被限定为第一个形参,因此我们只需要写一个参数即可:



operator==(Date d)
{
return year == d.year
&& month == d.month
&& day == d.day
}


编译器遇到 if(d1 == d2) 这样的语句,就会去处理成对应的重载运算符调用 if(d1.operator(d2)),这里编译器时很聪明的,你写的全局他会去调用全局,你写的成员他会去调用成员。而且遇到运算符重载他会在优先去类里面找,没有才回去类外面找,也就是说类的里外同时存在运算符重载函数是可以编译过去的。


注意一下


1. ::
2. sizeof
3. ?:
4. .
5. .\*


这五个操作符是不能进行重载的,在选择题中会经常出现。



![img](https://img-blog.csdnimg.cn/img_convert/3562e70626a5545e3beb351d8cdd08d7.png)
![img](https://img-blog.csdnimg.cn/img_convert/f431d72284190b97f7a31ef4ce3d4e62.png)

**既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上C C++开发知识点,真正体系化!**

**由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新**

**[如果你需要这些资料,可以戳这里获取](https://bbs.csdn.net/topics/618668825)**

715592664096)]
[外链图片转存中...(img-hUtrkyYu-1715592664097)]

**既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上C C++开发知识点,真正体系化!**

**由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新**

**[如果你需要这些资料,可以戳这里获取](https://bbs.csdn.net/topics/618668825)**

  • 3
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值