ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

【C++——类和对象(上)】

【C++——类和对象(上)】 目录一、类1.类定义格式(1)struct定义类2.访问限定符3.类域二、实例化1.成员变量和成员函数的存储2.编译器编译成员函数三、this指针四、类的默认成员函数1.构造函数2.析构函数3.拷贝构造函数一、类类是对一类事物的抽象描述是模板、蓝图类定义了一类事物有哪些属性、哪些行为1.类定义格式类的定义和结构体类似但区别是类中可以定义函数classStack{//成员变量属性//成员函数方法};class定义类的关键字Stack类的名字{}之间类的主体类体内容统称为类的成员类中的变量成员变量 / 属性类中的函数成员函数 / 方法Tips类定义结束大括号后面的分号千万不要忘记C 为了区分成员变量和函数形参编码习惯上成员变量会增加标识比如变量前后加下划线_或者以m开头例如_year、m_year写在类内部的成员函数默认是 inline 内联函数成员函数支持声明与定义分离类内只写函数声明函数实现写在类外部由于外部属于全局作用域全局函数只会在全局寻找因此必须使用类名::指定所属类域classData{public://类内声明voidInit(intyear,intmonth,intday);};//类外定义必须加类域Data::voidData::Init(intyear,intmonth,intday){_yearyear;_monthmonth;_dayday;}(1)struct定义类C 中struct兼容 C 语言结构体用法同时升级成为类内部也可以定义成员函数。日常开发中我们优先使用class来定义类//C语言写法structListNodeC{intval;structListNodeC*next;//C中struct结构体名称代表类型};typedefstructListNodeC1{intval;structListNodeC1*next;}LTNodeC;//C struct不需要重复写structstructListNodeCPP{intval;ListNodeCPP*next;};2.访问限定符封装是面向对象三大特性之一访问限定符是C的一种实现封装的方式。C 通过类把属性和方法整合在一起再通过访问限定符选择性对外提供接口访问限定符 { 在类外可以直接被访问 p u b l i c 在类外不能直接被访问 p r o t e c t e d 、 p r i v a t e 访问限定符\begin{cases} 在类外可以直接被访问public\\ 在类外不能直接被访问protected、private \end{cases}访问限定符{在类外可以直接被访问public在类外不能直接被访问protected、private​访问权限作用域从该限定符出现位置开始直到下一个访问限定符出现如果后面没有其他限定符则作用域到类结束的}定义的成员没有被访问限定符修饰class定义的类默认访问权限为 privatestruct定义的类默认访问权限为 public编码规范成员变量一般设置为private/protected做封装需要外部调用的接口函数设置为public3.类域类定义了一个全新的作用域类的所有成员都属于这个类域。如果在类体外实现成员函数必须使用::作用域解析运算符指明该成员属于哪一个类类域的作用影响编译器标识符查找规则二、实例化实例化使用类类型在物理内存上创建对象的过程。对象类的实例根据模板生产出来的具体实体。关系一个类可以实例化出多个对象内存规则类只是模板类里面的成员变量仅仅是声明不会分配内存空间实例化出来的对象才会占用物理内存用来存储成员变量。1.成员变量和成员函数的存储成员变量每个对象各自独立每个对象单独存储自己的成员变量。成员函数所有对象调用的是同一份函数代码全部存放在公共代码区避免内存重复浪费。函数指针只是地址不会存放在对象内存中调用成员函数时会被编译成汇编指令编译器编译链接阶段直接找到函数地址#includeiostreamusingnamespacestd;classA{public:voidPrint(){}};classB{};intmain(){A a;B b;coutaendl;coutsizeof(a)endl;coutsizeof(b)endl;return0;}a、b大小运行结果运行程序后我们发现成员变量A、B类的大小不为0空类没有任何成员变量的类空类的大小为 1 字节。这 1 字节仅仅做占位用来标识这个对象是唯一存在的不存储有效数据2.编译器编译成员函数#includeiostreamusingnamespacestd;classA{public:voidPrint(){coutA::Print()endl;}};intmain(){A*pnullptr;p-Print();return0;}成员函数地址编译阶段就已经确定和对象本身没有关系这里不会对空指针做解引用操作因此p-Print();并没有因为解引用空指针报错但语法上仍然要求通过对象 / 对象指针调用成员函数两个原因需要通过对象类型确定对应的类域编译器去类里面查找这个成员函数。需要传递隐含的this指针作为函数参数。三、this指针不同对象调用同一个成员函数函数能够区分当前操作的是哪一个对象底层依靠隐含的this 指针this指针编译器处理成员函数时会自动在成员函数形参第一个位置增加当前类类型的指针。this 指针指向调用该函数的对象。#includeiostreamusingnamespacestd;classDate{public://编译器底层等价于 void Init(Date* const this,int year,int month,int day)voidInit(intyear,intmonth,intday){_yearyear;//等价 this-_yearyear;_monthmonth;//等价 this-_monthmonth;_dayday;//等价 this-_dayday;}//等价 void Print(Date* const this)voidPrint(){cout_year/_month/_dayendl;}private:int_year;int_month;int_day;};intmain(){Date d1;//编译器底层转换 d1.Init(d1,2026,9,20);d1.Init(2026,9,20);//编译器底层转换 d1.Print(d1);d1.Print();return0;}在成员函数内部访问成员本质全部是通过this-访问。❗不能在实参、形参位置显式写 this但是函数体内可以显式写this-。this指针本身是Date* const指针本身不能被修改但是指针指向对象的内容可以修改。this 属于函数形参一般存放在栈帧部分编译器会做优化放到寄存器提高运行效率。面向对象三大特性封装、继承、多态C 把数据和函数收纳到类中通过访问限定符管控访问权限这是封装的基础体现四、类的默认成员函数默认成员函数用户没有显式写编译器会自动帮我们生成的成员函数默认成员函数 { 初始化和清理 { 构造函数主要完成初始化工作 析构函数主要完成清理工作 拷贝复制 { 拷贝构造使用同类对象初始化创建对象 赋值重载主要是把一个对象赋值给另一个对象 取地址重载主要是对普通对象和const对象取地址这两个很少会自己实现 默认成员函数\begin{cases} \text{初始化和清理}\begin{cases} \text{构造函数主要完成初始化工作}\\ \text{析构函数主要完成清理工作} \end{cases}\\ \text{拷贝复制}\begin{cases} \text{拷贝构造使用同类对象初始化创建对象}\\ \text{赋值重载主要是把一个对象赋值给另一个对象} \end{cases}\\ \text{取地址重载主要是对普通对象和const对象取地址这两个很少会自己实现} \end{cases}默认成员函数⎩⎨⎧​初始化和清理{构造函数主要完成初始化工作析构函数主要完成清理工作​拷贝复制{拷贝构造使用同类对象初始化创建对象赋值重载主要是把一个对象赋值给另一个对象​取地址重载主要是对普通对象和const对象取地址这两个很少会自己实现​1.构造函数构造函数是特殊成员函数在对象实例化的时候自动调用完成对象初始化替代我们手动写的Init初始化函数构造函数的特点函数名和类名完全相同没有返回值不能写 void创建对象实例时编译器自动调用支持函数重载#includeiostreamusingnamespacestd;classDate{public://无参构造Date(){_year1;_month1;_day1;}//带参构造Date(intyear,intmonth,intday){_yearyear;_monthmonth;_dayday;}voidPrint(){cout_year/_month/_dayendl;}private:int_year;int_month;int_day;};intmain(){Date d1;d1.Print();Dated2(2026,9,21);d2.Print();return0;}⚠️注意调用无参构造不要写括号Date d1();这会被编译器识别成函数声明编译器无法识别会报警告不会创建对象如果我们没有写任何构造函数编译器自动生成一份无参默认构造只要用户显式实现任意构造函数编译器就不再生成默认构造默认构造函数不需要传递实参就可以调用的构造函数。包括三种无参构造函数全缺省构造函数编译器自动生成的无参构造Tips这三者有且只能存在一个不能同时共存C 类型划分内置类型基本类型语言原生类型int、char、double、指针等自定义类型class /struct 我们自己写的类编译器自动生成的默认构造行为内置类型成员没有强制初始化要求值随机看编译器实现。自定义类型成员自动调用该类型的默认构造函数初始化如果该类没有默认构造直接编译报错此时需要初始化列表解决。默认的无参的显式初始化需要传参数、如果有内置类型需要我们自行处理…所以构造函数大多数情况下都需要自己实现小部分类似MyQueue、Stack有默认构造情况下可以依靠编译器自动生成大部分场景构造函数建议自己实现遵循「构造函数应写尽写」2.析构函数析构函数和构造函数功能相反对象生命周期结束的时候自动调用完成对象资源释放清理等价于之前数据结构写的Destroy销毁函数析构函数的特点析构函数名是在类名前加上字符~函数名~类名无返回值、无参数一个类只能有一个析构函数不手动写编译器自动生成默认析构对象生命周期结束系统自动调用编译器生成默认析构内置类型成员什么都不处理自定义类型成员会自动调用它的析构函数。就算自己手写析构类里面的自定义类型成员依然会自动调用析构编码判断类内部没有向堆申请资源如Date 日期类不需要手写析构使用编译器默认生成即可。默认生成的析构可用如MyQueue不需要手写析构类中有 malloc/new 申请堆内存资源如Stack 栈类必须手动写析构释放资源否则内存泄漏classStack{public:Stack(intn4){_a(int*)malloc(sizeof(int)*n);if(nullptr_a){perror(malloc fail);return;}_capacityn;_top0;}~Stack(){free(_a);_anullptr;_top_capacity0;}private:int*_a;int_top;int_capacity;};intmain(){Stack st1;Stack st2;return0;}如果有多个对象对象销毁顺序后定义的对象先析构3.拷贝构造函数拷贝构造是特殊的构造函数使用一个已经存在的同类对象去初始化创建另一个新对象拷贝构造函数的第一个参数必须是自身类类型的引用且其余参数要有缺省默认值#includeiostreamusingnamespacestd;classDate{public:Date(intyear1,intmonth1,intday1){_yearyear;_monthmonth;_dayday;}//拷贝构造函数Date(constDated){_yeard._year;_monthd._month;_dayd._day;}voidPrint(){cout_year/_month/_dayendl;}private:int_year;int_month;int_day;};拷贝构造关键特点拷贝构造属于构造函数的重载。第一个参数必须是类的引用不能传值因为语法逻辑上拷贝构造传值传参会引发无穷递归调用C 规定自定义类型发生拷贝行为必须调用拷贝构造。对象传值传参、传值返回都会调用拷贝构造。用户不显式写拷贝构造编译器自动生成默认拷贝构造内置类型逐字节拷贝浅拷贝 / 值拷贝自定义类型成员调用该类型的拷贝构造函数。Tips参数建议加上const保护源对象不被修改引发无穷递归调用原因C规定传值传参要调用拷贝构造传值传参要调用拷贝构造调用拷贝构造要先传参如果拷贝构造完成了再继续调用原来的函数但是如果本身调用的就是拷贝构造调用这个拷贝构造要传值传参传值传参会形成一个新的拷贝构造就会造成无穷递归但是如果第一个参数是类类型的引用引用传参传入的参数是别名不会形成新的拷贝构造#includeiostreamintmain(){Dated1(2026,9,20);//普通构造d1.Print();Dated2(d1);//拷贝构造d2.Print();//两种写法都会调用拷贝构造return0;}值拷贝/浅拷贝按字节复制一个字节一个字节的拷贝深拷贝不仅拷贝对象本身对象指向的堆资源也要重新开辟一份拷贝如果一个类显式实现了析构并释放资源那么它就需要显式写拷贝构造否则就不需要⚠️Tips如果类中管理堆资源Stack 栈默认生成的浅拷贝会让多个对象指向同一块堆空间对象析构时同一块内存会被 free 多次程序直接崩溃这种情况必须手写拷贝构造实现深拷贝Stack 深拷贝示例Stack(constStackst){_a(int*)malloc(sizeof(int)*st._capacity);if(nullptr_a){perror(malloc fail);return;}memcpy(_a,st._a,sizeof(int)*st._top);_topst._top;_capacityst._capacity;}调用拷贝构造#includeiostreamintmain(){Stack st1;st1.Push(1);//方式一Stackst2(st1);//方式二Stack st3st1;return0;}传值返回Stackfunc1(){Stack st;returnst;}intmain(){Stack retfun1();return0;}传值返回会产生一个临时对象调用拷贝构造。传值引用返回返回的是返回对象的别名引用没有产生拷贝传引用返回局部对象Stackfunc2(){Stack st;returnst;}intmain(){Stack retfunc2();return0;}如果返回对象是一个当前函数局部域的局部对象函数结束就销毁了这时的引用相当于一个野引用类似一个野指针传引用返回可以避免拷贝前提返回的对象生命周期不会随着函数结束销毁
返回列表