C++类与对象(1)—初步认识

目录

一、面向过程和面向对象

二、类

1、定义

2、类的两种定义方式 

3、访问限定符

4、命名规范化 

5、类的实例化

6、计算类对象的大小

7、存储方式

三、this指针 

1、定义 

2、存储位置

3、辨析

 四、封装好处


一、面向过程和面向对象

  • C语言是面向过程的,关注的是过程,分析出求解问题的步骤,通过函数调用逐步解决问题。
  • C++是基于面向对象的,关注的是对象,将一件事情拆分成不同的对象,靠对象之间的交互完成。

面向过程和面向对象是两种主要的编程范式。它们的主要区别在于如何组织代码和处理数据。

1. 面向过程编程:

面向过程编程是一种编程范式,它以过程(也称为函数)为中心,数据和过程是分开的。在面向过程编程中,程序是一系列被调用的函数或过程,数据被视为辅助函数运行的附属物。这种编程范式的主要特点是流程化,按照事物处理的逻辑顺序来编写程序。

例如,假设我们要编写一个程序来制作一杯咖啡。在面向过程的编程中,我们会创建一个函数来热水,一个函数来研磨咖啡豆,一个函数来混合水和咖啡,等等。每个函数都有一个明确的任务,并按照特定的顺序执行。

2. 面向对象编程:

面向对象编程是一种编程范式,它将数据和处理数据的函数组合成一个整体,称为“对象”。在面向对象编程中,程序是由对象组成的。每个对象都包含数据(称为属性)和一组处理数据的函数(称为方法)。这种编程范式的主要特点是封装性,继承性和多态性。

还是以制作咖啡为例,面向对象编程会创建一个“咖啡”对象,这个对象有一些属性(如水,咖啡豆等)和一些方法(如热水,研磨咖啡豆,混合水和咖啡等)。所有的操作都是通过操作对象的方法来完成的。

二、类

C语言结构体中只能定义变量,而在C++中,结构体不仅可以定义变量,也可以定义函数。

在C++中把结构体升级成了类,而且结构体的名字就是类型。

//C++兼容C结构体用法
typedef struct ListNode
{int val;//C struct ListNode是类型struct ListNode* next;
}LN;struct ListNode
{int val;//C++ ListNode是类型ListNode* next;
};

1、定义

class className
{// 类体:由成员函数和成员变量组成};  // 一定要注意后面的分号
  • class为定义类的关键字(C语言中用struct),ClassName为类的名字,{}中为类的主体,注意类定义结束时后面分号不能省略。
  • 类体中内容称为类的成员:类中的变量称为类的属性或成员变量; 类中的函数称为类的方法或者成员函数。

2、类的两种定义方式 

在之前数据结构中学过的栈,因为用C语言实现,结构体中只能定义变量,现在我们可以通过C++的方式实现栈,这样在结构体中也可以定义函数。  

第一种:声明和定义全部放在类体中 

注意:

  • 成员函数和成员变量定义在类中的位置没有要求,在调用时会在整个类中查找,不会像类之外使用变量或函数时,编译器只会向上查找。 
  • 成员函数如果在类中定义,编译器可能会将其当成内联函数处理。

这里出现了public和private,他们是访问限定符,稍后进行讲解。 

class Stack
{
public:// 成员函数void Init(int n = 4)//缺省参数{a = (int*)malloc(sizeof(int) * n);if (nullptr == a){perror("malloc fail");return;}capacity = n;size = 0;}void Push(int x){//...a[size++] = x;}
private:		// 成员变量int* a;int size;int capacity;
};
int main()
{Stack st;st.Init();st.Push(1);st.Push(2);st.Push(3);return 0;
}

第二种:类声明放在.h文件中,成员函数定义放在.cpp文件中(常用这种)

 头文件中:

//struct Stack无需访问限定符
class Stack//必须有访问限定符,否则报错
{
public:// 成员函数void Init(int capacity = 4);void Push(int x);private:// 成员变量int* a;int size;int capacity;
};

源文件中:

函数名前要加类名 : : (域作用限定符)。

#include "Stack.h"void Stack::Init(int n)
{a = (int*)malloc(sizeof(int) * n);if (nullptr == a){perror("malloc申请空间失败");return;}capacity = n;size = 0;
}void Stack::Push(int x)
{//...a[size++] = x;
}

3、访问限定符

在头文件中使用 stuct 定义类:

struct Stack
{void Init(int capacity = 4);void Push(int x);int* a;int size;int capacity;
};

成功编译: 

在头文件中使用 class 定义类:

class Stack
{void Init(int capacity = 4);void Push(int x);int* a;int size;int capacity;
};

 结果编译后报错如下:

我们可以发现成员函数无法访问类的成员,这是因为未加访问限定符的class中默认访问权限为私有private,stuct 默认为公有public。

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

 

 

访问权限符说明:

  • public修饰的成员在类外可以直接被访问
  • protected和private修饰的成员在类外不能直接被访问(此处protected和private是类似的)
  • 访问权限作用域从该访问限定符出现的位置开始直到下一个访问限定符出现时为止
  • 如果后面没有访问限定符,作用域就到 } 即类结束。
  • class的默认访问权限为private,struct为public(因为struct要兼容C)
注意:访问限定符只在编译时有用,当数据映射到内存后,没有任何访问限定符上的区别

 

4、命名规范化 

我们来看这个代码:

class Date
{
public:void Init(int year, int month, int day){year = year;month = month;day = day;}
private:int year;int month;int day;
};

 在这个代码中,存在一个问题。在Init函数中,你试图将传入的参数赋值给类的成员变量,但是由于参数和成员变量的名称相同,会导致赋值操作无效。

我们可以选择修改参数名或类成员名,比较好的是在成员名前加上符号_ ,这样小改动保证成员名和函数参数名之间的关系。 

class Date
{
public:void Init(int year, int month, int day){_year = year;_month = month;_day = day;}
private:int _year;int _month;int _day;
};

5、类的实例化

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

  • 类是对对象进行描述的,是一个模型一样的东西,限定了类有哪些成员,定义出一个类并没有分配实际的内存空间来存储它;比如:入学时填写的学生信息表,表格就可以看成是一个类,来描述具体学生信息。
  • 一个类可以实例化出多个对象,实例化出的对象占用实际的物理空间、存储类成员变量。

类中成员变量是声明,没有分配空间。

class Date
{
public:void Init(int year, int month, int day){_year = year;_month = month;_day = day;}
private:int _year;int _month;int _day;
};

 只有当我们使用类创建一个对象也就是”类的实例化“时,成员变量才会被分配空间,存储成员变量。

int main()
{Date d1;//实例化d1.Init(2023, 2, 3);return 0;
}

 我们通过下面这段代码看看类实例化之后,有没有占用实际的物理空间。

class A2 {
public:void f2() {}
};int main()
{A2 aa1;A2 aa2;//实例化就能打印地址cout << &aa1 << endl;cout << &aa2 << endl;return 0;
}

通过打印地址可以发现,实例化后变量确实被分配空间用来存储类成员变量了。 

 

6、计算类对象的大小

类的大小也遵循与结构体一样的计算方式,详细请看这篇文章:结构体内存对齐

7、存储方式

我们先来看下面这段代码:

class Date
{
public:void Init(int year, int month, int day){_year = year;_month = month;_day = day;}private:int _year; int _month;int _day;
};int main()
{Date d1;d1.Init(2023, 2, 2);//打印d1的大小cout << sizeof(d1) << endl;return 0;
}

我们由输出结果可知: d1的大小为12字节,由此可以推断出类的对象d1中只存储了成员变量。

 

那么为什么成员变量在对象中,成员函数不在对象中呢?

因为每个对象成员变量是不一样的,需要独立存储;每个对象调用成员函数是一样的,它们被放到共享公共区域(代码段) 。

class A2 {
public:void f2() {}
};int main()
{A2 a;cout << sizeof(a) << endl;return 0;
}

类中只有成员函数,这样定义的对象A2大小是1字节,这1字节不存储有效数据,是用来占位的,标识对象被实例化定义出来了。 

三种类的大小对比

// 类中既有成员变量,又有成员函数
class A1 {
public:void f1(){}
private:int a;
};// 类中仅有成员函数
class A2 {
public:void f2(){}
};// 类中什么都没有---空类
class A3
{};
  • 一个类的大小,实际就是该类中”成员变量”之和,当然要注意内存对齐
  • 注意空类的大小,空类比较特殊,编译器给了空类一个字节来唯一标识这个类的对象。

 

三、this指针 

class Date{
public:void Init(int year, int month, int day){_year = year;_month = month;_day = day;cout << this << endl;}private:int _year; int _month;int _day;
};int main()
{Date d1;Date d2;d1.Init(2022, 2, 2);d2.Init(2023, 2, 2);return 0;
}
对于上述类,有这样的一个问题:
Date类中有 Init 成员函数,函数体中没有关于不同对象的区分,那当d1调用 Init 函数时,该函数是如何知道应该设置d1对象,而不是设置d2对象呢?

1、定义 

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

我们在初始化Init函数中打印this指针,看看this指针有没有发挥作用。

class Date
{
public:void Init(int year, int month, int day){cout << this << endl;//可以显式使用this,不能显式定义thisthis->_year = year;this->_month = month;this->_day = day;}private:int _year;int _month;int _day;
};int main()
{Date d1;Date d2;d1.Init(2022, 2, 2);d2.Init(2023, 2, 2);return 0;
}

 输出了两个地址,我们通过调试检查一下这两个地址是不是对象d1和d2的地址。

 

在调试中地址如下,上下两幅图对比可以得知,每次初始化时,this指针会指向参数的地址。

在实际中类的成员函数一般不需要加this指针。 

2、存储位置

this存在哪里?

栈,因为他是隐含形参,vs下在ecx寄存器中。

 通过之前计算类的对象大小不包括this,所以this在栈上,它是一个隐含的形参。

 3、辨析

class Date
{
public:void Init(int year, int month, int day){cout << this << endl;//可以显式使用this,不能显式定义thisthis->_year = year;this->_month = month;this->_day = day;}void func(){cout << "func()" << endl;}private:int _year;int _month;int _day;
};int main()
{Date* ptr = nullptr;ptr->func();return 0;
}

程序正常运行: 

 

如果调用这句呢? 

ptr->Init(2022, 2, 2);

结果运行崩溃:

 

下面来解释这两种情况: 

  1. ptr->func()这行代码尝试通过空指针ptr调用成员函数func()。虽然ptr是空指针,但是在这种情况下,由于func()函数没有使用或修改任何成员变量,它可以被静态调用。这意味着它不依赖于具体的对象实例,不需要借助this指针,因此不会引发崩溃。

  2. ptr->Init(2022, 2, 2)这行代码尝试通过空指针ptr调用成员函数Init()。由于Init()函数内部使用了this指针来访问对象的成员变量,而空指针没有有效的对象实例,对空指针的解引用无效,因此在访问this->_yearthis->_monththis->_day时会导致程序崩溃。

同理这段代码也可以正常运行: 

(*ptr).func();

 

 四、封装好处

C++中: 

  1. 数据和方法都封装到类里面
  2. 控制访问方式,愿意给你访问公有,不愿意给你访问私有。

C语言中: 

  1. 数据和方法是分离的
  2. 数据访问控制是自由的,不受限制的

 C++实现栈:

typedef int DataType;
class Stack
{
public:void Init(){_array = (DataType*)malloc(sizeof(DataType) * 3);if (NULL == _array){perror("malloc申请空间失败!!!");return;}_capacity = 3;_size = 0;}void Push(DataType data){CheckCapacity();_array[_size] = data;_size++;}void Pop(){if (Empty())return;_size--;}DataType Top() { return _array[_size - 1]; }int Empty() { return 0 == _size; }int Size() { return _size; }void Destroy(){if (_array){free(_array);_array = NULL;_capacity = 0;_size = 0;}}
private:void CheckCapacity(){if (_size == _capacity){int newcapacity = _capacity * 2;DataType* temp = (DataType*)realloc(_array, newcapacity *sizeof(DataType));if (temp == NULL){perror("realloc申请空间失败!!!");return;}_array = temp;_capacity = newcapacity;}}
private:DataType* _array;int _capacity;int _size;
}; ​
C++中通过类可以将数据 以及 操作数据的方法进行完美结合,通过访问权限可以控制那些方法在类外可以被调用,即封装,在使用时就像使用自己的成员一样,更符合人类对一件事物的认知。而且每个方法不需要传递Stack*的参数了,编译器编译之后该参数会自动还原,即C++中 Stack * 参数是编译器维护的,C语言中需用用户自己维护。

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

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

相关文章

4.6每日一题(多元函数的隐函数求导)

三元方程确定的二元函数类型的隐函数 方法一&#xff1a;两边对x求偏导&#xff0c;把y看成常数 注&#xff1a;z可以把x和y同时代入求出答案 方法二&#xff1a;带公式

Python爬虫教程:从入门到实战

更多Python学习内容&#xff1a;ipengtao.com 大家好&#xff0c;我是涛哥&#xff0c;今天为大家分享 Python爬虫教程&#xff1a;从入门到实战&#xff0c;文章3800字&#xff0c;阅读大约15分钟&#xff0c;大家enjoy~~ 网络上的信息浩如烟海&#xff0c;而爬虫&#xff08;…

学习指南:如何快速上手媒体生态一致体验开发

过去开发者们在使用多媒体能力时&#xff0c;往往会遇到这样的问题&#xff0c;比如&#xff1a;为什么我开发的相机不如系统相机的效果好&#xff1f;为什么我的应用和其他的音乐一起发声了&#xff0c;我要怎么处理&#xff1f;以及我应该怎么做才能在系统的播控中心里可以看…

计算机组成原理-双端口RAM和多模块存储器

文章目录 存取周期总览双端口RAM多体并行存储器低地址交叉编址有多少个存储体合适&#xff08;体号&#xff09;多模块存储器&#xff08;多体存储器&#xff09;总结实际场景 存取周期 总览 双端口RAM RAM&#xff1a;用于主存或高速缓存&#xff0c;断电数据丢失 多体并行…

qsort函数使用方法总结

目录 一、qsort函数原型 二、compar参数 三、各种类型的qsort排序 1. int 数组排序 2. 结构体排序 3. 字符串指针数组排序 4. 字符串二维数组排序 四、回调函数 1. 什么是回调函数 2. 为什么要用回调函数&#xff1f; 3. 怎么使用回调函数&#xff1f; 4.下面是…

分支限界法(1)--旅行商问题

一、概述 有n个城市&#xff0c;旅行者要访问所有n个城市&#xff0c;最终回到起始点&#xff0c;假设起始点给定为1&#xff0c;城市间距离已知&#xff0c;求能够完成旅行的最短距离。题干如下图。 算法&#xff1a;分支限界法&#xff0c;使用队列进行bfs搜索。 二、代码 i…

低代码在ERP中的理解与应用:提升开发效率与业务灵活性

企业资源规划&#xff08;ERP&#xff09;指通过融合不同部门的信息和流程&#xff0c;提升企业效率、融洽运营的管理体系。ERP系统通过提供一套集成化应用程序&#xff0c;助力企业管理工作流程&#xff0c;包含选购、库存、销售、生产规划等。 低代码&#xff08;Low-Code&a…

AI电话机器人能否代替人工?优缺点介绍

AI电话机器人是一种基于人工智能技术的自动语音系统&#xff0c;它可以模拟人类的语音交互&#xff0c;执行客服、销售、调查等任务。随着人工智能的发展&#xff0c;AI电话机器人的功能越来越强大&#xff0c;它们在某些领域已经能够替代人工执行任务。那么&#xff0c;AI电话…

在listener.ora配置文件中配置listener 1527的监听并且使用tnsnames连接测试

文章目录 前言&#xff1a;一、命令语句实现1、监听介绍2、编辑 listener.ora 文件&#xff1a;寻找配置文件对配置文件进行配置 3、重启监听4、配置TNS 二、图形化界面实现1、listener.ora文件配置2、tnsnames.ora文件配置 三、测试连接 前言&#xff1a; 命令实现和图形化实…

遥感数据

在研究中&#xff0c;我们常需要遥感数据。在下面的网站中&#xff0c;可以得到遥感数据。 EarthExplorer (usgs.gov)https://earthexplorer.usgs.gov/登陆网站&#xff1a; 通常&#xff0c;在Additional Criteria中&#xff0c;可以下载遥感数据。 不过&#xff0c;这个选项…

安顿APP3.0全新升级,引领智能穿戴健康革新,专注预警疾病风险

随着人们生活水平的提高和工作压力的增加&#xff0c;心脑血管疾病已经成为现代社会的严重问题&#xff0c;特别是心梗、脑卒中等疾病已经开始夺去年轻人的生命。 据报道&#xff0c;近年来&#xff0c;多位年轻人因心脑血管疾病突发去世&#xff0c;如42岁的知名男演员、30岁的…

【C刷题】day7

&#x1f3a5; 个人主页&#xff1a;深鱼~&#x1f525;收录专栏&#xff1a;【C】每日一练&#x1f304;欢迎 &#x1f44d;点赞✍评论⭐收藏 一、选择题 1、以下对C语言函数的有关描述中&#xff0c;正确的有【多选】&#xff08; &#xff09; A: 在C语言中&#xff0c;一…

AI语音克隆

安装 下载安装github代码库 git clone https://github.com/Plachtaa/VITS-fast-fine-tuning.git安装文档 中日语言模型网站 目前支持的任务: 从 10条以上的短音频 克隆角色声音从 3分钟以上的长音频&#xff08;单个音频只能包含单说话人&#xff09; 克隆角色声音从 3分钟以…

2311rust,到60版本更新

1.54.0稳定版 属性可调用类似函数的宏 Rust1.54支持在属性中调用类似函数的宏.类似函数的宏是像基于macro_rules!宏一样调用的或像macro!(...)一样的过程宏. 注意,常见用例是,在Rust文档注解中包含其他文件中的文档.如,如果项目的README代表了一个很好的文档注释,则可用incl…

对于可变参数的处理

可变参数&#xff1a;可变参数“...”根据前面参数的地址确定可变参的地址&#xff0c;所以可变参前面至少要有一个参数来确定可变参的地址&#xff1b;接口函数&#xff1a; #include <stdarg.h>void va_start(va_list ap, last); //申请空间&#xff0c;自动获取栈类型…

java--拼图游戏

1、了解拼图游戏基本功能&#xff1a; 拼图游戏内容由若干小图像块组成的&#xff0c;通过鼠标点击图像块上下左右移动&#xff0c;完成图像的拼凑。 2、拼图游戏交互界面设计与开发&#xff1a; 通过创建窗体类、菜单、中间面板和左右面板完成设计拼图的交互界面 &#xff…

0069【Edabit ★☆☆☆☆☆】【求一个数的N次方】To the Power of _____

0069【Edabit ★☆☆☆☆☆】【求一个数的N次方】To the Power of _____ logic loops math numbers Instructions Create a function that takes a base number and an exponent number and returns the calculation. Examples calculateExponent(5, 5) // 3125 calculateEx…

观察者模式的运用——消息队列

观察者模式是一种行为设计模式&#xff0c;它定义了一种一对多的依赖关系&#xff0c;让多个观察者对象同时监听某一个主题对象&#xff0c;当主题对象发生变化时&#xff0c;会自动通知所有观察者对象。观察者模式广泛应用于事件处理、用户界面更新和异步消息传递等情境中。 …

ChatGPT之父被OpenAI解雇

首席技术官 Mira Murati 任命临时首席执行官领导 OpenAI&#xff1b;山姆阿尔特曼&#xff08;Sam Altman&#xff09;离开公司。 阿尔特曼先生的离职是在董事会经过深思熟虑的审查程序之后进行的&#xff0c;审查程序得出的结论是&#xff0c;他在与董事会的沟通中始终不坦诚…

java 实现串口通讯

1、引入依赖 <dependency><groupId>org.scream3r</groupId><artifactId>jssc</artifactId><version>2.8.0</version> </dependency>2、配置启动串口 Component public class ContextHolder implements ApplicationContextAw…