• C++————类与对象<一>


    目录

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

    二、类的引入

    三、类的两种定义

    第一种定义:声明和定义全部放在类体中

    第二种定义:声明放在.h文件中,类的定义放在.cpp文件中

     四、类的访问限定符及封装

    1、访问限定符

    2、封装

    五、类的作用域

    六、类的实例化

    七、类对象大小及存储方式

     八、隐藏的this指针


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

    C 语言是 面向过程 的, 关注 的是 过程 ,分析出求解问题的步骤,通过函数调用逐步解决问题。
    C++ 基于面向对象 的, 关注 的是 对象 ,将一件事情拆分成不同的对象,靠对象之间的交互完成。
    举个例子:好比我们要设计一个外卖点餐系统:
    面向过程:关注的是实现下单、接单、送餐这些过程。它体现到代码层面就是函数(方法)的实现。
    面向对象:关注的则是类与对象间的关系,用户、商家、骑手他们之间的关系
    这个面向过程与面向对象会随着我们对知识了解的深度的加深而加深。

    二、类的引入

    那么,到底什么是类呢?

            其实形如我们c语言中的结构体。比如当我们要录入学生系统中的成绩的时候,我们需要知道学生的班级、姓名、成绩等等。这里的学生就是类。再比如日期也可以作为类,那么就可以包含年、月、日。我们虽说在c语言中将其叫做结构体,但是在c++中我们升级为了类。

    结构体与类的定义变量的区别:

    在c语言中如果我们创建了结构体之后需要定义变量,一般是这样定义的: struct Student s1;但是在c++中我们可以不用加struct,直接用Student s2来定义变量;并且在c++中这里就不能称之为变量了,而是对象(即s2是Student的一个对象);

    如图:

    1. struct Student
    2. {
    3. char name[10];
    4. int age;
    5. int id;
    6. };
    7. int main()
    8. {
    9. struct Student s1;//兼容c
    10. Student s2;//升级到类,Student是类名,也是类型
    11. strcpy(s1.name, "zhangsan");
    12. s1.age = 18;
    13. s1.id = 000;
    14. strcpy(s2.name, "lisi");
    15. s2.age = 19;
    16. s2.id = 111;
    17. return 0;
    18. }

    三、类的两种定义

    1. class ClassName
    2. {
    3. // 类体:由成员函数和成员变量组成
    4. };// 一定要注意后面的分号

    class为定义类的关键字,ClassName为类的名字,{}中为类的主体,注意类定义结束时后面分号。类中的元素成为类的成员:类中的数据成为类的属性或者成员变量;类中的函数称为类的方法或者成员函数。

    第一种定义:声明和定义全部放在类体中

    1. #include
    2. using namespace std;
    3. class Date
    4. {
    5. int _year;
    6. int _month;
    7. int _day;
    8. void Init(int year, int month, int day)
    9. {
    10. _year = year;
    11. _month = month;
    12. _day = day;
    13. }
    14. void Print()
    15. {
    16. cout << _year << "-" << _month << "-" << _day << endl;
    17. }
    18. };
    19. int main()
    20. {
    21. Date d1;
    22. d1.Init(2022, 1, 15);
    23. d1.Print();
    24. return 0;
    25. }

    第二种定义:声明放在.h文件中,类的定义放在.cpp文件中

     四、类的访问限定符及封装

    1、访问限定符

    C++ 实现封装的方式: 用类将对象的属性与方法结合在一块,让对象更加完善,通过访问权限选择性的将其 接口提供给外部的用户使用
    【访问限定符说明】
    1. public 修饰的成员在类外可以直接被访问
    2. protected private 修饰的成员在类外不能直接被访问 ( 此处 protected private 是类似的 )
    3. 访问权限 作用域从该访问限定符出现的位置开始直到下一个访问限定符出现时为止
    4. class 的默认访问权限为 private struct public( 因为 struct 要兼容 C)

    2、封装

    面向对象的三大特性: 封装、继承、多态
    在类和对象阶段,我们只研究类的封装特性,那什么是封装呢?
    封装:将数据和操作数据的方法进行有机结合,隐藏对象的属性和实现细节,仅对外公开接口来和对象进行 交互。
    封装本质上是一种管理:我们如何管理兵马俑呢?比如如果什么都不管,兵马俑就被随意破坏了。那么我们首先建了一座房子把兵马俑给 封装 起来。但是我们目的全封装起来,不让别人看。所以我们 开放了售票通 ,可以买票突破封装在合理的监管机制下进去参观。类也是一样,我们使用类数据和方法都封装到一下。
    不想给别人看到的,我们使用 protected/private 把成员 封装 起来。 开放一些共有的成员函数对成员合理的访问。所以封装本质是一种管理。

    五、类的作用域

    类定义了一个新的作用域 ,类的所有成员都在类的作用域中 在类体外定义成员,需要使用 :: 作用域解析符指明成员属于哪个类域。
    1. class Person
    2. {
    3. public:
    4. void PrintPersonInfo();
    5. private:
    6. char _name[20];
    7. char _gender[3];
    8. int _age;
    9. };
    10. // 这里需要指定PrintPersonInfo是属于Person这个类域
    11. void Person::PrintPersonInfo()
    12. {
    13. cout<<_name<<" "_gender<<" "<<_age<
    14. }

    比如以上代码,由于需要访问private中的成员,所以需要用到::作用域解析符指明成员属于哪个类域。

    六、类的实例化

    用类的类型创建对象的过程,称为类的实例化
    1. 类只是 一个 模型 一样的东西,限定了类有哪些成员,定义出一个类 并没有分配实际的内存空间 来存储它。
    2. 一个类可以实例化出多个对象, 实例化出的对象 占用实际的物理空间,存储类成员变量
    3. 做个比方。类实例化出对象就像现实中使用建筑设计图建造出房子,类就像是设计图,只设计出需要什么东西,但是并没有实体的建筑存在,同样类也只是一个设计,实例化出的对象才能实际存储数据,占用物理空间。

    1. class Date
    2. {
    3. public:
    4. void Init(int year, int month, int day)
    5. {
    6. _year = year;
    7. _month = month;
    8. _day = day;
    9. }
    10. void Print()
    11. {
    12. cout << _year << "-" << _month << "-" << _day << endl;
    13. }
    14. private:
    15. int _year;
    16. int _month;
    17. int _day;
    18. };
    19. int main()
    20. {
    21. //将Date类实例化出多个对象
    22. Date d1;
    23. Date d2;
    24. Date d3;
    25. Date d4;
    26. return 0;
    27. }

    七、类对象大小及存储方式

    1. // 类中既有成员变量,又有成员函数
    2. class A1 {
    3. public:
    4. void f1() {}
    5. private:
    6. int _a;
    7. };
    8. // 类中仅有成员函数
    9. class A2 {
    10. public:
    11. void f2() {}
    12. };
    13. // 类中什么都没有---空类
    14. class A3
    15. {};
    16. int main()
    17. {
    18. A1 aa;
    19. A2 bb;
    20. A3 cc;
    21. cout << sizeof(aa) << endl;
    22. cout << sizeof(bb) << endl;
    23. cout << sizeof(cc) << endl;
    24. cout << &bb << endl;
    25. cout << &cc << endl;
    26. }

    结果如下:

     通过上面的例子与运行结果我们可以发现,无论有没有成员函数,并不会影响类的大小,主要影响类的大小的是成员变量,并且空类占1字节的空间,虽然空类中没有内容,但也有相应的地址。

    总结:计算类或者类对象的大小,只看成员变量,考虑内存对其,c++的内存对其规则与c语言一致。

    结构体内存对齐规则
    1. 第一个成员在与结构体偏移量为 0 的地址处。
    2. 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
    3. 结构体总大小为:最大对齐数(所有变量类型最大者与默认对齐参数取最小)的整数倍。
    4. 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。

     八、隐藏的this指针

    我们首先定义一个日期类:

    1. #include
    2. using namespace std;
    3. class Date
    4. {
    5. public:
    6. void Init(int year, int month, int day)
    7. {
    8. _year = year;
    9. _month = month;
    10. _day = day;
    11. }
    12. void Print()
    13. {
    14. cout << _year << "-" << _month << "-" << _day << endl;
    15. }
    16. private:
    17. int _year;
    18. int _month;
    19. int _day;
    20. };
    21. int main()
    22. {
    23. Date d1;
    24. d1.Init(2022, 1, 15);
    25. d1.Print();
    26. Date d2;
    27. d2.Init(2022, 1, 16);
    28. d2.Print();
    29. return 0;
    30. }

     对于上述类,有这样一个问题:

    Date类中有Init和Pint两个成员函数,函数体中没有关于不同对象的区分,那当s1调用Init函数时,该函数是如何知道应该设置s1对象,而不是设置s2对象呢?

    C++ 中通过引入 this 指针解决该问题,即: C++ 编译器给每个 非静态的成员函数 增加了一个隐藏的指针参 数,让该指针指向当前对象 ( 函数运行时调用该函数的对象 ) ,在函数体中所有成员变量的操作,都是通过该 指针去访问。只不过所有的操作对用户是透明的,即用户不需要来传递,编译器自动完成
    如果将隐藏的this指针显现出来就是如下注释部分代码:
    1. #include
    2. using namespace std;
    3. class Date
    4. {
    5. public:
    6. //void Init(Date* this,int year, int month, int day)
    7. void Init(int year,int month,int day)
    8. {
    9. _year = year;
    10. _month = month;
    11. _day = day;
    12. /*this->_year = year;
    13. this->_month = month;
    14. this->_day = day;*/
    15. }
    16. //void Print(Date* this)
    17. void Print()
    18. {
    19. cout << _year << "-" << _month << "-" << _day << endl;
    20. //cout << this->_year << "-" << this->_month << "-" << this->_day << endl;
    21. }
    22. private:
    23. int _year;
    24. int _month;
    25. int _day;
    26. };
    27. int main()
    28. {
    29. Date d1;
    30. d1.Init(2022, 1, 15);//d1.Init(&d1,2022, 1, 15);
    31. d1.Print();//d1.Print(&d1);
    32. Date d2;
    33. d2.Init(2022, 1, 16);//d2.Init(&d2,2022, 1, 15);
    34. d2.Print();//d2.Print(&d2);
    35. return 0;
    36. }

    就用Init函数举例子,看似只有三个参数,其实还有个隐藏的this指针的参数。

    重点:

    1. this指针放在哪里?

    2. 1.一般情况下是在栈中的(形参)

    3. 2.有些编译器会放到寄存器中,如VS2019 ,放到了ecx中

    4. 调用成员函数时,不能显示传实参给this

    5. 定义成原函数时,也不能显示声明形参this

    6. 在成员函数内部,我们可以显示的使用this

    相关例题

    1. class A
    2. {
    3. public:
    4. void PrintA()
    5. {
    6. cout<<_a<
    7. }
    8. void Show()
    9. {
    10. cout<<"Show()"<
    11. }
    12. private:
    13. int _a;
    14. };
    15. int main()
    16. {
    17. A* p = nullptr;
    18. p->PrintA();
    19. p->Show();
    20. }

    解析

    运行p->Show()时运行成功。

    运行p->PrintA()时运行崩溃。

    p->Show():

    1、p虽然是空指针,但是p调用成员函数不会编译报错,因为空指针不是语法错误,编译器检查不出来。

    2、p虽然是空指针,但是p调用成员函数也不会出现空指针访问,因为成员函数没有存在对象里。

    3、这里会把p作为实参传递给隐藏的this指针。(运行崩溃是由于在PrintA成员函数里面this->_a 对空指针进行了解引用)。

  • 相关阅读:
    4、paxos协议
    MongoDB中ObjectId获取时间
    带头双向循环链表
    速卖通卖家如何抓住产品搜索权重
    Leetcode 位运算
    运维面临挑战?智能运维管理系统来帮您
    <MySQL> 如何合理的设计数据库中的表?数据表设计的三种关系
    Linux服务器上搭建JupyterNotebook教程
    Codeforces Round 901 (Div. 1) B. Jellyfish and Math(思维题/bfs)
    【机器学习】包裹式特征选择之基于遗传算法的特征选择
  • 原文地址:https://blog.csdn.net/m0_57249790/article/details/126377003