C++ 类和对象 (中)

发布时间:2026/10/8 14:28:50
C++ 类和对象 (中) 目录 : 构造函数 析构函数 拷贝构造函数 运算符重载 赋值运算符重载 流提取/插入运算符重载 const 成员函数 取地址运算符重载类的六大默认成员函数 :构造函数析构函数拷贝构造函数赋值运算符重载operator取地址运算符重载operatorconst 取地址运算符重载operator() const 构造函数构造函数是类的一个特殊成员函数实例化对象的时候编译器自动调用用来初始化对象的成员变量 构造函数的硬性规则 :函数名必须和类名完全相同没有返回值连void都不能写可以重载可以写多个构造函数参数列表不同不能手动调用实例化对象时自动执行如果自己不写任何构造函数编译器自动生成默认构造函数无参classStudent{public:// 构造函数名字类名无返回值Student(){age0;}Student(inta)// 重载有参构造{agea;}private:intage;};intmain(){Student s1;// ✅ 自动调用无参构造 Student()Students2(20);// ✅ 自动调用有参构造 Student(int a)return0;} 默认构造函数满足下面任意一个都叫默认构造无参构造函数所有参数都带缺省参数的构造函数只要自己手写了任意一个构造函数有参编译器就不再生成默认无参构造编译器默认⽣成的构造对内置类型成员变量的初始化没有要求也就是说是是否初始化是不确定的看编译器 析构函数定义析构函数是类的特殊成员函数作用对象销毁时自动调用用来释放对象申请的堆内存、清理资源语法规则名字~类名()没有返回值、没有参数不能重载一个类最多 1 个析构对象⽣命周期结束时系统会⾃动调⽤析构函数⼀个局部域的多个对象C规定后定义的先析构如果我们显⽰写析构函数对于⾃定义类型成员也会调⽤他的析构⾃定义类型成员⽆论什么情况都会⾃动调⽤析构函数C 里 struct 本质就是 class二者唯一区别是默认访问权限不同编译器⾃动⽣成的析构函数对内置类型成员不做处理⾃定类型 (结构体) 成员会调⽤ ta 的析构函数✅ 重点坑点默认析构不会释放堆上开辟的内存 如果类中有资源申请时必须手写析构函数释放否则内存泄漏classStudent{public:Student(){}//构造~Student(){}//析构}; 拷贝构造函数定义拷贝构造函数是一种特殊的构造函数用同一个类的已存在对象去初始化一个新对象语法类名(const 类名 引用名);classStudent{public:// 拷贝构造Student(constStudents){// 把对象s的数据拷贝给新对象}};✅参数必须是同类的引用一般加 const 保护源对象注意不能传值传值会无限递归调用拷贝构造什么时候自动调用拷贝构造?用一个对象初始化另一个对象Student s1;Student s2s1;// 调用拷贝构造Students3(s1);// 调用拷贝构造注意赋值运算符 s2 s1 不是拷贝构造这是两个已经存在对象之间赋值调用operator,很多人这里踩坑函数传参值传递传对象不是引用 / 指针voidfunc(Student s){}func(s1);// 实参s1拷贝一份给形参s触发拷贝构造函数返回值按值返回对象Studentfunc(){Student temp;returntemp;// 返回临时对象触发拷贝构造编译器可能优化RVO}默认拷贝构造编译器自动生成如果你不手写拷贝构造编译器会自动生成一个默认拷贝构造内置类型直接按字节拷贝浅拷贝自定义成员调用成员自己的拷贝构造⚠️ 浅拷贝灾难浅拷贝只是把成员变量的值原样复制过去如果成员是指针只复制指针里存的地址不复制地址指向的堆内存析构的时候两个对象都会去释放同一块空间→ 程序崩溃重复释放 解决手写深拷贝给新对象单独开辟一块堆内存拷贝数据两个对象资源相互独立,析构互不干扰深拷贝写法 (解决上面问题)classTest{public:int*p;Test(intval)// 构造函数{pnewint(val);}// 手写深拷贝Test(constTestt){pnewint(*t.p);// 新开一块堆内存复制内容不是复制地址}~Test()//析构函数{deletep;}}; 什么时候浅拷贝是安全的类里面没有指针、没有申请堆内存资源的时候浅拷贝完全没问题比如 日期类成员只有 int _year,_month,_day默认拷贝构造浅拷贝直接复制 int 值不存在重复释放问题不需要手写深拷贝 运算符重载运算符重载本质给运算符写一个函数让运算符可以作用于自定义类的对象运算符重载特点 :不能发明新运算符只能重载 C 已有的重载操作符⾄少有⼀个类类型参数重载运算符函数的参数个数和该运算符作⽤的运算对象数量⼀样多不能改变运算符的优先级、结合性、操作数个数不能重载 :::、.*、sizeof、?:、.C规定后置重载时增加⼀个int形参跟前置构成函数重载⽅便区分重载运算符时有前置和后置运算符重载函数名都是operator⽆法很好的区分// d1;// d1.operator(0);Date Date::operator(int){Date tmp*this;*this1;returntmp;}// d1;// d1.operator();DateDate::operator(){*this1;return*this;}自定义类型,能用前置就用前置,因为后置多2次拷贝,前置无拷贝成员函数重载语法 :返回值类型 operator运算符(参数)左操作数this 指向的对象参数右操作数如果重载运算符函数是成员函数则它的第⼀个运算对象默认传给隐式的 this 指针因此运算符重载作为成员函数时参数⽐运算对象少⼀个// 成员函数重载booloperator(constDated){return_yeard._year_monthd._month_dayd._day;}运算符本质就是函数调用 :// 日期类Dated1(2026,10,4);// 构造函数Dated2(2026,10,4);// 构造函数if(d1d2)// 等价于 d1.operator(d2);{cout相等;} 赋值运算符重载operatoroperator是成员函数重载不能写成全局函数返回值必须是当前类的引用目的支持连续赋值 abc (为了修改自身后再赋值给其他变量)常见运算符重载的返回值 :classDate{public:Date(inty,intm,intd):_year(y),_month(m),_day(d){}// 赋值运算符重载Dateoperator(constDated){// 1. 防止自赋值自己赋值给自己 d1 d1if(this!d){_yeard._year;_monthd._month;_dayd._day;}return*this;// 返回*this支持连续赋值}private:int_year;int_month;int_day;};返回类型Date引用返回避免拷贝如果写成值返回会产生临时对象效率低连续赋值也可以但不推荐参数const Date d常引用接收避免拷贝C中函数传参是否拷贝的对比if(this ! d)自赋值判断d1 d1不加判断在有堆内存时会出现先释放资源的严重 bug函数是成员函数隐含 this 指针左操作数就是调用对象 编译器默认生成的赋值重载如果自己不写operator编译器会自动生成默认赋值重载行为按字节浅拷贝逐个把成员变量复制过去对⾃定义类型成员变量会调⽤他的赋值重载函数和默认拷贝构造很像但触发时机不同拷贝构造创建新对象时用已有对象初始化赋值重载两个已经存在的对象之间互相赋值Dated1(2026,10,1);// 触发构造函数Dated2(2025,1,1);// 触发构造函数d2d1;// 触发赋值重载两个对象都已经存在Date d3d1;// 触发拷贝构造新建对象 流提取 / 插入 运算符重载operator与operator在C标准库中,已经写好了一些流提取 / 插入 运算符的重载这里讨论的是对于自定义类的流提取 / 插入 运算符重载类的流插入运算符重载operator// 类的流插入运算符重载// 全局函数不能写成类成员函数ostreamoperator(ostreamout,constDated){outd._year年d._month月d._day日;// 返回out引用支持链式 cout d1 d2;returnout;}第一个参数ostream out(规定)输出流对应cout必须传引用流不能拷贝第二个参数const Date dconst保护对象不被修改引用避免拷贝返回值ostream为了链式连续输出特点只读对象不会修改 Date如果_year私有要在类内声明为friend友元 注意 :连续多个 是多次调用不同版本的operator 不是只调用一次 调用逻辑 :调用一次对应右操作数版本的 ,然后就返回 (返回值作为下一次调用的左操作数) ,接着再调用对应下一个右操作数版本的 …调用cout d;→ 等价operator(cout, d);类的流提取运算符重载operator// 类的流提取运算符重载istreamoperator(istreamin,Dated){ind._yeard._monthd._day;// 返回in引用支持链式 cin d1 d2;returnin;}第一个参数istream in( 规定 )输入流对应cin同样必须引用第二个参数Date d不能加 const 因为要读取输入修改对象成员返回值istream支持链式输入 cin d1 d2;访问私有成员需要友元声明调用cin d;→ 等价operator(cin, d); 注意 :in d._year d._month d._day;语法上是连续 3 次调用 运算符实际上一次只读 1 个整数 , 不是一次性读完三个数等价写法 :ind._year;// 第1次 读第一个整数ind._month;// 第2次 读第二个整数ind._day;// 第3次 读第三个整数读取时,默认跳过空白空格、回车、Tab以空白作为分隔标记 const 成员函数定义在类的成员函数括号后面加 const这个函数就是const成员函数classDate{public:// const成员函数voidPrint()const{cout_year-_month-_dayendl;// _year 2026; // ❌ 报错不能修改成员变量}private:int_year;int_month;int_day;};本质修饰隐藏的 this 指针普通成员函数void Print(Date* this)const 成员函数void Print(const Date* this)this 指针变成const Date*不能通过 this 修改对象里面的成员核心规则const 成员函数内不能修改类的成员变量const 成员函数内可以调用其他 const 成员函数不能调用普通成员函数普通成员函数会有可能修改对象普通对象可以调用 const 成员函数const 对象只能调用 const 成员函数不能调用普通成员函数(权限不可以放大)最重要考点const 对象不允许调用有可能修改自身的函数 取地址运算符重载取地址运算符重载分为普通取地址运算符重载和 const 取地址运算符重载⼀般这两个函数编译器⾃动⽣成的就可以够我们⽤了不需要去显⽰实现除⾮⼀些很特殊的场景⽐如我们不想让别⼈取到当前类对象的地址就可以⾃⼰实现⼀份胡乱返回⼀个地址// 取地址运算符重载classDate{public:Date*operator(){returnthis;// return nullptr;}public:constDate*operator()const{returnthis;// return nullptr;}private:int_year;// 年int_month;// 月int_day;// 日};