持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第26天,点击查看活动详情
前言
本文就来分享一波作者对this指针和类作用域的学习心得与见解。
笔者水平有限,难免存在纰漏,欢迎指正交流。
this指针
this指针的引出
我们先来定义一个日期类 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 d1, d2;
d1.Init(2022,9,21);
d2.Init(2022,9,22);
d1.Print();
d2.Print();
return 0;
}
对于上述类,有这样的一个问题: Date类中有 Init 与 Print 两个成员函数,函数体中没有关于不同对象的区分,那当d1调用 Init 函数时,该函数是如何知道应该设置d1对象,而不是设置d2对象呢? C++中通过引入this指针解决该问题,即:C++编译器给每个“非静态的成员函数“增加了一个隐藏的指针参数,让该指针指向当前对象(函数运行时调用该函数的对象),在函数体中所有“成员变量”的操作,都是通过该指针去访问。只不过所有的操作对用户是透明的,即用户不需要来传递,编译器自动完成。
this指针的特性
- this指针的定义和传递都是编译器自己完成的,不需要我们管(也管不了),不过我们可以在类里面使用this指针,且只能在“成员函数”的内部使用
- this指针的类型:
类名*const this,所以成员函数中,不能给this指针赋值。 - this指针本质上是“成员函数”的形参,当对象调用成员函数时,将对象地址作为实参传递给this形参。同时对象的大小不包括成员函数,所以对象中是不存储this指针的,this指针是存储在成员函数的栈帧中的。
- this指针是“成员函数”第一个隐含的指针形参,在VS中比较特别,是通过ecx寄存器自动传递的(进行了优化),不需要用户传递。(g++下又是怎样的呢?以后Linux学习到一定深度后再探讨)
【问题】
-
this指针存在哪里?
答:一般而言是存储在栈帧的,而VS下比较特殊,进行了一定的优化,用寄存器ecx传递this指针。
-
this指针可以为空吗?
答:可以,但是最好不要。
经典问题:
// 1.下面程序编译运行结果是? A、编译报错 B、运行崩溃 C、正常运行
class A
{
public:
void Print()
{
cout << "Print()" << endl;
}
private:
int _a;
};
int main()
{
A* p = nullptr;
p->Print();
return 0;
}
// 2.下面程序编译运行结果是? A、编译报错 B、运行崩溃 C、正常运行
class A
{
public:
void PrintA()
{
cout<<_a<<endl;
}
private:
int _a;
};
int main()
{
A* p = nullptr;
p->PrintA();
return 0;
}
类作用域
说明介绍
在类中定义的名称(比如类成员变量名或类成员函数名)的作用域都为整个类,也就是说这些名称只在该类中是可见的,而在类外是不可见的。因此,在不同类中使用相同的类成员名是不会起冲突的,就好比不同的家庭中给孩子起的名字可能相同,全国同名的人多了去了,但是会发生矛盾冲突么?不会,这些家庭之间一般是相互独立的。
其实类作用域就限制了在类外对类成员的访问,就像被“围墙”围起来了一样,在类外无法直接根据类成员名称来访问对应成员,一来是这些成员在类外不可见,二来是有那么多类,难免会出现同名成员,那到底是哪一个类的成员呢,就好像叫一声张三众人纷纷回头,到底哪一个是你要找的张三呢?
那么如何在类外面访问类成员呢?首先要明确一点:不是所有类成员都能在类外被直接访问的,还记得之前文章提到过的访问限定符么?只有public修饰的公有成员才能在类外被直接访问,通常是通过.或->来访问的,可以类比一下结构体成员的访问。
可以用::来指定是类的成员函数定义,类对象可以用.来直接访问公有成员,类对象指针可以用->来间接访问公有成员。
示例
class Ik
{
private:
int fuss;
public:
Ik(int f = 9){fuss = f;}
void ViewIk() const;
};
void Ik::ViewIk() const//用Ik::来指定是Ik类的成员函数定义
{
cout << fuss << endl;
}
//...
int main()
{
Ik* pik = (Ik*)malloc(sizeof(Ik));//还没有提到new,就先用老朋友malloc
if(pik == nullptr)
{
perror("malloc fail");
exit(-1);
}
Ik ik = Ik(8);
ik.View();//对象可以用.来直接访问公有成员
pik->ViewIk();//对象指针可以用->来间接访问公有成员
return 0;
}
有人可能会问了,那可不可以直接用::来访问类成员?不能!可别忘了类说到底也只是声明,只是描述了对象的形式,可以说是“虚”的,只有类实例化成了对象实体才真正能够访问成员。
作用域内枚举(C++11)
不知道你有没有发现过这样一个问题:两个枚举定义中的枚举量可能会同名,这样一来就可能发生冲突。
比如:
enum egg
{
Small,
Medium,
Large,
Jumbo
};
enum t_shirt
{
Small,
Medium,
Large,
Xlarge
};
这将无法通过编译,为什么?因为这两个枚举位于相同的作用域内(全局作用域),它们的枚举量的命名有三个是相同的,使用起来会发生冲突。就好像有两个人名字都叫张三,要是他们之间没什么联系没聚在一块还好说,但要是这两人都在同一个学校同一个班级中就不太妙了,老师说小明啊你帮我把张三叫来一趟,然后小明一去叫张三发现有两个人回头,到底是哪个张三呢?
为了避免这一类问题,C++11提供了一种新枚举类型,其枚举量的作用域将变为类作用域。
格式:enum class 枚举名 { };
可以用struct来替代class,效果相同。
无论哪种方式,都需要使用枚举名来限定枚举量,就比如:
enum class egg
{
Small,
Medium,
Large,
Jumbo
};
enum class t_shirt
{
Small,
Medium,
Large,
Xlarge
};
int main()
{
egg choice = egg::Large;
t_shirt Floyd = t_shirt::Large;
}
枚举量的作用域为类后就不会发生名称冲突这样的问题了,这和命名空间有点相似,可以类比一下。
除此之外,C++11还提高了作用域内枚举的类型安全,在有些情况下,常规枚举类型将自动转换为整型,比如在赋值给int变量或用于比较表达式时,但是作用域内枚举不能隐式类型转换为整型。
以上就是本文全部内容,感谢观看,你的支持就是对我最大的鼓励~