当前位置: 首页 > news >正文

【C++】类和对象(一)

目录

1 类的定义

1.1 类的定义格式

1.2 类域

2 实例化

2.1实例化的概念

2.2 对象

2.3 对象的大小

3 this指针

4 C++和C语言实现Stack对比


1 类的定义

1.1 类的定义格式

class为定义类的关键字,格式为class+类成员名字,然后{ }内部写代码,需要注意大括号后加分号,先看一段代码:

#include <iostream>
#include <assert.h>
using namespace std;
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;
};

这段代码中Date为类的名字,{}中为类的主体,类中的变量称为类的属性或者成员变量,类中的函数称为类的方法或者成员函数

为了区分成员变量,一般在成员变量加一个特殊标识,这里在成员变量前加一个_,这个不是唯一的标识,一些公司在成员变量后面加_  

C++⼀种实现封装的方式,用类将对象的属性与方法结合在⼀块,让对象更加完善,通过访问权限 选择性的将其接口提供给外部的用户使用。所以这里就提出一个访问限定符的概念,上述代码中public和private就是访问限定符。public修饰的成员可以在类的外面直接被访问,private和protected修饰的成员在类外面不能被访问,在这里private和protected是一样的,区别在后续写继承的时候会说明。

访问权限作用域从访问限定符开始到下一个访问限定符出现为止,如果后面没有访问限定符,作用域到}结束。

在C++中struct可以定义类,C++兼容C语言中struct的用法,定义类时将class替换成struct也是可以的。一般习惯用class定义类。这里注意一点,class定义成员没有被访问限定符修饰的,默认为private,struct默认为public。

1.2 类域

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

#include<iostream>
using namespace std;
class Stack
{
public://成员函数void Init(int n = 4);
private://成员变量int* array;size_t capacity;size_t top;
};
// 声明和定义分离,需要指定类域
void Stack::Init(int n)
{array = (int*)malloc(sizeof(int) * n);if (nullptr == array){perror("malloc申请空间失败");return;}capacity = n;top = 0;
}

类域影响的是编译的查找规则,下面程序中Init如果不指定类域Stack,那么编译器就把Init当成全局函数,那么编译时,找不到array等成员的声明在哪里,就会报错。

2 实例化

2.1实例化的概念

用类的类型在物理内存中创建对象的过程,称为类实例化出对象。

打个比方,类就像房子的蓝图,对象就是实际的房子

  • 类 (Class):就像是房子的蓝图

    蓝图不是真正的房子,它定义了房子的规划:有几个卧室、几个卫生间。蓝图本身只是一个“模板”。
  • 对象 (Object):就是根据蓝图建造出来的真正的房子。根据同一张蓝图,可以建造出无数个具体的房子。每个房子都是独立的,互不影响。

所以:类是用来创建对象的模板或蓝图,而对象是这个模板的一个具体实例

下面看代码来感受一下:

// 这是一个类(蓝图)
class Stack { 
private:// 以下是属性特征int* _data;     int _top;       int _capacity;  public:// 以下是方法void Init(int capacity = 4) { // 初始化栈_data = new int[capacity];_capacity = capacity;_top = 0;}void Push(int x) { // 压栈操作// ... 检查容量等逻辑_data[_top++] = x;}void Pop() { // 出栈操作// ... 检查栈是否为空--_top;}int Top() { // 取栈顶元素return _data[_top - 1];}};

2.2 对象

对象是根据类这个蓝图创建出来的实实在在的变量。这个过程叫做“实例化”。具体解释请见注释。

int main() {// 以下是创建对象Stack s1;    // 创建了第一个栈对象 s1 (根据蓝图建了第一套房)Stack s2;    // 创建了第二个栈对象 s2 (根据同一张蓝图建了第二套房)// 对s1进行操作s1.Init(10); s1.Push(1);  s1.Push(2);  std::cout << s1.Top() << std::endl; // 输出s1的栈顶元素// 对s2进行操作s2.Init(5);  s2.Push(100); std::cout << s2.Top() << std::endl; // 输出s2的栈顶元素// 关键点:s1和s2互不影响!std::cout << s1.Top() << std::endl; // 输出s1的栈顶元素,仍然是2。s2的操作没影响s1。return 0;
}

2.3 对象的大小

当用一个类实例化出一个对象时,就会有独立的数据空间。对象的数据空间中一定包含成员变量,但不包含成员函数;函数被编译后是一段指令,对象没办法存储。如果要存储,只能存储函数指针。但是函数指针没必要进行存储,类实例化成员函数时,即使成员函数不同,函数指针也是一样的,假设实例化100个对象,那么重复存储100次就会很浪费,所以C++中函数指针不需要存储,编译链接时就会找到函数的地址,而不是在运行时找。

C++中类实例化的对象也要遵循C语言中提到的内存对齐的规则。

内存对齐:

  • 第⼀个成员在与结构体偏移量为0的地址处。
  • 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
  • 对齐数=编译器默认的⼀个对齐数与该成员大小的较小值。
  • VS中默认的对齐数为8。
  • 结构体总大小为:最大对齐数(所有变量类型最大者与默认对齐参数取最小)的整数倍。
  • 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最⼤对齐数的整数倍处,结构体的整体大小 就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
#include<iostream>
using namespace std;
// 计算⼀下A / B / C实例化的对象是多⼤?class A
{
public:void Print(){cout << _ch << endl;}
private:char _ch;int _i;
};
class B
{
public:void Print(){//...}
};
class C
{};
int main()
{A a;B b;C c;cout << sizeof(a) << endl;cout << sizeof(b) << endl;cout << sizeof(c) << endl;return 0;
}

输出结果:

类B和C,没有成员变量和成员函数为空,大小为1个字节,是为了占位标识对象存在,来表示对象存在过。

3 this指针

#include<iostream>
using namespace std;
class Date
{
public:// void Init(Date* const this, int year, int month, int day)void Init(int year, int month, int day){// 编译报错:左操作数必须为左值// this = nullptr;// this->_year = year;_year = year;this->_month = month;this->_day = day;}void Print(){cout << _year << "/" << _month << "/" << _day << endl;}
private:// 这里没有开空间int _year;int _month;int _day;
};
int main()
{// Date类实例化出对象d1和d2Date d1;Date d2;// d1.Init(&d1, 2025, 8, 30);d1.Init(2025, 8, 30);d1.Print();d2.Init(2025, 8, 31);d2.Print();return 0;
}

如上述代码,在Date类中有两个成员函数,函数体中没有对象的区分,假如当d1调用Init函数时,添加参数后,是如何知道访问的是d1还是d2呢?这里就有一个隐含的this指针知识。

编译器编译后,实际上都会在成员函数的形参第一个位置,增加一个类的类型指针。这个指针就叫做this指针。比如Date类中的Init真实原型就是void Init(Date* const this,int year,int month,int day)

C++中规定不可以在实参和形参的位置上显示this指针,可以在函数体内部使用this指针。

4 C++和C语言实现Stack对比

C++中数据和函数都放到了类里面,通过访问限定符进行了限制,不能再随意通过对象直接修改数 据,这是C++封装的⼀种体现,这个是最重要的变化。本质是⼀种更严格规范的管 理,避免出现乱访问修改的问题。当然封装不仅仅是这样的,后面会不断地说明。

C++中有⼀些相对方便的语法,比如Init给的缺省参数会方便很多,成员函数每次不需要传对象地 址,因为this指针隐含的传递了,方便了很多

C语言实现Stack:

#include<stdio.h>
#include<stdlib.h>
#include<stdbool.h>
#include<assert.h>
typedef int STDataType;
typedef struct Stack
{STDataType* a;int top;int capacity;
}ST;
void STInit(ST* ps)
{assert(ps);ps->a = NULL;ps->top = 0;ps->capacity = 0;
}
void STDestroy(ST* ps)
{assert(ps);free(ps->a);ps->a = NULL;ps->top = ps->capacity = 0;
}
void STPush(ST* ps, STDataType x)
{assert(ps);if (ps->top == ps->capacity){int newcapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;STDataType* tmp = (STDataType*)realloc(ps->a, newcapacity *sizeof(STDataType));if (tmp == NULL){perror("realloc fail");return;}ps->a = tmp;ps->capacity = newcapacity;}ps->a[ps->top] = x;ps->top++;
}
bool STEmpty(ST* ps)
{assert(ps);return ps->top == 0;
}
void STPop(ST* ps)
{assert(ps);assert(!STEmpty(ps));ps->top--;
}
STDataType STTop(ST* ps)
{assert(ps);assert(!STEmpty(ps));return ps->a[ps->top - 1];
}
int STSize(ST* ps)
{assert(ps);return ps->top;
}

C++实现Stack:

#include<iostream>
using namespace std;
typedef int STDataType;
class Stack
{
public:// 成员函数void Init(int n = 4){_a = (STDataType*)malloc(sizeof(STDataType) * n);if (nullptr == _a){perror("malloc");return;}_capacity = n;_top = 0;}void Push(STDataType x){if (_top == _capacity){int newcapacity = _capacity * 2;STDataType* tmp = (STDataType*)realloc(_a, newcapacity *sizeof(STDataType));if (tmp == NULL){perror("realloc fail");return;}_a = tmp;_capacity = newcapacity;}_a[_top++] = x;}void Pop(){assert(_top > 0);--_top;}bool Empty(){return _top == 0;}int Top(){assert(_top > 0);return _a[_top - 1];}void Destroy(){free(_a);_a = nullptr;_top = _capacity = 0;}
private:// 成员变量STDataType* _a;size_t _capacity;size_t _top;
};

本章完,如果这篇文章对你有所帮助,可以点点赞哦,你的支持就是我写下去的动力,后续会继续写类和对象的其他知识。

http://www.dtcms.com/a/359788.html

相关文章:

  • 【全功能图片处理工具详解】基于Streamlit的现代化图像处理解决方案
  • 二.Shell脚本编程
  • 微软开源TTS模型VibeVoice,可生成 90 分钟4人语音
  • 李宏毅NLP-13-Vocoder
  • 中级统计师-统计实务-第四章 专业统计
  • FPGA入门指南:从零开始的可编程逻辑世界探索
  • 【Proteus仿真】点亮小灯系列仿真——小灯闪烁/流水灯/交通灯
  • 常用的20个c++函数
  • 11.1.5 实现文件删除,共享和共享下载排行榜
  • 【GaussDB】排查应用高可用切换出现数据库整体卡顿及报错自治事务无法创建的问题
  • Photoshop - Ps 裁剪并拉直图片
  • 【C++详解】C++11(二) lambda表达式、类型分类、引⽤折叠、完美转发
  • 计算机视觉(四):二值化
  • 如何重置SVN被保存的用户名和密码
  • 基于路测点云标注生成OpenDrive地图的全流程解析
  • TI-92 Plus计算器:函数图像功能介绍
  • ros2bag_py的api小结、丢帧问题对策
  • 第四十九天(springboot模版注入ThymeleafFreemarkerVelocity)
  • 飞牛NAS上部署Markdown文稿编辑器,阅读.md文件同时还可以跨平台访问!
  • 根据Excel数据表快速创建Word表格(标签)
  • JavaScript中的XMLHttpRequest对象分析
  • Docker 存储原理精要
  • s[:] = reversed(s) 和 s = reversed(s)的区别
  • Blender建模:对于模型布线的一些思考
  • FPGA设计杂谈之七:异步复位为何是Recovery/Removal分析?
  • 浅谈 SQL 窗口函数:ROW_NUMBER() 与聚合函数的妙用
  • 深入解析数据结构之单链表
  • 人工智能加速漏洞利用,15分钟即可完成概念验证?
  • 网络:相比于HTTP,HTTPS协议到底安全在哪?
  • go 开发环境配置 air + dlv debug 踩坑之旅