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.hqwc.cn/news/190978.html

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

相关文章

算法设计与分析 | 分治棋盘

题目 在一个2^k * 2^k个方格组成的棋盘中&#xff0c;恰有一个方格与其他方格不同&#xff0c;称该方格为一特殊方格&#xff0c;且称该棋盘为一特殊棋盘。在棋盘覆盖问题中&#xff0c;要用图示的4种不同形态的L型骨牌覆盖给定的特殊棋盘上除特殊方格以外的所有方格&#xff0…

解锁OpenAI潜力:OpenAI 全面解析与最佳实践

该项目是由OpenAI公司提供的一个大型代码库&#xff0c;其中包含了各类与OpenAI API相关的代码示例和最佳实践。 此项目名为OpenAI Cookbook&#xff0c;目的是为了帮助使用者更有效地利用OpenAI API&#xff0c;将其应用于自己的工作和生活中。具体来说&#xff0c;可以解决一…

【数字人】7、GeneFace++ | 使用声音驱动的面部运动系数作为 condition 来指导 NeRF 重建说话头

文章目录 一、背景二、相关工作2.1 唇形同步的 audio-to-motion2.2 真实人像渲染 三、方法3.1 对 GeneFace 的继承3.2 GeneFace 的结构3.2.1 Pitch-Aware Audio-to-Motion Transform3.2.2 Landmark Locally Linear Embedding3.2.3 Instant Motion-to-Video Rendering 四、效果 …

二叉树中的深搜之二叉树的所有路径

257. 二叉树的所有路径 - 力扣&#xff08;LeetCode&#xff09; 对于二叉树的深度搜索&#xff0c;要学会从以下三个角度来去看待问题&#xff1a; 1. 全局变量&#xff0c;有时候全局变量会减少参数的个数&#xff0c;简化很多流程&#xff1b; 这道题目&#xff0c;要返回根…

视频怎么做成二维码?在线教学视频码的制作技巧

视频是怎么制作成二维码的呢&#xff1f;现在经常会发现扫描很多的二维码会观看视频内容的情况&#xff0c;这种方式简化视频传递的过程&#xff0c;能够更加简单快捷的在线获取视频内容。对于想要了解视频二维码制作方法的小伙伴&#xff0c;小编通过本篇文章来教大家一招&…

手把手教你用C语言写出“走迷宫”小游戏(能看懂文字就会自己敲系列)

目录 设计迷宫地图 设计主角——小球 完整代码 这次教大家编写一个简单的“走迷宫”小游戏&#xff0c;我们可以通过键盘上的‘W’、‘S’、‘A’、‘D’四个键来控制一个“小球”向上&#xff0c;下&#xff0c;左&#xff0c;右移动&#xff0c;目的就是让这个“小球”从起…

【0基础学Java第十课】-- 认识String类

10. 认识String类 10.1 String类的重要性10.2 常用方法10.2.1 字符串构造10.2.2 String对象的比较10.2.3 字符串查找10.2.4 转化10.2.5 字符串替换10.2.6 字符串拆分10.2.7 字符串截取10.2.8 字符串的不可变性10.2.9 字符串修改 10.3 StringBuilder和StringBuffer10.3.1 String…

制作翻页电子相册,这个工具你必须了解!

电子相册作为一种很有纪念意义的载体&#xff0c;无论是生日、旅行、结婚、毕业纪念等等&#xff0c;可以应用在很多场合当中&#xff0c;如何制作呢&#xff1f; 而对于不会制作电子相册的人来说&#xff0c;使用套用模板是最直接快速的方式了。所以&#xff0c;推荐大家使用…

关于SPJ表的数据库作业

打字不易&#xff0c;且复制且珍惜 建表 use 库名;create table S( --供应商 SNO char(6) not null, SNAME char(10) not null, STATUS INT, CITY char(10), primary key(SNO));create table P( --零件 PNO char(6) not null, PNAME char(12)not null, COLOR char(4), WEIGHT…

小命令,大世界

Linux是一个大系统&#xff0c;功能丰富&#xff0c;好比是一台巨型机器&#xff0c;而命令&#xff0c;就是这台机器的操作台。要想控制好这台机器&#xff0c;用好这台机器&#xff0c;就得会看仪表&#xff0c;会操作各种按钮。《Linux常用命令自学手册》就是介绍如何操作这…

1334. 阈值距离内邻居最少的城市/Floyd 【leetcode】

1334. 阈值距离内邻居最少的城市 有 n 个城市&#xff0c;按从 0 到 n-1 编号。给你一个边数组 edges&#xff0c;其中 edges[i] [fromi, toi, weighti] 代表 fromi 和 toi 两个城市之间的双向加权边&#xff0c;距离阈值是一个整数 distanceThreshold。 返回能通过某些路径…

无重复最长字符串(最长无重复子字符串),剑指offer,力扣

目录 原题&#xff1a; 力扣地址&#xff1a; 我们直接看题解吧&#xff1a; 解题方法&#xff1a; 难度分析&#xff1a; 难度算中下吧&#xff0c;这个总体不算很难&#xff0c;而且滑动窗口&#xff0c;以及哈希都比较常见 审题目事例提示&#xff1a; 解题思路&#xff08;…