C++面向对象的三大特性为:封装继承多态
C++认为万事万物都皆为对象,对象上有其属性和行为

一、封装

1.1封装的意义

封装是C++面向对象三大特性之一
封装的意义:

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

封装意义一:
在设计类的时候,属性和行为写在一起,表现事物
语法:**class 类名{ 访问权限: 属性 / 行为 }**

示例 1:封装类的属性、行为、访问权限

设计一个圆类,求圆的周长

提示:
1、圆的周长的公式:2PI半径

  1. #include <iostream>
  2. using namespace std;
  3. static double PI=3.1415926;
  4. class Circle
  5. {
  6. public: //访问权限
  7. int m_r; //属性//半径
  8. double calculateZC() //行为
  9. {
  10. return 2*PI*m_r;
  11. }
  12. };
  13. int main()
  14. {
  15. Circle C1;
  16. C1.m_r=10;
  17. cout<<"圆的周长为:"<<C1.calculateZC()<<endl;
  18. return 0;
  19. }

结果:
image.png

示例2:设计一个学生类,属性有姓名和学号,可以给姓名和学号赋值,可以生的姓名和学号

#include <iostream>
using namespace std;
class student
{

public:
    string name;
    int id;
    void Showstudent()
    {
        cout << "学生的名字:  " << name << "  学生的学号:  " << id << endl;
    }
};
int main()
{
    student stu;
    stu.name = "张三";
    stu.id = 202101;
    stu.Showstudent();
    return 0;
}

结果:
image.png

示例3:通过行为将上面的示例对类内的属性进行赋值

#include <iostream>
using namespace std;
class student
{

public:
    string name;
    int id;
    void Showstudent()
    {
        cout << "学生的名字:  " << name << "  学生的学号:  " << id<<endl;
    }
    void setName(string s_name)
    {
          name= s_name;
    }
    void setID(int s_id)
    {
          id=s_id ;
    }
};
int main()
{
    student stu;
    stu.setID(202102);
    stu.setName("李四");
    stu.Showstudent();
    return 0;
}

结果:
image.png
注意:我们将类中的属性和行为都称为成员

  • 属性:成员属性、成员变量
  • 行为:成员函数、成员方法

    1.2、封装-访问权限

    封装意义二:

    类在设计时,可以把属性和行为放在不同的权限下,加以控制
    访问权限有三种:

  • public 公共权限 类内可以访问 类外可以访问

  • protected 保护权限 类内可以访问 类外不可以访问 (在继承中,儿子可以防问保护权限)
  • private 私有权限 类内可以访问 类外不可以访问(在继承中,儿子不可以防问私有权限)

    示例:

    ```cpp

    include

    using namespace std; class Person { public: //公共权限 string M_name; protected: //保护权限 string M_car; private: //私有权限 int M_password; void func() {
      M_name = "张三";
      M_car = "奔驰";
      M_password = 123456;
    
    }

}; int main() { Person P1; P1.M_name = “李四”; P1.M_car = “劳斯莱斯”; P1.M_password=123123 return 0; }

**结果:**<br />**![image.png](https://cdn.nlark.com/yuque/0/2021/png/2352229/1614330249502-009e8441-6baf-40a3-8cdc-477b8ec2d15e.png#align=left&display=inline&height=411&margin=%5Bobject%20Object%5D&name=image.png&originHeight=505&originWidth=574&size=30509&status=done&style=none&width=467)**<br />**注意:**类外无法访问:protected和 private权限的内容。
<a name="SbVDh"></a>
## 1.3、struct和class的区别
在C++中struct和class唯一的区别就在于**默认的访问权限不同** <br />**区别:**

- struct默认权限为public
- class默认权限为private
```cpp
struct Person
{
    string M_name;      //默认为public
};
class Person
{
    string M_name;      //默认为private
}

1.4、成员属性设置为私有

优点1:将所有成员属性设置为私有,可以自己控制读写权限
优点2:对于写权限,我们可以检测数据的有效性

示例1:

#include <iostream>
using namespace std;
class Student
{
public:
    void Print_Student()
    {
        cout << "学生姓名:" << M_name <<   "  学生ID:" << M_id << "  学生年龄:" <<M_age << endl;
    }
    void setName(string name)
    {
        M_name = name;
    }
    void setID(string id)
    {
        M_id = id;
    }
    void setAge(int age)
    {
        M_age = age;
    }
private:
    string M_name;
    string M_id;
    int M_age;
};
int main()
{
    Student stu1;
    stu1.setAge(22);
    stu1.setID("20210226");
    stu1.setName("张三");
    stu1.Print_Student();
    return 0;
}

结果:
image.png

示例2:设置程序可读写

#include <iostream>
using namespace std;
class Student
{
public:
    void setName(string name)       //设置姓名    姓名可写
    {
        M_name = name;
    }
    string getName()                //获取姓名    姓名可读
    {
        return M_name;
    }
    void setID(string id)           //设置ID       ID可写
    {
        M_id = id;
    }
    string getID()                  //获取ID       ID可读
    {
        return M_id;
    }
    void setAge(int age)            //设置年龄      年龄仅可写不可读
    {
        M_age = age;
    }
private:
    string M_name;
    string M_id;
    int M_age;
};
int main()
{
    Student stu1;
    stu1.setAge(22);
    stu1.setID("20210226");
    stu1.setName("张三");
    cout <<"学生姓名:"<<stu1.getName() <<"  学生ID:"<< stu1.getID() << endl;
    return 0;
}

练习案例1:点和圆的关系

设计一个圆形类(Circle),和一个点类(Point),计算点和圆的关系,点是在圆上,还是圆内,或是在圆外。
提示:
设圆半径的长度为m_R,则:

  • 如果点到圆心的距离=m_R 则点在圆上
  • 如果点到圆心的距离>m_R 则点在圆外
  • 如果点到圆心的距离<m_R 则点在圆内

求点和圆心的距离公式:(X1-X2)(X1-X2)+(Y1-Y2)(Y1-Y2)和m_R*m_R进行对比即可
image.png

#include <iostream>
using namespace std;
class Point               //构建点类
{
public:
    void Set_P_x(int x)   //设置点的x坐标
    {
        P_x = x;
    }
    int Get_P_x()         //获取点的x坐标
    {
        return P_x;
    }
    void Set_P_y(int y)   //设置点的y坐标
    {
        P_y = y;
    }
    int Get_P_y()         //获取点的y坐标
    {
        return P_y;
    }
private:
    int P_x;
    int P_y;
};
class Circle                        //构建圆类
{
public:
    void Set_C_r(int r)             //设置半径
    {
        C_r = r;
    }
    int Get_C_r()                   //获取半径
    {
        return C_r;
    }
    void Set_Center(Point Center)   //设置圆心
    {
        C_center = Center;
    }
    Point Get_Center()              //获取圆心
    {
        return C_center;
    }

private:
    Point C_center;                 //构建圆心的位置的对象,因为圆心也是个点,所以这里直接用了点的类,构建了对象
    int C_r; 
};
void PinC(Circle& c, Point& p)     //计算位置,确定点和圆的关系,这里用了引用,形参会改变实参
{                                  //这里使用了提示中计算点到圆心距离的公式
    int ref = (c.Get_Center().Get_P_x() - p.Get_P_x()) * (c.Get_Center().Get_P_x() - p.Get_P_x()) + (c.Get_Center().Get_P_y() - p.Get_P_y()) * (c.Get_Center().Get_P_y() - p.Get_P_y());
    if (ref == c.Get_C_r()* c.Get_C_r())
    {
        cout << "点在圆上!" << endl;
    }
    else if (ref < c.Get_C_r() * c.Get_C_r())
    {
        cout << "点在圆内!" << endl;
    }
    else
    {
        cout << "点在圆外!" << endl;
    }
}
int main()
{
    Circle circle;            //构建圆类对象
    Point point;              //构建点类对象
    Point center;             //构建圆心点类对象
    center.Set_P_x(10);       //设置圆心X坐标
    center.Set_P_y(0);        //设置圆心Y坐标
    circle.Set_C_r(5);        //设置圆的半径为10
    circle.Set_Center(center); //设置圆心的位置
    point.Set_P_x(10);         //设置点X坐标的位置
    point.Set_P_y(10);         //设置点Y坐标的位置
    PinC(circle, point);       //计算位置
    return 0;
}

结果:
image.png

1.5、对象的初始化和清理

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

    1.5.1、构造函数和析构函数

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

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

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

C++利用了构造函数和析构函数解决上述问题,这两个函数将会被编译器自动调用,完成对象初始化和清理工作。对象的初始化和清理工作是强制要我们做的事情,因此如果我们不提供构造和析构,编译器会提供提供 的构造函数和析构函数是空实现。

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

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

  • 构造函数,没有返回值也不写void

  • 函数名称与类名相同
  • 构造函数可以有参数,因此可以发生重载
  • 程序在调用对象时候会自动调用构造,无须手动调用,而且只会调用一次

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

  • 析构函数,没有返回值也不写void

  • 函数名称与类名相同,在名称前加符号~
  • 析构函数不可以有参数,因此不可以发生重载
  • 程序在对象销毁前会自动调用析构,无须手动调用,而且只会调用一次

    示例:

    #include <iostream>
    using namespace std;
    class Person
    {
    public:
      //建立Person的构造函数
      //构造函数的名称与类名相同
      //构造函数没有返回值也不需要写void
      //构造函数可以有参数,因此可以发生重载
      //构造函数会在程序执行前由系统自动调用一次
      Person() 
      {
          cout << "Person 的构造函数的调用" << endl;
      }
      //建立Person的析构函数
      //析构函数的名称与类名相同,前面加符号~
      //析构函数不可以有参数,因此不能发生重载
      //析构函数会在程序销毁前由系统自动调用一次
      ~Person()
      {
          cout << "Person 的析构函数的调用"<<endl;
      }
    };
    void test()
    {
      Person p;     //注意这里,我并没有调用对象P,但是构造和析构自动调用了
    }
    int main()
    {
      test();
      system("pause");
      return 0;
    }
    

    结果:
    image.png
    如果我将上面的程序改动一下,如下

    #include <iostream>
    using namespace std;
    class Person
    {
    public:
      //建立Person的构造函数
      //构造函数的名称与类名相同
      //构造函数没有返回值也不需要写void
      //构造函数可以有参数,因此可以发生重载
      //构造函数会在程序执行前由系统自动调用一次
      Person() 
      {
          cout << "Person 的构造函数的调用" << endl;
      }
      //建立Person的析构函数
      //析构函数的名称与类名相同,前面加符号~
      //析构函数不可以有参数,因此不能发生重载
      //析构函数会在程序销毁前由系统自动调用一次
      ~Person()
      {
          cout << "Person 的析构函数的调用"<<endl;
      }
    };
    int main()
    {
      Person p;                //注意,改动在这里,我去掉了函数test(),直接创建对象P
      system("pause");
      return 0;
    }
    

    结果:
    image.png
    思考一下为什么?为什么析构函数没有被调用呢?
    首先我们再来看一下析构函数的定义,是在程序销毁前自动调用一次,但是上面的程序有一个system(“pause”);所以程序是卡在了“请按任意键继续”,等咱们按完了任意键后,程序才是销毁前,所以这个析构函数会在按完任意键后被执行。
    结果:
    image.png

    1.5.2构造函数的分类以及调用

    两种分类方式:

  • 按参数分为:有参构造函数和无参构造函数

  • 按类型分为:普通构造函数和拷贝构造函数

三种调用方式:

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

    示例1:括号法

    #include <iostream>
    using namespace std;
    class Person
    {
    public:
               //构造函数的分类
               //无参构造函数(默认构造)
      Person() 
      {
          cout << "Person 的无参构造函数的调用" << endl;
      }
      //有参构造函数
      Person(int a)
      {
          age = a;
          cout << "Person 的有参构造函数的调用" << endl;
      }
    
      //拷贝构造函数
      Person(const Person &p)
      {
          age = p.age;      //将传入人身上的属性,拷贝到我(当前对象)身上
          cout << "Person 的拷贝构造函数的调用" << endl;
      }
      ~Person()
      {
          cout << "Person 的析构函数的调用"<<endl;
      }
      int age;
    };
    void test()
    {
      Person p;   //无参构造函数(默认构造函数)的调用
      Person p1(10);   //有参构造函数的调用                   括号法
      Person p2(p1);   //拷贝构造函数的高用                   括号法
      cout << "p1的年龄为:" << p1.age << endl;
      cout << "p2的年龄为:" << p2.age << endl;
      return;
    }
    void test1()
    {
    
      return;
    }
    int main()
    {
      test();
      system("pause");
      return 0;
    }
    

    结果:
    image.png
    注意事项:
    调用无参构造函数的时候不要有(),例如Person p();这段代码编译器会认为是函数的声明

示例2:显示法

#include <iostream>
using namespace std;
class Person
{
public:
             //构造函数的分类
             //无参构造函数(默认构造)
    Person() 
    {
        cout << "Person 的无参构造函数的调用" << endl;
    }
    //有参构造函数
    Person(int a)
    {
        age = a;
        cout << "Person 的有参构造函数的调用" << endl;
    }

    //拷贝构造函数
    Person(const Person &p)
    {
        age = p.age;      //将传入人身上的属性,拷贝到我(当前对象)身上
        cout << "Person 的拷贝构造函数的调用" << endl;
    }
    ~Person()
    {
        cout << "Person 的析构函数的调用"<<endl;
    }
    int age;
};
void test()
{

    Person p1=Person(10);    //有参构造函数的调用           显示法
                             //这里的Person(10)叫匿名对象,特点是执行完当前行,马上析构
                             //不要用拷贝构造函数来初始化一个匿名对象,例如:Person(p3)
    Person p2=Person(p1);    //拷贝构造函数的高用           显示法
    cout << "p1的年龄为:" << p1.age << endl;
    cout << "p2的年龄为:" << p2.age << endl;
    return;
}
int main()
{
    test();
    system("pause");
    return 0;
}

结果:
image.png

示例3:隐式转换法

#include <iostream>
using namespace std;
class Person
{
public:
             //构造函数的分类
             //无参构造函数(默认构造)
    Person() 
    {
        cout << "Person 的无参构造函数的调用" << endl;
    }
    //有参构造函数
    Person(int a)
    {
        age = a;
        cout << "Person 的有参构造函数的调用" << endl;
    }

    //拷贝构造函数
    Person(const Person &p)
    {
        age = p.age;      //将传入人身上的属性,拷贝到我(当前对象)身上
        cout << "Person 的拷贝构造函数的调用" << endl;
    }
    ~Person()
    {
        cout << "Person 的析构函数的调用"<<endl;
    }
    int age;
};
void test()
{
    Person p1 = 10;      //隐式转换法    相当于写了Person p1(10)
    Person p2 = p1;      //隐式转换法    相当于写了Person p2(p1)
}
int main()
{
    test();
    system("pause");
    return 0;
}

结果:
image.png

1.5.3、拷贝构造函数调用时机

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

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

    示例1:使用一个已经创建完毕的对象来初始化一个新对象

    #include <iostream>
    using namespace std;
    class Person
    {
    public:
               //构造函数的分类
               //无参构造函数(默认构造)
      Person() 
      {
          cout << "Person 的无参构造函数的调用" << endl;
      }
      //有参构造函数
      Person(int a)
      {
          age = a;
          cout << "Person 的有参构造函数的调用" << endl;
      }
      //拷贝构造构数
      Person(const Person& p) 
      {
          age = p.age;
          cout << "Person 的拷贝构造函数的调用" << endl;
      }
      ~Person()
      {
          cout << "Person 的析构函数的调用"<<endl;
      }
      int age;
    };
    void test()
    {
      Person p1(20);
      Person p2(p1);
      cout << "p2的年龄:" << p2.age<<endl;
    }
    int main()
    {
      test();
      system("pause");
      return 0;
    }
    

    结果:

    image.png

    示例2:值传递的方式给函数参数传值

    ```cpp

    include

    using namespace std; class Person { public:
           //构造函数的分类
           //无参构造函数(默认构造)
    
    Person() {
      cout << "Person 的无参构造函数的调用" << endl;
    
    } //有参构造函数 Person(int a) {
      age = a;
      cout << "Person 的有参构造函数的调用" << endl;
    
    } //拷贝构造构数 Person(const Person& p) {
      age = p.age;
      cout << "Person 的拷贝构造函数的调用" << endl;
    
    } ~Person() {
      cout << "Person 的析构函数的调用"<<endl;
    
    } int age; };

void dowork(Person p) { return; } void test() { Person p1; dowork(p1); } int main() { test(); system(“pause”); return 0; }

<a name="nNO5J"></a>
#### 结果:
![image.png](https://cdn.nlark.com/yuque/0/2021/png/2352229/1614482809811-d25b4445-b16c-4b52-9de2-8ddebf13468e.png#align=left&display=inline&height=119&margin=%5Bobject%20Object%5D&name=image.png&originHeight=119&originWidth=272&size=10954&status=done&style=none&width=272)
<a name="QzBXB"></a>
#### 示例3:以值方式返回局部对象
```cpp
#include <iostream>
using namespace std;
class Person
{
public:
             //构造函数的分类
             //无参构造函数(默认构造)
    Person() 
    {
        cout << "Person 的无参构造函数的调用" << endl;
    }
    //有参构造函数
    Person(int a)
    {
        age = a;
        cout << "Person 的有参构造函数的调用" << endl;
    }
    //拷贝构造构数
    Person(const Person& p) 
    {
        age = p.age;
        cout << "Person 的拷贝构造函数的调用" << endl;
    }
    ~Person()
    {
        cout << "Person 的析构函数的调用"<<endl;
    }
    int age;
};

Person dowork()
{
    Person p1;
    return p1;
}
void test()
{
    Person p=dowork();      //这行代码相当于  Person p(p1)  也是上面讲的显示法调用拷贝函数  Person p= person(p1);
    return;
}
int main()
{
    test();
    system("pause");
    return 0;
}

结果:

image.png

1.5.4、构造函数的调用规则

默认情况下,C++编译器至少给一个类添加3个函数

  • 默认构造函数(无参,函数体为空)
  • 默认析构函数(无参,函数体为空)
  • 默认拷贝构造函数,对属性进行值拷贝

构造函数调用规则如下:

  • 如果用户定义有参构造函数,C++不再提供默认无参构造,但会提供默认拷贝构造
  • 如果用户定义拷贝构造函数,C++不会再提供其它构造函数

    示例:如果我们自己写了一个拷贝构造函数,那么系统将不再提供有参和无参构造

    #include <iostream>
    using namespace std;
    class Person
    {
    public:
      //这里只写了拷贝构造构数
      Person(const Person& p) 
      {
          age = p.age;
          cout << "Person 的拷贝构造函数的调用" << endl;
      }
      ~Person()
      {
          cout << "Person 的析构函数的调用"<<endl;
      }
      int age;
    };
    void test()
    {
      Person p1;
      Person p2(10);
      Person p3(p1);
    }
    int main()
    {
      test();
      system("pause");
      return 0;
    }
    

    结果:
    image.pngimage.png
    这里就印证了如果自己写了一个拷贝构造函数,系统就不再提供有参和无参的构造构说,另外Person P实际上也是创建对象的操作,如果你只写了拷贝构造函数,那就说明你只能用 Person p(p1)这样的方式来创建对象了。

    1.5.5、深拷贝和浅拷贝

    深浅拷贝是面试经典问题,也是常见的一个坑

  • 浅拷贝:简单的赋值拷贝操作

  • 深拷贝:在堆区重新申请空间,进行拷贝操作

    示例 1:浅拷贝

    #include <iostream>
    using namespace std;
    class Person
    {
    public:
      Person()
      {
          cout << "Person 的无参构造函数的调用" << endl;
      }
      Person(int age)
      {
          cout << "Person 的有参构造函数的调用" << endl;
          m_age = age;
      }
    
      ~Person()
      {
          cout << "Person 的析构函数的调用"<<endl;
      }
      int m_age;
    };
    void test()
    {
      Person p1(18);
      cout << "p1的年龄:" << p1.m_age << endl;
      Person p2(p1);      //浅拷贝
      cout << "p2的年龄:" << p2.m_age << endl;
    }
    int main()
    {
      test();
      system("pause");
      return 0;
    }
    

    结果:
    image.png

    示例2:浅拷贝存在的问题:堆区的内存重复释放

    #include <iostream>
    using namespace std;
    class Person
    {
    public:
      Person()
      {
          cout << "Person 的无参构造函数的调用" << endl;
      }
      Person(int age,int height)
      {
          cout << "Person 的有参构造函数的调用" << endl;
          m_age = age;
          m_height = new int(height);
      }
    
      ~Person()
      {
          if (m_height != NULL)      //如果m_height指针不为空,则清空内存地址  这段代码主要是将在栈开辟的内存空间释放
          {
              delete m_height;
              m_height = NULL;       //防止野指针,为m_height设置为NULL
          }
          cout << "Person 的析构函数的调用"<<endl;
      }
      int m_age;
      int* m_height;
    };
    void test()
    {
      Person p1(18,160);
      cout << "p1的年龄:" << p1.m_age << "身高为:"<< *p1.m_height<<endl;
      Person p2(p1);      //浅拷贝
      cout << "p21的年龄:" << p2.m_age << "身高为:" << *p2.m_height << endl;
    }
    int main()
    {
      test();
      system("pause");
      return 0;
    }
    

    结果:报错,程序运行不起来
    image.png
    所以,浅拷贝带来的问题要用深拷贝来解决

    示例3:深拷贝

    #include <iostream>
    using namespace std;
    class Person
    {
    public:
      Person()
      {
          cout << "Person 的无参构造函数的调用" << endl;
      }
      Person(int age,int height)
      {
          cout << "Person 的有参构造函数的调用" << endl;
          m_age = age;
          m_height = new int(height);
      }
      Person(const Person& p)//自己写个拷贝构造函数来解决浅拷贝带来的问题    深拷贝
      {
          cout << "Person 拷贝构造函数的调用" << endl;
          m_age = p.m_age;
          //m_height = p.m_height;      //这个是由编译器默认生成的就是这行代码,存在问题
          m_height = new int(*p.m_height);  //这个是自己编写的深拷贝代码,这样的目的是让系统再次在栈区开启一块新的内存空间,这样就不存在重复释放的问题
      }
      ~Person()
      {
          if (m_height != NULL)      //如果m_height指针不为空,则清空内存地址  这段代码主要是将在栈开辟的内存空间释放
          {
              delete m_height;
              m_height = NULL;       //防止野指针,为m_height设置为NULL
          }
          cout << "Person 的析构函数的调用"<<endl;
      }
      int m_age;
      int* m_height;
    };
    void test()
    {
      Person p1(18,160);
      cout << "p1的年龄:" << p1.m_age << "身高为:"<< *p1.m_height<<endl;
      Person p2(p1);      //浅拷贝
      cout << "p21的年龄:" << p2.m_age << "身高为:" << *p2.m_height << endl;
    }
    int main()
    {
      test();
      system("pause");
      return 0;
    }
    

    结果:
    image.png

    1.5.6、初始化列表

    作用:
    C++提供了初始化列表语法,用来初始化属性
    语法:构造函数():属性(值 1),属性2(值 2)……..{}
    这个是构造函数的另外一种初始化列表的方式

    示例1:传统的利用构造函数的初始化操作

    #include <iostream>
    using namespace std;
    class Person
    {
    public:
      //传统的初始化操作,用构造函数来进行初始化
      Person(int a,int b,int c)
      {
          m_A = a;
          m_B = b;
          m_C = c;
      }
      int m_A;
      int m_B;
      int m_C;
    };
    void test()
    {
      Person p(10,20,30);
      cout << "a=" << p.m_A << "  b=  " << p.m_B << "  c=  " << p.m_C << endl;
    }
    int main()
    {
      test();
      system("pause");
      return 0;
    }
    

    结果:
    image.png
    示例 2:利用初始化列表对成员进行初始化操作

    #include <iostream>
    using namespace std;
    class Person
    {
    public:
      //利用初始化列表进行初始化成员属性
      Person() :m_A(10), m_B(20), m_C(30)
      {
    
      }
      int m_A;
      int m_B;
      int m_C;
    };
    void test()
    {
      Person p;
      cout << "a=" << p.m_A << "  b=  " << p.m_B << "  c=  " << p.m_C << endl;
    }
    int main()
    {
      test();
      system("pause");
      return 0;
    }
    

    结果:
    image.png
    示例 3:利用初始化列表对成员进行初始化操作的第二种方式

    #include <iostream>
    using namespace std;
    class Person
    {
    public:
      //利用初始化列表进行初始化成员属性
      Person(int a,int b,int c) :m_A(a), m_B(b), m_C(c)
      {
    
      }
      int m_A;
      int m_B;
      int m_C;
    };
    void test()
    {
      Person p(30,20,10);
      cout << "a=" << p.m_A << "  b=  " << p.m_B << "  c=  " << p.m_C << endl;
    }
    int main()
    {
      test();
      system("pause");
      return 0;
    }
    

    结果:
    image.png

    1.5.7、类对象作为另一个类的成员

    C++类中的成员可以是另一个类的对象,我们称该成员为对象成员
    例如:

    class A {};
    class B
    {
      A a;
    }
    

    B类中有对象A作为成赠,A为对象成员
    那么当创建对象时,A与B的构造和析构的顺序是谁先谁后?

    示例1:构造和析构的顺序

    #include <iostream>
    using namespace std;
    class A
    {
    public:
      A()
      {
          cout << "A 的构造函数的调用" << endl;
    
      }
      ~A()
      {
          cout << "A 的析构函数的调用" << endl;
      }
    };
    class B
    {
    public:
      B()
      {
          cout << "B 的构造函数的调用" << endl;
      }
      A a;
      ~B()
      {
          cout << "B 的析构函数的调用" << endl;
      }
    };
    void test()
    {
      B B;
    }
    int main()
    {
      test();
      system("pause");
      return 0;
    }
    

    结果:
    image.png

    示例2:

    #include <iostream>
    using namespace std;
    class Phone       //手机类
    {
    public:
      Phone(string p)
      {
          Pname = p;
      }
      //手机品牌
      string Pname;
    };
    class Person      //人类
    {
    public:
      Person(string name, string Pname) :P_name(name), P_phone(Pname)
      {
      }
      //姓名
      string P_name;
      //手机
      Phone P_phone;
    };
    void test()
    {
      Phone p("三星");
      Person P1("张三", "三星");
      cout << "手机:" << p.Pname << endl;
      cout << P1.P_name << "拿着一部" << P1.P_phone.Pname <<endl;
    }
    int main()
    {
      test();
      system("pause");
      return 0;
    }
    

    结果:
    image.png

    1.5.8、静态成员

    静态成员就是在成员变量和成员函数前加上关键字static,称为静态成员

  • 静态成员分为为:

  1. 静态成员变量
  2. 所有对象共享同一份数据
  3. 在编译阶段分配内存
  4. 类内声明,类外初始化
  • 静态成员函数
  1. 所有对象共享一同一个函数
  2. 静态成员函数只能访问静态成员变量

    示例1:静态成员变量

    #include <iostream>
    using namespace std;
    class Person
    {
    public:
     static int m_A;        //静态成员变量类内声明
    };
    int Person::m_A=100;       //类外初始化
    void test()
    {
     Person p;
     p.m_A = 200;           //静态成员变量不属于某个对象上,所有对象都共享一份数据  ,这里改成200,如果有另外一个对象,那么m_A=200
     cout << p.m_A << endl;
    }
    int main()
    {
     test();
    }
    
    结果:
    image.png
    静态成员变量的两种访问方式:

    示例1.1:通过对象访问

    #include <iostream>
    using namespace std;
    class Person
    {
    public:
     static int m_A;        //静态成员变量类内声明
    };
    int Person::m_A=100;       //类外初始化
    void test()
    {
     Person p;
     cout << p.m_A << endl;
    }
    int main()
    {
     test();
    }
    
    结果:
    image.png

    示例1.2:通过类名访问

    #include <iostream>
    using namespace std;
    class Person
    {
    public:
     static int m_A;        //静态成员变量类内声明
    };
    int Person::m_A=100;       //类外初始化
    void test()
    {
     cout<<Person::m_A<<endl;
    }
    int main()
    {
     test();
    }
    
    结果:
    image.png

    示例2:静态成员函数

    #include <iostream>
    using namespace std;
    class Person
    {
    public:
     static void func()        //静态成员函数
     {
         cout << "静态成员函数的调用!" << endl;
     }
    };
    void test()
    {
     Person p;  //通过对象调用静态成员函数
     p.func();  //通过类名调用静态成员函灵敏
     Person::func();
    }
    int main()
    {
     test();
     system("pause");
    }
    
    结果:
    image.png
    另外需要注意的是,静态成员函数只能调用静态成员变量,不能调用类内的普通成员变量。
    示例2.1:
    #include <iostream>
    using namespace std;
    class Person
    {
    public:
     static void func()        //静态成员函数
     {
         A = 100;              //静态成员函数只能访问静态成员变量,不能够访问类内普通的成员变量
         B = 200;              //这里调用了非静态成员变量,是错误的用法;
         cout << "静态成员函数的调用!" << endl;
     }
     static int A;
     int B;
    }; 
    int Person ::A = 0;
    void test()
    {
     Person p;  //通过对象访问静态成员函数
     p.func();
     Person::func();
    }
    int main()
    {
     test();
     system("pause");
    }
    

    2、C++对象模型和this指针

    2.1、成变变量和成员函数分开存储

  • 在C++中,类内的成员变量和成员函数分开存储
  • 只有非静态成员变量才属于类的对象上
  • 静态成员函数、静态成员变量和非静态成员函数都不属于类的对象上

    2.2、this指针

    通过2.1我们知道C++中成员变量和成员函数是分开存储的
    每一个非静态成员函数只会诞生一份函数实例,也就是说多个同类型的对象会共用一块代码
    那么问题是:这一块代码是如何区分哪个对象在调用自己呢?
    C++通过提供特殊的对象指针,this指针,解决上述问题,this指针指向被调用的成员函数所属的对象.
    this指针是隐含每一个非静态成员函数内的一种指针
    this指针不需要定义 ,直接使用即可

this 指针的用途:
当形参和成员变量同名时,可用this指针来区分
当类的非静态成员函数中返回对象本身,可使用retrun this
*示例:

#include <iostream>
using namespace std;
class Person
{
public:
    Person(int age)
    {
        this->age = age;        //this指针被谁调用就指向谁
    }
    int age;
}; 
void test()
{
    Person p1(10);             // 这里的this指针被p1调用,所以是指向p1的
    cout << p1.age << endl;
}
int main()
{
    test();
    system("pause");
}

结果:
image.png

3、空指针访问成员函数

C++中空指针也是可以调用成员函数的,但是也要注意有没有用到this指针
如果用到this指针,需要加以判断保证代码的健壮性

示例:

#include <iostream>
using namespace std;
class Person
{
public:

    void showage()
    {
        if (this == NULL)                    //为了防止空指针报错,这行代码增加了代码的健壮性,如果遇到空指针,程序直接结束
        {
            return;
        }

        cout << "年龄为:"<< this->age << endl;
    }
    int age;
}; 
void test()
{
    Person* p=NULL;                   //建立一个Person类的空指针
    p->showage();
}
int main()
{
    test();
    system("pause");
}

4、const修饰成员函数

常函数:

  • 成员函数后加const后我们称这个函数为常函数
  • 常函数内不可以修改成员属性
  • 成员属性声明时加关键排班字mutable后,在常函数中依然可以修改

    常对象:

  • 声明对象前加const称该对象为常对象

  • 常对象只能调用常函数

    4.1、常函数

    示例:

    #include <iostream>
    using namespace std;
    class Person
    {
    public:
    
      void A()
      {
          this->age = 100;   //这里的this其本质是一个指针常量
      }
      int age;
    }; 
    int main()
    {
      system("pause");
    }
    

    看一下这行代码,其中this->age = 100; 这行代码需要注意一点,这里的this相当于指针常量,也就是说指针的指向不可以改变,但是值是可以改变的,相当于Person * const this,那么常函数应该如何修饰呢,如下:

    public:
    
      void A() const         //在函数后边加了const即是常函数,其本质相当于const Person * const this
      {
          this->age = 100;   //const Person * const this 也就是说指针指向和里面的值都不可以改动,所以这段代码是错误的
      }
      int age;
    };
    

    在函数后边加了const修饰后就变成了常函数,相当于const Person * const this,也就是说指针指向和里面的值都不可以改动,所以this->age = 100;这行代码是错误的。
    那如果还是需要更改里面的值怎么办,那在成员变量前加一个mutable的关键字即可,如下:

    public:
      void A() const  
      {
          this->age = 100; 
      }
      mutable int age;  //成员变量前加关键字mutable
    };
    

    4.2、常对象

    ```cpp

    include

    using namespace std; class Person { public:

    void A() const {

      this->age = 200;   
      cout << this->age << endl;
    

    } mutable int age;

}; void test() {

const Person p;     //常对象
p.age = 100;
p.A();        //常对象只能调用常函数

} int main() { test(); system(“pause”); } ``` 定义常对象的方法:const Person p; 需要注意的是常对象只能调用常函数,具体原因是:因为如果不是常函数的话,函数内部是可以修改成员变量的值,这个与常函数的定义相违背。