【ONE·C++ || 类和对象(一)】

总言

  类和对象入门:初步认识面向对象、类的基本认识(定义、访问限定符、作用域、实例化、大小计算)、this指针。

  


  
  
  
  
  
  

1、类的引入

  📌什么是面向对象编程(OOP)和面向过程编程(POP)?它们的主要区别是什么?
  📌解释一下类和对象的概念,以及它们在OOP中的作用。
  📌描述一下封装、继承和多态的概念,并解释它们在OOP中的重要性。
  📌你能给出一个使用面向对象设计模式的例子吗?并解释为什么使用这种模式。
  

1.1、对面向对象和面向过程的初步认识

在这里插入图片描述概念说明

  1、C语言是面向过程的,关注的是过程,分析出求解问题的步骤,通过函数调用逐步解决问题。
  2、C++是基于面向对象的,关注的是对象,将一件事情拆分成不同的对象,靠对象之间的交互完成。

  3、即C++非纯面向对象,可以面向对象和面向过程混编(因其兼容C语言),纯面向对象的语言,比如Java。

在这里插入图片描述简单举例:

  比如:设计一个学校外卖系统。
  对面向过程:
  关注点:点餐、接单、送餐过程,即关注流程函数的实现。
  对面向对象:
  关注点:用户、商家、骑手,即关注点在对象之间的关系。
  
  
  
  
  

1.2、类的引入(结构体在C、C++中的区别)

  1)、C语言和C++对结构体的使用区别
  1、C语言结构体中只能定义变量,在C++中,结构体内不仅可以定义变量,也可以定义函数。

在这里插入图片描述以实现一个栈来举例:

  ①以C语言实现栈:我们要定义一个结构体,并在外部实现其对象的增删查改。

// c
struct Stack
{
	int* a;
	int top;
	int capacity;
};

void StackInit(struct Stack* ps);
void StackPush(struct Stack* ps, int x);
void StackPop(struct Stack* ps);

  ②以C++来实现栈,可以看到,我们能在结构体中直接定义需要的功能函数(增删查改)。此外,这些函数也能访问结构体的成员变量。

//C++
struct Stack
{
	void Init()
	{
		a = 0;
		top = capacity = 0;
	}
	
	void Push(int x)
	{
		// ...
	}

	void Pop()
	{
		// ...
	}

	int* a;
	int top;
	int capacity;
};

int main()
{
	struct Stack st1;//对C的兼容:比如此处的定义结构体
	Stack st2;//升级成为对象:此处创建结构体我们可以直接使用类名

	st1.Init();//升级成为对象:以类似于访问结构体成员的方法访问成员函数。
	st1.Push(1);
	st1.Push(2);
	st1.Push(3);

	return 0;
}

  即,C++兼容C中结构体的语法,同时将结构体struct升级成了类。(以下演示在C语言中结构体自引用部分曾提及过,这里再次提及,用以回顾区别。)

// C
typedef struct ListNode_C
{
	struct ListNode_C* next;//C中定义了结构体,我们在使用时也需要写明struct ListNode_C
	int val;
}LTNode;//typedef在此行以后才生效

// C++
struct ListNode_CPP
{
	ListNode_CPP* next;//而C++中定义结构体,我们可直接使用ListNode_CPP,因为其也是类名。
	int val;
};

在这里插入图片描述
  需要说明的是,尽管可以使用struct,但在C++中更喜欢用class来代替。此外structclass也有细节区别(后续谈到)。
  
  
  
  
  
  

2、类的基本认识

2.1、类的访问限定符和封装

2.1.1、类的访问限定符

  📌什么是类的访问限定符,它们在C++中有哪些?
  📌解释一下public、private和protected访问限定符的含义。

  
  1)、整体介绍
在这里插入图片描述
  
  

  2)、举例理解

在这里插入图片描述对公有和私有:

  在上述,我们演示了用C++来写一个struck栈,此处我们将struct修改为class,即类。

class Stack
{
	void Init()
	{
		a = 0;
		top = capacity = 0;
	}
	
	void Push(int x)
	{
		// ...
	}

	void Pop()
	{
		// ...
	}

	int* a;
	int top;
	int capacity;
};

int main()
{
	Stack st1;

	st1.Init();
	st1.Push(1);
	st1.Push(2);
	st1.Push(3);

	return 0;
}

  可看到结果报错。即:class的默认访问权限为private,struct为默认为public(因为struct要兼容C)。

在这里插入图片描述

  
  
  那么上述问题需要如何修改呢? 这里我们就需要使用到访问限定符:

class Stack
{
public: //在其头上添加上public访问限定符,表示下述内容为公有,直到遇到下一个访问限定符或者类作用域结束。
	void Init()
	{
		a = 0;
		top = capacity = 0;
	}
	
	void Push(int x)
	{
		// ...
	}

	void Pop()
	{
		// ...
	}
private//将类中的成员变量设置为私有。
	int* a;
	int top;
	int capacity;
};

int main()
{
	Stack st1;

	st1.Init();
	st1.Push(1);
	st1.Push(2);
	st1.Push(3);

	return 0;
}

  需要注意的是,privatepublicprotected访问限定符是针对类外访问 。在类中,即使是private私有成员,我们也能正常访问。
  
  
  

在这里插入图片描述一个问题:C++中structclass的区别是什么?

  回答:
  1、C++需要兼容C语言,所以C++中 struct可以当成结构体 使用。此外,C++中struct 还可以用来定义类 ,和class定义类是一样的。
  2、区别是struct 定义的类默认访问权限publicclass定义的类默认访问权限是private
  3、注意:在继承模板参数列表位置,structclass也有区别,后序将学习到。
  
  
  
  
  
  

2.1.2、类的封装(面向对象的三大特性之一)

  1)、面向对象的三大特性:封装、继承、多态

  1、此处的含义是,面向对象有很多特性,但封装、继承、多态这三大特性占据相对重要的位置。
  2、类和对象章节,主要是研究类的封装特性(继承和多态见后续章节)。
  
  

  2)、什么是封装?
在这里插入图片描述
  
  
  

2.2、类的定义和作用域

2.2.1、基础说明

  1)、类的基本结构格式

class className
{
 // 类体:由成员函数和成员变量组成
 
}; // 一定要注意后面的分号

  class:定义类的关键字
  ClassName:类的名字
  {}中为类的主体,注意类定义结束时后面分号不能省略。

  类体中内容:类的成员,类中可存在成员变量和成员函数。
  类中的变量:类的属性或成员变量
  类中的函数:类的方法或者成员函数。
  
  
  
  
  

2.2.2、类成员函数两种定义方式(声明、定义)

2.2.2.1、声明和定义同时定义在类体中

在这里插入图片描述方式一:声明和定义同时定义在类体中

  说明: 成员函数如果在类中定义,编译器 可能会将其当成内联函数处理。

  这里的“可能”指的是编译器有选择的余地,并且,是否真正内联还取决于编译器的优化策略、函数的复杂度、调用频率以及其他一些因素。
  当函数被内联时,调试器在逐语句调试时可能无法“进入”该函数,因为该函数体已经被嵌入到调用它的代码中。这意味着调试器可能不会显示一个单独的内联函数步骤,而是直接跳过到该函数调用之后的代码。然而,调试器通常会提供一些方式来查看内联函数的源代码或汇编代码,以帮助开发者理解程序的执行流程。

  演示代码如下:

class Stack
{
public:
	void Init()
	{
		a = 0;
		top = capacity = 0;
	}
	
	void Push(int x)
	{
		// ...
	}

	void Pop()
	{
		// ...
	}

	int* a;
	int top;
	int capacity;
};

int main()
{
	Stack st1;

	st1.Init();
	st1.Push(1);
	st1.Push(2);
	st1.Push(3);

	return 0;
}

  验证操作如下:
  根据之前所学(C++基础章),debug版本下默认内联不起效,此处几个函数都有Call操作。(以下为相关汇编语言)

	st1.Init();
00275A91  lea         ecx,[st1]  
00275A94  call        std::operator<<<std::char_traits<char> > (027129Eh)  
	st1.Push(1);
00275A99  push        1  
00275A9B  lea         ecx,[st1]  
00275A9E  call        std::endl<char,std::char_traits<char> > (0271299h)  
	st1.Push(2);
00275AA3  push        2  
00275AA5  lea         ecx,[st1]  
00275AA8  call        std::endl<char,std::char_traits<char> > (0271299h)  
	st1.Push(3);
00275AAD  push        3  
00275AAF  lea         ecx,[st1]  
00275AB2  call        std::endl<char,std::char_traits<char> > (0271299h)  

  我们设置后让它生效来观察,可看到直接没有call操作了。
  说明符合条件情况下,当成员函数声明和定义都在类中时,其会被当成内联函数处理。

	Stack st1;

	st1.Init();
00085A91  mov         dword ptr [st1],0  
00085A98  mov         dword ptr [ebp-0Ch],0  
00085A9F  mov         dword ptr [ebp-10h],0  
	st1.Push(1);
	st1.Push(2);
	st1.Push(3);

	return 0;
00085AA6  xor         eax,eax  

  
  
  
  
  

2.2.2.2、声明和定义分离

在这里插入图片描述方式二:声明和定义分离

  声明和定义分离:是指类声明放在.h文件中,成员函数定义放在.cpp文件中。

  此处我们以队列来演示:

//Queue.h头文件 :涵盖了我们定义的类及其成员变量、成员函数声明
struct QueueNode
{
	QueueNode* next;
	int val;
};

class Queue
{
public:
	void Init();
	void Push(int x);
	void Pop();
private:
	QueueNode* head;
	QueueNode* tail;
};

  关于类成员函数声明和定义分离中,有一点需要注意:
  我们在定义成员函数时,需要说清楚对应函数出自哪个域(此处即类域::后续讲到)。
  如果没有作用域限定符指出函数的来源,此处使用其中的类成员tailhead编译会报错。因为编译器是先在局部域中寻找相关相关变量,若没有,则去全局域中寻找。在没有展开命名空间的情况下,编译器是不会自动对命名空间进行搜索的。

//Queue.cpp文件 :涵盖了类成员函数的具体实现方法
#include "f.h"

void Queue::Init()//成员函数名前需要加类名:: 含义是Init这个函数属于Queue该类域
{
	head = tail = nullptr;
}

void Queue::Push(int x)
{}

void Queue::Pop()
{}

  从这里可以得出一个结论:在成员函数声明和定义分离时,不能使用内联,因为内联要求声明和定义不分离。
  
  
  
  
  
  

2.2.2.3、小结

在这里插入图片描述简单使用总结:

  1、类中函数若是小函数,想要成为inline,可以直接放在类里面定义。
  2、类中函数若是大函数,则应该声明与定义分离。
  
  
  使用声明定义分离的主要意义: 方便看类体的整体概况。
  例如:如果只是想了解有些什么函数,而不具体了解该函数怎么实现,就可以直接在.h定义了类体的文件中查看。而当要具体看某个函数的实现时,再跳转到对应的.cpp文件。
  
  
  
  
  
  
  
  

2.2.3、类的作用域

  📌什么是类的作用域,它与对象的作用域有何不同?
  📌解释一下类的作用域解析运算符(::)的含义和用途。
  📌类的作用域与命名空间的作用域有何异同?
  📌什么是类的作用域嵌套,它有哪些规则和限制?
  📌类的作用域与函数的作用域有何关联?
  
  
  1)、基本说明
  类定义了一个新的作用域,类的所有成员都在类的作用域中。 在类体外定义成员时,需要使用::作用域操作符指明成员属于哪个类域。
  
  以下为一个演示例子:

class Person
{
public:
	void PrintPersonInfo();
private:
	char _name[20];
	char _gender[3];
	int _age;
};

// 这里需要指定PrintPersonInfo是属于Person这个类域
void Person::PrintPersonInfo()
{
	cout << _name << " " << _gender << " " << _age << endl;
}

  假若不加Person::,此处的PrintPersonInfo可以和类中的同名函数共存,原因是二者作用域不一致。但此处_name等私有成员变量就不能够被访问。
  
  

  2)、扩展:类域与生命周期
  1、有些域会影响生命周期,有些域只影响访问。 比如,类域和命名空间域只影响访问,而局部域会影响生命周期。
  2、实际上,域更多的影响的是编译器的搜索规则。 比如,默认情况下先在局部域中找,然后再在全局域中寻找。若指定了命名空间,则在命名空间里找,指定了类,则在类里找。
  3、生命周期与存储的位置有关系。 在Linux中,我们将学到进程内存地址空间,比如栈区、堆区、全局区、代码区等。在不同区域存储的变量有其一定的生命周期。 比如堆区是malloc或new出来的,因此其生命周期若我们不free或delete就一直存在。
  
  
  
  
  
  
  

2.3、类的实例化

2.3.1、如何区别一个变量是声明还是定义?

  1)、问题引入

  问题: 区分函数是声明还是定义相对而言比较容易,如此处的 void PrintPersonInfo();,其是函数声明,后续的void Person::PrintPersonInfo()则是函数定义。然而,如何区分一个变量是声明还是定义呢? 比如此处的 _name[20]_gender[3]_age

//person.h头文件

int age;//此处的变量是定义

class Person
{
public:
	void PrintPersonInfo();//这里是函数声明
private:
	char _name[20]; //提问:此处是变量声明还是定义?
	char _gender[3];
	int _age;
};

void Person::PrintPersonInfo()
{
 cout << _name << " "<< _gender << " " << _age << endl;
}

  
  
  

  2)、解释声明和定义
  概述: 对于变量声明和定义,其最大的区别在于是否给变量开辟了空间。 声明只是告诉编译器某个变量或函数的名称和类型,而定义则是实际创建变量并为其分配内存空间的过程。
  
  声明(Declaration): 声明是告诉编译器某个变量或函数的名称以及其类型。它通常出现在代码的顶部,并在其作用域内声明变量的名称和类型。声明不分配内存空间给变量,它只是让编译器知道变量即将被使用。

int a; // 这是一个声明,告诉编译器有一个名为a的整数类型变量

  定义(Definition): 定义是实际创建变量的过程,它不仅告诉编译器变量的名称和类型,还为变量分配内存空间。定义通常在声明之后进行,并且只能进行一次。

int a = 10; // 这是一个定义,它创建了一个名为a的整数类型变量,并为其分配了内存空间,同时初始化为10

  
  
  

  3)、回到上述问题
  上述类中成员变量并没有开辟空间,因此其属于声明。只有当我们用person这个类去定义一个对象时(类的实例化),才算真正的开辟空间。如下:

int main()
{
	Person p1;//对象p1:是用Person类的实例化
	Person p2;

	cout << sizeof(Person) << endl;//问题一:关于类的大小计算。

	return 0;
}

在这里插入图片描述

  此处有一个问题,既然只有类时相关变量还没有开辟空间,为什么我们可以直接用类计算出大小?
  这就像建造房子,虽然我们暂时还没把房屋建造出来,但是因为拥有了平面设计图,我们是能知道这房屋能够住多少人的。
  类似的,虽然我们并没有用类去实例化一个对象,但类的模板在那里,我们自然也能算出其大小。
  
  
  
  

  
  

2.3.2、类的实例化解释

  1)、相关解释
  基本介绍: 用类类型创建一个或多个对象的过程,称为类的实例化。
  事项说明:
    1、是对对象进行描述的,是一个模型一样的东西,限定了类有哪些成员。定义出一个类并没有分配实际的内存空间来存储它
    2、一个类可以实例化出多个对象,实例化出的对象才占用实际的物理空间,存储类成员变量。
  
  
  
  
  
  

2.3.3、扩展:关于头文件中定义一个全局变量,如何在多个.cpp文件中展开使用说明

2.3.3.1、问题说明

  person.h头文件内容:

//这是person.h头文件
#pragma once
#include<iostream>
using namespace std;

int age;//我们在头文件中定义了一个全局变量
 
class Person
{
public:
	void PrintPersonInfo();
private:
	char _name[20];
	char _gender[3];
	int _age;
};

  person.cpp文件内容:

//这是person.cpp函数方法实现文件

#include "person.h"

void Person::PrintPersonInfo()
{
	cout << _name << " " << _gender << " " << _age << endl;
}

  test.cpp文件内容:


#include "person.h"
int main()
{
	Person p1;
	Person p2;

	cout << sizeof(Person) << endl;

	return 0;
}

  三个文件总体预览已经运行结果演示:可发现最后链接错误。这是因为person.h文件在person.cpptest.cpp两个文件中展开,头文件中的 int age该全局变量将被定义两次。

在这里插入图片描述

  
  
  

2.3.3.2、解决方案一:extern

  解决方案一:person.h中将原代码修改为:extern int age
  原理:此处extern是告诉编译器这是一个变量的声明。(PS:需要与extern C区别开,后者是告诉编译器其中代码需要用C语言编译)。

//person.h文件内容:

#pragma once

#include<iostream>
using namespace std;

extern int age;//添加一个extern:告诉编译器这是一个声明

class Person
{
public:
	void PrintPersonInfo();
private:
	char _name[20];
	char _gender[3];
	int _age;
};

  然后在其中一个.cpp文件中定义全局变量,其他文件只需要包含这个声明,就可以在其它.cpp文件中使用该全局变量。

//person.cpp文件内容:
#include "person.h"

int age = 10;//定义

void Person::PrintPersonInfo()
{
	cout << _name << " " << _gender << " " << _age << endl;
}

在这里插入图片描述
  
  
  
  
  

2.3.3.3、解决方案二:static

  解决方案二: 如果不使用extern解决,则可使用static 来解决。
  原理:限制了被修饰变量的链接属性,让其只能在当前文件可见。(而普通的全局变量则是在所有展开文件中可见(放入符号表))。
  解释:如果用static修饰的全局变量是在.h头文件中,而在其它两个不同的.cpp文件中都包含了该头文件,那么该static修饰的全局变量在两个.cpp文件中是不同的存在(连地址都不同)。若没被static修饰,则在正确声明和定义的情况下,该全局变量是同一个。

//person.h文件内容:
#pragma once
#include<iostream>
using namespace std;

static int age;//我们在头文件中定义了一个全局变量

class Person
{
public:
	void PrintPersonInfo();
private:
	char _name[20];
	char _gender[3];
	int _age;
};


//person.cpp文件内容:
#include "person.h"

void Person::PrintPersonInfo()
{
	//cout << _name << " " << _gender << " " << _age << endl;
	cout << "person.cpp age:" << &age << endl;
}

//test.cpp文件内容:
#include "person.h"

int main()
{
	Person p1;
	Person p2;

	p1.PrintPersonInfo();
	cout << "test.cpp age:" << &age << endl;

	return 0;
}

  演示结果:可看到虽然person.hperson.cpptest.cpp文件中展开,但头文件中全局变量age在这两个中的地址不同。

在这里插入图片描述
  
  
  
  
  
  

2.4、类对象模型

2.4.1、如何计算对象的大小(引入篇)

  问题:类中既可以有成员变量,又可以有成员函数,那么一个类的对象中包含了什么?如何计算一个类的大小?
  
  以下为演示使用代码:

class A
{
public:
	void PrintA() //类中有成员函数
	{
		cout << _a << endl;
	}
private:
	char _a; //类中有成员变量
};


int main()
{
	cout << sizeof(A) << endl;
}

  成员函数是否要计算在内呢? 我们可以简单推测与验证一番。
  假如成员函数计算在内,PrintA是函数指针类型,为4个字节,再加上一个char类型的_a,一共5字节。和结构体类似,此处需要对齐,那么总的来说是8字节。

  来看看运行结果:结果却显示此处只有1字节,说明成员函数不计算在内。

在这里插入图片描述

  这就有一个疑惑,类中成员是如何存储的,以至于才会得到上述这样的大小结果?
  
  
  
  
  
  

2.4.2、类对象的存储方式猜测

  演示代码如下:

class A
{
public:
	void PrintA()
	{
		cout <<  "_a " << &_a << endl;
	}
	char _a;
};

  
  1)、类对象存储方式推测一:成员函数和成员变量都存储在类对象里
  这种存储方式导致结果/问题:每个对象中都会保存一份代码,相同代码保存了多次,造成浪费空间。
在这里插入图片描述

class A
{
public:
	void PrintA()
	{
		cout <<  "_a " << &_a << endl;
	}
	char _a;
};


int main()
{
	A aa1;
	A aa2;
	aa1._a = 1; //不同对象中,此处的_a是同一个变量吗?
	aa2._a = 1;
	printf("aa1_a:%p\n", &(aa1._a));
	printf("aa2_a:%p\n", &(aa2._a));
	aa1.PrintA(); 不同对象中,此处的PrintA()是同一个函数吗?
	aa2.PrintA();

}

  验证结果:同一个类类型实例化出的地址不同的对象,其中,相同的成员变量是独自存储的,但却调用同一份成员函数(即成员函数相同)

在这里插入图片描述
  
  
  
  
  2)、类对象存储方式推测二:将成员函数置成一张虚表,在对象中存储指向该虚表的指针
在这里插入图片描述
  结果:这中存储方式的设计保障了成员函数只存储一份,不向方案一一样存储多份。
  
  
  
  
  3)、对象存储方式三:只存储成员对象,将成员函数存储在公共代码区(代码段)
在这里插入图片描述
  对第三种存储方式的含义理解·实例:

class A
{
public:
	void PrintA()
	{
		cout << _a << endl;
	}

	void func()
	{
		cout << "void A::func()" << endl;
	}
//private:
	char _a;
};

int main()
{

	//以下运行结果是什么? A、崩溃  B、编译报错  C、正常运行
	A* ptr = nullptr;
	ptr->func();

	return 0;
}

  回答:程序正常运行。

在这里插入图片描述

  解释:
  1、首先,要明确代码含义:ptr = nullptr,将ptr定义为空指针,而后ptr->func()对空指针解引用。
  2、根据我们所学,空指针解引用是会报错的。但此处既然能正常运行,说明并没有经历解引用空指针这一操作。
  3、而要达成这样的情况,在三种类对象存储方式中,只能是第三种存储方式。

  原因:
  1、若是第一种存储方式(即将函数存储在对象中),调用时就需要解引用指针,在对象中找该函数的地址。但是对象是空指针,解引用会崩溃。
  2、对于第二种存储方式(在对象中存储的是指向函数地址存储的指针),仍旧需要解引用访问对象,通过对象中存储的函数指针,找到函数地址。由于对象为空指针,仍旧会崩溃。
  3、对于第三种(函数地址存储在一个公共代码区),在编译链接时会到该公共代码区找到函数地址对其进行call。而非去ptr指针指向的对象中寻找该函数地址(不去ptr对象中寻找,就不涉及ptr指针的解引用,就并不会发生空指针解引用问题)
  
  
  
  
  
  
  

2.4.3、内存对齐规则与例题分析

  1)、结构体内存对齐规则:同样适用于类

在这里插入图片描述

  
  
  2)、一些例题分析

在这里插入图片描述分析例题一:

  下面不同对象的大小分别是什么?

// 类中既有成员变量,又有成员函数
class A1 {
public:
	void f1() {}
private:
	int _a;
};

// 类中仅有成员函数
class A2 {
public:
	void f2() {}
};

// 类中什么都没有---空类
class A3
{};

int  main()
{
	cout <<" " << sizeof(A1) << endl;
	cout <<" " << sizeof(A2) << endl;
	cout <<" " << sizeof(A3) << endl;

	return 0;
}

在这里插入图片描述

  注意事项:
  1、一个类的大小,实际就是该类中”成员变量”之和,当然要注意内存对齐
  2、注意空类的大小,空类比较特殊,编译器给了空类一个字节用于占位,不存储实际数据,而是标识对象的存在(反过来理解亦然。正因为对象存在,拥有不同的地址,所以不能视作内存大小都为0。)

class A3
{};
int  main()
{
	A3 a1;
	A3 a2;
	A3 a3;
	cout <<" " << sizeof(a1) << endl;
	cout << &a1 << endl;
	cout << " " << sizeof(a2) << endl;
	cout << &a2 << endl;
	cout << " " << sizeof(a3) << endl;
	cout << &a3 << endl;

	return 0;
}

在这里插入图片描述
  
  
  
  
  
  

2.5、this指针

2.5.1、为什么要拥有this指针(引入篇)

  1)、关于类中定义的变量为什么下标要加_的原因:

  在不加下标的情况:

class Date
{
public:
	void Init(int year, int month, int day)
	{
		year = year; //此处的初始化函数:是谁为谁赋值?谁是类成员,谁是传入形参?
		month = month;
		day = day;
	}

private:
	int year;     // 年  
	int month;    // 月
	int day;      // 日
};

int main()
{

	return 0;
}

  ①如上述代码,为防止在类中定义成员函数时,无法区分成员和形参(传入实参)。
  ②故在CPP中,常见变量写法:_yearyear_m_year
  
  以下为命名规则的一些方法说明:
  Ⅰ、单词与单词之间首字母大写间隔(驼峰法),例:GetYear
  Ⅱ、单词全部小写,单词之间用下划线分割,例:get_year
  Ⅲ、这里我们主要采用驼峰法:
    a、函数名、类名等所有单词首字母大写 DateMgr
    b、变量首字母小写,后面单词首字母大写 dateMgr
    c、成员变量首单词前面加下划线"_" _dateMgr

class Date
{
public:
	void Init(int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}

private:
	int _year;     // 年  
	int _month;    // 月
	int _day;      // 日
};

int main()
{

	return 0;
}

  
  
  
  2)、this指针引入:
  根据我们上述代码,我们实现一下这个日期类几个简单功能,让它能够在我们输入日期时,将其打印出来:

class Data
{
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()
{
	Data d1;
	Data d2;
	d1.Init(2022, 11, 11);
	d2.Init(2022, 12, 12);
	d1.Print();
	d2.Print();

	return 0;
}

在这里插入图片描述

  对于上述类,有这样的一个问题:
  Date类实例化了出了两个对象。函数体中没有关于不同对象的区分,那当d1调用 Init 函数时,该函数是如何知道应该设置d1对象,而不是设置d2对象呢?
  
  
  3)、为什么需要this指针:
  C++中通过引入this指针解决该问题,即:C++编译器给每个 “非静态的成员函数” 增加了一个隐藏的指针参数,让该指针指向当前对象(函数运行时调用该函数的对象),在函数体中所有“成员变量”的操作,都是通过该指针去访问。只不过所有的操作对用户是透明的,即用户不需要来传递,编译器自动完成。

  在编译器眼里,上述代码实际上是这样的:

//编译器对类成员函数的处理
class Date
{
public:
	//C++编译器给每个非静态的成员函数增加了一个隐藏的指针(this指针)
	void Init(Date* const this, int year, int month, int day)//此处const加在*之后,修饰的是 this指针本身不能被修改
	{
		this->_year = year;//编译器的处理:在成员变量前面都会加一个this指针,指向相应对象
		this->_month = month;
		this->_day = day;
	}
	//下面这个成员函数也一样
	void Print(Date* const this)
	{
		cout << this->_year << "-" << this->_month << "-" << this->_day << endl;
	}


private:
	int _year;     // 年   -> 声明
	int _month;    // 月
	int _day;      // 日
};

//编译器对实际传参的处理:
int main()
{
	Data d1;
	Data d2;
	d1.Init(&d1,2022, 11, 11);//传递的是类对象的地址
	d2.Init(&d2,2022, 12, 12);
	d1.Print(&d1);
	d2.Print(&d2);

	return 0;
}

  注意事项:
  1、对我们用户而言,我们不能在实参和形参位置处显示传递和接受this指针,但我们 可以在成员函数内部可以使用this指针
  2、注意 this指针是被const修饰的,即使我们能在成员函数中使用,我们也不能随意修改它的指向。
  
  
  
  4)、一些例题分析:

在这里插入图片描述分析例题一:

// 1.下面程序编译运行结果是?  A、编译报错  B、运行崩溃  C、正常运行
// 2.this指针存在哪里?  A、堆  B、栈  C、静态区 D、常量区  
class A
{
public:
	void Print( )
	{
		cout << "Print()" << endl;
	}

private:
	int _a;
};

int main()
{
	A* p = nullptr;
	p->Print();  // C、正常运行

	return 0;
}

在这里插入图片描述

  
  

在这里插入图片描述分析例题二:

// 1.下面程序编译运行结果是?  A、编译报错  B、运行崩溃  C、正常运行
// 2.this指针存在哪里?  A、堆  B、栈  C、静态区 D、常量区  
class A
{
public:

	void PrintA()
		{
			cout << this << endl;
			cout << _a << endl;
			cout << _a << endl;
			//cout << this_a << endl;
		}
		
private:
	int _a;
};

int main()
{
	A* p = nullptr;
	p->PrintA();   // B、运行崩溃

	return 0;
}

在这里插入图片描述
在这里插入图片描述
  
  
  
  

2.5.2、this指针特性

  1、this指针的类型:类类型* const,即成员函数中,不能给this指针赋值。
  2、只能在“成员函数”的内部使用。
  3、this指针本质上是“成员函数”的形参,当对象调用成员函数时,将对象地址作为实参传递给this形参。所以,对象中不存储this指针。
  4、this指针是“成员函数”第一个隐含的指针形参,一般情况由编译器通过ecx寄存器自动传递,不需要用户传递。
在这里插入图片描述

  关于this指针存在哪的问题说明:
  通常情况下this指针存在栈区,因为它是一个形参,形参存在栈上。有时编译器为了提高this访问效率,会对其进行优化处理,比如vs下 优化处理后this指针存放在寄存器(ecx) 中。
  
  
  
  
  
  

2.5.3、C语言实现栈和C++实现栈的对比

  后续学习。
  
  
  
  
  
  
  
  
  
  
  
  

TBC、共勉。

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值