类和对象(一)

简介: 今天小编就开始给大家带来类和对象的介绍了,当我们学习完类与对象之后,我们也算半只脚踏入C++的大门了,那么话不多说,我们直接开始今天相关内容的学习。

前言

今天小编就开始给大家带来类和对象的介绍了,当我们学习完类与对象之后,我们也算半只脚踏入C++的大门了,那么话不多说,我们直接开始今天相关内容的学习。


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

想必大家在学习编程时肯定听说过C语言时一门面向过程的语言,而C++是一种面向对象的语言,那么这两者具体又有什么区别呢?这里我举个例子给大家简单说明一下


面向过程,那么我们关注的肯定是做一件事情的过程,那么就假设我们这里以洗衣服为例,面向过程,我们关注的便是洗衣服的每个过程,也就是:



面向对象则关注的是对象,将一件事情拆分成不同的对象,靠对象之间的交互完成。以洗衣服为例也就是:



通过这样我相信大家就会很形象的理解面向过程和面向对象的区别,而面向对象很明显更符合我们现实生活中的思考方式。


2.类的引入

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

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

会发现struct中也可以定义函数。

这里我们就会发现和面向过程的语言相比,我们这里面向对象的语言,不仅封装了一个对象的属性还有一系列的方法。

这里给大家简单的演示一下:

typedef int DataType;
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;
 ++_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;
}


这里我们就在结构体内实现了一个栈,而且是将该成员变量和函数封装在一起的一个栈,那么这里也就为我们的调用提供了更多的简便,这里我给大家运行一下,查看该程序是否能够运行



这里我们发现我们的程序是可以运行的,而且也是按照我们设计的逻辑到达的效果。 而这就是一个类的创建,那么大家肯定有疑问,我们印象中的类不是用到的是class这个关键字吗?我们继续往下看:


3.类的定义

在C++中struct升级成了类,但是尽管struct可以进行类的定义,C++中更喜欢用class来代替。


那么类的定义格式如下:


class className

{

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

};  // 一定要注意后面的分号

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


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


3.1 类定义的两种方式

对于类的定义我们即可以将成员函数的声明和定义放在类内,也可以将类的定义放在类外,但是两者在本质上却有着一丝不同这里就需要我们仔细去了解一下:


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

联函数处理。 也就是:


这里也就会造成小编和大家说的可执行程序变大,那么我们怎么去避免这种情况的出现呢?这里就有我们第二种定义方式:


2. 类声明放在.h文件中,成员函数定义放在.cp

在声明和定义分开的情况下就需要我们在定义时去指定类域,对于类域的内容我们继续往下看。这里需要给大家说明一下的是:如果我们在一般练习的情况下,我推荐大家使用第一种,但是偏于工程性的我这里建议大家使用第二种定义方式。

3.2 成员变量命名规则建议

这里我们定义类的时候会经常出现一种情况比如就是:


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

这里就会造成歧义,所以我们在定义成员变量时,一般会加前缀或者后缀,比如这里的 year 我们可以定义为 int _year,或者int year_。


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

4.1 访问限定符

C++实现封装的方式:用类将对象的属性与方法结合在一块,让对象更加完善,通过访问权限选

择性的将其接口提供给外部的用户使用,所以我们就会使用对应的访问限定符来决定什么内容提供给用户,什么不对外显示,那么访问限定符有以下我们暂时需要去了解一下:

【访问限定符说明】


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

2. protected和private修饰的成员在类外不能直接被访问(此处protected和private是类似的,具体的不同我们就需要在后续给大家介绍)

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

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

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

4.2 封装

首先我们要知道面向对象的三大特性:封装、继承、多态。而这里我们研究封装:


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


封装本质上是一种管理,让用户更方便使用类,比如:对于电脑这样一个复杂的设备,提供给用 户的就只有开关机键、通过键盘输入,显示器,USB插孔等,让用户和计算机进行交互,完成日 常事务。但实际上电脑真正工作的却是CPU、显卡、内存等一些硬件元件。而对于类我们就可以使用相关访问限定符来进行封装,比如下面我实现一个简单的类,我们成员方法是允许给用户提供使用的,但是我们的成员变量是不允许用户访问的,那么我们就可以,实现如下定义:

class Date
{
public :
  int GetmMonthday(int year, int month)
  {
  int daysArrary[13] = { 0,1,2,3,4,5,6,7,8,9,10,11,12 };
  if (month == 2 && (year % 4 == 0 && year % 100 != 0) || year % 400 == 0)
  {
    return 29;
  }
  return daysArrary[month];
  }
private:
  int _year;
  int _month;
  int _day;
};


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


5.类的作用域

类定义了一个新的作用域,类的所有成员都在类的作用域中。在类体外定义成员时,需要使用 ::

作用域操作符指明成员属于哪个类域。

这也就为什么我们在成员方法声明和定义分开时需要进行进行指定作用域。

6.类的实例化

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

1. 类是对对象进行描述的,是一个模型一样的东西,限定了类有哪些成员,定义出一个类并没

有分配实际的内存空间来存储它

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

这里大家可以简单的把类想象成为一个图纸,而我们的对象就是我们根据图纸建造出来的房子,而房子是占实际空间的,但是图纸是不占空间的。


7.类对象模型

既然我们知道了一个对象是占实际空间的,但是一个类内有包含了成员函数和成员变量,那么我们该如何去计算一个对象的大小?


首先这里我们就需要去知道一个类的存储模型:


那么一个类的存储模型实际上是将成员变量存放在类内,成员函数存放在公共的代码段,也就是:



但是这里我们对于成员变量我们也是要求内存对齐的。这里的对齐规则和我给大家C语言中给大家介绍结构体内存对齐是一个道理,我就不给大家再次介绍了,这里我给大家简单的聊内存对齐的意义。


1. 平台原因(移植原因):


不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特


定类型的数据,否则抛出硬件异常。(我们对应的硬件可能需要在默认的位置,读取指定的空间大小,内存对齐后就会减少该访问次数)


2. 性能原因:


数据结构(尤其是栈)应该尽可能地在自然边界上对齐。


原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访


问。


这里我们可以看出来默认对齐是一种以空间换时间的方式,但是当我们空间不足时,我们也可以修改默认对齐数,到达节省空间的方式。


8.this指针

大家肯定都看过此类调用成员函数的方式:

class Date
{
public :
  void show()
  {
  cout << _year << endl;
  cout << _month << endl;
  cout << _day << endl;
  }
  int _year;
  int _month;
  int _day;
};
int main()
{
  Date d1;
  d1.show();
  Date d2;
  d2.show();
  return 0;
}


这里我们有个问题需要思考一下,对于不同对象调用同一个函数,在没有参数变化的情况下,该是如何区分不同对象的呢?


C++中通过引入this指针解决该问题,即:C++编译器给每个“非静态的成员函数“增加了一个隐藏


的指针参数,让该指针指向当前对象(函数运行时调用该函数的对象),在函数体中所有“成员变量”


的操作,都是通过该指针去访问。只不过所有的操作对用户是透明的,即用户不需要来传递,编


译器自动完成。


所以实际上我们通过对象调用同一个函数我们实际上是:



而这个this指针就是我们函数区分不同对象的方式。


8.1 this指针的特性

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

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

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

this形参。所以对象中不存储this指针。

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

递,不需要用户传递

这里我再给大家简单的说两道面试题:

// 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指向的是空指针,这里我们p有一个解引用符号,这里大家是不是以为这里会导致编译错误,但是我们的成员函数并不在类内,所以实际上该是没有进行解引用操作的,所以这里我们运行时正确的。


那么对于第二题,我们通过第一题我们可以发现,这里的编译的过程没有问题,但是在我们开始运行函数时,我们可以我们函数中是对类内成员变量_a进行了访问操作,所以这里就发生了对空指针解引用的操作,这就会导致我们运行时程序崩溃。

相关文章
|
5天前
|
存储 编译器 C语言
C++:类和对象(上)
C++:类和对象(上)
60 0
|
7月前
|
存储 编译器 C++
|
5天前
类和对象
类和对象
10 0
|
5天前
|
存储 编译器 程序员
C++类和对象(中)
C++类和对象
66 0
|
5天前
|
Java
类和对象三
类和对象三
9 2
|
5天前
|
存储 编译器 C语言
C++-类和对象(2)
C++-类和对象(2)
27 0
|
5天前
|
Java C++ Python
【c++】理解类和对象
【c++】理解类和对象
【c++】理解类和对象
|
5天前
|
编译器 C++
【C++】:类和对象(3)
【C++】:类和对象(3)
39 0
|
5月前
|
Java 编译器
类和对象!
类和对象!
30 1
|
5月前
|
编译器 C++
类和对象(下)
类和对象(下)
29 0