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

C++类和对象(一)基础内容讲解

C++类和对象(一)基础内容讲解

  • 1 类的定义
    • 1.1 类定义的语法格式
    • 1.2 类域
      • 1.2.1 访问限定符
      • 1.2.2 类域的概念与使用
  • 2 实例化
    • 2.1 实例化的概念
    • 2.2 对象大小
  • 3 this指针
    • 3.1 this指针概念及使用
    • 3.2 真题实战,夯实基础

1 类的定义

1.1 类定义的语法格式

class Stack{ };其中class是定义类的关键字,Stack是类的名字(类名就是类型),{}内是类的主体,类定义结束时末尾的;不可省略。类的主体中的内容称作类的成员:类中的变量称作类的属性或成员变量;类中的函数称为类的方法或者成员函数。比如:

class Date
{//属性、成员变量int _year;int _month;int _day;//方法、成员函数void Init(int year, int month, int day){_year = year;_month = month;_day = day;}
};

知晓语法格式后,我们仍需注意以下三点:

  • 当类中成员变量较多易引起命名冲突时,为了区分成员变量,一般习惯在成员变量的前后加上_或者m开头,虽然C++中并未对此做出强制要求,但在项目实践中却是常见的。
  • C++中struct也可以定义类,C++兼容了C语言中struct的用法,同时将struct升级为了类,显著变化为现在struct中已经可以定义函数。但一般情况下我们最好还是使用class来定义类。
  • 定义在类里面的成员函数默认是inline修饰的内联函数。

1.2 类域

1.2.1 访问限定符

用类将对象的属性和方法绑定在一块,并通过访问权限控制,选择性地暴露接口给外部用户,让对象更加完善。这是C++中一种实现封装的方式。
本小节所提及的访问限定符就是通过实现对访问权限的控制以达到数据隐藏目的而使用的关键字。
访问限定符有以下三种:

  • public:成员可在类内外被直接访问
  • private:成员只能在类内部访问
  • protected:成员可在类内部或者派生类中访问

class内定义的成员未被访问限定符修饰时权限默认为private,在struct中权限默认为public
注意:1、访问权限作⽤域是从该访问限定符出现的位置开始一直到下⼀个访问限定符出现时为⽌,如果后⾯没有访问限定符,作⽤域就到}即类结束。
2、一般情况下成员变量都会被限制为private/protected,需要给别人使用的成员函数权限会被放到public中。如下:

class Date
{
public://方法、成员函数void Init(int year, int month, int day){_year = year;_month = month;_day = day;}
private://属性、成员变量//这里只是声明,没有开空间int _year;int _month;int _day;
};

1.2.2 类域的概念与使用

类域是类所定义的一个新的作用域,类的所有成员都在类的作用域中。当要在类外面定义成员时,需要使用::作用域解析运算符指明成员属于哪个类域,其格式为:<返回类型> 类名::成员函数名(参数列表) {函数体}

class Date
{
public://方法、成员函数void Init(int year, int month, int day);
private://属性、成员变量int _year;int _month;int _day;
};//函数声明和定义分离,需要指定类域
void Date::Init(int year, int month, int day)
{_year = year;_month = month;_day = day;
}

由于类域影响的是编译的查找规则,上面代码中Init如果不指定类域Date,那么编译器就会把Init当成全局函数,在编译时,编译器找不到_year等成员的声明/定义在哪里,就会报错。指定类域Date后,编译器就知道Date是成员函数,当前局部域中找不到_year等成员时,就会去类域中查找,在类域中也找不到时才会去全局域中查找。

2 实例化

2.1 实例化的概念

通过前面对类的讲解我们可以知道,类其实是一个抽象的模板,其定义的是对象的结构(属性和方法),但本身不占用内存。对象的创建就是类实例化分配内存的结果,系统会在物理内存中为该对象分配空间,存储其成员变量的值。用类类型在物理内存中创建对象的过程,称为类实例化出对象

类与对象是一对多的关系,一个类可以实例化出多个对象,我们可以将类视为住房设计图,图中规划有房间个数和各个房间的功能,但是由于没有实体建筑的存在,所以无法住人和实现房间功能。然而一幅住房设计图可以生成许多套住房来完成上面的规划,这些住房就是类实例化出的对象。

          +-------------------+|      Person       |+-------------------+| - name: String    || - age: int        |+-------------------+| + setName()       || + getName():String|| + setAge()        || + getAge():int    |+-------------------+▲│ 实例化  箭头指向类,表示这两个对象是由类实例化来的│
+---------------------+        +---------------------+
|      person1        |        |      person2        |
+---------------------+        +---------------------+
| - name: "Alice"     |        | - name: "Bob"       |
| - age: 30           |        | - age: 25           |
+---------------------+        +---------------------+
class Date
{
public://方法、成员函数void Init(int year, int month, int day);void Dprint(){cout << _year << "|" << _month << "|" << _day << endl;}
private://属性、成员变量int _year;int _month;int _day;
};//函数声明和定义分离,需要指定类域
void Date::Init(int year, int month, int day)
{_year = year;_month = month;_day = day;
}int main()
{//类名就是类型,实例化出两个对象Date ret1;Date ret2;ret1.Init(2025, 1, 1);ret1.Dprint();ret2.Init(2025, 1, 2);ret2.Dprint();return 0;
}

2.2 对象大小

计算对象的大小,我们首先要知道都有哪些成员是存储在对象之中的。类实例化出的每个对象的成员变量都有独立的存储空间,所以对象的存储肯定是包含成员变量的。但是对于成员函数,对象中却无法存储,主要有以下两方面原因:

  • 函数被编译后是一段指令,对象中无法存储,这些指令会被存储到代码段当中,如果一定要存储相关函数,那么就只能存储成员函数的指针。但是对象中没有存储函数指针的必要。比如上面Date实例化出的ret1ret2两个对象,二者中_year等变量都是各自独立的,而ret1ret2的成员函数函数Init/Dprint的指针却是一样的,如果存储在对象中的话,当实例化的对象有几千个时,成员函数指针就会被重复存储几千次,如此就会非常浪费空间。
  • 从底层上讲,函数指针就是一个地址,调用函数会被编译成汇编指令[call地址],编译器在编译链接时,就需要找到函数地址,而非在运行时查找。(存储函数指针且在运行时查找只有在动态多态时才会出现)
    在这里插入图片描述
    由上可知,对象中只存储成员变量。C++中规定,类实例化出的对象也符合内存对齐原则。所以这里计算对象大小和C语言中计算结构体大小是相同的。
    注意:空类和对象中不包含成员变量的类所实例化出的对象的大小是1个字节,目的是为了占位标识对象存在。

3 this指针

3.1 this指针概念及使用

Date类中有两个成员函数InitDprint,但成员函数是如何识别是访问对象ret1还是访问对象ret2?这个问题的解决就涉及到C++中所提供的隐含的this指针。
当一个对象调用其成员函数时,this指针会隐式传递给该函数,作为该函数的第一个参数(void Init(Date* const this, int year, int month, int day)),this指针指向的是调用该函数的对象。

this指针的核心作用:

  • 区分不同对象:即使多个对象共享同一份成员函数代码(因为成员函数是放在类的代码段中),this指针确保函数能操作正确的对象数据。
  • 关联成员变量和函数:成员函数内部访问成员变量或调用其他成员函数时,编译器会通过this指针找到对应对象的存储位置。访问对象内成员变量,比如Init函数中的赋值,本质上是通过this->_year = year;实现的。

使用this指针的注意事项:

  • 静态函数中没有this指针,所以this指针无法在静态函数中使用。
  • C++规定不能在实参和形参的位置显式的写this指针(编译时编译器会处理),但是可以在函数体内部显式使用this指针(this->_year = year

this指针的特性:

  1. 类型为ClassType* const:是一个指向对象的常量指针,不能修改其指向(如this = nullptr;会报错)。
  2. 不占用对象内存sizeof(MyClass)的结果不包含this指针的大小,因为它是编译器隐式传递的,是成员函数的隐式参数,与对象无关,并不是对象的成员。
  3. 仅在非静态成员函数中可用:静态成员函数和普通函数中无法使用this
  4. 传递方式:通常通过寄存器(如ECX)传递,具体依赖编译器优化。

3.2 真题实战,夯实基础

例题一:下面程序编译运行的结果是:
A:编译报错 B:运行崩溃 C:正常运行

#include<iostream>
using namespace std;
class A
{
public:void Print(){cout << "A::Print()" << endl;}
private:int _a;
};int main()
{A* p = nullptr;p->Print();return 0;
}

首先,A* p = nullptr这是一种未定义行为,类A并没有实例化出具体的对象,这个类A类型的变量p只是一个空指针,其值无效,并不指向任何类A的对象。
其次,可见主函数中使用空指针p调用了类A当中的print函数,成员函数print的调用会隐式传递this指针,指向调用对象(即p所指向的对象),由于pnullptr,所以this也是nullptr
最后,因为成员函数print当中未访问任何成员变量或this指针指向的数据,所以不会触发对无效内存的访问。
综上,程序会正常运行,选C。

例题二:
下面程序编译运行的结果是:
A:编译报错 B:运行崩溃 C:正常运行

#include<iostream>
using namespace std;
class A
{
public:void Print(){cout << "A::Print()" << endl;cout << _a << endl;}
private:int _a;
};int main()
{A* p = nullptr;p->Print();return 0;
}

对比例题一我们可知例题二在成员函数print内增加了一行输出成员变量值的代码,由例题一可知类A并没有实例化出任何对象,当不访问this指针指向的数据时,运行不会出错。但是此时成员函数内访问的是this指针指向的数据_a,触发了无效访问,导致运行崩溃。故答案为B。
在这里插入图片描述

例题三:this指针存放于内存的哪块区域:
A:栈或寄存器 B:堆 C:静态区 D: 常量区 E:对象内部

this指针位于函数的形参部分,自然会被当做形参对待,所以其被存放于栈或寄存器当中。注意this指针的存储位置是灵活的,具体实现取决于编译器。在某些情况下,this指针会被压入到栈区,但大多数编译器(如VC、GCC)会将this指针优化到寄存器(如ECX)中,以提高访问效率。

全文至此结束!!!
写作不易,不知各位老板能否给个一键三连或是一个免费的赞呢(▽)(▽),这将是对我最大的肯定与支持!!!谢谢!!!(▽)(▽)

http://www.lryc.cn/news/593497.html

相关文章:

  • 【项目分享】动手做一个TypeC转TTL模块(附带原理图)
  • Spring MVC @RequestParam注解全解析
  • 数据库第四次作业
  • 【C++】初识C++(2)
  • 完美解决 Ubuntu 中自定义启动器图标重复的问题(以 MATLAB 为例)
  • nginx.conf模版
  • 基于GEE与哨兵2号的土地覆盖分类方法及实现
  • python网络爬虫之selenium库(二)
  • uview-ui使用u-row+u-avatar居中布局
  • uview-ui使用u-icon文字图标展示
  • react+antd 可拖拽模态框组件
  • Python之--元组
  • RabbitMQ01——基础概念、docker配置rabbitmq、内部执行流程、五种消息类型、测试第一种消息类型
  • Java学习--------消息队列的重复消费、消失与顺序性的深度解析​
  • I/O 多路复用select,poll
  • Java 中的继承与多态
  • 5.组合模式
  • 3.5软件开发活动[2-系统设计]面向对象设计-UML统一开发过程
  • [故障诊断方向]SNNs:针对小样本轴承故障诊断的孪生神经网络模型
  • 在Vscode中使用Kimi K2模型:实践指南,三分钟生成个小游戏
  • 练习三:熟知前端知识
  • 目标检测中的标签分配算法总结
  • MinIO深度解析:从核心特性到Spring Boot实战集成
  • Vue的路由模式的区别和原理
  • 《Qt5串口开发》搭建跨平台通信系统
  • VSCode用Python操作MySQL:环境配置与代码验证
  • 操作系统-分布式同步
  • 实验室危险品智能管控:行为识别算法降低爆炸风险
  • Mybatis学习之简介(一)
  • Vue 3 中封装并使用 IndexedDB 的完整教程(含泛型、模块化、通用 CRUD)