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

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

时间:2024-10-20 23:20:02浏览次数:3  
标签:STData 成员 -- void 编程 c++ st int top

文章目录

前言

在程序设计的广袤宇宙中,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++类初步的讲解,如果哪里有错的话,可以在评论区指正,也欢迎大家一起讨论学习,如果对你的学习有帮助的话,点点赞关注支持一下吧!!!
在这里插入图片描述

标签:STData,成员,--,void,编程,c++,st,int,top
From: https://blog.csdn.net/2301_82347435/article/details/143093332

相关文章

  • 手写Java代码生成工具(五)生成service和serviceImpl文件
    目录前言一、创建service文件以及导入所需的类二、生成service类需要的方法三、创建ServiceImpl文件以及生成导入语句四、生成的ServiceImpl类文件中添加内容五、遍历索引,生成基础增删改查前言写博客是为了记录和分享自己的学习,所分享内容是网上的资源,由于非原创项......
  • 前端学习Day8 CSS常用声明 (文本 、图像篇)
    目录一、文本1.1.1、字体样式的常用属性1.1.2、语法格式1.2.1、文本样式1.2.2、语法格式 二、图像2.1.1、图像控制的常用属性2.1.2、语法格式一、文本1.1.1、字体样式的常用属性属性说明font-family设置字体的类型font-weight设置字体的粗细font-size设置字体的......
  • 电子部授课1
    今天下午有院科协的授课,涉及电赛知识,单片机环境构建和模拟方向讲解。感觉要学知识还是很多呜呜呜这是电赛讲解,主要是五个方面,有一个讲太快了没有听清哈哈哈后面是全程搜概念的模拟,真的有很多知识不太明白慌乱的笔记,沉重的脑子哈哈哈,真的来不及,要好好了解一下电路了,甚至在......
  • windows下安装VirtualBox7.1.4
    记录详细的安装过程与遇到的问题;下载地址virtualbox官网清华镜像源下载下载完成后文件:双击打开;报错了意思是需要pc上先安装MicrosoftVisualC++2019https://learn.microsoft.com/zh-cn/cpp/windows/latest-supported-vc-redist?view=msvc-160下载后安装重启后继......
  • 什么是CNN?
    什么是CNN?你可以把CNN想象成一台非常聪明的图像处理机器。它特别擅长处理图片,因为它能自动找出图片里的重要信息,比如边缘、形状和颜色等等。这就好像你看一张图片时,先看出物体的轮廓,再慢慢认出具体是什么东西一样。CNN就像你的大脑,但它是通过数学运算来“看”图像的。CNN是怎......
  • 【计网笔记】数据链路层
    向上提供透明接口向下将比特流拆分为若干个帧,为每个帧计算校验和接收端基于校验和检查帧内的比特差错向上网络层接口无确认的无连接服务以太网无需逻辑连接双方无需确认有确认的无连接服务IEEE802.11(WiFi)无需逻辑连接接收方确认收到帧发送方如果超时未确认,就重新......
  • 【计网笔记】以太网
    经典以太网总线拓扑物理层Manchester编码数据链路层MAC子层MAC帧DIX格式与IEEE802.3格式IEEE802.3格式兼容DIX格式前导码(+帧开始定界符SOF)8字节前7字节均为0xAA第8字节为0xAB前7字节的Manchester编码将产生稳定方波,用于校准时钟周期目的地址6字节目的......
  • CNVD漏洞和证书挖掘经验总结
    前言        本篇文章主要是分享一下本人挖掘CVND漏洞碰到的一些问题,根据过往成功归档的漏洞和未归档的漏洞总结出的经验,也确实给审核的大佬们添了很多麻烦(主要真的没人教一下,闷着头尝试犯了好很多错误,希望各位以后交一个通过一个。当然,也一定要注意测试资产的范围、......
  • 【关联规则挖掘算法‌】基于兴趣度的关联规则挖掘算法
    目录一、基于兴趣度的关联规则挖掘算法概述1.1兴趣度度量1.2基于兴趣度的关联规则挖掘算法1.2.1支持度-置信度(SC)算法1.2.2支持度-提升度(SP)算法1.2.3支持度-互信息(SM)算法1.2.4基于兴趣度的关联规则挖掘算法二、基于兴趣度的关联规则挖掘算法优缺点和改进2.1  ......
  • 【关联规则挖掘算法‌】基于约束的关联规则挖掘算法
    目录一、基于约束的关联规则挖掘算法概述二、基于约束的关联规则挖掘算法优缺点和改进2.1  基于约束的关联规则挖掘算法优点2.2  基于约束的关联规则挖掘算法缺点2.3  基于约束的关联规则挖掘算法改进三、 基于约束的关联规则挖掘算法编程实现3.1  基于约束的......