C++基础(五):类和对象(上)

      从今天开始,我们正式进入面向对象编程,这是C++与C语言的重要区别,编程思想发生变化,那到底什么是面向对象编程呢?接下来,我们慢慢的深入学习。

目录

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

1.1 面向过程

1.2 面向对象

1.2.1 面向对象的概念

1.2.2  对象的主要属性

二、类的引入

三、类的定义

3.1 实际开发定义类的方式

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

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

4.1 访问限定符

4.2 封装

4.3 举例理解

五、类的作用域

六、类的实例化

6.1 如何理解

6.1.1 概念角度     

6.1.2 内存角度

6.1.3 通俗解释

6.2 对象的使用

七、C++对象模型

7.1 如何计算类对象的大小

7.2 类对象的存储方式猜测

7.2.1 方案一:各对象完全独立地安排内存的方案

7.2.2 方案二:各对象的代码区共用的方案​编辑

7.3 总结C++中对象的存储方式

7.4 实验

7.5 结构体内存对齐规则(复习)

八、this指针

8.1 this指针的引出

8.2 this指针的特性(非常重要)

8.3. C语言和C++实现Stack的对比


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

1.1 面向过程

        C语言是面向过程的,关注的是过程,分析出求解问题的步骤,通过函数调用逐步解决问题。但是,当软件规模超过一定的尺度后,采用结构化程序设计,其开发和维护就越来越难控制。其根本的原因就在于面向过程的结构化程序设计的方法与现实世界(包括主观世界和客观世界)往往都不一致,结构化程序设计的思想往往很难贯彻到底。

        在结构化程序设计中,采用的是"自顶向下,逐步细化的思想。具体操作方法是模块化,是按功能来分的,所以也称功能块(函数)。在C++中称为一个函数, 一个函数解决一个问题, 即实现一个功能或一个操作。在模块化的思想中已经出现了封装的概念,这个封装是把数据封装到模块中,即局部变量。但这是很不彻底的,因为模块是功能的抽象,而数据则是具有其个性的, 一旦发生一点变化, 抽象的功能模块就不再适用了。可维护性差成了制约结构化程序设计应用的瓶颈。

1.2 面向对象

1.2.1 面向对象的概念

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

       对象的概念是面向对象技术的核心所在。面向对象中的对象就是现实世界中,某个具体的物理实体在计算机世界(逻辑)中的映射和体现。也就说计算机中的对象,是模拟现实世界中的实体从现实世界到计算机的世界映射。

总结:

  1. 类是一组相关的属性(成员变量)和行为(成员函数)的集合。是由一个抽象概念设计的产物,它也代表一种自定义数据类型,创建类其实就是自定义类型的声明。
  2. 对象是由一个类实例化后的具体存在的实体,就是用类来定义一个对象,其实就是定义我们自己创建的类的一个变量,它具有类里面的成员变量和成员函数
  3.  成员变量是对象的属性(可以是变量, 指针,数组等),属性的值确定对象的状态。
  4.  成员函数是对象的方法,确定对象的行为。

1.2.2  对象的主要属性

        对象是由类实例化得来,类就是我们自己设计的自定义的数据类型,它包括成员变量和成员函数,因此,对象的主要属性就是状态和行为。

  1. 对象的状态主要指对象内部所包含的各种信息,也就是变量。每个对象个体都有自己专有的内部变量, 这些变量的值标明了对象所处的状态。
  2. 对象的行为主要指对象内部的成员函数一方面把对象的内部变量包裹,封装,保护起来,使得只有对象自己的方法才能操作对象内部变量,另一方面,对象的方法还是对象与外部环境和其他对象交互, 通信的接口,其他对象或外部环境可以通过这个接口来调用对象的方法,操纵对象的行为和改变对象的状态。

       面向对象程序设计是将数据及数据的操作封装在一起,成为一个不可分割的整体,同时将具有相同特性的实体抽象成为一种新的数据类型--->类。通过对象间的消息传递是整个系统运转。

二、类的引入

      C语言结构体中只能定义变量,在C++中,结构体内不仅可以定义变量,也可以定义函数。比如: 之前在数据结构初阶中,用C语言方式实现的栈,结构体中只能定义变量;现在以C++方式实现, 会发现struct中也可以定义函数。

#include<iostream>
using namespace std; 

这里是C++利用struct定义结构体,这里的结构体包括两部分:函数和结构体变量

typedef int DataType;
struct Stack
{
    //1、初始化函数
    void Init(size_t capacity)   
   {
      _array = (DataType*)malloc(sizeof(DataType) * capacity);
      if (nullptr == _array)
      {
         cout<<"malloc申请空间失败"<<endl;
         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;
};


int main()
{
    Stack s;     //实例化对象
    s.Init(10);
    s.Push(1);
    s.Push(2);
    s.Push(3);
    cout << s.Top() << endl;

    s.Destroy();
    return 0;
}

上面结构体的定义,在C++中更喜欢用class来代替,于是C++在C的基础上,便产生了类。

三、类的定义

       类的定义方式如下,可以看到,它的定义方式和结构体定义非常像,把typedef关键字换成了class,并且加入了成员函数。

class className
{
     // 类体:由成员函数和成员变量组成

};  // 一定要注意后面的分号
  1. class为定义类的关键字,ClassName为类的名字,{ }中为类的主体,注意类定义结束时后面分号不能省略。
  2. 类体中内容称为类的成员:类中的变量称为类的属性或成员变量;
  3. 类中的函数称为类的方法或者成员函数

3.1 实际开发定义类的方式

        类的两种定义方式有两种,

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

2. 类声明放在.h文件中,成员函数定义放在.cpp文件中,注意:成员函数名前需要加类名::,这叫做类作用域符。

一般情况下,实际项目开发采用第二种方式,我们现在学习阶段可以采用第一种方式。

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

// 我们看看这个函数,是不是很僵硬?
class Date
{
  public:
     void Init(int year)
    {
        // 这里的year到底是成员变量,还是函数形参?
        year = year;
     }
 private:
     int year;

};

// 所以一般都建议这样
class Date
{
   public:
       void Init(int year)
      {
         _year = year;
      }
  private:
      int _year;

};

// 或者这样
class Date
{
   public:
      void Init(int year)
      {
       mYear = year;
      }

  private:
     int mYear;

};

其他方式也可以的,主要看公司要求。一般都是加个前缀或者后缀标识区分就行。

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

4.1 访问限定符

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

【访问限定符说明】

  1. public修饰的成员在类外可以直接被访问
  2. protected和private修饰的成员在类外不能直接被访问(此处protected和private是类似的)
  3. 访问权限作用域从该访问限定符出现的位置开始直到下一个访问限定符出现时为止,如果后面没有访问限定符,作用域就到 } 即类结束。
  4. class的默认访问权限为private,struct为public(因为struct要兼容C)

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

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

        解答:C++需要兼容C语言,所以C++中struct可以当成结构体使用。另外C++中struct还可以用来定义类(C++中struct既可以用来创建结构体,也可以用来创建类)。和class定义类是一样的,区别是struct定义的类默认访问权限是public,class定义的类默认访问权限是private。注意:在继承和模板参数列表位置,struct和class也有区别,后序给大家介绍。

【面试题】 面向对象的三大特性:封装、继承、多态。

4.2 封装

        在类和对象阶段,主要是研究类的封装特性,那什么是封装呢? 封装:将数据和操作数据的方法进行有机结合,隐藏对象的属性和实现细节,仅对外公开接口来和对象进行交互。封装本质上是一种管理,让用户更方便使用类。比如:对于电脑这样一个复杂的设备,提供给用户的就只有开关机键、通过键盘输入,显示器,USB插孔等,让用户和计算机进行交互,完成日常事务。但实际上电脑真正工作的却是CPU、显卡、内存等一些硬件元件。

       对于计算机使用者而言,不用关心内部核心部件,比如主板上线路是如何布局的,CPU内部是如 何设计的等,用户只需要知道,怎么开机、怎么通过键盘和鼠标与计算机进行交互即可。因此计 算机厂商在出厂时,在外部套上壳子,将内部实现细节隐藏起来,仅仅对外提供开关机、鼠标以 及键盘插孔等,让用户可以与计算机进行交互即可。

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

4.3 举例理解

#include <iostream>
using namespace std;

//创建学生类
class Student
{

public:
	void ShowInfo()
	{
		cout << _name << endl;
		cout << _age << endl;
		cout << _id << endl;
	}

	int GetAge()
	{
		return _age;
	}

private:   //一般情况下,成员变量都是比较隐私的,都会定义成私有或者保护

	char _name[20];        //私有的,类外都不能访问!但类内可以直接访问
	int _age[10];         //私有的,类外都不能访问!但类内可以直接访问
	int _id[20];         //私有的,类外都不能访问! 但类内可以直接访问
};

我们知道:在类中定义的私有的成员变量,我们实例化出对象后,是不可以直接访问的!那如果要访问,该怎么解决呢???

 注意:    

  1.  类是一种数据类型, 设计时系统不为类分配存储空间,所以不能对类的数据成员初始化。类中的任何数据成员也不能使用关键字extern、auto或register限定其存储类型。
  2. 类中的成员变量被设计成私有的,这样的话,数据就受到了良好的保护,不易受外部环境的影响。但是这样如果类外想要访问类实例化出的对象中的私有成员变量,这是不可以的,此时,就必须要过公有的成员函数来访问/修改这个私有的成员变量;这就是公有成员函数的作用!!!公有函数集定义了类的接口方便外界对对象中的数据进行访问或者修改!

五、类的作用域

        成员函数可以直接使用类定义中的任一-成员, 可以处理数据成员,也可调用函数成员。我们可以在类中只对成员函数作一个声明(函数的原型),然后将成员函数的具体实现写在类的外面,它和我们实现普通的函数一样,只不过要加类的作用域解析运算符 : :,它指出该函数是属于哪一个
类的成员函数。

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

//创建类
class Person
{
   public:
      void PrintPersonInfo();
   private:
     char _name[20];
     char _gender[3];
     int  _age;
};

// 这里需要指定PrintPersonInfo是属于Person这个类域

void Person::PrintPersonInfo()
{
     cout << _name << " "<< _gender << " " << _age << endl;
}

六、类的实例化

        对象是类的实例。声明一种数据类型只是告诉编译系统该数据类型的构造,并没有
预定内存。类只是一个样板(图纸),以此样板可以在内存中开辟出同样结构的实例对象。用类类型创建对象的过程,称为类的实例化。

6.1 如何理解

6.1.1 概念角度     

类是对对象进行描述的,是一个模型一样的东西,限定了类有哪些成员,定义出一个类并没有分配实际的内存空间来存储它;比如:入学时填写的学生信息表,表格就可以看成是一个类,来描述具体学生信息。 类就像谜语一样,对谜底来进行描述,谜底就是谜语的一个实例。 谜语:"年纪不大,胡子一把,主人来了,就喊妈妈" 谜底:山羊

6.1.2 内存角度

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

int main()
{
    Person._age = 100;   // Person是类名,编译失败:error C2059: 语法错误:“.”
    return 0;
}

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

6.1.3 通俗解释

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

6.2 对象的使用

        对象使用的规则很简单,只要在对象名后加点号(点操作符 . ),再加成员变量或成员函数名就可以了。但是这些成员必须是公有的成员,只有公有成员才能在对象的外面对它进行访问。

七、C++对象模型

7.1 如何计算类对象的大小

class A
{
    public:
       void PrintA()
       {
         cout<<_a<<endl;
       }
   private:
       char _a;
};

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

7.2 类对象的存储方式猜测

简单复习:

      首先,我们要明白以下几个概念,这样,我们才会有深刻的认识:

  1. 类是对对象进行描述的,限定了类有哪些成员,定义出一个类并没有分配实际的内存空间来存储它;实例化出的对象才会占用实际的物理内存空间,可以把对象就理解成一个变量。
  2. 由类实例化出的对象,他就是一个变量,它里面存储的成员变量占用的内存区域,取决于对象(变量)是什么类型的变量,具体如下:1)如果对象是函数内的局部变量,则对象以及对象的成员变量保存在栈区。2)如果对象是全局变量或者静态局部变量,则对象以及对象的成员变量保存在数据区。3)如果对象是new出来的(动态开辟的),则对象以及对象的成员变量保存在堆区。
  3.  对象中的成员函数存放在代码区,而不是和对象存储在一起(下面会解释),代码区存放程序的可执行代码,包括函数的机器指令。类的成员函数本质上是一些可执行的指令,它们定义了类的行为。当程序运行时,CPU需要访问这些指令来执行相应的操作。代码区是只读的,防止代码在运行时被意外修改,提高了程序的安全性和稳定性。

7.2.1 方案一:各对象完全独立地安排内存的方案

      上图是系统为每一个对象分配了全套的内存,包括安放成员数据的数据区(这里的数据区指的是栈区、堆区、数据区)和安放成员函数的代码区。但是区别同一个类所实例化的对象,是由属性(数据成员)的值决定,不同对象的数据成员的内容是不一样的; 而行为(操作)是用函数来描述的,这些操作的代码对所有的对象都是一样的。

缺陷:每个对象中成员变量是不同的,但是调用同一份函数,如果按照此种方式存储,当一 个类创建多个对象时,每个对象中都会保存一份代码,相同代码保存多次,浪费空间。

7.2.2 方案二:各对象的代码区共用的方案

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

       如果多个对象共享同一个成员函数的实现,那么该成员函数的代码只需在内存中存放一份,从而节省了内存。每个对象都有自己独立的成员变量存储空间,但它们共享相同的成员函数代码。类的所有对象共享同一个成员函数的代码。这些代码在程序加载时就已经在代码区分配好了内存,所有对象访问同一个成员函数地址,因此,将所有的成员函数都存放在代码区,创建出来的对象都共享这块内存区域(代码区),当我们调用对象的成员函数时,就取代码区对应的地址处调用函数。实际上存放的是函数名/函数指针(函数入口地址)。

7.3 总结C++中对象的存储方式

总结:

        C++中类的成员变量存放在数据区、堆区或栈区,成员变量的内存位置取决于对象的定义的位置,这是因为成员变量存储的是对象的状态信息,需要频繁的读写和修改,而代码区主要用于存储不可变的程序指令。这样设计有助于优化内存利用,提高程序运行效率和安全性。

7.4 实验

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

// 类中既有成员变量,又有成员函数
class A1 
{
   public:
     void f1(){}
   private:
     int _a;
};


// 类中仅有成员函数
class A2 
{
  public:
    void f2() {}
};


// 类中什么都没有---空类
class A3
{

};

sizeof(A1) :4                                      sizeof(A2) : 1                               sizeof(A3) : 1

        没有成员变量的类的对象的大小是1字节,为什么是1?而不是0 ==>开一个字节不是为了存数据,而是为了占位,表示对象存在(可以取地址).

如何计算一个类/对象的大小?

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

7.5 结构体内存对齐规则(复习)

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

【面试题】

1. 结构体怎么对齐? 为什么要进行内存对齐?

2. 如何让结构体按照指定的对齐参数进行对齐?能否按照3、4、5即任意字节对齐?

3. 什么是大小端?如何测试某台机器是大端还是小端,有没有遇到过要考虑大小端的场景

八、this指针

8.1 this指针的引出

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

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(2022,1,11);
    d2.Init(2022, 1, 12);
    d1.Print();
    d2.Print();
    return 0;
}

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

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

编译器底层会将代码修改如下:

8.2 this指针的特性(非常重要)

1. this指针的类型:类类型* const,即成员函数中,不能给this指针赋值(因为有const修饰指针自身);

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

3. this指针本质上是“成员函数”的形参,当对象调用成员函数时,将对象地址作为实参传递给 this形参,所以对象中不存储this指针,因为this指针它就是一个函数形参,因此,它存放在栈区!;

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

【面试题】 1. 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;
}

首先注意:这里的:p->Print()这里不会发生指针崩溃 ! 原因如下:

正确答案是:第一个正常运行,第二个发生指针崩溃!原因如下:

第一个:只是传入的this指针为空,但是没有通过该this空指针解引用访问成员变数据!所以会正常运行!

第二个:传入的this指针为空,并且对this 这个空指针解引用访问了成员变量! 所以会发生指针崩溃!

8.3. C语言和C++实现Stack的对比

C语言实现:

typedef int DataType;

typedef struct Stack
{
   DataType* array;
   int capacity;
   int size;
}Stack;

void StackInit(Stack* ps)
{
   assert(ps);
   ps->array = (DataType*)malloc(sizeof(DataType) * 3);
   if (NULL == ps->array)
  {
     assert(0);
     return;
  }
   ps->capacity = 3;
   ps->size = 0;
}

void StackDestroy(Stack* ps)
{
   assert(ps);
   if (ps->array)
   {
     free(ps->array);
     ps->array = NULL;
     ps->capacity = 0;
     ps->size = 0;
  }
}


void CheckCapacity(Stack* ps)
{
    if (ps->size == ps->capacity)
    {
       int newcapacity = ps->capacity * 2;
       DataType* temp = (DataType*)realloc(ps->array, 
       newcapacity*sizeof(DataType));
       if (temp == NULL)
       {
          perror("realloc申请空间失败!!!");
          return;
       }

       ps->array = temp;
       ps->capacity = newcapacity;
    }
}

void StackPush(Stack* ps, DataType data)
{
    assert(ps);
    CheckCapacity(ps);
    ps->array[ps->size] = data;
   ps->size++;
}

int StackEmpty(Stack* ps)
{
   assert(ps);
   return 0 == ps->size;
}

void StackPop(Stack* ps)
{
   if (StackEmpty(ps))
   return;
   ps->size--;
}


DataType StackTop(Stack* ps)
{
   assert(!StackEmpty(ps));
   return ps->array[ps->size - 1];
}

int StackSize(Stack* ps)
{
   assert(ps);
   return ps->size;
}

int main()
{
    Stack s;
    StackInit(&s);
    StackPush(&s, 1);
    StackPush(&s, 2);
    StackPush(&s, 3);
    StackPush(&s, 4);
    printf("%d\n", StackTop(&s));
    printf("%d\n", StackSize(&s));
    StackPop(&s);
    StackPop(&s);
    printf("%d\n", StackTop(&s));
    printf("%d\n", StackSize(&s));
    StackDestroy(&s);
    return 0;
}

可以看到,在用C语言实现时,Stack相关操作函数有以下共性:

  1. 每个函数的第一个参数都是Stack*
  2. 函数中必须要对第一个参数检测,因为该参数可能会为NULL
  3. 函数中都是通过Stack*参数操作栈的
  4. 调用时必须传递Stack结构体变量的地址

        结构体中只能定义存放数据的结构,操作数据的方法不能放在结构体中,即数据和操作数据 的方式是分离开的,而且实现上相当复杂一点,涉及到大量指针操作,稍不注意可能就会出 错。

C++实现:

typedef int DataType;
class Stack
{
    public:
      void Init()
      {
        _array = (DataType*)malloc(sizeof(DataType) * 3);
       if (NULL == _array)
       {
          perror("malloc申请空间失败!!!");
          return;
       }

        _capacity = 3;
        _size = 0;

     void Push(DataType data)
     {
 
        CheckCapacity();
        _array[_size] = data;
        _size++;
    }


    void Pop()
    {
      if (Empty())
      return;
      _size--;
    }


    DataType Top()
    { 
       return _array[_size - 1];
    }


    int Empty()
    {
        return 0 == _size;
    }


    int Size()
    { 
        return _size;
    }


   void Destroy()
   {
     if (_array)
     {
        free(_array);
        _array = NULL;
        _capacity = 0;
        _size = 0;
     }
   }


private:
      void CheckCapacity()
      {
         if (_size == _capacity)
         {
             int newcapacity = _capacity * 2;
             DataType* temp = (DataType*)realloc(_array, newcapacity*sizeof(DataType));
            if (temp == NULL)
            {
               perror("realloc申请空间失败!!!");
               return;
            }
            _array = temp;
            _capacity = newcapacity;
          }
     }


private:
       DataType* _array;
       int  _capacity;
       int  _size;
};

int main()
{
   Stack s;
   s.Init();
   s.Push(1);
   s.Push(2);
   s.Push(3);
   s.Push(4);
 
   printf("%d\n", s.Top());
   printf("%d\n", s.Size());
   s.Pop();
   s.Pop();
   printf("%d\n", s.Top());
   printf("%d\n", s.Size());
   s.Destroy();
   return 0;
}

       C++中通过类可以将数据以及操作数据的方法进行完美结合,通过访问权限可以控制那些方法在 类外可以被调用,即封装,在使用时就像使用自己的成员一样,更符合人类对一件事物的认知。 而且每个方法不需要传递Stack*的参数了,编译器编译之后该参数会自动还原,即C++中 Stack * 参数是编译器维护的,C语言中需用用户自己维护。

至此,C++面向对象上的全部内容就学习完毕,认真复习消化,熟练使用,C++相对来说较为复杂,我们应该时刻理清自己的思路,耐下心来,一点点积累, 星光不问赶路人,加油吧,感谢阅读,如果对此专栏感兴趣,点赞加关注! 

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

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

相关文章

【Python】变量与基本数据类型

个人主页&#xff1a;【&#x1f60a;个人主页】 系列专栏&#xff1a;【❤️Python】 文章目录 前言变量声明变量变量的命名规则 变量赋值多个变量赋值 标准数据类型变量的使用方式存储和访问数据&#xff1a;参与逻辑运算和数学运算在函数间传递数据构建复杂的数据结构 NameE…

腾讯混元文生图开源模型推出小显存版本,6G显存即可运行,并开源caption模型

7月4日&#xff0c;腾讯混元文生图大模型&#xff08;混元DiT&#xff09;宣布开源小显存版本&#xff0c;仅需6G显存即可运行&#xff0c;对使用个人电脑本地部署的开发者十分友好&#xff0c;该版本与LoRA、ControlNet等插件&#xff0c;都已适配至Diffusers库&#xff1b;并…

达梦数据库 页大小与数据库字段长度的关系

对于达梦数据库实例而言&#xff0c;页大小 (page_size)、簇大小 (extent_size)、大小写敏感 (case_sensitive)、字符集 (charset) 这四个参数&#xff0c;一旦确定无法修改&#xff1b;如果过程中发现这些数据设置的不对&#xff0c;只能是重新新建数据库实例&#xff0c;而不…

脑启发设计:人工智能的进化之路

编者按&#xff1a;你可以用左手&#xff08;不常用的那只手&#xff09;的小指与食指拿起一件物品么&#xff1f; 试完你是不是发现自己竟然可以毫不费力地用自己不常用的手中&#xff0c;两根使用频率相对较低的手指&#xff0c;做一个不常做的动作。这就是人类大脑不可思议…

14-5 小语言模型SLM 百科全书

想象一下这样一个世界&#xff1a;智能助手不再驻留在云端&#xff0c;而是驻留在你的手机上&#xff0c;无缝理解你的需求并以闪电般的速度做出响应。这不是科幻小说&#xff1b;这是小型语言模型 (SLM) 的前景&#xff0c;这是一个快速发展的领域&#xff0c;有可能改变我们与…

台灯学生用哪个牌子最好?学生用台灯品牌排行榜分析

台灯学生用哪个牌子最好&#xff1f;护眼台灯在近年来成为家长和长时间使用电子设备人群关注的家电/学生产品。对于家中有孩子或经常面对电子屏幕的人士来说&#xff0c;很多人可能已经对这类产品有所了解并进行了购买。然而&#xff0c;部分家长对护眼台灯的认识还不够深入&am…

windows安装jdk21

下载 下载zip解压 设置环境变量 设置JAVA_HOME环境变量 Path环境变量添加如下值%HAVA_HOME%\bin 打开新的cmd&#xff0c;输入java --version查看效果

CentralCache中心缓存

目录 一.CentralCache基本结构 1.CentralCache任务 2.基本结构 二.函数调用层次结构/.h文件 三.Span和SpanList的封装 Span:大块内存跨度 PAGE_ID _pageId size_t _objSize _useCount SpanList:管理Span的双链表(桶锁) 四.获取大块内存GetOneSpan 五.FetchRangeObj输…

源代码防泄漏之反向沙箱方案的经验分享

反向沙箱&#xff08;Reverse Sandbox&#xff09;是一种安全技术&#xff0c;主要用于检测和分析恶意软件的行为。与传统沙箱不同&#xff0c;反向沙箱的重点在于模拟恶意软件的预期运行环境&#xff0c;以诱导恶意软件展示其真实行为。这种技术可以帮助安全专家更深入地理解恶…

四川蔚澜时代电子商务有限公司打造抖音电商服务新高地

在数字化浪潮汹涌澎湃的今天&#xff0c;电商行业以其独特的魅力和强大的市场潜力&#xff0c;成为了推动经济增长的新引擎。四川蔚澜时代电子商务有限公司&#xff0c;作为这个领域的佼佼者&#xff0c;正以其专业的服务、创新的理念和卓越的实力&#xff0c;引领抖音电商服务…

【Linux进阶】Linux目录配置,FHS

在了解了每个文件的相关种类与属性&#xff0c;以及了解了如何修改文件属性与权限的相关信息后&#xff0c;再来要了解的就是&#xff0c;为什么每个Linux发行版它们的配置文件、执行文件、每个目录内放置的东西&#xff0c;其实都差不多&#xff1f;原来是有一套标准依据&…

在 Mac 上使用 MLX 微调微软 phi3 模型

微调大语言模型是常见的需求&#xff0c;由于模型参数量大&#xff0c;即使用 Lora/Qlora 进行微调也需要 GPU 显卡&#xff0c;Mac M系是苹果自己的 GPU&#xff0c;目前主流的框架还在建立在 CUDA 的显卡架构&#xff0c;也就是主要的卡还是来自英伟达。如果要用 Mac 来做训练…

pnpm的坑

请问pnpm的两个坑怎么解决&#xff1a; 第一个坑&#xff1a;没有节省磁盘空间 我已经配置了依赖的存储位置&#xff0c; 但我在项目里pnpm install以后&#xff0c;发现依赖包还是很大&#xff0c; 然后发现里面的链接并不是指向先前配置的依赖存储位置&#xff0c;而是指…

中霖教育怎么样?注册会计师可以跨省考试吗?

中霖教育怎么样?注册会计师可以跨省考试吗? 1. 考试地点安排&#xff1a; 注册会计师考试是在全国范围内统一举行的&#xff0c;通常设在各省、自治区和直辖市指定的考区。考生须依据准考证上提供的信息&#xff0c;核实自己的具体考试地点。该考试实行的网上统一报名制度&…

DBeaver连接clickhouse最全教程

环境 clickhouse server 20.3 dbeaver 24.1.1.202406231636在使用 dbeaver 连接 clickhouse 的时候需要&#xff0c;它默认是没有驱动的&#xff0c;然后其默认会安装 clickhouse-jdbc的 latest 版本&#xff0c;比如当前最新的驱动版本为 0.6.2&#xff0c;然后等我去连接的时…

LabVIEW汽车转向器测试系统

绍了一种基于LabVIEW的汽车转向器测试系统。该系统集成了数据采集、控制和分析功能&#xff0c;能够对转向器进行高效、准确的测试。通过LabVIEW平台&#xff0c;实现了对转向器性能参数的实时监测和分析&#xff0c;提升了测试效率和数据精度&#xff0c;为汽车转向器的研发和…

嵌入式Linux系统编程 — 6.6 信号掩码

目录 1 信号掩码介绍 2 sigprocmas函数 3 sigsuspend函数阻塞等待信号 1 信号掩码介绍 信号掩码&#xff08;Signal Mask&#xff09;是操作系统中用于控制进程接收信号的一种机制。每个进程都有一个或多个信号掩码&#xff0c;它们定义了哪些信号在特定时间被阻塞&#xf…

2024年在WordPress中创建销售活动的专家级优惠券方法

2024年在WordPress中创建销售活动的专家级优惠券方法 今天我想和大家分享一些关于如何在WordPress网站上使用专家级优惠券工具来创建销售活动的经验。对于已经在电商领域有一定经验的店主&#xff0c;利用专家级优惠券不仅能吸引顾客&#xff0c;还能显著增加销量。在这篇文章…

地铁车厢火灾3D模拟逃生演习减少了资源损耗和风险

在消防安全领域&#xff0c;为了更好地提升安全实训效果&#xff0c;我们在VR安全培训领域打造了多款消防安全VR模拟实训系统&#xff0c;不仅实现了与现实世界无异的交互操作&#xff0c;更在虚拟空间中超越了现实的限制&#xff0c;模拟出那些现实中难以搭建的复杂场景。 利用…

The Sandbox 创作者的幕后采访: 了解创作者的内心世界

我们采访了一些在 "创作者挑战" 中脱颖而出的顶尖创作者&#xff0c;探讨他们成功的秘诀以及在创造玩家喜爱的体验方面的心得。 The Sandbox 创作者挑战涌现出许多才华横溢的创作者&#xff0c;他们在游戏制作机制上的创新和突破引起了 The Sandbox 社区的广泛关注。…