当前位置: 首页 > news >正文

CPP_类和对象

面向对象:

更接近真实世界(关注各对象之间的关系,而非各步骤的进行)


  • 将结构体升级成立类

  • 类里面可以有:成员函数,成员变量

  • class Stack {
    public:void Init(int defaultCapacity=4 ) {_a = (int*)malloc(sizeof(int) * defaultCapacity);_capacity = defaultCapacity;_top = 0;}void Push(int x) {_a[_top++] = x;}void Destory() {free(_a);_a = nullptr;}
    private:int* _a;int _top;int _capacity;
    };
    int main() {Stack st;st.Init();st.Push(1);st.Destory();return 0;
    }
    
  • 访问限定符(即访问权限):

    • public:类外可以访问
    • protected
    • private
    • 每一种访问限定符的作用域是从当前限定符到下一个限定符
  • 一个类里面的函数,也可以声明与定义分离(如果函数太长了话)(如果声明和定义都直接写在类里面,如果这些函数符合内联规则,则默认都是内联函数

  • //stack.c
    class Stack {
    public:void Init(int defaultCapacity=4);void Push(int x) {a[top++] = x;}
    }
    
  • //stack.h
    //指定类域
    void Stack::Init(int defaultCapacity=4 ) {a = (int*)malloc(sizeof(int) * defaultCapacity);capacity = defaultCapacity;top = 0;
    }
    

封装
  • 本质上是一种更好的管理(便于用户使用类)

  • 对于成员变量,只有在实例化某一个类的时候,才会为类里面的成员变量开辟空间(即它的定义)(也就相当于类其实是一种设计,并没有实际的东西,只有实例化之后,才有这种设计对应的实例)

  • class Stack {
    private:int* _a;int _top;int _capacity;/*这些变量是声明,不是定义(对于变量来说,是否开空间是区分声明和定义的最大区别)所以在实例化对象的时候,才定义这些变量*/
    };
    
  • 对于某一个类的实例化对象来说,sizeof() 计算的是成员变量在经过内存对齐之后的总大小,不加成员函数。(因为对于每一个实例来说,都有一套成员变量都是单独属于它们自己的。但对于成员函数来说,所有实例的该方法都是一样的,没有必要每个实例都存储一份成员函数,只需要在一个公用区域存一份,所有实例都访问这一个就行了,所以sizeof()只有成员变量,没有成员函数)

  • 如果一个类只有成员函数,或者什么都没有,sizeof() 的计算结果是1。相当于这个类的对象默认占一个字节,相当于占一个位置,表示这个对象是存在的,但不存有效数据


This指针
  • 其实对于实例化对象来说,虽然成员函数是共用的,但每个实例调用时都是使用自己的成员变量的值,其实本质上是因为编译器默认使用了this指针,将当前实例的地址传给了成员函数,所以不同实例在调用的时候,使用的是自己的成员变量

  • class Date { 
    public:void print() {cout << year << " " << mon << " " << day;}void init() {year = 1;mon = 2;day = 3;}
    private:int year;int mon;int day;
    };
    int main() {Date d;d.init();d.print();return 0;
    }
    /*本质上其实是将当前实例的地址传给了成员函数,所以不同实例在调用的时候,使用的是自己的成员变量void print(Date* this) {cout << this->year << " " << this->mon;}Date d;d.print(&d);
    */
    
  • this不能在形参和实参传递,但可以直接在函数内部使用

  • this指针是形参,所以和普通参数一样,是存在栈里面的,作为栈帧的一部分(随压栈一起)


类的六大默认成员函数(不显式写出编译器就会自行生成)
  • 一.构造函数(完成实例的初始化):

    • 函数名与类名相同

    • 无返回值(也不需要写void)

    • 在对象实例化的时候编译器自动调用对应的构造函数(如果不写构造函数,则编译器会自动生成默认的构造函数)

    • 构造函数可以重载(多种初始化的方式)

    • 没有自己写的构造函数时,编译器生成默认的构造函数,这个函数针对内置类型不做处理,针对自定义类型成员,会去调用该类型的构造函数(而对于成员变量的声明,可以给默认参数,本质上就是给这个自动生成的构造函数使用的

    • **默认构造函数(无参构造函数,全缺省构造函数,没有构造函数时编译器默认生成的构造函数)**最多只能有一个,也可以没有(如果没有,那就不能用默认方式初始化)如果当自己写了别的不是默认构造函数形式的构造函数,那就需要自己在写一个默认构造函数

    • 有两种情况不需要自己写构造函数:

      • 内置类型成员都有缺省值,且初始化符合我们的要求
      • 全是自定义类型的数据,且这些类型都有自己的默认构造函数
    • class stack {
      public:stack(int capacity=4) {_a = (int*)malloc(sizeof(int) * capacity);_capacity = 0;_top = 0;}private:/*给的是缺省值,是给编译器的默认构造函数用的,这里并没有开辟空间,在实例化的时候才会开辟空间*/int* _a=nullptr;int _capacity=1;int _top=1;
      };
      

  • 二.析构函数(相当于destroy)(完成对象中资源的清理工作,即free等,为了防止编译器直接销毁对象造成内存泄露):

    • 在类名前加上~

    • 无返回值无参数

    • 不可以重载

    • 编译系统自动调用

    • 不写析构函数,编译器会自动生成(这个函数针对内置类型不做处理,针对自定义类型成员,会去调用该类型的析构函数)

    • 有动态申请的资源,就需要自己写析构函数(因为普通的变量会在函数本身创建的栈上,随函数结束栈的销毁而销毁了,所以只有在堆区或其他区上有自行申请的资源才需要析构)

    • class stack {
      public:~stack() {free(_a);_a = nullptr;_capacity = 0;_top = 0;}
      private:int* _a;int _capacity;int _top;
      };
      

  • 三.拷贝构造

    • 是一种构造函数的特殊重载形式,相当于传一个同类型的变量作为参数

    • 在传参时,必须以引用的方式传递参数,不然会发生无限递归

    • 因为如果以传值调用来传递参数,本质上来说其实是要调用该类型的拷贝构造函数来创建形参的,也就相当于对于内置数据类型形参是直接拷贝,而自定义数据类型则是调用拷贝构造函数来创建形参的

    • class Date {
      public:Date(const Date & d) {_year = d._year;_mon = d._mon;_day = d._day ;}
      private:int _year = 1;int _mon = 1;int _day = 1;
      };
      
    • 虽然_year这些成员属性是私有的,但私有属性只有在类外不能访问,此时在类内,是可以访问的

    • 如果不显式定义,编译器会自己生成默认拷贝构造函数,默认是:

      • 内置数据类型完成值拷贝/浅拷贝
      • 自定义数据类型会调用它的拷贝构造函数
    • 在有些场景下,浅拷贝不足以满足要求,就需要自己写拷贝构造函数,以实现深拷贝(比如在stack里,如果进行浅拷贝,那么会使两个栈里面的数组指针指向同一块空间(因为浅拷贝只将数值原封不动的拷贝过去,所以它们指向同一块空间),显然不是我们想要的)


  • 四.赋值运算符重载

    • 用于已经存在的两个对象之间的赋值拷贝

    • 本质就是将赋值运算符重载了一下

    • 如果不显式定义,编译器会自己生成默认赋值运算符重载,默认是:

        • 内置数据类型完成值拷贝/浅拷贝
        • 自定义数据类型会调用它的赋值运算符重载
      • 赋值运算符只能定义在类内部,不能重载成全局函数。(别的运算符既可以重载在类内,也可以重载成全局,但由于赋值运算符重载是一种默认成员函数,所以只能在类内)
    • class Date {
      public:Date& operator= (Date& x) {_day = x._day;return *this;}
      //之所以返回值是一个(Date&),是为了支持连续赋值的情景,如果不要求支持连续赋值,则返回值可以写成void
      private:int _year = 1;int _mon = 1;int _day = 1;
      };int main() {Date d1(1);Date d2(2);Date d3(3);d3 = d1 = d2;return 0;
      }
      

  • 五.取地址运算符的重载

  • Date* Date::operator& () {return this;
    }
    const Date* Date::operator& () const {return this;
    }//这两个函数因为参数不同构成函数重载
    

  • const

    • 对应一些成员实例,如果是const修饰的,那么在使用一些成员函数的时候可能会造成权限的放大,从而导致错误

    • 下面这个代码里,如果print函数右侧不加const,则无法正常打印,因为此时d1的类型是const Date ,而且指向该对象的指针this是隐式传递的,类型是Date* 由于它的类型是编译器底层传递的,我们不能显式来写出this,所以只能将const写在最右边,其实本质就是修饰this指针的

    • void print() const//修饰this指针的const
      {cout << _year<<"--"<<_mon<<"--"<<_day << endl;
      }const Date d1(2023, 1, 26);
      d1.print();
      

在谈构造函数
  • 第一种:构造函数体赋值

  • Date::Date(int year, int mon, int day) {_year = year;_mon = mon;_day = day;
    }
    
  • 第二种:初始化列表

    • 有三类成员变量必须用初始化列表初始化:
      • 没有默认构造函数的结构体变量
      • 引用类型
      • const修饰的变量
    • 对于初始化列表的理解:在实例化对象时相当于时整个对象的定义,而对于它的成员变量,其实是在初始化列表所在的位置定义的,因为引用和const修饰的变量只有在定义的时候才能赋值,所以它们只能在初始化列表里面初始化。其实在初始化列表里,如果不写出所有成员变量,那么就是自定义类型调用默认构造函数,内置类型不做处理(其实内置类型的成员变量在声明的时候给的默认值,就是传到初始化列表里,用于内置类型成员变量初始化的。)它下面的那个{},是为了某些多步初始化的过程无法在初始化列表里完成,需要在{}里完成
    • 成员变量的初始化顺序是按类中的声明顺序初始化的,和其在初始化列表里的顺序无关
  • Date(int year, int mon, int day):_year(year), _mon(mon), _day(day)
    {}
    

explicit 关键字
  • 如果构造函数在传参的时候不想让其发生隐式类型转换,就可以在函数名之前加上explicit

静态成员,函数
  • 静态成员变量是属于这个类的,生命周期是全局的。而普通的成员变量是属于该类的实例的。也就是说静态成员变量是所有实例共享的变量。
  • 静态成员变量没有初始化列表,所以静态成员变量必须在类外边,全局位置定义。(本质上相当于用类去封装全局变量)
  • 静态成员函数:没有this指针,只要指定类域和访问限定符即可访问。静态成员函数只能访问静态成员变量,因为它没有this指针,没法访问某一个实例的普通成员变量
  • 非静态函数可以访问静态成员变量,静态函数不可以访问静态成员变量,函数(因为静态函数没有this指针)
class A {
public:static int _a;
private:
};int A::_a = 0;int main() {A aa;cout << ++A::_a << endl;cout << A::_a << endl;return 0;
}
-------------------------------------------------------------
-------------------------------------------------------------   
class A {
public:static int Get() {return _a;}
private:static int _a;
};int A::_a = 0;int main() {A aa;cout << aa.Get() << endl;cout << A::Get() << endl;return 0;
}

相关文章:

  • 智能外呼系统的技术演进与多场景落地实践
  • 【k8s】LVS/IPVS的三种模式:NAT、DR、TUN
  • NOIP2009提高组.Hankson的趣味题
  • Spring JDBC 的开发步骤(非注解方式)
  • SpringBoot入门实战(第七篇:项目接口-商品管理)
  • Ubuntu启动SMB(Samba)服务步骤
  • pytest心得体会
  • vue2+Vant 定制主题
  • 第二章:ForgeAgent Core
  • 极狐GitLab 的合并请求部件能干什么?
  • 【C语言】C语言中的字符函数和字符串函数全解析
  • COMSOL多孔结构传热模拟
  • VTK-8.2.0源码编译(Cmake+VS2022+Qt5.12.12)
  • 零跑B01上海车展全球首秀,定义纯电轿车新基准
  • 3D模型格式转换工具HOOPS Exchange 2025.3.0更新:iOS实现Rhino格式支持!
  • CS144 Lab3 实战记录:TCP 发送器实现
  • 奶茶店里面的数据结构
  • ProxySQL实现mysql8主从同步读写分离
  • Vue3祖先后代组件数据双向同步实现方法
  • TypeScript-知识点梳理
  • 保险经纪公司元保在纳斯达克挂牌上市,去年净赚4.36亿元
  • 西部航空回应飞机上卖彩票:与重庆福彩合作,仅部分航班售卖
  • 中国武术协会原主席张耀庭逝世,曾促成电影《少林寺》拍摄
  • 看见“看得见的手”,看见住房与土地——读《央地之间》
  • 民营经济促进法出台,自今年5月20日起施行
  • 厚重与潮流交织,淮安展现“运河之都”全新城市想象