c++|类和对象(上)

目录

一、面向过程和面向对象初步认识

二、类的引入和定义

2.1类的引入

2.2类的定义

三、类的访问限定符及封装

3.1访问限定符 

3.2封装 

四、类的作用域

五、类的实例化

六、类的对象大小的计算

6.1如何计算对象的大小

6.2类对象的存储方式 

七、类成员函数的this指针

7.1this指针的引出

7.2this指针的特性


一、面向过程和面向对象初步认识

 面向过程:关注的是过程,分析出求解问题的步骤,通过函数调用逐步解决问题。

例如:洗衣服

拿盆子——>放水——>放衣服——>放洗衣粉——>手搓——>换水——>手搓——>拧干——>晾衣服 

我们知道C语言面向的是过程,例如我们要得到两数之和的结果,那么得造一个和函数,先进行传实参,形参相加,得到参数之和,返回之和的值,这就是得到两数之和的一个过程。

面向对象:关注的是对象,将一件事情拆分成不同的对象,靠对象之间的交互完成。

 还是洗衣服,有四个对象:人、衣服、洗衣粉、洗衣机 

那么洗衣服过程:人将衣服放入洗衣机、倒入洗衣粉,启动洗衣机,洗衣机就会完成洗衣过程,人不需要关注洗衣机是如何洗衣服的。

那么照样我们也要得到两数之和的结果,就不需要去实现函数了,而是直接用函数就行,这个函数是已经被实现的,这就是直接使用对象,不需要关注实现过程。

那么还有疑问的是,对象之间的操作怎么看起来也是一个过程,其实是不同角度产生的看法,那么对于同一件事情,洗衣服,从过程来看,这个衣服需要执行被洗的过程,在乎的是怎么洗的过程,而从对象来看,只需要用洗衣机完成洗衣服,怎么洗的过程不在乎。其实这就相当与面向对象是以更高的角度去看待面向过程。

我再从更高的角度去看待面向对象,那么面向对象又可以看成面向过程。所以对于任何实现从不同角度既可以看成面向过程,也可以看成面向对象

对于c++我们将其看成面向对象的语言,因为对标C语言,c++很多实现要比C语言简单的多,不用造轮子(自己实现没必要去浪费时间做的函数),当然你以更高的角度来看c++实现的函数也可以看成过程 ,但是你得在更高的角度弄出点东西来,不然也改变不了啥。

二、类的引入和定义

2.1类的引入

回顾C语言实现栈时,需要有一个结构体存放定义的变量,而具体的实现需要额外定义函数完成压栈、出栈、销毁栈等。那么在c++中,结构体不仅可以定义变量,也可以定义函数了,同样我们用栈来举个例子

#include <iostream>
using namespace std;typedef int DataType;
struct stack
{//栈的初始化void Init(size_t capacity){_array = (DataType*)malloc(sizeof(DataType*) * capacity);if (nullptr == _array){perror("malloc fail");exit(-1);}_capacity = capacity;_size = 0;}//压栈void Push(const DataType& data){_array[_size] = data;++_size;}//取队头元素DataType Top(){return _array[_size - 1];}//栈的销毁void Destroy(){if (_array){free(_array);_array = nullptr;_capacity = 0;_size = 0;}}//变量的定义DataType* _array;size_t _capacity;size_t _size;
};int main()
{stack s;s.Init(5);s.Push(1);s.Push(2);s.Push(3);cout << s.Top() << endl;s.Destroy();return 0;
}

运行结果:

 

 从上面代码可以看出,我们将变量的定义与函数的实现都放在结构体中,且这些变量可以在函数中使用,像这种将函数和变量都放在一个结构体中,在这种情况下,在c++中我们称之为类,也就是说,C语言中的结构体在c++中升级成了类,当然类依然支持C语言中的结构体用法,毕竟c++兼容C语言嘛。那么接下来讲解类定义的详细用法

注意:上述结构体的定义,在c++中更喜欢用class来代替,其实struct和class还是有区别的,在类的访问限定中会讲解。

2.2类的定义

形式规则:

class className
{//类体:由成员函数和成员变量组成};//一定要注意后面的分号

 class为定义类的关键字,className为类的名字,{}中为类的主体,注意类定义结束时后面分号不能省略。

类体中内容称为类的成员:类中的变量称为称为类的属性成员变量;类中的函数称为类的方法或者成员函数

以上是类定义的形式规则,但对于类中的成员函数定义和成员变量命名也有一定的规则。

成员函数的定义有两种方式:

1.成员函数的声明和定义都放在类体中,需要注意的是,编译器可能会将其当成内联函数处理。

 2.类声明放在.h文件中,成员函数放在.cpp中,注意:成员函数名前需要加类名::

//声明放在类的头文件fun.h中
#include <iostream>
using namespace std;class Person
{
public://显示基本信息void showinfo();
public:char* _name;char* _sex;int _age;
};//定义放在类的实现文件中
#include "fun.h"void Person::showinfo()
{cout << _name << "-" << _sex << "-" << _age << endl;
}int main()
{Person s;s._name = (char*)"张三";//由于张三是字面常量,类型为const char*而_name为char*类型s._sex = (char*)"男";s._age = 18;s.showinfo();return 0;
}

 运行结果:

一般情况下,最好采用第二种方式,但在之后的讲解中,为了方便,可能会使用方式一。但在以后的工作中的话,那尽量使用方式二 。

成员变量命名规则的建议:

#include <iostream>
using namespace std;class Date
{
public:void Init(int year){//这里的year到底是成员变量还是函数形参,虽然知道左边的year是定义的变量,而右边的year是形参的year//但难免带来一种令人不友好的感觉year = year;//所以一般建议定义的变量加上前缀或者后缀,对于我而言习惯加上前缀int _year = year;int year_ = year;int myear = year;}
private:int year;int _year;int year_;int myear;
};int main()
{Date d;d.Init(3);return 0;
}

注意:默认直接在类里面定义的函数就是inline函数,不需要加inline关键字,当然在编译时展不展开函数还是取决编译器 。

三、类的访问限定符及封装

3.1访问限定符 

 在上述代码中,看到我们加了public、private这类符号,是什么?

像这类符号我们称之为访问限定符,其实,这是c++实现封装方式的必要手段,用类将对象的属性和方法结合在一块,让对象更加完善,再通过访问访问限定符选择性的将其接口提供给外部的用户使用。

那具体是啥意思,访问限定符有哪些?look

访问限定符,用来限定类外是否能访问类里面的成员,访问限定符分为三类,public(公有)protected(保护)和private(私有)

访问限定符说明:

1.public表示类外是可以访问类里面被public修饰的成员,

2.protected和private表示类外不能直接访问类里面被这两个修饰的成员,他们是类似的,但还是有区别的,在继承阶段会讲解。

3.访问权限作用域从该访问限定符出现的位置开始知道下一个访问限定符出现时为止,如果后面没有访问限定符,作用域就到 } 即类结束

4.上面我们提到了class和struct是有区别,对于class,其成员的默认访问权限为private,而struct为public(想想,虽然在c++中struct升级成了类,但依然支持C语言struct的用法,而C语言的struct成员都是可以直接被访问的,不需要限定符。所以在这里struct的默认访问权限是public)

注意:访问限定符只在编译时有用,当数据映射到内存后,没有任何访问限定符上的区别 

3.2封装 

对于对象而言,无非就三大特性:封装、继承、多态。继承和多态后面的章节再说,在类和对象阶段,主要说的就是封装,那么什么是封装呢?

封装:将数据和操作数据的方法进行有机结合,隐藏对象的属性和实现细节,仅对外公开接口来和对象进行交互,封装本质上是一种管理,让用户更方便实用类。说白了,不就是用类将他们包起来,再通过访问限定符是否对外提供接口来实现交互。

在现实中,这个道理也是一样的,像电脑,里面的零件都是被封装的,然后提供一个USB接口给用户使用。

四、类的作用域

在上面,我们提到并给出了代码,类中的函数长的声明和定义要分离,分离定义的函数怎么与类中声明的函数进行联系呢?跟命名空间一样,类也是一个作用域,类的所有成员在类的作用域中,类外定义成员时,需要使用::作用域操作 符指明成员属于那个类域

 

#include <iostream>
using namespace std;class Person
{
public://打印基本信息void printpersoninfo();
public:char _name[20];char _gender[3];int _age;
};//指明函数属于哪个类域
void Person::printpersoninfo()
{cout << _name << "-" << _gender << "-" << _age << endl;
}int main()
{Person s;strcpy(s._name , "李四");strcpy(s._gender , "女");s._age = 19;s.printpersoninfo();return 0;
}

运行结果:

 

五、类的实例化

 仅仅有一个类不能对里面的内容进行访问,而是要创建一个对象,通过对象来对类中的成员进行访问。用类类型创建对象的过程,称为类的实例化。

类是对对象进行描述的,是一个模型一样的东西,限定类有那些成员,定义出一个类并没有分配实际的内存空间来存储它。必须实例化出对象才占有实际的物理空间,存储类成员变量。且一个类可以实例化出多个对象。

比如:房子设计图,就是一个类,设计图描述了房子的各种信息,但是并没有为这些信息建造出一个实际的空间。而只有建造出了真正的房子才是实例化,才分配了空间,且通过设计图,也可以建造出多个房子,即实例化多个对象。再通过代码来比较。

#include <iostream>
using namespace std;class Person
{
public://显示基本信息void printpersoninfo();
public:char _name[20];char _gender[3];int _age;
};//指明函数属于哪个类域
void Person::printpersoninfo()
{cout << _name << "-" << _gender << "-" << _age << endl;
}int main()
{Person s;//创建对象,且实例化多个对象Person t;Person u;strcpy(s._name , "李四");strcpy(t._name , "王五");strcpy(u._name , "李六");strcpy(s._gender, "女");strcpy(t._gender, "男");strcpy(u._gender, "男");s._age = 18;t._age = 19;u._age = 20;s.printpersoninfo();t.printpersoninfo();u.printpersoninfo();return 0;
}

运行结果:

 

六、类的对象大小的计算

6.1如何计算对象的大小

对于类中既有成员函数和成员变量,那么计算大小时是不是他们都要算呢?look

#include <iostream>
using namespace std;
class A
{
public:void PrintA(){cout << _a << endl;}
private:char _a;
};int main()
{A a1;cout << sizeof(a1);//猜猜多大return 0;
}

  运行结果:

通过结果发现,大小只有1,只计算了成员变量_a 的大小。这是怎么回事呢?且看下面分析

6.2类对象的存储方式 

以正常的理解,当一个类实例化多个对象时,对象与对象之间的成员变量、成员函数是互不干扰的,但每个对象都会保存一份该成员函数的代码,相同的代码保存了多份,实例化了多份,就会形成空间的浪费。

所以给了对象的新的存储方式,对象中只保存成员变量,而对于成员函数,只保存一份放在公共代码区,每个对象调用该函数时,其实就是调用公共代码区的该函数。所以在上述计算大小时,就不会算上成员函数。

再来看几个例子:

空类:

#include <iostream>
using namespace std;class A3
{};int main()
{cout << sizeof(A3);return 0;
}

 运行结果:

为什么也是1呢,不应该是0吗?明明是一个空类。

 OK,空类其实也可以实例化对象,这是前提。既然可以实例化对象,那么对象就会在内存分配空间,该对象有地址存在,只不过不存储任何数据。如果,空类的大小为0表示该类没有任何成员或对象。这意味着该类不占用内存空间。所以,为了标识该对象存在,编译器规定该空类的大小为1

类中仅有成员函数:

#include <iostream>
using namespace std;class A2 {
public:void f2() {}
};int main()
{cout << sizeof(A2);return 0;
}

运行结果:

 

大小也是1,根据上面的解释成员函数是放在公共代码区,它们在内存中独立存在,所以在计算大小时, 相当于计算空类的大小。

注意:计算类的大小依然遵守结构体内存对齐规则 !!!

七、类成员函数的this指针

7.1this指针的引出

#include <iostream>
using namespace std;class Date
{
public:void Init(int year = 2023, int month = 11, int day = 23)//给缺省值{_year = year;_month = month;_day = day;}void print(){cout << _year << "-" << _month << "-" << _day << endl;}
private:int _year;int _month;int _day;
};int main()
{Date d1, d2;d1.Init(2023, 1, 11);d2.Init(2023, 1, 12);d1.print();d2.print();return 0;
}

运行结果:

对于上述类,实例化两个对象,在表面上两个对象都调用了Init函数时,但是在深层,函数怎么识别是谁调用?

c++引入了this指针来识别对象的调用:c++编译器给每个“非静态的成员函数”增加了一个隐藏的指针参数,让该指针指向当前对象(函数运行时调用该函数的对象),在函数体中所有“成员变量”的操作,都是通过该指针去访问。但要注意的是,this指针的所有操作对于程序员而言是透明的,即this指针不需要程序员来传递,编译器会自动完成(可以认为是祖师爷设定好的)。

 7.2this指针的特性

1.this指针的类型:类类型* const,例如Date* const this。由于this被const修饰,所以在成员函数中,不能给this指针赋值。

2.只能在“成员函数”的内容使用

3.this指针本质上是“成员函数”的形参,当对象调用成员函数时,将对象地址作为实参传递给this形参。所以对象本身不存储this指针。

4.this指针是“成员函数”第一个隐含的指针形参,一般情况由编译器通过ecx寄存器自动传递,不需要用户传递。

其实上述代码的本质就是第二张图的形式,对应着上述规则。注意的是在调用函数传实参、以及函数的形参中不能显示的将this指针写出来,否则会报错,但在成员函数中,可以显示的写出。

【面试题】 

1.this指针存在哪里?

将上述代码,转到反汇编后,可以发现,d1对象和d2对象存放到rcx寄存器。由此this指针在VS下存放在栈区的寄存器中。

2.this指针可以为空吗 ?

先看代码1:下面运行结果? A、编译报错 B、运行崩溃 C、正常运行

#include <iostream>
using namespace std;class A
{
public:void Print(){cout << "Print()" << endl;}
private:int _a;
};int main()
{A* p = nullptr;p->Print();return 0;
}

运行结果:

答案是正常运行,疑问的是p是空指针,为什么可以访问Print函数?

其实这是一个误区,想想前面讲解的,首先成员函数不是放在对象中,而是在公共代码区,其次在这里p并没有去访问Print函数,但是,如果没有访问该函数,那又是怎么寻得该函数。在C语言中,就知道了,是在编译、链接阶段通过符号表来寻找函数名字,所以在这里p的作用就是告诉编译器它属于A类,然后在编译阶段,A类中寻找是否存在Print函数。

代码2:下面运行结果? A、编译报错 B、运行崩溃 C、正常运行

#include <iostream>
using namespace std;class A
{
public:void Print(){cout << _a << endl;}
private:int _a;
};int main()
{A* p = nullptr;p->Print();return 0;
}

运行结果:

运行结果是崩溃的,对比第一个代码,修改了成员函数的输出,且成员变量并没有初始化。本质上就是this->_a,这个就是空指针访问了成员变量,而空指针并不指向任何有效的对象,因此其中的成员变量也是未定义的。 

综上所述,虽然成员函数的代码本身放在公共代码区,但成员函数通常涉及对对象中的成员变量进行操作,这可能导致问题。因此最好不要让this指针为空,以避免潜在的未定义行为。

end~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/166517.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Python-Django的“日志功能-日志模块(logging模块)-日志输出”的功能详解

01-综述 可以使用Python内置的logging模块来实现Django项目的日志记录。 所以与其说这篇文章在讲Django的“日志功能-日志模块-日志输出”&#xff0c;不如说是在讲Pthon的“日志功能-日志模块-日志输出”&#xff0c;即Python的logging模块。 下面用一个实例来进行讲解。 …

2023年亚太杯数学建模A题水果采摘机器人的图像识别功能(免费思路)

中国是世界上最大的苹果生产国&#xff0c;年产量约为 3500 万吨。同时&#xff0c;中国也是世界上最大的苹果出口国&#xff0c;世界上每两个苹果中就有一个出口到国。世界上每两个苹果中就有一个来自中国&#xff0c;中国出口的苹果占全球出口量的六分之一以上。来自中国。中…

基于51单片机超声波测距汽车避障系统

**单片机设计介绍&#xff0c; 基于51单片机超声波测距汽车避障系统 文章目录 一 概要二、功能设计设计思路 三、 软件设计原理图 五、 程序六、 文章目录 一 概要 基于51单片机的超声波测距汽车避障系统是一种用于帮助汽车避免碰撞和发生事故的设备&#xff0c;以下是一个基本…

Visual Studio 2022安装教程(千字图文详解),手把手带你安装运行VS2022以及背景图设置

VS2022最新最全安装教程 很高兴你打开了这篇博客&#xff0c;接下来我们一起安装并且使用VS2022吧 文章目录 VS2022最新最全安装教程一.官网下载二.安装启动三.项目测试1.创建新项目2.选择我们使用的模板&#xff08;C空项目&#xff09;&#xff0c;继续冲&#xff01;3.进入…

docker的使用方法

文章目录 为什么要用dockerdocker安装docker工作原理docker命令docker搭建练习docker可视化docker镜像docker容器数据卷DockerFiledocker全流程Docker网络原理docker composedocker swarm 为什么要用docker 官网&#xff1a;https://www.docker.com文档地址&#xff1a;https:…

系列五、声明式事务(xml方式)

一、概述 声明式事务(declarative transaction management)是Spring提供的对程序事务管理的一种方式&#xff0c;Spring的声明式事务顾名思义就是采用声明的方式来处理事务。这里所说的声明&#xff0c;是指在配置文件中声明&#xff0c;用在Spring配置文件中声明式的处理事务来…

搞笑的客服日常,今天遇到的晒白客服,你遇到过吗?

大家看看这个 https://blog.csdn.net/a871923942/article/details/129778391?utm_mediumdistribute.pc_feed_404.none-task-blog-2 今天遇到傻逼审核 开头第一句就使用快捷话术 客服感觉自己发错了 然后就撤回消息 第二次继续发话术&#xff0c;说违规&#xff0c;审核不通过…

案例015:Java+SSM+uniapp基于微信小程序的校园防疫系统

文末获取源码 开发语言&#xff1a;Java 框架&#xff1a;SSM JDK版本&#xff1a;JDK1.8 数据库&#xff1a;mysql 5.7 开发软件&#xff1a;eclipse/myeclipse/idea Maven包&#xff1a;Maven3.5.4 小程序框架&#xff1a;uniapp 小程序开发软件&#xff1a;HBuilder X 小程序…

互联网上门洗鞋店小程序

上门洗鞋店小程序门店版是基于原平台版进行增强的&#xff0c;结合洗鞋行业的线下实际运营经验和需求&#xff0c;专为洗鞋人和洗鞋店打造的高效、实用、有价值的管理软件系统。 它能够帮助洗鞋人建立自己的私域流量&#xff0c;实现会员用户管理&#xff0c;实现用户与商家的点…

【SpringCloud】微服务架构设计模式

一、聚合气微服务设计模式 最常见、最简单的设计模式&#xff0c;效果如图所示&#xff1a; 聚合器调用多个服务实现应用程序所需的功能 它可以是一个简单的 Web 页面&#xff0c;将检索到的数据进行处理并展示&#xff0c;也可以是一个更高层次的组合微服务&#xff0c;对…

记录ruoyi-plus-vue部署的问题

ruoyi-vue-plus5.x 后端 ruoyi-vue-plus5.x 前端 前端本地启动命令 # 克隆项目 git clone https://gitee.com/JavaLionLi/plus-ui.git# 安装依赖 npm install --registryhttps://registry.npmmirror.com# 启动服务 npm run dev# 构建生产环境 yarn build:prod # 前端访问地址…

[C++历练之路]vector的介绍以及底层模拟实现

W...Y的主页 &#x1f60a; 代码仓库分享 &#x1f495; &#x1f354;前言&#xff1a; 我们学习了STL中的string以及其所有重要接口并进行了模拟实现&#xff0c;但是STL中包含的内容不止于此。学习了string之后继续学习STL中的vector&#xff0c;学习成本会大大降低&#…

产品经理面试必看!To B和To C产品的隐秘差异,你了解多少?

大家好&#xff0c;我是小米&#xff0c;一位对技术充满热情的产品经理。最近在和小伙伴们交流中发现一个热门话题&#xff1a;To B&#xff08;面向企业&#xff09;和To C&#xff08;面向消费者&#xff09;的产品经理究竟有何异同&#xff1f;这可是我们产品经理面试中的经…

FFmpeg常用命令讲解及实战二

文章目录 前言一、ffmpeg 常用命令1、ffmpeg 的封装转换2、ffmpeg 的编转码3、ffmpeg 的基本编转码原理 二、ffprobe 常用参数1、show_format2、show_frames3、show_streams4、print_format5、select_streams 三、ffplay 的常用命令1、ffplay 常用参数2、ffplay 高级参数3、ffp…

【附代码】判断线段是否相交算法(Python,C++)

【附代码】判断线段是否相交算法&#xff08;Python&#xff0c;C&#xff09; 文章目录 【附代码】判断线段是否相交算法&#xff08;Python&#xff0c;C&#xff09;相关文献测试电脑配置基础向量旋转向量缩放向量投影推导 点乘定义推导几何意义 叉乘定义推导几何意义 判断线…

四数之和java版

题目描述 给定一个包含 n 个整数的数组 nums 和一个目标值 target&#xff0c;判断 nums 中是否存在四个元素 a&#xff0c;b&#xff0c;c 和 d &#xff0c;使得 a b c d 的值与 target 相等&#xff1f;找出所有满足条件且不重复的四元组。 注意&#xff1a;答案中不可以…

物流实时数仓:数仓搭建(ODS)

系列文章目录 物流实时数仓&#xff1a;采集通道搭建 物流实时数仓&#xff1a;数仓搭建 文章目录 系列文章目录前言一、IDEA环境准备1.pom.xml2.目录创建 二、代码编写1.log4j.properties2.CreateEnvUtil.java3.KafkaUtil.java4.OdsApp.java 三、代码测试总结 前言 现在我们…

当内容创作进入 AGI 时代,你也可以成为「神笔马良」

我神笔马良的童话故事我们或多或少都听过&#xff0c;一支神笔在手&#xff0c;想画什么就能画出什么&#xff0c;栩栩如生。创造者的理解力、想象力和创作力都能通过这支神笔释放。 近一年&#xff0c;随着 AIGC 内容生产工具的快速出圈&#xff0c;有人把 Stable Diffusion、…

Sublime Text 4168最新代码编辑

Sublime Text是一款功能强大的文本编辑器&#xff0c;具有以下主要功能&#xff1a; 支持多种编程语言的语法高亮和代码自动完成功能&#xff0c;包括Python、JavaScript、HTML、CSS等。提供代码片段&#xff08;Snippet&#xff09;功能&#xff0c;可以将常用的代码片段保存…