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.rhkb.cn/news/125308.html

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

相关文章

【办公类-16-01-02】2023年度上学期“机动班下午代班的排班表——跳过周三、节日和周末”(python 排班表系列)

背景需求&#xff1a; 2023年第一学期&#xff08;2023年9-2024年1月&#xff09;&#xff0c;我又被安排为“机动班”&#xff0c;根据新学期的校历&#xff0c;手动推算本学期的机动班的带班表 排版原则 1、班级数量&#xff1a;共有6个班级&#xff0c;循环滚动 2、每周次…

说说MySQL回表查询与覆盖索引

分析&回答 什么是回表查询&#xff1f; 通俗的讲就是&#xff0c;如果索引的列在 select 所需获得的列中&#xff08;因为在 mysql 中索引是根据索引列的值进行排序的&#xff0c;所以索引节点中存在该列中的部分值&#xff09;或者根据一次索引查询就能获得记录就不需要…

二叉树的存储结构

&#x1f4d9;作者简介&#xff1a; 清水加冰&#xff0c;目前大二在读&#xff0c;正在学习C/C、Python、操作系统、数据库等。 &#x1f4d8;相关专栏&#xff1a;C语言初阶、C语言进阶、C语言刷题训练营、数据结构刷题训练营、有感兴趣的可以看一看。 欢迎点赞 &#x1f44d…

Android lint配置及使用

关于作者&#xff1a;CSDN内容合伙人、技术专家&#xff0c; 从零开始做日活千万级APP。 专注于分享各领域原创系列文章 &#xff0c;擅长java后端、移动开发、商业变现、人工智能等&#xff0c;希望大家多多支持。 目录 一、导读二、概览三、将 lint 配置为不显示警告3.1 在 A…

多维时序 | MATLAB实现GWO-GRU灰狼算法优化门控循环单元的多变量时间序列预测

多维时序 | MATLAB实现GWO-GRU灰狼算法优化门控循环单元的多变量时间序列预测 目录 多维时序 | MATLAB实现GWO-GRU灰狼算法优化门控循环单元的多变量时间序列预测预测效果基本介绍程序设计参考资料 预测效果 基本介绍 MATLAB实现基于GWO-GRU灰狼算法优化门控循环单元的多变量时…

船舶稳定性和静水力计算——绘图体平面图,静水力,GZ计算(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

Java入门第三季

一、异常与异常处理 1. 异常简介 在Java中&#xff0c;**异常是程序在执行过程中出现的问题或意外情况&#xff0c;导致程序无法按照预期的流程进行。**异常处理是Java中用于处理程序中出现的异常的一种机制。 Java中的异常可以分为两大类&#xff1a;受检查的异常&#xff…

2023国赛数学建模B题思路分析 - 多波束测线问题

# 1 赛题 B 题 多波束测线问题 单波束测深是利用声波在水中的传播特性来测量水体深度的技术。声波在均匀介质中作匀 速直线传播&#xff0c; 在不同界面上产生反射&#xff0c; 利用这一原理&#xff0c;从测量船换能器垂直向海底发射声波信 号&#xff0c;并记录从声波发射到…

Linux服务使用宝塔面板搭建网站,并发布公网访问 - 内网穿透

文章目录 前言1. 环境安装2. 安装cpolar内网穿透3. 内网穿透4. 固定http地址5. 配置二级子域名6. 创建一个测试页面 前言 宝塔面板作为简单好用的服务器运维管理面板&#xff0c;它支持Linux/Windows系统&#xff0c;我们可用它来一键配置LAMP/LNMP环境、网站、数据库、FTP等&…

【ALM工具软件】上海道宁与Perforce为您带来用于整个生命周期的应用程序生命周期管理软件

Helix ALM是 用于整个生命周期的 应用程序生命周期管理的ALM软件 具有专用于 需求管理&#xff08;Helix RM&#xff09;、测试用例管理&#xff08;Helix TCM&#xff09; 问题管理&#xff08;Helix IM&#xff09;的功能模块 Helix ALM提供了 无与伦比的可追溯性 您将…

【解决】mysqladmin flush-hosts

问题 mysql出现 mysqladmin flush-hosts&#xff0c;是因为其他客户机连接错误次数过多时&#xff0c;mysql会禁止客户机连接。 解决方法 1、进入服务器数据库&#xff0c;打开数据库命令行界面输入 flush hosts; 此时便可连接 2、可以.修改mysql配置文件&#xff0c;在[…

LeetCode(力扣)46. 全排列Python

LeetCode46. 全排列 题目链接代码 题目链接 https://leetcode.cn/problems/permutations/ 代码 class Solution:def backtracking(self, nums, result, path, used):if len(path) len(nums):result.append(path[:])for i in range(len(nums)):if used[i]:continuepath.app…

2023年MySQL-8.0.34保姆级安装教程

重点放前面&#xff1a;演示环境为windows环境。 MySQL社区版本安装教程如下&#xff1a; 一、MySQL安装包下载二、安装配置设置三、配置环境变量 大体分为3个步骤&#xff1a;①安装包的下载&#xff1b;②安装配置设置&#xff1b;③配置环境变量 一、MySQL安装包下载 下载官…

docker 生成镜像的几个问题

docker 生成镜像的几个问题 根据jdk8.tar.gz 打包Jdk8 镜像失败运行镜像报错差不多是网络ip错误,在网上说重启docker即可解决运行mysql5.7.25 镜像失败向daemon.json文件添加内容导致docker重启失败docker run 命令常用参数根据jdk8.tar.gz 打包Jdk8 镜像失败 首选做准备工作…

核货宝:收银系统后台一般是怎样的,有哪些功能

收银系统后台是一个重要的管理工具&#xff0c;它为企业提供了对收银机的全面控制和配置。收银系统后台是一个用于管理和配置收银机的软件界面。它通常由以下几个主要部分组成&#xff1a; 1. 登录和权限管理 收银系统后台需要一个安全的登录系统&#xff0c;以确保只有授权人…

使用命令行创建仓库

如果你还没有任何代码&#xff0c;可以通过命令行工具创建一个全新的Git仓库并初始化到本项目仓库中。 git clone https://e.coding.net/***/neurosens.git cd neurosens echo "# neurosens" >> README.md git add README.md git commit -m "first commi…

Pygame中Trivia游戏解析6-1

1 Trivia游戏简介 Trivia的含义是“智力测验比赛中的各种知识”。Trivia游戏类似智力竞赛&#xff0c;由电脑出题&#xff0c;玩家进行作答&#xff0c;之后电脑对玩家的答案进行判断&#xff0c;给出结果并进行评分。该游戏的界面如图1所示。 图1 Trivia游戏界面 2 游戏流程 …

基于SpringBoot的社团管理系统

基于SpringBootVue的社团管理系统&#xff0c;前后端分离 开发语言&#xff1a;Java数据库&#xff1a;MySQL技术&#xff1a;SpringBoot、Vue、Mybaits Plus、ELementUI工具&#xff1a;IDEA/Ecilpse、Navicat、Maven 角色&#xff1a;普通用户、管理员 管理员&#xff1a;…

咪蒙团队转型做短剧行业,年收入近2个亿

我是卢松松&#xff0c;点点上面的头像&#xff0c;欢迎关注我哦&#xff01; 很多人不知道咪蒙是谁&#xff0c;他曾经是公众号时代的no.1&#xff0c;她发一篇带广告的推文大几十万, 那个时候不知道带动多少人去做公众号,2019年发表不恰当文章而被封禁。 但最近我看到一则新…

【数据结构】二叉树的顺序结构实现及时间复杂度计算(二)

目录 一&#xff0c;二叉树的顺序结构实现 1&#xff0c;二叉树的顺序结构 2&#xff0c;堆的概念及结构 3&#xff0c;堆的接口实现 1&#xff0c;堆的创建 2&#xff0c;接口函数 3&#xff0c;初始化 4&#xff0c;销毁 5&#xff0c;是否增容 6&#xff0c;交换数据…