# 👧 面向对象

百度百科:面向对象编程(Object Oriented Programming,OOP,面向对象程序设计)的主要思想是把构成问题的各个事务分解成各个对象,建立对象的目的不是为了完成一个步骤,而是为了描叙一个事物在整个解决问题的步骤中的行为。面向对象程序设计中的概念主要包括:对象、类、数据抽象、继承、动态绑定、数据封装、多态性、消息传递。通过这些概念面向对象的思想得到了具体的体现。

在上面那段话中,你能看明白多少?

全都看不懂?没关系,我们一开始接触到这方面知识,并不需要一下子就完全掌握其中的全部概念。饭得一口一口吃,路得一步一步走,先来个初步理解:

“面 向 对 象 编 程”,我们可以拆成三个词语来分析:面向对象编程

  • 面向: 我们大概听过类似的 "面向小学生开放 ..."、"面向未来我们要 ..."、"四个面向" 等等。这里 面向 的作用就是和这些话是同一个意思,或者换个同义词——面对。
  • 对象: 指行动或思考时作为目标的事物。举例说明:
    • 当你上网时,你控制的对象是 键盘、鼠标、电脑 ...
    • 当你睡觉时,你依靠的对象是 枕头、床、被子 ...
    • 当你上班时,你沟通的对象是 同事、客户、老板 ...
    • 通过以上例子,其实可以发现,万物皆可作为对象,只是在不同的场景中,操作的对象不一样。
  • 编程: 编写程序

所以,连起来可以这么解释:面向对象编程——面对某个任务,先分析出该任务中需要操作的对象,然后构建出对象,编写程序对这些对象进行操作,从而达到完成该任务的目的。


通过以上解释,我想你对面向对象编程的概念有了一个基础认识。

通过 对象,我们可以引申出另外一个概念——

# 1. 程序的内存模型

C++程序在执行时,将内存大方向划分为4个区域

  • 代码区:存放代码,由OS进行管理,不允许修改
  • 全局区:存放全局变量和静态变量以及常量
  • 栈区:由编译器自动分配和释放,存放函数的参数值、局部变量等。向下增——向着内存地址减小的方向增长
  • 堆区:由程序员负责分配和释放,若没有释放,将在程序结束时由OS自动释放。向上增——向着内存地址增加的方向增长

在程序编译后,生成了exe可执行程序,未执行该程序前分为两个区域

代码区:

  • 存放CPU执行的机器指令
  • 代码区是共享的,共享的目的是对于频繁被执行的程序,只需要在内存中存中有一份代码即可
  • 代码区是只读的,使其只读的原因是防止程序意外地修改了它的指令

全局区:

  • 全局变量和静态变量存放在此
  • 全局区还包含了常量区,字符串常量和其他常量也存放在此(全局变量放在全局区,局部常量不放在全局区)
  • 该区域的数据在程序结束后由操作系统释放

📌 总结:

  • C++中在程序运行前分为全局区和代码区代码区特点是共享和只读
  • 全局区中存放全局变量、静态变量、常量
  • 常量区中存放const修饰的全局常量和字符串常量

在程序运行后

栈区:

  • 由编译器自动分配释放,存放函数的参数值、局部变量等
  • 注意事项:不要返回局部变量的地址,栈区开辟的数据由编译器自动释放

堆区:

  • 由程序员分配释放,若程序员不释放,程序结束时由操作系统回收
  • 在C++中主要利用new在堆区开辟内存(new()的对象可由delete释放)
      int * arr = new int[10];  // 在堆区创建数组
      delete [] arr;            // 释放堆区数组
    
    1
    2
    1
    2

# 2. 引用

作用:给变量起别名

语法:数据类型 &别名 = 原名

本质:在C++内部实现是一个指针常量

  int a = 10;
  int &ref = a; // 自动转换为 int * const ref = &a;
  ref = 20;     // 内部发现ref是引用,自动帮我们转换为 *ref = 20;
1
2
3
1
2
3
  • 引用必须初始化,初始化后不可改变

  • 常量引用使用场景:用来修饰形参,防止误操作







     



      // 一种用法
      const int & ref = 10; // 等于:int temp = 10; const int &ref = temp;
    
      // 使用场景
      void showValue(const int &val)
      {
        val = 100;  // 报错:表达式必须是可修改的左值
        cout << val << endl;
      }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    1
    2
    3
    4
    5
    6
    7
    8
    9

# 3. 函数重载

作用:函数名可以相同,提高复用性

函数重载满足条件:

  • 同一个作用域下
  • 函数名称相同
  • 函数参数类型不同 或者个数不同 或者顺序不同

➰ 示例:




































 

  // 实现一个两数或三数相加的函数
  int add (int a, int b) {
    return a + b;
  }
  int add (int a, int b, int c) {
    return a + b + c;
  }

  /*
    Notes:
    1.函数的返回值不可以作为函数重载的条件
    2.引用作为重载的条件
    3.函数重载碰到默认参数,出现二义性
  */

  // 2.引用作为重载的条件
  void func (int &a) {
    cout << "func (int &a)调用" << endl;
  }
  void func (const int &a) {
    cout << "func (const int &a)调用" << endl;
  }
  ...
  int a = 10;
  func(a);  // result >> func (int &a)调用
  func(10); // result >> func (const int &a)调用
  // func(10)结果解析:因为 int &a = 10 不合法,而 const int &a = 10 合法

  // 3.函数重载碰到默认参数
  void func2 (int a, int b = 10) {
    cout << "func2 (int a, int b = 10)调用" << endl;
  }
  void func2 (int a) {
    cout << "func2 (int a)调用" << endl;
  }
  func2(10);  // error,出现二义性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36

# 4. 类和对象

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

C++认为万事万物都皆为对象,对象上有其属性和行为

例如:

人可以作为对象属性有姓名、年龄、身高、体重...,行为有走、跑、跳、吃饭、唱歌...

车也可以作为对象属性有轮胎、方向盘、车灯...,行为有载人、放音乐、放空调...

具有相同性质的对象,我们可以抽象称为类,人属于人类,车属于车类

# 封装

封装是C++面向对象三大特性之一

封装的意义:将属性和行为作为一个整体,表现生活中的事物,将属性和行为加以权限控制

语法:class 类名 { 访问权限: 属性/行为 };

➰ 示例:

  /*
    用户类 
  */
  class User {

  // 公共权限 
  public:
    
    // 属性
    int age;			// 年龄
    int sex;			// 性别
    string id;			// 唯一id 
    string username;	// 用户名
    
    // 行为/方法 
    string getUsername () {	// 获取用户名 
      return username;
    }
    void setAge (int a) {	// 重新设置年龄 
      age = a;
    }
    
  };  
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
访问权限 成员、类内 类外
公共权限 public 可以访问 可以访问
保护权限 protected 可以访问 不可以访问
私有权限 private 可以访问 不可以访问

➰ 示例:

 /*
    用户类 
  */
  class User {

  // 公共权限 
  public:
    int age;			// 年龄
    int sex;			// 性别
    string username;	// 用户名
    
    string getUsername () {	// 获取用户名 
      return username;
    }  


  protected:
    string id;			// 唯一id 

    void setAge (int a) {	// 重新设置年龄 
      age = a;
    }


  private:
    string password;  // 密码

    void setPw (String pw) {	// 重新设置密码 
      password = pw;
    }

  };

  ...

  User u1;
  u1.sex;           // 可以访问
  u1.setAge(20);     // 可以访问
  u1.id;            // error,不可以访问
  u1.setPw("xxx");  // error,不可以访问

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
# struct和class区别

在C++中 struct和class唯一的区别就在于 默认的访问权限不同

区别:

  • struct 默认权限为公共
  • class 默认权限为私有
# 成员属性设置为私有

优点1: 将所有成员属性设置为私有,可以自己控制读写权限

优点2: 对于写权限,我们可以检测数据的有效性

# 对象的初始化和清理
  • 生活中我们买的电子产品都基本会有出厂设置,在某一天我们不用时候也会删除一些自己信息数据保证安全
  • C++中的面向对象来源于生活,每个对象也都会有初始设置以及 对象销毁前的清理数据的设置。
# 构造函数和析构函数

对象的初始化和清理也是两个非常重要的安全问题

​一个对象或者变量没有初始状态,对其使用后果是未知

​同样的使用完一个对象或变量,没有及时清理,也会造成一定的安全问题

C++利用了构造函数析构函数解决上述问题,这两个函数将会被编译器自动调用,完成对象初始化和清理工作。

对象的初始化和清理工作是编译器强制要我们做的事情,因此如果我们不提供构造和析构,编译器会提供

编译器提供的构造函数和析构函数是空实现。

  • 构造函数:主要作用在于创建对象时为对象的成员属性赋值,构造函数由编译器自动调用,无须手动调用。
  • 析构函数:主要作用在于对象销毁前系统自动调用,执行一些清理工作。

构造函数语法:类名(){}

  1. 构造函数,没有返回值也不写void
  2. 函数名称与类名相同
  3. 构造函数可以有参数,因此可以发生重载
  4. 程序在调用对象时候会自动调用构造,无须手动调用,而且只会调用一次

析构函数语法: ~类名(){}

  1. 析构函数,没有返回值也不写void
  2. 函数名称与类名相同,在名称前加上符号 ~
  3. 析构函数不可以有参数,因此不可以发生重载
  4. 程序在对象销毁前会自动调用析构,无须手动调用,而且只会调用一次

➰ 示例:

  class User {
  public:
    int age;
    int sex;
    string username;
    // 构造函数
    User(int a, int s, string uname) { // 可以带参数
      age = a;
      sex = s;
      username = uname;
    }
    // 析构函数
    ~User() {
      cout << "Person的析构函数调用" << endl;
    }
  }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 构造函数的分类及调用

两种分类方式:

  • 按参数分为: 有参构造和无参构造
  • 按类型分为: 普通构造和拷贝构造

三种调用方式:

  • 括号法
  • 显示法
  • 隐式转换法

注意:

  • 调用无参构造函数不能加括号,如果加了编译器认为这是一个函数声明
  • 不能利用 拷贝构造函数 初始化匿名对象 编译器认为是对象声明

➰ 示例:

  class User {
  public:
    int age;
    int sex;
    string username;
    // 无参构造函数
    User () {

    }
    // 有参构造函数
    User (int a, int s, string uname) {
      age = a;
      sex = s;
      username = uname;
    }
    //拷贝构造函数
    User(const User& p) {
      age = p.age;
    }
  }
  ...
  // 括号法
  User u1(15, 0, "张三");
  // 显式法
  User u2 = User(15, 0, "张三");
  // 隐式法
  User u3 = u2;

  //Person(10)单独写就是匿名对象  当前行结束之后,马上析构
  //Person p5(p4); // Warning
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# 拷贝构造函数调用时机

C++中拷贝构造函数调用时机通常有三种情况

  • 使用一个已经创建完毕的对象来初始化一个新对象
  • 值传递的方式给函数参数传值
  • 以值方式返回局部对象