【c++】——类和对象(上)——万字详细解疑

news2024/9/22 11:38:15

目录

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

2.类的引入

3.类的定义

3.1类的两种定义方式:

3.2 成员变量命名规则建议

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

4.1 访问限定符

4.2.封装

5.类的作用域

6.类的实例化

7.类对象模型

7.1 如何计算类对象的大小

7.2 类对象的存储方式猜测

7.3 结构体内存对齐规则

8.this指针

8.1 this指针的特性

8.2 this指针相关面试题

1. this指针存在哪里?

2. this指针可以为空吗?

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


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

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

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

我们现在进入c++的学习之后,我们就不用关注做事情的过程,而是关注做事情对应的对象了。比如洗衣服,我们出现了四个对象—(人,衣服,洗衣粉,洗衣机),但是过程中对衣服的繁琐过程比如(打开洗衣机,倒入洗衣机...等)是不需要关心的。

为了更加的对面向对象的c++语言有更深的认识,继续往下看吧。 


2.类的引入

  • 💡C语言结构体中只能定义变量

图一:c语言——对于这个结构体来说:struct ListNode是这个结构体的类型,struct必须带上的。

图二:c++语言——c++将结构体定义成了类,C++中可以直接用struct后面的做结构体类型,可以不用加struct。

  • 💡在C++中,结构体内不仅可以定义变量,也可以定义函数。

比如:

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

struct Stack
{
  //成员函数
    void Init(size_t capacity)
    {
        _array = (DataType*)malloc(sizeof(DataType) * capacity);
        if (nullptr == _array)
        {
            perror("malloc申请空间失败");
            return;
        }
        _capacity = capacity;
        _size = 0;
    }
    void Push(const DataType& data)
    {
        // 扩容
        _array[_size] = data;
    }
    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;
};

通过.的方式可以调用结构体中的函数。

上面结构体的定义,在C++中更喜欢用class来代替。class是类的关键字,用class来表示类。


3.类的定义

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

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

类体中内容称为类的成员:类中的变量称为类的属性或成员变量;

类中的函数称为类的方法或者成员函数

3.1类的两种定义方式:

1. 声明和定义全部放在类体中(下面的成员变量其实是声明)

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

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

那在成员函数showlnfo前面加上Person::的作用呢其实就是告诉编译器showlnfo不是全局的函数,而是Person这个类中的成员函数,如果不加就会报错的,因为在全局找不到该函数。那这样如果在函数中用到了对应的成员变量,编译器也会到类中去寻找。另外要注意如果有缺省参数前面我们说了要在函数声明中给。

3.2 成员变量命名规则建议

class Date
{
public:
    void Init(int year)
    {
        //这里的year到底是成员变量,还是函数形参?
        year = year;
    }
private:
    //成员变量
    int year;
    int month;
    int day;
};

Date有一个成员变量(属性)year,然后还有一个成员函数Init,但是Init函数的形参和成员变量同名,那这里就有一个问题,Init中的year到底是成员变量,还是函数形参?

class Date
{
public:
    void Init(int year)
    {
        //这里的year到底是成员变量,还是函数形参?
        _year = year;
    }
private:
    //成员变量
    int _year;
    int month;
    int day;
};

成员变量的前面我们可以加一个_和形参进行区分。

这只是建议,大家可以按照自己的想法进行区分。以后大家进入公司工作主要看公司要求。


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

4.1 访问限定符

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

【访问限定符说明】

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

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

现阶段我们刚开始学习类和对象,可以先不在意protected和private具体的区别。

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

pubilc:.....private

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

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

我们在这里没有设定访问限定符,这里说明了class默认访问权限是private,所以是不供用户进行访问的。

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

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

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


4.2.封装

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

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

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

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

比如:对于电脑这样一个复杂的设备,提供给用户的就只有开关机键、通过键盘输入,显示器,USB插孔等,让用户和计算机进行交互,完成日常事务。但实际上电脑真正工作的却是CPU、显卡、内存等一些硬件元件。

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

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

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

这个大家先了解一下即可,在后续学习过程中我们还会不断加深对封装等特性的理解。


5.类的作用域

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

class Person
{
    //在类里面定义函数
public:
    void PrintPersonInfo(const char*_name,const char*_gender,int _age);
//在类里面定义变量
private:
    char _name[20];
    char _gender[3];
    int _age;
};
// 这里需要指定PrintPersonInfo是属于Person这个类域
void Person::PrintPersonInfo(const char*_name, const char*_gender, int _age)
{
    cout << _name << " " << _gender << " " << _age << endl;
}

int main()
{
    Person p;
    p.PrintPersonInfo("peter", "男", 19);
    return 0;
}

这里的PrintPersonInfo是属于Person这个类域,格式Person(类名)::PrintPersonInfo(成员函数)。

来表明PrintPersonInfo成员函数是属于Person的。


6.类的实例化

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

1. 类是对对象进行描述的,是一个模型一样的东西,限定了类有哪些成员,定义出一个类并没有分配实际的内存空间来存储它;比如:入学时写的学生信息表,表格就可以看成是一个类,来描述具体学生信息。

类就好比是建造房子的图纸,类的实例化就好比用图纸去建造房子。图纸只是对房子进行了一个描述,用图纸建造出来的房子(类的实例化)才占用实际空间。

类只是一个图纸,不占据空间,类的实例化就是对图纸的内容进行创建一个实打实的物体并且占据空间。

举个例子吧:

类就像谜语一样,对谜底来进行描述,谜底就是谜语的一个实例。

谜语:"年纪不大,胡子一把,主人来了,就喊妈妈" 谜底:山羊

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

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

man就是我们person这个类实例化出来的一个对象。

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


7.类对象模型

7.1 如何计算类对象的大小

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

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

  • pubilc公有访问限定符内的函数就相当于健身房,篮球场一样,是可以公共开放的(如果每一家都开一个健身房和篮球场,会使占用面积增大)
  • private私有访问限定符内的函数就相当于房子里面的卧室,是不可以开放使用的。

这里的A和aa1的大小都是1,不考虑成员函数。


7.2 类对象的存储方式猜测

我们猜测,有以下几种可能:

  • 1.对象中包含类的各个成员,成员变量和函数都存储在对象中

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

  • 2.代码只保存一份,在对象中保存存放代码的地址

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

对于上述三种存储方式,那计算机到底是按照那种方式来存储的,下面我们就来验证一下:

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

首先提醒大家C++中类对象大小的计算方法和C语言结构体是一样的,都要考虑内存对齐。


首先给类Date创建一个对象的d1,看它的大小是多大?

我们只看类中的成员变量。大小是12.

所以呢:

没错,正确的存储方式是第三种:类对象中只存储成员变量,不存储成员函数(地址也没有),成员函数存放在公共的代码段。

打个比方,大家可以这样理解:

我们说了类就好比是建造房子的图纸,一张图纸可以建造多个房子,那同样道理,一个类就可以实例化多个对象。

  • 类中对象的属性(成员变量)呢?

就可以看作是房子里面的厨房、浴室…这些东西,每栋房子里面都有。

  • 类中的成员函数(方法)呢?

就可以看作小区里的篮球场,小卖部等,这种东西需要一个房子里面建一个吗?那就太浪费了吧,是不是整个小区共用一个就行了啊。所以成员函数是不存在对象里的,而是存在公共的代码段。

总结:

计算一个类对象的大小,只需要考虑其中的成员变量就行了,当然记得要按照结构体内存对齐的规则进行计算。


下面我们就来做几个练习,计算几个类的大小

三种情况的题目:

类的大小是多大,它创建的对象就是多大,就像整型int的大小是4个字节,用int创建的变量也是4个字节。

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


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

没有成员变量,只有成员函数,那么它的大小是0吗? 


  • 3.类中什么都没有---空类
// 类中什么都没有---空类
class A3
{};

一个空类,那按照上面的结果来分析,A3的大小也应该是0,也是一个成员变量都没有:

是的,也是1.


那为什么不含成员变量的类大小是1个字节呢?

  • 一个类的大小,实际就是该类中”成员变量”大小之和,当然要注意内存对齐。

仅有成员函数,没有成员变量,编译器都给了一个字节来唯一标识这个类的对象。

注意空类的大小,空类比较特殊,编译器给了空类一个字节来唯一标识这个类的对象。

  • 空类有意义嘛?——相信任何东西存在就有意义,只是我们现在还不知道。

——我们可以这样,如果空类或者仅有成员函数,没有成员变量,怎么表示它定义了,这就需要编译器默认给它们的大小为1.证明它们虽然没有成员变量,但是定义了。

7.3 结构体内存对齐规则

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

我们在C语言阶段也详细讲解过了,大家不熟悉的可以复习一下:

链接: 结构体内存对齐

C语言专栏中的一篇文章:自定义类型(结构体,枚举,联合,位段)

为什么要内存对齐?——读取的时候只能在对齐数的倍数上起始位置上读


8.this指针

#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 a;
};

那我们现在用该类创建两个对象,并调用成员函数:

int main()
{
    Date d1, d2;
    d1.Init(2022, 1, 11);
    d2.Init(2022, 1, 12);
    d1.Print();
    d2.Print();
    return 0;
}

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

我们看到这里能够正确的对d1,d2的属性(成员变量)进行初始化并打印。

我们运行这段代码,查看了结果,为什么d1,d2调用的是同一个函数,调同一个函数,为什么打印的结果是不一样的呢?

答:——我们大多数是第一想法就是对象不一样,Date类有俩个对象d1,d2,对象的不同调用同一个函数的结果就不同。

可是调用函数和对象有关系嘛?

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

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

那原因在于: 

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

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

以前对于c中的栈什么的,都是自己手动写出来代码,而在c++中编译器不会显示给用户,自动给成员函数传地址。——比如this指向的是d1的年月日,那么就会打印对象d1的内容。


8.1 this指针的特性

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

比如在上面的Date类中,this指针的类型就是Date* const

我们这来复习一下const* / *const 的区别——c语言回顾

const:修饰离const最近的第一个成形类型。

区分:const int* p2=&a ; int const*p3=&a 这俩者的修饰的类型和修饰的内容都是一样的,修饰的内容是无法修改的(简单记成const在*前面修饰的类型(int,float,double),而不是(int*,float*,double*...)

 区分:int *const p4=&b; const在*之后修饰的类型(int*,float*,double*),,是不可以修改constt之后的内容。

  • *在const之前,修饰的类型一定是 int*类型。 int *const p4;表示修饰的内容是p4,修饰的类型是int。 p4是不可以修改的。
  • *在const之后,修饰的类型一定是 int类型. int const *p4//const int *p4;表示修饰的内容是*p4,修饰的类型是int. *p4是不可以修改的。修饰的内容是无法改变的

我们复习了const的内容,我们知道const修饰的内容是不可以修改的,所以是在后面Date *const this ,而不能 const Date *this或者Date const *this ,这样修饰的内容是*this ,这里就表示*this是不可以修改的。

左操作数必须为左值指一个能用于赋值运算左边的表达式。左值必须能够被修改,不能是常量权。我们用变量作左值,还可以看到,指针和引用也可以作左值。

一般形式为:变量=表达式。其作用是将一个表达式的值赋给一个左值。计算赋值运算符右侧表达式的值(“=”为赋值运算符),将赋值运算符右侧表达式的值赋给左侧的变量,将赋值运算符左侧的变量的值作为表达式的值。

所以可以表示this指针是不可以修改的。


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

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

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


有些易错点:

我们将成员变量设置成公共部分,能不能通过类直接访问呢?——不能

为什么不能?成员变量只是声明,就好比在图纸里面找到自己的房间,图纸里面有住的房间嘛?抱歉没有你的房间,还有人这样写

::这是域限定符号,怎么能通过对象访问成员变量呢?

.符号,.就相当于对象访问成员的一种符号。

我们的步骤必须,给类创建对象,然后通过对象访问成员函数之间的符号是(.),而不是::域限定符号。

不能通过类访问成员变量和成员函数,所以不能在图纸中找到自己房间。——类的实例化


8.2 this指针相关面试题

1. this指针存在哪里?

——this指针是形参,实参是对象的地址,所以this指针跟普通参数一样存在栈里面,作为栈帧的一部分。

vs下面对this指针,进行的优化,对象的地址放在exc,exc存储this指针的值。

2. 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;
}

首先我们可以排除A,不可能是编译错误。空指针是运行的问题,不会报编译的错误。

对象是.,指针是->. C

p是一个Date类型的空指针,然后我们通过p1去调用类的成员函数Print

  • 大家可能会想,这里不是对空指针解引用了吗?怎么还运行正常啊?

那要告诉大家的是,我们不能看到->或者.就认为一定存在解引用,还是要根据具体情况进行分析。

我们上面说调用类成员函数时会进行一个隐式的传参,传的是当前调用成员函数的对象的地址,那现在的情况是什么,是不是传过去了一个空指针啊。

  • 传参传空指针一定会出错吗?

是不是不一定啊,函数那边没有进行空指针的检查,那是不是只要不对空指针进行解引用就没问题啊。

而 Print 函数里面是不是只是打印了一个字符串 "Print()" ,并没有对空的this指针解引用,所以程序正常运行,没有问题。

// 1.下面程序编译运行结果是? A、编译报错 B、运行崩溃 C、正常运行
class A
{ 
public:
  void PrintA() 
  {
 cout<<_a<<endl;
 }
private:
 int _a;
};
int main()
{
 A* p = nullptr;
 p->PrintA();
 return 0;
}

那这下是不是就要出问题了啊,因为Init函数里面是不是要通过this指针去找当前对象的成员变量(解引用了),但现在传过来的this指针是空指针,那对空指针解引用是不是程序就崩溃了。

左边没有解引用,右边有解引用,空指针不能解引用。——对空指针解用是会使运行崩溃。

总结:

不进行对函数的解引用(不对函数中成员变量进行调用)就不会使运行崩溃。

我们不能因为是空指针都是运行崩溃,我们得看是不是对其进行解引用,有没有进行函数内部的成员变量进行调用,如果只是进行打印内容是不会使运行崩溃的。


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

1.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相关操作函数有以下共性:

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

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

2. 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语言中需用用户自己维护。

这里可以清晰的可以看出其中的差别了。


 祝我看似低矮,万尽山开。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

聊聊“JVM 调优JVM 性能优化”是怎么个事?

所谓“调优”就是一个诊断和处理手段&#xff0c;最终的目标是让系统的处理能力&#xff0c;也就是“性能”达到最优化。 计算机系统中&#xff0c;性能相关的资源主要分为这几类&#xff1a; CPU&#xff1a;CPU 是系统最关键的计算资源&#xff0c;在单位时间内有限&#xf…

树莓派系统文件解析

title: “树莓派系统文件分析” date: 2023-10-25 permalink: /posts/2023/10/blog-post-5/ tags: 树莓派 本篇blog来分析和总结下树莓派系统文件以及他们的作用。使用的系统是Raspberry Pi OS with desktop System: 64-bitKernel version: 6.1Debian version: 12 (bookworm)…

09、Python 字典入门 及 高级用法

目录 字典创建字典通过key访问value添加key-value对删除key-value对替换key-value对 判断是否包含指定keydict与列表字典的常用方法演示&#xff1a; 用字典格式化字符串 创建字典 操作字典key-value对 理解dict与list的关系 字典常用方法 使用字典格式化字符串 字典 字典用于…

Spark_SQL函数定义(定义UDF函数、使用窗口函数)

一、UDF函数定义 &#xff08;1&#xff09;函数定义 &#xff08;2&#xff09;Spark支持定义函数 &#xff08;3&#xff09;定义UDF函数 &#xff08;4&#xff09;定义返回Array类型的UDF &#xff08;5&#xff09;定义返回字典类型的UDF 二、窗口函数 &#xff08;1&…

用VScode做PPT:marp插件

文章目录 初步认识指令设置图像设置布局设置 初步认识 marp是支持Markdown格式的PPT神器&#xff0c;有了这个就可以敲代码写PPT了。更绝的是&#xff0c;marp提供了VScode插件&#xff0c;故而可以愉快地在VScode中写PPT了。 在VScode扩展商店中搜索marp&#xff0c;安装Mar…

双向电平电压转换器TXS0102DCTR应用电路设计

1、TXS0102简介 TXS0102DCTR是一个2位双向电压电平转换器&#xff0c;主要用途是与数据I/O&#xff08;例如I2C或1-wire&#xff09;上的开漏驱动器连接&#xff08;其中数据是双向的且无可用的控制信号&#xff09;&#xff0c;在混合电压系统之间建立数字开关兼容性。它使用…

Linux系统编程07

线程 为什么有了进程还需要线程 进程切换的时候会花费很大的代价 &#xff08;1&#xff09;上下文切换&#xff0c;CPU寄存器需要切换 &#xff08;2&#xff09;虚拟地址和物理地址的映射需要切换 进程间通信麻烦 线程是轻量级的进程 &#xff08;1&#xff09;线程是一个正…

【设计模式】第4节:创建型模式之“单例模式”

一、介绍 采取一定的方法保证在整个的软件系统中&#xff0c;对某个类只能存在一个对象实例&#xff0c;并且该类只提供一个取得其对象实例的方法。 不使用单例模式的UML类图&#xff1a; 使用单例模式的UML类图&#xff1a; 使用场景&#xff1a; 需要频繁创建或销毁的对象…

【密评】商用密码应用安全性评估从业人员考核题库(十二)

商用密码应用安全性评估从业人员考核题库&#xff08;十二&#xff09; 国密局给的参考题库5000道只是基础题&#xff0c;后续更新完5000还会继续更其他高质量题库&#xff0c;持续学习&#xff0c;共同进步。 2751 多项选择题 GM/T 0051《 密码设备管理 对称密钥管理技术规范…

倾斜摄影三维模型根节点合并效率提升的技术方法分析

倾斜摄影三维模型根节点合并效率提升的技术方法分析 提高倾斜摄影三维模型根节点合并效率是倾斜摄影领域的重要挑战之一。快速而准确地处理大量数据和复杂的场景需要使用高效的技术方法。本文将探讨几种可以提高倾斜摄影三维模型根节点合并效率的技术方法。 首先&#xff0c;使…

可视化工具Datart踩(避)坑指南(7)——下载的极限

作为目前国内开源版本最好用的可视化工具之一&#xff0c;Datart无疑是低成本高效率可供二开的可视化神兵利器。当然&#xff0c;免费的必然要付出一些踩坑的代价。 本篇我们来讲一讲可视化工具Datart踩&#xff08;避&#xff09;坑指南&#xff08;7&#xff09;之下载的极限…

大语言模型在天猫AI导购助理项目的实践!

本文主要介绍了Prompt设计、大语言模型SFT和LLM在手机天猫AI导购助理项目应用。 ChatGPT基本原理 “会说话的AI”&#xff0c;“智能体” 简单概括成以下几个步骤&#xff1a; 预处理文本&#xff1a;ChatGPT的输入文本需要进行预处理。 输入编码&#xff1a;ChatGPT将经过预…

Ubuntu22.04(非虚拟机)安装教程(2023最新最详细)

目录 简介 一.下载Ubuntu Server镜像&#xff0c;官方地址下载即可 ​二.安装Ubuntu镜像 简介 Linux是一种自由和开放源代码的操作系统内核&#xff0c;被广泛应用于各种计算机系统中。它以稳定性、安全性和灵活性而闻名&#xff0c;并成为服务器、嵌入式设备和个人计算机等…

国产手机性能再次飞升,H公司落后三代,但仍然比不过苹果

国产手机将采用全新的芯片&#xff0c;性能将进一步提升&#xff0c;这是国产手机的又一个重大进步&#xff0c;这次不再挤牙膏&#xff0c;真正为消费者带来性能跃升的手机&#xff0c;让消费者刷视频更流畅&#xff0c;玩游戏也更畅快。 据了解国产手机即将采用的新款芯片骁龙…

EMT4J—— Java 版本迁移检测工具

最近因为工作需要研究了emt4j&#xff0c;这里写一篇文章记录一下。 非专业Java er&#xff0c;有不同意见欢迎评论区分享。 目录 EMT4J是什么&#xff1f; 如何使用&#xff1f; Command-line Java Agent 简单的源码分析 目录分析 规则解析 参考资料 EMT4J是什么&am…

nginx只允许英文名的文件下载,中文名就是找不到文件

本文主要向大家介绍了Linux运维知识之linux下nginx不支持中文URL路径的解决方案&#xff0c;通过具体的内容向大家展现&#xff0c;希望对大家学习Linux运维知识有所帮助。 1、确定你的系统是UTF编码 [rootlocalhost ~]# echo $LAGN en_US.UTF-8 2、nginx配置文件里默认编码…

python爬虫之正则表达式实战----爬取图片

文章目录 1. 图片爬取流程分析2. 爬取家常菜图片 1. 图片爬取流程分析 先获取网址&#xff0c;URL&#xff1a;https://www.xiachufang.com/category/40076/ 定位想要爬取的内容使用正则表达式爬取导入模块指定URLUA伪装&#xff08;模拟浏览器&#xff09;发起请求&#xff0…

【springcloud-config】配置中心客户端导入依赖spring-cloud-config-server后,maven一直爆红问题解决

问题描述 配置中心客户端导入了 spring-cloud-config-server 后&#xff0c;导入依赖爆红&#xff1b; 解决办法&#xff1a; 参考官网中文文档&#xff1a;spring-cloud -config 配置中心 中文文档 补充导入 spring-config-starter-config 配置即可 <!--springcloud-c…

Transformer英语-法语机器翻译实例

依照Transformer结构来实例化编码器&#xff0d;解码器模型。在这里&#xff0c;指定Transformer编码器和解码器都是2层&#xff0c;都使用4头注意力。为了进行序列到序列的学习&#xff0c;我们在英语-法语机器翻译数据集上训练Transformer模型&#xff0c;如图11.2所示。 da…

【设计模式】第5节:创建型模式之“简单工厂、工厂方法和抽象工厂模式”

一、简单工厂模式 ProductFactory是创建商品的工厂&#xff0c;商品Product可以实现Product接口中的一些功能。 当需要根据入参的不同生成多种不同的产品时&#xff0c;可以将生成不同产品的逻辑剥离出来&#xff0c;使用产品工厂创建不同的产品。 二、工厂方法 ConcreteFact…