C++的类和对象

目录

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

封装

构造函数和析构函数

构造函数的分类与调用

深拷贝与浅拷贝

类对象作为类成员

静态成员

成员变量和成员函数是分开存储的

const修饰成员函数

友元

运算符重载

继承

多态


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

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

具有相同性质的对象,可以抽象为类

封装

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

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

struct和class的区别在于

  • struct的默认权限是public
  • class的默认权限是private

成员属性一般设置为私有,好处是

  • 可以自己控制读写权限
  • 对于写,可以检测数据的有效性

构造函数和析构函数

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

构造函数:类名(参数) {}

  • 没有返回值,也不写void
  • 函数名称与类名相同
  • 构造函数可以有参数,可以重载
  • 程序在创建对象的时候会自动调用构造函数,而且只会调用一次

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

  • 没有返回值,也不写void
  • 在构造函数名字前面加上“~”
  • 不可以有参数,不会有重载的情况
  • 在对象销毁前自动调用,而且只会调用一次

构造函数的分类与调用

  • 按参数分为:有参构造、无参构造
  • 按类型分为:普通构造、拷贝构造(传入对象)
  • 三种调用方法:括号法、显示法、隐式转换法

括号法:

  • Person p1;      //默认构造函数调用
  • Person p2(10);      //有参构造函数
  • Person p3(p2);      //拷贝构造函数
  • 调用默认构造函数时,不要加(),若添加会被编译器认为是一个函数的声明

显示法:

  • Person p1;      //默认构造函数调用
  • Person p2 = Person(10);      //有参构造函数
  • Person p3 = Person(p2);      //拷贝构造函数
  • 形如Person(10)的对象,称为匿名对象,当前行执行后系统会自动回收

隐式转换法:

  • Person p4 = 10;     //有参构造函数
  • Person p5 = p4;      //拷贝构造函数

拷贝构造函数的调用时机通常有:

  • 使用一个已经创建好的对象来初始化一个新对象
  • 值传递的方式给函数参数传值(拿到传入的参数会进行拷贝)
  • 以值方式返回局部对象(局部函数return出来的其实是拷贝数据)

深拷贝与浅拷贝

  • 浅拷贝:简单的赋值拷贝操作
  • 深拷贝:在堆区重新申请空间,进行拷贝操作
  • 如果不利用深拷贝在堆区创建新内存,会导致浅拷贝带来的重复释放堆区的问题

类对象作为类成员

  • C++类中的成员可以是另一个类的对象,我们称该成员为对象成员
  • 当其他类对象作为本类成员,构造时先构造类对象,再构造自身
  • 析构时先析构自身,再析构类对象

静态成员

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

静态成员分为

静态成员变量

  • 所有对象共享一份数据
  • 在编译阶段分配内存
  • 类内声明,类外初始化
  • 可以直接通过类名访问

静态成员函数

  • 所有对象共享一个函数
  • 静态成员函数只能访问静态成员变量
  • 可以直接通过类名访问
  • 可以访问静态成员变量,不可以访问非静态成员变量

成员变量和成员函数是分开存储的

  • 空对象占用空间为:1
  • 只有非静态成员变量属于类的对象上
  • 其他成员不属于类的对象上

非静态成员函数如何区分哪个对象调用自己?通过this指针解决

this指针指向被调用的成员函数所属的对象,常见用途:

  • 当形参和成员变量同名,可用this指针来区分
  • 在类的非静态成员函数中返回对象本身,可使用return *this

const修饰成员函数

常函数

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

常对象

  • 声明对象前加上const,我们称其为常对象
  • 常对象只能调用常函数

友元

  • 友元的目的是让一个函数或者类访问另一个类中的私有成员
  • 友元的关键字为friend

友元函数的三种实现

  • 全局函数做友元
  • 类做友元
  • 成员函数做友元

运算符重载

  • 定义:对已有运算符重新定义,赋予另一种功能,以适应不同的数据类型
  • 成员函数重载,本质是p1.operator(p2)
  • 全局函数重载,本质是operator(p1,p2)
  • 左移运算符<< 只能利用全局运算符重载,配合友元可以实现输出自定义数据类型
ostream & operator<<(ostream& out,Person p){
    out << "a:" << p.m_a << "b:" << p.m_b ;
    return out;
}
  • 自增运算符++ 分为前置和后置,重载实现如下
    //重载前置++
	MyInteger& operator++() {
		m_num++;
		return *this;  
	}

	//重载后置++
	//int是占位参数,用于区分前置后置
	MyInteger& operator++(int) {
		MyInteger temp = *this;
		m_num++;
		return temp;
	}
  • 赋值运算符= 重载实现
Person& operator=(Person &p){
    //编译器提供的是浅拷贝,在析构时会出现问题
    //应该先判断是否有属性在堆区,如果有先释放再深拷贝
    if(m_Age != NULL){
        delete m_Age;
        m_Age = NULL;
    }

    //深拷贝
    m_Age = new int(*p.m_Age);

    //返回对象本身
    return *this;
}
  • 关系运算符重载
  • 函数调用运算符重载(仿函数,可以实现匿名对象调用)

继承

  • 好处:减少重复代码
  • 语法:class 子类 : 继承方式 父类
  • 子类也称派生类,父类也称基类

成员属性的继承

  • 父类中所有非静态成员属性都会被子类继承下去
  • 父类中私有成员属性是被编译器给隐藏了,因此访问不到,但确实是继承下去了的

构造与析构顺序

  • 先构造父亲,后构造儿子;析构相反

同名成员处理

  • 如果想访问父类中被隐藏的同名成员函数,需要加作用域 Base::

多继承语法

  • 语法:class 子类: 继承方式 父类1 , 继承方式 父类2
  • 当父类中出现同名成员,需要加作用域区分

菱形继承

在这里插入图片描述

  • 两个派生类继承同一个基类,又有某个类同时继承两个派生类
  • 二义性、数据冗余、使用不便

虚继承 virtual

在这里插入图片描述

  • 采用虚继承可以解决菱形继承问题
  • 虚继承后,继承类只会存储vbptr,意为虚基类指针,指向的是虚基类表
  • 虚基类表中存储了被继承类的地址偏移量
  • 通过这一偏移量,可以拿到被继承类中的数据

C++的类和对象_第1张图片

多态

  • 静态多态:函数重载 和 运算符重载属于静态多态,复用函数名
  • 动态多态:派生类和虚函数实现运行时多态

两种多态的区别

  • 静态多态的函数地址早绑定——编译阶段确定函数地址
  • 动态多态的函数地址晚绑定——运行阶段确定函数地址

动态多态满足条件

  • 有继承关系
  • 子类重写父类的虚函数

动态多态的使用

  • 父类指针或引用指向子类对象

动态多态的原理

C++的类和对象_第2张图片

 多态的好处

  • 组织结构清晰
  • 可读性强
  • 利于扩展和维护

纯虚函数

  • 在多态中,通常父类中虚函数的实现是毫无意义的,主要都是调用子类重写的内容
  • 因此可以将虚函数改为纯虚函数:virtual 返回值类型 函数名 (参数列表) = 0;
  • 当类中有了纯虚函数,该类也被称为抽象类

抽象类

  • 抽象类无法实例化对象
  • 子类必须重写抽象类中的纯虚函数,否则也是抽象类

虚析构

  • 可以解决父类指针释放子类对象时不干净的问题
  • 如果子类中没有堆区数据(new),可以不写虚析构

纯虚析构

  • virtual ~类名() = 0;
  • 需要在类外实现
  • 有纯虚析构的函数也是抽象类,无法实例化对象

你可能感兴趣的:(C++,c++)