C++: 面向对象

本文详细解释了C++中的类概念,包括封装、类域、成员变量和成员函数,重点介绍了构造函数、析构函数、拷贝构造和赋值运算符的语法与功能,以及this指针、static、const、友元和explicit关键字的运用。
摘要由CSDN通过智能技术生成

C语言是面向过程的语言, C++是面向对象的语言

面向对象可以将事物抽象, 概念(成员变量) + 行为(成员函数).

优势

  • 可以将做一件事拆分为对象与对象之间的关系(如网络通信中的服务端与客户端) 
  • 代码的拓展(抽象出一个基类, 拓展的时候往外添加而不用修改原代码)
  • 代码的维护(上层逻辑不变, 更换底层可以通过多态, 实现指向谁就使用谁)

面向对象三大特性

  • 封装:将数据和操作数据的方法进行结合, 隐藏具体的实现细节, 只提供接口用来交互(隐藏细节)
  • 继承:子类继承父类(复用代码)
  • 多态:不同对象调用统一函数, 会有不同的效果.(实现改写)

封装

类与对象
  • 语法:class name{...}; 里面可有成员函数, 成员变量
  • 类域: 访问类域里面的代码, 需要指定类域
  • 类的实例化: 类是对对象的描述, 不占空间, 只有实例化出对象的时候才会占空间
  • 类的大小计算: 根据内存对齐规则
  • 类成员的存储方式
    • 成员变量: 存在于实例化的对象中(非静态)
    • 成员函数: 存在于公共的代码段中(同类对象的成员函数是相同的,这样更节省空间)

默认成员函数
  • 默认成员函数: 构造函数,析构函数,拷贝构造,赋值运算符,取地址及const取地址操作符
  • 默认成员函数: 我们不写编译器自动生成
  • 总结:
    • 构造函数与析构 : 内置类型: 不处理       自定义类型: 调用其构造和析构
    • 拷贝构造与赋值 : 内置类型: 浅拷贝       自定义类型: 调用其拷贝构造和赋值

示例

class A
{
public:  
    ...
private:
    int _a;
    int _b;
};

1.构造函数


    //1.构造函数
    A(){}

    A(int a , int b)//初始化列表
     :_a(a),_b(b){}

  • 语法: 函数名与类名相同, 无返回值, 可以重载
  • 初始化列表:语法同上
    • const成员, 引用,自定义成员(没有默认构造函数), 必须在初始化列表初始化
    • 初始化列表的初始化顺序与成员变量的声明顺序相同
    • 成员变量在初始化列表中只能出现一次(初始化只能初始化一次)
  • 功能:用来初始化对象(自动调用), 函数体内给叫赋值
  • 默认成员函数: 我们不写, 编译器自动生成
  • 默认生成的构造函数: 对内置类型不处理, 对自定义类型调用其构造函数
  • 注意:
    • 默认构造函数: 无参的构造和全缺省的构造(默认构造函数只能有1个)不然会有歧义
    • 用类实例化的时候, 无参的构造不能加(), 与函数声明存在歧义

2.析构函数

    //2.析构函数
    ~A(){}
  • 语法:函数名与类名相同,无参,无返回值
  • 功能:回收资源(自动调用)
  • 默认生成的析构函数, 对内置类型不处理, 对自定义类型调用其析构函数

3.拷贝构造

    //3.拷贝构造
    A(const A& a)//参数必须是&, 否则会死递归
    {
        _a = a._a;
        _b = b._b;
    } 
    
  • 语法:函数名与类名相同等
    • 参数必须是引用:如果是传值(有临时变量产生会在调用拷贝构造), 而我们自己显示写了拷贝构造, 编译器会再次调用我们写的拷贝构造, 会出现死递归
  • 功能: 用已经存在的对象去构造一个新对象
  • 默认生成的拷贝构造: 对内置类型: 浅拷贝  对自定义类型调用其拷贝构造

4.赋值运算符

    //4.赋值运算符
    A& operator=(const A& a)
    {
        if(this != &a)//不给自己赋值
        {
            _a = a._a;
            _b = b._b;
        }
        return *this;
    } 
  • 语法:同上
    • 不要给自己赋值(无意义)
    • 要有返回值: 可用做到连续赋值
    • 返回值是&,参数是&, 可用提高效率(没有拷贝)
    • 参数加const 避免被修改
  • 功能: 把一个对象赋值给另一个已经存在的对象
  • 默认生成的赋值: 对内置类型: 浅拷贝  对自定义类型调用其赋值
  • 注意:
    • 不要将赋值运算符写成全局的, 我们不写编译器自动生成, 全局又有一个, 会产生歧义
    • 不能重载的运算符:    .      .*      ::      :?      sizeof 

继承

语法
class Person
{
    int age;
    string name;
};
//  派生类    继承方式   基类
class Student: public Person
{
    int id;
};

继承的赋值兼容规则(切割)

  • 切片: 派生类对象可以直接赋值给基类对象
  • 基类对象不能赋值给派生类对象
  • 基类的指针,引用可以通过强转赋值给派生类的指针或引用(但必须基类的指针指向派生类对象,不然可能会产生越界)

继承的作用域(隐藏)

  • 基类有基类的作用域, 派生类有派生类的作用域
  • 重定义(隐藏):子类成员屏蔽父类对同名成员的访问.(前提:不同作用域的同名函数), 但其仍在子类中,可以通过 基类:: 成员 显示的访问
    • 成员函数只要函数名相同就构成隐藏
  • 继承与友元: 友元关系不能继承,也就是说基类友元不能访问子类私有和保护成员
  • 继承与静态成员: 基类定义了static静态成员,则整个继承体系里面只有一个这样的成员

继承中的默认成员函数

子类调用父类的

构造函数

父类Person无默认构造, 在初始化列表中调用父类的构造函数初始化
Student(int num, const char* str, const name)
    :Person(name)
    ,_num(num)
    ,_str(str)
    {}

拷贝构造

s2(s1)
Student(const Student& s)
    :Person(s)
    ,_num(s._num)
    ,_str(s._str)
    {}

赋值运算符

Student& operator=(const Student& s)
{
    if(this != &s)
    {
        //显示调用父类的
        Person::operator=(s);
        _num = s._num;
        _str = s._str;
    }
    return *this;
}

析构函数

  • 1.析构函数名字被统一为destructor(构成隐藏)

  • 2.类析构会自动调用父类的析构(保证先子后父).

  • 非法访问: 在子类的析构显示的调用父类的析构, 若其还访问了父类成员(已经析构)

  • 析构函数一般为虚函数, 保证子类对象的资源释放

继承种类(单, 多, 菱形)
  • 单继承 : 同上
  • 多继承
class A{
    int a;
};
 
class B{
    int b;
};
 
class C: public A, public C{
    int c;
};
  • 菱形继承
class A{
    int a;
};
 
class B : public A{
    int b;
};
 
class C : public A{
    int c;
};
 
class D : public B, public C{
    int d;
};

导致的问题: 二意性,  数据冗余

  •  解决:虚继承(虚基表): 通过指针, 指向了虚基表, 虚基表中存了偏移量能找到A
    • 继承的时候加上virtual就行
class A{
    int a;
};
 
class B : virtual public A{
    int b;
};
 
class C : virtual public A{
    int c;
};
 
class D : public B, public C{
    int d;
};

继承与组合

  • 继承和组合

    • 继承 is-a 子类需要继承父类的行为或属性

    • 组合 has-a 一个类包含另一个类作为其一部分

    • 继承是"白箱复用"看的见所有细节, 组合是"黑箱复用"看不见细节

    • 组合耦合度低,继承耦合度高。优先用组合

      • 继承: 修改父类可能会影响到所有子类

      • 组合: 需要更多的代码来管理对象之间的关系

多态

  • 多态: 不同对象, 执行同一种方法, 而产出不同的状态(改写对象)
  • 语法条件(在继承的前提下)
    • 父类的指针或引用
    • 虚函数的重写

多态的原理

  • 静态多态原理: 函数重载
  • 动态的多态原理:虚表
    • 若包含虚函数, 类里面会有虚表指针(指向虚表), 虚表中存放了虚函数的地址,来调用虚函数
    • 父类对象和子类对象的虚表是不同的,若有虚函数的重写,子类的虚函数地址会覆盖父类的
    • 所以可以根据不同的对象, 来调用不同的函数
  • 虚表存放在常量区,且在编译的时候形成
    • 存放在常量区: 同一类型的对象共用一张虚表(节省空间)
    • 编译的时候形成: 虚函数的地址要入虚表, 编译的时候就得确定 
  • 虚表指针是在构造函数初始化列表的阶段初始化

语法

重写

  • 不同的作用域(基类,派生类的作用域)
  • 两个函数必须是虚函数
  • 函数名相同,参数相同,返回值相同
    • 例外1协变: 返回值可以不同,但必须死基类,派生类的指针
    • class person{
          virtual person* func(){ 
              return *this; 
          }      
      };
       
      class student : public person{
          virtual student* func(){ 
              return *this;
          }
      };
    • 例外2:派生类的虚函数可以不加virtual
    • class person{
          virtual person* func(){ 
              return *this; 
          }      
      };
       
      class student : public person{
          student* func(){ 
              return *this;
          }
      };

虚函数

  • 在成员函数前面 + virtual就是虚函数
class A{
    virtual void func(){}
};

抽象类

  • 包含纯虚函数的类叫抽象类
  • 特点:含有纯虚函数的类不能实例化出对象   
  • 功能: 间接强制重写(若继承了抽象类, 也会继承它的纯虚函数, 若不重写就无法实例化出对象)
  • 纯虚函数, 在成员函数后面加上 = 0
class A{
    virtual void func() = 0;
};

接口继承与实现继承

  • 接口继承:虚函数的继承, 目的是为了重写, 所以只继承接口
  • 实现继承:普通成员函数的继承, 目的是为了复用代码, 所以继承实现

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值