当前位置: 首页 > news >正文

C++类和对象(一)

希望文章能对你有所帮助,有不足的地方请在评论区留言指正,一起交流学习!

目录

1.面向对象的引入

2.类和对象定义

3.类的访问限定符及封装

3.1.访问限定符号

3.2封装

4.类的作用域

5.类的实例化

6.类对象模型

6.1.类对象的大小

7.this指针

1.面向对象的引入

        C语言是面向过程的,关注的是过程,分析出求解问题的步骤,通过函数调用逐步解决问题。
        C++是基于面向对象的,关注的是对象,将一件事情拆分成不同的对象,靠对象之间的交互完成。交互也是要使用到函数的,算是一个过程,但是首先关注的是对象。
语言的进化方向:面向过程到面向对象。

2.类和对象定义

(1)类的定义

        类是创建对象的蓝图或模板,它定义了一组属性(变量)和方法(函数)的集合。类本身不产生实际数据,而是作为一种数据类型的抽象定义。在C++中结构体升级为类,其中可以包含了变量和函数;可以当作数类型来创建变量。

  (2)对象的定义

        对象是类的实例,通过类创建。每个对象都有自己独立的属性值,但共享类定义的方法。

  对象就是通过类实例化的过程;每个对象都有独特的实例,具有唯一性

举个例子:在C语言中可以使用结构体将一组不同类型的数据封装在一起;在C++中struc也可以用来封装函数。

typedef int DataType;struct Stack //或者使用 class Stack
{//成员函数void Init(size_t capacity){_array = (DataType*)malloc(sizeof(DataType) * capacity);if (nullptr == _array){perror("malloc fail");return;}_capacity = capacity;_size = 0;}void Push(const DataType& data){// 扩容_array[_size] = data;++_size;}DataType Top(){return _array[_size - 1];}void Destroy(){if (_array){free(_array);_array = nullptr;_capacity = 0;_size = 0;}}//成员变量DataType* _array;size_t _capacity;size_t _size;
};

        上述代码就是定义了栈的类,然后我门使用类创建实际的一个栈,就是对象的创建。以及函数的调用和结构体调用变量相同,使用 . 符号调用 指针情况使用->符号调用。

Stack s;    //创建对象
s.Init(10); //对对象进行操作

        C++中,花括号定义的也是一个域 ,在不同的域中函数名字可以是相同的;每个类属于不同的域,类域是一个整体。在C语言中, 由于没有类域的区分,只能在初始化前面需要加上 Que或者Stack来做区分,但是 C++的类域中是不用的。

        注意的是类域是一个整体,成员变量和成员函数的顺序是没有关系的,

(3)使用class定义类

class className
{
// 类体:由成员函数和成员变量组成
};  // 一定要注意后面的分号
        class为定义类的关键字,ClassName为类的名字,{}中为类的主体,注意类定义结束时后面号不能省略。 类体中内容称为类的成员:类中的变量称为类的属性成员变量; 类中的函数称为类的方法或者成员函数。
类有两种定义方式
1)将声明和定义全部放在类体中
就像上述使用struct创建的栈的类一样;这种情况下编译器可能会将一些函数当成
联函数处理。内联函数的特性:定义和声明是不可以分开的。
class Stack
2)类声明放在.h文件中,成员函数定义放在.cpp文件中
这种情况函数的定义,需要在函数名前加上类的名字加以区分,防止命名的冲突。
// class.h 中的程序
#include <iostream>
using namespace std;
typedef int DataType;
class Stack
{
private:DataType* _array = nullptr;//空指针使用nullptrsize_t _size ;// size_t _capacity = 0;
public:// 初始化 给栈区的容量void Init(const DataType& capacity);// 常数的引用// 插入数据void Push(const DataType& data);// 出栈void  Pop();// 栈顶元素DataType Top();// 销毁 栈void Destory();// 判断栈区是否为空bool Empty();};
// class.cpp 中的程序
// 初始化 给栈区的容量
void Stack::Init(const DataType& capacity)// 常数的引用
{_array = (DataType*)malloc(sizeof(DataType)*capacity);if (_array == nullptr){perror("malloc fail");}_capacity = capacity;_size = 0;
}
// 插入数据
void Stack::Push(const DataType& data)
{// 扩容if (_size >= _capacity){_array = (DataType*)realloc(_array, sizeof(DataType)*_capacity * 2);_capacity = _capacity * 2;}if (_array == nullptr){perror("realloc fail");}_array[_size] = data;_size++;
}
// 出栈
void  Stack::Pop()
{_size--;
}// 栈顶元素
DataType Stack::Top()
{return _array[_size - 1];
}
// 销毁 栈
void Stack::Destory()
{free(_array);_array = nullptr;_size = 0;_capacity = 0;
}
// 判断栈区是否为空
bool Stack::Empty()
{if (_size == 0)return true;elsereturn false;}

        总结,类中的成员函数一般需要在其他文件定义,避免定义为内联函数。其定义形式在函数名前加上 类的名字;类也是一种类域。

        如果内联函数定义在类中,可能会存在链接不上的问题,内联函数在编译过程中不会进入符号。

        类域是一个整体,成员变量和成员函数的顺序是没有关系的,

        struct和class的区别

        权限问题, struct 默认是共有的,class默认是私有的,不建议使用默认属性,一定要显示定义类。

3.类的访问限定符及封装

3.1.访问限定符号
        通过访问权限选择性的将其接口提供给外部的用户使用;
  • public(公有) 其修饰的成员在类外可以直接访问;
  • protected(保护)
  • private(私有).protected和private修饰的成员在类外不能直接被访问,二者类似。
注意
        访问限定的作用域从该访问限定符出现的位置开始直到下一个访问限定符出现时为止;如果后面没有访问限定符,作用域就到 } 即类结束。
3.2封装
        面向对象的三大特性:封装、继承、多态
        封装:将数据和操作数据的方法进行有机结合,隐藏对象的属性和实现细节,仅对外公开接口来和对象进行交互。
        封装本质上是一种管理,让用户更方便使用类。比如:对于电脑这样一个复杂的设备,提供给用 户的就只有开关机键、通过键盘输入,显示器,USB插孔等,让用户和计算机进行交互,完成日常事务。但实际上电脑真正工作的却是CPU、显卡、内存等一些硬件元件。
        对于计算机使用者而言,不用关心内部核心部件,比如主板上线路是如何布局的,CPU内部是如 何设计的等,用户只需要知道,怎么开机、怎么通过键盘和鼠标与计算机进行交互即可。因此计 算机厂商在出厂时,在外部套上壳子,将内部实现细节隐藏起来,仅仅对外提供开关机、鼠标以 及键盘插孔等,让用户可以与计算机进行交互即可。
        在C++语言中实现封装,可以通过类将数据以及操作数据的方法进行有机结合,通过访问权限来隐藏对象内部实现细节,控制哪些方法可以在类外部直接被使用。

4.类的作用域

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

class person
{
private:int age;char name[20];
public:// 输出名字void Push_name();void Pop_name(){cout << name << endl;}
};
//这里需要指定Push_nameo是属于person这个类域
// 输出名字
void person::Push_name()
{cin >> name;
}

5.类的实例化

        使用创建的类来创建对象的过程,就是类的实例化

Stack pa;

        Stack为类创建的类型;pa为类的类型创建的对象。

注意

1..类是对对象进行描述的定义类并没 有分配实际的内存空间来存储它;

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

3.类实例化出对象就像现实中使用建筑设计图建造出房子,类就像是设计图。

class  Stack
{
public:void Init(){}int top;int* _array;int capacity;};
int main()
{Stack s1;Stack s2;s2.top = 2;// 下述方式不可以,在类中只是声明,没有定义开辟空间,因此不可以直接使用Stack::top = 0;return 0;
}

        静态变量是可以的,因为静态变量在声明的时候已经创建了内存地址,在全局数据区(静态区)。

6.类对象模型

6.1.类对象的大小

        在计算对象大小的时候,仅需要计算成员变量的大小即可;因为成员变量是每一个对象所具有的,存在着不同;而成员函数是在公共区域的,调用会前往公共区域调用。

        使用上述栈的例子;计算创建对象的大小;其大小是为12 字节;遵循结构体中的内存对齐原则。

class  Stack
{
public:
// 所有对象公有的void Init(){cout << "Init Success "<< endl;}void Push(int& data){cout << "Push Success " << endl;}
// 每个对象私有int top;int* _array;int capacity;};
int main()
{Stack st1;Stack st2;Stack st3;Stack st4;cout << sizeof(st1) << endl;cout << sizeof(st2) << endl;return 0;
}

        再有一个例子

#include <iostream>
using namespace std;class A
{
public:void PrintA(){cout << _a << endl;}char _a;
};int main()
{A aa1;A aa2;A aa3;cout << sizeof(aa1) << endl;cout << sizeof(A) << endl;aa1._a = 0;//直接可以在对象中找到aa1.PrintA();//需要前往公共代码区虚招调用函数}

        使用sizeof计算类和对象的大小是一样的;类是蓝图,对象是房子,可以计算出大小。

// 类中仅有成员函数
class A1 {
public:void f1() {}
};
// 类中什么都没有---空类
class A2
{};
int main()
{cout << sizeof(A1) << endl;cout << sizeof(A2) << endl;A1 a1;A2 a2;cout << &a1 << endl;cout << &a2 << endl;return 0;
}

        空类或者没有成员函数的类也是会占用一个字节的,为了占位,表示对象存在,不存储有效的数据;便于创建对象的时候开辟内存空间,可以查询对象的地址。

7.this指针

       (1) 问题的引入,请看下述的代码

#include <iostream>
using namespace std;class Date
{
public:void Init(int year, int month, int day){_year = year;_month = month;_day = day;}void Print(){cout << _year << "-" << _month << "-" << _day << endl;}
private:int _year;int _month;int _day;
};int main()
{Date d1, d2;d1.Init(2025, 7, 13);d2.Init(2025, 7, 14);d1.Print();d2.Print();return 0;
}
        Date类中有 Init Print 两个成员函数,函数体中没有关于不同对象的区分,那当d1调用 Init 函数时,该函数是如何知道应该设置d1对象,而不是设置d2对象呢?

上述代码中的两次调用Print()函数;调用的是同样的函数为什么输出的结果是不一样的。

        在编译过程中生成的call指针调用函数的地址也是一样的。我们直接看二者的反汇编

        上述两行代码的反汇编,在调用函数之前会将对象的地址传到ecx中,方便传递参数,起始已经传递了对象的指针,但是在程序中是没有显示也没有写的。我们称这样的指针为this指针。

        C++编译器给每个非静态的成员函数增加了一个隐藏的指针参数,让该指针指向当前对象(函数运行时调用该函数的对象),在函数体中所有成员变量的操作,都是通过该指针去访问。只不过所有的操作对用户是透明的,即用户不需要来传递,编译器自动完成。
        非静态的成员函数的理解:
    目前仅仅需要知道,静态的成员函数的属于类的本身,不属于任何对象的实例化;
它们不与特定的对象关联,因此不需要this指针来指向某个对象。
总结:就是在调用处于公共区代码中函数的时候,会传递一个隐藏的this指针,这个指针就是对象的地址。

        其真实的情况类似于上述的情况,存在着隐含指针this,真正调用函数的形式类似于下述的代码。

void Print(){cout << _year << "-" << _month << "-" << _day << endl;}void Print(Data* this){cout << this->_year << "-" << this->_month << "-" << this->_day << endl;}
// 这里的this 指针就是 上面代码中的传递的对象的地址。

(2)this指针的特性

        this在c++中也是一个关键字

  1.  规定this指针不能在形参和实参中显示,但是可以在函数内部显示使用。
void Print(){cout << this << endl;cout << _year << "-" << _month << "-" << _day << endl;}

运行结果如下

2.this指针是无法被修改的,其指针类型是指针常量,指针无法被修改,指向的内容是可以被修改的。

// 上述例子中对应的this指针类型
Data* cons this

    总结

    1.  this指针的类型:类型* const,即成员函数中,不能给this指针赋值。即 指针常量。
    2. 只能在成员函数的内部使用
    3. this指针本质上是成员函数的形参,当对象调用成员函数时,将对象地址作为实参传递给this形参。所以对象中不存储this指针;所以this指针和普通的参数一样存储在栈区中,一旦栈帧销毁,this指针也会销毁。
    4.  this指针是成员函数第一个隐含的指针形参,一般情况由编译器通过ecx寄存器自动传递,不需要用户传递; 
    5. this指针的传递,将对象的地址放在ecx中,使用ecx存储this指针的值。

    问题 :

    1. this指针存在哪里?
    2. this指针可以为空吗?
    1.this指针存储在栈区
    2.this可以为空,但是在成员函数内部能再调用this指针。
    // 1.下面程序编译运行结果是? A、编译报错 B、运行崩溃 C、正常运行
    class A
    {
    public:void Print(){cout << "Print()" << endl;}
    private:int _a;
    };
    int main()
    {A* p = nullptr;p->Print();return 0;
    }
    // 1.下面程序编译运行结果是? A、编译报错 B、运行崩溃 C、正常运行
    class A
    {
    public:void PrintA(){cout << _a << endl;}
    private:int _a;
    };
    int main()
    {A* p = nullptr;p->PrintA();return 0;
    }

            程序1 运行正常,程序2运行运行崩溃。

            原因是程序2对p 空指针进行了解引用的操作;因此程序崩溃。在调用函数PrintA的时候,只需要前往公共区代码区调用即可,没有p的解引用,在这里的this指针就是 p 空指针。当然使用 A::Print()来调用成员函数是不可行的,因为没有this指针,而且A也没有开辟空间,没有对应的地址。

    http://www.dtcms.com/a/276898.html

    相关文章:

  1. Intel英特尔ICH7R/ICH8R/ICH9R/ICH10R系列下载地址--intel_msm_8961002 下载 Version 8.9.6.1002
  2. 001_Claude开发者指南介绍
  3. UNet改进(22):融合CNN与Transformer的医学图像分割新架构
  4. MaxCompute过程中常见的数据倾斜场景以及对应的解决方案
  5. std::sort的核心设计思想
  6. C++:宏
  7. python暑假课第三次作业
  8. 从爆红到跑路:AI明星Manus为何仅用四个月就“抛弃”了中国?
  9. 详解缓存淘汰策略:LFU
  10. macOS - Chrome 关闭自动更新
  11. 12.1 MMU配置与管理
  12. 人工智能之数学基础:神经网络的矩阵参数求导
  13. 基于CMMI的软件质量管理体系深度解析
  14. 初级网安作业笔记1
  15. 2025上海市“星光计划“信息安全管理与评估赛项二三阶段任务书
  16. 【leetcode】字符串,链表的进位加法与乘法
  17. 贝叶斯状态空间神经网络:融合概率推理和状态空间实现高精度预测和可解释性
  18. 新手向:使用Python构建高效的日志处理系统
  19. Linux系统之iprdbg 命令详解
  20. 12.4 内存隔离与保护
  21. 《Llama: The Llama 3 Herd of Models》预训练数据篇——论文精读笔记
  22. Linux | 数据库操作基础
  23. EVO-0:具有隐空间理解的视觉-语言-动作模型
  24. 维基艺术图片: 构建模型 (3)
  25. 应用层协议和JSON的使用
  26. 文心大模型4.5开源测评:轻量化部署实践与多维度能力验证
  27. 贝尔量子实验设想漏洞
  28. 云服务器的基础使用
  29. [Dify]-基础入门8- 使用 Dify 创建文档问答机器人(零代码实现)
  30. 39.Sentinel微服务流量控制组件