【类与对象】封装&对象的初始化及清理

简介: 【类与对象】封装&对象的初始化及清理

C++面向对象的三大特性:封装、继承、多态。具有相同性质的对象,抽象为类。


1 封装

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


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

示例:设计一个圆类,求圆的周长

/*
  设计一个圆类,求圆的周长
    圆的周长公式: 2 * PI * 半径
*/
const double PI = 3.14;

class Circle
{
  //访问权限
public:

  //1、属性
  //半径
  int m_r;

  //2、行为
  //获取圆的周长
  double calculateZC()
  {
    return 2 * PI * m_r;
  }

};
int main()
{
  system("color 1E");
  //通过圆类,创建具体的圆c ==>>实例化(通过一个类实例化一个具体的对象)
  Circle c1;
  //给圆对象的属性赋值
  c1.m_r = 10;
  cout << "圆的周长:" << c1.calculateZC() << endl;
  system("pause");  
  return 0;
}

示例2:设计一个学生类,属性:姓名、学号。给学生姓名和学号赋值,然后显示出姓名和学号

class Student
{
public: //  访问权限 ->公共权限

  //1、属性 :成员属性,成员变量
  string s_name;
  int stuId;

  //2、行为 :成员函数 成员方法
  void showStudent()
  {
    cout << "学生的姓名:" << s_name << " 学号是:" << stuId << endl;
  }
  //给姓名赋值
  void setName(string name)
  {
    s_name = name;
  }
  //给学号赋值
  void setId(int id)
  {
    stuId = id;
  }
};
int main()
{
  system("color 1E");
  //通过学生类,创建具体那个学生 ==>>实例化(通过一个类实例化一个具体的对象)
  Student stu1;
  Student stu2;
  //给学生对象的属性赋值
  stu1.setName ("张三");
  stu1.setId( 21202201);
  stu1.showStudent();
  stu1.s_name = "李四";
  stu1.stuId = 21202202;
  stu1.showStudent();
  system("pause");  
  return 0;
}


1.2 封装的意义(二)

说明:类在设计时,可以把属性和行为放在不同的权限下,加以控制


访问权限三种:

  • public:公共权限
  • 成员 :类内可以访问,类外也可以访问
  • protected:保护权限
  • 成员: 类内可以访问,类外不可以访问
  • 儿子可以访问父亲的保护内容
  • private:私有权限
  • 成员:类内可以访问,类外不可以访问
  • 儿子不可以访问父亲的私有内容


class Person
{

public:
  string m_name;

protected:
  string m_Car;

private: 
  int m_Password;

public:
  void func()
  {
    m_name = "张三";
    m_Car = "保时捷";
    m_Password = 12345;
  }
};


1.3 struct 和 class区别

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


区别:

  • struct:默认权限为公共
  • class:默认权限为私有
class C1
{
  int m_a; //默认权限 私有
};

struct C2
{
  int m_a; //默认权限 公共
};
C1 c1;
//c1.m_a = 100; //报错
C2 c2;
c2.m_a = 100; //公共权限,类内类外都可以访问
1.4 成员属性设置为私有

优点:

  • 将所有成员属性设置为私有之后,自己可以控制读写权限
  • 对于写权限,检测数据的有效性
#include<iostream>
#include<algorithm>
#include<string>
using namespace std;

/*
  成员属性设置为私有
    1、可以自己控制读写权限 --某些属性的读写
    2、对于以检测数据的有效性  --对于年龄的特殊设置

*/
//设计人类
class Person 
{
public:
  //设置姓名
  void setName(string name)
  {
    m_Name = name;
  }
  //获取姓名
  string getName()
  {
    return m_Name;
  }
  int setAge(int age)
  {
    m_Age = 0;
    if (age < 0 || age>150)
    {
      cout << "你输入的年龄有误,默认设置为0." << endl;
      return m_Age;
    }
    m_Age = age;
  }
  int getAge()
  {
    return m_Age;
  }
  void setLower(string lover)
  {
    m_Lover = lover;
  }

private:
  //姓名   可读可写
  string m_Name;
  //年龄   只读
  int m_Age;
  //情人    只写
  string m_Lover;

};
int main()
{
  system("color 1E");
  Person p;
  p.setName("唐三");
  cout << "输入的姓名:" << p.getName()<<endl;
  //检测数据的有效性
  p.setAge(1000);
  cout << "输入的年龄:" << p.getAge()<<endl; //年龄只读
  p.setLower("小舞");  //情人只能可写
  return 0;
}


练习案例:
1 设计立方体类

求出立方体的面积和体积。分别用全局函数和成员函数判断两个立方体是否相等

#include<iostream>
#include<algorithm>
#include<string>
using namespace std;

/*
  立方体的类的设计
    1、创建立方体类
    2、设计属性
    3、设计行为 获取立方体面积和体积
    4、分别利用全局函数和成员函数  判断两个立方体是否相等

*/
class Cube
{

public:
  void setL(double l) {
    m_L = l;
  }
  void setW(double w) {
    m_W = w;
  }
  void setH(double h) {
    m_H = h;
  }
  double getL() {
    return m_L;
  }
  double getW() {
    return m_W;
  }
  double getH() {
    return m_H;
  }
  //获取立方体的面积
  double getArea()
  {
    double area;
    area = 2 * (m_H*m_L + m_H * m_W + m_L * m_W);
    return area;
  }
  //获取立方体的体积
  double getVolu()
  {
    double volume;
    volume = m_H * m_L  * m_W ;
    return volume;
  }
  //利用成员函数判断两个立方体是否相等
  bool isSameByClass(Cube &c)
  {
    if (m_L = c.m_L && m_H == c.m_H && m_W == c.m_W)
    {
      return true;
    }
    return false;
  }

private:
  double m_L, m_W, m_H;



};

//全局函数判断两个函数是否相等
bool isSameByClasss(Cube &c1, Cube &c2)
{
  if (c1.getL() == c2.getL() && c1.getH() == c2.getH() && c1.getW() == c2.getW())
  {
    return true;
  }
  return false;
}

int main()
{
  system("color 1E");
  Cube c,c1,c2;
  c.setL(1);
  c.setW(2);
  c.setH(3);
  c1.setL(2);
  c1.setW(2);
  c1.setH(2);
  c2.setL(2);
  c2.setW(2);
  c2.setH(3);
  cout << "立方体的面积:" << c.getArea() << endl;
  cout << "立方体的体积:" << c.getVolu() << endl;
  bool gouRet = isSameByClasss(c1, c2);
  if (gouRet == true)
  {
    cout << "全局函数的两个立方体是相等的" << endl;
  }
  else
    cout << "全局函数的两个立方体是不相等的" << endl;
  bool ret = c1.isSameByClass(c2);
  if (ret == true)
  {
    cout << "两个立方体是相等的" << endl;
  }
  else
    cout << "两个立方体是不相等的" << endl;
  return 0;
}


2 对象的初始化和清理

  • 生活中有些电子产品都有出厂设置
  • 每个对象初始设置以及对象销毁前的清理数据的设置


2.1 构造函数和析构函数

对象的初始化和清理是重要的安全问题


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

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

C++利用构造函数和析构函数解决上述问题,这两个函数会被编译器自动调用 ,完成对象初始化和清理工作。若是不提供构造和析构,编译器会提供构造函数和析构函数是空实现的(自己不写,有人写)


构造函数:主要作用于创建对象时为对象的成员属性赋值,构造函数由编译器自动调用,无需手动

析构函数:作用于对象销毁前自动调用,执行一些清理 工作。

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


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


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

  • 1 析构函数,没有返回值不写void
  • 2 函数名称与类名相同,在名称前面加上符合 ~
  • 3 析构函数不可以有参数,因此不能发送重载
  • 4 程序在对象销毁前会自动调用析构,无需手动调用,只调用一次
class Person
{
public:
  Person()
  {
    cout << "正在构造函数!! " << endl;
  }

  ~Person()
  {
    cout << "正在析构函数!! " << endl;
  } 
};
void test01() 
{
  Person p;
}
int main()
{
  test01();

  return 0;
}


2.2 构造函数的分类及调用

两种分类方式:

  • 按照参数分:有参构造和无参构造
Person()
  {
    cout << "无参的构造函数调用!! " << endl;
  }
  Person(int a)
  {
    age = a;
    cout << "有参的构造函数调用!! " << endl;
  }

按照类型分:普通构造和拷贝构造

//拷贝构造函数
  /*
    定义:若是有张三这个人,然后用李四去克隆张三这个人的属性
    不过张三本身属性不能改变,加上 const只读
  
  */
  Person(const Person &p)
  {
    age = p.age;
  }


三种调用方式:

  • 括号法(一般这个比较好 )
    注意:若是无参的函数构造不要写"p1()";这样会被认为是函数的声明
//1、括号法
  Person p1;
  //有参构造函数
  Person p2(10);
  cout << "p2的年龄为:" << p2.age << endl;
  //拷贝构造函数
  Person p3(p2);
  cout << "p3的年龄为:" << p3.age << endl;


  • 显示法
    注意:Person(10)若是在左侧,则是匿名对象;执行结束后,系统立马回收匿名对象函数
Person p1;
//有参构造函数
Person p2 = Person(10);
//拷贝构造函数
Person  p3 = Person(p2);


隐式转换法

Pensor p4=10; //相当于写了Person p4=Person(10) 有参构造
Pensor p5=p3; //拷贝构造
2.3 拷贝构造函数调用时机

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

  • 使用一个已经创建完毕的对象来初始化一个对象
  • 值传递的方式给函数参数传值
void doWork(Person p)
{
}
void test()
{
  Person p; //默认构造函数
    doWork(p);//拷贝构造函数,给函数的p,
}

以值的方式返回局部对象

Person doWork()
{
    Person p1;
    return p1; //返回p1的时候,也会产生一个拷贝构造函数。返回会重新生成一个跟p1一样的数据
}
void test()
{
    Person p = doWork();
}
2.4 构造函数调用规则

默认情况下,只要创建类,C++编译器至少给应该类添加3个函数

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

构造函数调用规则如下:

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


2.5 深拷贝与浅拷贝(面试提及)
  • 浅拷贝:简单的赋值拷贝(编译器做)-》会导致堆区的内存重复释放

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

class Person
{
public:
  int age;
  int * Height;
  //构造函数
  Person()
  {
    cout << "无参的构造函数调用!! " << endl;
  }
  Person(int a,int height)
  {
    age = a;
    //用new在堆区创建一个对象
    Height = new int(height);
    cout << "有参的构造函数调用!! " << endl;
  }
  //自己实现拷贝构造函数 解决浅拷贝带来的问题
  Person(const Person &p)
  {
    cout << "Person 拷贝构造函数调用" << endl;
    age = p.age;
    //Height = p.Height; 编译器默认实现这行代码

    //在堆区重新申请空间,进行深拷贝
    Height = new int(*p.Height);
  }
  //析构函数
  ~Person()
  {
    //释放堆区创建的内存
    if (Height != NULL)
    {
      delete Height;
      Height = NULL;
    }
    //作用:析构代码,将堆区开辟数据做释放
    cout << "正在析构函数!! " << endl;
  }
};
//调用
void test01()
{
  //p1走自己的析构,p2也是自己的析构
  Person p1(18, 180);
  cout << "p1的年龄为:" << p1.age << " 身高为:" << *p1.Height << endl; //*表示指针的实际化,输出的是一个值,不是地址
  Person p2(p1);
  cout << "p2的年龄为:" << p2.age << " 身高为:" << *p2.Height << endl;
}


说明:若是使用指针就会在堆区使用内存空间,p1和p2两个对象,然后两个对象指向同一块内存;delete是释放对象指向的内存,这里用浅拷贝会被释放两次。


2.6 初始化列表

说明:C++提供初始化列表语法,用来初始化属性

语法:构造类名():属性1(值1),属性2(值2) . . . { }

1)传统初始化列表:

class Person
{
public:
  int m_A;
  int m_B;
  int m_C;
  //传统列表赋初值
  Person(int a, int b, int c)
  {
    m_A = a;
    m_B = b;
    m_C = c;
  }
};
void test()
{
  Person p(10, 20, 30);
  cout << "m_A = " << p.m_A << endl;
  cout << "m_A = " << p.m_B << endl;
  cout << "m_A = " << p.m_C << endl;
}


改进之后的:

Person(int a,int b,int c):m_A(a),m_B(b),m_C(c)
{
}


2.7 类对象作为类成员



说明:C++类中的成员可以是另一个类的对象,称为该成员为对象成员。当其他类对象作为本类对象,构造的时候先构造其他类对象,再构造自身对象。析构的时候先释放自身内存 ,再释放其他对象内存。

//创建手机的类
class Phone
{
public:
  string m_Pname;
  Phone(string name)
  {
    m_Pname = name;
    cout << "正在调用手机类的构造函数" << endl;
  }

  ~Phone()
  {
    cout << "正在调用手机类的析构函数" << endl;
  }
};
class Person
{
public:

  string m_Name;
  Phone m_Phone;

  Person(string name, string phone):m_Name(name),m_Phone(phone)
  {
    cout << "正在调用Person类的构造函数" << endl;
  }

  ~Person()
  {
    cout << "正在调用Person类的析构函数" << endl;
  }
};
//调用
void test01()
{

  Person p("张三", "苹果14pro");
  cout << p.m_Name << "拿着:" << p.m_Phone.m_Pname << "手机" << endl;
   
}


2.8 静态成员

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


静态成员分为:

1)静态成员变量


所有对象共享一份数据(若是p1.a=100,若是p2也调用,p2.a=200,则p1.a=200;a是共享的)

在编译阶段分配内存

类内声明,类外初始化(在类外声明一个类内定义的属性,初始化:int Person::a=100),类外访问不到私有静态成员变量。

私有成员变量访问不到

class Person
{
public:
  static int m_Age;
};
int Person::m_Age = 100;

Person p;
//访问方式一:通过对象
cout << p.m_Age << endl; ->100
//访问方式二:通过类名
cout<<Person::m_Age<<endl; ->100
Person p1;
p1.m_Age = 200;
cout << p.m_Age << endl; ->200 //修改了


2)静态成员函数


  • 所有对象共享一个函数
  • 静态成员函数只能访问静态成员变量,非静态成员变量函数,无法区分到底是那个对象的属性;而静态成员变量所有人共享一份,不属于任何人。
  • 私有成员函数访问不到



相关文章
|
14天前
|
Java
【专栏】Java反射机制,该机制允许程序在运行时获取类信息、动态创建对象、调用方法和访问属性
【4月更文挑战第27天】本文探讨了Java反射机制,该机制允许程序在运行时获取类信息、动态创建对象、调用方法和访问属性。反射通过Class、Constructor、Method和Field类实现。文中列举了反射的应用场景,如动态创建对象、调用方法、访问属性和处理注解,并提供了相关实例代码演示。
|
4天前
|
设计模式 算法 编译器
【C++入门到精通】特殊类的设计 |只能在堆 ( 栈 ) 上创建对象的类 |禁止拷贝和继承的类 [ C++入门 ]
【C++入门到精通】特殊类的设计 |只能在堆 ( 栈 ) 上创建对象的类 |禁止拷贝和继承的类 [ C++入门 ]
9 0
|
1月前
new 一个对象的过程中发生了什么
new 一个对象的过程中发生了什么
12 2
|
3月前
|
安全 编译器 C++
C++类与对象【对象的初始化和清理】
C++类与对象【对象的初始化和清理】
C++类与对象【对象的初始化和清理】
|
4月前
|
存储 Java
Java 类与对象(对象的分配机制、对象的创建过程、匿名对象)
Java 类与对象(对象的分配机制、对象的创建过程、匿名对象)
15 0
|
7月前
|
Java 编译器
类 对象 封装
类 对象 封装
49 0
|
7月前
|
Java
对象的相等和引用相等的区别
对象的相等和引用相等的区别
|
11月前
|
程序员 编译器 数据安全/隐私保护
内存、引用、封装、函数
内存、引用、封装、函数
53 0
|
11月前
对象实例化错误
对象实例化错误
68 0
|
安全 编译器 数据安全/隐私保护
对象的动态创建和销毁以及对象的复制,赋值
🐰对象的动态创建和销毁 🐰对象的复制 🐰对象的赋值