【C++】类和对象--类的6个默认成员函数
目录
- 1.类的6个默认成员函数
- 2.构造函数
- 2.1概念
- 2.2特性
- 3.析构函数
- 3.1概念
- 3.2特性
- 4.拷贝构造函数
- 4.1概念
- 4.2特征
- 5.赋值运算符重载
- 5.1运算符重载
- 5.2赋值运算符重载
- 5.3前置++和后置++重载
- 5.4流插入和流提取运算符重载
- 6.const成员
- 7.取地址重载和const取地址操作符重载
1.类的6个默认成员函数
默认成员函数:用户没有显示实现,编译器会生成的成员函数称为默认成员函数。
如果一个类中什么成员都没有,简称为空类。
但空类中真的是什么都没有吗?并不是的,任何一个类在我们不写的情况下,都会自动生成下面6个默认成员函数。
class Date{};
- 构造函数: 完成初始化工作
- 析构函数: 完成清理工作
- 拷贝构造函数: 使用同类对象初始化创建对象
- 赋值重载: 把一个对象赋值给另一个对象
- 取地址操作符重载: 对普通对象取地址
- const取地址操作符重载: 对const修饰的对象取地址

2.构造函数
2.1概念
对于下面的Date类:
class Date
{
public:void Init(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 today1;today1.Init(2023,1,16);today1.Print();Date today2;today2.Init(2023, 1, 17);today2.Print();return 0;
}
对于Date类,可以通过Init公有方法给对象设置日期,但如果每次创建对象时都调用该方法设置信息,未免有点麻烦,那能否在对象创建时,就将信息设置进去呢?
我们就需要一个函数:保证对象被创造出来就被初始化了。
C++的构造函数提供了这个功能:
构造函数是一个特殊的成员函数,名字与类名相同,创建类类型对象时由编译器自动调用,以保证每个数据成员都有一个合适的初始值,并且在对象整个生命周期内只调用一次。
2.2特性
构造函数是特殊的成员函数,需要注意的是,构造函数虽然名称叫构造,但是构造函数的主要任务并不是开空间创建对象,而是初始化对象。
其特征如下:
-
函数名与类名相同。
-
无返回值(也不用写void)
-
对象实例化时编译器自动调用对应的构造函数。

-
构造函数可以重载。(一个类可以有多个构造函数)
class Date { public:Date(){cout << "自定义默认构造函数" << endl;}//Date(int year = 1, int month= 2, int day = 3)//{// cout << "自定义全缺省默认构造函数" << endl;//}//Date(int year, int month, int day = 1)//{// cout << "自定义半缺省构造函数" << endl;//}Date(int year, int month, int day){cout << "自定义构造函数" << endl;} private:int _year;int _month;int _day; };int main() {Date today(2023, 2, 6);return 0; } -
无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数一个类中只能有一个。
原因: 这两个构造函数虽然满足重载,但编译器无法调用,存在歧义。如上面代码的第一个无参构造函数和第二个注释的全缺省的构造函数,所以默认构造函数一个类只能有一个。(非默认构造函数也只能有一个,如第三个半缺省构造函数和第四个构造函数,需要传参,同时存在会有歧义)
注意: 更具构造函数需不需要传参数,我们将其分为两种
- 默认构造函数: 无参构造函数、全缺省构造函数,我们没写编译器默认生成的构造函数(下一条)(这些不需要传参数的构造函数,都认为是默认构造函数)
- 传参构造函数: 不缺省构造函数、全缺省构造函数
- 创建对象时,调用默认构造函数不要在对象后加括号(加括号后编译器会将其看作函数的声明,而不是创建的对象)。调用传参的构造函数在对象后加括号加参数。
-
如果类没有显示定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦用户显示定义编译器将不再生成。(构造函数可以重载,有很多种,只要我们写一种,编译器就不会默认生成构造函数)
注意: 如下,创建对象时不带括号,调用的是默认构造函数,带括号后跟参数,调用传参构造函数。
如下图类中已经定义了构造函数,编译器不会在自动生成默认构造函数。

在增加默认构造函数后,正常运行

关于编译器生成的默认构造函数,很多人会疑惑:不实现构造函数的情况下,编译器会生成默认的构造函数。但是看起来默认构造函数又没什么用?
如下面的代码,today对象调用了编译器生成的默认构造函数,但是today对象的三个成员变量_day/_month/_year,依然是随机数,也就是说在这里编译器生成的默认构造函数并没有什么用?

先介绍一点,C++将类型分为以下两种:
- 内置类型: 语言提供的数据类型,如:int、char…
- 自定义类型: 我们使用struct、class、union等自己定义的类型
如果一个类中存在自定义类型的成员变量,需要使用该成员变量对应类的默认构造函数来初始化,否则无法通过。这也就是默认构造函数存在的意义。
-
自定义类型的成员变量对应类存在默认构造函数
class A { public:A(){cout << "A" << endl;} private:int a;int b; };class Date { public:Date(){cout << "默认构造函数" << endl;}Date(int year, int month, int day){cout << "传参构造函数" << endl;} private:int _year;int _month;int _day;A a1; };int main() {Date today;return 0; }
-
自定义类型的成员变量对应类不存在默认构造函数
class A { public:A(int c){cout << "A" << endl;} private:int a;int b; };class Date { public:Date(){cout << "默认构造函数" << endl;}Date(int year, int month, int day){cout << "传参构造函数" << endl;} private:int _year;int _month;int _day;A a1; };int main() {Date today;return 0; }
注意: 在C++11中针对内置成员不初始化的缺陷,又打了补丁,即:内置类型成员变量在类中声明时可以给默认值。
class A
{
public:void Print(){cout << _a << " " << _b << endl;}
private:int _a = 10;int _b = 20;
};class Date
{
public:void Print(){a1.Print();}private:int _year = 2000;int _month = 1;int _day = 1;A a1;
};
int main()
{Date today;today.Print();return 0;
}

- 此为缺省值,当构造函数没有初始化成员变量时,成员变量的值即为该缺省值,若初始化,以构造函数为主(如下面代码,初始化了一个变量,该变量就以构造函数初始化为主,其他成员变量为缺省值)
class A
{
public:A(){_a = 40;}void Print(){cout << _a << " " << _b << endl;}
private:int _a = 10;int _b = 20;
};class Date
{
public:void Print(){a1.Print();}private:int _year = 2000;int _month = 1;int _day = 1;A a1;
};
int main()
{Date today;today.Print();return 0;
}

3.析构函数
3.1概念
我们知道了对象创建时需要构造函数来初始化,那对象销毁时又需要什么呢?
析构函数: 与构造函数相反,析构函数不是完成对对象本身的销毁,局部对象销毁工作是由编译器完成的。而对象在销毁时自动调用析构函数,完成对象中资源的清理工作。
我们创建一个对象,它是在对象生命周期结束后,对应函数的栈帧销毁时一并销毁,而析构函数是在销毁前函数自动调用,对该对象的资源做清理清空对象的空间或将申请的空间还给编译器。
对于清理工作,我们必须要做,否则可能会造成内存泄漏,而我们又常常忘记这一操作,于是C++增加了这么一个函数。
3.2特性
-
析构函数名是在类名前加上字符**~**(取反符号)
-
无参数也无返回值
-
一个类只能有一个析构函数。若未显示定义,系统会自动生成默认的析构函数。注意:析构函数不能重载
-
对象生命周期结束时,C++编译系统自动调用析构函数。
我们编写如下代码,向内存申请空间,利用析构函数释放对应的空间。
class Stack { public:Stack(){ArrStack = (int*)malloc(sizeof(int) * 4);if(!ArrStack)//下图中未写{preeor("malloc fail!");exit(-1);}_size = 4;_top = 0;}~Stack(){if (ArrStack){free(ArrStack);ArrStack = nullptr;_size = 0;_top = 0;}} private:int* ArrStack;int _size;int _top; };int main() {Stack st;return 0; }
-
如果类中没有申请资源时,析构函数可以不写,直接使用编译器生成默认析构函数,比如Date类;有资源申请时,一定要写,否则会造成资源泄漏。
如下面的代码,当我们对同一个类创建两个变量时,构造函数的执行顺序为:s1、s2,而函数是一种栈的形式,创建变量就是压栈,s1先入栈,s2后入栈,销毁时,s2先出栈,s1后出栈,析构函数的调用顺序为:s2、s1
class Stack
{
public:Stack(int num){ArrStack = (int*)malloc(sizeof(int) * num);if(!ArrStack)//下图中未写{preeor("malloc fail!");exit(-1);}_size = 4;_top = 0;}~Stack(){if (ArrStack){free(ArrStack);ArrStack = nullptr;_size = 0;_top = 0;}}
private:int* ArrStack;int _size;int _top;
};int main()
{Stack s1(10);Stack s1(40);return 0;
}
观察下图this->_size的变化

当一个类中有自定义类型的成员变量,那再销毁这个类创建的对象时,会调用该类中自定义类型的成员变量的析构函数
-
写析构函数
class A { public:~A(){cout << "A" << endl;} private:int a;int b; };class Date { public:~Date(){cout << "Date" << endl;} private:int _year;int _month;int _day;A a1; }; int main() {Date today;return 0; }
-
不写析构函数
class A { public:~A(){cout << "A" << endl;} private:int a;int b; };class Date { public:private:int _year;int _month;int _day;A a1; }; int main() {Date today;return 0; }

注意:
- 默认生成构造函数和默认生成析构函数,对内置类型不处理,处理自定义类型。(有些编译器会,但那时编译器的个人行为,和C++的语法无关)
4.拷贝构造函数
4.1概念
拷贝构造函数: 只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰),在用已存在的类类型对象创建新对象时由编译器自动调用。
该函数功能为将一个对象的数据赋值给另一个对象,发生拷贝时编译器就会调用该函数,如下:
class Date
{
public:Date(int year,int month,int day){_year = year;_month = month;_day = day;}Date(const Date& d)//拷贝构造函数{_year = d._year;_month = d._month;_day = d._day;cout << "拷贝构造函数" << endl;}private:int _year;int _month;int _day;
};void test(Date d)//调用拷贝构造函数
{}int main()
{Date today1(2023,2,7);Date today2(today1);//调用拷贝构造函数test(today1);return 0;
}

4.2特征
-
拷贝构造函数是构造函数的一个重载形式。
-
拷贝构造函数的参数只有一个 且 必须是类类型对象的引用 ,使用传参方式编译器会直接报错 ,因为会引发无穷递归调用。
如果不使用引用,代码如下:
class Date { public:Date(const Date d)//拷贝构造函数{_year = d._year;_month = d._month;_day = d._day;}private:int _year;int _month;int _day; };这样的拷贝构造函数,我们在调用它时会发生拷贝,而需要拷贝我们就要调用拷贝构造函数,这就会形参死循环,因为要用你我调用你,而想要调用你就要用你,编译器不会允许这样的事情发生。

如上图,将对象d1的数据拷贝到d2,需要调用拷贝构造函数,而调用的过程形参发生拷贝又要调用拷贝构造函数,就这样一直下去,很明显这是不行的。
所以在这里我们要使用引用,如下:
Date(const Date& d)//拷贝构造函数{_year = d._year;_month = d._month;_day = d._day;}在第一次调用的时候,使用d给对象起别名,就不用再调用其他拷贝构造函数。
对于这个函数建议使用
const修饰,防止我们在写这个函数时不小心写错,使对象的成员变量发生改变。 -
**若未显示定义,编译器会生成默认的拷贝构造函数。**默认的拷贝构造函数对象按内存存储按字节序完成拷贝,这种拷贝叫浅拷贝,或值拷贝。
即上面的Date类对象,若发生浅拷贝只是将一个对象所占空间内所有成员变量的值拷贝到另一个对象的成员变量,这么做看起来似乎很合理其实不然,对于内置类型,这么做当然没有问题,但如栈这样的数据结构,是万万不能的。如下面栈的代码
class Stack { public:Stack(size_t capacity = 10){_array = (int*)malloc(int* sizeof(int));if (nullptr == _array){perror("malloc申请空间失败");return;}_size = 0;_capacity = capacity;}void Push(const DataType& data){// CheckCapacity();_array[_size] = data;_size++;}~Stack(){if (_array){free(_array);_array = nullptr;_capacity = 0;_size = 0;}} private:int *_array;size_t _size;size_t _capacity; }; int main() {Stack s1;s1.Push(1);s1.Push(2);s1.Push(3);s1.Push(4);Stack s2(s1);return 0; }
这样程序必定会发生错误。
如果想要让程序正确运行我们,需要我们自己编写拷贝构造函数,也就是深拷贝,让他们每个对象的的成员变量在面对这种情况时都有自己独立的空间,而不是共用一块空间。
这也是拷贝构造函数存在的意义,编译器只能做浅拷贝的工作,若果一个对象的拷贝需要使用深拷贝,就需要程序员手动来完成这个任务,这也是C语言存在的缺陷,C++的很好的弥补了这一点。
修改后的栈代码如下:
class Stack { public:Stack(size_t capacity = 10){_array = (int*)malloc(capacity * sizeof(int));if (nullptr == _array){perror("malloc申请空间失败");return;}_size = 0;_capacity = capacity;}Stack(const Stack& st){_array = (int*)malloc(sizeof(int) * st._capacity);if (_array == nullptr){perror("malloc申请空间失败");return;}for (int i = 0; i < st._size; i++){_array[i] = st._array[i];}_size = st._size;}void Push(const int& data){// CheckCapacity();_array[_size] = data;_size++;}~Stack(){if (_array){free(_array);_array = nullptr;_capacity = 0;_size = 0;}} private:int* _array;size_t _size;size_t _capacity; }; int main() {Stack s1;s1.Push(1);s1.Push(2);s1.Push(3);s1.Push(4);Stack s2(s1);return 0; }所以如果类中没有涉及资源申请时,拷贝构造函数是否写都可以,若涉及到资源申请时,则拷贝构造函数是一定要写的,否则就是浅拷贝。
-
拷贝构造函数调用频率最多的三种场景场景如下
- 使用以存在的对象创建新对象
- 函数参数类型为类类型对象
- 函数返回值类型为类类型对象

通过这些我们也可以看出,拷贝在编写代码中是一个平常的事情,但其消耗的资源却不少,所以在实际使用中,如果可以使用引用,尽量使用引用,减少计算机消耗,创出更优得程序。
5.赋值运算符重载
5.1运算符重载
C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数, 也具有其返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。
在C++中类的封装性是做的很好的,如果想在类和类之间进行比较,拷贝等操作需要在类内调用函数,而对应普通的内置类型,只需要使用简单的运算符即可完成,C++规定可以将部分运算符重载来完成这个功能,增强了代码的可读性。
函数名字为:关键字operator后面接需要重载的运算符符号。
函数原型:返回值类型 operator操作符(参数列表)
注意:
- 不能通过连接其他符号来创建新的操作符:比如operator@
- 重载操作符必须有一个类类型参数
- 用于内置类型的运算符,其含义不能改变,例如:内置的整形+,不能改变其含义
- 作为类成员函数重载时,其形参看起来比操作数数目少1.因为成员函数的第一个参数为隐藏的this
.* :: sizeof ?: .注意以上5个运算符不能重载。在这个经常在笔试选择题中出现。
如下代码,若运算符重载函数作用域为全局,那类的成员变量必须为公有的,这样封装性就无法保证
class Date
{
public:Date(int year = 2000, int month = 1, int day = 1){_year = year;_month = month;_day = day;}//成员变量变为公有,才能使类外访问
//private:int _year;int _month;int _day;
};bool operator==(const Date& d1, const Date& d2)
{return d1._year == d2._year && d1._month == d2._month&& d1._day == d2._day;
}bool test()
{Date today1(2023, 2, 7);Date today2;return today1 == today2;
}
这里我们可以使用友元解决,也可以将运算符重载函数放入类中,我们一般将其放入类中。
class Date
{
public:Date(int year = 2000, int month = 1, int day = 1){_year = year;_month = month;_day = day;}bool operator==(const Date& d1){return _year == d1._year && _month == d1._month&& _day == d1._day;}private:int _year;int _month;int _day;
};bool test()
{Date today1(2023, 2, 7);Date today2;//today1.operator==(today2)return today1 == today2;
}
在调用成员函数时,编译器会自动将调用的对象作为this指针传递,我们只要写入一个参数即可。
注意:
-
在使用时需要注意运算符优先级,如下面使用运算符重载需使用括号
cout << (today1 == today2) << endl; -
运算符重载中,如果有多个参数,第一参数为左操作数,第二个参数为右操作数,以此类推。如上面的代码,第一个参数为today1,为左操作数,由该对象调用运算符重载函数,第二参数today2即为参数。
5.2赋值运算符重载
赋值运算符如果不自己实现,编译器会默认生成,只有赋值和取地址是这样的,其它的自定义类型需要使用,就要我们自己写。(取地址在下面)
赋值运算符重载格式:
- 参数类型: const Typedef&,传递引用可以提高传参效率
- 返回值类型: Typedef&,返回引用可以提高返回得效率,有返回值目的是为了支持连续赋值。
- 检测是否自己给自己赋值
- **返回*this:**要符合连续赋值得含义
class Date
{
public:Date(int year = 2000, int month = 1, int day = 1){_year = year;_month = month;_day = day;}Date& operator=(const Date& d){if (this != &d)//检测是否自己给自己赋值{_year = d._year;_month = d._month;_day = d._day;}return *this;//返回*this}private:int _year;int _month;int _day;
};
赋值运算符只能重载成类得成员函数不能重载成全局函数
class Date
{
public:Date(int year = 2000, int month = 1, int day = 1){_year = year;_month = month;_day = day;}//private:int _year;int _month;int _day;
};//全局函数不能用`this`指针,需要给两个参数
Date& operator=(const Date& d1, const Date& d2)
{if (d1 != &d2)//检测是否自己给自己赋值{d1._year = d2._year;d1._month = d2._month;d1._day = d2._day;}return d1;//返回*this
}
- 其中为了访问类中得成员变量,将其公有化,失去了封装性。
- 这样得函数注定编译失败,其中赋值运算符没有实现,则编译器会在类中自己实现一个默认的赋值运算符,而在调用得时候,我们自己实现了一个,编译器又实现了一个这就产生冲突。
所以,赋值运算符重载只能是类的成员函数。
上面已经讲了,如果我们没有自己写,编译器会自己实现一个默认的赋值运算符重载,在运行是是以值得方式逐字节拷贝。 上面得拷贝构造函数中,编译器自己默认创建的拷贝构造函数也是相同的,只能进行浅拷贝,只能拷贝值无法为其分配内存,但赋值运算符重载还是有一点不同的,它初始化需要分配空间的时候,会先为创建的对象分配空间,之后在使用赋值运算符,将分配好的空间舍弃,存入其他对象的空间地址。
如下代码:
// 这里会发现下面的程序会崩溃掉?这里就需要我们以后讲的深拷贝去解决。
class Stack
{
public:Stack(size_t capacity = 10){_array = (int*)malloc(capacity * sizeof(int));if (nullptr == _array){perror("malloc申请空间失败");return;}_size = 0;_capacity = capacity;}void Push(const int& data){// CheckCapacity();_array[_size] = data;_size++;}~Stack(){if (_array){free(_array);_array = nullptr;_capacity = 0;_size = 0;}}
private:int* _array;size_t _size;size_t _capacity;
};
int main()
{Stack s1;s1.Push(1);s1.Push(2);s1.Push(3);s1.Push(4);Stack s2;s2 = s1;return 0;
}

我们要注意,如果类中未涉及到资源管理,赋值运算符是否实现都可以;一旦涉及到资源管理则必须要实现。
5.3前置++和后置++重载
对于前置++,我们按照正常的运算符重载模式写即可,但记得返回类型需要使用类类型&,将修改后的对象返回。
class Date
{
public:Date(int year = 2000, int month = 1, int day = 1){_year = year;_month = month;_day = day;}Date& operator++(){_year += 1;return *this;}private:int _year;int _month;int _day;
};
int main()
{Date today(2023, 2, 7);Date d;d = ++today; //d:2024.2,7 today:2024,2,7return 0;
}
至于后置++,为了可以让两个函数实现重载,规定增加一个int类型的参数,作为区分。
注意:前置++是先++后使用,所以可以直接返回其修改后的对象,对于后置++是先使用后++,所以返回的应该是未修改的对象,我们可以在修改原对象前对其进行拷贝,然后修改原对象,返回时直接返回之前拷贝的对象,这样原对象即改变了,使用的也是未改变的对象,符合后置++
class Date
{
public:Date(int year = 2000, int month = 1, int day = 1){_year = year;_month = month;_day = day;}Date operator++(){Date& temp(*this);_year += 1;return temp;}private:int _year;int _month;int _day;
};int main()
{Date today(2023, 2, 7);Date d;d = today++; //d:2023,2,7 today:2024,2,7return 0;
}
5.4流插入和流提取运算符重载
在C++中,我们输出和输入一个数据通常是通过cout、cin,它们两其实就是一个类对象,重载了<<、>>两个运算符,所以输入、输出其实就是调用两个运算符重载函数。

如上图,它们的类型分别为ostream、istream,存放在iostream这个头文件中,而C++库内定义的东西都存放在std这个命名空间内,所以我们每次开头需要写这两行代码。
对于内置类型,如下:
int a = 10;
double b = 10.0;
cout << a;
cout << b;
通过函数重载调用不同的运算符函数,将其打印。
下面我们一起来看一下这两个运算符是如何重载的。
流提取
在类中定义:
class Date
{
public:Date(int year = 2000, int month = 1, int day = 1){_year = year;_month = month;_day = day;}void operator<<(ostream& out){//下面就是输出内置类型的值,流提取调用头文件<iostream>内的out << _year << "年" << _month << "月" << _day << "日" << endl;}private:int _year;int _month;int _day;
};int main()
{Date today;//第一个参数为左操作数,第二个参数为右操作数,由创建的对象调用类内的重载函数//today.operator<<(cout)today << cout;return 0;
}

我们看到,函数的使用形式是today << cout;,类对象抢占第一个参数,一定在左边,cout在右边,这么写肯定不符合我们平常的习惯,如果要将cout放在第一个位置,我们需要将函数在全局定义。
class Date
{
public:friend ostream& operator<<(ostream& out, const Date& d);Date(int year = 2000, int month = 1, int day = 1){_year = year;_month = month;_day = day;}private:int _year;int _month;int _day;
};//不对对象的成员变量做修改,最好使用const修饰,防止写错,发生错误
ostream& operator<<(ostream& out,const Date& d)
{out << d._year << "年" << d._month << "月" << d._day << "日" << endl;return out;
}int main()
{Date today;cout << today;return 0;
}

如上面的代码,我们函数变为全局后,很好的解决了位置的问题,但我们又无法访问类中的成员变量,这里有三种方法,我们使用第一种
- 使该函数变为类的友元函数,在类中public作用域下,使用friend修饰函数的声明,即可在该函数内使用对应类的对象调用成员变量。
- 增加接口,在类中创建输出函数,调用对应函数即可得到对应的成员变量值,对象在类外无法访问成员变量,但可以访问对外开发的函数。(java喜欢这么做)
- 删除private作用域,这样成员变量就可以访问。(不建议这么做,破坏封装性)
为了防止出现下面的情况,以此要输出多个对象的值,我们需要使重载的函数返回cout,使函数可以正常运行。
cout << d1 << d2 << d3 << endl;
//cout << d1 //调用重载函数,调用后返回cout继续执行
//cout << d2 //同时,运行后返回cout
//..
//cout << endl; //与重载的类型不匹配,调用头文件内的函数
流插入
class Date
{
public:friend ostream& operator<<(ostream& out, const Date& d);friend istream& operator>>(istream& in, Date& d);Date(int year = 2000, int month = 1, int day = 1){_year = year;_month = month;_day = day;}private:int _year;int _month;int _day;
};ostream& operator<<(ostream& out, const Date& d)
{out << d._year << "年" << d._month << "月" << d._day << "日" << endl;return out;
}//需要改变对象的成员变量,不能使用const修饰
istream& operator>>(istream& in, Date& d)
{in >> d._year >> d._month >> d._day;return in;
}int main()
{Date today;cin >> today;cout << today;return 0;
}

如上面的代码与流提取相似。
6.const成员
如下面的代码,是否可以正常运行呢?
class Date
{
public:Date(int year=2000,int month = 1,int day = 1){_year = year;_month = month;_day = day;}void Print(){cout << "Print" << endl;}
private:int _year;int _month;int _day;
};int main()
{const Date d;d.Print();return 0;
}
它不能正常运行,因为对象d使用const修饰了,它的值是无法改的(该对象的成员变量无法修改)。在调用成员函数时,编译器默认传过去的值为Date* const this,this指针表示对象本身,意味着在此函数内成员变量可以改变,这产生了冲突。更简单的说,这就是将原本只能读的对象变成可读可写,无视其权限。
想要解决这个问题,只要使用const修饰*this使其无法改变即可,而this又是编译器默认的,它是被隐藏着的不好修改,C++给出了如下方法,在成员函数的括号后直接加const即表示修饰*this,如下
void Print() const{cout << "Print" << endl;}
如果我们使用为被修饰的const对象调用被const修饰的成员函数,这时可以的,原本对象就可以通过调用成员函数修改和读取,现在只是传过去只能使成员函数读取这没有问题。
class Date
{
public:Date(int year=2000,int month = 1,int day = 1){_year = year;_month = month;_day = day;}void Print() const{cout << "Print" << endl;}
private:int _year;int _month;int _day;
};int main()
{Date d;d.Print();return 0;
}
同理:在类中成员函数是可以相互调用的,但被const修饰的成员函数无法调用没有被修饰的,因为被修饰的成员函数所函数*this指针是无法改变的,而没有被修饰的是可以改变的,const失去了作用,这种写法是错误的。而没有被修饰的成员函数是可以调用被修饰的,这属于即可读又可写的情况向只可读的情况发展,没有改变语法。
注意:
-
类内部不改变成员变量的成员函数,最好加上const,防止数据被修改
-
一般会在下面的场景用到const成员
class Date { public:Date(int year=2000,int month = 1,int day = 1){_year = year;_month = month;_day = day;}void Print() const{cout << _year << endl;} private:int _year;int _month;int _day; };void test(const Date& d) {d.Print(); }int main() {Date td;test(td);return 0; }我们在创建对象之初一般不为其修饰cosnt,但我们会经常将对象作为实参传递给其他函数,如果形参被const修饰,那在这个函数内它只能被读,无法修改意味着调用的成员函数也必须被const修饰。
-
const这种写法只针对成员函数
-
若定义和声明分离,需要修饰const时,定义和声明都要修饰
-
成员函数被const修饰和不被修饰构成const重载
void Print() const{cout << _year << endl;}void Print(){cout << _year << endl;}一个形参为
Date* const this,一个为const Date* const this,形参不同满足重载 -
若是成员函数被const修饰注意它的返回值类型,若返回的是成员变量,也需要修饰const,否则权限发生变化,编译会出错
7.取地址重载和const取地址操作符重载
取地址重载和const取地址操作符重载是最后两个编译器默认生成的成员函数,我们一般不会去写它,而是直接去使用编译器默认生成的。
class Date
{
public:Date(int year=2000,int month = 1,int day = 1){_year = year;_month = month;_day = day;}
private:int _year;int _month;int _day;
};int main()
{Date d1;const Date d2;cout << &d1 << endl;cout << &d2 << endl;return 0;
}

我们如果想要写出来也可以,如下:
class Date
{
public:Date(int year=2000,int month = 1,int day = 1){_year = year;_month = month;_day = day;}Date* operator&()//取地址重载{return this;}const Date* operator&() const //const取地址操作符重载{return this;}
private:int _year;int _month;int _day;
};int main()
{Date d1;const Date d2;cout << &d1 << endl;cout << &d2 << endl;return 0;
}

两个使用的场景不同,取地址重载用在取一般的对象的地址,const取地址操作符重载用在取被const修饰的对象的地址。
相关文章:
【C++】类和对象--类的6个默认成员函数
目录1.类的6个默认成员函数2.构造函数2.1概念2.2特性3.析构函数3.1概念3.2特性4.拷贝构造函数4.1概念4.2特征5.赋值运算符重载5.1运算符重载5.2赋值运算符重载5.3前置和后置重载5.4流插入和流提取运算符重载6.const成员7.取地址重载和const取地址操作符重载1.类的6个默认成员函…...
常见面试题---------如何处理MQ消息丢失的问题?
如何处理MQ消息丢失的问题? RabbitMQ丢失消息分为如下几种情况: 生产者丢消息: 生产者将数据发送到RabbitMQ的时候,可能在传输过程中因为网络等问题而将数据弄丢了。 RabbitMQ自己丢消息: 如果没有开启RabbitMQ的持久化&#x…...
十四、Linux网络:高级IO
目录 五种IO模型 同步IO 阻塞IO 非阻塞IO 信号驱动IO IO多路转接 异步IO...
带你走进API安全的知识海洋
Part1什么是API API(Application Programming Interface,应用程序接口)是一些预先定义的接口(如函数、HTTP接口),或指软件系统不同组成部分衔接的约定。用来提供应用程序与开发人员基于某软件或硬件得以访…...
【Java】TCP的三次握手和四次挥手
三次握手 TCP三次握手是一个经典的面试题,它指的是TCP在传递数据之前需要进行三次交互才能正式建立连接,并进行数据传递。(客户端主动发起的)TCP之所以需要三次握手是因为TCP双方都是全双工的。 什么是全双工? TCP任何…...
JUC并发编程
1.什么是JUC java.util工具包、包、分类 业务:普通业务线程代码 Thread Runable: 没有返回值、效率相比Callable相对较低。 2.线程和进程 进程:一个程序,QQ.exe Music.exe 程序的集合 一个进程往往可以包含多个线程,至少包含一个…...
概率统计·假设检验【正态总体均值的假设检验、正态总体方差的假设检验】
均值假设检验定义 2类错误 第1类错误(弃真):当原假设H0为真,观察值却落入拒绝域,因而拒 绝H0这类错误是“以真为假” 犯第一类错误的概率显著性水平α第2类错误(取伪):当原假设H0不…...
如何预测机组设备健康状态?你可能需要这套解决方案
1. 应用场景随机振动[注1]会发生在工业物联网的各个场景中,包括产线机组设备的运行、运输设备的移动、试验仪器的运行等等。通过分析采集到的振动信号可以预估设备的疲劳年限、及时知晓设备已发生的异常以及预测未来仪器可能发生的异常等等。本篇教程会提供给有该方…...
C++类和对象:面向对象编程的核心。| 面向对象还编什么程啊,活该你是单身狗。
👑专栏内容:C学习笔记⛪个人主页:子夜的星的主页💕座右铭:日拱一卒,功不唐捐 文章目录一、前言二、面向对象编程三、类和对象1、类的引入2、类的定义Ⅰ、声明和定义在一起Ⅱ、声明和定义分开Ⅲ、成员变量命…...
CUDA虚拟内存管理
CUDA中的虚拟内存管理 文章目录CUDA中的虚拟内存管理1. Introduction2. Query for support3. Allocating Physical Memory3.1. Shareable Memory Allocations3.2. Memory Type3.2.1. Compressible Memory4. Reserving a Virtual Address Range5. Virtual Aliasing Support6. Ma…...
线程池小结
什么是线程池 线程池其实就是一种多线程处理形式,处理过程中可以将任务添加到队列中,然后在创建线程后自动启动这些任务。这里的线程就是我们前面学过的线程,这里的任务就是我们前面学过的实现了Runnable或Callable接口的实例对象; 为什么使用线程池 …...
vue3状态管理模式 Pinia
状态管理库 Pinia是Vue的专属状态管理库,它允许你跨组件或页面共享状态 1:安装与使用pinia 1.1 安装语法:npm install pinia1.2 创建一个pinia(根存储)并将其传递给应用程序 import { createApp } from vue import…...
python基于django的自媒体社区交流平台
自媒体社区平台采用python技术,基于django框架,mysql数据库进行开发,实现了以下功能: 本系统主要包括管理员,用户,商家和普通管理员四个角色组成,主要包括以下功能: 1;前台:首页、需求通告、优质案例、帮助中心、意见反馈、个人中心、后台管理…...
Python中类和对象(2)
1.继承 Python 的类是支持继承的:它可以使用现有类的所有功能,并在无需重新编写代码的情况下对这些功能进行扩展。 通过继承创建的新类称为 “子类”,被继承的类称为 “父类”、“基类” 或 “超类”。 继承语法是将父类写在子类类名后面的…...
SpringMvc入门
Spring与Web环境的集成 1.ApplicationContext应用上下文的获取方式 分析 之前获取应用上下文对象每次都是从容器中获取,编写时都需要new ClasspathXmlApplicationContext(Spring配置文件),这样的弊端就是配置加载多次应用上下文就创建多次。 在Web项目…...
设计模式之单例模式(C++)
作者:翟天保Steven 版权声明:著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处 一、单例模式是什么? 单例模式是一种创建型的软件设计模式,在工程项目中非常常见。通过单例模式的设计&am…...
贪心算法(基础)
目录 一、什么是贪心? (一)以教室调度问题为例 1. 问题 2. 具体做法如下 3. 因此将在这间教室上如下三堂课 4. 结论 (二)贪心算法介绍 1. 贪心算法一般解题步骤 二、最优装载问题 (一…...
【九宫格坐标排列 Objective-C语言】
一、这个案例做好之后的效果如图: 1.这个下载是可以点击的,当你点击之后,弹出一个框,过一会儿,框框自动消失,这里变成“已安装” 2.那么,我现在先问大家一句话:大家认为在这一个应用里面,它包含几个控件, 3个,哪3个:一个是图片框,一个是Label,一个是按钮, 这…...
Tomcat简介
目录 一、Tomcat简介 二、下载安装Tomcat 三、利用Tomcat部署静态页面 一、Tomcat简介 Tomcat是一个HTTP服务器,可以按照HTTP的格式来解析请求来调用用户指定的相关代码然后按照HTTP的格式来构造返回数据。 二、下载安装Tomcat 进入Tomcat官网选择与自己电脑…...
Python基础及函数解读(深度学习)
一、语句1.加注释单行注释:(1)在代码上面加注释: # 后面跟一个空格(2)在代码后面加注释:和代码相距两个空格, # 后面再跟一个空格多行注释:按住shift 点击三次"&am…...
内存分配函数malloc kmalloc vmalloc
内存分配函数malloc kmalloc vmalloc malloc实现步骤: 1)请求大小调整:首先,malloc 需要调整用户请求的大小,以适应内部数据结构(例如,可能需要存储额外的元数据)。通常,这包括对齐调整,确保分配的内存地址满足特定硬件要求(如对齐到8字节或16字节边界)。 2)空闲…...
RocketMQ延迟消息机制
两种延迟消息 RocketMQ中提供了两种延迟消息机制 指定固定的延迟级别 通过在Message中设定一个MessageDelayLevel参数,对应18个预设的延迟级别指定时间点的延迟级别 通过在Message中设定一个DeliverTimeMS指定一个Long类型表示的具体时间点。到了时间点后…...
3.3.1_1 检错编码(奇偶校验码)
从这节课开始,我们会探讨数据链路层的差错控制功能,差错控制功能的主要目标是要发现并且解决一个帧内部的位错误,我们需要使用特殊的编码技术去发现帧内部的位错误,当我们发现位错误之后,通常来说有两种解决方案。第一…...
基于当前项目通过npm包形式暴露公共组件
1.package.sjon文件配置 其中xh-flowable就是暴露出去的npm包名 2.创建tpyes文件夹,并新增内容 3.创建package文件夹...
成都鼎讯硬核科技!雷达目标与干扰模拟器,以卓越性能制胜电磁频谱战
在现代战争中,电磁频谱已成为继陆、海、空、天之后的 “第五维战场”,雷达作为电磁频谱领域的关键装备,其干扰与抗干扰能力的较量,直接影响着战争的胜负走向。由成都鼎讯科技匠心打造的雷达目标与干扰模拟器,凭借数字射…...
Web 架构之 CDN 加速原理与落地实践
文章目录 一、思维导图二、正文内容(一)CDN 基础概念1. 定义2. 组成部分 (二)CDN 加速原理1. 请求路由2. 内容缓存3. 内容更新 (三)CDN 落地实践1. 选择 CDN 服务商2. 配置 CDN3. 集成到 Web 架构 …...
Python ROS2【机器人中间件框架】 简介
销量过万TEEIS德国护膝夏天用薄款 优惠券冠生园 百花蜂蜜428g 挤压瓶纯蜂蜜巨奇严选 鞋子除臭剂360ml 多芬身体磨砂膏280g健70%-75%酒精消毒棉片湿巾1418cm 80片/袋3袋大包清洁食品用消毒 优惠券AIMORNY52朵红玫瑰永生香皂花同城配送非鲜花七夕情人节生日礼物送女友 热卖妙洁棉…...
使用Matplotlib创建炫酷的3D散点图:数据可视化的新维度
文章目录 基础实现代码代码解析进阶技巧1. 自定义点的大小和颜色2. 添加图例和样式美化3. 真实数据应用示例实用技巧与注意事项完整示例(带样式)应用场景在数据科学和可视化领域,三维图形能为我们提供更丰富的数据洞察。本文将手把手教你如何使用Python的Matplotlib库创建引…...
接口自动化测试:HttpRunner基础
相关文档 HttpRunner V3.x中文文档 HttpRunner 用户指南 使用HttpRunner 3.x实现接口自动化测试 HttpRunner介绍 HttpRunner 是一个开源的 API 测试工具,支持 HTTP(S)/HTTP2/WebSocket/RPC 等网络协议,涵盖接口测试、性能测试、数字体验监测等测试类型…...
代码规范和架构【立芯理论一】(2025.06.08)
1、代码规范的目标 代码简洁精炼、美观,可持续性好高效率高复用,可移植性好高内聚,低耦合没有冗余规范性,代码有规可循,可以看出自己当时的思考过程特殊排版,特殊语法,特殊指令,必须…...
