C++中的类与对象你了解多少?

目录

1、面向过程和面向对象初步认识:

2、类的引入:

3、类的访问限定符及封装 :

3.1、访问限定符:

3.2、封装:

4、类的定义:

5、类的作用域:

6、类的实例化:

7、类对象模型:

7.1、如何计算类对象的大小:

7.2、类对象的存储方式猜测:

7.3、内存对齐规则:

8、this指针:

8.1、this指针的引出: 

8.2、this指针的特性: 

8.3、关于this指针常见的面试题: 


1、面向过程和面向对象初步认识:

C
语言是
面向过程
的,
关注
的是
过程
,分析出求解问题的步骤,通过函数调用逐步解决问题、
C++

基于面向对象
的,
关注
的是
对象
,将一件事情拆分成不同的对象,靠对象之间的交互完成、

2、类的引入:

C++引入了类的概念,但是在最初时,是使用关键字struct来引入的类,在C语言中,定义的结构体中只能声明变量,而在C++中,定义的类内不
仅可以声明变量,也可以声明和定义函数、
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
using namespace std;
在C语言中,若定义一个学生的 结构体 ,则如下所示:
//struct Student
//{
//	//在C语言中定义结构体的话,结构体成员中只能声明变量、
//	char _name[20];
//	char _gender[3];
//	int _age;
//};
//上述代码,若在C语言中就看做是定义的一个结构体,若在C++中则看做是定义了一个类、
//由于C++兼容C语言,所以C++也兼容C语言中的关键字struct的用法,所以在C++中也能够成功执行上述代码,只不过是,上述代码在C语言看来就是定义了一个结构体,而在C++看来则是定义了一个类,除此之外,在C++中,该类里面不仅仅可以声明变量,也可以声明和定义函数,C++同时对C语言中的关键字struct进行了升级,把C语言中的关键字struct定义的结构体升级成了类,主要意义在于:
//1、现C++中的类名 Student 可以直接作为类型进行使用、
//2、在类里面,除了可以声明变量之外,还可以声明和定义函数、
//在C++中,下述代码则会被视为定义的一个类,其中,struct为类关键字,Student为类名,或者是类标签、
struct Student
{
	//声明和定义函数、
	void Init(const char* name, const char* gender, int age)
	{
		strcpy(_name, name);
		strcpy(_gender, gender);
		_age = age;
	}
	void Print()
	{
		cout << _name << " " << _gender << " " << _age << endl;
	}
	//声明变量、
	//C++中,当在类里面声明变量时,一般在变量名前面或者后面加上_ ,(或者是其他形式,看公司,C++并没有规定必须写成什么形式,但Java中有着明确的规定),注意不是必须要加的,主要是用来表明这是类里面的成员变量,否则可能在某些地方会出现歧义、
	char _name[20];
	char _gender[3];
	int _age;
};   
//注意: 上面的类中的变量的声明和函数的声明和定义的位置是随意的,变量上,函数下或者变量下,函数上或者任意位置,都是可以的,这是因为: 类是一个整体,此时编译器并不是只会向上找,而是在该类这个整体里面去寻找,所以上述写法也是可以的,除了类之外,则编译器均默认是向上查找的,那么上述类里面的写法就会出现错误,还要知道,在C++中看到上述代码,要把他看做是定义的一个类,不要把他看做是定义的一个结构体,而我们所谓的C++兼容C语言中的关键字struct的用法,只不过是在使用时可以按照结构体的用法去使用,但本质上已经升级成了类,所以要看做类去处理、

在C语言中:
结构体:
//struct ListNode
//{
//	int val;
//	struct ListNode* next; //不可以写成:ListNode* next,即使加上typedef也是不可以的,如下所示:
//};
结构体:
//typedef struct ListNode
//{
//	int val;
//	ListNode* next; //错误写法、
//}ListNode;
在C++中可以写成如下所示:
类:  类,不是只能定义一个,可以定义多个、
//struct ListNode
//{
//	int val;
//	//C++兼容C语言中struct的用法:
//	struct ListNode* next;//正确写法、
//	//新增用法,这是因为C++对C语言中的struct进行了升级,把C语言中的struct结构体升级成了类,下面则是类的使用方法:
//	ListNode* next; //正确写法、
//};

在C++中一般不需要再对struct ListNode进行typedef为ListNode,因为类名可以直接当做类型进行使用,除非类名过长,若再想对类名进行重命名的话,一般写成下面这种情况;
一、
//struct ListNode
//{
//	int val;
//	ListNode* next;
//};
//typedef ListNode ST;  //struct ListNode s2 或者 ListNode s2; 或者 ST s2;   其中: ST s2; === ListNode s2;
二、
//struct ListNode
//{
//	int val;
//	ListNode* next;
//};
//typedef struct ListNode ST;  //struct ListNode s2 或者 ListNode s2; 或者 ST s2;    其中: ST s2; === struct ListNode s2;
上述方法一般不经常使用,意义不大、

int main()
{
	C++兼容C语言中struct的用法,如下所示;
	//struct Student s1;  //s1在C++中常称为对象,类定义的对象、
	//新增用法如下:
	Student s2;           //s2在C++中常称为对象,类定义的对象、
	//访问类里面的变量:
	s2._age = 10;
	//调用类里面的函数;
	s2.Init("惠俊明", "男", 25); //初始化、
	s2.Print();//打印、 //惠俊明 男 25
	return 0;
}

C++里
类的定义中,虽然可以使用关键字struct来定义类,但是更喜欢用关键字
class
来代替关键字struct来定义类,那么使用这两个关键字来定
义类有什么不同呢,下面再进行阐述、

3、类的访问限定符及封装

3.1、访问限定符:

C++实现封装的方式:用类将对象的
属性与方法
结合在一块,让对象更加完善,通过访问权限,选择性的将其接口提供给外部的用户使用,
类里
面的内容并不一定全部是开源的,比如说,开源的内容都定义成公有,则使用关键字public加冒号来定义,访问限定符是限制在类外面使用对象
进行访问、

访问限定符说明:
1、公有public修饰的成员函数和成员变量(成员对象),在类外可以被访问,而私有private修饰的成员函数和成员变量(成员对象),在类外不可以
被访问、
2、protected和
private修饰的成员函数和成员变量(成员对象),在类外不能被访问(
此处
protected

private
是类似的,现阶段认为这两者的功能是
一样的,但是在继承中,两者就会出现区别,具体后期再进行讲述
)、
3、访问权限的
作用域是从该访问限定符出现的位置开始,直到下一个访问限定符出现的位置为止,类中的最后一个访问限定符的访问权限作用
域是从该访问限定符出现的位置开始,直到 };结束、
4、关键字class
的默认访问权限为私有
private
,关键字
struct默认的访问权限
为公有
public,(
因为关键字
struct
要兼容
C语言),两者均可以手动写
上访问限定符、
5、关键字struct默认的访问权限为公有public,但是也可以手动的写上访问限定符,比如手动写上访问限定符私有
private,则在结构体外面不可
以被访问、
6、不管在关键字class还是关键字struct定义的类中,不是只能有一个访问限定符,而是可以存在多个访问限定符、
注意:访问限定符只在编译时有用,当数据映射到内存后,没有任何访问限定符上的区别、

面试题:
问题:
C++
中关键字
struct
和关键字
class
的区别是什么?
解答:
C++
需要兼容
C
语言,所以
C++
中关键字
struct
可以当成结构体去使用其用法,另外
C++
中关键字
struct
还可以用来定义类,和关键字class
是定义类是一样的,区别是关键字
struct
的成员默认访问方式是
public
,关键字
class
是的成员默认访问方式是private、

3.2、封装:

C++面向对象具有三大特性:封装、继承、多态、

在类和对象阶段,我们只研究类的封装特性,那什么是封装呢?

封装:将类的数据和操作数据的方法(类的方法)进行有机结合,隐藏对象的属性和实现细节,仅对外公开某一些接口来和对象进行交互、
封装本质上是一种管理:我们如何管理兵马俑呢?比如如果什么都不管,兵马俑就被随意破坏了,那么我们首先建了一座房子把兵马俑给封装
来,但是我们目的全封装起来,不让别人看,所以我们开放了售票通
,可以买票突破封装在合理的监管机制下进去参观,类也是一样,我们使
用类数据和方法都封装到一下,不想给别人看到的,我们使用protected/private把成员封装起来,开放一些共有的成员函数对成员合理的访
问,所以封装本质是一种管理、

//C语言定义栈结构、
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
//在C语言中定义的栈结构的数据和方法是分离的、
struct Stack
{
	//栈结构的结构体成员变量/数据/属性、
	int* a;
	int top;
	int capacity;
};
//栈结构操纵数据的方法、
void StackInit(struct Stack*ps)
{
	ps->a = NULL;
	ps->top = 0; //ps->top = -1;
	ps->capacity = 0;
}
void StackPush(struct Stack*ps, int x)
{}  //具体实现省略、
int StackTop(struct Stack*ps)
{}  //具体实现省略,以此方法为例、
//在C语言中定义的栈结构的数据和方法是分离的,会存在什么样的问题呢?
//太过自由,不能很好的进行管理,如下所示:
int main()
{
	//定义一个栈(局部结构体变量)、
	struct Stack st;
	//初始化栈、
	StackInit(&st);
	//入栈数据、
	StackPush(&st, 1);
	StackPush(&st, 2);
	StackPush(&st, 3);
	//取栈顶元素、
	//方法一: 规范的方法->调用函数接口来取栈顶元素、
	printf("%dn", StackTop(&st));
	//方法二: 不规范的方法、
	printf("%dn", st.a[st.top]);
	//方法二也是可以的,但是会在某些情况下存在歧义,比如:top不明确到底是栈顶元素的位置还是栈顶元素的下一个位置,
	//此时,使用者就可能存在误用,top不明确到底是栈顶元素的位置还是栈顶元素的下一个位置,取决于初始化,若初始化top为0
	//则正确访问应该写成:printf("%dn", st.a[st.top-1]); 若初始化top为-1,则正确写法应是: printf("%dn", st.a[st.top]);
	//数据结构只是一个思想,并没有规定必须把top初始化为某个值,所以使用方法二是不合适的,即,太过自由,不能很好的进行管理,是不好的、
	//这是因为方法二的使用和栈结构的结构体成员变量/数据/属性的定义是具有联系的,比如:top初始化的值决定了方法二中到底使用那个语句,
  //其次,如果改成链表来实现栈结构的话,那么方法二也需要进行改动才可以达到目的、
	return 0;
}
//接下来看一下C++是如何解决上述问题的: C++不会存在像上面这种误用的情况,所以不会出现歧义的,这是因为C++设计出了类、

//C++定义栈结构、
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
using namespace std;
//在C++中定义的栈结构的 类的数据和类的方法 是合并的、
//1、类的数据和类的方法一起封装到类里面、
//2、当封装到一起后才可以控制访问权限,这样在C语言中若直接通过 数据 进行访问的话,可能会存在歧义,但是若在C++中,把类的方法和类的数据都封装到类中,再把类的数据设置为私有,把类的方法设置为公开,这样一来,在类外面就不可以访问私有的内容,这样就不可以通过 类的数据 来进行访问,就避免了上述可能会出现的歧义、
class Stack
{
	//C++的封装本质上就是一种更加严格管理的设计、
	//一般情况下,设计类,成员变量/成员对象/类的数据/类的属性都是设置成私有或者保护的,类的方法,即成员函数的权限要分情况考虑,若想开源的就设置成公用,若不想开源的就设置成私有或者保护即可,所以成员函数(类的方法)并不是一定为公用
	//也不是一定为私有或保护的,要根据自己的情况进行设定,但成员变量/成员对象/类的数据/类的属性在一般情况下都会设置成私有或保护的、
private:
	void CheckCapacity()
	{}    //用户没有必须去调用判断是否需要增容的函数接口,所以就设置成私有或者保护的即可、
public:
	//栈结构操纵数据的方法,即类的方法如下,其次,调用函数的函数名可以简化,并且也不需要把结构传过来、
	void Init()
	{}
	void Push(int x)
	{}
	int Top()
	{}
private:
	//栈结构的成员变量/成员对象/类的数据/类的属性、
	int* _a;
	int _top;
	int _capacity;
};
int main()
{
	//定义一个栈、
	Stack st;
	//初始化栈、
	st.Init();
	//入栈数据、
	st.Push(1);
	st.Push(2);
	st.Push(3);
	//取栈顶元素、
	//只能通过调用函数接口来取栈顶元素、
	cout << st.Top() << endl;
	错误方法:
	//cout << st._a[st._top] << endl;//这是因为在类里面已经把 类的数据 设置为私有,所以在类的外面不可以进行访问,这样要想成功运行,必须使用上述唯一的方法,所以就避免了出现歧义,这样就不需要考虑到底把top初始化为0还是-1了,不需要关心,这就体现出了C++强制的通过定义使得用户在使用的时候更加规范,这样一来,不需要考虑栈到底是使用顺序表还是链表来实现的,也不需要考虑top的值到底初始化为0还是-1,只需要调用对应的函数接口就可以达到目的,底层的实现过程不需要考虑,都不会影响调用函数接口得到的结果,这就叫做低耦合,关联关系低,在C语言中如果只使用方法一的话也可以做到低耦合,但是避免不了会有人误用了方法二,这样就会出现问题,而在C++中,已经规定了类似于C语言中方法二的用法是错误的,所以就避免了出现歧义的情况关联关系越高,越不好、
	return 0;
}

4、类的定义:

class className    //类关键字:class    类名:className
{
   //类体:由成员函数和成员变量(成员对象)组成、
 
}; // 一定要注意后面的分号
class为定义类的关键字,ClassName为类的名字,{ }中为类的主体,注意类定义结束时后面分号,类中的元素称为类的成员:类中定义的变量
称为类的属性,类的数据成员变量或者是成员对象,类中定义的的函数称为类的方法(操纵数据的方法)或者成员函数、
在C语言中定义的结构体里面定义的变量称为结构体成员变量,数据或者是属性,一般不称为成员对象,因为C语言不存在对象这一概念,在结
构体外面定义的函数称为方法(操纵数据的方法)、
类里面还可以嵌套定义类,这就是所谓的内部类,在后期会进行阐述,类,不是只能定义一个,可以同时定义多个、
在一些项目中,如果代码较多时,要把调用函数的声明和定义进行分离(在不同的文件中),其好处在于,比如在C语言中的自定义的头文件里面就
可以知道,声明的结构体的整体结构是什么,声明了那些调用函数,要想看调用函数的具体实现,就去源文件中查看即可,而在C++中定义类的
话,就如下所示:
类的两种定义方式:

注意:下面不管是那种方式,类的数据都是在头文件(.h文件)中的类体中进行声明的、
一、成员函数的声明和定义全部放在头文件(.h文件)中的类体里面(即,成员函数的声明和定义不分离(所谓分离即为在不同的文件中),并且两者
不分开写),需要注意:类成员函数如果在类体中进行定义,编译器默认将其当成内联函数处理(一般情况下,较短的函数并且函数体内不存在循
环/递归的时候,会在类体内定义,但不是强制规定,而较长的函数或函数体内存在递归或循环等,一般采取方法二)、
//类成员函数在 类内 定义、
//Stack.h文件
#pragma once
class Stack
{
public:
	//类成员函数的声明和定义:
	void Init()  //不需要再指定类域、
	{
		//从 类内 进行的访问、
		_a = nullptr;
		_top = 0;
		_capacity = 0;
	}
	void Push(int x)//不需要再指定类域、
	{}
	void Pop()//不需要再指定类域、
	{}
private:
	int* _a;
	int _top;
	int _capacity;
};

二、
类成员函数的声明在头文件(.h文件)中的类体里,类成员函数的定义在源文件(.cpp文件)中,即类成员函数的声明和定义分离(在不同的文件中):
//类成员函数在 类外 定义、
//Stack.h文件
#pragma once
class Stack
{
public:
	//类成员函数的声明:
	void Init();
	void Push(int x);
	void Pop();
private:
	int* _a;
	int _top;
	int _capacity;
};

//Stack.cpp文件
#include"Stack.h"
//类成员函数的定义:
void Stack::Init() //指定类域、
{
	_a = nullptr;
	_top = 0;
	_capacity = 0;
}//访问限定符限制的是从 类外 进行的访问,此处在类成员函数Init的定义里面访问类成员变量,即类成员函数访问类成员变量不属于从类外进行的访问,因为,虽然该类成员函数的定义在类外,但是该类成员函数的声明在类内,而我们要以声明所在的位置为准,所以,即使该类成员函数的定义在类外,但,该类成员函数仍属于类内,而该类成员函数的函数体内中的所有内容均属于该类成员函数,而该类成员函数仍属于类内,所以,该类成员函数的函数体中的所有内容也均属于类内,这样的话,类成员函数访问类成员变量就属于是从类内进行的访问,所以这种情况下,访问限定符不起任何作用、

//该类成员函数属于类内,可以从该类成员函数的声明和定义中看出来:
//其一,在该类成员函数的定义中,该类成员函数的函数名前面标明了类域、
//其二就是该类成员函数的声明存在于类体里面、
//所以由此可知,该类成员函数是属于这个类内,故该类成员函数定义中的所有内容均属于这个类内,所以此处并
//不属于从 类外 进行的访问,所以即使类的数据是私有的,也是可以访问的,还要知道,访问限定符限制的是从 //类外 进行的访问,但并不限定从 类内 进行访问,从 类内 可以任意访问到私有或者保护和公用的内容、
void Stack::Push(int x) //指定类域、
{}
void Stack::Pop() //指定类域、
{}

注意:可以采用方式一进行类的定义,也可以采用方式二,也可以方式一和二混用,但在一般情况下,更期望采用第二种方式,但这是常见的两

种方式,还有其他方式,比如把方式一整体放在test.cpp里面中main函数的上面也是可以的、

5、类的作用域:

类定义了一个新的作用域
,类的所有成员都在类的作用域中,
在类体外定义成员
,需要使用
::
作用域解析符指明成员属于哪个类域、
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
using namespace std;
//类域、
class Person
{
public:
	void PrintPersonInfo();
private:
	char _name[20];
	char _gender[3];
	int _age;
};
//这里需要指定PrintPersonInfo是属于Person这个类域、
void Person::PrintPersonInfo()
{
	cout << _name << " "<<_gender << " " << _age << endl;
}
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
using namespace std;
//类,不是只能定义一个,可以同时定义多个,如下所示:
//Stack类域、
class Stack
{
public:
	void Push(int x)
	{}
};
//Queue类域、
class Queue
{
public:
	void Push(int x)
	{}
};
//这两个Push函数可以同时存在,这是因为处于不同的作用域内,在不同的作用域内可以存在函数名相同且形参列表相同的函数,以及相同的变量名,
//上述的两个Push函数不构成函数重载(在同一个作用域内,函数名相同,但形参列表必须不同)、	

6、类的实例化:

头文件(.h文件)中的类体里面的类成员变量(包括C语言中,结构体里面的结构体成员变量)只是声明,并不是定义,一般情况下,变量或函数的声
明和定义的区别在于,是否为其开辟了内存空间,当使用类名去定义对象时(或者是在C语言中当使用结构体类型定义变量时),才算是对头文件
(.h文件)中的类体里面的类成员变量(C语言中,结构体里面的结构体成员变量)进行了定义,才为其开辟了内存空间,局部变量,静态变量,全局
变量这三者的声明均不会为其开辟内存空间,而局部变量的定义是在栈区上为其开辟空间,静态变量和全局变量的定义都是在静态区(数据段)上
为其开辟内存空间,函数的声明和定义的区别也是在于是否为其开辟了内存空间,其声明是不开辟内存的,仅仅告诉编译器,要声明的部分存
在,要预留一点空间,定义则需要开辟内存,在定义函数时,不管是全局函数还是类成员函数,均是以函数体二进制代码的形式在内存中的公共
代码区上为其开辟内存空间、
用类类型创建对象的过程,称为类的实例化:
1、类只是一个模型一样的东西,是一种自定义的数据类型,系统并不为类分配存储空间,限定了类有哪些成员,定义出一个类,并没有分配实
际的内存空间来存储它,在C语言中定义出一个结构体,也没有分配实际的内存空间,在通常情况下,我们讨论"分配空间"时,指的仅仅是数
据,对于代码和语义的东西,那完全是不同的概念,比如说函数,他本来由高级语言编写,编译后只不过是转换成机器语言代码,保存在程序中
而已,其它没什么实质变化,程序运行时,所有的机器语言代码都调入内存,用到谁就一条条地执行谁,所以,上述所谓的并没有分配实际的内
存空间来存储它,指的只是类体里面的类的数据,并没有指类体中的类的方法,在类体中既可以声明类的数据,也可以声明和定义类的方法,类
的方法是以函数体二进制代码的形式存放在内存中的公共代码区上的,类的数据(即类成员变量,它不属于局部变量,也不属于全局变量),只是
局部变量或全局变量中的一部分而已,目前来说,不考虑C语言中在结构体成员变量前面加上关键字static的情况,类本身并不可以存储数据,但
类定义出或者是实例化出的对象就可以存储数据了,C语言中定义的结构体本身不可以存储数据,但是由结构体类型定义出的结构体变量就可以
存储数据了、
2、一个类可以实例化出多个对象,实例化出的对象占用实际的物理空间,用来存储类成员变量、
3、做个比方,类实例化出对象就像现实中使用建筑设计图建造出房子,类就像是设计图,只设计出需要什么东西,但是并没有实体的建筑存
在,同样类也只是一个设计,实例化出的对象才能实际存储数据,占用物理空间、
4、所谓的计算所占内存空间的大小,包括占用的内存空间中的所有的区,而在计算类实例化出来的对象的大小时,并没有计算类中的类成员函
数所占的内存大小,是因为,类的成员函数并不存在于类实例化出来的对象里面,类的成员函数是共用的,假设有一个调用函数为:

void HjmLcc(){ },,该调用函数可以作为类的成员函数,也可以作为普通的函数,只不过是,当作为类的成员函数时仅仅比作为普通函数时,
在形参部分会多出一个this指针而已,其他地方则没有区别,类实例化出来的对象里面无非就是对类的数据进行的实例化,函数不管是普通还是
类的成员函数(包括非静态类成员函数和静态类成员函数),均是以函数体二进制代码的形式在内存中的公共代码区上为其开辟内存空间,这两者
都是在进行调用时才会在栈区上建立栈帧、
5、类的静态类成员变量在类定义时就已经在全局数据区分配了内存(全局变量也是如此),因而它是属于类的,对于非静态类成员变量,是在类的
实例化过程中(构造对象)才在栈区,数据段(静态区)或者堆区为其分配内存,是为每个对象生成一个拷贝,所以它是属于对象的、
6、应当说明,常说的"某某对象的类成员函数",是从逻辑的角度而言的,而类成员函数的存储方式,是从物理的角度而言的,二者是不矛盾的、
7、静态类成员函数和非静态类成员函数的区别:静态类成员函数和非静态类成员函数都是在类的定义时放在内存的公共代码区的,因而可以说
它们都是属于类的,但是类为什么只能直接调用静态类成员函数,而非静态类成员函数(即使函数没有参数)只有类对象才能调用呢?原因是类的
非静态类成员函数其实都内含了一个指向类对象的指针型参数(即this指针),因而只有类对象才能调用(此时this指针有实值)、
8、不论类成员函数在类内定义还是在类外定义,类成员函数的代码段都用同一种方式存储,不要将类成员函数的这种存储方式和inline(内联)函
数的概念混淆,不要误以为用inline声明(或默认为inline)的类成员函数,其代码占用对象的存储空间,而不用inline声明的类成员函数,其代码不
占用对象的存储空间,不论是否用inline声明(或默认为inline),类成员函数的代码都不占用对象的存储空间,用inline声明的作用是在调用该函数
时,将函数的代码复制插入到函数调用点,而若不用inline声明,在调用该函数时,流程转去函数代码的入口地址,在执行完该函数代码后,流程
返回函数调用点,inline与类成员函数是否占用对象的存储空间无关,它们不属于同一个问題,不应搞混、
静态类成员:
静态类成员就是在类成员变量或类成员函数前面加上关键字static,称为静态类成员,静态类成员分为:静态类成员变量和静态类成员函数、
静态类成员变量:在类成员变量前面加上关键字static、
1、均共享同一份该静态类成员变量中存储的数据,即,该静态类成员变量中存储的数据当通过某一途径访问到的值假设为整型100,若通过其他
途径再次进行访问时,访问到的值仍为整型100,多个途径访问到的数据是同一个数据,静态类成员变量是不属于对象的,而是属于类的、关键
字static修饰的类成员变量属于类,不属于某个具体的对象,即使创建多个对象,也只为静态类成员变量分配一份内存,所有对象使用的都是这份
内存中的数据,当某个对象修改了该静态类成员变量中存储的数据时,也会影响到其他对象、
2、在编译阶段分配内存,并不是在创建对象之后才在栈区(用类名实例化出局部对象),数据段(静态区,用类名实例化出全局对象或静态对象)或
者堆区(动态开辟)分配的内存,相当于在程序运行之前就已经分配了内存,而能够在程序运行之前就可在内存中分配内存空间的区域只有:公共
代码区和全局数据区,所以是在编译阶段在全局数据区内为其分配内存空间、
3、在类内声明,在类外进行定义,并且两者都必须要进行之后才可以去使用该静态类成员变量,否则不能使用,若只在类内进行声明,并未在
类外进行定义,则不管是访问还是修改该静态类成员变量中的值,都会报一个链接错误,所以,这两者必须都要进行后才不会报错,要注意:当
在类外进行定义时,要进行分类讨论:
(1)、如果所有的代码均在test.cpp文件中的话,静态类成员变量在类外进行定义要保证不能在main函数中进行,还要保证必须在类体外的下面进
行,在满足这些条件的前提下,可在任意位置进行静态类成员变量的定义、
(2)、若把类定义在头文件Stack.h中,并且静态类成员变量的声明在类体中,静态类成员变量的定义则可以在源文件Stack.cpp中进行,除此之
外,静态类成员变量的定义也可以放在头文件Stack.h中,但还要保证在类体外的下面才可以,原因同上,静态类成员变量的定义和初始化的格式
见代码所示,在类外进行定义时,可以赋初值,也可以不赋值,即可以初始化,也可以不初始化,如果不赋值,那么会被默认初始化为 0,全局
数据区的变量都有默认的初始值 0,而动态数据区(堆区、栈区)变量的默认值是不确定的,一般认为是垃圾值、
4、静态类成员变量也具有访问权限,静态类成员变量在内存中(全局数据区)只有一份,非静态类成员变量在内存中(栈区,静态区,堆区)不一定
只有
一份,可能会存在多份、
5、静态类成员变量在类外进行定义和初始化时不能再加关键字 static,但必须要有数据类型,被 private、protected、public 修饰的静态类成员
变量都可以用这种方式初始化、
6、关键字static修饰的类成员变量的内存既不是在类内声明时分配,也不是在创建对象时分配,而是在(类外)进行定义和初始化时分配,反过来
说,没有在类外进行定义和初始化的被关键字 static 所修饰的类成员变量不能使用、

 
7、关键字 static 修饰的类成员变量不占用对象的内存,而是在所有对象之外开辟内存(全局数据区),即使不创建对象也可以访问,具体来说,
static 类成员变量(静态类成员变量)和普通的 static 变量(静态变量)类似,都在内存分区中的全局数据区分配内存,并且都仅有一份,到程序结束
时才释放,这就意味着,static 类成员变量不随对象的创建而分配内存,也不随对象的销毁而释放内存,而普通的类成员变量在对象创建时分配
内存,在对象销毁时释放内存、
8、一个类体中可以有一个或多个静态类成员变量,均共享这些静态类成员变量,都可以引用它、
#include <iostream>
using namespace std;
//静态类成员变量:
class Person
{
public:
	//int m_A; //非静态类成员变量,只是声明、
	static int _mA;//静态类成员变量,只是声明,类内声明、

	//静态类成员变量也具有访问权限、
private:
	static int _mB;//静态类成员变量,只是声明,类内声明、
};
int Person::_mA = 100; //类外定义和初始化,要指定类域、
//类外定义和初始化,但不能在main函数中进行,还要保证在 类体外 的下面进行,因为使用到了 Person::,若在 类体外 上面进行的话,系统会向上查找Person::,就找不到,所以会报错,
//在满足上述条件的基础下,可以在任意位置进行静态类成员变量的定义和初始化,其格式如上所示、
//上述在进行静态类成员变量的定义和初始化时,必须要进行指定类域,若写成: int _mA = 10; 的话,这代表的是全局变量,这样就看不出来是对类体中的静态类成员变量进行的定义和初始化
//所以必须要指定类域才可以、

//类外定义和初始化:
int Person::_mB = 200;
void test01()
{
	Person p;
	cout << p._mA << endl; //100
	Person p2;
	p2._mA = 200;
	cout << p._mA << endl;//200
}
void test02()
{
	//非静态类成员变量属于对象,而不属于类,所以其访问形式只能通过对象进行访问,不可以通过类名加作用域限定符的形式进行访问、
	//静态类成员变量不属于对象,而属于类,所以它有两种访问形式:
	//1、通过对象进行访问静态类成员变量:
	Person p;
	cout << p._mA << endl; //100
	//2、通过类名加作用域限定符的形式进行访问静态类成员变量,也可以不用创建对象再使用对象来进行访问,因为静态类成员变量本身就不属于对象,而属于类、
	cout << Person::_mA << endl; //100
	
	//cout << Person::_mB << endl; //错误写法,在 类外 不可以访问私有的静态类成员变量、
}
int main()
{
	//test01();
	test02();
	return 0;
}
静态类成员函数:在类成员函数前面加上关键字static、
1、均共享同一个静态类成员函数,静态类成员函数属于类,不属于对象、
2、静态类成员函数只能访问静态类成员变量,不能访问非静态类成员变量,非静态类成员函数可以访问静态和非静态类成员变量、
3、静态类成员函数在内存中(公共代码区)中只有一份,非静态类成员函数在内存中(公共代码区)中也只有一份,平常常说的类成员函数和类成员
变量是分别默认为非静态类成员函数,非静态类成员变量,一个类体中可以有一个或多个静态类成员函数和非静态类成员函数,均共享这些静态
类成员函数和非静态类成员函数,都可以引用它、
4、静态类成员函数也具有访问权限、
5、
(1)、当所有的代码均在源文件test.cpp中时,静态类成员函数和非静态类成员函数均可在类体中进行声明和定义、除此之外,只在类体中进行声
明的话,而定义要在类体外的话,不能把定义放在其他函数内部,只能在其他函数外部,即全局中来进行定义,这是因为函数不能嵌套定义,只
能嵌套调用,但是还要保证放在类体外的下面,原因同上,在保证这里条件的基础下,在任意位置进行静态类成员函数和非静态类成员函数的定
义都是可以的、
(2)、若把类定义在头文件Stack.h中,并且只在类体中进行静态类成员函数和非静态类成员函数的声明,则静态类成员函数和非静态类成员函数
的定义可以在源文件Stack.cpp中进行,除此之外,静态类成员函数和非静态类成员函数的定义也可以放在头文件Stack.h中,但还要保证在类体
外的下面才可以,原因同上,除此之外,若把类定义在头文件Stack.h中的话,把静态类成员函数和非静态类成员函数的声明和定义均放在类体中
也是可以的,不管是静态类成员函数还是非静态类成员函数,都可以把定义放在类外,而静态类成员变量的定义必须在类外,非静态类成员变量
的定义是在由类名实例化对象的过程中进行定义的,静态类成员函数和非静态类成员函数的定义的格式是一样的,见代码所示、
    #include <iostream>
using namespace std;
//静态类成员函数:
//1、均共享同一个静态类成员函数、
//2、静态类成员函数只能访问静态类成员变量,不能访问非静态类成员变量、
class Person
{
public:
	//void func()//非静态类成员函数的声明和定义、
	//{}
	static void func()//静态类成员函数的声明和定义、
	{
		_mA = 100;//类体内的类成员函数访问类成员变量不受访问限定符的限制,限定符只限制从类外进行的访问,类成员函数可以访问类成员变量,类体内的一切对于类成员函数而言都是透明的,
		//所以上述操作是可以的,即使静态类成员变量_mA是私有的,也是可以的,这就是所谓的静态类成员函数可以访问静态类成员变量、
		cout << "static void func调用" << endl;
		//_mB = 200;
		//报错,静态类成员函数不可以访问非静态类成员变量,原因是:首先,该静态类成员函数在内存中(公共代码区)只有一份,非静态类成员变量属于对象,而不属于类,所以其访问形式只能通过对象进行访问
		//所以要先创建对象,再通过该对象来访问该非静态类成员变量,而静态类成员函数也是共用的,大家共享同一份静态类成员函数,比如由该类名Person实例化出来了两个对象分别为:P1和P2,然后再通过这两个对象调用同一个静态类成员函数func来访问该非静态类成员变量_mB,而该函数体(不管是静态类成员函数还是非静态类成员函数)中并不能区分不同的对象,虽然
		//对象P1和P2在调用这个静态类成员函数func,但是该函数体内并不能区分不同的对象,所以静态类成员函数不可以访问非静态类成员变量,因为静态类成员函数中并没有内含this指针,但是在非静态类成员函数中是可以访问非静态类成员变量的,即使其非静态类成员函数的函数体内不能区分不同的对象
		//但非静态类成员函数中都内含了一个this指针,所以也可以达到目的,而这里为什么静态类成员函数可以访问静态类成员变量,是因为,静态类成员变量_mA并不属于对象,而是属于类,该静态类成员变量_mA是共享的,大家均共用同一份静态类成员变量_mA,所以在该静态类成员函数的函数体内
		//并不需要区分该静态类成员变量_mA到底属于哪个对象,因为静态类成员变量_mA根本就不属于对象,而属于类,大家都共享同一份静态类成员变量_mA,不需要来区分该静态类成员变量_mA到底属于哪个对象,所以、静态类成员函数是可以访问静态类成员变量的、
	}
	//静态类成员变量、
	static int _mA;//静态类成员变量,只是声明,静态类成员变量要在类内声明、
	//非静态类成员变量、
	int _mB;//非静态类成员变量,只是声明、

	//静态类成员函数也具有访问权限、
private:
	static void func2()
	{
		cout << "static void func2调用" << endl;
	}
};
//静态类成员变量要在类外定义和初始化、
int Person::_mA = 0; //静态类成员变量在类外定义和初始化,要指定类域、
void test01()
{
	//非静态类成员函数也属于类,而不属于对象,但是由于非静态类成员函数其实都内含了一个指向类对象的指针型参数(即this指针),所以即使它属于类
	//但是其访问形式也只有一种,即只有通过对象才能访问非静态类成员函数(此时this指针有实值),而不可以通过下述的方式2进行访问非静态类成员函数、

	//静态类成员函数不属于对象,而属于类,所以它有两种访问形式:
	//1、通过对象进行访问静态类成员函数:
	//Person p;
	//p.func(); //static void func调用
	//2、通过类名加作用域限定符的形式进行访问静态类成员函数,也可以不用创建对象再使用对象来进行访问,因为静态类成员函数本身就不属于对象,而属于类、
	Person::func(); //static void func调用

	1、
	//Person p;
	//cout << p._mA << endl;//100
	2、
	//cout<< Person::_mA << endl;//100

	1、
	//Person p1;
	//p1.func2(); //报错,在 类外 不可以访问私有的静态类成员函数、
	2、
	//Person::func2();//报错,在 类外 不可以访问私有的静态类成员函数、
}
int main()
{
	test01();
	return 0;
}
注意:在此不考虑C语言中,在结构体成员变量前面加关键字static的情况,所以,当说到静态成员时,就默认指的是C++中,类体里面的静态成
员变量和静态成员函数、

类的定义不能像下面这样进行定义,否则在实例化的时候会出现错误:

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

在实例化的过程中将会无穷无尽,这是被C++语法所不允许的,结论:在类的定义中,类成员变量不能使用当前类名作为其类型、

7、类对象模型:

7.1、如何计算类对象的大小:

class A 
{
public:
 void PrintA()
 {
     cout<<_a<<endl;
 }
private:
     char _a;
};
问题:类体中既可以有成员变量,又可以有成员函数,那么一个类的对象中包含了什么?如何计算一个类的对象所占内存空间的大小呢?

7.2、类对象的存储方式猜测:

class stu
{
public:
	void SetStuInfo()
	{

	}
	void PrintInfo()
	{

	}
//类的成员函数以其地址的形式进行存储、
private:
	char _name[10];
	int _age;
	char _sex[5];
};

一:对象中包含类的所有成员:

当计算由类实例化出来的对象所占内存空间的大小时,若对象中包含类的所有的成员,包括类的成员函数时,那么在计算时,就默认类的成员函

数以其地址(4/8byte)进行存储:

缺陷:每个对象中类的成员变量所存储的值是不同的,但是调用同一份类的成员函数,如果按照此种方式存储,当一个类创建多个对象时,每个

对象中都会保存一份代码(类成员函数的地址),相同代码(类成员函数的地址)保存多次,浪费空间,但这并不是说方式一不可以,它是可以的,但

不太好,那么如何解决呢?

二:对象中只保存类的成员变量,类的成员函数存放在公共的代码段:

对于上述两种方式,计算机采用的是第二种方式,所以当计算类实例化出来的对象所占内存空间大小的时候,由于对象中只保存了类的成员变量

(虚函数指针和虚基类指针也属于数据部分),而未保存类的成员函数,把类的成员函数存放在了公共的代码段上,类实例化出的所有对象都使

用同一份类成员函数,也就是说在内存中的公共代码区上只有一份拷贝,各个对象通过各自的this指针对他们进行访问,从而节约内存空间,所

以,计算类实例化出来的对象所占内存空间大小就等价于只计算类的成员变量所占内存空间的大小,忽略(不计算)类的成员函数所占内存空间的

大小,其计算方法和C语言中计算结构体变量所占内存大小是一样的,也遵循内存对齐的原则,所以,一个类对象中只包含了类的成员变量,并

没包含类的成员函数、

我们再通过对下面的不同对象分别获取所占内存空间大小来分析看下:
//类体中既有成员变量,又有成员函数、
class A1   //8byte
{
public:
 void f1(){}
private:
 int _a;
 char _ch;
};   
//类体中仅有成员函数、
class A2   //1byte
{
public:
 void f2() {}
};
//类中什么都没有---空类、
class A3    //1byte
{};
//sizeof(A2)和sizeof(A3)结果应该是一样的,两者都没有类的成员变量,按理说结果应该都是0byte,但是,当类体里面不存在类的成员变量时,系统会给它开辟1byte的内存空间,这是因为,若某个类型所占内存空间大小为0byte的话,由该类型定义的实例化出的对象(变量)的地址就是空地址,这样就没办法表示该实例化出的对象(变量)存在过,这1byte不是为了存储类成员变量的,而是为了表示实例化出的对象存在过,即,没有类成员变量的类对象,编译器会给他们分配1byte用来占位,表示实例化出的对象存在过,即使考虑上内存对齐,最后的结果也是1byte、

//类体中仅有类成员变量、
class A2   //1byte,这1byte就是用来存储类成员变量_ch的、
{
    char _ch;
};

7.3、内存对齐规则:

1、第一个成员在与结构体(或类体)偏移量为0的地址处、

2、其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处,
3、注意:对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值,VS中默认的对齐数为8、
4、结构体总大小为:最大对齐数(所有变量类型最大者与默认对齐参数取最小)的整数倍、
5、如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对
齐数)的整数倍、

8、this指针:

8.1、this指针的引出: 

我们先来定义一个类名为Date的日期类
Date、
#include <iostream>
using namespace std;
class Date
{
public:
	void Print()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}
	void Init(int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}
private:
	int _year; // 年
	int _month; // 月
	int _day; // 日
};
int main()
{
	Date d1, d2;
	//此处调用的类成员函数Init是同一份类成员函数(公共代码区),此处调用的类成员函数Print是同一份类成员函数(公共代码区)、
	d1.Init(2022, 5, 11);
	d2.Init(2022, 5, 12);
	d1.Print();  //2022-5-11
	d2.Print();  //2022-5-12
	return 0;
}

观察上述打印出来的结果,考虑,在此处,两次调用的类成员函数Print是同一份类成员函数(公共代码区),而观察类体内的类成员函数Print的声

明和定义可知,虽然通过对象d1和d2来调用该类成员函数Print,但是该类成员函数Print的函数体内并不能进行区分对象,那么该类成员函数Print

是怎么来打印出正确匹配的结果呢,即,当通过对象d1调用类成员函数Print时,该类成员函数Print是如何知道应该打印对象d1中的类成员变量,

而不是打印对象d2中的类成员变量的呢,同理,两次调用的类成员函数Init是同一份类成员函数(公共代码区),而观察类体内的类成员函数Init的

声明和定义可知,虽然通过对象d1和d2来调用该类成员函数Init,但是该类成员函数Init的函数体内也不能进行区分对象,(一般情况下,不管是普

通的函数还是静态或非静态类成员函数,其函数体内都不能进行对象的区分),那么该类成员函数Init是怎么来初始化出正确匹配的结果的呢,即

当通过对象d1来调用类成员函数Init时,该类成员函数Init是如何知道应该设置对象的d1中的类成员变量,而不是设置对象d2中的类成员变量的

呢,,此时,这两个类成员函数均不属于某一个对象,而是属于类,这是因为,每个非静态类成员函数内部均内含了一个this指针,上述代码中

的两个类成员函数会被编译器进行处理,如下所示:

C++中通过引入this指针解决该问题,即:C++编译器给每个"非静态类成员函数"增加了一个隐藏的this指针参数,让该指针指向当前对象(该非静

态类成员函数运行时调用该非静态类成员函数的对象),在非静态类成员函数的函数体中对所有的类成员变量的操作,都是通过该指针去访问的,

只不过所有的操作对用户是透明的,即,用户不需要来传递,编译器自动完成、

//隐含的this指针、
//每一个非静态类成员函数中都内含了一个this指针,但是每一个非静态类成员函数的形参和实参部分都不能 显式 的写出来有关this指针的内容
//因为这是 隐含的 this指针,是编译器做的活,但是在把非静态类成员函数的声明和定义(不分开写)都放在类体里面的前提下,是可以且只能在该非静态类成员函数的函数体内显式的使用this指针变量的、
#include <iostream>
using namespace std;
class Date
{
public:
	//const放在了this的左边,修饰的是this指针变量,其本身不能被修改,但是this指针变量所指的内容是可以被改变的、
	void Print(Date* const this)  //该行代码不可以这样写,即,非静态类成员函数的形参部分不能显式的写出有关this指针的内容,this是一个形参指针变量,也是一个新增的关键字、
	{
		cout << this << endl;//该行代码可这样写,不会报错,因为在满足上述的前提下,可以且只能在该非静态类成员函数的函数体内显式的使用this指针变量的、
		//所有访问类成员变量的地方,前面都会加上一个 this-> 、
		cout << this->_year << "-" << this->_month << "-" << this->_day << endl;  //该行代码可这样写,不会报错,原因同上,若自己不加上this->的话,编译器也会自己加上,所以一般情况下不需要手动加上、
		//此处访问的是某个对象的_year,_month,_day,并不是类体中的,具体是哪个对象,就看实参传的是哪个对象的地址,这是因为类体中的类成员变量只是声明,不是定义、
	}
	//const放在了this的左边,修饰的是this指针变量,其本身不能进行改变,但是this指针变量所指的内容是可以被改变的、
	void Init(Date* const this, int year, int month, int day) //该行代码不可以这样写,原因同上,Date* const this要放在第一个形参位置上、
	{
		//this = nullptr; //this指针本身不能被修改、
		cout << this << endl;//该行代码可这样写,不会报错,原因同上、
		//所有访问类成员变量的地方,前面都会加上一个 this-> 、

		//this指针所指的对象是可以被修改的、
		this->_year = year;   //该行代码可这样写,不会报错,原因同上,若自己不加上this->的话,编译器也会自己加上,所以一般情况下不需要手动加上、
		this->_month = month; //该行代码可这样写,不会报错,原因同上,若自己不加上this->的话,编译器也会自己加上,所以一般情况下不需要手动加上、
		this->_day = day;	  //该行代码可这样写,不会报错,原因同上,若自己不加上this->的话,编译器也会自己加上,所以一般情况下不需要手动加上、
		//此处访问的是某个对象的_year,_month,_day,并不是类体中的,具体是哪个对象,就看实参传的是哪个对象的地址,这是因为类体中的类成员变量只是声明,不是定义、
	}
private:
	int _year; // 年
	int _month; // 月
	int _day; // 日
};
int main()
{
	Date d1, d2;
	//此处调用的类成员函数Init是同一份类成员函数(公共代码区),此处调用的类成员函数Print是同一份类成员函数(公共代码区)、
	d1.Init(&d1, 2022, 5, 11); //&d1要放在实参的第一个位置上,该行代码不可以这样写,原因同上、
	d2.Init(&d2, 2022, 5, 12); //&d2要放在实参的第一个位置上,该行代码不可以这样写,原因同上、
	d1.Print(&d1);  //2022-5-11  ,该行代码不可以这样写,原因同上、
	d2.Print(&d2);  //2022-5-12  ,该行代码不可以这样写,原因同上、
	return 0;
}

8.2、this指针的特性: 

1、this指针的类型:类类型* const 、
2、
只能
在"非静态类成员函数"的函数体内部显式的使用this指针变量,前提必须把非静态类成员函数的声明和定义(不分开写)都放在类体里面,
由于静态类成员函数和普通全局函数等其他类型的函数内部都没有隐藏的this指针,所以在普通全局函数和静态类成员函数等其他类型的函数的
函数体内部并不能使用显示的this指针、
3、this指针本质上其实是一个非静态类成员函数的形参,是对象调用非静态类成员函数时,将对象地址作为实参传递给this形参,所以对象中不
存储this指针,在计算对象所占内存空间的大小时,并没有把this指针所占的内存空间算入对象所占内存空间中,也能表明对象中不存储this指
针、
4、this指针是非静态类成员函数第一个隐含的指针形参,一般情况由编译器通过ecx寄存器自动传递,不需要用户传递,如果用户强制传递,那
么编译器会报错、
5、形参指针变量this是编译器默认加上的,无论非静态类成员函数的函数体内是否用得到,编译器都会默认加上,即使该非静态类成员函数的函
数体内为空,编译器也会默认加上、
6、this指针变量在非静态类成员函数开始执行前构造,在非静态类成员函数执行结束后清除、
7、const修饰this指针变量,所以this指针变量本身并不能被修改,故为常量(常属性),若在非静态类成员函数的函数体内进行显式的 &this 操作
是不对的,因为,不能对常量(常属性)进行取地址操作、
8、this指针变量 只有 在非静态类成员函数的函数体中才有定义,且存储位置会因编译器不同有不同存储位置、

8.3、关于this指针常见的面试题: 

1、问:this指针是存在哪的?

答:一般情况下(大部分编译器下)是存在于栈区上的,因为this指针是形参,当然,有的编译器会使用寄存器进行优化(比如VS编译器就会使用寄存器进行优化,并且在VS编译器下,在调用函数建立栈帧之前先要进行压参数,该编译器是从右往左压参数的,其他编译器并不一定是从右往左,并没有进行明确的规定)(为什么进行优化?因为我们可能在非静态类成员函数中频繁的使用this指针,若存在栈区上,即存在内存中,其在内存中的读取速度比较慢,频繁的使用this指针就会导致整体效率较低,所以要使用寄存器进行优化,是因为寄存器的读取速度更快,具体在三级缓存中有讲解,所以当频繁使用this指针时,进行寄存器优化会提高效率),将this指针存放在ecx寄存器上,也就是说,this指针在某些编译器下可能会存在于ecx寄存器中,比如VS编译器、

2、问:this指针可以为空吗?

答:可以,因为无论是传递什么,传递过去的都是一个值,空指针也只是一个值,如何理解空指针? 以32位为例,在进程地址空间中,空指针是一个存在的地址,若是32位的话,内存区域总共占4G,是规定的,也和指针的大小具有关联,从0x00000000到0xFFFFFFFF,总共能够存储2^32次方个地址,每个地址存储单位是byte,则总共能够存储2^32byte个地址,即有这些个编号,1个byte等于8bit,所谓,指针就是地址,地址就是指针,地址是一个编号,所以指针也是编号,所以空指针即为0x00000000,第0byte的编号,所以空指针是一个存在且有效的地址,而对空指针进行解引用操作报错的原因是因为:空指针所在的位置是预留出来的,不存储任何数据,不能进行初始化,所以不能够去空指针所指向的内存空间中访问数据,因为他其中是不存储任何数据的,系统会对这一操作进行检查, 若检查到对空指针进行解引用操作,即访问空指针所指的内存空间中的数据时会报错,这一报错是检查规定的行为,内核是存在于高地址上的,还要知道,虚拟内存不是电脑上的物理内存,虚拟内存和物理内存之间还需要通过页表去进行映射,空指针一定是0X00000000,但不是物理内存中的0处的地址,而是虚拟内存中0处的地址,物理内存可以给任何程序进行映射,不需要再进行划分具体的区域,每个程序中都有物理内存,虚拟内存是每个进程地址空间中都有的,进程地址空间也可以成为虚拟进程地址空间、

例题1、
//1.下面程序编译运行结果是?A、编译报错 B、运行崩溃 C、正常运行
#include <iostream>
using namespace std;
class A
{
public:
	//编译器处理为: void Show(A * const this)
	void Show()//传过来的是一个空指针、
	{
		//cout << this << endl;
		cout << "Show()" << endl;
	}
private:
	int _a;
//public:
//	int _a;
};
int main()
{
    A*p=(A*)0x11223344;//程序也能正常运行,打印出Show() 、
	A* p = nullptr;//在非静态类成员函数Show调用的过程中并没有对指针变量p,即空指针指向的那块空间进行访问,而是去公共代码区中查找该非静态类成员函数Show的地址,所以不会出现对空指针解引用这样的运行时错误,即运行崩溃,并且,不管指针变量p中存储的值是什么,程序都能正常运行,就是因为,会直接去公共代码区查找,并不会去指针变量p所指的内存空间中进行访问,即所谓的不进行解引用操作,即使指针变量p中存储的值是空指针也是可以的,压根就不会去指针变量p所指的内存空间中进行访问,当然就不会出现运行崩溃的错误、
	p->Show();//编译器处理为: p->Show(p);  此处的两个 -> 均不是解引用操作,和普通函数的调用是一样的,去公共代码区里查找该非静态类成员函数Show的地址,变成call地址进行调用、
	//p->_a = 10; //错误写法,此时 -> 为解引用操作,不可对空指针nullptr进行解引用操作,会报运行时错误、
}
//要知道,访问操作符 .和-> 首次出现在C语言中的结构体章节中,而在C语言中的结构体的定义里面,只能进行结构体成员变量的声明,所以通过结构体类型定义出来的结构体变量
//中把结构体的定义里面的东西全部包含了,所以在C语言中的访问操作符 .和-> 代表的意思均为解引用,故,若在C语言中使用到访问操作符 .和-> 的话,一律把他们看成解引用操作
//而在C++中,已经不存在结构体的概念了,而是把结构体升级成了类,而在类的定义中(类体中),不仅仅能声明类成员变量(静态和非静态),还能声明和定义类成员函数(静态和非静态),而通过类名实例化出的对象中只包含了
//类成员变量(静态和非静态),此时,访问操作符 .和-> 并不全是代表解引用操作,若访问操作符访问的内容是类成员变量(静态和非静态),则访问操作符代表的是解引用操作,这是因为,对象只包含了类成员变量(静态和非静态),若访问的
//内容是类成员函数(静态和非静态)而不是类成员变量(静态和非静态)的话,那么访问操作符代表的就不再是解引用操作了,这是因为,对象中并没有包含类成员函数(静态和非静态),所以若在C++中使用到了访问操作符 .和-> 的话
//首先要观察访问操作符访问的内容到底是类成员变量(静态和非静态)还是类成员函数(静态和非静态),访问操作符访问的内容若包含在了对象中,即访问的内容是类成员变量(静态和非静态)的话,那么访问操作符代表的意思就是解引用,
//访问操作符访问的内容若不包含在了对象中,即访问的内容是类成员函数(静态和非静态)的话,那么访问操作符代表的意思就不是解引用操作、

//此时,指针变量p中存储的是空指针nullptr,当进行 p->Show(); 时,看似好像在
//进行解引用操作,但是由于指针变量p中存储的是空指针nullptr,所以对空指针nullptr进行解引用操作是不对的,会报错,但是这种错误不会是编译错误,即,
//对空指针nullptr进行解引用或者是对野指针进行解引用操作的话,都属于运行时错误,所以一定不会选择A,而是从B和C中选取,而该题的正确答案应该是C,正常运行
//原因是因为,对象里面只包含类体中的类成员变量(静态和非静态),而不包含类成员函数(静态和非静态),所以,题目中的 -> 并不是在进行解引用操作,并且,每个非静态类成员函数中都内含了一个
//this指针,所以在进行p->Show();时,其实参部分是把指针变量p传参过去,即把一个空指针nullptr传参了过去,这里虽然const修饰的是this指针变量本身,其本身
//不能被修改,但是空指针nullptr是实参传参过去的,并不受const的控制,也就是所谓的,this指针可以为空指针nullptr,即,const修饰的指针变量this本身不能被修改
//但是可以被初始化,而对象中包含类成员变量(静态和非静态),所以若进行 p->_a=10; 的话,-> 才起到解引用的作用,即对空指针进行了解引用,所以会出现运行时错误,

例题2、
#include <iostream>
using namespace std;
//2.下面程序编译运行结果是?A、编译报错 B、运行崩溃 C、正常运行
class A
{
public:
	void PrintA()
	{
		cout << _a << endl;
	}
	编译器处理为:
	//void PrintA(A* const this)
	//{
	//	cout << this->_a << endl;//此处进行操作this->_a,由于对象中包含了类成员变量(静态和非静态),所以此处的 -> 为解引用操作,而形参指针变量this接收到的
	//	//是空指针nullptr,故此处对空指针进行了解引用操作,所以会报运行时错误,相当于访问this指针变量所指向的空间中的内容,所以程序会运行崩溃、
	//}
private:
	int _a;
};
int main()
{
	A* p = nullptr;
	p->PrintA();
	//编译器处理为: p->PrintA(p);  这里不会出现问题,只是将指针变量p中的空指针nullptr当做实参传给形参this指针变量、
	//此处的 -> 不是解引用操作,和普通函数的调用是一样的,去公共代码区里查找该非静态类成员函数PrintA的地址,变成call地址进行调用、
}

注意:之前所谓的,在类外不能访问类体中私有或保护的内容,其实并不是不能访问,而是不能直接(目前所学的,通过类名加作用域限定符或者

通过对象的形式进行的访问称为直接访问)访问,可以通过间接的方法去进行访问,具体等后期再进行阐述、 

本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
THE END
分享
二维码
< <上一篇
下一篇>>