跳到主要内容
C++ 类与对象详解:封装、this 指针与默认成员函数 | 极客日志
C++ 算法
C++ 类与对象详解:封装、this 指针与默认成员函数 C++ 面向对象编程核心概念,涵盖类与对象的定义、封装机制、访问限定符。讲解内存对齐规则及对象大小计算原理。深入解析 this 指针的作用域与空指针调用风险。详细介绍默认成员函数,包括构造函数、析构函数、拷贝构造(浅拷贝与深拷贝)、赋值运算符重载。最后阐述运算符重载的实现方式,特别是日期类的算术运算及流插入/提取运算符重载。
暗影行者 发布于 2026/3/24 更新于 2026/5/12 25K 浏览1. 类与对象的概念
C 语言是面向过程(功能)的语言,注重解决问题的过程、步骤;C++ 是面向对象的语言,注重对象之间的关系及其交互。面向对象是比面向功能更高级的开发方式,像 Java、C#、Python 都是面向对象的语言。其实 C++ 最早的别名是 C with classes,主要做的改进就是加入类(class)和对象(object),将现实世界类和对象映射到虚拟计算机系统。
类的思想是封装。在 C 语言中变量和函数是分离的,我们提供了接口供使用者调用,但对于取栈顶元素这个函数,有的人可能觉得就一句代码直接写了,还调什么函数啊,然后就出现了下面的代码。因为不知道 top 究竟是指向栈顶元素还是栈顶元素的下一个位置,不知道底层实现,在这里乱用很危险。
int main () {
ST st;
STInit (&st);
int top = st.a[top];
return 0 ;
}
1.1 类的定义与实例化
于是 C++ 提出将变量和函数封装到一起,封装的思想是规范的管理。用访问限定符(public, private, protected)来限定类外对类内成员变量、成员函数的访问。一般情况下,变量是私有,用户不能访问,函数是公有,用户可以使用。在这种情况下,用户只能调用已有方法,不能随意访问成员变量(又称属性)。类里面可以定义成员变量和成员函数,成员变量一般是私有,成员函数一般是公有。
class Stack {
public :
void Init (int defaultCapacity = 4 ) {
_a = (int *)malloc (sizeof (int ) * defaultCapacity);
_capacity = defaultCapacity;
_top = 0 ;
}
bool Empty () {
return _top == ;
}
{
(_top == _capacity) {
* tmp = ( *) (_a, ( ) * _capacity * );
(tmp == ) {
( );
;
}
_a = tmp;
_capacity *= ;
}
_a[_top++] = x;
}
{
(! ());
_a[_top - ];
}
{
(_a);
_a = ;
_top = ;
_capacity = ;
}
:
* _a;
_top;
_capacity;
};
{
Stack st;
st. ();
;
}
0
void Push (int x)
if
int
int
realloc
sizeof
int
2
if
NULL
perror
"realloc failed"
return
2
int Top ()
assert
Empty
return
1
void Destroy ()
free
NULL
0
0
private
int
int
int
int main ()
Init
return
0
当我们尝试访问类的私有成员变量时就编不过。我们看到成员变量是上锁的。
现阶段 private 和 protected 看作等价,但在继承中有不同,一般不用 protected。public, private, protected 的作用遇到下一个为止。class 中默认私有,struct 中默认公有。
类一般在头文件定义,在类里定义的函数一般是内联(较长或递归就不是内联,决定权在编译器手里),类里较长的函数可以先声明,在另一个文件定义。
同时也引出类域作为整体这个概念,C/C++ 可以理解为 { } 定义的是一个域,域会限制访问,变量搜索优先级(如果是在类里):局部域->类域->全局域,一般认为展开的命名空间和全局域等价,命名空间不展开不会主动去命名空间里找,局部域、全局域会影响生命周期。
类实例化出对象,对象才开了空间,对象才能存数据。举个例子,类好似设计图,实例化出来的对象好似根据设计图盖出来的一栋又一栋房子。不能用类访问数据,因为成员变量在类里仅仅做了声明。
1.2 类与对象大小计算 对象大小计算,也可以用类来计算。只计算成员变量的大小,不计算成员函数的大小,因为成员函数是所有实例化出的对象都要使用的,每个对象都要存一次函数太浪费了,所以放在公共区域了,每次调用的时候编译器会自己去找。
1.2.1 有成员变量(内存对齐) 规则如下:内存对齐,变量对齐数为 min{变量大小,默认对齐数(VS 下为 8B)},第一个变量存储位置从 0 开始计算,每个变量存储位置为自身对齐数的整数倍,整个类的大小为所有对齐数中最大值的整数倍。
int main () {
Stack st;
cout << "sizeof(Stack)=" << sizeof (Stack) << endl;
cout << "sizeof(st)=" << sizeof (st) << endl;
return 0 ;
}
sizeof (Stack)=16
sizeof (st)=16
如果不内存对齐存放,就会导致读写效率下降,性能有所损失。本质是用空间换时间,大多数情况下其实空间是充足的,但是嵌入式场景开发、对时间性能要求没那么高的场景下也可以考虑空间换时间,此时默认对齐数调小一些即可。
1.2.2 无成员变量 无成员变量的情况下类的大小为 1,起到占位的作用,不开空间,如何区分实例化的不同对象呢?
1.3 this 指针 如上图所示,如果是在 C 中对栈进行操作的话,我们看到 &st 这个传参很频繁,C++ 就给出了 this 指针的概念。我们在用对象去调用类里的成员函数时不需要传对象的地址,并且是不允许我们传参!由编译器来传,this 指针就是对象的指针,是形参,与普通参数一样存放在栈上。VS2022 对指针进行优化,对象的地址存放在寄存器 rcx 中。
bool Empty () {
return _top == 0 ;
}
bool Empty (Stack* this ) {
return this ->_top == 0 ;
}
int main () {
Stack st;
st.Init ();
st.Push (1 );
st.Push (2 );
st.Push (3 );
st.Push (4 );
st.Destroy ();
return 0 ;
}
那么就引申出来,方法不是从对象内部进行访问的。我们来看两道题:
问题在于 p 时空指针,使用空指针是运行时错误,编译没问题。1 是正常运行,2 是运行崩溃,因为首先我们根据上图看到,成员函数的地址和对象地址没有必然关联,访问成员函数不是从对象内部去访问的,1 中我们访问 Print 的时候只是去特定的地址调用 Print,做了输出,自然没问题;2 中在调用 Print 时用到了 p 指向对象的成员变量,这时候要解引用访问,自然崩溃。
class A {
public :
void Print () {
cout << "Print()" << endl;
}
private :
int _a;
};
int main () {
A* p = nullptr ;
p->Print ();
return 0 ;
}
class A {
public :
void PrintA () {
cout << _a << endl;
}
private :
int _a;
};
int main () {
A* p = nullptr ;
p->PrintA ();
return 0 ;
}
下列有关 this 指针使用方法的叙述正确的是( )
A.保证基类保护成员在子类中可以被访问
B.保证基类私有成员在子类中可以被访问
C.保证基类公有成员在子类中可以被访问
D.保证每个对象拥有自己的数据成员,但共享处理这些数据的代码
答案:D
B.不可以,C 在子类和类外都可以,this 是对象的指针,用于区分不同对象的私有数据;A 正确,但和 this 无关
下面描述错误的是 ( )
A.this 指针是非静态成员函数的隐含形参
B.每个非静态的成员函数都有一个 this 指针
C.this 指针是存在对象里面的
D.this 指针可以为空
答案:C
this 指针只能在类的非静态成员函数内部进行使用
2. 默认成员函数 有些时候我们可能会忘了初始化和销毁,前者导致随机值,运行错误;后者可能导致内存泄漏亦或者比较繁琐。于是引入默认成员函数。
2.1 定义
2.2 分类 创建对象、销毁对象不是函数是系统来完成的,局部变量存在依赖于函数栈帧,变量创建和销毁是在创建、销毁函数栈帧时由系统完成的。
2.2.1 构造函数 class Stack {
public :
Stack (int defaultCapacity = 4 ) {
_a = (int *)malloc (sizeof (int ) * defaultCapacity);
_capacity = 4 ;
_top = 0 ;
}
Stack (int * a, int n) {
_a = a;
_capacity = n;
_top = 0 ;
}
...
};
int main () {
int a[] = { 1 , 2 , 3 , 4 , 5 };
Stack st1;
Stack st2 (a, 5 ) ;
return 0 ;
}
没有写构造函数,系统自动生成构造函数,使用声明的缺省值对内置变量(指针属于内置类型)进行初始化。自定义了拷贝构造函数,没有写构造函数,VS2022 不会生成构造函数。
下列关于构造函数的描述正确的是 ( )
A.构造函数可以声明返回类型
B.构造函数不可以用 private 修饰
C.构造函数必须与类名相同
D.构造函数不能带参数
答案:C
2.2.2 析构函数 ~Stack () {
free (_a);
_a = NULL ;
_top = 0 ;
_capacity = 0 ;
}
在函数 F 中,本地变量 a 和 b 的构造函数 (constructor) 和析构函数 (destructor) 的调用顺序是:( )
Class A;
Class B;
void F () {
A a;
B b;
}
答案:D
按初始化顺序构造,析构根据栈后进先出,先销毁 b,后销毁 a
设已经有 A,B,C,D4 个类的定义,程序中 A,B,C,D 析构函数调用顺序为?( )
C c;
int main () {
A a;
B b;
static D d;
return 0 ;
}
答案:B
先构建全局变量 c,按顺序构建局部变量 a, b, d,由于 d 是 static,在静态区,不在栈上,先销毁 b, a, 然后是 d, c.
2.2.3 拷贝构造函数
2.2.3.1 传参 传引用,如果传值,会导致无限递归,但是编译器一般都会检查,编不过。
接着就是无休无止的 Stack st(st1);。
以及形参一般都是 const 类型,我们形参设置,如果传过来的参数只进行读,不进行写,那就一般考虑 const 修饰,防止一个不留神就把它改了。
2.2.3.2 深拷贝与浅拷贝 接下来我们探讨一下浅拷贝与深拷贝,拷贝构造函数一般原则是浅拷贝,内置类型变量只拷贝值,自定义变量调用对应的拷贝构造函数。
我们不定义拷贝构造函数,使用系统生成的拷贝构造函数,此时是浅拷贝,st1._a 直接赋给了 st2._a,两次析构会出问题。最终要达到的目的是,动态开辟空间的数组内容拷贝过来,但是地址不一致。因为一方面,st2 会先被析构,此时释放_a 指向的空间,如果 st2 是浅拷贝,将 st1._a 赋给 st2._a,那么析构 st1 的时候还会释放_a,访问野指针;其次,如果修改 st1,也会影响 st2,这不是我们想看到的。
Stack (const Stack& st) {
_a = (int *)malloc (sizeof (int ) * st._capacity);
_capacity = st._capacity;
_top = st._top;
memcpy (_a, st._a, sizeof (int ) * st._capacity);
}
所以拷贝构造一般情况下如果有动态开辟内存,就自行写拷贝构造,如果没有动态开辟,而且包含自定义类型都写了符合需求的拷贝构造,就不需要写拷贝构造,编译器生成就够。
因为有些自定义类型拷贝代价较大,比如上面要拷贝数组,所以在传参的时候一般考虑传引用,除非是非法行为,比如返回局部变量的引用;正确性和效率取正确性,方向错了,跑再快有什么用。
假定 MyClass 为一个类,则该类的拷贝构造函数的声明语句是 ( )
A.MyClass(MyClass x)
B.MyClass &(MyClass x)
C.MyClass(MyClass &x)
D.MyClass(MyClass *x)
答案:C
2.2.2.4 赋值运算符重载
2.2.2.4.1 运算符重载 运算符重载的目的是让自定义类型能够像内置类型一样去使用操作符进行操作。自定义类型一般都无法直接使用运算符进行操作,可以理解为结构体无法直接比大小。假如我们在类内写下面的函数来比大小,但是问题是可能有命名不规范问题,写成 fun1, fun2 等无法很好区分。
提出运算符重载,运算符重载操作数等于形参数。假设我们先在全局定义,在类外操作数等于形参数(在类外访问类内 private 变量的问题,可以先将类内变量置为 public 用于测试)。
不能通过重载构建新的运算符,只能重载已有的操作符。
运算符重载,如果是赋值,必须写在类内,因为是默认成员函数,如果不写在类内,类会自动生成,和全局定义的区分不开;其它运算符重载在全局和类内都可以,但是要考虑 private。
操作数至少有一个是自定义类型,如果都是内置类型,要改变编译器自身的操作数原则吗,不可以。
五个不能重载:.* sizeof :: ?: .(笔试常考查,用*做干扰选项)。
bool operator <(const Date& d1, const Date& d2) {
if (d1. _year < d2. _year) return true ;
else if (d1. _year == d2. _year && d1. _month < d2. _month) return true ;
else if (d1. _year == d2. _year && d1. _month == d2. _month && d1. _day < d2. _day) return true ;
else return false ;
}
int main () {
Date d1 (2026 , 1 , 10 ) ;
Date d2 (2025 , 12 , 31 ) ;
operator <(d1, d2);
d1 < d2;
return 0 ;
}
汇编代码 operator<(d1,d2) 等价于 d1<d2。
但是在类内有一个隐性参数就是 this 指针,所以我们手动传的形参数要比操作数少一个,在类内定义。
Date::Date (int year, int month, int day) {
_year = year;
_month = month;
_day = day;
}
bool Date::operator <(const Date& d) const {
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) const {
return _year == d._year && _month == d._month && _day == d._day;
}
bool Date::operator >(const Date& d) const {
return !(*this < d || *this == d);
}
bool Date::operator >=(const Date& d) const {
return !(*this < d);
}
bool Date::operator <=(const Date& d) const {
return *this < d || *this == d;
}
bool Date::operator !=(const Date& d) const {
return !(*this == d);
}
2.2.2.4.2 赋值运算符重载 如果是系统生成的赋值运算符重载,处理规则和拷贝构造函数有些类似,内置变量浅拷贝,自定义变量调用对应的赋值运算符重载函数。所以赋值运算符重载一般情况下如果有动态开辟内存,就自行写赋值运算符重载,如果没有动态开辟,而且包含自定义类型都写了符合需求的赋值运算符重载,就不需要写赋值运算符重载,编译器生成就够。
Date& operator =(const Date& d) {
_year = d._year;
_month = d._month;
_day = d._day;
return *this ;
}
int main () {
int i, j, k = 1 ;
i = j = k;
Date d1 (2026 , 12 , 31 ) ;
Date d2 (2025 , 12 , 31 ) ;
Date d3;
d2 = d3 = d1;
d1 = d2;
return 0 ;
}
拷贝构造和赋值的区别,写=不一定是赋值,从定义出发:拷贝构造是用一个变量初始化另一个变量,因为拷贝构造就是构造的一种函数重载,构造就是用来初始化的;而赋值是将一个变量的值拷贝给一个已经存在的变量。
int main () {
Date d1;
Date d2;
d2 = d1;
return 0 ;
}
2.2.2.4.3 日期实例运算符重载
2.2.2.4.3.1 日期+/-天数 日期 - 日期 运算符重载,以实际意义为中心,日期加日期没有意义,但日期 + 天数、日期 - 天数、日期 - 日期有意义。
int Date::GetMonthDay (int year, int month) {
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];
}
Date& Date::operator +=(int day) {
if (day < 0 ) return this -= -day;
_day += day;
while (_day > GetMonthDay (_year, _month)) {
_day -= GetMonthDay (_year, _month);
_month++;
if (_month == 13 ) {
_year++;
_month = 1 ;
}
}
return *this ;
}
下面 const 修饰的是 this 指向的变量,因为涉及到权限的问题,如果此处没有 const,如果是 const Date 类型的 d 进行 +,其指针没办法赋给this,因为 this 是 Date,权限放大,权限可以平移、缩小,不能放大,所以此处用 const,无论是 const Date 还是 Date 都可以接收,而且符合规则,防止误改;一般不需修改的都要加 const,防止权限扩大以及误改。
这个 const 放末尾,因为 this 是不可以显式传参的,只能这样做来标明。
const Date* p, Date const* p,不能修改的是 p 内容(相当于 const 修饰*p,p 是指针,*p 就是指向的内容);Date * const p 不能修改的是 p 本身(const 修饰指针 p,指针不能改)。
但是 this 指针的类型是 Date* const this,当所指向的变量也加 const,this 的类型就是 const Date* const this。
Date Date::operator +(int day) const {
Date tmp = *this ;
tmp += day;
return tmp;
}
Date& Date::operator ++() {
*this += 1 ;
return *this ;
}
Date Date::operator ++(int ) {
Date tmp (*this ) ;
*this += 1 ;
return tmp;
}
Date& Date::operator --() {
*this -= 1 ;
return *this ;
}
Date Date::operator --(int ) {
Date tmp (*this ) ;
*this -= 1 ;
return tmp;
}
Date& Date::operator -=(int day) {
if (day < 0 ) {
return *this += -day;
}
_day -= day;
while (_day <= 0 ) {
--_month;
if (_month == 0 ) {
_year--;
_month = 12 ;
}
_day += GetMonthDay (_year, _month);
}
return *this ;
}
Date Date::operator -(int day) const {
Date tmp (*this ) ;
tmp -= day;
return tmp;
}
int Date::operator -(const Date& d) const {
Date max = *this ;
Date min = d;
int flag = 1 ;
if (*this < d) {
max = d;
min = *this ;
flag = -1 ;
}
int n = 0 ;
while (min != max) {
n++;
min++;
}
return n * flag;
}
使用函数重载来实现相同运算符不同功能的重载,学以致用。
我们来看拷贝消耗,如下图所示,更推荐先实现+=,再复用+=实现+。
日期 - 日期,可以先通过对齐年,再对齐月、日;比如 2023/3/1-2020/1/1,先将 2020/1/1 调整到 2023/1/1,中间需要特殊处理的是闰年;然后计算 2023/1/1 到 2023/3/1 相差的天数,正的 +,负的 -;但是上面不使用这种方法,一方面上面的代码简单易读,而且这种计算在 computer 面前就是小 case.
2.2.2.4.3.2 cout, cin 重载 之前我们提到过 cout 是可以自动识别类型的,查 C++ 的标准库可以看到是函数重载实现的。
现在我们考虑实现一个自定义类型的 cout,如果是下面这样,每次调用是 d.Print();。
void Date::Print () {
cout << this ->_year << "-" << this ->_month << "-" << this ->_day << endl;
}
ostream& operator <<(ostream& out);
ostream& Date::operator <<(ostream& out) {
cout << _year << "年" << _month << "月" << _day << "日" << endl;
return out;
}
int main () {
Date d1 (2023 , 2 , 4 ) ;
d1 << cout;
return 0 ;
}
定义在类内,会导致抢位置的问题,为了使自定义类型使用 cout, cin 如内置类型一般,需要在全局进行定义。
friend ostream& operator <<(ostream& out, const Date& d);
friend istream& operator >>(istream& in, Date& d);
ostream& operator <<(ostream& out, const Date& d);
istream& operator >>(istream& in, Date& d);
ostream& operator <<(ostream& out, const Date& d) {
cout << d._year << "年" << d._month << "月" << d._day << "日" << endl;
return out;
}
istream& operator >>(istream& in, Date& d) {
in >> d._year >> d._month >> d._day;
return cin;
}
int main () {
Date d1 (2023 , 2 , 4 ) ;
cout << d1;
return 0 ;
}
Date::Date (int year, int month, int day) {
if (month > 0 && month <= 12 && day > 0 && day <= GetMonthDay (year, month)) {
_year = year;
_month = month;
_day = day;
} else {
cout << "非法日期" << endl;
assert (false );
}
}
istream& operator >>(istream& in, Date& d) {
int year, month, day;
in >> year >> month >> day;
if (month > 0 && month <= 12 && day > 0 && day <= d.GetMonthDay (year, month)) {
d._year = year;
d._month = month;
d._day = day;
} else {
cout << "非法日期" << endl;
assert (false );
}
return cin;
}
#include <iostream>
using namespace std;
class Date {
public :
Date (int year, int month, int day) {
_year = year;
_month = month;
_day = day;
}
void print () {
printf ("%d-%02d-%02d\n" , _year, _month, _day);
}
int GetDay (int year, int month) {
int day[13 ] = { 0 , 31 , 28 , 31 , 30 , 31 , 30 , 31 , 31 , 30 , 31 , 30 , 31 };
if (month == 2 && (year % 400 == 0 || (year % 4 == 0 && year % 100 != 0 )))
return 29 ;
return day[month];
}
Date& operator +=(int inday) {
_day += inday;
while (_day > GetDay (_year, _month)) {
_day -= GetDay (_year, _month);
_month++;
if (_month == 13 ) {
_month = 1 ;
_year++;
}
}
return *this ;
}
private :
int _year, _month, _day;
};
int main () {
int m, year, month, day, inday;
cin >> m;
while (m--) {
cin >> year >> month >> day >> inday;
Date d (year, month, day) ;
d += inday;
d.print ();
}
}
#include <iostream>
using namespace std;
class Date {
public :
Date (int year, int month, int day) {
_year = year;
_month = month;
_day = day;
}
int GetDay (int year, int month) {
int day[13 ] = { 0 , 31 , 28 , 31 , 30 , 31 , 30 , 31 , 31 , 30 , 31 , 30 , 31 };
if (month == 2 && (year % 400 == 0 || (year % 4 == 0 && year % 100 != 0 )))
return 29 ;
return day[month];
}
int Day () {
int m = 1 , n = 0 ;
while (m < _month) {
n += GetDay (_year, m);
m++;
}
n += _day;
return n;
}
private :
int _year, _month, _day;
};
int main () {
int year, month, day;
cin >> year >> month >> day;
Date d (year, month, day) ;
cout << d.Day () << endl;
}
#include <iostream>
using namespace std;
class Date {
public :
Date (int year, int month, int day) {
_year = year;
_month = month;
_day = day;
}
int GetDay (int year, int month) {
int day[13 ] = { 0 , 31 , 28 , 31 , 30 , 31 , 30 , 31 , 31 , 30 , 31 , 30 , 31 };
if (month == 2 && (year % 400 == 0 || (year % 4 == 0 && year % 100 != 0 )))
return 29 ;
return day[month];
}
Date& operator +=(int inday) {
_day += inday;
while (_day > GetDay (_year, _month)) {
_day -= GetDay (_year, _month);
_month++;
if (_month == 13 ) {
_month = 1 ;
_year++;
}
}
return *this ;
}
Date& operator ++() {
return *this += 1 ;
}
bool operator >(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 operator ==(Date& d) {
return _year == d._year && _month == d._month && _day == d._day;
}
bool operator !=(Date& d) {
return !(*this == d);
}
int operator -(Date d) {
int n = 0 ;
Date max = *this , min = d;
if (d > *this ) {
max = d;
min = *this ;
}
while (max != min) {
n++;
++min;
}
return n + 1 ;
}
private :
int _year, _month, _day;
};
Date GetD (int date) {
int year = date / 10000 ;
int month = (date / 100 ) % 100 ;
int day = date % 100 ;
Date d (year, month, day) ;
return d;
}
int main () {
int date1, date2;
while (cin >> date1 >> date2) {
Date d1 = GetD (date1), d2 = GetD (date2);
if (d1 > d2) cout << (d1 - d2) << endl;
else cout << (d2 - d1) << endl;
}
}
#include <iostream>
using namespace std;
class Date {
public :
Date (int year, int month, int day) {
_year = year;
_month = month;
_day = day;
}
Date (int year, int inday) {
_year = year;
int month = 1 ;
while (inday > GetDay (year, month)) {
inday -= GetDay (year, month);
month++;
}
_month = month;
_day = inday;
}
void print () {
printf ("%d-%02d-%02d\n" , _year, _month, _day);
}
int GetDay (int year, int month) {
int day[13 ] = { 0 , 31 , 28 , 31 , 30 , 31 , 30 , 31 , 31 , 30 , 31 , 30 , 31 };
if (month == 2 && (year % 400 == 0 || (year % 4 == 0 && year % 100 != 0 )))
return 29 ;
return day[month];
}
private :
int _year, _month, _day;
};
int main () {
int year, inday;
while (cin >> year >> inday) {
Date d (year, inday) ;
d.print ();
}
}
static int GetMonthDay (int year, int month) ;
istream& operator >>(istream& in, Date& d) {
int year, month, day;
in >> year >> month >> day;
if (month > 0 && month <= 12 && day > 0 && day <= Date::GetMonthDay (year, month)) {
d._year = year;
d._month = month;
d._day = day;
} else {
cout << "非法日期" << endl;
assert (false );
}
return cin;
}
2.2.2.5/6 取地址运算符重载 普通对象和 const 对象取地址操作符重载,很少自己实现,系统自动生成就够用。
Date* Date::operator &() {
return this ;
}
const Date* Date::operator &() const {
return this ;
}
二者区别主要是在如果不想让获取到普通成员的指针,可以像下面这样实现:
Date* Date::operator &() {
return nullptr ;
}
假设 AA 是一个类,AA* abc () const 是该类的一个成员函数的原型。若该函数返回 this 值,当用 x.abc () 调用该成员函数后,x 的值是( )
A.可能被改变
B.已经被改变
C.受到函数调用的影响
D.不变
答案:D
相关免费在线工具 加密/解密文本 使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
Gemini 图片去水印 基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online
Base64 字符串编码/解码 将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
Base64 文件转换器 将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
Markdown转HTML 将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online
HTML转Markdown 将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online