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语言中需用用户自己维护
感谢大家阅读!!!后续给大家带来析构函数和构造函数有关内容!