• 【C++】类和对象(上)


    目录

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

    二,类的引入

    三,类的定义

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

    1,访问限定符

    2,封装

    五,类的作用域

    六,类的实例化

    七,类对象模型

    1,如何计算类对象的大小

    2,类对象的存储方式

    3,结构体内存对齐规则

    八,this 指针

    1,this 指针的引出

    2,this 指针的特性


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

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

    就比如洗衣服,c语言就是:

    主要的是面向过程;

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

    C++ 主要是面向对象,不需要关心那么多的细节;

    二,类的引入

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

    比如: 之前在数据结构初阶中,用 C语言 方式实现的栈,结构体中只能定义变量;

    现在以 C++ 方式实现, 会发现 struct 中也可以定义函数

    1. #include
    2. using namespace std;
    3. typedef int DataType;
    4. struct Stack
    5. {
    6. void Init(size_t capacity)
    7. {
    8. _array = (DataType*)malloc(sizeof(DataType) * capacity);
    9. if (nullptr == _array)
    10. {
    11. perror("malloc申请空间失败");
    12. return;
    13. }
    14. _capacity = capacity;
    15. _size = 0;
    16. }
    17. void Push(const DataType& data)
    18. {
    19. // 扩容
    20. _array[_size] = data;
    21. ++_size;
    22. }
    23. DataType Top()
    24. {
    25. return _array[_size - 1];
    26. }
    27. void Destroy()
    28. {
    29. if (_array)
    30. {
    31. free(_array);
    32. _array = nullptr;
    33. _capacity = 0;
    34. _size = 0;
    35. }
    36. }
    37. DataType* _array;
    38. size_t _capacity;
    39. size_t _size;
    40. };
    41. int main()
    42. {
    43. Stack s;
    44. s.Init(10);
    45. s.Push(1);
    46. s.Push(2);
    47. s.Push(3);
    48. cout << s.Top() << endl;
    49. s.Destroy();
    50. return 0;
    51. }

    上面结构体的定义,在 C++ 中更喜欢用 class 来代替;

    三,类的定义

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

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

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

    类的两种定义方式:

    1,声明和定义全部放在类体中,需注意:成员函数如果在类中定义,编译器可能会将其当成内联函数处理。

    1. //人
    2. class Person
    3. {
    4. public:
    5. //显示基本信息
    6. void showInfo()
    7. {
    8. cout << _name << "-" << _sex << "-" << _age << endl;
    9. }
    10. public:
    11. char* _name; //姓名
    12. char* _sex; //性别
    13. int _age; //年龄
    14. };

    2,类声明放在 .h 文件中,成员函数定义放在 .cpp 文件中,注意:成员函数名前需要加类名::

    1. Person.h
    2. //人
    3. class Person
    4. {
    5. public:
    6. //显示基本信息
    7. void showInfo();
    8. public:
    9. char* _name; //姓名
    10. char* _sex; //性别
    11. int _age; //年龄
    12. };
    13. Person.cpp
    14. //显示基本信息,实现:输出 名字,性别,年龄
    15. void Person::showInfo()
    16. {
    17. cout << _name << "-" << _sex << "-" << _age << endl;
    18. }

    一般情况下,更期望采用第二种方式

    注意:上课为了方便演示使用方式一定义类,大家后序工作中尽量使用第二种。

    成员变量命名规则的建议:

    1. // 我们看看这个函数,是不是很僵硬?
    2. class Date
    3. {
    4. public:
    5. void Init(int year)
    6. {
    7. // 这里的year到底是成员变量,还是函数形参?
    8. year = year;
    9. }
    10. private:
    11. string year;
    12. };
    13. int main()
    14. {
    15. return 0;
    16. }

    那上面的 year 都是代表函数新参的;

    所以我们优化一下,一般都是这样写的:

    1. class Date
    2. {
    3. public:
    4. void Init(int year)
    5. {
    6. _year = year;
    7. }
    8. private:
    9. int _year;
    10. };

    或者:

    1. class Date
    2. {
    3. public:
    4. void Init(int year)
    5. {
    6. mYear = year;
    7. }
    8. private:
    9. int mYear;
    10. };
    11. // 其他方式也可以的,主要看公司要求。一般都是加个前缀或者后缀标识区分就行。

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

    1,访问限定符

    C++ 实现封装的方式:用类将对象的属性与方法结合在一块,让对象更加完善,通过访问权限选择性的将其接口提供给外部的用户使用。

    【访问限定符说明】

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

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

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

    4,如果后面没有访问限定符,作用域就到 } 即类结束

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

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

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

    解答:C++ 需要兼容 C语言,所以C++中struct可以当成结构体使用;

    另外C++中struct还可以用来定义类。和class定义类是一样的,区别是struct定义的类默认访问权限是public,class定义的 默认访问权限是private;

    注意:在继承和模板参数列表位置,struct和class也有区别,后序给大家介绍。

    2,封装

    在类和对象阶段,主要是研究类的封装特性,那什么是封装呢?

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

    封装本质上是一种管理,让用户更方便使用类。

    比如:对于电脑这样一个复杂的设备,提供给用户的就只有开关机键、通过键盘输入,显示器,USB插孔等,让用户和计算机进行交互,完成日常事务;

    但实际上电脑真正工作的却是CPU、显卡、内存等一些硬件元件;

    对于计算机使用者而言,不用关心内部核心部件,比如主板上线路是如何布局的,CPU内部是如 何设计的等,用户只需要知道,怎么开机、怎么通过键盘和鼠标与计算机进行交互即可。

    因此计算机厂商在出厂时,在外部套上壳子,将内部实现细节隐藏起来,仅仅对外提供开关机、鼠标以及键盘插孔等,让用户可以与计算机进行交互即可;

    C++ 语言中实现封装,可以通过类将数据以及操作数据的方法进行有机结合,通过访问权限来 隐藏对象内部实现细节,控制哪些方法可以在类外部直接被使用

    五,类的作用域

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

    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 << endl;
    14. }

    要指定域才行,如果有两个类里面有一个函数名称相同的函数,那就判断不了调用哪个类里面的函数了。

    六,类的实例化

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

    1,类是对对象进行描述的,是一个模型一样的东西,限定了类有哪些成员,定义出一个类并没有分配实际的内存空间来存储它;

    比如:入学时填写的学生信息表,表格就可以看成是一个类,来描述具体学生信息;

    类就像谜语一样,对谜底来进行描述,谜底就是谜语的一个实例。 谜语:"年纪不大,胡子一把,主人来了,就喊妈妈" 谜底:山羊

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

    1. class Person
    2. {
    3. public:
    4. //显示基本信息
    5. void showInfo();
    6. public:
    7. char* _name; //姓名
    8. char* _sex; //性别
    9. int _age; //年龄
    10. };
    11. int main()
    12. {
    13. Person._age = 100; // 编译失败:error C2059: 语法错误:“.”
    14. return 0;
    15. }

    Person 类是没有空间的,只有 Person 类实例化出的对象才有具体的年龄 

    这边的类里面的变量,对象都只是声明,并没有开辟内存地址;

    所以给声明赋值是会报错的;

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

    类就像是设计图,我们不能在设计图来上洗手间,一定要实例化之后建造出了房子才可以用洗手间;

    要这样先向内存申请空间定义类才行;

    七,类对象模型

    1,如何计算类对象的大小

    类中既可以有成员变量,又可以有成员函数,那么一个类的对象中包含了什么?如何计算 一个类的大小?

    1. class A
    2. {
    3. public:
    4. void PrintA()
    5. {
    6. cout << _a << endl;
    7. }
    8. private:
    9. char _a;
    10. };
    11. int main()
    12. {
    13. cout << sizeof(A) << endl;
    14. return 0;
    15. }

    2,类对象的存储方式

    只保存成员变量,成员函数存放在公共的代码段

    我们再通过对下面的不同对象分别获取大小来分析看下 

    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. cout << sizeof(A1) << endl << sizeof(A2) << endl << sizeof(A3) << endl;;
    19. return 0;
    20. }

    结论:

    一个类的大小,实际就是该类中”成员变量”之和,当然要注意内存对齐注意空类的大小,空类比较特殊,编译器给了空类一个字节来唯一标识这个类的对象。

    3,结构体内存对齐规则

    1,第一个成员在与结构体偏移量为 0 的地址处。

    2,其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。

         注意:对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值。

         VS中默认的对齐数为8

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

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

    八,this 指针

    1,this 指针的引出

    我们先来定义一个日期类 Date

    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 d1, d2;
    22. d1.Init(2022, 1, 11);
    23. d2.Init(2023, 1, 12);
    24. d1.Print();
    25. d2.Print();
    26. return 0;
    27. }

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

    C++ 中通过引入 this 指针解决该问题,即:

    C++ 编译器给每个“非静态的成员函数“增加了一个隐藏的指针参数,让该指针指向当前对象(函数运行时调用该函数的对象),在函数体中所有“成员变量” 的操作,都是通过该指针去访问。只不过所有的操作对用户是透明的,即用户不需要来传递,编译器自动完成。

    2,this 指针的特性

    1,this 指针的类型:类类型* const,即成员函数中,不能给this指针赋值

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

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

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

    会被编译器处理成这个样子; 

    只是我们看不到而已,实际上都是编译器在替我们负重前行;

    C++ 中通过类可以将数据以及操作数据的方法进行完美结合,通过访问权限可以控制那些方法在类外可以被调用,即封装,在使用时就像使用自己的成员一样,更符合人类对一件事物的认知。 

    而且每个方法不需要传递 Stack* 的参数了,编译器编译之后该参数会自动还原,即 C++ 中 Stack * 参数是编译器维护的,C语言中需用用户自己维护。

  • 相关阅读:
    如何绕过api的防重放做安全测试
    博客整理 vim编译器
    高速,低延,任意频丨庚顿新一代实时数据库鼎力支撑电力装备服务数字化
    基于Java+SpringBoot+Vue+echarts校园资料分享平台设计和实现
    【力扣刷题练习】93. 复原 IP 地址
    SD00HA 80mΩ,可调快速响应限流功率开关芯片IC
    一、MySQL-Replication(主从复制)
    牛客网C语言刷题(指针篇)
    Matlab基础内容学习大纲
    C#结合OpenCVSharp4图片相似度识别
  • 原文地址:https://blog.csdn.net/m0_71676870/article/details/133956924