C++从入门到精通——this指针

this指针

  • 前言
  • 一、this指针的引出
    • 问题
  • 二、this指针的特性
  • 三、例题
    • 什么时候会出现编译报错
    • 什么时候会出现运行崩溃
    • this指针存在哪里
    • this指针可以为空吗
  • 四、C语言和C++实现Stack的对比
    • C语言实现
    • C++实现


前言

this指针是一个特殊的指针,在C++类的成员函数中使用。它指向调用该成员函数的对象的地址。通过使用this指针,成员函数可以访问和修改调用它的对象的属性和其他成员函数。这种机制使得成员函数能够识别和操作其所属的对象,从而实现了面向对象编程中的封装性和数据隐藏。


一、this指针的引出

this指针是C++中的一个特殊指针,它指向当前对象。它的引入主要是为了解决成员函数与成员变量同名的问题。

在一个类中,成员函数可以访问类的成员变量。当类的成员变量与成员函数的参数同名时,如果没有使用this指针,编译器无法区分两者。因此,this指针的引入使得编译器能够准确地识别成员变量与成员函数的参数。

this指针可以在非静态成员函数中使用,它指向调用该函数的对象,可以通过this指针访问对象的成员变量和成员函数。

this指针的使用场景主要有以下几种:

  • 在类的成员函数中,如果成员变量与成员函数的参数同名,可以使用this指针来明确指出要访问的是成员变量。
  • 在类的成员函数中,如果需要返回当前对象本身,可以使用return *this;
  • 在类的成员函数中,如果需要在函数中访问当前对象的地址,可以使用this指针来获取。

总结来说,this指针的引入解决了成员函数与成员变量同名的问题,同时也提供了一种简便的方式来访问当前对象的成员变量和成员函数。

问题

我们先来定义一个日期类 Date

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(2022, 1, 11);
	d2.Init(2022, 1, 12);
	d1.Print();
	d2.Print();
	return 0;
}

对于上述类,有这样的一个问题:

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

C++中通过引入this指针解决该问题,即:C++编译器给每个“非静态的成员函数“增加了一个隐藏的指针参数,让该指针指向当前对象(函数运行时调用该函数的对象),在函数体中所有“成员变量”的操作,都是通过该指针去访问。只不过所有的操作对用户是透明的,即用户不需要来传递,编译器自动完成。

二、this指针的特性

  1. this指针的类型:类类型* const,即成员函数中,不能给this指针赋值。
  2. 只能在“成员函数”的内部使用
  3. this指针本质上是“成员函数”的形参,当对象调用成员函数时,将对象地址作为实参传递给this形参。所以对象中不存储this指针。
  4. this指针是“成员函数”第一个隐含的指针形参,一般情况由编译器通过ecx寄存器自动传递,不需要用户传递
  5. 禁止在静态成员函数中使用:静态成员函数不属于任何对象,因此不能使用this指针。
  6. 允许链式调用:this指针的存在允许成员函数进行链式调用,即返回*this指针。
  7. 可以修改成员变量:使用this指针可以访问和修改当前对象的成员变量。
  8. 可以调用其他成员函数:使用this指针可以调用当前对象的其他成员函数。
  9. 可以用于比较和判断是否为同一对象:使用this指针可以比较两个对象是否为同一个对象。

在这里插入图片描述

三、例题

// 1.下面程序编译运行结果是? A、编译报错 B、运行崩溃 C、正常运行
class A
{
public:
	void Print()
	{
		cout << "Print()" << endl;
	}
private:
	int _a;
};
int main()
{
	A* p = nullptr;
	p->Print();
	return 0;
}

在这个代码中,首先定义了一个类 A,其中有一个公有的成员函数 Print() 和一个私有的成员变量 _a

然后在主函数 main 中,定义了一个 A 类型的指针 p,并将其初始化为 nullptr。接下来,通过 p 指针调用 Print() 函数。
由于 p 是一个空指针,正常来说试图通过空指针调用函数会导致运行时错误,但是本题并没有对指针进行解引用调用,而是直接使用cout函数,所以会正常运行。


// 1.下面程序编译运行结果是? A、编译报错 B、运行崩溃 C、正常运行
class A
{
public:
	void PrintA()
	{
		cout << _a << endl;
	}
private:
	int _a;
};
int main()
{
	A* p = nullptr;
	p->PrintA();
	return 0;
}

在这个代码中,首先定义了一个类 A,其中有一个公有的成员函数 Print() 和一个私有的成员变量 _a

然后在主函数 main 中,定义了一个 A 类型的指针 p,并将其初始化为 nullptr。接下来,通过 p 指针调用 Print() 函数。
由于 p 是一个空指针,试图通过空指针调用函数会导致运行时错误,本题是对p指针解引用调用_a,所以会出现运行崩溃(即this->_a)

什么时候会出现编译报错

编译报错通常在编程过程中出现,以下是一些常见的情况:

  1. 语法错误:如果代码中包含了错误的语法,编译器将无法解析代码并报错。例如,缺少括号、缺少分号、错误的变量命名等。

  2. 类型错误:如果代码中使用了错误的类型或进行了不兼容的类型转换,编译器将报错。例如,将字符串赋值给整数类型的变量、使用未声明的变量等。

  3. 缺少依赖库:如果代码中使用了某个依赖库,但没有将其正确导入或链接到项目中,编译器将无法找到该库并报错。

  4. 重复定义:如果代码中定义了重复的变量、函数或类型等,编译器将报错。

  5. 系统限制:有时编译器会实施一些限制,例如最大堆栈大小、代码行数限制等。如果代码超过了这些限制,编译器将报错。

当编译报错时,通常会提供详细的错误信息,其中包含了错误的位置和具体原因,开发人员可以根据这些信息来定位和修复错误。

什么时候会出现运行崩溃

运行崩溃是指在程序运行过程中突然停止或无响应的情况。崩溃可能出现在各种软件和硬件系统中,以下列举了一些常见的运行崩溃的情况:

  1. 程序错误:程序中存在错误或漏洞,导致程序运行时崩溃。这可能是由于编程错误、内存泄漏、资源耗尽等引起的。

  2. 内存问题:程序运行时需要占用大量内存,但系统资源不足,导致程序崩溃。这可能是由于内存泄漏、内存溢出、过多的进程占用内存等引起的。

  3. 硬件故障:硬件设备出现故障,导致程序无法正常运行或崩溃。这可能是由于硬盘故障、电源故障、内存损坏等引起的。

  4. 操作系统错误:操作系统出现错误,导致程序无法正常运行或崩溃。这可能是由于操作系统错误、驱动程序冲突、系统文件损坏等引起的。

  5. 网络问题:程序依赖网络连接进行通信,但网络出现故障或断开,导致程序无法正常运行或崩溃。

总而言之,运行崩溃可能由多种原因引起,包括程序错误、内存问题、硬件故障、操作系统错误、网络问题等。对于开发者来说,重要的是通过调试和测试找出并修复这些问题,以确保程序能够稳定运行。

this指针存在哪里

this指针是在C++类中的一个特殊指针,它指向当前对象的地址。在类的成员函数中,可以使用this指针来访问当前对象的成员变量和成员函数。在C++中,每个非静态成员函数都隐含地包含一个this指针。
在这里插入图片描述

this是个形参,存放在栈区中,或叫ecx寄存器,上述图片可以直接展现编译器将d1的地址存放到寄存器中

this指针可以为空吗

this指针可以为空。在C++中,this指针指向当前对象的地址,如果对象不存在,即为空,this指针也将为空。在访问对象的成员函数时,需要先判断this指针是否为空,以避免访问空指针错误。

这个问题的具体示例在上述的题目,我们给this传入了一个空指针,我们不对this指针进行解引用,程序是正常运行的,我们一旦解引用程序便会报错。

四、C语言和C++实现Stack的对比

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) * 3);
	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;
}
int main()
{
	Stack s;
	StackInit(&s);
	StackPush(&s, 1);
	StackPush(&s, 2);
	StackPush(&s, 3);
	StackPush(&s, 4);
	printf("%d\n", StackTop(&s));
	printf("%d\n", StackSize(&s));
	StackPop(&s);
	StackPop(&s);
	printf("%d\n", StackTop(&s));
	printf("%d\n", StackSize(&s));
	StackDestroy(&s);
	return 0;
}

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

  • 每个函数的第一个参数都是Stack*
  • 函数中必须要对第一个参数检测,因为该参数可能会为NULL
  • 函数中都是通过Stack*参数操作栈的
  • 调用时必须传递Stack结构体变量的地址

结构体中只能定义存放数据的结构,操作数据的方法不能放在结构体中,即数据和操作数据的方式是分离开的,而且实现上相当复杂一点,涉及到大量指针操作,稍不注意可能就会出错。

C++实现

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. C++入门精通——nullptr

    2024-04-11 13:50:02       20 阅读

最近更新

  1. TCP协议是安全的吗?

    2024-04-11 13:50:02       16 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2024-04-11 13:50:02       16 阅读
  3. 【Python教程】压缩PDF文件大小

    2024-04-11 13:50:02       15 阅读
  4. 通过文章id递归查询所有评论(xml)

    2024-04-11 13:50:02       18 阅读

热门阅读

  1. python之迭代

    2024-04-11 13:50:02       11 阅读
  2. 七彩之城的独特序列(蓝桥杯)

    2024-04-11 13:50:02       15 阅读
  3. SpringCloudAlibaba-整合gateway(五)

    2024-04-11 13:50:02       15 阅读
  4. opencv使用滑动窗口提取GLCM特征做svm图像分类

    2024-04-11 13:50:02       12 阅读
  5. uniapp实现文件和图片选择上传功能实现

    2024-04-11 13:50:02       12 阅读
  6. Linux 系统如何设置免密登录

    2024-04-11 13:50:02       14 阅读
  7. 山东济南教育投稿邮箱与投稿信箱

    2024-04-11 13:50:02       13 阅读
  8. PaddleOCR 图片日期识别

    2024-04-11 13:50:02       12 阅读
  9. Linux 文件比较工具

    2024-04-11 13:50:02       11 阅读
  10. 如何构建数据指标体系

    2024-04-11 13:50:02       14 阅读