类和对象、this指针、类里的默认生成函数

🐶博主主页:@ᰔᩚ. 一怀明月ꦿ 

❤️‍🔥专栏系列:线性代数,C初学者入门训练,题解C,C的使用文章,「初学」C++,linux

🔥座右铭:“不要等到什么都没有了,才下定决心去做”

🚀🚀🚀大家觉不错的话,就恳求大家点点关注,点点小爱心,指点指点🚀🚀🚀

目录

类和对象

类的引入

类的定义

类的定义方式

1.声明和定义放在类体

2. 声明放在.h文件中,类的定义放在.cpp文件中

类的访问限定符及封装

访问限定符

类的作用域 

类的实例化

类对象模型

结构体内存对齐规则

this指针

this指针的特性

this指针存在哪里?

this指针可以为空吗?

补充

类里的默认生成函数

构造函数

析构函数


类和对象

面向过程和面向对象初步认识

C语言是面向过程的,关注的是过程,分析出求解问题的步骤,通过函数调用逐步解决问题。

C++是基于面向对象的,关注的是对象,将一件事情拆分成不同的对象,靠对象之间的交互完成

类的引入

C语言中,结构体中只能定义变量,在C++中,结构体内不仅可以定义变量,也可以定义函数。

struct ADD
{
    void add(int x,int y)
    {
        cout<<x+y<<endl;
    }
    
    int a;
    int b;
};

C++兼容C语言,结构体可以继续使用,同时struct也升级成了类

struct A
{
    int age;
    int score;
};
int main()
{
    struct A a1;
    A a2;
}
定义变量时,既可以struct A a1;,也可以A a2;

但是在C++中更喜欢使用class来代替

类的定义

class 类名
{
    类体:成员函数和成员变量组成
};//一定主意后面的分号

class为定义类的关键字,类中的元素称为类的成员,类中的数据称为类的属性或者成员变量; 类中的函数称为类的方法或者成员函数。

类的定义方式

1.声明和定义放在类体

注意:成员函数如果在类中定义,编译器可能将其当成内联函数

class student
{
  void show()
    {
      cout<<_name<<_sex<<_age<<endl;
  }
    char* _name;
    char* _sex;
    int _age;
};
2. 声明放在.h文件中,类的定义放在.cpp文件中
class student
{
    void show();
    char* _name;
    char* _sex;
    int _age;
};

#include"main.h"
void student::show()
{
    cout<<_name<<_sex<<_age<<endl;
}
建议我们使用第二种方法

类的访问限定符及封装

访问限定符

public(公有) protected(保护) private(私有)

1. public修饰的成员在类外可以直接被访问

2. protected和private修饰的成员在类外不能直接被访问(此处protected和private是类似的)

3. 访问权限作用域从该访问限定符出现的位置开始直到下一个访问限定符出现时为止

4. class的默认访问权限为private,struct为public(因为struct要兼容C)

注意:访问限定符只在编译时有用,当数据映射到内存后,没有任何访问限定符上的区别

C++中struct和class的区别是什么?

C++需要兼容C语言,所以C++中struct可以当成结构体去使用。另外C++中struct还可以用来定义类。和class是定义类是一样的,区别是struct的成员默认访问方式是public,class是struct的成员默认访问方式是private

封装

封装:将数据和操作数据的方法进行有机结合,隐藏对象的属性和实现细节,仅对外公开接口来和对象进行交互。

类的作用域 

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

例如成员函数的类外定义

void student::show()
{
    cout<<_name<<_sex<<_age<<endl;
}

类的实例化

用类类型创建对象的过程,称为类的实例化

1. 类只是一个模型一样的东西,限定了类有哪些成员,定义出一个类并没有分配实际的内存空间来存储它

2. 一个类可以实例化出多个对象,实例化出的对象 占用实际的物理空间,存储类成员变量

3. 做个比方。类实例化出对象就像现实中使用建筑设计图建造出房子,类就像是设计图,只设计出需要什么东西,但是并没有实体的建筑存在,同样类也只是一个设计,实例化出的对象才能实际存储数据,占用物理空间

class student
{
    void show();
    char* _name;
    char* _sex;
    int _age;
};
int main()
{
    student s1;
    return 0;
}
其中的student类实例化了一个对象s1

类对象模型

如何计算类对象的大小

首先我们得搞明白类的存储方式:只保存成员变量,成员函数放在公共的代码代码段

//成员函数和成员变量
class student1
{
    void show(){}
    char* _name;
    char* _sex;
    int _age;
};
//成员函数
class student2
{
    void show(){}
};
//成员变量
class student3
{
    char* _name;
    char* _sex;
    int _age;
};
//空类
class student4
{
};
int main()
{
    cout<<"class student1:"<<sizeof(student1)<<endl;
    cout<<"class student2:"<<sizeof(student2)<<endl;
    cout<<"class student3:"<<sizeof(student3)<<endl;
    cout<<"class student4:"<<sizeof(student4)<<endl;
}
结果:
class student1:24
class student2:1
class student3:24
class student4:1

结论:一个类的大小,实际就是该类中”成员变量”之和,当然也要进行内存对齐,注意空类的大小,空类比较特殊,编译器给了空类一个字节来唯一标识这个类(就是告诉编译器这个类存在)。

结构体内存对齐规则

1.第一个成员与结构体变量偏移量为0的地址处
2.其他成员变量要对齐到某个数(对齐数)的整数倍的地址处
3.对齐数=编译器默认的一个对齐数(8)与该成员大小的较小值
4.结构体的总大小是结构体的所有成员的对齐数中最大的那个对齐数的整数倍
5.如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍

对齐数的详细分析:「自定义类型」C语言中的构造数据类型如结构,联合,枚举-CSDN博客

this指针

在C++中,this指针是一个特殊的指针,它指向当前对象的地址。它是作为类的非静态成员函数的隐式参数传递给函数的,可以在成员函数中使用它来访问当前对象的成员变量和成员函数。

事例

class Box
{
public:
    Box(double length,double width,double height)
    {
         _length=length;
         _width=width;
         _height=height;
    }
    void volume()
    {
        cout<<_length*_width*_height<<endl;
    }
private:
    double _length;
    double _width;
    double _height;
};
int main()
{
    Box box1(2,3,5);
    Box box2(1,2,3);
}

c++为类的对象分配内存空间时只为对象的数据成员分配内存空间,而将对象的成员函数放在另外一个公共区域,同一个类的多个对象共享它们的成员函数。那么,同一个类的多个对象的成员函数在访问对象的数据成员时,怎么确保访问的是正确的数据成员呢?例如声明的长方体类Box,定义了两个对象box1和box2,对于调用“box1.volume()”,应该box1中的_height,_width和_length计算长方体box1的体积,对于“box2.volume()”,应该box2中的_height,_width和_length计算长方体box1的体积。现在box1和box2调用的都是同一段代码,系统是怎么区分应该访问box1的数据成员还是box2的数据成员呢?


其实在每一个成员函数中都包含了一个特殊的指针,这个指针的名字是固定的,称为this指针。this指针是指向本类对象的指针,它的指向是被调用成员函数所在的对象,即调用哪个对象的该成员函数,this指针就指向哪个对象。在成员函数内部访问数据成员的前面隐藏着this指针。如前面前面提到的Box类中的volume函数,其中的height*width*length实际等价于(this->_height)*(this->_width)*(this->_length)。如果调用box1对象的volume函数,则this指针就指向对象box1,所以(this->_height)*(this->_width)*(this->_length)就相当于(box1._height)*(box1._width)*(box1._length),这样就是计算的box1的体积。

this指针的特性

1. this指针的类型:类类型* const(指针常量)

2. 只能在“成员函数”的内部使用

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

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

this指针存在哪里?

其实编译器在生成程序时加入了获取对象首地址的相关代码。并把获取的首地址存放在了寄存器ECX中(vs2019编译器是放在ECX中,其它编译器有可能不同)。也就是成员函数的其它参数正常都是存放在栈中。而this指针参数则是存放在寄存器中。类的静态成员函数因为没有this指针这个参数,所以类的静态成员函数也就无法调用类的非静态成员变量。

this指针可以为空吗?

可以为空,当我们在调用函数的时候,如果函数内部并不需要使用到this,也就是不需要通过this指向当前对象并对其进行操作时才可以为空(当我们在其中什么都不放或者在里面随便打印一个字符串),如果调用的函数需要指向当前对象,并进行操作,则会发生错误(空指针解引用)

补充

下面的运行结果?1.编译报错    2.运行崩溃    3.正常运行

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

结果是正常运行,调用函数分为两步,第一步传参,第二步传函数的地址,成员函数的地址不存在对象里面,成员函数地址在公共代码区域,所以 p->Print();没有对p解引用,所以不会报错。

下面的运行结果?1.编译报错    2.运行崩溃    3.正常运行

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

结果是运行崩溃,cout<<_a<<endl;这里会转化为 cout<<this->_a<<endl;this是指向p,所以这里对空指针解引用,所以运行崩溃

类里的默认生成函数

如果一个类中什么成员都没有,简称为空类。空类中什么都没有吗?并不是的,任何一个类在我们不写的情况下,都会自动生成下面6个默认成员函数

1.构造函数

2.析构函数

3.拷贝构造函数

4.赋值运算符的重载函数

5.取地址操作符的重载函数

6.const修饰的取地址操作符的重载函数

构造函数

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

构造函数是特殊的成员函数,需要注意的是,构造函数的虽然名称叫构造,但是需要注意的是构造函数的主要任务并不是开空间创建对象,而是初始化对象。

其特征如下:

1. 函数名与类名相同。

2. 无返回值。

3. 对象实例化时编译器自动调用对应的构造函数。

4. 构造函数可以重载

class Date
{
public:
    //无参数的构造函数
    Date(){}
    //有参数的构造函数
    Date(int year, int month, int day)
    {
    _year = year;
    _month = month;
    _day = day;
    }
    void Display()
    {
        cout <<_year<< "-" <<_month << "-"<< _day <<endl;
    }
private:
        int _year;
        int _month;
        int _day;
    };
int main()
{
    //注意:如果通过无参构造函数创建对象时,对象后面不用跟括号,否则就成了函数声明
    Date d1;//调用无参的构造函数
    Date d2(2023,7,20);//调用带参数的构造函数
    return 0;
}

5.如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦用户显式定义编译器将不再生成。

class Date
{
public:
    void Display()
    {
        cout <<_year<< "-" <<_month << "-"<< _day <<endl;
    }
private:
        int _year;
        int _month;
        int _day;
    };
int main()
{
    Date d;
    return 0;
}

6.无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个。注意:无参构造函数、全缺省构造函数、我们没写编译器默认生成的构造函数,都可以认为是默认成员函数。

class Date
{
public:
    //无参数的构造函数
    Date(){}
    //全缺省参数的构造函数
    Date(int year=0, int month=0, int day=0)
    {
    _year = year;
    _month = month;
    _day = day;
    }
    void Display()
    {
        cout <<_year<< "-" <<_month << "-"<< _day <<endl;
    }
private:
        int _year;
        int _month;
        int _day;
    };
int main()
{
    Date d1;
    Date d2(2023,7,20);
    return 0;
}

这里编译会出错, Date d1;创建d1这个对象时,没有传递参数,但是构造函数一个是无参的,一个是全缺省的,编译器不知道调用哪一个,这里就是典型的二义性错误。

7.关于编译器生成的默认成员函数,很多人就会疑惑:在我们不实现构造函数的情况下,编译器会生成默认的构造函数。但是看起来默认构造函数又没什么用?d对象调用了编译器生成的默认构造函数,但是d对象_year,_month,_day,依旧是随机值。也就说在这里编译器生成的默认构造函数并没有用?

C++把类型分成内置类型(基本类型)和自定义类型。内置类型就是语法已经定义好的类型:如int/char…(所有的指针类型都是内置类型),自定义类型就是我们使用class/struct/union自己定义的类型,看看下面的程序,就会发现编译器生成默认的构造函数会对自定类型成员_t调用的它的默认成员函数

class Time
{
public:
    Time(int hour=0, int minute=0, int second=0)
    {
    cout<<"Time(int hour=0, int minute=0, int second=0)"<<endl;
    _hour=hour;
    _minute=minute;
    _second=second;
    }
private:
    int _hour;
    int _minute;
    int _second;
};
class Date
{
private:
    //基本类型(内置类型)
        int _year;
        int _month;
        int _day;
    //自定义类型
    Time _time1;
};
int main()
{
    Date d;
    return 0;
}
结果:
Time(int hour=0, int minute=0, int second=0)

析构函数

概念

前面通过构造函数的学习,我们知道一个对象时怎么来的,那一个对象又是怎么没呢的?

析构函数:与构造函数功能相反,析构函数不是完成对象的销毁,局部对象销毁工作是由编译器完成的。而对象在销毁时会自动调用析构函数,完成类的一些资源清理工作

特性

析构函数是特殊的成员函数。

其特征如下: 

1. 析构函数名是在类名前加上字符 ~。

2. 无参数无返回值。

3. 一个类有且只有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。

4. 对象生命周期结束时,C++编译系统系统自动调用析构函数。

class Date
{
public:
    Date(int year=0, int month=0, int day=0)
    {
        _year = year;
        _month = month;
        _day = day;
    }
    ~Date()
    {
        cout<<"~Date()"<<endl;
    }
private:
        int _year;
        int _month;
        int _day;
    };
int main()
{
    Date d;
    return 0;
}
结果:
~Date()

5. 关于编译器自动生成的析构函数,是否会完成一些事情呢?下面的程序我们会看到,编译器生成的默认析构函数,对会自定类型成员调用它的析构函数。

class Time
{
public:
    Time(int hour=0, int minute=0, int second=0)
    {
    cout<<"Time(int hour=0, int minute=0, int second=0)"<<endl;
    _hour=hour;
    _minute=minute;
    _second=second;
    }
    ~Time()
    {
        cout<<"~Time()"<<endl;
    }
private:
    int _hour;
    int _minute;
    int _second;
};
class Date
{
private:
    //基本类型(内置类型)
        int _year;
        int _month;
        int _day;
    //自定义类型
    Time _time1;
};
int main()
{
    Date d;
    return 0;
}
结果:
Time(int hour=0, int minute=0, int second=0)
~Time()

设计析构函数的本意是对象生命周前结束前完成资源清理,比如,在C语言中写的栈,有个函数是完成栈的销毁,但是我们很容易忘记调用,在这里我们就可以把销毁的函数放在析构函数中,系统会自动调用。资源清理主要是清理系统不能执行的,清理动态内存申请的空间等。其实我们还可以利用析构函数会被自动调用的特性,完成一些打印的功能等。

  🌸🌸🌸如果大家还有不懂或者建议都可以发在评论区,我们共同探讨,共同学习,共同进步。谢谢大家! 🌸🌸🌸 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/603913.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【仪酷LabVIEW AI工具包案例】使用LabVIEW AI工具包+YOLOv5结合Dobot机械臂实现智能垃圾分类

‍‍&#x1f3e1;博客主页&#xff1a; virobotics(仪酷智能)&#xff1a;LabVIEW深度学习、人工智能博主 &#x1f384;所属专栏&#xff1a;『仪酷LabVIEW AI工具包案例』 &#x1f4d1;上期文章&#xff1a;『【YOLOv9】实战二&#xff1a;手把手教你使用TensorRT实现YOLOv…

升级PQC后,「谷歌浏览器」却频频报错......

上周Chrome 124发布后&#xff0c;默认启用了新的抗量子X25519Kyber768封装机制&#xff0c;不过&#xff0c;一些谷歌Chrome用户在使用后反应说&#xff0c;新版本的浏览器在连接网站、服务器和防火墙时出现了问题。 谷歌在8月份开始测试后量子安全TLS密钥封装机制&#xff0c…

嫦娥六号近月制动成功,建立月球基地又迈进一步!

嫦娥六号探测器在近月制动的关键时刻&#xff0c;北京航天飞行控制中心内弥漫着紧张而庄重的氛围。每一个航天科技工作者都屏息以待&#xff0c;他们的眼神中充满了期待与自豪。随着一系列精妙绝伦的指令如同琴弦上的音符般流畅地奏响&#xff0c;嫦娥六号探测器在万众瞩目的目…

使用SpringBoot+Redis做一个排行榜【推荐】

SpringBoot Redis实现排行榜 一、Zset有序集合介绍 Zset是一个没有重复元素的字符串集合。不同之处是有序集合的每个成员都关联了一个评分( score) ,这个评分( score)被用来按照从最低分到最高分的方式排序集合中的成员。集合的成员是唯一的&#xff0c;但是评分可以是重复了…

通讯录项目—顺序表实现

在上次我介绍顺序表后相信大家对顺序表有了一定的了解&#xff0c;现在就让我们来练练如何用它&#xff0c;这篇是在顺序表基础上新增的(建议看看线性表—顺序表实现-CSDN博客)。 目录 通讯录简介 创建用户信息 适配和理解通讯录 功能实现 初始化通讯录 销毁通讯录 增加…

为什么跑腿越来越受到年轻人的青睐

跑腿服务越来越受到年轻人的青睐&#xff0c;主要源于以下几个方面的原因&#xff1a; 1. 便捷快速&#xff1a;在快节奏的现代生活中&#xff0c;年轻人追求的是效率和速度。跑腿服务提供了一种即时、便捷的解决方案&#xff0c;使他们能够在繁忙的生活和工作中节省时间和精力…

鸿蒙开发实战:智能日志定位与高效调试技巧

前言 在鸿蒙系统的开发过程中,日志定位是一个关键的调试步骤。想象一下,如果你能够轻松地在繁杂的代码中快速定位到日志产生的位置,那将会极大地提高你的开发效率。今天,我将分享一套代码,它能帮助你实现这一目标。 效果展示 当你使用这套代码时,日志的打印效果将如下…

qt开发解压缩zip文件实现

作者开发环境&#xff1a;Qt5.8 &#xff0c;win10 总体思路&#xff1a;首先我们编译zip源码&#xff0c;生成zip的动态库&#xff1b;然后再编译quazip源码&#xff0c;得到quazip的动态库&#xff1b;最后在我们的程序中去调用。 详细步骤&#xff1a; 1、编译zlib zlib…

Day22 代码随想录打卡|字符串篇---实现 strStr()

题目&#xff08;leecode T28&#xff09;&#xff1a; 给你两个字符串 haystack 和 needle &#xff0c;请你在 haystack 字符串中找出 needle 字符串的第一个匹配项的下标&#xff08;下标从 0 开始&#xff09;。如果 needle 不是 haystack 的一部分&#xff0c;则返回 -1…

WSL2中使用USB串口实验

一、主要参考网站: Connect USB devices | Microsoft Learn 连接 USB 设备 | Microsoft Learn 二、安装usbipd-win WSL 本身并不支持连接 USB 设备,因此你需要安装开源 usbipd-win 项目 PS C:\Users\issta> winget install --interactive --exact dorssel.usbipd-win …

yaml配置文件的在深度学习中的简单应用

1 .创作灵感 小伙伴们再阅读深度学习模型的代码的时候&#xff0c;经常会遇到yaml格式的配置文件。用这个配置文件是因为我们在训练模型的时候会涉及很多的参数&#xff0c;如果这些参数东一个&#xff0c;西一个&#xff0c;我们调起来的时候就会很不方便&#xff0c;所以用y…

Springboot+vue项目人事管理系统

开发语言&#xff1a;Java 开发工具:IDEA /Eclipse 数据库:MYSQL5.7 应用服务:Tomcat7/Tomcat8 使用框架:springbootvue JDK版本&#xff1a;jdk1.8 文末获取源码 系统主要分为管理员和普通用户和员工三部分&#xff0c;主要功能包括个人中心&#xff0c;普通用户管理&…

4.用python爬取保存在text中的格式为m3u8的视频

文章目录 一、爬取过程详解1.寻找视频的m3u8链接2.从网页源码中寻找视频的m3u8链接的第二部分内容3.从视频的m3u8链接获取视频 二、完整的代码 一、爬取过程详解 1.寻找视频的m3u8链接 这个文档承接了爬虫专栏的 第一节.python爬虫爬取视频网站的视频可下载的源url&#xff0…

头歌实践教学平台:CG3-v2.0-图形几何变换

第1关&#xff1a;平移、缩放、旋转正方体 一. 任务描述 1. 本关任务 (1) 理解几何变换基本原理, 掌握平移、旋转、缩放变换的方法; (2) 根据平移算法原理补全translation、scale、rotation_x、rotation_y和rotation_z函数; (3) 根据几何变换基本原理,将main函数中的transla…

RK3568 学习笔记 : Linux emmc 内核启动 rootfs 根文件系统无法正常挂载问题的分析

问题描述 平台 &#xff1a; NanoPi-R5C 开发板 RK3568 平台。 手动编译的 Linux 内核&#xff0c;结果发现大概率 emmc 无法正常初始化&#xff0c;导致 rootfs 根文件系统无法正常挂载 Linux 内核版本&#xff1a; 6.1 Linux 内核代码位置&#xff1a; https://github.com…

上网行为监控软件有哪些(上网审计软件)【收藏】

上网行为监控软件&#xff08;也被称为上网审计软件&#xff09;正逐渐成为企业信息安全管理的必备工具。 没错&#xff01; 这些软件通过对员工的上网行为进行全面、细致的监控和审计&#xff0c;帮助企业提升工作效率、保护数据安全&#xff0c;并规范员工的网络使用行为。 …

Springboot+vue项目健身房课程预约平台

开发语言&#xff1a;Java 开发工具:IDEA /Eclipse 数据库:MYSQL5.7 应用服务:Tomcat7/Tomcat8 使用框架:springbootvue JDK版本&#xff1a;jdk1.8 本系统主要实现了首页、个人中心、用户管理、教练管理、会员卡管理、购买会员管理、课程类型管理、课程信息管理、课程购买…

生信新包|LINGER·从单细胞多组学数据推断基因调控网络

题目&#xff1a;Inferring gene regulatory networks from single-cell multiome data using atlas-scale external data 原理 LINGER 是一个计算框架&#xff0c;旨在从单细胞多组学数据推断基因调控网络。 使用基因表达和染色质可及性的计数矩阵以及细胞类型注释作为输入&…

Linux下创建达梦数据库自动备份任务

分享一个自己用的银河麒麟下达梦数据库自动备份任务脚本。 达梦数据库备份脚本。按日期备份&#xff0c;备份后压缩为tar.gz文件&#xff0c;自动清理导出的文件。 备份脚本保留最后30天记录&#xff0c;以节省硬盘空间&#xff0c;可根据具体情况修改。 达梦数据库备份脚本 …

​​​【收录 Hello 算法】第 4 章 数组与链表

第 4 章 数组与链表 数据结构的世界如同一堵厚实的砖墙。 数组的砖块整齐排列&#xff0c;逐个紧贴。链表的砖块分散各处&#xff0c;连接的藤蔓自由地穿梭于砖缝之间。 本章内容 4.1 数组4.2 链表4.3 列表4.4 内存与缓存 *4.5 小结
最新文章