【c++篇】:解析c++类--优化编程的关键所在(一)

devtools/2024/10/21 7:49:18/

文章目录

  • 前言
  • 一.面向过程和面向对象
  • 二.c++中的类
    • 1.类的引入
    • 2.类的定义
    • 3.类的封装和访问限定符
    • 4.类的作用域
    • 5.类的实例化
    • 6.类对象模型
  • 三.`this`指针
    • 1.`this`指针的引出
    • 2.`this`指针的特性
    • 3.C语言和c++实现栈Stack的对比

前言

在程序设计的广袤宇宙中,C++以其强大的功能和灵活性,成为众多开发者手中的利器。C++不仅继承了C语言的高效和直接操作硬件的能力,还引入了面向对象编程的概念,让代码的组织和管理变得更加清晰和高效。而在C++的面向对象体系中,类(class)无疑是最核心的概念之一。而本篇文章将初步学习类(class)。

一.面向过程和面向对象

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

c++是面向对象的,关注的是对象,将一件事情拆分成不同的对象,靠对象之间交互完成。

比如一个外卖系统,C语言关注的是点餐,下单,付款,送单等一系列的过程,而c++则关注的是顾客,商家,骑手等对象之间的交互。

但c++中的对象又是什么呢?

带着这个问题我们先来了解一下什么是类Class

二.c++中的类

1.类的引入

在C语言中我们知道结构体struct中可以定义变量,而在c++中,struct升级成了类,结构体中不仅可以定义变量,也可以定义函数,同时可以直接用类名不带struct。以之前用C语言写的栈为例,用c++方式实现:

#include<stdio.h>
#include<stdlib.h>
#include<iostream>
using namespace std;
typedef int STData;
struct Stack {//成员函数void StackInit(int capacity=4) {_arr = (STData*)malloc(sizeof(STData) * capacity);if (_arr == nullptr) {perror("malloc fail");return;}_top = 0;_capacity = capacity;}void StackPush(STData x) {if (_top == _capacity) {STData* tmp = (STData*)realloc(_arr,sizeof(STData) * _capacity * 2);if (tmp == nullptr) {perror("malloc fail");return;}_arr = tmp;_capacity *= 2;}_arr[_top++] = x;}void StackPop() {if (_top == 0) {return;}_top--;}STData StackTop() {if (_top == 0) {return NULL;}return _arr[_top-1];}void StackDestroy() {free(_arr);_arr = nullptr;_top = 0;_capacity = 0;}//成员变量STData* _arr;int _top;int _capacity;
};
int main() {Stack st;st.StackInit(10);st.StackPush(1);st.StackPush(2);st.StackPush(3);st.StackPush(4);st.StackPush(5);st.StackPush(6);while (st._top != 0) {cout << st.StackTop() << " ";st.StackPop();}st.StackDestroy();return 0;}

上面用结构体的定义,在c++中更喜欢用class来替代。

2.类的定义

类通过关键字class来定义,后面跟着类名classname和一对花括号{},花括号内的为类的主体也就是类的成员声明。注意花括号后的;不要省略。

class classname{//成员函数void fun(){....}....//成员变量int _a;char _b;....
};

类体中的内容为类的成员:

  • 类中的变量称为类的属性或者成员变量,这些是类的数据部分,用于存储对象的状态信息。
  • 类中的函数称为类的方法或者成员函数,这些是类的行为部分,用于描述对象可执行的操作。成员函数可以访问和修改成员变量的值。

类有两种定义方式:

  • 声明和定义全部放在类体中:

    class Data{void dataprint(){cout<<_year<<_month<<_day<<endl;}int _year;int _month;int _day;
    };
    
  • 声明(.h)和定义(.cpp)放在不同的文件中:

    在.cpp文件定义时,成员函数名前要加类名::

    //.h文件
    class Data{//声明void Dataprint();int _year;int _month;int _day;
    };
    //.cpp文件
    //定义
    void Data::Dataprint(){cout<<_year<<_month<<_day<<endl;
    }
    

成员变量命名规则建议:

如果成员变量名和成员函数参数名相同时就会容易混淆不易区分,比如:

class Data{void Dataprint(int year){year=year;cout<<year<<month<<day<<endl;}int year;int month;int day;
};

为了解决这种情况,通常习惯加一些字符,比如一些公司要求成员变量名前加_,有的会将_加在成员变量名后,有的也会用其他字符区分。主要还是看公司要求,我们在日常练习时,可以根据自己喜好来设定。

class Data{void Dataprint(int year){_year=year;cout<<_year<<_month<<_day<<endl;}int _year;int _month;int _day;
};

3.类的封装和访问限定符

前面我们了解到c++是面向对象的,而面向对象具有三大特性:

封装,继承,多态

在类和对象阶段,我们首先来学习类的封装特性。

封装是面向对象编程的一个基本原则,在c++语言实现封装,可以通过类将数据以及操作数据的方法进行有机结合,通过访问权限来隐藏对象内部的实现细节,控制那些方法可以在类外部直接使用,提高代码的安全性和可维护性。

注意:通过访问权限限定的某些方法虽然不能在类外部直接使用,但是在类内部还是可以访问的。

而如何实现封装呢?

这就要借助c++的访问限定符,public(公有),protected(保护),private(私有)。比如:

class Data{
//公有
public:void Dataprint();
//私有
private: int _year;int _month;int _day;
};
  • public修饰的成员在类外可以直接被访问
  • protectedprivate修饰的成员在类外不能直接被访问,但是protected成员在派生类(子类)中可以访问private成员只能在类内部访问
  • 访问权限作用域从该访问符出现的位置开始直到下一个访问限定符出现时为止。如果后面没有访问限定符,作用域就到}也就是类结束为止。
  • class的默认访问权限为private,struct的为public

4.类的作用域

在c++入门的时候我们学过一个新的域,叫命名空间域,而现在我们了解到类之后要再认识一个新的域,也就是类域

类定义了一个新的作用域为类域,类的所有成员都在类的作用域中。在类体外定义成员时,需要使用::域作用限定符来指明该成员属于哪个类域。

class Data{
public:void Dataprint();
private: int _year;int _month;int _day;
};
//这里需要指定Dataprint是属于Data这个类域中
void Data::Dataprint(){cout<<_year<<_month<<_day<<endl;
}

5.类的实例化

类定义了对象的类型,但类本身不是对象,因此我们要创建对象。

而用类类型创建对象的过程,就叫类的实例化。

  • 类是对对象进行描述的,是一个模型一样的东西,限定了类都有哪些成员,定义出一个类并没有分配实际的内存空间来存储它。而类实例化对象,就是为该对象开辟空间来进行存储。

    //以实例化一个栈为例
    class Stack{
    public:void Init();void Destroy();...
    private:int*a;int top;int capacity;
    };
    int main(){//类实例化对象/对象定义Stack st;
    }
    
  • 一个类可以实例化出多个对象,实例化出的对象,占用实际的物理空间,存储类成员变量capaciyt是没用空间的,只有实例化的对象st1才有具体的容量。

    //以实例化一个栈为例
    class Stack{
    public:void Init();void Destroy();...//类的成员变量是声明不是定义//声明和定义的区别是,声明不开空间而定义开空间int*a;int top;int capacity;
    };
    int main(){//类实例化多个对象Stack st1;Stack st2;//错误,声明没有开辟空间不能存储数据Stack::capacity=4;//正确st1.capacity=4;
    }
    

    在这里插入图片描述

  • 做个比方:类实例化出对象就像现实中使用建筑设计图建造出房子,类就是设计图,只是设计出需要什么东西,但是并没有实体的建筑存在,同样类也只是一个设计,实例化出的对象才能实际存储数据,占用物理空间。

6.类对象模型

我们知道类中既有成员变量又可以有成员函数,那么一个类的对象中包含了什么?而一个类的大小又该怎样计算?

我们先假设类对象的存储方式为包含类的各个成员:

在这里插入图片描述

如果是上面这种情况,每个对象的成员变量不同,但是会调用相同的函数,按照这种方式存储,当一个类创建多个对象时,每个对象都有一份相同的函数,就会大大浪费空间,那么如何解决呢?

每个对象只存储成员变量,没有存储成员函数,成员函数存放在公共的代码段

在这里插入图片描述

上面这种存储方式大大节省了内存空间同时又提高了代码的可重用性。

明白了上面的之后我们在来看以下几种情况:

//类中既有成员变量,又有成员函数
class A1{
public:void f1();
private:int _a;};
//类中只有成员函数
class A2{
public:void f2();
};
//类中什么都没有,也就是空类
class A3{};

上面这三种情况类的大小:

sizeof(A1)=4;sizeof(A2)=1;sizeof(A3)=1;

结论:一个类的大小,实际就是该类中成员变量之和,注意内存对齐。而空类或者是没有成员变量的大小为1字节,是为了占位,表示对象存在,但不存储有效数据。

三.this指针

1.this指针的引出

我们先来看一下下面这个日期类Data

class Data {
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() {Data d1;Data d2;d1.Init(2024, 10, 18);d2.Init(2024, 10, 19);d1.print();d2.print();return 0;
}

在上面这段代码中,我们定义了两个日期类对象d1d2Data类中有两个成员函数Init,printd1d2都调用了这两个函数,那么又是如何对这两个不同的对象调用相同的函数进行区分呢?

c++中为了解决这种问题引入了this指针,对于非静态成员函数来说,他们实际上都隐含了一个指向当前对象的指针,也就是this指针,这个this指针在函数调用时自动传递,指向调用该成员函数的对象。这样,尽管成员函数的代码在公共段,他们依然能够通过this指针来访问和操作特定对象的成员变量。

2.this指针的特性

  • this指针的类型为:类类型*const,也就是成员函数中,不能给this指针赋值。

  • this指针本质上是成员函数的形参,当对象调用成员函数时,将对象地址作为实参传递给this指针,所以对象中不存储this指针。

  • this指针允许在成员函数内部使用,但不能在形参和实参显示传递,一般情况下由编译器通过ecx寄存器自动传递,不需要用户传递。

  • this 指针是形参,所以this指针和普通参数一样存在函数调用的栈帧里,调用结束时,栈帧销毁,this指针也会销毁。

  • this指针可以为空。但是不能在调用的成员函数中对其解引用。比如下面两段代码:

    运行正常:

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

    在这里插入图片描述

    运行崩溃:

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

    在这里插入图片描述

为什么这两段代码的结果不同,虽然对象指针p都为空,p调用成员函数时作为实参传递该this指针,但是第一种情况,在成员函数中,this指针为空但没有发生解引用所以正常运行,而第二种情况this指针为空并且发生了解引用,所以运行崩溃。

3.C语言和c++实现栈Stack的对比

  • C语言实现:

    //stack.h头文件:
    #include<stdio.h>
    #include<stdlib.h>
    #include<assert.h>
    typedef int STData;
    typedef struct Stack1 {STData* array;int top;int capacity;
    }Stack1;
    void _InitStack(Stack1*st);
    bool _IsEmpty(Stack1*st);
    void _PushStack(Stack1*st,int x);
    void _PopStack(Stack1* st);
    STData _TopStack(Stack1* st);
    void _DestroyStack(Stack1* st);
    void _checkcapacity(Stack1* st);
    //stack.cpp定义实现文件:
    void _InitStack(Stack1* st) {assert(st);st->array = (STData*)malloc(sizeof(STData) * 4);if (st->array == nullptr) {perror("malloc fail");return;}st->top = 0;st->capacity = 4;
    }
    void _checkcapacity(Stack1* st) {assert(st);if (st->top == st->capacity) {STData* tmp = (STData*)realloc(st->array, sizeof(STData) * (st->capacity) * 2);if (tmp == nullptr) {perror("malloc fail");return;}st->array = tmp;st->capacity *= 2;}
    }
    bool _IsEmpty(Stack1* st) {assert(st);return st->top==0;
    }
    void _PushStack(Stack1* st, int x) {assert(st);_checkcapacity(st);st->array[st->top++] = x;
    }
    void _PopStack(Stack1* st) {assert(st);if (_IsEmpty(st)) {return;}st->top--;
    }
    STData _TopStack(Stack1* st) {assert(st);if (_IsEmpty(st)) {return 0;}return st->array[st->top - 1];
    }
    void _DestroyStack(Stack1* st) {free(st->array);st->array = NULL;st->top = 0;st->capacity = 0;
    }//test.cpp测试文件:
    int main() {Stack1 st1;_InitStack(&st1);_PushStack(&st1, 1);_PushStack(&st1, 2);_PushStack(&st1, 3);_PushStack(&st1, 4);_PushStack(&st1, 5);_PushStack(&st1, 6);while (!_IsEmpty(&st1)) {printf("%d ", _TopStack(&st1));_PopStack(&st1);}_DestroyStack(&st1);return 0;
    }
    

    在这里插入图片描述

    在用C语言实现时,Stack相关操作函数有以下共性:

    • 每个函数的第一个参数都是Stack1*
    • 函数中必须对第一个参数检查,判断是否为空
    • 函数中都是通过Stack1*参数操作栈的
    • 调用函数时必须传递Stack1结构体变量的地址

结构体中只能定义存放数据的结构,操作数据的方法不能放在结构体中,即数据和操作数据 的方式是分离开的,而且实现上相当复杂一点,涉及到大量指针操作,稍不注意可能就会出错。

  • c++实现:

    //stack.h头文件:
    #include<stdio.h>
    #include<stdlib.h>
    #include<iostream>
    using namespace std;typedef int STData;class Stack {
    public:void InitStack();bool IsEmpty();void PushStack(int x);void PopStack();STData TopStack();void DestroyStack();
    private:void checkcapacity();STData* _a;int _top;int _capacity;
    };
    //stack.cpp定义实现文件:
    #include"stack.h"void Stack::InitStack() {_a = (STData*)malloc(sizeof(STData) * 4);if (_a == nullptr) {perror("malloc fail");return;}_top = 0;_capacity = 4;
    }
    void Stack::checkcapacity() {if (_top == _capacity) {STData* tmp = (STData*)realloc(_a,sizeof(STData) * _capacity * 2);if (tmp == nullptr) {perror("malloc fail");return;}_a = tmp;_capacity *= 2;}
    }
    void Stack::PushStack(int x) {checkcapacity();_a[_top++] = x;
    }
    bool Stack::IsEmpty() {return _top == 0;
    }void Stack::PopStack() {if (IsEmpty()) {return;}_top--;
    }
    STData Stack::TopStack() {return _a[_top - 1];
    }
    void Stack::DestroyStack() {free(_a);_a = nullptr;_top = 0;_capacity = 0;
    }
    //test.cpp测试文件:
    #include"stack.h"
    int main() {Stack st;st.InitStack();st.PushStack(1);st.PushStack(2);st.PushStack(3);st.PushStack(4);st.PushStack(5);while (!st.IsEmpty()) {cout << st.TopStack() << " ";st.PopStack();}st.DestroyStack();return 0;
    }
    

    在这里插入图片描述

    c++中通过类可以将数据和操作数据的方法进行有机结合,通过访问权限可以控制那些方法在类外可以被调用,也就是封装。而且通过this指针,不需要传递Stack*的参数,编译器编译之后该参数会自动还原。使用起来会非常方便。

以上就是关于c++类初步的讲解,如果哪里有错的话,可以在评论区指正,也欢迎大家一起讨论学习,如果对你的学习有帮助的话,点点赞关注支持一下吧!!!
在这里插入图片描述


http://www.ppmy.cn/devtools/127506.html

相关文章

Vscode 插件开发 - TreeView

树状视图很多插件都可能用到&#xff0c;通常作为一个简单的列表使用&#xff0c;比如管理项目依赖、展示搜索结果。那么 Vscode 的 TreeView 具体怎么玩呢&#xff1f; 基础操作 1.创建视图 在 package.json 的 contributes.views 节点下定义视图&#xff1a; {"name…

SqlDbx连接oracle(可用)

解压SqlDbx.zip,将SqlDbx放到C:盘根目录 1.Path里面增加&#xff1a;C:\SqlDbx Path是为了找tnsnames.ora 2.增加系统变量&#xff1a;ORACLE_HOME&#xff0c;路径&#xff1a;C:\SqlDbx ORACLE_HOME是为了找oci.dll 3.用sqlDbx查询时&#xff0c;如果出现中文乱码&#xf…

【Vue.js】vue2 项目在 Vscode 中使用 Ctrl + 鼠标左键跳转 @ 别名导入的 js 文件和 .vue 文件

js 文件跳转 需要安装插件 Vetur 然后需要我们在项目根目录下添加 jsconfig.json 配置&#xff0c;至于配置的作用&#xff0c;可以参考我的另外一篇博客&#xff1a; 【React 】react 创建项目配置 jsconfig.json 的作用 它主要用于配置 JavaScript 或 TypeScript 项目的根…

Java五子棋小游戏

这个代码是简单的五子棋代码&#xff0c;下一个是进阶版的&#xff0c;可以看看&#xff0c;从简单开始上手 第一步&#xff1a;创建项目 打开你的IDE&#xff08;如Eclipse、IntelliJ IDEA等&#xff09;。创建一个新的Java项目。在项目中创建一个新的Java类&#xff0c;命名…

TypeScript中 interface接口 type关键字 enum枚举类型

type interface总是傻傻分不清~~~ Type Aliases (type) type 关键字用于为类型定义一个别名。这可以是基本类型、联合类型、元组、数组、函数等。type 定义的类型在编译后的 JavaScript 代码中会被移除&#xff0c;不会留下任何运行时的代码。 //联合类型 type StringOrNumbe…

4、.Net 快速开发框架:DncZeus - 开源项目研究文章

DncZeus 是一个基于 ASP.NET Core 和 Vue.js 的前后端分离的通用后台管理系统框架&#xff0c;其愿景是成为一个易于使用且功能丰富的 .NET Core 通用后台权限管理模板系统基础框架。项目名称 "DncZeus" 由 "Dnc"(.NET Core 的缩写)和 "Zeus"(古…

scratch机器人捡垃圾 2024年9月scratch二级真题 中国电子学会 图形化编程 scratch二级真题和答案解析

目录 scratch机器人捡垃圾 一、题目要求 1、准备工作 2、功能实现 二、案例分析 1、角色分析 2、背景分析 3、前期准备 三、解题思路 1、思路分析 2、详细过程 四、程序编写 五、考点分析 六、推荐资料 1、入门基础 2、蓝桥杯比赛 3、考级资料 4、视频课程 …

jmeter是怎么用的,怎么设计接口测试用例的

如何使用&#xff1a; Apache JMeter 是一款广泛使用的开源性能测试工具&#xff0c;用于对各种服务&#xff08;如 Web 应用、数据库、FTP、WEB 服务等&#xff09;进行负载测试和性能测试。以下是如何使用 JMeter 的基本步骤。 1. 安装 JMeter 下载 JMeter&#xff1a;访问…