在已经入门了类和对象之后,今天我们来到了类和对象的part2部分--this指针。先三连后看是好习惯!!!
目录
一、this指针的引入
二、this指针的特性
三、this指针必会
四、C语言和C++实现Stack的对比
1. C语言实现
2. C++实现
一、this指针的引入
class Date
{
public:void Init(int year, int month, int day){_year = year;_month = month;_day = day;}void Print(){cout << _year << "-" << _month << "-" << _day << endl;}private:int _year;int _month;int _day;
};
int main()
{Date d1, d2;d1.Init(2024, 4, 6);d2.Init(2022, 4, 7);d1.Print();d2.Print();return 0;
}
Date类中有 Init 与 Print 两个成员函数,成员函数都在公共代码区里,d1和d2调用的是同一个函数,那成员函数是怎么确定操作d1对象还是d2对象呢?
C++中通过引入this指针解决该问题,即:C++编译器给每个“非静态的成员函数”增加了一个隐藏的指针参数,让该指针指向当前对象(函数运行时调用该函数的对象),在函数体中所有“成员变量”的操作,都是通过该指针去访问。
编译器处理之后,其实是把对象的地址传给了对应的函数。
当成员函数Print( )通过this指针访问这些成员变量时,它实际上访问的是调用这个成员函数的实例化对象里的成员变量。每个对象的_year、_month和_day都存储在各自独立的内存区域中,这些内存区域是在对象被创建时随对象一起分配的。
注意:这些操作对用户是透明的,不需要用户来传递,编译器会自动完成。
二、this指针的特性
- this指针的类型:类名* const,即成员函数中,不能给this指针赋值,但是this指向的内容可以被改变。
- this指针只能在“成员函数”的内部使用
- this指针本质上是“成员函数”的形参,当对象调用成员函数时,将对象地址作为实参传递给this形参,所以对象中不存储this指针。
- this指针是“成员函数”第一个隐含的指针形参,一般情况由编译器通过ecx寄存器自动传递,不需要用户传递。
三、this指针必会
1. this指针存在哪里?
this指针存在栈上,因为this指针是一个形参。
有时this指针也会存在寄存器上。因为this指针会频繁访问,放在寄存器上方便快速取用。
2. 下面程序编译运行结果是? A、编译报错 B、运行崩溃 C、正常运行
class A
{
public:void Print(){cout << "Print()" << endl;}
private:int _a;
};
int main()
{A* p = nullptr;p->Print();return 0;
}
本道题答案选C。我们发现p是空指针,没有对空指针解引用,该语句符合语法规则,不会编译报错,因此A错误。我们通过空指针访问成员函数,可能认为这是错的就选了B。但实际上,尽管p被初始化为空指针,但Print( )函数只是打印一条消息,没有访问任何成员变量。如果调用成员函数没有实际依赖于this指针指向的对象,不需要访问通过this指针指示的内存地址,代码可以运行。因此,对于这种不访问任何成员变量的成员函数,通过空指针调用可以正常运行。
class A
{
public:void Print(){cout << this << endl;cout << "Print()" << endl;}
private:int _a;
};
int main()
{A* p = nullptr;p->Print();return 0;
}
为了更好地理解,我们在Print( )函数打印this指针的地址,打印结果为0。p是A类型的指针,他就会到A类型里找Print( )函数。Print( )函数要么传递对象的地址,要么传递指针。这里就是传递的p这个指针,传递一个空指针不会有问题。
class A
{
public:void Print(){cout << this << endl;cout << "Print()" << endl;}
private:int _a;
};
int main()
{A a;A* p = &a;p->Print();return 0;
}
如果我们传递一个对象,虽然仍然是p->Print( ),但实际上传递的是a的地址,this指针这时候接收的是一个实例化对象的地址,这个地址不为空。
注意:上面代码中的p->Print( )中不管p是否为空指针,->都不会进行解引用。
3. 下面程序编译运行结果是? A、编译报错 B、运行崩溃 C、正常运行
class A
{
public:void PrintA(){cout << _a << endl;}
private:int _a;
};
int main()
{A* p = nullptr;p->PrintA();return 0;
}
本道题答案选B。p是空指针,空指针不会编译报错, A错误。因为这里p->Print( )给this指针传递的是p,this指针也为空。而访问Print( )函数中打印_a的值是通过this->_a来实现的。_a存在了this指针指向的空间上,但this指针此时为空,所以找不到_a,代码就会运行崩溃。
class A
{
public:void PrintA(){cout << _a << endl;}
private:int _a;
};
int main()
{A a;A* p = &a;p->PrintA();return 0;
}
在这种情况下,p->Print( )给this指针传递的是对象a的地址,此时this指针不为空,能在this指针指向的空间上找到_a,就可以正常运行。
总结:调用成员函数创建了对象就传递对象的地址,直接创建了指针就传递指针。空指针的this不能访问成员变量,非空指针的this允许在函数体中访问成员变量。
四、C语言和C++实现Stack的对比
1. C语言实现
typedef int DataType;
typedef struct Stack
{DataType* array;int capacity;int size;
}Stack;
void StackInit(Stack* ps)
{assert(ps);ps->array = (DataType*)malloc(sizeof(DataType) * 2);if (NULL == ps->array){assert(0);return;}ps->capacity = 3;ps->size = 0;
}
void StackDestroy(Stack* ps)
{assert(ps);if (ps->array){free(ps->array);ps->array = NULL;ps->capacity = 0;ps->size = 0;}
}
void CheckCapacity(Stack* ps)
{if (ps->size == ps->capacity){int newcapacity = ps->capacity * 2;DataType* temp = (DataType*)realloc(ps->array,newcapacity * sizeof(DataType));if (temp == NULL){perror("realloc申请空间失败!!!");return;}ps->array = temp;ps->capacity = newcapacity;}
}
void StackPush(Stack* ps, DataType data)
{assert(ps);CheckCapacity(ps);ps->array[ps->size] = data;ps->size++;
}
int StackEmpty(Stack* ps)
{assert(ps);return 0 == ps->size;
}
void StackPop(Stack* ps)
{if (StackEmpty(ps))return;ps->size--;
}
DataType StackTop(Stack* ps)
{assert(!StackEmpty(ps));return ps->array[ps->size - 1];
}
int StackSize(Stack* ps)
{assert(ps);return ps->size;
}
在用C语言实现时,Stack相关操作函数有以下共性:
- 每个函数的第一个参数都是Stack*
- 函数中必须要对第一个参数检测,因为该参数可能会为NULL
- 函数中都是通过Stack*参数操作栈的
- 调用时必须传递Stack结构体变量的地址
结构体中只能定义存放数据的结构,相关功能函数不能放在结构体中,数据和操作数据的方式是分离的,而且实现复杂,涉及到大量指针操作,容易出错。
2. C++实现
typedef int DataType;
class Stack
{
public:void Init(){_array = (DataType*)malloc(sizeof(DataType) * 2);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;
};
C++中通过类可以将数据以及方法完美结合,通过访问权限可以控制那些方法在类外可以被调用(封装)。而且每个方法不需要传递Stack*的参数了,编译器编译之后该参数会自动还原,即C++中 Stack *参数是编译器维护的,C语言中需用用户自己维护。