博主的一次转变(感谢阅读): 起初写博客只是一时兴起,纯当帮助复习知识点,所创作的博文能得到大家的喜欢也让我很高兴。然而最近深刻的感受到了大篇冗长的博客真的写得太累了,且这段时间收到不少粉丝反馈建议挑一些难点来写博客,博主思考再三后决定采纳大家的建议,所以 接下来的博客不会再事无巨细的将相关知识点”全盘列出“,而是会选择一些博主认为值得分析的知识点进行分享,许多相对简单的基础知识点相信读者直接查阅相关书籍也一定能读懂。
前言:本文将介绍类对象中的this指针、“拷贝构造函数为什么不能用值传递?”、“const权限缩放问题”。
回顾一下之前咱们通过C语言实现栈时,用到的一些函数声明
入栈操作的函数声明:
void StackPush(ST* ps,STDataType elem);
出栈操作的函数声明
void StackPop(ST* ps);
栈的销毁函数声明
void StackDestroy(ST* ps);
在栈函数的实现过程当中,无论我们是在实现哪一个函数,都必须传递一个“栈指针”过去,以确保后续过程能顺利执行。
那么,在C++扩充的类对象当中,还需要这样传递“栈指针”吗?
举个例子:
class Date
{
public:
void Display()
{
cout << _year << " " << _month << " " << _day << endl;
}
void SetDate(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
};
private:
int _year;
int _month;
int _day;
};
int main()
{
Date a1, a2;
a1.SetDate(2000, 5, 12);
a2.SetDate(2006, 6, 19);
a1.Display();
a2.Display();
return 0;
}
在上面的例子中,Date类中有Display和SetDate两个成员函数,函数体中并没有关于不同对象的区分,那当a1调用SetDate函数的时候,该函数是如何知道我们是要设置s1还是s2呢???
答案就是this指针
其实,我么在调用函数时候,系统便已经悄悄帮我们转换了,上例中,我们调用Display时候,编译器会隐式传给Display一个Date* 指针,即实际传参为a1.Display(&a1). 而定义Display时候,编译器也同样会隐式传递一个Date* 指针
这个隐式传递的指针就是this指针,其本质就是一个成员函数的形参,由编译器通过ecx寄存器自动传递。
this指针只是cpp编译器分配给“非静态成员函数”的隐藏指针参数,注意哦!注意哦!!是非静态成员函数,static修饰的静态成员函数就没有this指针了哦!
this指针相关面经
1.this指针存在内存的哪个区域?
既然this指针本质是成员函数的形参,自然是存放在内存中的栈区啦!
2.this指针可以为空吗?
可以的,但要分情况,请看以下代码:
class A
{
public:
void PrintA()
{
cout<<_a<<endl;
}
void Show()
{
cout<<"Show()"<<endl;
}
private:
int _a;
};
int main()
{
A* p = nullptr;
p->PrintA(); //操作1
p->Show(); //操作2
}
结果:PrintA() 调用时会崩溃,Show() 调用时顺利进行
分析:
1.调用PrintA时候,this指针访问了_a,访问成员值的本质是this->成员
,但是p是空的,还传给this,导致this也是nullptr,但是空指针是不能访问其成员的呀!所以程序崩溃。
2.调用show成员函数时,不会特意去访问p所指向的空间,也就不存在空指针解引用的问题!只是简单把p传递给了this指针,但是成员函数没有解引用this指针哦!没有哦! (成员函数的地址不在对象中存储,而是存储在公共代码段)
以日期类为例:
class Date
{
public:
Date(int year=1943,int month=8,int day=8)
{
_year = year;
_month = month;
_day = day;
}
//×,传值调用拷贝构造,无限递归
Date(const Date d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1;
Date d2(d1);
return 0;
}
使用传值方式的话会引发无穷递归调用
如何解决?
传引用调用拷贝构造(其实传指针也行,但没必要,引用效率更高,少了一次拷贝)
Date(const Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
对于日期类确实如此,但是看下Stack类:
class Stack
{
public:
Stack(int capacity = 4)
{
_a = (int*)malloc(sizeof(int)*capacity);
if (_a == nullptr)
{
cout << "malloc fail" << endl;
exit(-1);
}
_top = 0;
_capacity = capacity;
}
~Stack()
{
// 像Stack这样的类,对象中的资源需要清理工作,就用析构函数
free(_a);
_a = nullptr;
_top = _capacity = 0;
}
private:
// 跟构造函数类似,我们不写,编译器默认生成构造函数做了偏心处理:
// 1、内置类型成员不处理
// 2、自定义类型成员会去调用他析构函数
int* _a;
int _top;
int _capacity;
};
int main()
{
Stack st1;
Stack st2(st1);
return 0;
}
直接运行程序会崩溃,因为第一次定义对象时,调用了构造函数,在堆区开辟了一块空间,但是第二次定义对象时候,调用的是拷贝构造,也就是说,这一次并没有再开辟空间(拷贝构造用的是传引用哦!传引用哦!记住是传引用哦!前边刚讲了),而是直接把st1的内容复制一份给st2,那么st1和st2的成员都是指向了一块空间,但是st2会先析构,释放空间,后边st1也要析构,但是同一块空间不可能析构俩次,所以程序崩溃。
class Date
{
public:
void print()
{
cout << "day:" << _day << endl;
}
private:
int _day;
};
int main()
{
Date d1;
d1.print();
const Date d2;
d2.print();
return 0;
}
编译器提示d2有错误,那么d2到底哪里错了呢?
这就是const的权限缩放问题.我们知道d2的类型是const Date,那么d2在调用print函数时,系统便会传给print一个this指针。而this指针的类型是Date* ,但是d2的地址类型是const Date* , 将一个const修饰的指针传给普通指针接收,将会扩大权限,扩大权限是不被允许的!!!
解决方式就是引入const修饰类的成员函数
将const修饰的类成员函数称之为const成员函数,const修饰类成员函数,实际修饰该成员函数隐含的this指针(注意哦!注意哦!只修饰this指针这个形参哦),表明在该成员函数中,不能对类的任何成员进行修改。
class Date
{
public:
void print() const//本质就是修饰形参列表的this指针
{
cout << "day:" << _day << endl;
}
private:
int _day;
};
const四连问
1.const对象可以调用非const成员函数吗?
答:不行,权限不能放大
2.非const对象可以调用const成员函数吗?
答:可以,允许权限缩小,比如d1仍然可以调用const修饰类的成员函数
3.const成员函数内可以调用其它的非const成员函数吗?
答:不行,只要调用成员函数就一定传this指针,但是被const修饰的函数去调用未被修饰的,就会扩大权限
4.非const成员函数内可以调用其它的const成员函数吗?
答:可以,权限可以被缩小.
感谢您的阅读!!!如果内容对你有帮助的话,记得给我三连(点赞、收藏、关注)——做个手有余香的人。
版权说明 : 本文为转载文章, 版权归原作者所有 版权申明
原文链接 : https://blog.csdn.net/weixin_48953972/article/details/121141210
内容来源于网络,如有侵权,请联系作者删除!