类和对象(中)

简介: 类和对象(中)

类和对象(中)

主要是对于类对象大小的计算,以及复习结构体内存对齐规则,还有对于this指针的理解和使用,最后是关于构造函数和析构函数的讲解

如何计算类对象的大小

对于计算一个类型的大小,我们首先想到的是sizeof关键字,我们知道class或者struct都是自定义类型,所以我们根据sizeof也是可以计算类对象的大小

class Person{
  int data;
  double name;
};
int main()
{
    int ans1=sizeof(Person);
    Person p;
    int ans2=sizeof(p);
    cout<<ans1<<" "<<ans2<<endl;
    return 0;
}

根据上述程序可知,我们知道了,C++中对于类的大小的计算是根据类中的成员变量在内存中的大小决定的,反应了,C++是对于C语言的延申,保留了C语言中对于结构体的内存的计算方式,类的内存大小计算方式与结构体类似,但是有一点不同,下面代码讲解

struct A{};
class B{};
int main()
{
    int ans1=sizeof(A);
    int ans2=sizeof(B);
    return 0;
}

如果类中没没有成员变量,或者就是为空,默认空间大小为1,这是因为,我们要对于这个类预留一个字节的空间,毕竟是一个自定义类型,怎么能没有空间呢?(这样讲好理解)

类中的成员变量和函数是如何保存的呢

每一个对象中的成员变量是不一样的,但是带哦用同一个函数,如果按照这个方法来存储,当类创建多个对象的时候,每一个对象中都会保存一部分相同的代码,这样浪费空间,所以让我们来看看类中的成员是如何保存的?

  • 只保留成员变量,成员函数放在公共区的代码段
    所以我们sizeof得到类的大小的时候,实际上就是成员变量的在内存中的大小,函数是对象调用的时候,才去公共区寻找
  • 一个类的大小实际上就是该类中成员变量之和(内存对齐之后),当为空类时候,编译器给了空类一个字节来唯一标识这个类的对象

结构体内存对齐的规则

结构体内存对齐,详情在C语言篇章的结构体部分已经讲解过了,这里就用代码一笔带过

struct Person{
    int data;
    double name;
};//输出结果为   16  
int main()
{
    cout<<sizeof(Person)<<endl;
    return 0;
}

内存对齐步骤

1.第一个成员与结构体偏移量的0地址处对齐

2.其他成员变量一套对齐某个数字(对齐数)的整数倍的地址处对齐数:编译器默认的一个对齐数与这个成员大小的较小值,VS中默认对齐数为8(当然可以用代码来规定对齐数是几)

3.结构体的总大小为:最大对齐数的整数倍最大对齐数:所有变量类型的最大者与默认对齐数取最小

4.如果嵌套了结构体的情况下,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数的整数倍

上述已经对于内存对齐问题做了详细讲解,接下来是相关的面试题

  • 结构体怎么对齐? 为什么要进行内存对齐?
    上文已经很详细了,是有他设置的内存对齐规则,内存对齐是为了编译器读取数值的时候方便,且更多的是因为
    1.平台原因(移植原因)不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特 定类型的数据,否则抛出硬件异常。
    2. 性能原因:数据结构(尤其是栈)应该尽可能地在自然边界上对齐。 原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。

  • 如何让结构体按照指定的对齐参数进行对齐?能否按照3、4、5即任意字节对齐?
    是可以通过宏指令来改变默认对齐数,改变成2的n次方的指定数值,#pragma pack(任意值),所以3,5不可以,4可以。
  • 什么是大小端?如何测试某台机器是大端还是小端,有没有遇到过要考虑大小端的场景
    大小端是编译器的存储方式

大端(存储)模式:是指数据的低位保存在内存的高地址中,而数据的高位,保存在内存的低地址中。

小端(存储)模式:是指数据的低位保存在内存的低地址中,而数据的高位,,保存在内存的高地址中。

  • 通过运用指针的知识可以测试大小端
int main()
{
    int a=1;
    char* i=(char*)&a;
    if(*i==1){
        cout<<"大端"<<endl;
    }else{
        cout<<"小端"<<endl;
    }
    return 0;
}

内存对齐实际上是用空间来换取时间的做法,为了更加节省空间,我们应该在写结构体的时候,将占用空间小的成员尽量放在一起

this指针

this指针的使用,我们来看下面的代码

#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;
class Person {
public:
  int add(int x, int y) {
    return x + y;
  }
  void getName() {
    cout << name << endl;
  }
private:
  int data;
  string name;
};
int main()
{
  Person p1, p2;//我们创建了两个对象,分别调用了两个方法,都要输出对象中的成员变量name
  p1.getName();
  p2.getName();//我们知道这样的输出结果是不同的,那么我们是怎么使得编译器来区分不同的对象使用相同的函数得到不同的结果呢
  return 0;
}

这就引出了this指针的概念:C++编译器给每个“非静态的成员函数”增加了一个this指针,这是一个隐藏的指针参数,让该指针指向当前对象(调用函数方法的那个对象),在函数体中所有的成员变量的操作(比如输出name),都是通过该this指针来访问的,只不过所有操作对于用户来讲是观察不到的,即编译器会自动完成

this指针的特性

1.this指针的类型:类名类型* const,即成员函数中,不能给this种子很赋值。比如this=nullptr这是会报错的

2.只能在成员函数的内部使用,在类域外或者是成员变量处是无法使用的

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

4.this指针是成员函数的第一个隐含的指针形参,一般情况下由编译器通过ecx寄存器自动传递,不需要用户传递

//对于上面四条this的总结,我们来看下面的代码
//1.对于指针类型
class Person{
  public:
    void Print(Person* const this)//实际上是这样
    {
        cout<<this._name<<endl;
    }//这就是 类名* const this
  private:
    string _name;   
};
//2.
class Person{
  public:
    void Print()
    {
        cout<<_name<<endl;
    }
  private:
    string _name;  
    //不能在这里使用如
    this._name=.....;
};
//当然也不能在类域外使用 
//3.对象中并不存储this指针,只是在调用成员函数的时候隐式使用this形参,将对象的信息(地址)给这个指针

面试题

  1. this指针存在哪里?

this指针是形参,所以是跟普通参数一样存在函数调用的栈帧里面

  1. this指针可以为空吗?

this指针语法上可以为空的,但是也要根据是否对this解引用来判断,如果需要调用成员变量(this解引用),那就不能为空,反之可以,this指针是否为空,是看对象的,因为this指针的值实际上是对象在ecx寄存器中的地址值,也就是说,如果对象地址为空,this也为空,只要不用到类中的相关变量的信息就没有影响,程序正常运行,反之,会运行崩溃

this为空时,程序正常运行代码,不需要对this解引用

class Person{
  void Print(){
      cout<<"Print()"<<endl;//只是输出Print(),没有进行调用类中成员变量,所以就算this为空,也会正常运行程序
  }  
    int name;
};
int main()
{
    Person* p=nullptr;//对象地址赋值为nullptr
    p.Print();//这个输出结果为  Print()
  return 0;
}
//p调用Print,不会发生解引用,因为Print函数地址不在对象中,p会作为实参传递给this指针

this为空时,程序崩溃,需要对this解引用

class Person{
  void Print(){
      cout<<name<<endl;//这里我们要调用类的成员变量,但是因为,this为空(对象为空),所以无法调用,即程序崩溃
  }  
    int name;
};
int main()
{
    Person* p=nullptr;//对象地址赋值为nullptr
    p.Print();//调用类的成员函数
  return 0;
}
//p调用Print,不会发生解引用,因为Print函数地址不在对象中,p会作为实参传递给this指针

类的6个默认成员函数

空类:类中什么成员都没有

空类中没有成员,但是会有编译器自动生成的6个默认成员函数

默认成员函数:用户没有显式实现,编译器会生成的成员函数称为默认成员函数

构造函数

构造函数:就是对于类的成员变量进行初始化的函数,当没有自定义构造函数的时,编译器会使用默认构造函数,所以自己写,实际上算是函数重载

//下面是对于构造函数的使用
class Person{
  int data;   //只有变量,没有自定义构造函数
};
int main()
{
    //实例化类Person
    Person p;//这个时候,我们创建了p这个对象,也就直接调用了默认构造函数
    return 0;
}

自定义构造函数的方法:不需要返回值,构造函数的函数名为类名,括号内写形参

构造函数是一个特殊的成员函数,名字与类名相同,创建类类型对象时由编译器自动调用,以保证每个数据成员都有一个合适的初始值,并且在对象整个生命周期内只调用一次。

特点如下

  1. 函数名与类名相同。
  2. 无返回值。
  3. 对象实例化时编译器自动调用对应的构造函数。
  4. 构造函数可以重载。
//下面是代码演示
class Person{
public:
    Person(){}//无参构造函数
    Person(int data){//有参构造函数
        this.data=data;
    }
private:
    int data;
};
int main()
{
    Person p;//这是调用无参构造函数
    Person d(10);//调用有参构造函数
    return 0;
}

调用无参构造的时候,不需要加上括号(),如果加上的话,和函数声明有逻辑冲突,,因为这个时候Person是自定义类型。

如果没有显示定义构造函数,那么编译器就会隐式定义默认构造函数,(有显示用显示,无显示用隐式)

默认构造函数有没有用

  • 我们使用默认构造函数之后,发现得到的数值都是随机值,这样看起来编译器生成的默认构造函数并没有什么用?
  • 但是,我们知道C++将类型分成内置类型(基本类型,int,double,int*……),和自定义类型。自定义类型就是我们使用class/struct/union等自己定义的类型。
  • 下面的程序,会发现,编译器对于自定义类型变量会调用他的构造函数
using namespace std;
class Person{
private:
    int data;
    string name;
    Student _std;
};
class Student{
  public:
    Student()
    {
        cout<<"Stduent()"<<endl;
        data=10;
    }
private:
    int data;
};
int main()
{
    Person p;
    return 0;
}

当类中有自定义函数的时候,调用构造函数的顺序为:按照自定义类型在成员变量顺序调用,最后是类的构造函数

类中的自定义类型调用构造函数的时候,不能是有参构造,应该是无参构造

如果是调用默认构造的时候,成员变量都是随机值,可能有些编译器在存在自定义变量的时候,初始化会进行优化,不再是随机值,但是我们默认这个,调用默认构造,成员变量就是随机值

C++11的特性:加上了,对于类的内置类型可以在声明的时候进行赋值

class Person{
public:
  int data=10;  //基本类型(内置类型)
};
int main()
{
    Person p;
    cout<<p.data<<endl;
    return 0;
}

无参构造函数和全缺省的构造函数都为默认构造函数,且默认构造函数只能有一个

注意:无参构造函数、全缺省构造函数、编译器默认生成的构造函数,都可以认为是默认构造函数

class Person{
  public:
    Person(){
        _data=10;//无参构造函数
    }
    Person(int data=10){
        _data=data;
    }//全缺省构造函数   //这两个不能同时存在,语法没有问题,在编译的时候有问题
  private:
    int _data;
};
int main()
{
    Person p;
    return 0;
}

这两个不能同时存在,语法没有问题,在编译的时候有问题。

析构函数

我们都知道C语言中使用指针的时候,结束使用的时候,使用free来释放空间,指向NULL来防止野指针,在C++中我们只需要一个函数就可以完成这个事情,那就是析构函数

析构函数的特征

  1. 析构函数名是在类名前加上字符 ~。
  2. 无参数无返回值类型。
  3. 一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。注意:析构函数不能重载
  4. 对象生命周期结束时,C++编译系统系统自动调用析构函数
class Person{
public:
    Person()
    {
        cout<<"Person()构造函数"<<endl;
    }
    ~Person()
    {
        cout<<"~Person()析构函数"<<endl;
    }
private:
    int data;
    Student s;
};
class Student{
public:
    Student(){
         cout<<"Student()构造函数"<<endl;
    }
   ~Student()
   {
       cout<<"~Student()析构函数"<<endl;
   }
};
int main()
{
    Person p;
    return 0;
}

总结:

  1. 当类中有自定义类型时,先调用该自定义类型的构造函数(多个自定义函数,就按照顺序来依次调用)
  2. 当类中有自定义类型的时候,先调用这个类的析构函数,然后调用自定义类型的析构函数
  3. 内置类型成员,销毁时不需要资源清理,最后系统直接将其内存回收即可


相关文章
|
数据可视化
Paraview: Calculate Derivatives of 3-D Unstructured Dataset
关注九天学者微信公众号(扫码关注)第一时间获取技术贴更新! Paraview 是一款基于VTK的、开源的、跨平台的科学数据可视化软件,其三维显示和后处理功能非常强大。
3358 0
|
数据格式 XML 存储
带你读《多媒体技术教程(原书第2版)》之一:多媒体导论
本书内容取自课堂上讲述的实际素材,适合作为计算机科学和工程专业学生的教材。从多媒体数据表现、多媒体数据压缩、多媒体通信和联网、多媒体信息分享和检索四个层面对多媒体涉及的基本概念、基本原理和基本技术进行了详细介绍。作者用一种实用的方式来讲述基本概念,使学生能够运用享有的技术来解决现实多媒体世界中的问题。
|
1月前
|
自然语言处理 安全 API
大模型选型:选择适配的智能体核心引擎
OPC创业者选大模型,重适配而非最优!DeepSeek性价比高,GPT强于复杂推理,豆包免费易用,千问适配企业,Claude胜在安全。推荐“主力DeepSeek+关键任务GPT+质检Claude”组合,按任务动态选模,快速落地优先。
|
算法 安全 定位技术
最新V2.0社交交友系统源码-语聊交友运营级别成品源码-三端源码交付核心功能模块详解(附代码示例)
这是一款2026年合规交友平台解决方案,集成智能匹配、语聊直播、会员变现与分销体系,支持私有化部署,内置实名认证、AI内容审核等安全模块,助力企业合法高效运营。
228 0
最新V2.0社交交友系统源码-语聊交友运营级别成品源码-三端源码交付核心功能模块详解(附代码示例)
|
Android开发 安全
我的Android进阶之旅------&gt;Android中adb install 安装错误常见列表
adb的安装过程分为传输与安装两步。 在出错后,adb会报告错误信息,但是信息可能只是一个代号,需要自己定位分析出错的原因。      下面是从网上找到的几种常见的错误及解决方法: 1、INSTALL_FAILED_INVALID_APK:无效的安装包,安装包已损坏 请检查安装包是否完整。
2701 0
|
4月前
|
机器学习/深度学习 人工智能 算法
孪生素数分布的大规模计算实验:分段筛实现与经验统计分析
孪生素数猜想断言存在无穷多对形如 (p,p+2)(p,p+2) 的素数,但该猜想至今仍未解决。Hardy–Littlewood 的第一猜想给出了孪生素数计数函数的经典启发式渐近公式,而张益唐、Maynard 与 Polymath8 的工作则证明了存在无穷多对素数,其间隔有统一上界,但这一上界尚未降至 2 [1–3]。
555 13
|
6月前
|
Java 关系型数据库 MySQL
零基础入门 Go 语言
Go语言以其高性能、原生并发和极简语法成为云原生开发的新宠。本文全面介绍了Go的核心特性,包括环境搭建、基础语法、函数、结构体与接口等核心概念,重点解析了goroutine和channel的并发模型。通过对比Java,展示了Go在部署、并发和语法简洁性上的优势,并提供了HTTP服务器和MySQL操作等实战案例。文章还总结了Go开发的最佳实践,帮助开发者快速掌握这门高效的后端开发语言。
832 1
|
7月前
|
存储 安全 数据管理
没听过冷数据?一文带你读懂冷数据
冷数据指长期不用但需合规保存的历史数据,如旧订单、合同等。它虽不常用,却关乎成本、安全与合规。管理不当将导致存储浪费、系统变慢、审计风险。应通过分类、分级存储、自动归档与索引管理,确保“用时能查”,实现数据治理的精细化与可持续化。
|
7月前
|
SQL 自然语言处理 数据可视化
分析Agent产品推荐:Quick BI智能小Q,中小企业用得起的对话式ChatBI分析工具
Quick BI智能小Q,连续6年入选Gartner魔力象限,专为中小企业打造的对话式BI工具。无需技术背景,用自然语言提问即可秒级获取数据洞察,打破数据孤岛,实现“所问即所得”。低成本、易上手、响应快,助力企业高效决策,让数据驱动真正普惠化。Quick BI智能小Q,中小企业用得起的对话式BI分析工具。
|
10月前
|
机器学习/深度学习 算法 调度
【微电网优化调度】五种多目标优化算法(MSSA、MOAHA、MOPSO、NSGA3、NSGA2)求解微电网多目标优化调度研究【创新未发表】(Matlab代码实现)
【微电网优化调度】五种多目标优化算法(MSSA、MOAHA、MOPSO、NSGA3、NSGA2)求解微电网多目标优化调度研究【创新未发表】(Matlab代码实现)
460 0

热门文章

最新文章