【c++】类和对象(二)this指针

简介: 朋友们大家好,本节内容来到类和对象第二篇,本篇文章会带领大家了解this指针

1.this指针

1.1this指针的引出

首先我们定义一个日期类date:


1. class Date
2. {
3. public:
4.  void Init(int year, int month, int day)
5.  {
6.   _year = year;
7.   _month = month;
8.   _day = day;
9.  }
10.   void Print()
11.   {
12.   cout << _year << "-" << _month << "-" << _day <
13.   }
14. private:
15.   int _year;
16.   int _month;
17.   int _day;
18. };
19. int main()
20. {
21.   Date d1, d2;
22.   d1.Init(2005, 6, 23);
23.   d2.Init(2024, 3, 25);
24.   d1.Print();
25.   d2.Print();
26. 
27.   return 0;
28. }

我们来思考这么一个问题:


Date类中有 Init 与 Print 两个成员函数,函数体中没有关于不同对象的区分,也就是说,d1和d2调用的是同一个函数,那当d1调用 Init 函数时,该函数是如何知道应该设置d1对象,而不是设置d2对象呢


首先思考,这里打印函数,访问的变量是哪里的?

1. void Print()
2.  {
3.   cout << _year << "-" << _month << "-" << _day <
4.  }


这里访问的是private声明下的吗?


1. private:
2.  int _year;
3.  int _month;
4.  int _day;

并不是,因为这里只是声明,并没有开辟空间,真正访问的是实例化的d1,d2


在private部分声明的变量_year、_month、_day等,在类中只是进行了声明,实际上并没有为它们分配内存空间。**内存空间是在创建类的实例(也就是对象)**时为这些成员变量分配的。每个对象都有自己独立的一套成员变量,占用各自的内存空间


因此,当成员函数Print()通过this指针(隐式指向当前对象)访问这些成员变量时,它实际上访问的是调用这个成员函数的那个==特定对象(实例)==的成员变量。每个对象的_year、_month和_day都存储在各自独立的内存区域中,这些内存区域是在对象被创建时随对象一起分配的


那么我d1,d2如何找到这两个函数呢?


这里就与隐含的this指针有关了


this指针是面向对象编程语言中的一个特殊指针,它指向调用成员函数的那个对象。通过this指针,成员函数可以访问调用它的那个对象的成员变量和成员函数。this指针是隐式传递给成员函数的,是成员函数的一个隐含参数


可以理解为,编译器处理后处理为上述的样子,调用的地方,编译器也会处理:


它会把调用对象当做形参进行传递


这里我们也能知道,为什么d1访问能打印d1,d2访问能打印d2


这个东西我们并不陌生,在前面数据结构中我们也有学过:


1.2this指针的特性

this指针是“成员函数”第一个隐含的指针形参,一般情况由编译器通过ecx寄存器自动传递,不需要用户传递

this指针的类型:类类型* const,(Date* const this)即成员函数中,不能给this指针赋值,但是this指向的内容可以被改变

特点:


在形参和实参的位置,我们不能显示写出来

在函数内部可以使用

1.3思考题

一,this指针是存在哪里的?


不同的数据是存储在不同的区域的,思考一下this指针是存在哪个区域的呢?


const int i = 0;
int j = 1;
cout << &i << endl;
cout << &j << endl;


c++中,const定义的变量是存储在栈中的,我们可以打印它们的地址:


发现是相邻的


const int i = 0;
int j = 1;
const char* p = "abcdefg";
cout << &i << endl;
cout << &j << endl;
cout << &p << endl;
cout << (void*)p << endl;


在C++中,变量和数据的存储位置分为几个区域,主要包括栈(Stack)、堆(Heap)、全局/静态存储区(Global/Static Area)和常量区(Constant Pool)。具体到您提供的代码示例中的变量,它们的存储位置如下:


const int i = 0;


i是一个常量整型变量。在C++中,const修饰的局部变量默认存储在栈上,但是编译器优化可能会将其存储在程序的只读数据段中(常量区),尤其是当它被视为编译时常量时。然而,取地址操作&i表明i必须在内存中有实际的存储位置,所以它很可能位于栈上,除非进行了特殊的优化

int j = 1;

j是一个非const局部变量,存储在栈上。栈用于存储局部变量和函数调用的上下文

const char* p = "abcdefg";

这里p是一个指针,指向一个字符串常量。字符串常量"abcdefg"存储在常量区(也称为字符串字面量区或只读数据段),这是因为字符串字面量在程序的整个生命周期内都不应被修改。而指针p本身(即存储字符串地址的变量)作为局部变量,存储在栈上

i(取决于编译器优化)和j存储在栈上。

字符串常量"abcdefg"存储在常量区。

指针p(存储字符串常量的地址)存储在栈上。

在上述的讲解后,我们能够推出this指针的存储位置:this是一个形参,它指向调用该成员函数的对象,this指针在成员函数调用时需要被快速访问并用于访问对象的成员,所以我们推测它存储在栈上


为了提高访问速度,某些编译器可能选择将this指针存储在某个寄存器中,尤其是在成员函数调用时。这实际上可以减少内存访问次数,从而提高程序的执行效率,寄存器是CPU内部的极小量存储器,具有非常高的数据访问速度


二,判断下面程序的运行结果(this能否是空指针?)


class A
{
public:
  void PrintA()
  {
  cout << "PrintA()" << endl;
  }
private:
  int _a;
};
int main()
{
  A* p = nullptr;
  p->PrintA();
  return 0;
}


我们发现它是可以正常运行的,我们接下来简单分析一下

尽管p被初始化为nullptr,指向A类型对象的指针p是空的,但PrintA()函数只是打印一条消息,没有访问任何对象的成员变量。这种特殊情况下,代码可运行,主要是因为成员函数的调用并没有实际依赖于this指针指向的对象实例的状态


因为PrintA()不访问对象的任何成员变量,所以这个调用在技术上不需要访问通过this指针指示的内存地址。因此,对于这种不访问任何成员变量的成员函数,通过nullptr调用可能不会导致运行时错误


简单来说,


void PrintA()
  {
  cout << "PrintA()" << endl;
  }


这串代码传递空指针并没有任何影响


接下来看下面的代码:


class A
{
public:
  void PrintA()
  {
  cout << _a << endl;
  }
private:
  int _a;
};
int main()
{
  A* p = nullptr;
  p->PrintA();
  return 0;
}

这串代码运行异常,因为这里访问a是通过this->_a来实现的


1.4C语言和C++实现Stack的对比

c语言实现


1. void StackInit(Stack* ps)
2. {
3.  assert(ps);
4.  ps->array = (DataType*)malloc(sizeof(DataType) * 3);
5.  if (NULL == ps->array)
6.  {
7.   assert(0);
8.   return;
9.  }
10.   ps->capacity = 3;
11.   ps->size = 0;
12. }
13. void StackDestroy(Stack* ps)
14. {
15.   assert(ps);
16.   if (ps->array)
17.   {
18.   free(ps->array);
19.   ps->array = NULL;
20.   ps->capacity = 0;
21.   ps->size = 0;
22.   }
23. }
24. void CheckCapacity(Stack* ps)
25. {
26.   if (ps->size == ps->capacity)
27.   {
28.   int newcapacity = ps->capacity * 2;
29.   DataType* temp = (DataType*)realloc(ps->array,
30.     newcapacity * sizeof(DataType));
31.   if (temp == NULL)
32.   {
33.     perror("realloc申请空间失败!!!");
34.     return;
35.   }
36.   ps->array = temp;
37.   ps->capacity = newcapacity;
38.   }
39. }
40. void StackPush(Stack* ps, DataType data)
41. {
42.   assert(ps);
43.   CheckCapacity(ps);
44.   ps->array[ps->size] = data;
45.   ps->size++;
46. }
47. int StackEmpty(Stack* ps)
48. {
49.   assert(ps);
50.   return 0 == ps->size;
51. }
52. void StackPop(Stack* ps)
53. {
54.   if (StackEmpty(ps))
55.   return;
56.   ps->size--;
57. }
58. DataType StackTop(Stack* ps)
59. {
60.   assert(!StackEmpty(ps));
61.   return ps->array[ps->size - 1];
62. }
63. int StackSize(Stack* ps)
64. {
65.   assert(ps);
66.   return ps->size;
67. }
68. int main()
69. {
70.   Stack s;
71.   StackInit(&s);
72.   StackPush(&s, 1);
73.   StackPush(&s, 2);
74.   StackPush(&s, 3);
75.   StackPush(&s, 4);
76.   printf("%d\n", StackTop(&s));
77.   printf("%d\n", StackSize(&s));
78.   StackPop(&s);
79.   StackPop(&s);
80.   printf("%d\n", StackTop(&s));
81.   printf("%d\n", StackSize(&s));
82.   StackDestroy(&s);
83.   return 0;
84. }

在用C语言实现时,Stack相关操作函数有以下共性:


每个函数的第一个参数都是Stack*

函数中必须要对第一个参数检测,因为该参数可能会为NULL

函数中都是通过Stack*参数操作栈的

调用时必须传递Stack结构体变量的地址

结构体中只能定义存放数据的结构,操作数据的方法不能放在结构体中,即数据和操作数据的方式是分离开的


c++实现:


typedef struct Stack
{
  DataType* array;
  int capacity;
  int size;
}Stack;
typedef int DataType;
class Stack
{
public:
  void Init()
  {
  _array = (DataType*)malloc(sizeof(DataType) * 3);
  if (NULL == _array)
  {
    perror("malloc申请空间失败!!!");
    return;
  }
  _capacity = 3;
  _size = 0;
  }
  void Push(DataType data)
  {
  CheckCapacity();
  _array[_size] = data;
  _size++;
  }
  void Pop()
  {
  if (Empty())
    return;
  _size--;
  }
  DataType Top() { return _array[_size - 1]; }
  int Empty() { return 0 == _size; }
  int Size() { return _size; }
  void Destroy()
  {
  if (_array)
  {
    free(_array);
    _array = NULL;
    _capacity = 0;
    _size = 0;
  }
  }
private:
  void CheckCapacity()
  {
  if (_size == _capacity)
  {
    int newcapacity = _capacity * 2;
    DataType* temp = (DataType*)realloc(_array, newcapacity *
    sizeof(DataType));
    if (temp == NULL)
    {
    perror("realloc申请空间失败!!!");
    return;
    }
    _array = temp;
    _capacity = newcapacity;
  }
  }
private:
  DataType* _array;
  int _capacity;
  int _size;
};
int main()
{
  Stack s;
  s.Init();
  s.Push(1);
  s.Push(2);
  s.Push(3);
  s.Push(4);
  printf("%d\n", s.Top());
  printf("%d\n", s.Size());
  s.Pop();
  s.Pop();
  printf("%d\n", s.Top());
  printf("%d\n", s.Size());
  s.Destroy();
  return 0;
}

C++中通过类可以将数据以及数据的方法进行完美结合,通过访问权限可以控制那些方法在类外可以被调用,即封装,在使用时就像使用自己的成员一样,更符合人类对一件事物的认知。而且每个方法不需要传递Stack*的参数了,编译器编译之后该参数会自动还原,即C++中 Stack * 参数是编译器维护的,C语言中需用用户自己维护


感谢大家阅读!!!后续给大家带来析构函数和构造函数有关内容!


相关文章
|
1天前
|
设计模式 IDE 编译器
【C++面向对象——类的多态性与虚函数】编写教学游戏:认识动物(头歌实践教学平台习题)【合集】
本项目旨在通过C++编程实现一个教学游戏,帮助小朋友认识动物。程序设计了一个动物园场景,包含Dog、Bird和Frog三种动物。每个动物都有move和shout行为,用于展示其特征。游戏随机挑选10个动物,前5个供学习,后5个用于测试。使用虚函数和多态实现不同动物的行为,确保代码灵活扩展。此外,通过typeid获取对象类型,并利用strstr辅助判断类型。相关头文件如&lt;string&gt;、&lt;cstdlib&gt;等确保程序正常运行。最终,根据小朋友的回答计算得分,提供互动学习体验。 - **任务描述**:编写教学游戏,随机挑选10个动物进行展示与测试。 - **类设计**:基类
11 3
|
23小时前
|
编译器 数据安全/隐私保护 C++
【C++面向对象——继承与派生】派生类的应用(头歌实践教学平台习题)【合集】
本实验旨在学习类的继承关系、不同继承方式下的访问控制及利用虚基类解决二义性问题。主要内容包括: 1. **类的继承关系基础概念**:介绍继承的定义及声明派生类的语法。 2. **不同继承方式下对基类成员的访问控制**:详细说明`public`、`private`和`protected`继承方式对基类成员的访问权限影响。 3. **利用虚基类解决二义性问题**:解释多继承中可能出现的二义性及其解决方案——虚基类。 实验任务要求从`people`类派生出`student`、`teacher`、`graduate`和`TA`类,添加特定属性并测试这些类的功能。最终通过创建教师和助教实例,验证代码
18 5
|
23小时前
|
存储 算法 搜索推荐
【C++面向对象——群体类和群体数据的组织】实现含排序功能的数组类(头歌实践教学平台习题)【合集】
1. **相关排序和查找算法的原理**:介绍直接插入排序、直接选择排序、冒泡排序和顺序查找的基本原理及其实现代码。 2. **C++ 类与成员函数的定义**:讲解如何定义`Array`类,包括类的声明和实现,以及成员函数的定义与调用。 3. **数组作为类的成员变量的处理**:探讨内存管理和正确访问数组元素的方法,确保在类中正确使用动态分配的数组。 4. **函数参数传递与返回值处理**:解释排序和查找函数的参数传递方式及返回值处理,确保函数功能正确实现。 通过掌握这些知识,可以顺利地将排序和查找算法封装到`Array`类中,并进行测试验证。编程要求是在右侧编辑器补充代码以实现三种排序算法
16 5
|
22小时前
|
C++ 芯片
【C++面向对象——类与对象】Computer类(头歌实践教学平台习题)【合集】
声明一个简单的Computer类,含有数据成员芯片(cpu)、内存(ram)、光驱(cdrom)等等,以及两个公有成员函数run、stop。只能在类的内部访问。这是一种数据隐藏的机制,用于保护类的数据不被外部随意修改。根据提示,在右侧编辑器补充代码,平台会对你编写的代码进行测试。成员可以在派生类(继承该类的子类)中访问。成员,在类的外部不能直接访问。可以在类的外部直接访问。为了完成本关任务,你需要掌握。
32 18
|
22小时前
|
存储 编译器 数据安全/隐私保护
【C++面向对象——类与对象】CPU类(头歌实践教学平台习题)【合集】
声明一个CPU类,包含等级(rank)、频率(frequency)、电压(voltage)等属性,以及两个公有成员函数run、stop。根据提示,在右侧编辑器补充代码,平台会对你编写的代码进行测试。​ 相关知识 类的声明和使用。 类的声明和对象的声明。 构造函数和析构函数的执行。 一、类的声明和使用 1.类的声明基础 在C++中,类是创建对象的蓝图。类的声明定义了类的成员,包括数据成员(变量)和成员函数(方法)。一个简单的类声明示例如下: classMyClass{ public: int
28 13
|
1天前
|
Serverless 编译器 C++
【C++面向对象——类的多态性与虚函数】计算图像面积(头歌实践教学平台习题)【合集】
本任务要求设计一个矩形类、圆形类和图形基类,计算并输出相应图形面积。相关知识点包括纯虚函数和抽象类的使用。 **目录:** - 任务描述 - 相关知识 - 纯虚函数 - 特点 - 使用场景 - 作用 - 注意事项 - 相关概念对比 - 抽象类的使用 - 定义与概念 - 使用场景 - 编程要求 - 测试说明 - 通关代码 - 测试结果 **任务概述:** 1. **图形基类(Shape)**:包含纯虚函数 `void PrintArea()`。 2. **矩形类(Rectangle)**:继承 Shape 类,重写 `Print
17 4
|
2月前
|
存储 编译器 C语言
【c++丨STL】string类的使用
本文介绍了C++中`string`类的基本概念及其主要接口。`string`类在C++标准库中扮演着重要角色,它提供了比C语言中字符串处理函数更丰富、安全和便捷的功能。文章详细讲解了`string`类的构造函数、赋值运算符、容量管理接口、元素访问及遍历方法、字符串修改操作、字符串运算接口、常量成员和非成员函数等内容。通过实例演示了如何使用这些接口进行字符串的创建、修改、查找和比较等操作,帮助读者更好地理解和掌握`string`类的应用。
67 2
|
2月前
|
存储 编译器 C++
【c++】类和对象(下)(取地址运算符重载、深究构造函数、类型转换、static修饰成员、友元、内部类、匿名对象)
本文介绍了C++中类和对象的高级特性,包括取地址运算符重载、构造函数的初始化列表、类型转换、static修饰成员、友元、内部类及匿名对象等内容。文章详细解释了每个概念的使用方法和注意事项,帮助读者深入了解C++面向对象编程的核心机制。
120 5
|
2月前
|
存储 编译器 C++
【c++】类和对象(中)(构造函数、析构函数、拷贝构造、赋值重载)
本文深入探讨了C++类的默认成员函数,包括构造函数、析构函数、拷贝构造函数和赋值重载。构造函数用于对象的初始化,析构函数用于对象销毁时的资源清理,拷贝构造函数用于对象的拷贝,赋值重载用于已存在对象的赋值。文章详细介绍了每个函数的特点、使用方法及注意事项,并提供了代码示例。这些默认成员函数确保了资源的正确管理和对象状态的维护。
124 4
|
2月前
|
存储 C语言
C语言如何使用结构体和指针来操作动态分配的内存
在C语言中,通过定义结构体并使用指向该结构体的指针,可以对动态分配的内存进行操作。首先利用 `malloc` 或 `calloc` 分配内存,然后通过指针访问和修改结构体成员,最后用 `free` 释放内存,实现资源的有效管理。
183 13