类与对象(上篇)

前言

在之前我们学的C++入门主要是为现在学习类与对象打基础,今天我们才算真正开始学习C++了。因为类与对象的知识点比较多,所以我们将它分为三部分讲解,今天我们学习类与对象的上篇。

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

1、面向过程

面向过程顾名知义,关注的是过程,分析出求解问题的步骤,通过函数调用逐步解决问题。例如:C语言。

2、面向对象

面向对象,关注的是对象,将一件事情拆分成不同的对象,靠对象之间的交互完成。例如:C++,java(注意:C++兼容C,所以C++并不是纯面向对象的语言,是支持面向对象和面向过程的“混编”)。

3、举个例子

外卖系统分别用面向过程与面向对象是怎样实现的?如下图所示:

在这里插入图片描述
tip: 现在大家先对概念理解一下,随着以后的学习会慢慢理解面向对象的思想。

二、类的引入

在C语言中,数据与方法是分离的。而在C++中,数据与方法没有分离。所以在C++中struct被升级成了类——结构体内不仅可以定义变量,也可以定义函数。

代码示例:写一个简易的栈类

#include<iostream>
#include<stdlib.h>//展开命名空间
using namespace std;
//栈类
typedef int DataType;//栈的元素类型
struct Stack
{//成员函数//栈的初始化void Init(int capacity = 4){//在堆区开辟capacity个栈空间_array = (DataType*)malloc(sizeof(DataType) * capacity);if (nullptr == _array){perror("malloc fail");return;}//初始化栈的容量与栈顶位置_capacity = capacity;_top = 0;//指向栈顶位置的下一个}//入栈void push(const DataType& x){//判断是否扩容if (_top == _capacity){//扩容}//入栈_array[_top] = x;_top++;}//访问栈顶元素DataType Top(){return _array[_top - 1];}//销毁void Destroy(){free(_array);_array = nullptr;_capacity = 0;_top = 0;}//成员变量DataType* _array;//指向堆区开辟的数组int _capacity;//栈的容量int _top;//栈顶位置
};int main()
{//C++兼容C,struct以前的用法都可以继续使用struct Stack s1;//C++类名就是类型,所以可以直接写StackStack s2;s2.Init();//缺省参数——没有传实参,使用缺省值s2.push(1);s2.push(1);cout << s2.Top() << endl;s2.Destroy();return 0;
}

tip:

①C++将struct升级成了类,不仅可以定义变量,还可以定义函数。

②C++中类名就是类型,在C里面struct 类名组合在一起才是类型。因为C++兼容C所以两种写法都可以,struct以前的用法都可以继续使用。

③我们发现在类中变量成员在声明前面可以使用,这是因为类域是一个整体,所以变量写在后面,也不用声明。

虽然struct被升级成了类,但是在C++中更喜欢用class来代替。

三、类的定义

1、类定义的代码示例

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

解读:

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

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

2、类的两种定义方法

①声明和定义全部放在类体中 ,需要注意:成员函数如果在类中定义,默认为内联函数,最后由编译器决定。

class Person
{//函数的声明与实现都在类中void showInfo(){cout << _name << " " << _age << endl;}//成员函数char* _name;int _age;
};

②类声明放在.h文件中,成员函数定义放在.cpp文件中。如下图:

在这里插入图片描述
tip:类成员函数定义时,注意要成员函数名前面要类名::,表明它是那个类的。

总结:对于这两种方法,平时我博客讲解的时候为了方便使用方法1定义类,但是建议大家在以后写项目和工作时使用方法2。

3、成员变量的命名习惯

为了避免成员变量与成员函数的参数同名,我们一般可以①成员变量加前缀_;②成员变量加前缀my_;③成员变量加后缀_等方法。

代码示例:

class Date
{
public:void Init(int year){//因为成员变量加了前缀_,所以这里我们能很好的区分该语句是给对象的_year赋值_year = year;}
private:int _year;
};

四、类的访问限定符与封装

1、类的访问限定符

(1)引入访问限定符

我们先来看两段代码:

代码1:能运行吗?

struct Person
{char _name[10];int _age;
};int main()
{//定义对象p1Person p1;//直接修改对象p1的年龄p1._age = 18;return 0;
}

代码2:能运行吗?

class Person
{char _name[10];int _age;
};int main()
{//定义对象p1Person p1;//直接修改对象p1的年龄p1._age = 18;return 0;
}

答案是:代码1能运行,代码2不能运行,出现语法错误,这是为什么呢?这就是我们接下来要讲解的类的访问限定符。

(2)访问限定符

C++实现封装的方式:用类将对象的属性与方法结合在一起,让对象更加完善,通过访问权限选择性的将接口提供给外部的用户使用。

在这里插入图片描述

(3)访问限定符说明

①public修饰的成员在类外可以直接被访问。

②protected和private修饰的成员在类外不能直接被访问。(在C++初阶protected和private类似,在后面进阶讲继承的时候才能体现他们的区别)

③访问权限作用域从该访问限定符出现的位置开始直到下一个访问限定符出现为止。

④如果后面没有访问限定符,作用域就到“}”即类的结束。

⑤class的默认访问权限为private,struct为public(因为struct要兼容C)。
注意:访问限定符只在编译时有用,当数据映射到内存后,没有任何访问限定符上的区别。

(4)问题:C++中struct和class的区别

C++需要兼容C,所以C++中struct可以当作结构体使用。另外C++中struct还可以用来定义类。和class定义类一样,区别是struct定义的类默认访问权限是public,class定义的类默认权限是private。注意:在继承和模板参数列表位置,struct和class也有区别,后序再给大家讲解。

2、封装

(1)面向对象的三大特性

面向对象的三大特性:封装、继承、多态。

(2)什么是封装

封装:将数据和操作数据的方法进行有机结合,隐藏对象的属性和实现细节,仅对外公开接口来和对象进行交换。

在我们生活中有许多封装的实例,例如:你家的房子,就是一个封装。如果不封装的话,那谁都可以进你家了。

(3)封装的本质

封装的本质是一种管理,让用户更方便使用类。

在C++语言中实现封装,可以通过类将数据以及操作数据的方法进行有机结合,通过访问权限来隐藏对象内部实现细节,控制那些方法可以在类外直接被使用。

在这里插入图片描述

五、类的作用域

1、类域

①类定义了一个新的作用域,类的所有成员都在类的作用域中。

②在类体外定义成员时,需要使用::作用域操作符指明成员属于哪个类域。

③类域是一个整体,成员变量不是一定写在成员函数后面的。

代码示例:

#include<iostream>
using namespace std;class Person
{
public:void PrintPersonInfo();
private:char _name[20];char _gender[3];int _age;
};//这里需要指定PrintPersonInfo是属于Person这个类域
void Person::PrintPersonInfo()
{cout << _name << " " << _gender << " " << _age << endl;
}

2、简单总结我们已经学过的域

①我们已经学习了四种域:局部域、全局域、命名空间域、类域。

②同一个域不能定义同名变量,不同域可以定义同名变量。

③域都会影响访问,但只有局部域和全局域影响生命周期。

④编译器访问变量规则:一般默认先在局部找,找不到再去全局找(都找不到则报错);特殊类方法先局部找,找不到去类域找,最后再去全局找。

⑤命名空间域与全局域平行,但是如果不展开就不会访问。

⑥::作用域操作符,指定访问某个域的变量。指定方式:域名::变量名。

六、类的实例化

1、定义

用类类型创建对象的过程,称为类的实例化。

2、实例化的说明

①类是对对象进行描述的,是一个模型一样的东西,限定了类有哪些成员,定义出一个类并没有分配实际的内存空间来存储它。

②一个类可以实例化出多个对象,实例化出的对象才占用实际的物理空间,存储类的成员变量。

类就像是设计图,类实例化出对象就像现实中使用建筑设计图建造出的房子。 图纸并没有实体,同样类也只是一个设计,只有实例化出的对象才能实际存储数据,占用物理空间。

在这里插入图片描述
代码示例:

#include<iostream>
using namespace std;class Person
{
public:void PrintPersonInfo(){cout << _name << " " << _sex << " " << _age << endl;}
public:char* _name;char* _sex;int _age;
};int main()
{//下面语句是否正确?//Person::_age = 18;//错误,因为类没有实例化,并没有开辟空间。只有类实例化出的对象才有具体的年龄。//tip:类的对象要整体实例化才可以。//类实例化对象/对象定义Person man;//tip:只有类实例化,开辟了空间,才能存储数据man._name = (char*)"zhangsan";man._sex = (char*)"男";man._age = 18;man.PrintPersonInfo();return 0;
}

七、类对象模型

1、如何计算类的大小

问题: 类中既有成员变量,也有成员函数,那么一个类的对象中包含什么?如何计算一个类的大小?

我们先来一段代码示例,用编译器运行计算看类A的大小是多少。

#include<iostream>
using namespace std;class A
{
public://成员函数void f(){}
private://成员变量int _a;char _ch;
};int main()
{//实例化对象A a;//打印类对象的大小cout << sizeof(a) << endl;return 0;
}

运算结果:类A的大小

在这里插入图片描述
解读:

①类对象存储: 类对象只保存类的成员变量,不保存类的成员函数。(为什么会这样呢?详细讲解在后面类对象存储猜测)

②类的大小计算: 与C语言计算结构体的方式一样,需要注意内存对齐。

③回顾内存对齐的规则:

1、结构体的第一个成员,对齐到结构体变量在内存中存放位置的0偏移量。

2、从第二个成员开始,每个成员变量都要对齐到(一个对齐数)的整数倍。

  • 对齐数=编译器默认的一个对齐数与一个结构体成员自身大小的较小值。
  • VS默认对齐数为8;Linux gcc没有默认对齐数,对齐数就是结构体成员自身大小。


3、结构体总大小,必须是所有成员变量的对齐数中最大对齐数的整数倍。

4、如果是嵌套结构体的情况,嵌套的结构体对齐到自己最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。

④内存对齐的意义:

1、平台原因(移植原因):不是所有的硬件平台都能访问任意地址上的任意数据;某些硬件平台只能在某些地址处取某些特定的数据,否则抛出硬件异常(例:int——>对齐到4的整数倍)。

2、性能原因:计算机读取数据时,并不是想访问哪个字节就访问哪个字节,而是从结构体的对齐边界开始按照访问倍数去访问。假设CPU一次访问4个字节(具体与硬件有关),对齐访问_a只要访问一次,不对齐_a要访问两次,如下图所示:

在这里插入图片描述
如图可知,内存对齐是拿空间换时间。

⑤类A的内存大小为8,如图所示:

在这里插入图片描述

2、类对象的存储猜测

①对象中包含类的各个成员:

在这里插入图片描述
缺陷: 不同对象中成员变量不同,但是调用同一个函数,如果按照此方式存储,当一个类创建多个对象时,**每个对象都会保存一份代码,相同代码保存多次,浪费空间。

②只保存成员变量,成员函数存放在公共的代码段:

在这里插入图片描述
tip:关于上述两种存储方式,计算机按照方式二来存储。

3、空类与仅有成员函数的类大小

代码示例:

#include<iostream>
using namespace std;//类中仅有成员函数
class A1
{
public:void f(){}
};//类中什么都没有——空类
class A2
{};int main()
{//输出A1,A2的大小cout << sizeof(A1) << endl;cout << sizeof(A2) << endl;return 0;
}

运行结果:

在这里插入图片描述
总结:

①没有成员变量的类对象,需要一个字节,是为了占位(不存储有效数据),表示对象存在。

②一个类的大小,实际就是该类中成员变量之和,还需要注意内存对齐。

八、this指针

1、this指针的引出

我们来定义一个日期类Data:

#include<iostream>
using namespace std;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, d2;d1.Init(2022, 1, 11);d2.Init(2022, 1, 12);d1.Print();d2.Print();return 0;
}

运行结果:

在这里插入图片描述

问题: d1与d2调用同一个函数print,为什么打印结果不一样呢?(在类的存储模型我们知道成员函数与对象无关,它存储在公共区域。)

答案是: C++中引入this指针解决该问题,即:C++编译器给每个“非静态的成员函数”添加了一个隐藏的this指针参数,让该this指针指向当前对象(函数运行时调用该函数的对象),在函数体中所有“成员变量”的操作,都是通过该指针去访问。只不过所有的操作对用户是透明的,即用户不需要来传递,编译器自动完成。如下图所示:

在这里插入图片描述

2、this指针的特点

(1)语法规定:this指针不能在形参和实参显示传递,但是可以在函数内部显示使用

①调用成员函数时,不能显示传递对象的地址给this,因为编译器会自动传递了,不需要用户传递。

tip: 既然编译器会自动传递对象地址,为什么不通过类来调用函数——因为通过类来调用函数,不会传this指针;通过对象调用函数,虽然不再对象里找函数,但是会传this指针。

②定义成员函数时,也不能显示定义this指针,因为编译器会自动定义。

tip: this指针是一个关键字,指向当前对象地址。

③this指针可以在函数体内部显示使用,如下代码所示:

void Print(){//语法规定:this指针不能在形参和实参显示传递,但是可以在函数内部显示使用//函数体中所有成员变量都要通过this指针访问//1、在函数体中,访问成员变量,你不写他会自动添加thiscout << _year << "-" << _month << "-" << _day << endl;//2、在函数体内可以自己显示使用this指针cout << this->_year << "-" << this->_month << "-" << this->_day << endl;}

tip: 在函数内部,你访问对象的成员变量你不写this指针它会自动添加,你也可以显示使用。

(2)this指针的类型:类类型* const,即成员函数中,不能给this指针赋值

tip:左定值,右定向。

const在*的左边,则指针指向的变量的值不能通过指针改变;在 * 的右边,则指针的指向不能改变。

代码示例:

int main()
{int a = 10;//右定向int* const pa = &a;int b = 9;//pa = &b;//报错,左值不可修改,即指针指向不能改变*pa = b;//可以修改指向变量的值return 0;
}

3、面试题

问题1: this指针存在哪里?

答案是:this指针本质上是“成员函数”的形参,所以this指针与普通参数一样存在函数调用的栈帧里面。如下图汇编代码:

在这里插入图片描述
tip: 在VS集成开发环境下,对this指针进行了优化,对象地址是放在ecx寄存器,ecx存储this指针的值。

问题2: this指针可以为空吗?

我们先来看两段代码,判断A、编译报错 B、运行崩溃 C、正常运行

代码1:

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

代码2:

class A
{
public:void PrintA(){cout << _a << endl;}
private:int _a;
};
int main()
{A* p = nullptr;p->PrintA();return 0;
}

答案是: 代码1:C、运行成功;代码2:B、运行崩溃。解析如下图所示:

在这里插入图片描述

4、this指针的好处

在这里插入图片描述

tip: C就像手动挡需要自己控制变速箱,C++有了封装,引入this指针就像自动挡电脑程序控制变速箱。简单来说,就是更简单,不易出错了

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

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

相关文章

[github初学者教程] 分支管理-以及问题解决

作者&#xff1a;20岁爱吃必胜客&#xff08;坤制作人&#xff09;&#xff0c;近十年开发经验, 跨域学习者&#xff0c;目前于新西兰奥克兰大学攻读IT硕士学位。荣誉&#xff1a;阿里云博客专家认证、腾讯开发者社区优质创作者&#xff0c;在CTF省赛校赛多次取得好成绩。跨领域…

工作记录---为什么双11当天不能申请退款?(有趣~)

为什么&#xff1f; 服务降级了 服务降级&#xff1a; 当服务器压力剧增的情况下&#xff0c;根据实际业务情况及流量&#xff0c;对一些服务和页面有策略的不处理或换种简单的方式处理&#xff0c;从而释放服务器资源以保证核心交易正常运作或高效运作。 分布式系统的降级…

用Java实现贪吃蛇小游戏

一、创建新项目 首先创建一个新的项目&#xff0c;并命名为贪吃蛇。 其次在贪吃蛇项目下创建一个名为images的文件夹用来存放游戏相关图片。 然后再在项目的src文件下创建一个com.xxx.view的包用来存放所有的图形界面类&#xff0c;创建一个com.xxx.controller的包用来存放启…

基于AVR单片机的心电信号获取与分析

基于AVR单片机的心电信号获取与分析是一项常见的生物医学工程应用&#xff0c;用于监测和分析人体的心脏活动。本文将介绍基于AVR单片机的心电信号获取与分析的原理和设计&#xff0c;并提供相应的代码示例。 1. 概述 心电信号是记录和分析心脏电活动的重要手段。AVR单片机是…

mysql 中with的用法(3)

有表&#xff08;tb&#xff09;,数据如下&#xff1a; 请用SQL,生成如下的样式&#xff1a; 一、建表 CREATE TABLE tb (id varchar(3) DEFAULT NULL,pid varchar(3) DEFAULT NULL,name varchar(64) DEFAULT NULL ) INSERT INTO tb (id, pid, name) VALUES(002, 0, 浙江省)…

docker更换国内源

docker更换国内源 1、编辑Docker配置文件 在终端中执行以下命令&#xff0c;编辑Docker配置文件&#xff1a; vi /etc/docker/daemon.json2、添加更新源 在打开的配置文件中&#xff0c;添加以下内容&#xff1a; {"registry-mirrors": ["https://hub-mirror…

Chrome中设置安全来源域名

目的&#xff1a; 使得本地映射的域名能被浏览器安全访问&#xff0c;允许调用设备资源 步骤&#xff1a; 在Chrome中导航栏打开 chrome://flags/#unsafely-treat-insecure-origin-as-secure 填入hosts域名&#xff1a;如 http://h5-twzc003.local.com 参考&#xff1a; h…

赴日开发工程师是做什么的?

日本的软件开发岗位对技术要求和沟通能力都有较高的要求&#xff0c;赴日开发工程师主要负责软件设计、开发和测试&#xff0c;包括编写代码、测试代码和修复漏洞等工作。开发人员必须对软件架构、设计模式和业务逻辑有深入的理解&#xff0c;并能做出合适的技术决策。 当然&a…

时间序列与 Statsmodels:预测所需的基本概念(1)

后文&#xff1a;时间序列与 statsmodels&#xff1a;预测所需的基本概念&#xff08;2&#xff09;-CSDN博客 一、说明 本博客解释了理解时间序列的基本概念&#xff1a;趋势、季节性、白噪声、平稳性&#xff0c;并使用自回归、差分和移动平均参数进行预测示例。这是理解任何…

江湖再见,机器视觉兄弟们,我已经提离职了,聪明的机器视觉工程师,离职不亏本!

我闻江湖已叹息&#xff0c;又闻人间繁闹闹。同为布衣沦落人&#xff0c;相逢何必曾相识。 此生谁料事事休&#xff0c;道不尽人情冷暖&#xff0c;聚散离合总平常&#xff0c;不似勇气少年时。 我估计今年公司年底是发不出工资了&#xff0c;因为订单续不上。年终奖更是没有&…

Android 弹出自定义对话框

Android在任意Activity界面弹出一个自定义的对话框&#xff0c;效果如下图所示: 准备一张小图片&#xff0c;右上角的小X图标64*64&#xff0c;close_icon.png&#xff0c;随便找个小图片代替&#xff1b; 第一步&#xff1a;样式添加&#xff0c;注意&#xff1a;默认在value…

通过微软MediaCreationTool制作Win10系统安装U盘,安装纯净版Win10的通用教程

最近新入手了一台Lenovo的入门级主机。 为了避免以后忘记装机步骤&#xff0c;特写下此博客记录。 装机步骤是在Lenovo网站上看的&#xff0c;在这表示感谢。 https://iknow.lenovo.com.cn/detail/177365

(十二)Flask重点之session

session 自我介绍&基本使用&#xff1a; 在Flask中&#xff0c;Session是一种用于在客户端和服务器之间存储和传输数据的机制。它允许您在用户与应用程序之间保持状态&#xff0c;并且可以存储和检索有关特定用户的信息。 Flask使用Werkzeug库提供的SecureCookie来实现S…

LangChain 4用向量数据库Faiss存储,读取YouTube的视频文本搜索Indexes for information retrieve

接着前面的Langchain&#xff0c;继续实现读取YouTube的视频脚本来问答Indexes for information retrieve LangChain 实现给动物取名字&#xff0c;LangChain 2模块化prompt template并用streamlit生成网站 实现给动物取名字LangChain 3使用Agent访问Wikipedia和llm-math计算狗…

Python接口自动化(什么是接口、接口优势、类型)

简介 经常听别人说接口测试&#xff0c;接口测试自动化&#xff0c;但是你对接口&#xff0c;有多少了解和认识&#xff0c;知道什么是接口吗&#xff1f;它是用来做什么的&#xff0c;测试时候要注意什么&#xff1f;坦白的说&#xff0c;笔者之前也不是很清楚。接下来先看一下…

Python懒羊羊

目录 系列文章 写在前面 绘图基础 懒羊羊 写在后面 系列文章 序号文章目录直达链接表白系列1浪漫520表白代码https://want595.blog.csdn.net/article/details/1306668812满屏表白代码https://want595.blog.csdn.net/article/details/1297945183跳动的爱心https://want595…

requests库出现AttributeError问题的修复与替代方法

在使用App Engine时&#xff0c;开发者们通常会面临需要发送爬虫ip请求的情况&#xff0c;而Python中的requests库是一个常用的工具&#xff0c;用于处理爬虫ip请求。然而&#xff0c;在某些情况下&#xff0c;开发者可能会遇到一个名为AttributeError的问题&#xff0c;特别是…

力扣贪心——跳跃游戏I和II

1 跳跃游戏 利用边界进行判断&#xff0c;核心就是判定边界&#xff0c;边界内所有步数一定是最小的&#xff0c;然后在这个边界里找能到达的最远地方。 1.1 跳跃游戏I class Solution {public boolean canJump(int[] nums) {int len nums.length;int maxDistance 0;int te…

Day36力扣打卡

打卡记录 T 秒后青蛙的位置&#xff08;DFS&#xff09; 链接 class Solution:def frogPosition(self, n: int, edges: List[List[int]], t: int, target: int) -> float:g [[] for _ in range(n 1)]for x, y in edges:g[x].append(y)g[y].append(x)g[1].append(0)ans …

Python编程技巧 – 使用字典

Python编程技巧 – 使用字典 Python Programming Skills – Using Dictionary Dictionary, 即字典&#xff0c;这是Python语言的一种重要的数据结构&#xff1b;Python字典是以键&#xff08;key&#xff09;值(value)对为元素&#xff0c;来存储数据的集合。 前文提到Python列…