C++类和对象(中)详解
1、类的默认成员函数
默认成员函数就是用户没有显式实现,编译器会自动生成的成员函数叫做默认成员函数。一个类,我们不写的情况下编译器会默认生成以下6个默认成员函数,需要注意的是这6个中最重要的是前4个,最后俩个取地址重载不重要,大家做个稍微地了解就可以了。
2、构造函数
构造函数是特殊的成员函数,需要注意的是,构造函数虽然名叫构造但是构造函数的主要任务不是开空间创建对象(我们常使用的局部对象是栈帧创建时空间就开好了),而是对象实例化时初始化对象。构造函数的本质是要替代我们以前Stack和Data类中写的init函数的功能,构造函数自动调用的特点就完美替代了init。
构造函数的特点:
#include<iostream>
using namespace std;
class Date
{
public:Date(){_year = 1;_month = 1;_day = 1;}//带参构造函数Date(int year, int month, int day){_year = year;_month = month;_day = day;}
void Print(){cout << _year << "/" << _month << "/" << _day << endl;}private:int _year;int _month;int _day;
};int main()
{Date d1;//调用默认构造函数Date d2(2025, 1, 1);//调用带参的构造函数Date d3();//这里是声明,注意:如果通过通过无参构造函数创建对象时,
//对象后面不用跟括号,否则编译器无法区分这里是函数声明还是实例化对象。d1.Print();d1.Print();return 0;
}
1、函数名与类名相同
2、无返回值(返回值啥都不需要给,也不需要写void,不要纠结,C++规定)
3、对象实例化时系统会自动调用对应的构造函数。
4、构造函数可以重载。
5、如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦用户显式定义编译器将不再生成。
6、无参构造函数、全缺省构造函数、我们不写构造时编译器默认生成的构造函数,都叫做默认构造函数。但是这三个函数有且只有一个存在,不能同时存在。无参构造函数和全缺省构造函数虽然构成函数重载,但是调用时会存在歧义。要注意很多同学会认为默认构造函数是编译器默认生成那个叫默认构造,实际上无参构造函数、全缺省构造函数也是默认构造,总结一下就是不传实参就可以调用的构造就叫默认构造。
7、我们不写,编译器默认生成的构造,对内置类型成员变量的初始化没有要求,也就是说是否初始化是不确定的,看编译器。对于自定义类型的成员变量,要求调用这个成员变量的默认构造函数初始化。如果这个成员变量,没有默认构造函数,那么就会报错,我们要初始化这个成员变量,需要用初始化列表才能解决...
(内置类型:就是语言提供的原生数据类型如:int/char/double/指针等,自定义类型:就是我们使用的class/struct等关键字自己定义的类型)
3、析构函数
析构函数与构造函数功能相反,析构函数不是完成对对象本质的销毁,比如局部对象是存在栈帧的函数结束栈帧销毁,它就释放了,不需要我们管,c++规定对象在销毁时会自动调用析构函数,完成对象中资源的清理释放工作。(后定义的先析构)
析构函数的特点:
1、析构函数名在类名前加上字符 ~ .
2、无参数无返回值。(这里和构造类似,也不需要加void)
3、一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。
4、对象生命周期结束后,系统会自动调用析构函数。
5、和构造函数类似,我们不写编译器自动生成的析构函数对内置类型成员不做处理,自定义类型成员会调用它的析构函数
6、还需要注意的是:我们显式写析构函数,对于自定义类型成员也会调用它的析构函数,也就是说,自定义类型成员无论什么情况都会自动调用析构函数。
#include<iostream>
using namespace std;typedef int STDataType;
class Stack
{
public:Stack(int n = 4){_a = (STDataType*)malloc(sizeof(STDataType) * n);if (nullptr == _a){perror("malloc 申请空间失败");return;}_capacity = n;_top = 0;}~Stack(){cout << "~Stack()" << endl;free(_a);_a = nullptr;_top = _capacity = 0;}
private:STDataType* _a;size_t _capacity;size_t _top;
};//俩个stack实现队列
class MyQueue
{
public://编译器默认生成MyQueue的析构函数调用了Stack的析构,释放的Stack内部的资源//显示写析构,也会自动调用Stack的析构private :Stack pushst;Stack popst;
};int main()
{Stack st;MyQueue mq;return 0;
}
4、拷⻉构造函数
如果一个构造函数的第一个参数是自身类类型的引用,且任何额外的参数都有默认值,则此构造函数也叫做拷贝构造函数,也就是说拷贝构造是一个特殊的构造函数。
拷贝构造的特点:
#include<iostream>
using namespace std;class Date
{
public://带参构造Date(int year=1,int month=1,int day=1){cout << "Date(int year=1,int month= 1,int day=1)" << endl;_year = year;_month = month;_day = day;}//d2(d1)Date(const Date& d){_year = d._year;_month = d._month;_month=d._day;}~Date(){cout << "~Date()" << endl;}
private:int _year;int _month;int _day;
};
int main()
{Date d1(2025, 10, 8);//构造Date d2(d1);//拷贝构造return 0;
1、拷贝构造函数是构造函数的一个重载。
2、拷贝构造函数的第一个参数必须是类类型对象的引用,使用传值方式编译器直接报错,因为语法逻辑上会引发无穷递归调用。拷贝构造函数也可以多个参数,但是第一个参数必须是类类型对象的引用,后面的参数必须有缺省值。
3、C++规定自定义类型对象进行拷贝行为必须调用拷贝构造,所以这里自定义类型传值传参和传值返回都会调用拷贝构造完成。
4、若未显式定义拷贝构造,编译器会自动生成拷贝构造函数。自动生成的拷贝构造函数对内置类型成员变量会完成值拷贝/浅拷贝(一个字节一个字节的拷贝),对自定义类型成员变量会调用它的拷贝构造。
5、像MyQueue这样的类型内部主要是⾃定义类型Stack成员,编译器⾃动⽣成的拷⻉构造会调⽤Stack的拷⻉构造,也不需要我们显⽰实现 MyQueue的拷⻉构造。这⾥还有⼀个⼩技巧,如果⼀个类显⽰实现了析构并释放资源,那么他就 需要显⽰写拷⻉构造,否则就不需要。
6、传值返回会产⽣⼀个临时对象调⽤拷⻉构造,传值引⽤返回,返回的是返回对象的别名(引⽤),没有产⽣拷⻉。但是如果返回对象是⼀个当前函数局部域的局部对象,函数结束就销毁了,那么使⽤引⽤返回是有问题的,这时的引⽤相当于⼀个野引⽤,类似⼀个野指针⼀样。传引⽤返回可以减少拷⻉,但是⼀定要确保返回对象,在当前函数结束后还在,才能⽤引⽤返回
int& f1()
{int ret = 0;return ret;
}
//返回了局部变量的引用,分析如下
//在函数int& f1()中,int ret = 0; 是局部变量,它储存在函数的栈帧中,当函数
//执行完毕后,栈帧会被销毁,ret所占用的内存空间也会被释放,此时若返回ret的引用,
//(即指向这片已经释放内存的“无效指针”,会导致程序崩溃,或者产生一个随机值。int main()
{cout << f1() << endl;return 0;
}
5、赋值运算符重载
#include <iostream>
class Date {
private:int _year;int _month;int _day;// 获取当月天数(辅助函数)int GetMonthDay(int year, int month) const {static int days[13] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};if (month == 2 && ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0))) {return 29;}return days[month];}public:// 构造函数Date(int year = 2000, int month = 1, int day = 1) : _year(year), _month(month), _day(day) {}// 赋值运算符重载(核心)Date& operator=(const Date& d) {// 1. 自我赋值检测(避免不必要的操作,也防止析构后访问内存)if (this != &d) {// 2. 拷贝数据_year = d._year;_month = d._month;_day = d._day;}// 3. 返回自身引用,支持连续赋值(如 date1 = date2 = date3;)return *this;}// 打印日期(辅助函数)void Print() const {std::cout << _year << "年" << _month << "月" << _day << "日" << std::endl;}
};int main() {Date d1(2025, 10, 8);Date d2;d2 = d1; // 调用赋值运算符重载d2.Print(); // 输出:2025年10月8日Date d3;d3 = d2 = d1; // 连续赋值(因返回引用,支持该语法)d3.Print(); // 输出:2025年10月8日return 0;
}
#include <iostream>
class Date {
private:int _year, _month, _day;// 辅助:获取当月天数(处理闰年)int GetMonthDay(int year, int month) const {static int days[] = {0,31,28,31,30,31,30,31,31,30,31,30,31};if (month == 2 && ((year%4==0&&year%100!=0) || year%400==0)) {return 29;}return days[month];}public:Date(int y=2000, int m=1, int d=1) : _year(y), _month(m), _day(d) {}// 1. 前置++重载:返回修改后的对象引用(++date)Date& operator++() {_day++; // 先修改日期// 处理月份/年份进位(如 1月31日++ → 2月1日)if (_day > GetMonthDay(_year, _month)) {_day = 1;_month++;if (_month > 12) {_month = 1;_year++;}}return *this; // 返回自身(已修改)}// 2. 后置++重载:增加int形参,返回修改前的临时对象(date++)Date operator++(int) { // int形参仅用于区分,无需传实参Date tmp(*this); // 先保存当前状态到临时对象++(*this); // 调用前置++完成修改(复用逻辑,避免冗余)return tmp; // 返回修改前的临时对象}// 打印日期void Print() const {std::cout << _year << "年" << _month << "月" << _day << "日\n";}
};// 测试
int main() {Date d(2025, 10, 8);// 前置++:先加1,再使用Date d1 = ++d;std::cout << "前置++后 d1(修改后的值):";d1.Print(); // 输出:2025年10月9日std::cout << "前置++后 d(自身已修改):";d.Print(); // 输出:2025年10月9日// 后置++:先使用,再加1Date d2 = d++;std::cout << "后置++后 d2(修改前的值):";d2.Print(); // 输出:2025年10月9日std::cout << "后置++后 d(自身已修改):";d.Print(); // 输出:2025年10月10日return 0;
}
#include <iostream>
#include <cmath>
using namespace std;// 提前声明Point类,让友元函数知道该类存在
class Point;
// 提前声明友元函数
double calculateDistance(const Point& p1, const Point& p2);class Point {
private:// 私有成员:点的横、纵坐标double x;double y;public:// 构造函数:初始化点的坐标Point(double x_val, double y_val) : x(x_val), y(y_val) {}// 关键:声明calculateDistance为当前类的友元函数friend double calculateDistance(const Point& p1, const Point& p2);
};// 友元函数定义:直接访问Point类的私有成员x和y
double calculateDistance(const Point& p1, const Point& p2) {// 两点间距离公式:√[(x2-x1)² + (y2-y1)²]double dx = p2.x - p1.x; // 直接访问私有成员xdouble dy = p2.y - p1.y; // 直接访问私有成员yreturn sqrt(dx * dx + dy * dy);
}int main() {Point p1(1.0, 2.0);Point p2(4.0, 6.0);// 调用友元函数计算距离cout << "两点距离:" << calculateDistance(p1, p2) << endl; // 输出结果:5.0return 0;
}
举例:我们创建一个关于类似于日期计算器的东西
//这个是Date.h
#pragma once
#include <assert.h>
#include <iostream>
using namespace std;
class Date
{
public:int GetMonthDay(int year, int month){assert(month > 0 && month < 13);static int monthDayArray[13] = { -1,28,31,30,31,30,31,30,31,30,31,30,31 };if (month == 2 &&( (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)))return 29;elsereturn monthDayArray[month];}void Print();Date(int year = 2025, int month = 1, int day = 1);Date operator+=(int day);Date operator+(int day);Date operator-=(int day);Date operator-(int day);//年月日与天数的减法int operator-(const Date& d);//年月日之间的减法bool operator==(const Date& d);//判断是否相同bool operator<(const Date& d);bool operator>(const Date& d);bool operator<=(const Date& d);bool operator!=(const Date& d);bool operator>=(const Date& d);//++d1Date operator++();//d1++Date operator++(int);
private:int _year;int _month;int _day;
};
//这是Date.cpp
#include "Date.h"void Date::Print()
{cout << _year << "/" << _month << "/" << _day << endl;
}
Date::Date(int year , int month , int day )
{ _year = year;_month = month;_day = day;
}Date Date :: operator+=(int day)
{_day += day;while (_day > GetMonthDay(_year, _month)){_day -= GetMonthDay(_year, _month);++_month;if (_month == 13){_month = 1;_year++;} }return *this;
}//保证原日期对象的状态不被改变,创建一个临时对象
Date Date ::operator+(int day)
{Date tmp(*this);tmp._day += day;while (tmp._day > GetMonthDay(tmp._year, tmp._month)){tmp._day -= GetMonthDay(tmp._year, tmp._month);++tmp._month;if (tmp._month ==13){tmp._month = 1;tmp._year++;}}return tmp;
}Date Date:: operator-=(int day)
{*this = *this-day;return *this;
}Date Date:: operator-(int day)
{Date tmp(*this);tmp._day -= day; while (tmp._day <= 0){--tmp._month;if (tmp._month == 0){tmp._month = 12;--tmp._year;}tmp._day += GetMonthDay(_year, _month);//获取当前月份的最大天数,
//加到_day上,完成跨月天数的调整}return *this;
}//d1-d2
int Date:: operator -(const Date& d)
{Date max = *this;Date min = d;int flag = 1;if (*this < d){max = d;min = *this;flag = -1;}int n = 0;while (min != max){++min;++n;}return n*flag;
}bool Date:: operator==(const Date& d)
{return _year == d._year&& _month == d._month&& _day == d._day;
}
bool Date ::operator<(const Date& d)
{if (_year < d._year)//年小于年就为真{return true;}else if (_year == d._year && _month < d._month)//年相同但是月小于就为真{return true;}else if (_year == d._year && _month == d._month && _day < d._day){return true;//年月都相同,日小于就为真。}else{return false;}}
bool Date:: operator>(const Date& d)
{return !(*this <= d);
}
bool Date:: operator<=(const Date& d)
{return *this < d || *this == d;
}
bool Date :: operator!=(const Date& d)
{return !(*this == d);
}
bool Date :: operator>=(const Date& d)
{return !(*this < d);
}//++d1
Date Date ::operator++()
{*this += 1;return *this;
}
//d1++
Date Date::operator++(int)
{Date tmp(*this);*this += 1;return tmp;}
//这是Test.cpp
#include "Date.h"
#include <iostream>
void test3()
{Date d1(2025, 10, 8);Date d2(2025, 11, 9);cout << (d1 < d2) << endl;cout << (d1 > d2) << endl;cout << (d1 == d2) << endl;cout << (d1 - d2) << endl;}
using namespace std;
int main()
{Date d1;Date d2(d1 + 100);Date d3(d1-=10000);d1.Print();d2.Print();d3.Print();test3();return 0;
}