c++类与对象

文章目录

  • 前言
  • 一、
    • 1、类的引入
    • 2、类的定义
    • 3、类的访问限定符及封装
    • 4、类的实例化
    • 5、类对象模型
    • 6、this指针
    • 7、封装


前言

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


一、

1、类的引入

C语言结构体中只能定义变量,在C++中,结构体内不仅可以定义变量,也可以定义函数。比如:之前在数据结构初阶中,用C语言方式实现的栈,结构体中只能定义变量;现在以C++方式实现,会发现struct中也可以定义函数。

struct Stack
{void Init(int n){a = (int*)malloc(sizeof(int) * n);if (nullptr == a){perror("malloc申请空间失败\n");return;}capacity = n;size = 0;}void Push(int x){if ((size == capacity) && (size != 0)){a = (int*)realloc(a,sizeof(int) * (2*capacity));}a[size] = x;size++;}int* a;int size;int capacity;
};int main()
{Stack s;s.Init(4);s.Push(1);s.Push(2);s.Push(3);s.Push(4);s.Push(5);return 0;
}

2、类的定义

虽然在c++中可以使用struct来定义类,但是在C++中更喜欢用class来代替。
class为定义类的关键字,ClassName为类的名字,{}中为类的主体,注意类定义结束时后面分号不能省略。
类体中内容称为类的成员:类中的变量称为类的属性或成员变量; 类中的函数称为类的方法或者成员函数。

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

类的两种定义方式

(1). 声明和定义全部放在类体中,需注意:成员函数如果在类中定义,编译器可能会将其当成内联函数处理。

class Stack
{
public:void Init(int n = 4){a = (int*)malloc(sizeof(int) * n);if (nullptr == a){perror("malloc申请空间失败\n");return;}capacity = n;size = 0;}void Push(int x){if ((size == capacity) && (size != 0)){a = (int*)realloc(a,sizeof(int) * (2*capacity));}a[size] = x;size++;}
private:int* a;int size;int capacity;
};int main()
{Stack s;s.Init(4);s.Push(1);s.Push(2);s.Push(3);s.Push(4);s.Push(5);return 0;
}

(2). 类声明放在.h文件中,成员函数定义放在.cpp文件中,注意:成员函数名前需要加类名::。因为类定义了一个新的作用域,类的所有成员都在类的作用域中。在类体外定义成员时,需要使用 :: 作用域操作符指明成员属于哪个类域。
需要注意的是在定义类成员函数时要使用Stack::,即告诉编译器Init方法不是一个全局方法,是在Stack类中的方法。
在这里插入图片描述

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

在上面定义类的成员函数时,我们可以发现在前面加上了public:修饰,如果不加的话就会显示该类的成员函数不可以访问。这时因为类的访问限定问题。例如如果我们创建了一个类,该类有些重要的函数我们不希望在其他地方被调用,此时我们就可以使用类的访问限定符来修饰该函数。用类将对象的属性与方法结合在一块,让对象更加完善,通过访问权限选择性的将其接口提供给外部的用户使用。
访问限定符分为三类
(1). public (公有)
(2). protected (保护)
(3). private (私有)

访问限定符说明

  1. public修饰的成员在类外可以直接被访问
  2. protected和private修饰的成员在类外不能直接被访问(此处protected和private是类似的)
  3. 访问权限作用域从该访问限定符出现的位置开始直到下一个访问限定符出现时为止
  4. 如果后面没有访问限定符,作用域就到 } 即类结束。
  5. class的默认访问权限为private,struct为public(因为struct要兼容C)
    注意:访问限定符只在编译时有用,当数据映射到内存后,没有任何访问限定符上的区别
    所以我们就可以理解为什么前面写的代码中需要在类的成员函数前面加上public修饰,因为类中的默认访问权限都为private,即在外面不能访问类的成员函数,所以要将访问权限改为public,这样才能在外面访问到这些成员函数。
    在这里插入图片描述

4、类的实例化

用类类型创建对象的过程,称为类的实例化
(1). 类是对对象进行描述的,是一个模型一样的东西,限定了类有哪些成员,定义出一个类并没
有分配实际的内存空间来存储它。
(2). 一个类可以实例化出多个对象,实例化出的对象 占用实际的物理空间,存储类成员变量。
可以理解为类的定义就是设计出一个楼房的设计图,此时只有一个设计图,该设计图不占用土地,即类的定义不占用空间,类的实例化就是根据这个设计图来盖出来一栋楼房,此时该楼房占用了土地。一个类可以实例化多个对象就是根据这一个设计图可以盖出来很多楼房。

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._year;   //因为此时没有给该类开辟空间// 类对象实例化 -- 开空间Date d1;Date d2;//d1._year = 200;  //因为该Date类的属性都为private,所以不可以在外面修改d1.Init(2023, 9, 8);return 0;
}

5、类对象模型

一个类中既可以有成员变量,又可以有成员函数,那么一个类的对象中包含了什么?如何计算一个类的大小?
可以看到使用sizeof()来计算类的大小只是计算了类的成员变量的大小,并没有计算类的成员函数的大小。
在这里插入图片描述
为什么需要这样存储呢?这是因为每个对象中成员变量的值是不同的,但是调用同一份函数,如果按照此种方式存储,当一个类实例化多个对象时,每个对象中都会保存一份函数代码,相同代码保存多次,浪费空间。所以在类实例化为对象时,每个对象的成员变量不一样,所以独立存储,但是每个对象调用的成员函数是一样的,所以将成员函数放到共享公共区域(代码段)。
下面的图片中可以看到当类中没有成员变量时,该类也占用了1个字节空间,这1字节空间并不存储有效数据,而是占位用,标识该对象被实例化定义了出来。因为如果不标识的话,如果调用A2中的f2方法就没法调用了,因为根本没有空间,不知道在哪里调用。
在这里插入图片描述
结论
一个类的大小,实际就是该类中”成员变量”之和,当然要注意内存对齐。注意空类的大小,空类比较特殊,编译器给了空类一个字节来唯一标识这个类的对象。

6、this指针

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;Date d2;d1.Init(2023, 9, 8);d2.Init(2021, 6, 6);d1.Print();d2.Print();return 0;
}

在这里插入图片描述
通过上面的代码和运行结果我们可以看到不同的对象调用Print()方法打印出来的数据是不一样的,函数体中没有关于不同对象的区分,那当d1调用 Init 函数时,该函数是如何知道应该设置d1对象,而不是设置d2对呢?

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

this指针的特性
(1). this指针的类型:类类型* const,即成员函数中,不能给this指针赋值。
(2). 只能在“成员函数”的内部使用。
(3). this指针本质上是“成员函数”的形参,当对象调用成员函数时,将对象地址作为实参传递给this形参。所以对象中不存储this指针。
(4). this指针是“成员函数”第一个隐含的指针形参,一般情况由编译器通过ecx寄存器自动传递,不需要用户传递。
在这里插入图片描述
那么this指针存在哪里呢?
答:栈里面,因为this指针实际就是类成员函数的形参,而当调用函数时,函数的形参都存在栈里面,所以this指针也在栈里面,this指针本质就是一个类指针,只不过该指针不需要我们自己定义,而是编译器自动给我们增加的一个隐藏指针参数,我们只需要知道this指针的存在,然后使用它即可。

空指针问题
在下面的代码中,对空指针调用func()函数可以正常执行,而对空指针调用Init和Print函数会报空指针异常,这是因为在Init函数中对this指针进行了解引用,而this指针此时是一个nullptr空指针,所以对空指针解引用会报错。而ptr->func()和(*ptr).func()不报错是因为没有对该空指针进行解引用,编译器解引用时会先判断右边要访问的内容在不在这个对象里面,如果在这个对象里面才会解引用,而不在这个对象里面就不会进行解引用,而func()函数的本质不在对象里面,所以没有解引用。

class Date
class Date
{
public://成员函数//void Init(Date* this, int year, int month, int day)void Init(int year, int month, int day){this->_year = year;this->_month = month;this->_day = day;}//void Print(Date* this)void Print(){cout << this->_year << " " << this->_month << " " << this->_day << endl;}void func(){cout << "func()" << endl;}private://成员变量/类的属性int _year;int _month;int _day;
};
int main()
{Date d1;Date d2;d1.Init(2023, 9, 8);d2.Init(2021, 6, 6);d1.Print();d2.Print();Date* ptr = nullptr;ptr->func();  //正常运行(*ptr).func();  //正常运行ptr->Init(2023, 3, 4);  //报错return 0;
}

7、封装

面向对象的三大特性:封装、继承、多态
封装:将数据和操作数据的方法进行有机结合,隐藏对象的属性和实现细节,仅对外公开接口来
和对象进行交互。
例如在c语言中我们实现栈操作时,数据和方法是分离的,即在使用时,我们先需要创建一个Stack结构体变量,然后给该变量初始化,然后调用一系列方法对栈进行操作,并且这些方法什么位置都可以调用,可以在任何时候使用int top = StackTop(&st)来获取栈的栈顶元素,这样是不安全的。

#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
#include<stdbool.h>typedef int STDataType;typedef struct Stack
{STDataType* data;int top;int capacity;
}Stack;void StackInit(Stack* ps);void StackPush(Stack* ps, STDataType x);void StackPop(Stack* ps);bool StackIsEmpty(Stack* ps);STDataType StackTop(Stack* ps);
#define _CRT_SECURE_NO_WARNINGS
#include"Stack.h"void StackInit(Stack* ps)
{assert(ps);ps->data = NULL;ps->top = 0;ps->capacity = 0;
}void CheckCapacity(Stack* ps)
{assert(ps);int newCapacity = ps->capacity = 0 ? 4 : ps->capacity * 2;if (ps->top == ps->capacity){STDataType* tmp = (STDataType*)realloc(ps->data, newCapacity * sizeof(STDataType));if (tmp == NULL){perror("realloc fail");exit(-1);}ps->data = tmp;ps->capacity = newCapacity;}
}void StackPush(Stack* ps, STDataType x)
{assert(ps);CheckCapacity(ps);ps->data[ps->top] = x;ps->top++;
}void StackPop(Stack* ps)
{assert(ps);assert(!StackIsEmpty(ps));ps->top--;
}bool StackIsEmpty(Stack* ps)
{assert(ps);if (ps->top == 0){return true;}return false;
}STDataType StackTop(Stack* ps)
{assert(ps);assert(!StackIsEmpty(ps));return ps->data[ps->top - 1];
}
#define _CRT_SECURE_NO_WARNINGS
#include"Stack.h"int main()
{Stack st;StackInit(&st);StackPush(&st, 1);StackPush(&st, 2);StackPush(&st, 3);StackPush(&st, 4);StackPush(&st, 5);StackPush(&st, 6);printf("%d\n", StackTop(&st));StackPop(&st);printf("%d\n", StackTop(&st));return 0;
}

而在c++中栈可以这样实现。

#include<assert.h>
#include<stdlib.h>
#include<stdbool.h>typedef int STDataType;typedef struct Stack
{STDataType* data;int top;int capacity;
}Stack;void StackInit(Stack* ps);void StackPush(Stack* ps, STDataType x);void StackPop(Stack* ps);bool StackIsEmpty(Stack* ps);STDataType StackTop(Stack* ps);
#include"Stack.h"void StackInit(Stack* ps)
{assert(ps);ps->data = NULL;ps->top = 0;ps->capacity = 0;
}void CheckCapacity(Stack* ps)
{assert(ps);int newCapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;if (ps->top == ps->capacity){STDataType* tmp = (STDataType*)realloc(ps->data, newCapacity * sizeof(STDataType));if (tmp == NULL){perror("realloc fail");exit(-1);}ps->data = tmp;ps->capacity = newCapacity;}
}void StackPush(Stack* ps, STDataType x)
{assert(ps);CheckCapacity(ps);ps->data[ps->top] = x;ps->top++;
}void StackPop(Stack* ps)
{assert(ps);assert(!StackIsEmpty(ps));ps->top--;
}bool StackIsEmpty(Stack* ps)
{assert(ps);if (ps->top == 0){return true;}return false;
}STDataType StackTop(Stack* ps)
{assert(ps);assert(!StackIsEmpty(ps));return ps->data[ps->top - 1];
}
#include"Stack.h"int main()
{Stack st;StackInit(&st);StackPush(&st, 1);StackPush(&st, 2);StackPush(&st, 3);StackPush(&st, 4);StackPush(&st, 5);StackPush(&st, 6);printf("%d\n", StackTop(&st));StackPop(&st);printf("%d\n", StackTop(&st));return 0;
}

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

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

相关文章

@Autowired为什么会报错?如何解决?

作者 | 磊哥 来源 | Java中文社群&#xff08;ID&#xff1a;javacn666&#xff09; 转载请联系授权&#xff08;微信ID&#xff1a;GG_Stone&#xff09; Autowired报错信息相信大部分程序员都遇到过&#xff0c;奇怪的是虽然代码报错&#xff0c;但丝毫不影响程序的正常执行&…

go work 不同包下mod + work实现.go文件的互相调用

一、文件架构 . ├── go.mod ├── go.work ├── main │ └── main.go └── util├── go.mod└── util.go其中go.mod module testgo 1.21.0其中go.work go 1.21.0use (../util )main/main.go 1 package main …

QT day5

服务器&#xff1a; #include "widget.h" #include "ui_widget.h"Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget) {ui->setupUi(this);//给服务器指针实例化对象server new QTcpServer(this); }Widget::~Widget() {delete ui…

无涯教程-JavaScript - BIN2DEC函数

描述 BIN2DEC函数将二进制数字转换为十进制。 语法 BIN2DEC (number)争论 Argument描述Required/Optionalnumber 您要转换的二进制数。 Number cannot contain more than 10 characters (10 bits). 数字的最高有效位是符号位。其余的9位是幅度位。 负数使用二进制补码表示。…

切片机制和MR工作机制

InputFormat基类 TextInputFormat&#xff1a;TextInputFormat是默认的FileInputFormat实现类。按行读取每条记录。键是存储该行在整个文件中的起始字节偏移量&#xff0c; LongWritable类型。 CombineTextInputFormat&#xff1a;CombineTextInputFormat用于小文件过多的场景…

Calico IP In IP模拟组网

Calico IP In IP模拟组网 网络架构 模拟组网 先在k8s-master-1节点执行如下命令&#xff1a; # 创建veth-pair设备对ip link add veth1 type veth peer name eth0# 创建ns1网络命名空间ip netns add ns1# 将eth0网卡插入ns1网络命名空间ip link set eth0 netns ns1# 为ns1网…

润和软件HopeStage与上海瑞美云LIS系统管理软件完成产品兼容性互认证

近日&#xff0c;江苏润和软件股份有限公司&#xff08;以下简称“润和软件”&#xff09;HopeStage 操作系统与上海瑞美电脑科技有限公司&#xff08;以下简称“上海瑞美”&#xff09;瑞美云LIS系统管理软件完成产品兼容性测试。 测试结果表明&#xff0c;企业级通用操作系统…

为什么零基础选择语言首选python

在众多编程语言中&#xff0c;似乎已经没有什么能够阻挡Python的步伐。本月Python又是第一名&#xff0c;市场份额达到了13.42%&#xff0c;在2023年&#xff0c;Python已经连续7个月蝉联榜首&#xff0c;遥遥领先于其他对手。 每个月榜单发布后&#xff0c;都有小伙伴会好奇&…

基于docker环境的tomcat开启远程调试

背景&#xff1a; Tomcat部署在docker环境中&#xff0c;使用rancher来进行管理&#xff0c;需要对其进行远程调试。 操作步骤&#xff1a; 1.将容器中的catalina.sh映射出来&#xff0c;便于对其修改&#xff0c;添加远程调试相关参数。 注意&#xff1a;/data/produce2201…

kafka 命令脚本说明以及在java中使用

一、命令行使用 1.1、topic 命令 1、关于topic,这里用window 来示例 bin\windows\kafka-topics.bat2、创建 first topic,五个分区&#xff0c;1个副本 bin\windows\kafka-topics.bat --bootstrap-server localhost:9092 --create --partitions 5 --replication-factor 1 -…

Redis从基础到进阶篇(三)----架构原理与集群演变

目录 一、缓存淘汰策略 1.1 LRU原理 1.2 案例分析 1.3 Redis缓存淘汰策略 1.3.1 设置最⼤缓存 1.3.2 淘汰策略 二、Redis事务 2.1 Redis事务典型应⽤—Redis乐观锁 2.2 Redis事务介绍 2.3 事务命令 2.3.1 MULTI 2.3.2 EXEC 2.3.3 DISCARD 2.3.4 WATCH 2.3.5 UNW…

实时操作系统Freertos开坑学习笔记:(八):信号量、事件标志组、任务通知机制

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、信号量的简介1.信号量与队列的区别&#xff1f; 二、二值信号量及其实例1.什么是二值信号量2.二值信号量相关API函数3.二值信号量实例 三、计数型信号量四、…