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

C++初阶-类和对象(中)

目录

1.类的默认成员函数

2.构造函数(难度较高)

​编辑

​编辑

​编辑

3.析构函数

4.拷贝构造函数

5.赋值运算符重载

5.1运算符重载

5.2赋值运算符重载

6.取地址运算符重载

6.1const成员函数

6.2取地址运算符重载

7.总结



1.类的默认成员函数

类的默认成员函数即用户没有显式实现,编译器就会自动生成的成员函数。一个类我们不写的情况下会默认生成以下6个默认成员函数:

最重要的是前面四个函数,后面两个分别为移动构造和移动赋值,以后会讲。

默认成员函数很重要,也比较复杂。我们要从两个方面去学习:

(1)我们不写时,编译器默认生成的函数行为是什么?是否满足我们的需求。

(2)编译器默认生成的函数不满足我们的需求,我们需要自己实现,如何自己实现?

所以类和对象中主要讲的就是这四个成员函数的手动实现和运算符重载。

2.构造函数(难度较高)

构造函数是特殊的成员函数,但构造函数主要任务并不是开空间创建对象(我们常使用的局部对象是栈帧创建时,空间就开好了),而是对象实例化时初始化对象。构造函数的本质是替代Init函数的功能,构造函数自动调用的特点就完美替代的了Init。

构造函数的特点:

(1)函数名与类名相同。

(2)无返回值(返回值啥都不要给,也不需要写void)。

(3)对象实例化时,系统会自动调用对应的构造函数。

如:

#include<iostream>
using namespace std;
class Date
{
public:Date(){_year = 1;_month = 1;_day = 1;}void Print(){cout << _year << "-" << _month << "-" << _day << endl;}
private:int _year;int _month;int _day;
};
int main()
{Date d1;d1.Print();return 0;
}

我们发现:在我们不主动调用这个构造函数的情况下,我们还是能进行初始化,这是构造函数一些基本特点。

那我们如果想手动传入日期怎么办?

class Date
{
public:Date(int year = 1,int month = 1,int day = 1){_year = year;_month = month;_day = day;}void Print(){cout << _year << "-" << _month << "-" << _day << endl;}
private:int _year;int _month;int _day;
};
int main()
{Date d1(2025,4,15);d1.Print();Date d2;d2.Print();return 0;
}

我们发现如果在不传参的情况下就会直接用我们的缺省值来作为参数,否则就会用我们的自己传的参数进行初始化,而我们在实例化对象的时候就在实例化对象加入所传的参数即可以把我们想要初始化的值直接传入进去。

但是我们不能在实例化对象的时候加上一个()而不传参,因为这样无法区分这是函数声明还是类的对象定义。

其次,我们写构造函数的时候能全缺省也可以不加一个形参,也可以直接不写(构造函数的第六个特点)。

(4)构造函数可以重载。

我们如果写了多个构造函数,我们的函数名是一样的,但是参数类型和参数个数都可以进行改变,如我们可以直接指定哪一年哪个月等等。

(5)如果没有显式定义构造函数,则C++编译器会自动生成一个无参的构造函数,一旦用户显式定义编译器将不再生成。

我们可以自己写,但是编译器生成的可能把初始值置为空,也可以置为随机值,如下:

class Date
{
public:/*Date(int year = 1,int month = 1,int day = 1){_year = year;_month = month;_day = day;}*/void Print(){cout << _year << "-" << _month << "-" << _day << endl;}
private:int _year;int _month;int _day;
};
int main()
{/*Date d1(2025,4,15);d1.Print();*/Date d2;d2.Print();return 0;
}

在VS编译器会生成一个很小的值,这主要取决于编译器,如果我们之后想要把值修改的话可以通

过其他函数的访问或者自己写,因为我们如果直接这样写:

class Date
{
public:void Print(){cout << _year << "-" << _month << "-" << _day << endl;}
private:int _year;int _month;int _day;
};
int main()
{Date d1(2025,4,15);d1.Print();Date d2(2025,4,15);//d2.Date(2025, 4, 15);d2.Print();return 0;
}

则会报错:

因为我们没有手动定义这个Date,所以会有报错,我们只能根据编译器自己生成的构造函数的初始化来进行初始化。所以建议自己写或者之后写一些函数来进行手动初始化。

(6)我们不写,编译器默认生成的构造,对内置类型成员变量的初始化没有要求,也就是说:是否初始化时不确定的,看编译器。对于自定义类型的变量,要求调用这个成员变量的默认构造函数初始化。如果这个成员变量,没有默认构造函数,那么就会报错,我们要初始化这个成员变量,需要用初始化列表才能解决。

内置类型可以说是编译器本来就有的类型,如:int/double/……/指针 ,自定义类型:class/struct/union/……

默认生成的构造函数不符合我们的预期,所以要我们自己写:如栈、日期这样的类。

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;}
private:STDataType* _a;size_t _capacity;size_t _top;
};

我们发现,如果我们不写构造函数的情况下,我们并不能申请内存空间去初始化栈,所以我们需要自己写这个函数。而在我们之前写过的题目中有一个题目很方便,不用写构造函数,就是:用两个栈实现一个队列,我们可以直接写出以下的类:

// 两个Stack实现队列
class MyQueue
{
public:
//编译器默认⽣成MyQueue的构造函数调⽤了Stack的构造,完成了两个成员的初始化
private:
Stack pushst;
Stack popst;
};

我们之前自定义了Stack这个类,在使用MyQueue这个类中,我们全部都是Stack类型,所以我们在调用MyQueue这个类的构造函数的时候,直接用栈这个类的构造函数就可以得到结果了,所以这是一个不用自己写的构造(也可称为不需要写默认生成就可以用的构造)。

(7)无参构造函数、全缺省构造函数、我们不写构造时编译器默认生成的构造函数都叫默认构造函数。但三个函数有且只能有一个存在,不能同时存在。总结一下就是:不传实参就可以调用的构造就叫默认构造。

但是大多数情况下我们都要自己去写默认构造函数,但是都要遵循(1)-(4)这四个特点。

3.析构函数

其功能与构造函数功能相反,析构函数不是完成对对象本身的销毁,而是完成对象中资源的清理释放工作,C++规定对象在销毁时会自动调用析构函数。析构函数功能类比我们之前Stack实现的Destroy功能(本质上就是动态申请内存空间的释放),而像Date类没有Destroy。其实就是没有资源的释放,所以严格说Date是不需要析构函数的。(也就是说只有我们向内存中动态开辟了空间的时候我们才要写析构函数的(因为编译器自动生成的析构函数就够了))。析构函数的特点:

(1)析构函数名是在类名前加上字符'~'(这不是一个横线,和我们的C语言中的按位取反是一样的符号)。

(2)无参数,无返回值。(不要加void)

(3)一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。

(4)对象的生命周期结束时,系统会自动调用析构函数。

(5)我们不写,编译器自动生成的析构函数对内置类型成员不做处理,自定义类型成员会调用它的析构函数。

如:~Date(){free(_year);free(_month);free(_day);}

这种写法是错误的,无资源释放,所以不需要析构。

如:~Stack(){free(_a);_a=nullptr;_top=_capacity=0;}

这个就是正确的写析构函数的方法,只有动态开辟资源的才需要写析构!如果成员变量是自定义类型,则如果自定义类型成员的析构函数已经满足我们使用,我们就不要写这个析构函数了,否则我们需要自己写一个析构函数。

(6)还要注意的是,我们显式写析构函数,对自定义类型成员也会调用它的析构,也就是说自定义类型成员无论如何都会自动调用析构函数。

构造函数与析构函数的好处:不用Init和Destroy了,我们也不需要主动调用了,虽然有时候需要我们自己写这两个函数,但是还是有一定的作用的。

(7)如果类中没有申请资源时,析构函数可以不写,直接使用编译器自动生成的析构函数,如:Date;如过默认生成的析构就可以用,也就不需要显式写析构,如:MyQueue;但是有资源申请时,一定要自己写析构,否则会造成资源泄露,如:Stack。

(8)一个局部域的多个对象,C++规定后定义的先析构。

由于析构函数只有一些需要注意的点,所以我在这里就不用代码演示了。

4.拷贝构造函数

如果一个构造函数的第一个参数就是自身类型的引用,且任何额外的参数都有默认值,则此构造函数也叫拷贝构造函数,也就是说拷贝构造是一个特殊的构造函数。

拷贝构造函数的特点:

(1)拷贝构造函数是构造函数的一个重载。

也就是说拷贝构造函数名也是类名,但是参数不是一样的,如:

class Date
{
public://构造函数Date(int year = 1,int month = 1,int day = 1){_year = year;_month = month;_day = day;}//拷贝构造函数Date(Date& d){_year = d._year;_month = d._month;_day = d._day;}void Print(){cout << _year << "-" << _month << "-" << _day << endl;}
private:int _year;int _month;int _day;
};
int main()
{//调用构造函数Date d1(2025, 4, 19);//调用拷贝构造函数Date d2(d1);return 0;
}

我们发现d2在实例化时传参是d1,这就是拷贝构造,而这个参数就是Date类,也就是说,我们通过已经实例化的对象作为参数去实例化另外一个对象这个过程就是拷贝构造,而拷贝构造就把开始的对象的每一个值拷贝到另外一个对象上。但是形参必须是这个类的类型对象的引用(见第二个特点)。

(2)拷贝构造函数的第一个参数必须是类类型对象的引用,使用传值的方式编译器直接报错,因为语法逻辑上会引发无穷递归调用。拷贝构造函数也可以有多个参数,但是第一个参数必须是类类型的对象的引用,后面的参数必须有缺省值!

如:void func1(Date d ){……}Date d1(2025,4,19);func1(d1);.

若我们是这种写法的话,我们就会发现:(1)还没运行就会出现:类 "Date" 的复制构造函数不能带有 "Date" 类型的参数;(2)运行时发现,有一堆的报错:

为什么?

C++规定:无论你是在传参,还是初始化时,只要用一个自定义类型对象去初始化另外一个自定义类型的对象,要调用拷贝构造。

也就是说d1和形参d在func1(d1)语句会直接去调用拷贝构造函数,而拷贝构造函数又有形参类型为自定义类型,又会调用自身的拷贝构造函数,也就是一直调用自己,所以会报错。而解决这个问题的方法就是自定义类型的拷贝构造函数的第一个参数就是自定义类型的引用。

所以之后写拷贝构造函数的时候第一个形参必须是自身类类型的对象的引用。

(3)C++规定自定义类型对象进行拷贝行为必须调用拷贝构造,所以这里自定义类型传值传参和传值返回都会调用拷贝构造完成。

要验证这个特点就是,把之前代码改为:

void func1(Date d)
{}
Date func2()
{Date d;return d;
}
int main()
{//调用构造函数Date d1(2025, 4, 19);//调用拷贝构造函数//Date d2(d1);//d2.Print();func1(d1);Date ret = func2();return 0;
}

发现最终结果会为:Date(Date& d),如果把func1(d1)这个语句去掉,则没有打印,因为在Vs2022上会对这个语句产生优化,而且会使Date ret = func2();和func2();两个句子都报错,所以我们不能在Vs2022上验证这个结论,所以传值返回会报错,因为传值返回返回的不是返回d,而是d的拷贝(产生临时对象),调用完后会销毁。得出结论:自定义类型传值传参和传值返回比内置类型付出更大的代价。因为内置类型是系统自己定义的类型,它在指定时已经完成拷贝,它的对象比较小,但自定义类型更复杂一些。所以我们应该在func2中改返回值为Date&,而且我们一般都返回的是Date&,而不是Date。

为什么用引用?

引用是取别名,在语法上可以视为没有开辟空间,且不会因为函数结束时对象销毁而报错。

推荐在形参类型前加const,因为我们可能会出现代码有误的问题,防止对象被修改了(可以增强程序的健壮性)!(一般传引用基本上加const)

如果把拷贝构造写成这样的形式:Date(const Date& d,int x = 1){},则不会有问题,但是我们必须要给第二个参数一个缺省值(规定,原因不要深究)。

(4)若未显式定义拷贝构造,编译器会⽣成⾃动⽣成拷⻉构造函数。⾃动⽣成的拷⻉构造对内置类型成员变量会完成值拷⻉/浅拷⻉(⼀个字节⼀个字节的拷⻉),对⾃定义类型成员变量会调⽤他的拷⻉构造。

在没有显式定义拷贝构造的情况下,如果没有自定义类型对象,就基本上没有问题;但是如果在有自定义类型对象的情况下,则会出大问题。如:栈。

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;}void Push(STDataType x){if (_top == _capacity){int newcapacity = _capacity * 2;STDataType* tmp = (STDataType*)realloc(_a, newcapacity *sizeof(STDataType));if (tmp == NULL){perror("realloc fail");return;}_a = tmp;_capacity = newcapacity;}_a[_top++] = x;}~Stack(){cout << "~Stack()" << endl;free(_a);_a = nullptr;_top = _capacity = 0;}
private:STDataType* _a;size_t _capacity;size_t _top;
};
int main()
{Stack st1;st1.Push(1);st1.Push(2);st1.Push(3);st1.Push(4);Stack st2(st1);return 0;
}

会报错,因为我们浅拷贝会把_a的地址也拷贝了(本质就是浅拷贝的问题),但是析构会出问题:堆数组(动态开辟的数组)空间释放两次(有st1、st2两个对象,我们在main函数销毁时会出现二者都要析构的情况,所以会出现两次释放),会报错,或者这个空间释放后又被申请了,但是又释放了结果导致这个东西的地址也没了;还有一个问题:一个改变影响另外一个。如果我们把st1出栈两次,而我们st2如果访问第三个元素,就会报错,会导致数组越界的问题。或者我们把栈顶数据修改,我们如果想用st1去访问和用st2访问,则两个结果不会相同,所以这就是浅拷贝的一个很严重的问题。所以我们现在要写深拷贝(内部有资源的就需要写深拷贝),就是把指向的资源也进行拷贝。我们需要自己再开辟一个相同的空间去存储数据。

我们在现阶段一般用以下方式来进行深拷贝:

Stack(const Stack& st)
{_a = (STDataType*)malloc(sizeof(STDataType) * st._capacity);if (nullptr == _a){perror("malloc申请空间失败!!!");return;}memcpy(_a, st._a, sizeof(STDataType) * st._top);_top = st._top;_capacity = st._capacity;
}

如果是链表我们还需要把结点什么的都拷贝过来,我们是先开辟和st一样大小的空间,再进行拷贝数据。

之后学了string才会进行更深入的理解,这个深拷贝比较简单。

如果一个类只要你必须显式的写析构函数,就要显式写拷贝构造。(都有资源的申请)

(5)像Date这样的类成员变量全是内置类型且没有指向什么资源,编译器⾃动⽣成的拷⻉构造就可以完成需要的拷⻉,所以不需要我们显⽰实现拷⻉构造。像Stack这样的类,虽然也都是内置类型,但是_a指向了资源,编译器⾃动⽣成的拷⻉构造完成的值拷⻉/浅拷⻉不符合我们的需求,所以需要我们⾃⼰实现深拷⻉(对指向的资源也进⾏拷⻉)。像MyQueue这样的类型内部主要是⾃定义类型Stack成员,编译器⾃动⽣成的拷⻉构造会调⽤Stack的拷⻉构造,也不需要我们显⽰实现
MyQueue的拷⻉构造。

(6) 传值返回会产⽣⼀个临时对象调⽤拷⻉构造,传值引⽤返回,返回的是返回对象的别名(引⽤),没有产⽣拷⻉。但是如果返回对象是⼀个当前函数局部域的局部对象,函数结束就销毁了,那么使⽤引⽤返回是有问题的,这时的引⽤相当于⼀个野引⽤,类似⼀个野指针⼀样。传引⽤返回可以减少拷⻉,但是⼀定要确保返回对象,在当前函数结束后还在,才能⽤引⽤返回。

这是之前写的代码的解释的详尽化。

5.赋值运算符重载

5.1运算符重载

(1)当运算符被⽤于类类型的对象时,C++语⾔允许我们通过运算符重载的形式指定新的含义。C++规定类类型对象使⽤运算符时,必须转换成调⽤对应运算符重载,若没有对应的运算符重载,则会编译报错。

运算符重载和函数重载意思差不多,一个东西可以用作这个,也可以用作另外一个。如,我们重载之前+只能用于内置类型之间的加法运算,而我们重载后我们就可以用于内置类型与类类型的加法运算,或者类与类之间的加法运算。

(2)运算符重载是具有特殊名字的函数,他的名字是由operator和后⾯要定义的运算符共同构成。和其他函数⼀样,它也具有其返回类型和参数列表以及函数体。

(3)重载运算符函数的参数个数和该运算符作⽤的运算对象数量⼀样多。⼀元运算符有⼀个参数,二元运算符有两个参数,二元运算符的左侧运算对象传给第⼀个参数,右侧运算对象传给第⼆个参数。

如:+需要左边和右边俩个参数,所以叫做二元运算符,所以第一个参数需要为+的左边操作数,第二个参数需要为右边的操作数。~是按位取反,则我们只有一个参数,就只要指定一个值就可以了。而重载+函数的定义为int operator+(int x,int y){return x+y;}(这样是一个示例,本来+就有这个功能,我们没必要去真的重载一下这两个int类型的加法)。

(4)如果⼀个重载运算符函数是成员函数,则它的第⼀个运算对象默认传给隐式的this指针,因此运算符重载作为成员函数时,参数⽐运算对象少⼀个。

(5)运算符重载以后,其优先级和结合性与对应的内置类型运算符保持⼀致。

(6)不能通过连接语法中没有的符号来创建新的操作符:⽐如operator@。

(7).*  :: sizeof  ?: .这5个运算符不能进行重载。

.*的用法:若我们定义了A这个类,且这个类里面还有func()函数,又typedef  void(*PF)();在主函数中用PF pf=&A::func;A obj;(obj.*pf)();所以.*是调用成员函数指针的,而obj.*pf就是相当于func。

.的用法:就是和我们用类实例化对象然后对象.成员。

(8)重载操作符⾄少有⼀个类类型参数,不能通过运算符重载改变内置类型对象的含义,如: int
operator+(int x, int y)

我们不能重载+后却是实现-的功能,也就是我们不能改变它的功能。

(9)⼀个类需要重载哪些运算符,是看哪些运算符重载后有意义,⽐如Date类重载operator-就有意义,但是重载operator+就没有意义。

(10)重载++运算符时,有前置++和后置++,运算符重载函数名都是operator++,⽆法很好的区分。C++规定,后置++重载时,增加⼀个int形参,跟前置++构成函数重载,⽅便区分。

(11)重载<<和>>时,需要重载为全局函数,因为重载为成员函数,this指针默认抢占了第⼀个形参位置,第⼀个形参位置是左侧运算对象,调⽤时就变成了 对象<<cout,不符合使⽤习惯和可读性。重载为全局函数把ostream/istream放到第⼀个形参位置就可以了,第⼆个形参位置当类类型对象。

下一讲我将用这些学过的知识来实现一个系统,就是日期管理系统,我们可以通过这个系统来进行复习之前的知识和介绍新知识,而且里面有很多运算符重载的函数,所以在这一讲我不会用很多代码来说明。

5.2赋值运算符重载

赋值运算符重载是一个默认成员函数,用于完成两个已经存在的对象直接的拷贝赋值,这里要注意的跟拷贝构造区分,拷贝构造用于一个对象拷贝初始化给另一个要创建的对象。如:

class Date
{
public://构造函数Date(int year = 1,int month = 1,int day = 1){_year = year;_month = month;_day = day;}//拷贝构造函数Date(Date& d){_year = d._year;_month = d._month;_day = d._day;cout << "Date(Date&d)" << endl;}//=运算符重载void operator=(const Date& d){_year = d._year;_month = d._month;_day=d._day;}void Print(){cout << _year << "-" << _month << "-" << _day << endl;}
private:int _year;int _month;int _day;
};
int main()
{Date d1(2024, 11, 14);Date d3(2025, 11, 14);d1 = d3;return 0;
}

特点:

(1)赋值运算符重载是⼀个运算符重载,规定必须重载为成员函数。赋值运算重载的参数建议写成const 当前类类型引⽤,否则传值传参会有拷⻉。

赋值运算符重载不支持连续赋值,因为d1=d3相当于函数调用,d2=d1=d3相当于d2把返回值拿走了,所以我们返回值类型为Date,返回值为*this,但是结合之前的说法,我们要返回的是Date&,提高效率,而*this出了这个语句后还在,所以我们用*this是没有问题的。

(2)有返回值,且建议写成当前类类型引⽤,引⽤返回可以提⾼效率,有返回值⽬的是为了⽀持连续赋值场景。

(3)没有显式实现时,编译器会⾃动⽣成⼀个默认赋值运算符重载,默认赋值运算符重载⾏为跟默认拷⻉构造函数类似,对内置类型成员变量会完成值拷⻉/浅拷⻉(⼀个字节⼀个字节的拷⻉),对⾃定义类型成员变量会调⽤他的赋值重载函数。

(4)像Date这样的类成员变量全是内置类型且没有指向什么资源,编译器⾃动⽣成的赋值运算符重载就可以完成需要的拷⻉,所以不需要我们显⽰实现赋值运算符重载。像Stack这样的类,虽然也都是内置类型,但是_a指向了资源,编译器⾃动⽣成的赋值运算符重载完成的值拷⻉/浅拷⻉不符合我们的需求,所以需要我们⾃⼰实现深拷⻉(对指向的资源也进⾏拷⻉)。像MyQueue这样的类型内部主要是⾃定义类型Stack成员,编译器⾃动⽣成的赋值运算符重载会调⽤Stack的赋值运算符重载,也不需要我们显⽰实现MyQueue的赋值运算符重载。这⾥还有⼀个⼩技巧,如果⼀个类显⽰实现了析构并释放资源,那么他就需要显⽰写赋值运算符重载,否则就不需要。

由于我们的栈是需要自动实现这个赋值运算符功能的,所以需要自己写,这是一个基本样例:

//赋值运算符重载
Stack& operator=(const Stack& st)
{//先释放原空间free(_a);//再让创建一个新空间_a = (STDataType*)malloc(sizeof(STDataType) * st._capacity);//把另外一个栈中的数据拷贝过来//我们拷贝的只有top个数据memcpy(_a, st._a, sizeof(STDataType) * st._top);//再把_top和_capacity拷贝过去,完成深拷贝_top = st._top;_capacity = st._capacity;return *this;
}

如果我们是用链表或者二叉树来进行赋值运算符重载的话,我们就需要进行深拷贝的操作,就是把所有的东西包括结点都要创建一个,这个之后会涉及到的。

6.取地址运算符重载

6.1const成员函数

(1)将const修饰的成员函数称之为const成员函数,const修饰成员函数放到成员函数参数列表后面。

如果我们这样写:

class Date
{
public://构造函数Date(int year = 1,int month = 1,int day = 1){_year = year;_month = month;_day = day;}//拷贝构造函数Date(Date& d){_year = d._year;_month = d._month;_day = d._day;cout << "Date(Date&d)" << endl;}//Date& operator+=(int t)//{//	_day += t;//	return *this;//}void Print() {cout << _year << "-" << _month << "-" << _day << endl;}
private:int _year;int _month;int _day;
};
int main()
{const Date d1(2025, 4, 20);d1.Print();//d1 += 10;return 0;
}

那么这里的d1.Print()是用不了的,如果我们用其他的成员函数基本上也用不了的,因为每一个成员函数都包含了一个this指针,this类型是Date*,而如果调用Print这个成员函数,则传地址是&d1->const Date*相当于this是传的为const Date*,所以这里涉及到权限放大的问题,所以我们要么就在d1初始化时把const去掉,或者在成员函数定义时如果有成员变量不会修改的成员函数,那么就在那个参数列表后加const,这样哪怕d1不是const Date类型也不会报错,我们发现Print函数只是打印这个对象中的成员变量,所以我们把它改为:

void Print() const
{cout << _year << "-" << _month << "-" << _day << endl;
}

但是如果我们有成员变量修改的部分则我们不要加const,而且在之后的对象成员函数调用时我们就不要调用它了(如果对象是具有常性的情况下)。

(2)const实际修饰改成员函数隐含的this指针,表明在改成员函数不能对类的任何成员进行修改,const修饰的Date类的Print成员函数,Print隐含的this指针有Date* const this改为 const Date* const this。

第二个const是修饰this的,防止this被修改,第一个const是修饰指针所指向的对象,表明this指针指向的对象是不可以被改变的,无法通过this指针来修改所指向的Date对象的成员变量。

6.2取地址运算符重载

取地址运算符重载分为普通取地址运算符重载和const取地址运算符重载,⼀般这两个函数编译器⾃动⽣成的就可以够我们⽤了,不需要去显⽰实现。除⾮⼀些很特殊的场景,⽐如我们不想让别⼈取到当前类对象的地址,就可以⾃⼰实现⼀份,胡乱返回⼀个地址。

这也是另外两个默认成员函数,这两个成员函数我们不需要自己显式实现,注意的点不是很多,所以代码我也不怎么多讲解,只要知道有这个东西就可以了。

//普通
Date* operator&()
{return this;
}
//const
const Date* operator&() const
{//返回值不可以省略const,因为会涉及到权限放大问题return this;//最后的const也不能省略(见const修饰成员函数的含义)
}

如果我们不想让外界取到地址,我们就可以返回nullptr或者一个随机的地址。

当然,还有一个要注意的点:如果我们只写一个取地址运算符重载的成员函数,另外一个是不会自动生成的!所以要么不写,要么全部都写上。

7.总结

类和对象中涉及的点太多了,远远不及我讲的这些,而且构造函数还有一些点也是没有讲完的,这些点将会放到类和对象下去讲,至于其他的补充知识也要看之后的内容。之后我会把之前学过的内容应用到我实现的系统里面,这个系统是日期管理系统,能实现日期加减天数,和日期减日期的功能,里面还会涉及到初始化列表的东西,这个会在类和对象下再讲,这个代码你们只要copy就可以了。喜欢的可以一键三连哦!

相关文章:

C++初阶-类和对象(中)

目录 1.类的默认成员函数 2.构造函数&#xff08;难度较高&#xff09; ​编辑 ​编辑 ​编辑 3.析构函数 4.拷贝构造函数 5.赋值运算符重载 5.1运算符重载 5.2赋值运算符重载 6.取地址运算符重载 6.1const成员函数 6.2取地址运算符重载 7.总结 1.类的默认成员函数…...

Linux网络UDP与TCP

基础知识 传输层 负责数据能够从发送端传输接收端。 端口号(Port)标识了一个主机上进行通信的不同的应用程序; 在 TCP/IP 协议中, 用 “源 IP”, “源端口号”, “目的 IP”, “目的端口号”, “协议号” 这样一个五元组来标识一个通信(可以通过 netstat -n 查看); 端口号范…...

23、.NET和C#有什么区别?

1、定义与范畴 .NET 定义 .NET 是一个由微软开发的开发平台&#xff08;Platform&#xff09;&#xff0c;它提供了一套完整的工具、库和运行时环境&#xff0c;用于构建各种类型的应用程序。 范畴 包括 .NET Framework、.NET Core&#xff08;现称为 .NET 5 及以上版本&a…...

Qt6离线安装过程

Qt6离线安装过程 说明解决方案联网笔记本安装qt6拷贝到离线电脑修改qtenv2.bat文件 说明 现在qt6已经不能通过离线的方式下载安装包安装了&#xff0c;只能通过登陆的方式在线安装&#xff0c;但是&#xff0c;又有离线安装运行的需求&#xff0c;那么怎么办呢&#xff1f;请跟…...

如何在 Go 中创建和部署 AWS Lambda 函数

AWS Lambda 是一个无服务器计算平台&#xff0c;您可以使用自己喜欢的编程语言编写代码&#xff0c;无需担心设置虚拟机。 您只需为 Lambda 函数的调用次数和运行时间&#xff08;毫秒&#xff09;付费。 我们大多数人都了解 JavaScript 和 Python&#xff0c;但它们的内存效率…...

【后端】【Django】Django 模型中的 `clean()` 方法详解:数据校验的最后防线

Django 模型中的 clean() 方法详解&#xff1a;数据校验的最后防线 在 Django 的模型系统中&#xff0c;我们经常使用字段级别的校验器&#xff08;validators&#xff09;来约束某个字段的取值范围。但当校验逻辑涉及多个字段之间的关系时&#xff0c;字段级别校验就无能为力…...

内存管理详解(曼波脑图超详细版!)

(✪ω✪)曼波来解答三连问啦&#xff01;准备好内存知识大礼包了吗&#xff1f;(≧∇≦)&#xff89; ━━━━━━━━━━━━━ ฅ^•ω•^ฅ ━━━━━━━━━━━ 一、内存分配详解 (๑>ᴗ<๑) (1) 栈内存 → 像便签纸&#x1f4dd; void calculate() {int a …...

【2025最新redis数据结构之Hypeloglog介绍】关于Hypeloglog

HyperLogLog (HLL) 算法深度解析 一、HLL 基本概念 HyperLogLog 是一种用于基数统计&#xff08;distinct counting&#xff09;的概率算法&#xff0c;能够在极小内存占用下&#xff08;通常只需几KB&#xff09;估算巨大数据集的基数&#xff08;不重复元素数量&#xff09…...

软考复习——知识点软件开发

开发模型 瀑布模型 各个活动规定为线性顺序连接的若干阶段的模型。是一种理想的现象开发模型&#xff0c;缺乏灵活性&#xff0c;无法理解软件需求不明确或不准确的问题。适用于需求明确的项目。 演化模型 从初始的原型逐步演化成最终软件产品&#xff0c;特别适用于对软件…...

关于AI:记忆、身份和锁死

作者&#xff1a;John Battelle 当生成式AI迎来投资热潮、产品发布和炒作高峰时&#xff0c;我们大多数人在奔向“下一个大事件”的过程中&#xff0c;忽略了一个深层次的缺陷。我们现在主流的AI产品和服务&#xff08;比如OpenAI、Google和Microsoft的产品&#xff09;都是通过…...

2024新版仿蓝奏云网盘源码,已修复已知BUG,样式风格美化,可正常运营生产

说起网盘源码&#xff0c;网络上出现的也很多&#xff0c;不过可真正正能够用于运营的少之又少。今天将的蓝奏云网盘源码&#xff0c;其实网络上也有&#xff0c;不过是残缺版&#xff0c;bug很多。我今天分享的仿蓝奏云模板是经过长时间测试修复后的源码&#xff0c;源码实测可…...

OJ - 设计循环队列

622. 设计循环队列 - 力扣&#xff08;LeetCode&#xff09; 循环队列是一种线性数据结构&#xff0c;其操作表现基于 FIFO&#xff08;先进先出&#xff09;原则&#xff0c;并且队尾被连接在队首之后以形成一个循环。它也被称为“环形缓冲器”。 循环队列的一个好处是我们可…...

实战指南:封装Faster-Whisper为FastAPI接口并实现高并发处理-附整合包

实战指南&#xff1a;封装Faster-Whisper为FastAPI接口并实现高并发处理-附整合包 「faster-whisper」 链接&#xff1a;https://pan.quark.cn/s/d4ddffb1b196 标题下面提供一个完整的示例&#xff0c;说明如何使用 FastAPI 封装 faster-whisper 接口&#xff0c;对外提供 RES…...

011数论——算法备赛

素数筛 给定n, 求2~n内的所有素数 埃氏筛 利用素数的定义&#xff0c; 输出素数2&#xff0c;然后筛掉2的倍数&#xff0c;得 {2,3,5,7,9,11,13&#xff0c;…}输出素数3&#xff0c;然后筛掉3的倍数&#xff0c;得 {2,3,5,7,11,13&#xff0c;…} 继续上述步骤&#xff0…...

C语言之机房机位预约系统

&#x1f31f; 嗨&#xff0c;我是LucianaiB&#xff01; &#x1f30d; 总有人间一两风&#xff0c;填我十万八千梦。 &#x1f680; 路漫漫其修远兮&#xff0c;吾将上下而求索。 C语言之机房机位预约系统 目录 博客&#xff1a;机房机位预约系统设计与实现 系统功能概述…...

中间件--ClickHouse-14--案例-3-其他案例思路概述

1、广告投放效果分析 案例背景&#xff1a; 一家广告平台需要分析广告的点击、曝光、转化等数据&#xff0c;以优化广告投放策略并提升 ROI&#xff08;投资回报率&#xff09;。 解决方案&#xff1a; 数据接入&#xff1a;将广告投放相关的数据&#xff08;如曝光、点击、…...

saas是什么?它做什么用的。及和Paas和laas有什么区别

Saas是什么&#xff1f;它做什么用的。及和Paas和laas有什么区别 提示&#xff1a;帮帮志会陆续更新非常多的IT技术知识&#xff0c;希望分享的内容对您有用。本章分享的是行业内容。前后每一小节的内容是存在的有&#xff1a;学习and理解的关联性&#xff0c;希望对您有用~ 文…...

Qt基础005(文件操作后续)

文章目录 QFileDialogQFileDialog打开开发案例QFileDialog保存开发案例实现文件打开功能开发流程打开功能优化 QComboBoxQListExtraSelection 简介 QFileDialog QFileDialog打开开发案例 #include <QApplication> #include <QFileDialog> #include <QStringLi…...

松灵Cobot Magic双臂具身遥操机器人(基于ROS的定位建图与协同导航技术)

摘要 本文以CobotMagic可移动协作机器人为研究对象&#xff0c;从硬件架构设计、软件系统架构、多传感器融合定位建图系统、智能导航系统协同机制四个维度&#xff0c;深入解析机器人系统工作原理。重点研究多传感器融合定位建图系统实现原理&#xff0c;结合实测数据验证系统…...

AI——神经网络以及TensorFlow使用

文章目录 一、TensorFlow安装二、张量、变量及其操作1、张量Tensor2、变量 三、tf.keras介绍1、使用tf.keras构建我们的模型2、激活函数1、sigmoid/logistics函数2、tanh函数3、RELU函数4、LeakReLu5、SoftMax6、如何选择激活函数 3、参数初始化1、bias偏置初始化2、weight权重…...

实现对象之间的序列化和反序列化

1.什么是序列化&#xff1f; 在项目的开发中&#xff0c;为了让前端更好的分析后端返回的结果&#xff0c;我们一般会将返回的信息进行序列化&#xff0c;序列化就是将返回对象的状态信息转换为一种标准化的格式&#xff0c;方便在网络中传输也方便打印日志时号观察&#xff0…...

QML中日期处理类

在 QML 中处理日期和时间主要使用 JavaScript 的 Date 对象以及 Qt 提供的一些相关功能。以下是常用的日期处理方式&#xff1a; 1. JavaScript Date 对象 QML 可以直接使用 JavaScript 的 Date 对象&#xff1a; qml // 创建当前日期时间 var currentDate new Date()// 创…...

基于docker-java封装的工具类

基于docker-java封装的工具类 背景环境工具类 背景 写OJ系统时需要用docker作为代码沙箱使用&#xff0c;顺手封装了一个工具类&#xff0c;给自己做个笔记&#xff0c;如果可以的话也希望帮助到其他人。 环境 docker 26.1.4docker-java 3.4.2docker-java-transport-httpcli…...

windows docker desktop 无法访问容器端口映射

为什么使用docker desktop访问映射的端口失败&#xff0c;而其端口对应的服务是正常的&#xff1f; 常见问题&#xff0c;容器的防火墙没有关闭&#xff01;&#xff01;&#xff01; 以centos7为例&#xff0c;默认情况下防火墙处于开启状态&#xff1a; 这下访问就OK了...

ReentrantReadWriteLock读写锁

一、锁的分类 这里不会对Java中大部分的分类都聊清楚&#xff0c;主要把 **互斥&#xff0c;共享** 这种分类聊清楚。 Java中的互斥锁&#xff0c;synchronized&#xff0c;ReentrantLock这种都是互斥锁。一个线程持有锁操作时&#xff0c;其他线程都需要等待前面的线程释放锁…...

Vue.js 入门教程

Vue.js 入门教程 Vue.js 是一款非常流行的前端 JavaScript 框架&#xff0c;适用于构建用户界面。它的设计思想是尽可能简单、灵活&#xff0c;易于与其他库或现有项目整合。本文将从最基础的概念开始&#xff0c;逐步引导你学习 Vue.js。 一、Vue.js 基础概念 1.1 什么是 V…...

解决Docker 配置 daemon.json文件后无法生效

vim /etc/docker/daemon.json 在daemon中配置一下dns {"registry-mirrors": ["https://docker.m.daocloud.io","https://hub-mirror.c.163.com","https://dockerproxy.com","https://docker.mirrors.ustc.edu.cn","ht…...

wpf stylet框架 关于View与viewmodel自动关联绑定的问题

1.1 命名规则 Aview 对应 AVIewModel, 文件夹 views 和 viewmodels 1.2 需要注册服务 //RootViewModel是主窗口 public class Bootstrapper : Bootstrapper<RootViewModel>{/// <summary>/// 配置IoC容器。为数据共享创建服务/// </summary…...

车载测试用例开发-如何平衡用例覆盖度和测试效率的方法论

1 摘要 在进行车载测试用例编写时&#xff0c;会遇到多个条件导致用例排列组合爆炸的情况&#xff0c;但是为了产品测试质量&#xff0c;我们又不得不保证用例设计的需求覆盖度&#xff0c;这样又会使得测试周期非常长。我们如何平衡效率和测试质量&#xff1f;本文进行了一些…...

leetcode(01)森林中的兔子

今天开始记录刷题的过程&#xff0c;每天记录自己刷题的题目和自己的解法&#xff0c;欢迎朋友们给出更多更好的解法。 森林中的兔子 森林中有未知数量的兔子&#xff0c;提问其中若干只兔子“还有多少只兔子与你&#xff08;被提问的兔子&#xff09;颜色相同”。将答案收集到…...

人工智能-机器学习其他技术(决策树,异常检测,主成分分析)

决策树 一种对实例进行分类的树形结构&#xff0c;通过多层判断区分目标所属类别 本质&#xff1a;通过多层判断&#xff0c;从训练数据集中归纳出一组分类规则 优点&#xff1a; 计算量校&#xff0c;运算速度快 易于理解 缺点&#xff1a; 忽略属性间的相关性 样本分布不均时…...

AIGC通信架构深度优化指南

AIGC通信架构深度优化指南 标题&#xff1a;《百亿参数大模型如何高效通信&#xff1f;揭秘AIGC系统的协议层设计艺术》 副标题&#xff1a;从分布式训练到多模态推理&#xff0c;构建高可靠AI通信系统 1. AIGC典型通信场景 1.1 分布式模型训练参数同步 sequenceDiagram训练…...

精益数据分析(7/126):打破创业幻想,拥抱数据驱动

精益数据分析&#xff08;7/126&#xff09;&#xff1a;打破创业幻想&#xff0c;拥抱数据驱动 在创业的道路上&#xff0c;我们都怀揣着梦想&#xff0c;但往往容易陷入自我编织的幻想中。我希望通过和大家一起学习《精益数据分析》&#xff0c;能帮助我们更清醒地认识创业过…...

Android Gradle多渠道打包

目录 1.多渠道打包是什么2.为什么需要多渠道打包3.多渠道配置VariantproductFlavorsbuildTypes 3.构建变体组合关于组合 4.渠道过滤5.渠道资源资源文件资源合并规则代码文件SourceSets 6. 渠道依赖项7.渠道统计meta-dataBuildConfig 8.管理渠道 1.多渠道打包是什么 多聚道打包…...

Day58 | 179. 最大数、316. 去除重复字母、334. 递增的三元子序列

179. 最大数 题目链接&#xff1a;179. 最大数 - 力扣&#xff08;LeetCode&#xff09; 题目难度&#xff1a;中等 代码&#xff1a; class Solution {public String largestNumber(int[] nums) {String[] strsnew String[nums.length];for(int i0;i<nums.length;i)str…...

LabVIEW发电机励磁系统远程诊断

变流器在风电系统中承担电能转换与控制的关键角色。它将发电机输出的低频、可变交流&#xff0c;通过整流、逆变等环节转为频率、电压稳定的交流&#xff0c;以满足电网接入要求&#xff1b;同时&#xff0c;根据实时风速调整发电机转速&#xff0c;实现最大功率追踪。 ​ 在某…...

性能比拼: Go vs Bun

本内容是对知名性能评测博主 Anton Putra Go (Golang) vs. Bun: Performance (Latency - Throughput - Saturation - Availability) 内容的翻译与整理, 有适当删减, 相关指标和结论以原作为准 我对 Bun 在之前的基准测试中的出色表现感到惊讶&#xff0c;因此我决定将它与 Go …...

Kubernetes相关的名词解释Dashboard界面(6)

什么是Kubernetes Dashboard&#xff1f; Kubernetes Dashboard 是一个基于 Web 的用户界面&#xff0c;用于管理 Kubernetes 集群。它是 Kubernetes 官方提供的可视化工具&#xff0c;允许用户通过直观的图形界面而不是命令行来部署、管理和监控集群中的应用程序。 Dashboard…...

Linux网络编程 TCP---并发服务器:多进程架构与端口复用技术实战指南

知识点1【并发服务器—多进程版】 并发服务器&#xff1a;服务器可以同时服务多个客户端 首先复习一下服务器的创建过程&#xff08;如下图&#xff09; 1、监听套接字&#xff08;套接字→绑定→监听&#xff08;连接队列&#xff09;&#xff09; 2、利用accept从连接队列…...

(done) 吴恩达版提示词工程 1. 引言

url: https://www.bilibili.com/video/BV1Z14y1Z7LJ/?spm_id_from333.337.search-card.all.click&vd_source7a1a0bc74158c6993c7355c5490fc600 LLM 有两种&#xff1a; 1.基础 LLM&#xff0c;通过文本训练数据预测后面的内容。 这种 LLM 当你给它提问&#xff1a;What is…...

uniapp微信小程序实现sse

微信小程序实现sse 注&#xff1a;因为微信小程序不支持sse请求&#xff0c;因为后台给的是分包的流&#xff0c;所以我们就使用接受流的方式&#xff0c;一直接受&#xff0c;然后把接受的数据拿取使用。这里还是使用uniapp的原生请求。 上代码 //注意&#xff1a;一定要下…...

【TeamFlow】3 Rust 与 WebAssembly (Wasm) 深度应用指南

WebAssembly 是一种低级的类汇编语言&#xff0c;能在现代浏览器中高效执行。Rust 因其无 GC、内存安全和卓越性能&#xff0c;成为编译到 Wasm 的理想语言。 一、为什么选择 Rust Wasm 性能优势&#xff1a;Rust 生成的 Wasm 代码执行效率接近原生 内存安全&#xff1a;避免…...

C 语言的未来:在变革中坚守与前行

C 语言&#xff0c;作为编程语言领域的一位 “老将”&#xff0c;自诞生以来就一直扮演着至关重要的角色。历经数十年的发展&#xff0c;它的影响力依然广泛而深远。在科技飞速发展的今天&#xff0c;新的编程语言如雨后春笋般不断涌现&#xff0c;C 语言的未来发展走向成为了众…...

SQL注入之information_schema表

1 information_schema表介绍&#xff1a; information_schema表是一个MySQL的系统数据库&#xff0c;他里面包含了所有数据库的表名 SQL注入中最常见利用的系统数据库&#xff0c;经常利用系统数据库配合union联合查询来获取数据库相关信息&#xff0c;因为系统数据库中所有信…...

android framework开发的技能要求

作为Android Framework开发工程师,需要具备深入的系统底层理解能力和对Android架构的全面认知。以下是核心技能要求,分为技术能力和软实力两大方向: 一、核心技术能力 Android系统架构深度掌握 Binder机制:理解Binder驱动、ServiceManager、AIDL跨进程通信原理,能分析Bind…...

AWS EC2完全指南:如何快速搭建高性能云服务器?

一、什么是AWS EC2&#xff1f;云时代的虚拟服务器革命 AWS Elastic Compute Cloud&#xff08;EC2&#xff09;作为全球领先的云服务器解决方案&#xff0c;正在重新定义虚拟服务器的可能性。与传统VPS相比&#xff0c;EC2提供&#xff1a; 秒级弹性扩展&#xff1a;CPU/RAM按…...

go环境安装mac

下载go安装包&#xff1a;https://golang.google.cn/dl/ 找到对应自己环境的版本下载。 注意有二进制的包&#xff0c;也有图形界面安装的包。图形界面直接傻瓜式点就行了。 二进制的按照下面操作&#xff1a; 1、下载二进制包。 2、将下载的二进制包解压至 /usr/local目录…...

Python实现对大批量Word文档进行批量自动化排版(15)

前言 本文是该专栏的第15篇,后面会持续分享Python办公自动化干货知识,记得关注。 在本专栏上一篇文章《Python实现对目标Word文档进行自动化排版【4万字精讲】(14)》中,笔者已经详细介绍“基于Python,实现对目标docx格式的word文档进行自动化排版”的实战教学(文章附带…...

嵌入式面试题解析:二维数组,内容与总线,存储格式

在嵌入式系统领域&#xff0c;扎实掌握基础概念是应对面试的关键。本文通过典型面试题&#xff0c;详细解析核心知识&#xff0c;梳理易错点&#xff0c;并补充常见面试题&#xff0c;助力新手快速入门。 一、二维数组元素地址计算 题目 若二维数组 arr[0..M-1][0..N-1] 的首…...

【iOS】alloc init new底层原理

目录 前言 alloc alloc核心操作 cls->instanceSize(extraBytes) calloc obj->initInstanceIsa init 类方法&#xff1a; 实例方法&#xff1a; new 前言 笔者最近在进行对OC语言源码的学习&#xff0c;学习源码的过程中经常会出现一些从来没有遇见过的函数&…...