news 2026/10/10 2:57:53

二、类和对象(一)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
二、类和对象(一)

👉 欢迎阅读这篇文章 👇

目录

  • 1、类的定义
    • 1.1类定义格式
    • 1.2访问限定符
    • 1.3类域
  • 2、 实例化
    • 2.1实例化概念
    • 2.2对象大小
  • 3、this指针

1、类的定义

1.1类定义格式

class为定义类的关键字,Stack是类的名字,{}中为类的主体,注意类定义结束后后面的分号不能省略。

类体中内容称为类的成员:类中的变量称为类的属性或成员变量;类中的函数称为类的方法或成员函数。

为了区分成员变量,一般会对成员变量加上标识符,如成员变量前面或者后面加_或者m开头。(注意C++中这个并不是强制的,只是⼀些惯例)

在C++中,struct也可以定义类,C++中兼容C的写法,并且将struct升级成了类,变化是struct中可以定义函数。一般情况还是使用class定义类。

定义在类⾯的成员函数默认为inline。

1.2访问限定符

类是C++实现封装的一种方式,用类将对象的属性和方法结合在一起,让对象更加完善。通过访问权限控制,选择性向外部提供接口。

有三个访问限定符分别是public(公有)、protected(保护)、private(私有)他们用于修饰类的成员。

public修饰的成员在类外可以直接被访问;protected和private修饰的成员在类外不能直接被访问。现在protected和private是⼀样的,以后继承章节才能体现出他们的区别。

访问权限作⽤域是从该限定符出现的位置开始,直到下一个访问限定符出现为止,如果后面没有访问限定符,那么作用域到}结束。

class定义成员没有被访问限定符修饰的时候默认为private;struct默认为public。

⼀般成员变量都会被限制为private/protected,需要给别⼈使⽤的成员函数会放为public。

#include<iostream>#include<assert.h>usingnamespacestd;classStack{//public访问限定符public://成员函数/类的方法voidInit(intn=4){array=(int*)malloc(sizeof(int)*n);if(nullptr==array){perror("malloc申请空间失败");return;}capacity=n;top=0;}voidPush(intx){// ...扩容array[top++]=x;}intTop(){assert(top>0);returnarray[top-1];}voidDestroy(){free(array);array=nullptr;top=capacity=0;}//public访问限定符作用域结束//private访问限定符private://成员变量/类的属性int*array;inttop;intcapacity;};intmain(){Stack st;st.Init();st.Push(1);st.Push(2);st.Push(3);cout<<st.Top()<<endl;st.Destroy();return0;}//private访问限定符作用域结束
classDate{public:voidInit(intyear,intmonth,intday){_year=year;_month=month;_day=day;}private://为了区分成员变量,⼀般习惯上成员变量//会加⼀个特殊标识,如_ 或者 m开头int_year;int_month;int_day;};intmain(){Date d;d.Init(2026,10,8);return0;}
//C++兼容C的用法typedefstructListNodeC{intdata;structListNodeC*next;}LTNode;// 不再需要typedef,ListNodeCPP就可以代表类型,升级成了类structListNodeCPP{voidInit(intx){data=x;next=nullptr;}intdata;ListNodeCPP*next;};

1.3类域

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

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

classStack{public:voidInit(intn=4);private:int*array;inttop;intcapacity;};//函数的声明和定义分离,类体外定义时要加上作用域操作符指明成员属于哪个类域。voidStack::Init(intn){array=(int*)malloc(sizeof(int)*n);if(array==nullptr){perror("use malloc");return;}top=0;capacity=0;}intmain(){Stack st;st.Init();return0;}

2、 实例化

2.1实例化概念

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

类是对对象的一种抽象的描述,就像模型。在类的定义中限定了类有哪些成员变量,这只是对这些成员变量进行了声明,没有分配空间,⽤类实例化出对象时,才会分配空间。

⼀个类可以实例化出多个对象,实例化出的对象占⽤实际的物理空间,存储类成员变量。

classDate{public:voidInit(intyear,intmonth,intday){_year=year;_month=month;_day=day;}private://这只是对三个变量进行了声明,并没有在物理存储上开辟实际的空间。int_year;int_month;int_day;};intmain(){//Date类实例化出对象d1和d2Date d1;d1.Init(2026,10,8);Date d2;d2.Init(2026,10,9);return0;}

2.2对象大小

类实例化出的每个对象,都有独立的数据空间,所以对象中肯定包含成员变量,那么是否包含成员函数呢?

函数被编译后是一段指令,对象中是无法存储的,这些指令存储在一个单独的区域。对象中要想存储的话,只能存储成员函数的指针,对象中是否有必要存储函数指针呢?

假设Date实例化出d1、d2两个对象,d1和d2都有各⾃独⽴的成员变量_year/_month/_day存储各⾃的数据,但是d1和d2的成员函数Init/Print指针却是⼀样的,如果将函数指针存储在对象中就属于浪费空间了。

其实函数指针是不需要存储的,函数指针是⼀个地址,调⽤函数被编译成汇编指令[call 地址], 其实编译器在编译链接时,就要找到函数的地址,不是在运⾏时找。

对象中只存储成员变量,C++规定类实例化的对象也要符合内存对⻬的规则。

内存对齐规则

  • 结构体的第1个成员对齐到和结构体变量起始位置偏移量为0的地址处。
  • 从第2个成员变量开始,都要对齐到偏移量为自身对⻬数的整数倍的地址处。
    • 对齐数=编译器默认的一个对齐数与该成员变量大小的较小值。
      • VS 中默认的值为 8
      • Linux中 gcc 没有默认对⻬数,对⻬数就是成员⾃⾝的⼤⼩
  • 结构体总大小为最大对齐数(结构体中每个成员变量都有⼀个对⻬数,所有对⻬数中最⼤的)的整数倍。
  • 如果嵌套了结构体,嵌套的结构体成员对齐到自己的成员中最⼤对⻬数的整数倍处,结构体的整体⼤⼩就是所有最⼤对⻬数(含嵌套结构体中成员的对⻬数)的整数倍。

若一个类中没有成员变量,那么实例化出的对象大小是1,为什么没有成员变量还要给1个
字节呢?因为如果是0的话,就无法证明对象存在,所以这里给1个字节是为了占位标识对象存在。

// 计算⼀下A/B/C实例化的对象是多⼤?classA{public:voidPrint(){cout<<_ch<<endl;}private:char_ch;int_i;};classB{public:voidPrint(){//...}};classC{};intmain(){A a;B b;C c;cout<<sizeof(a)<<endl;//8cout<<sizeof(b)<<endl;//1cout<<sizeof(c)<<endl;//1return0;}

3、this指针

假设Date类中有Init和Print两个成员函数,函数体中没有关于不同对象的区分,那当d1调⽤Init和Print时,该函数如何知道应该访问的是d1对象还是d2对象呢?

classDate{public:voidInit(intyear,intmonth,intday){_year=year;_month=month;_day=day;}voidPrint(){cout<<_year<<"/"<<_month<<"/"<<_day<<endl;}private:int_year;int_month;int_day;};intmain(){//Date类实例化出对象d1和d2Date d1;d1.Init(2026,10,8);d1.Print();Date d2;d2.Init(2026,10,9);d2.Print();return0;}

其实在编译器编译后,会在类的成员函数的第一个形参处,增加一个当前类类型的指针,叫this指针。⽐如Date类的Init,编译后就是void Init(Date*const this,int year, int month,int day)

类的成员函数中访问成员变量,本质都是通过this指针访问的,如Init函数中给_year赋值,
this->year = _year;

//编译后就会变成classDate{public://void Init(Date* const this,int year, int month,int day)//{// this->_year = year;// this->_month = month;// this->_day = day;//}//void Print(Date* const this)//{// cout << this->_year << "/" << this->_month << "/" << this->_day << endl;//}private:int_year;int_month;int_day;};intmain(){//Date d1;//d1.Init(&d1,2026, 10, 8);//d1.Print(&d1);//Date d2;//d2.Init(&d2,2026,10,9);//d2.Print(&d2);return0;}

C++规定不能在实参和形参的位置显式的写this指针(编译时编译器会处理),但是可以在函数体内显式使⽤this指针。

//编译后就会变成classDate{public://void Init(Date* const this,int year, int month,int day)//不能显式用voidInit(intyear,intmonth,intday){this->_year=year;//可以显式用this->_month=month;this->_day=day;}//void Print(Date* const this)voidPrint(){cout<<this->_year<<"/"<<this->_month<<"/"<<this->_day<<endl;}private:int_year;int_month;int_day;};intmain(){Date d1;//d1.Init(&d1,2026, 10, 8);//不能显式用//d1.Print(&d1);//不能显式用d1.Init(2026,10,8);d1.Print();Date d2;//d2.Init(&d2,2026,10,9);//不能显式用//d2.Print(&d2);//不能显式用d2.Init(2026,10,9);d2.Print();return0;}

练习

1.下⾯程序编译运⾏结果是()
A、编译报错 B、运⾏崩溃 C、正常运⾏

#include<iostream>usingnamespacestd;classA{public:voidPrint(){cout<<"A::Print()"<<endl;}private:int_a;};intmain(){A*p=nullptr;p->Print();return0;}

2.下⾯程序编译运⾏结果是()
A、编译报错 B、运⾏崩溃 C、正常运⾏

#include<iostream>usingnamespacestd;classA{public:voidPrint(){cout<<"A::Print()"<<endl;cout<<_a<<endl;}private:int_a;};intmain(){A*p=nullptr;p->Print();return0;}

上面这两段代码

  • Print是普通成员函数,不属于对象,存在代码段,不在对象内存里。
  • p->Print()编译时直接绑定到A::Print,不会去解引用指针获取对象。
  • 成员函数隐含参数this,这里this == nullptr。

第一段代码,没有访问成员变量_a,所以正常运行
第二段代码,访问成员变量_a,那么就触发了空指针访问,运行崩溃。

  1. this指针存在内存哪个区域的 ()
    A. 栈 B.堆 C.静态区 D.常量区 E.对象⾥⾯

this指针是成员函数的形参,函数调用时作为实参传入,形参开辟在栈上。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/10 2:56:59

价格回暖、机型分化,风电大基地发展“转向”!

今年开年至9月&#xff0c;风电大基地招采市场如火如荼。3月&#xff0c;华能赤峰翁牛特旗2GW大基地开标&#xff0c;三一重能东方电气瓜分&#xff0c;裸机报价1270–1295元/kW。9月&#xff0c;中广核新疆哈密伊州华曜1200 MW陆上风电项目开标&#xff0c;金风科技独揽&#…

作者头像 李华
网站建设 2026/10/10 2:56:03

薄膜开关行业应用解决方案:从医疗到工业的落地拆解

本文把薄膜开关放到真实行业场景中拆解&#xff0c;覆盖应用地图、方案要点和落地注意事项。一、应用地图行业典型场景核心价值家电微波炉、电磁炉、洗衣机控制面板面板一体化、防潮易清洁、按键寿命长工业控制仪器仪表、操作面板抗油污、密封可靠、适合复杂环境医疗监护仪、输…

作者头像 李华
网站建设 2026/10/10 2:55:58

Flink 2.3.0 从理论到实践 —— 第 3 章 环境准备与集群部署

Flink 2.3.0 从理论到实践 —— 第 3 章 环境准备与集群部署 课程定位&#xff1a;本章动手搭建 Flink 2.3.0 运行环境&#xff0c;从本地模式到 Standalone、YARN、Kubernetes 三种部署形态&#xff0c;覆盖 flink-conf.yaml 核心参数、Web UI 与 REST API 使用&#xff0c;以…

作者头像 李华
网站建设 2026/10/10 2:55:13

EtherCAT 的 PDO 映射到底怎么工作的?从对象字典到过程数据内存

在前面的文章中&#xff0c;我们已经从 EtherCAT 主站的角度理解了 IgH EtherCAT Master 的基本架构&#xff0c;也进一步分析了 Datagram、PDO 和 Domain。到了这里&#xff0c;一个非常关键的问题自然会出现&#xff1a;EtherCAT 从站明明已经被 IgH 识别了&#xff0c;为什么…

作者头像 李华
网站建设 2026/10/10 2:54:45

【YOLO】YOLOv5原理

===== 【相关资源获取途径】 ===== 1.CSDN:YOLO专栏 2.---------: XiaoJ1234567 目录 1. YOLOv5 概述 1)目标检测与 YOLO 2)检测网络三段式(Backbone,Neck,Head) 3)YOLOv5 介绍 2. YOLOv5 结构及参数 1)YOLOv5 模型整体结构 2)YOLOv5 整体参数(yaml 里) 3)YOLOv5 …

作者头像 李华