C++深入刨析类与对象的使用

目录
  • this指针
  • this指针存放在哪
  • nullptr与类
  • 类的默认成员函数
  • 构造函数
  • 意义
  • 析构函数
  • 拷贝构造
  • 运算符重载

this指针

现在给出一段代码,实现一个普通的日期 date 的打印:

class date
{
public:
	void print()
	{
		cout << year<<' ' << month <<' '<< day << endl;
	}
	void init(int y, int m, int d)
	{
		year = y;
		month = m;
		day = d;
	}
private:
	int year;
	int month;
	int day;
};
int main()
{
	date d1;
	date d2;
	date d3;
	d1.init(2022, 5, 15);
	d2.init(2022, 5, 14);
	d3.init(2022, 5, 13);
	d1.print();
	d2.print();
	d3.print();
	return 0;
}

结果如你所想:

那么问题来了,d1,d2,d3在调用类里面的 print 函数时,并没有指明对象或者给出形参,最后结果却能打印出不同的三个结果,这是为什么呢?、

这就是C++语法中的隐藏的 this 指针这里的 this 指针其实就是隐含的形参,这个形参会对函数进行处理,比如刚刚的 print 函数以及他的调用,他们的真面目其实是这样:

void print(date* this)

{ cout<< this->year << ’ ’ << this->month << ’ ’ << this->day << endl; }

d1->init(&d1,2022,5,15);

也就是说所有成员变量前面都会有一个 this 指针修饰(实际上 this 指针是个 const 类型,指针不能修改但指向内容可以修改),使得形参和实参之间架起一座无形的桥梁,进行连结。值得注意的是,这个过程是编译器在捣鼓实现的,不需要我们自己去搞,要是写的时候强行带上 this 指针反而还会报错。

那数据是如何对应上的呢?换个问题就是 print 每次是怎么样对应上 d1,d2,d3的,其实访问成员变量年月日,并不是在访问 private 里的年月日,private 里只是声明并不存在空间的开辟,访问但是同一个类里面的 init 函数从而访问到成员变量。

this指针存放在哪

this 指针存在寄存器里面!

其实编译器在生成程序时加入了获取对象首地址的相关代码。并把获取的首地址存放在了寄存器ECX中(VC++编译器是放在ECX中,其它编译器有可能不同)。也就是成员函数的其它参数正常都是存放在栈中,而this指针参数则是存放在寄存器中。

类的静态成员函数因为没有this指针这个参数,所以类的静态成员函数也就无法调用类的非静态成员变量。

nullptr与类

如果我定义了一个空值指针,让空值指针分别去访问我类里面的函数与成员变量会怎么样:

class Data
{
public:
	void print()
	{
		cout << "hello" << endl;
	}
	void printa()
	{
		cout << a << endl;
	}
private:
	int a;
};
int main()
{
	Data* n = nullptr;
	n->print();//访问函数
	n->printa();//访问成员变量
	return 0;
}

结果如图:

没错,程序崩溃辣!但是很明显,hello 打印出来了就说明函数的访问是没有问题的,但是是没有办法访问成员变量的。

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

空指针 nullptr 其实并不是真的“空”,实际上是真实存在的,他指向虚拟进程空间里面地址为 0 的地方,这个 0 地址处是用来程序初始化的,是预留出来的,并不是用来存储数据的。因此空指针一旦指向数据,这个数据就是不被认可的,没有意义的。

类的默认成员函数

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

构造函数

构造函数是特殊的成员函数,需要注意的是,构造函数虽然名叫构造,但他的主要任务并不是开辟空间或者创建对象,而是将对象初始化。我们不写,编译器也会生成一个默认无参数的构造函数,但这个默认的构造函数不一定有用,而 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;
}

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

析构函数

如果构造函数高速了我们对象是怎么来的,那么析构函数就是在告诉我们对象是怎么走的。析构函数与构造函数功能相反,他并不是完成了对象的销毁,因为局部对象销毁是由编译器来完成,而析构函数是完成对象的一些资源清理工作,他是在对象销毁时自动调用。

再三强调不是销毁对象本身!不是销毁对象本身!所谓的资源清理针对的对象是 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 还在使用这块空间,就会导致内存错误。

运算符重载

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

您可以重定义或重载大部分 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,因为这里他会报错

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

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

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

注意一下

::

sizeof

?:

.

.*

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

到此这篇关于C++深入刨析类与对象的使用的文章就介绍到这了,更多相关C++类与对象内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • C++中的类与对象深度解析

    目录 初始化列表 引论 初始化列表 explicit关键字 引论 explicit关键字使用 static成员 友元 引论 友元 内部类 基础概念 内部类的使用 补充 析构顺序例题 总结 初始化列表 引论 //初始化列表的引出 class B { public: B() { cout << "我是B的构造函数" << endl; } private: int _b; }; class A { public: A() { cout << "我

  • C++初阶教程之类和对象

    目录 类和对象<上> 1. 类的定义 2. 类的封装 2.1 访问限定修饰符 2.2 类的封装 3. 类的使用 3.1 类的作用域 3.2 类的实例化 4. 类对象的存储 5. this 指针 5.1 this 指针的定义 5.2 this 指针的特性 类和对象<中> 1. 构造函数 1.2 构造函数的定义 2.2 构造函数的特性 2. 析构函数 2.1 析构函数的定义 3. 拷贝构函数 3.1 拷贝构造函数的定义 3.2 拷贝构造函数的特性 4. 运算符重载 4.1 运算符重载的

  • C++深入探究类与对象之友元与运算符重载

    目录 友元 1 全局函数做友元 2 类做友元 3 成员函数做友元 运算符重载 1 加号运算符重载 2 左移运算符重载 3 递增运算符重载 4 赋值运算符重载 5 关系运算符重载 6 函数调用运算符重载 友元 生活中你的家有客厅(Public),有你的卧室(Private),客厅所有来的客人都可以进去,但是你的卧室是私有的,也就是说只有你能进去,但是呢,你也可以允许你的好闺蜜好基友进去. 在程序里,有些私有属性也想让类外特殊的一些函数或者类进行访问,就需要用到友元的技术. 友元的目的就是让一个函数

  • C++深入讲解类与对象之OOP面向对象编程与封装

    目录 1.面向对象编程 2.面向过程性编程和面向对象编程 3.类的引入 4.类的定义 4.1类的两种定义方式 4.1.1声明和定义全部放在类体中 4.2.2.声明和定义不放在类体中 5.类的访问限定符及封装 5.1 访问限定符 5.2封装 6.类的作用域 7.类的实例化 8.类对象模型 如何计算类对象的大小 面向过程编程也叫结构化编程.虽然结构化编程的理念提高了程序的清晰度,可靠性,并且方便维护.但它再编写大型的程序时,仍然面临这巨大的挑战,OOP(面向对象编程)提供了一种新的方法.与强调算法的

  • C++深入探究类与对象之对象模型与this指针使用方法

    目录 C++对象模型和this指针 1 成员变量和成员函数分开存储 2 this指针概念 3 空指针访问成员函数 4 const修饰成员函数 C++面向对象的三大特性为:封装.继承.多态 C++认为万事万物都皆为对象,对象上有其属性和行为 例如: ​ 人可以作为对象,属性有姓名.年龄.身高.体重…,行为有走.跑.跳.吃饭.唱歌… ​ 车也可以作为对象,属性有轮胎.方向盘.车灯…,行为有载人.放音乐.放空调… ​ 具有相同性质的对象,我们可以抽象称为类,人属于人类,车属于车类 C++对象模型和th

  • C++类和对象深入探索之分文件编写点和圆的关系详解

    目录 创建圆心类 创建圆类 判断点圆关系函数 最终实现 总结 上一篇封装直达 创建圆心类 point.h #pragma once #include<iostream> using namespace std; //创建圆心类 class Point { public: void setM_x(int x); int getM_x(); void setM_y(int y); int getM_y(); private: int m_x; int m_y; }; 把圆心的横纵坐标设为私有,公共

  • C++浅析类与对象的基础

    目录 面向过程和面向对象 类的引入 访问限定符 封装 类的作用域 类的实例化 面向过程和面向对象 类和对象是 C++ 的核心特性 我们之前的C语言就属于面向过程,关注过程,分析求解问题的步骤再通过函数调用解决问题:而现在C++是基于面向对象,关注对象,将一个问题拆分成不同对象,依靠对象之间的交互完成. 比如有一个图书馆系统,用C语言面向过程思路就是:统计图书,图书分类,同步上架图书数据,记录借阅信息.而面向对象我们会创建两个类,图书馆和用户,我们关心的是图书馆和用户之间的关系,再分别实现交互,这

  • C++深入探索类和对象之封装及class与struct的区别

    目录 封装的意义 访问权限 class和struct的区别 成员属性私有 案例练习 封装的意义 封装是C++三大面向对象之一 意义: 1.设计类的时候,属性和行为写在一起,表现事物 2.类在设计时,可以把属性和行为放在不同的权限下,加以控制 语法:class 类名 {访问权限:属性 / 行为}: 示例1:设计一个直角三角形,并求解他的面积 #include<iostream> using namespace std; //class代表设计一个类,后面跟着的是类名 class taiAngle

  • C++学习笔记之类与对象详解

    目录 前言: 1.访问限定符: [问题]C++中 struct和class的区别是什么? 2.封装 [问题]在类和对象的阶段,我们只研究类的封装特性,那什么是封装呢? 3.类的定义与声明 [问题]函数调用的问题 4.类的作用域 5.类的实例化 6.类的分类 总结 前言: 1.C 语言是面向过程的,关注的是过程,分析出求解的步骤,通过函数逐步调用解决问题. 2.C++是基于面向对象的,关注的是对象,蒋一件事情拆分成不同的对象,靠对象之间的交互完成. 举个例子:外卖系统 面向过程是下单.接单.送餐的

  • C++深入刨析类与对象的使用

    目录 this指针 this指针存放在哪 nullptr与类 类的默认成员函数 构造函数 意义 析构函数 拷贝构造 运算符重载 this指针 现在给出一段代码,实现一个普通的日期 date 的打印: class date { public: void print() { cout << year<<' ' << month <<' '<< day << endl; } void init(int y, int m, int d) {

  • Java集合框架之List ArrayList LinkedList使用详解刨析

    目录 1. List 1.1 List 的常见方法 1.2 代码示例 2. ArrayList 2.1 介绍 2.2 ArrayList 的构造方法 2.3 ArrayList 底层数组的大小 3. LinkedList 3.1 介绍 3.2 LinkedList 的构造方法 4. 练习题 5. 扑克牌小游戏 1. List 1.1 List 的常见方法 方法 描述 boolean add(E e) 尾插 e void add(int index, E element) 将 e 插入到 inde

  • JavaScript深入刨析this的指向以及如何修改指向

    目录 this 方法中 对象中 隐藏的this 严格模式 可以改变this指向 this 老规矩先看代码: 方法中 function test(){ console.log(this); } 对象中 Person={ name:"张三", eat:function(){ console.log(this) } } 在方法中,this表示该方法所属的对象.因为第一个是window上的方法,所以打印了window,而eat方法是Person方法,所以打印除了对象Person. 所以可以看出

  • Java由浅入深刨析继承

    目录 继承 继承的介绍 生活中的继承 继承的好处 继承的格式 继承的demo 子类不能继承的内容 super与this关键字 构造器不能被继承 final修饰的类不能被继承 方法重写 介绍 使用场景与案例 @Override重写注解 注意事项 完结 茫茫人海千千万万,感谢这一秒你看到这里.希望我的面试题系列能对你的有所帮助!共勉! 愿你在未来的日子,保持热爱,奔赴山海! Java基础知识(继承) 继承 继承的介绍 继承是java面向对象编程技术的一块基石,因为它允许创建分等级层次的类.描述的是事

  • C++深入刨析muduo中的抽象类Poller

    目录 Poller是抽象类,Eventloop通过抽象类Poller,引用不同的派生类对象(PollPoller或EpollPoller),调用同名覆盖方法,就可以很方便地去扩展不同的I/O复用 Poller.h源码 #include <map> #include <vector> #include "muduo/base/Timestamp.h" #include "muduo/net/EventLoop.h" namespace mudu

  • Java设计模式之组合模式深入刨析

    目录 1.基本介绍 2.结构 3.组合模式解决的问题 4.组合模式解决学校院系展示 5.组合模式的注意事项和细节 1.基本介绍 1)组合模式(Composite Pattern),又叫部分整体模式,它创建了对象组的树形结构,将对象组合成树状结构以表示“整体-部分”的层次关系 2)组合模式依据树形结构来组合对象,用来表示部分以及整体层次 3)这种类型的设计模式属于结构型模式 4)组合模式使得用户对单个对象和组合对象的访问具有一致性,即:组合能让客户以一致的方式处理个别对象以及组合对象 2.结构 组

  • Java面向对象特性深入刨析封装

    目录 1.认识封装 2.控制访问权限-访问修饰符 3.理解封装必须要知道-包 3.1理解包的概念 3.2 导入包中的类 3.3 自定义包 3.4 包的访问权限控制 3.5 java中常见的包 前面已经提过了 Java是一门面向对象(oop)的进行编程的语言, 面向对象的编程,有很多的好处,比如更容易开拓思维,分工合作,提高开发效率, 最主要的是 可重用性高,也就是下面将要提到的这三个核心特性(封装,继承,多态). 可扩展性,易于管理. 1.认识封装 简单的一句话就是套壳屏蔽细节. 比如说一部手机

  • Spring深入刨析声明式事务注解的源码

    目录 1.@EnableTransactionManagement 2.加载事务控制组件 2.1.AutoProxyRegistrar 2.2.ProxyTransactionManagementConfiguration 组件 2.3.上述组件如何关联起来的 2.4.invokeWithinTransaction⽅法 声明式事务很方便,尤其纯注解模式,仅仅几个注解就能控制事务了 思考:这些注解都做了什么?好神奇! @EnableTransactionManagement @Transactio

  • C++List容器常用函数接口刨析

    目录 一.基本结构 二.list的迭代器的构造 三.迭代器的实现 四.insert,erase 五.push_back,push_front,pop_back,pop_front 六.构造函数与赋值重载 七.析构与清空 一.基本结构 由源代码可知,list容器是有一个带头双向循环链表实现,所以我们模拟实现也需要实现一个带头双向循环链表的数据结构. template<class T> struct list_node { list_node<T>* _next; list_node&

  • C++深入刨析优先级队列priority_queue的使用

    目录 一.priority_queue的介绍 二.priority_queue的使用 三.priority_queue的模拟实现 四.容器适配器 4.1.什么是适配器 4.2.适配模式 4.3.STL标准库中stack和queue的底层结构 一.priority_queue的介绍 priority_queue官方文档介绍 翻译: 优先队列是一种容器适配器,根据严格的弱排序标准,它的第一个元素总是它所包含的元素中最大的. 此上下文类似于堆,在堆中可以随时插入元素,并且只能检索最大堆元素(优先队列中

随机推荐