类和对象的介绍一

简介: 类和对象的介绍一

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

面向对象和面向过程是两种编程思想。面向过程是一种以事件为中心的编程思想,编程的时候把解决问题的步骤分析出来,然后用函数把这些步骤实现,在一步一步的具体步骤中再按顺序调用函数。

而面向对象是一种以“对象”为中心的编程思想,把要解决的问题分解成各个对象,建立对象的目的不是为了完成一个步骤,而是为了描叙某个对象在整个解决问题的步骤中的属性和行为。

面向过程和面向对象各有优缺点。面向过程强调代码的短小精悍,善于结合数据结构来开发高效率的程序。但需要深入思考,耗费精力,代码重用性低,扩展能力差,后期维护难度比较大。而面向对象程序设计中代码间的相关性低(低耦合特性),使得代码很容易被复用和扩展,同时也说明了面向过程的代码重用性低、扩展能力差。但开销大,当要修改对象内部时,对象的属性不允许外部直接存取,所以要增加许多没有其他意义、只负责读或写的行为。这会为编程工作增加负担,增加运行开销,并且使程序显得臃肿。

2.类的引入

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

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

会发现struct中也可以定义函数,对于这样的结构体,c++中升级成了类。

因为c++兼容c,一个结构体我们利用struct创建变量,也可以直接结构体名加变量来创建。

typedef int datatype;
//利用结构体定义栈,内部可定义成员函数
struct Stack
{
  //初始化
  void stinit(int _capacity)
  {
    arr = (datatype*)malloc(sizeof(datatype) * _capacity);
    if (arr == nullptr)
    {
      return;
    }
     size= 0;
     capacity = _capacity;
  }
  //入栈
  void stackpush(const datatype& _data)
  {
    arr[size] = _data;
    size++;
  }
  void stackpop()
  {
    size--;
  }
  datatype gettop()
  {
    return arr[size-1];
  }
  void destroyst()
  {
    if (arr!= nullptr)
    {
      free(arr);
      arr = nullptr;
      size = capacity = 0;
    }
  }
  int* arr;
  int size;
  int capacity;
};
int main()
{
  struct Stack ST;
  ST.stinit(5);
  ST.stackpush(1);
  ST.stackpush(2);
  ST.stackpush(3);
  ST.stackpush(4);
  cout << ST.gettop() << endl;
  ST.stackpop();
  cout << ST.gettop() << endl;
  return 0;
}

如上我们定义了成员函数和成员变量,可以值间接调用成员函数。在这里struct 已经作为一个类在用了。

3.类的定义

在上述我们知道了struct 也可作为类,那么类的定义是什么?什么样的结构叫做类?

其实类是一种用户自定义的数据类型,它描述了一组有相同特性(属性)和相同行为(方法)的一组对象的集合。一个类的定义通常包含两部分的内容,一是该类的属性,另一部分是它所拥有的方法。这样的结构类型就可叫做类。

而对于c++,除了常用的结构体是类,c++也提供了一个关键字class专门用来定义类。这也是大多数人选择的方法。

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

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

号不能省略 。

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

成员函数

类的两种定义方式:

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

联函数处理。

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

这与我们之前实现数据结构类似,函数的实现我们一般都放在.c文件当中,c++也如此。

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

同样是类,为什么在选择类的定义是,人们一般不用struct,而取用clss,这里就要说到一点他们的访问限定符的区别。

在C++中,可以使用以下访问修饰符在进行声明时指定类型或成员的可访问性:public、protected、private。

453e807c969946209c954acbbb77132b.png

.public: 可以被该类中的函数、子类的函数、其友元函数访问,也可以由该类的对象访问。

.protected: 可以被该类中的函数、子类的函数、以及其友元函数访问,但不能被该类的对象访问。

.private: 只能由该类中的函数、其友元函数访问,不能被任何其他访问,该类的对象也不能访问 。

而对于我们刚才说到的struct,它的默认访问限制是public,外部都是可以访问的。而对于class,它的默认访问限制是private,一般除了类中的是无法访问的。而对于真正实现大项目时,为了防止相互可能存在访问的情况下,private对于程序员来说更好。这就是为什么大多数人选择class,其次在继承和模板参数列表位置,struct和class也有区别。

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

封装:

首先,面向对象的三大特性分别是:封装、继承、多态

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

封装是指一种将抽象性函数接口的实现细节部分包装、隐藏起来的方法。封装可以被认为是一个保护屏障,防止该类的代码和数据被外部类定义的代码随机访问。要访问该类的代码和数据,必须通过严格的接口控制。封装最主要的功能在于我们能修改自己的实现代码,而不用修改那些调用我们代码的程序片段。适当的封装可以让程式码更容易理解与维护,也加强了程式码的安全性。

在C++语言中实现封装,可以通过类将数据以及操作数据的方法进行有机结合,通过访问权限来

隐藏对象内部实现细节,控制哪些方法可以在类外部直接被使用。

对于类的内联函数,还是一样,函数不会进入符号表,直接展开。

5.类的作用域

对于类和一般定义的空间的内部的访问,他们一般都是有空间访问限制的,这就需要在访问时加上结构::成员,像这种结构就跟之前学的namespace的定义一样,在实现struct class这种结构时,其实也定义了空间。是不被外部访问的,需要借助域访问作用符。

6.类的实例化

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

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

定义的类就是一种类型,我们可以通过这种类实例化多个对象。

class  Person
{
public:
  //方法一
  //方法二
  //方法三 
private:
    //他的属性
  char* name;
  int age;
  int scoer;
  int id;
};
int main()
{
  Person p1;
  Person p2;
  Person p3;
}

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

2fb92ee731b845a98fcb714b66550a3b.png


对于一个类,它的变量只是声明,所有的成员函数不在类中存储且只是声明,它们会被提供一块公共的代码区域,一面每个对象都能访问且不同。

7.类的对象的大小的计算

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

     类的大小计算遵循结构体的对齐原则, 与普通数据成员有关,与成员函数和静态成员无关。虚函数和虚继承对类的大小有影响,是因为虚函数表指针和虚基表指针带来的影响。空类的大小为1。类只是一个类型定义,它是没有大小可言的。用sizeof运算符对一个类型名操作,得到的是具有该类型实体的大小。当类不包含虚函数和非静态数据成员时,其对象大小也为1。虚函数本身和其他成员函数一样,是不占用对象的空间的

class A1 {
public:
  void f1() {}
private:
  int _a;
  char c;
};
//只有成员函数
class A2
{
public:
  void f2() {}
  void f3() {}
};
// 类中什么都没有---空类
class A3
{};
int main()
{
  cout << sizeof(A1) << endl;
  cout << sizeof(A2) << endl;
  cout << sizeof(A3) << endl;
  return 0;
}

c6d28b4b91634f109ee0ad23ffa671da.png

根据编译的结果可以看到成员函数实际上是不计入大小的,只记入成员变量。空类认为是1。

结构体内存对齐规则

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

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

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

VS中默认的对齐数为8

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

4. 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整

体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍 。

根据类的大小我们可以看出成员函数的栈帧是不在类里定义的。

内存对齐的设计是为了便于访问。

8.类成员函数的this指针

1.this指针的特性:类型:类类型*const,在成员函数中无法赋值,无法作为实体参数。

2.只能在成员函数中使用


82bef04a71884edfa1d82e7c79afd1d6.png


ebc30786d7ee4327b97e55ad5aded07b.png

实际上是通过传入对象的指针来确定对函数的调用,类中定义的仅仅是个声明,不可能定义多个对象都去访问类中的变量。

其次我们虽然知道是有个this指针,但不可以直接传个对象指针作为this指针,this指针可以在函数内部访问成员变量会其他成员函数,但不能实体化作为参数,他是隐形的。

注意事项:


e9b42e63dd9a424d968ba6a6c7ccb691.png

上述三种给三种情况 ,A .编译错误  B.运行崩溃  C.正常运行

对于第一个,可以看到定义了一个对象指针 且为空,之后利用对象指针访问函数。首先我们会想到空指针访问需要解引用,但不会报编译错误,排除A,但实际上选C,正常运行:因为成员函数不存放在类中且也不存放在对象中,(存在对象中会是对象占良大量空间),成员函数实际是存在一块公共代码区域的,调用函数直接拿函数名去找这个函数的地址,因此并会对对象指针解引用,直接去访问函数。

第二个,与第一个原理类似,这里也不会报错,即使我们这样去写,但编译器不会解引用指针,也是直接那函数名去公共代码空间哪里找。

第三个,会运行崩溃,可以发现打印中会去访问对象的成员变量,空指针会去解引用,因此会运行崩溃。

其次在vs传递this指针时是通过寄存器来传递的。


相关文章
|
编译器 C++
【C++】类和对象(二)
类和对象(二) 类中有六个默认成员函数: 编译器会自动生成以下6个默认成员函数。 默认成员函数:用户没有显式实现,编译器会生成的成员函数称为默认成员函数。
58 0
|
存储 安全 编译器
C++类和对象【中】
C++类和对象【中】
40 0
|
4月前
|
编译器 程序员 C语言
C++ --> 类和对象(一)
C++ --> 类和对象(一)
29 5
|
编译器 C++
【C++】类和对象(下)(三)
【C++】类和对象(下)(三)
|
7月前
|
Java 编译器 C语言
|
7月前
|
Java 编译器 Linux
C++:类和对象(下)
C++:类和对象(下)
47 1
|
7月前
|
存储 编译器 C语言
【c++】类和对象1
【c++】类和对象1
42 1
|
7月前
|
编译器
C++-类和对象(3)
C++-类和对象(3)
41 0
|
7月前
|
存储 编译器 C语言
C++-类和对象(2)
C++-类和对象(2)
42 0