[C转C++之路]this指针与类作用域

349 阅读4分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 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++编译器给每个“非静态的成员函数“增加了一个隐藏的指针参数,让该指针指向当前对象(函数运行时调用该函数的对象),在函数体中所有“成员变量”的操作,都是通过该指针去访问。只不过所有的操作对用户是透明的,即用户不需要来传递,编译器自动完成。

image-20220922162914224

this指针的特性

  1. this指针的定义和传递都是编译器自己完成的,不需要我们管(也管不了),不过我们可以在类里面使用this指针,且只能在“成员函数”的内部使用
  2. this指针的类型:类名*const this,所以成员函数中,不能给this指针赋值。
  3. this指针本质上是“成员函数”的形参,当对象调用成员函数时,将对象地址作为实参传递给this形参。同时对象的大小不包括成员函数,所以对象中是不存储this指针的,this指针是存储在成员函数的栈帧中的。
  4. this指针是“成员函数”第一个隐含的指针形参,在VS中比较特别,是通过ecx寄存器自动传递的(进行了优化),不需要用户传递。(g++下又是怎样的呢?以后Linux学习到一定深度后再探讨)

【问题】

  1. this指针存在哪里?

    答:一般而言是存储在栈帧的,而VS下比较特殊,进行了一定的优化,用寄存器ecx传递this指针。

  2. 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;
 }

image-20220922172058873

类作用域

说明介绍

        在类中定义的名称(比如类成员变量名或类成员函数名)的作用域都为整个类,也就是说这些名称只在该类中是可见的,而在类外是不可见的。因此,在不同类中使用相同的类成员名是不会起冲突的,就好比不同的家庭中给孩子起的名字可能相同,全国同名的人多了去了,但是会发生矛盾冲突么?不会,这些家庭之间一般是相互独立的。

        其实类作用域就限制了在类外对类成员的访问,就像被“围墙”围起来了一样,在类外无法直接根据类成员名称来访问对应成员,一来是这些成员在类外不可见,二来是有那么多类,难免会出现同名成员,那到底是哪一个类的成员呢,就好像叫一声张三众人纷纷回头,到底哪一个是你要找的张三呢?

        那么如何在类外面访问类成员呢?首先要明确一点:不是所有类成员都能在类外被直接访问的,还记得之前文章提到过的访问限定符么?只有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变量或用于比较表达式时,但是作用域内枚举不能隐式类型转换为整型。


以上就是本文全部内容,感谢观看,你的支持就是对我最大的鼓励~

src=http___c-ssl.duitang.com_uploads_item_201708_07_20170807082850_kGsQF.thumb.400_0.gif&refer=http___c-ssl.duitang.gif