第三层:C++对象模型和this指针

简介: 第三层:C++对象模型和this指针

前情回顾


上回说到,在进入第二层之后,我见识到了对象的初始化和清理,封装的力量甚至只是为了给它打基础,但是有惊无险,我也成功掌握,进入到了第三层…


C++对象模型和this指针


进入到第三层,映入眼帘的是一块巨大的石碑,石碑上密密麻麻的都是字和代码,正当我还是迷惑这层的力量是什么的时候,那道声音的主人仿佛看出了我的迷惑,解释随之飘来:“这层的力量是一种隐藏在编译器内的神秘力量,对象模型和this指针,它们两个相辅相成,祝你好运,闯关者…"


🚄上章地址:第二层:对象的初始化和清理


类成员变量和类成员函数的储存


在C++中,类内的成员变量和成员函数是分开储存的,只有类内的非静态成员变量才属于这个类的对象,如下图所示。

0a2653c851af460fa595bd959398a8f1.png


那如果类内什么属性都没有,这个类实体化的对象会占用内存空间吗?


#include<iostream>
using namespace std;
class A
{
};
void test1()
{
  A a;
  cout << "对象a的大小是: " << sizeof(a) << endl;
}
int main()
{
  test1();
  return 0;
}

0a2653c851af460fa595bd959398a8f1.png

这是为什么?类内明明没有成员变量,为什么会占用大小?新机子蛙一直摸你肚子!对于C++的编译器而言,每一个对象都应该有一个独一无二的地址,当类内没有非静态成员变量时,创建一个对象,如果不给它一片空间,在遇到这个对象的时候,编译器不知道它开辟的内存空间在哪里,如果这种空对象多,那在未来里面加入非静态成员变量的时候,可能就会产生几个空对象指向同一片空间,出现错误。

当拥有一个非静态成员变量的时候,对象的大小是多少,是数据类型所占字节+1吗?还是会产生对齐,是数据类型的整数倍呢?


#include<iostream>
using namespace std;
class A
{
  int a;
};
void test1()
{
  A a;
  cout << "对象a的大小是: " << sizeof(a) << endl;
}
int main()
{
  test1();
  return 0;
}

0a2653c851af460fa595bd959398a8f1.png

看到了,是数据类型的大小,当里面不是空的时候,编译器就不会给对象这一个字节的大小来确定对象在内存空间中的位置,直接用类内非静态成员变量的位置,也变相的说明了,非静态成员变量是属于类实例化出来的对象的。

那上面说静态成员变量不属于对象,那现在也可以去验证一下。


#include<iostream>
using namespace std;
class A
{
  static int a;
};
void test1()
{
  A a;
  cout << "对象a的大小是: " << sizeof(a) << endl;
}
int main()
{
  test1();
  return 0;
}


0eacb84100b54626af849e6b562bf92a.png


又变成了1,说明我们的静态成员变量是不属于对象的。

那非静态成语函数呢?


#include<iostream>
using namespace std;
class A
{
public:
  void a()
  {
  }
};
void test1()
{
  A a;
  cout << "对象a的大小是: " << sizeof(a) << endl;
}
int main()
{
  test1();
  return 0;
}

0a2653c851af460fa595bd959398a8f1.png

根据这些可以得知,只有非静态成员变量属于类实例化的对象,类内属性和非静态成员函数是分开存放的,静态成员函数也与非静态成员函数一样,不属于类实例化的对象。


this指针


this指针概念


通过上面得知,在C++中,成员变量和成员函数是分开存放的,每一个非静态成员函数都会产生一份函数实例,也就是说,多个同类型的对象在调用函数的会共用同一块空间,那么问题就来了,这同一个函数,是通过上面来区分是哪个对象来调用函数的?这里就C++就提供了一个特殊对象指针——this指针,this指针,它指向被调用的成员函数所属的对象,它隐藏在每一个非静态成员函数内,不需要程序员去定义,可以直接使用,假设现在有三个对象:p1、p2、p3,其中p1要调用函数,其底层就如下图所示:

0eacb84100b54626af849e6b562bf92a.png

就如同这样p1不能直接指向,先指向this指针,这个时候this指针代表了p1,指向要调用的函数。


this指针用途


1.当成员函数的形参和成员变量名同名时,可以用this指针来区分

2.在非静态成员函数中返回对象本身是,可以使用return *this

用途1解释

当成员函数的形参和成员变量同名时,这个时候如果要对成员变量在成员函数内进行一个赋值的操作时会失败的,可以验证一下:


#include<iostream>
using namespace std;
class A
{
public:
  void a(int b)
  {
  b = b;
  }
  int b;
};
void test1()
{
  A a;
  a.a(10);
  cout << a.b << endl;
}
int main()
{
  test1();
  return 0;
}

0a2653c851af460fa595bd959398a8f1.png

可以看到,属性内b根本没有改变,这是因为,在编译器严重,它认为函数内的b都是形参,解决方法很简单,在右侧的b前面加上this指针,让this指针指向对象a中成员变量b的那块空间,进行改变。


#include<iostream>
using namespace std;
class A
{
public:
  void a(int b)
  {
  this->b = b;
  }
  int b;
};
void test1()
{
  A a;
  a.a(10);
  cout << a.b << endl;
}
int main()
{
  test1();
  return 0;
}

0eacb84100b54626af849e6b562bf92a.png

用途2解释

当对一个对象进行函数调用时,这个时候想多调用几次,除了使用循环,还有没有别的方法?试试下面这种,看看行不行:


对象名.调用函数.调用函数.调用函数…

尝试使用一下:

#include<iostream>
using namespace std;
class A
{
public:
  void a(int b)
  {
  this->b += b;
  }
  int b;
};
void test1()
{
  A a;
  a.b=0;
  a.a(10).a(10).a(10);
  cout << a.b << endl;
}
int main()
{
  test1();
  return 0;
}

0a2653c851af460fa595bd959398a8f1.png

可以看到报错,报错为表达式必须具有类类型,但它具有类型“void",这是这么回事呢?我们看代码

0eacb84100b54626af849e6b562bf92a.png

成员函数的返回值为void,所以类型不兼容,那把返回类型换成类名呢?


#include<iostream>
using namespace std;
class A
{
public:
  A a(int b)
  {
  this->b += b;
  return *this;
  }
  int b;
};
void test1()
{
  A a;
  a.b=0;
  a.a(10).a(10).a(10);
  cout << a.b << endl;
}
int main()
{
  test1();
  return 0;
}

0a2653c851af460fa595bd959398a8f1.png


确实不会报错了,但是调用了三次成员函数,为什么结果是这样?而不是30?为什么要返回* this,而不是this?首先,为什么是* this而不是this是因为,this是指针,它指向的是地址,也就是对象a的那块空间,*解引用找到的就是对象a;结果第一次是10,因为返回的类型是类名,就会调用拷贝构造函数,将数据重新放在一份新的空间,而不是原空间,所以这里要返回的值应该是引用:


#include<iostream>
using namespace std;
class A
{
public:
  A& a(int b)
  {
  this->b += b;
  return *this;
  }
  int b;
};
void test1()
{
  A a;
  a.b = 0;
  int b = 10;
  a.a(b).a(b).a(b);
  cout << a.b << endl;
}
int main()
{
  test1();
  return 0;
}

0eacb84100b54626af849e6b562bf92a.png


空指针调用成员函数


在创建对象的时候,这个时候创建的对象是一个对象指针,并且对这个指针置空,那这个时候让它去访问成员函数,会发生什么?


#include<iostream>
using namespace std;
class A
{
public:
  void a()
  {
  cout << "调用函数a" << endl;
  }
  void b()
  {
  c = 10;
  }
  int c;
};
void test1()
{
  A* p = NULL;
  p->a();
  p->b();
}
int main()
{
  test1();
  return 0;
}


0a2653c851af460fa595bd959398a8f1.png

0a2653c851af460fa595bd959398a8f1.png

在调用时可以发现,为什么只调用了a函数,b函数为什么不能调用?调试看一下:

0eacb84100b54626af849e6b562bf92a.png

看到,是在对非静态成员变量c赋值时产生的错误,错误为this为空指针,那这个给时候不难看出,对于空指针只能调用部分函数,这部分函数为不访问类内成员变量的函数,而访问类内成员变量的函数,则不能访问,是因为this为空指针,没有确定的对象,所以访问不到类内的成员变量。那这个时候就可以优化一下函数,用if或者assert来判断一下this指针是否为空,可以大大提高函数的健壮性。


const修饰的成员变量


当一个程序员不想让木某个函数体内部修改成员的值时,可以在函数后面跟一个const,该函数内部就不能修改成员变量了:


返回类型 函数(参数) const

{

实现功能

}


在this指针中提到,不管写不写this指针,函数内都会调用this指针,this指针属于指针常量,不可以改变指向,但可以改变指针的指向的值,所以this指针的本质就是:


类名 * const this;

那这个时候程序员不仅仅让this指针不能改变指向,也不能改变指向的值时,就可以想上面一样,加const,函数本身也就变成常函数了,这个时候,函数内部就不能改变成员变量了,如果在对象前加一个const,那对象就变成常对象了,也不能修改,同时,常对象只能调用常函数。


常函数内可以被修改的值


那有没有什么方法让常函数内也可以被修改?这里就有一种特殊变量,可以在常函数内进行修改,只需要在成员变量前加上关键字mutable,就可以在常函数内进行修改了。


突破!步入第四层


“轰隆”,一声巨响,面前的巨大石碑轰然倒下,通往下一层的楼梯也出现在眼前…


本章知识点(图片形式)


0a2653c851af460fa595bd959398a8f1.png


😘预知后事如何,关注新专栏,和我一起征服C++这座巨塔

🚀专栏:C++爬塔日记

🙉都看到这里了,留下你们的👍点赞+⭐收藏+📋评论吧🙉


相关文章
|
2天前
|
安全 编译器 程序员
【C++篇】C++类与对象深度解析(六):全面剖析拷贝省略、RVO、NRVO优化策略
【C++篇】C++类与对象深度解析(六):全面剖析拷贝省略、RVO、NRVO优化策略
13 2
|
2天前
|
存储 C++
【C++篇】C++类和对象实践篇——从零带你实现日期类的超详细指南
【C++篇】C++类和对象实践篇——从零带你实现日期类的超详细指南
12 2
【C++篇】C++类和对象实践篇——从零带你实现日期类的超详细指南
|
2天前
|
存储 编译器 C语言
C++类与对象深度解析(一):从抽象到实践的全面入门指南
C++类与对象深度解析(一):从抽象到实践的全面入门指南
20 8
|
1天前
|
编译器 C语言 C++
C++入门3——类与对象2-2(类的6个默认成员函数)
C++入门3——类与对象2-2(类的6个默认成员函数)
13 3
|
1天前
|
存储 编译器 C语言
C++入门2——类与对象1(类的定义和this指针)
C++入门2——类与对象1(类的定义和this指针)
10 2
|
19小时前
|
C++
C++番外篇——对于继承中子类与父类对象同时定义其析构顺序的探究
C++番外篇——对于继承中子类与父类对象同时定义其析构顺序的探究
27 1
|
1天前
|
编译器 C语言 C++
C++入门4——类与对象3-1(构造函数的类型转换和友元详解)
C++入门4——类与对象3-1(构造函数的类型转换和友元详解)
8 1
|
1天前
|
存储 编译器 C++
C++入门3——类与对象2-1(类的6个默认成员函数)
C++入门3——类与对象2-1(类的6个默认成员函数)
10 1
|
2天前
|
存储 编译器 数据安全/隐私保护
【C++篇】C++类与对象深度解析(四):初始化列表、类型转换与static成员详解2
【C++篇】C++类与对象深度解析(四):初始化列表、类型转换与static成员详解
13 3
|
2天前
|
编译器 C++
【C++篇】C++类与对象深度解析(四):初始化列表、类型转换与static成员详解1
【C++篇】C++类与对象深度解析(四):初始化列表、类型转换与static成员详解
18 3