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


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


相关文章
|
15天前
|
人工智能 机器人 编译器
c++模板初阶----函数模板与类模板
class 类模板名private://类内成员声明class Apublic:A(T val):a(val){}private:T a;return 0;运行结果:注意:类模板中的成员函数若是放在类外定义时,需要加模板参数列表。return 0;
|
15天前
|
存储 编译器 程序员
c++的类(附含explicit关键字,友元,内部类)
本文介绍了C++中类的核心概念与用法,涵盖封装、继承、多态三大特性。重点讲解了类的定义(`class`与`struct`)、访问限定符(`private`、`public`、`protected`)、类的作用域及成员函数的声明与定义分离。同时深入探讨了类的大小计算、`this`指针、默认成员函数(构造函数、析构函数、拷贝构造、赋值重载)以及运算符重载等内容。 文章还详细分析了`explicit`关键字的作用、静态成员(变量与函数)、友元(友元函数与友元类)的概念及其使用场景,并简要介绍了内部类的特性。
|
2月前
|
编译器 C++ 容器
【c++11】c++11新特性(上)(列表初始化、右值引用和移动语义、类的新默认成员函数、lambda表达式)
C++11为C++带来了革命性变化,引入了列表初始化、右值引用、移动语义、类的新默认成员函数和lambda表达式等特性。列表初始化统一了对象初始化方式,initializer_list简化了容器多元素初始化;右值引用和移动语义优化了资源管理,减少拷贝开销;类新增移动构造和移动赋值函数提升性能;lambda表达式提供匿名函数对象,增强代码简洁性和灵活性。这些特性共同推动了现代C++编程的发展,提升了开发效率与程序性能。
90 12
|
3月前
|
设计模式 安全 C++
【C++进阶】特殊类设计 && 单例模式
通过对特殊类设计和单例模式的深入探讨,我们可以更好地设计和实现复杂的C++程序。特殊类设计提高了代码的安全性和可维护性,而单例模式则确保类的唯一实例性和全局访问性。理解并掌握这些高级设计技巧,对于提升C++编程水平至关重要。
76 16
|
3月前
|
编译器 C++
类和对象(中 )C++
本文详细讲解了C++中的默认成员函数,包括构造函数、析构函数、拷贝构造函数、赋值运算符重载和取地址运算符重载等内容。重点分析了各函数的特点、使用场景及相互关系,如构造函数的主要任务是初始化对象,而非创建空间;析构函数用于清理资源;拷贝构造与赋值运算符的区别在于前者用于创建新对象,后者用于已存在的对象赋值。同时,文章还探讨了运算符重载的规则及其应用场景,并通过实例加深理解。最后强调,若类中存在资源管理,需显式定义拷贝构造和赋值运算符以避免浅拷贝问题。
|
3月前
|
存储 编译器 C++
类和对象(上)(C++)
本篇内容主要讲解了C++中类的相关知识,包括类的定义、实例化及this指针的作用。详细说明了类的定义格式、成员函数默认为inline、访问限定符(public、protected、private)的使用规则,以及class与struct的区别。同时分析了类实例化的概念,对象大小的计算规则和内存对齐原则。最后介绍了this指针的工作机制,解释了成员函数如何通过隐含的this指针区分不同对象的数据。这些知识点帮助我们更好地理解C++中类的封装性和对象的实现原理。
|
3月前
|
编译器 C++
类和对象(下)C++
本内容主要讲解C++中的初始化列表、类型转换、静态成员、友元、内部类、匿名对象及对象拷贝时的编译器优化。初始化列表用于成员变量定义初始化,尤其对引用、const及无默认构造函数的类类型变量至关重要。类型转换中,`explicit`可禁用隐式转换。静态成员属类而非对象,受访问限定符约束。内部类是独立类,可增强封装性。匿名对象生命周期短,常用于临时场景。编译器会优化对象拷贝以提高效率。最后,鼓励大家通过重复练习提升技能!
|
7月前
|
存储 C语言
C语言如何使用结构体和指针来操作动态分配的内存
在C语言中,通过定义结构体并使用指向该结构体的指针,可以对动态分配的内存进行操作。首先利用 `malloc` 或 `calloc` 分配内存,然后通过指针访问和修改结构体成员,最后用 `free` 释放内存,实现资源的有效管理。
626 13
|
9月前
|
存储 人工智能 C语言
C语言程序设计核心详解 第八章 指针超详细讲解_指针变量_二维数组指针_指向字符串指针
本文详细讲解了C语言中的指针,包括指针变量的定义与引用、指向数组及字符串的指针变量等。首先介绍了指针变量的基本概念和定义格式,随后通过多个示例展示了如何使用指针变量来操作普通变量、数组和字符串。文章还深入探讨了指向函数的指针变量以及指针数组的概念,并解释了空指针的意义和使用场景。通过丰富的代码示例和图形化展示,帮助读者更好地理解和掌握C语言中的指针知识。
376 4
|
8月前
|
C语言
无头链表二级指针方式实现(C语言描述)
本文介绍了如何在C语言中使用二级指针实现无头链表,并提供了创建节点、插入、删除、查找、销毁链表等操作的函数实现,以及一个示例程序来演示这些操作。
97 0