【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语言中需用用户自己维护


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


相关文章
|
7天前
|
存储 编译器 C++
【c++】类和对象(中)(构造函数、析构函数、拷贝构造、赋值重载)
本文深入探讨了C++类的默认成员函数,包括构造函数、析构函数、拷贝构造函数和赋值重载。构造函数用于对象的初始化,析构函数用于对象销毁时的资源清理,拷贝构造函数用于对象的拷贝,赋值重载用于已存在对象的赋值。文章详细介绍了每个函数的特点、使用方法及注意事项,并提供了代码示例。这些默认成员函数确保了资源的正确管理和对象状态的维护。
34 4
|
8天前
|
存储 编译器 Linux
【c++】类和对象(上)(类的定义格式、访问限定符、类域、类的实例化、对象的内存大小、this指针)
本文介绍了C++中的类和对象,包括类的概念、定义格式、访问限定符、类域、对象的创建及内存大小、以及this指针。通过示例代码详细解释了类的定义、成员函数和成员变量的作用,以及如何使用访问限定符控制成员的访问权限。此外,还讨论了对象的内存分配规则和this指针的使用场景,帮助读者深入理解面向对象编程的核心概念。
31 4
|
24天前
|
存储 安全 编译器
在 C++中,引用和指针的区别
在C++中,引用和指针都是用于间接访问对象的工具,但它们有显著区别。引用是对象的别名,必须在定义时初始化且不可重新绑定;指针是一个变量,可以指向不同对象,也可为空。引用更安全,指针更灵活。
|
1月前
|
存储 编译器 对象存储
【C++打怪之路Lv5】-- 类和对象(下)
【C++打怪之路Lv5】-- 类和对象(下)
27 4
|
1月前
|
编译器 C语言 C++
【C++打怪之路Lv4】-- 类和对象(中)
【C++打怪之路Lv4】-- 类和对象(中)
23 4
|
1月前
|
存储 安全 C++
【C++打怪之路Lv8】-- string类
【C++打怪之路Lv8】-- string类
21 1
|
1月前
|
存储 编译器 C语言
【C++打怪之路Lv3】-- 类和对象(上)
【C++打怪之路Lv3】-- 类和对象(上)
16 0
|
1月前
|
存储 编译器 C语言
深入计算机语言之C++:类与对象(上)
深入计算机语言之C++:类与对象(上)
|
1月前
|
C语言
无头链表二级指针方式实现(C语言描述)
本文介绍了如何在C语言中使用二级指针实现无头链表,并提供了创建节点、插入、删除、查找、销毁链表等操作的函数实现,以及一个示例程序来演示这些操作。
22 0
|
2月前
|
存储 人工智能 C语言
C语言程序设计核心详解 第八章 指针超详细讲解_指针变量_二维数组指针_指向字符串指针
本文详细讲解了C语言中的指针,包括指针变量的定义与引用、指向数组及字符串的指针变量等。首先介绍了指针变量的基本概念和定义格式,随后通过多个示例展示了如何使用指针变量来操作普通变量、数组和字符串。文章还深入探讨了指向函数的指针变量以及指针数组的概念,并解释了空指针的意义和使用场景。通过丰富的代码示例和图形化展示,帮助读者更好地理解和掌握C语言中的指针知识。