类的作用域及计算

时间:2024-04-06 09:12:13

目录

一:类的作用域

二:类的实例化

三:类的对象模型 

3.1如何计算类对象的大小

7.2类对象的存储方式猜测

3.3结构体内存对其规则

四:this指针

8.1this指针的引出

8.2this指针的特性

4.3C语言和C++实现Stack的对比


一:类的作用域

类定义了一个域,类的所有成员都在类的作用域中。在类外定义变量成员时,需要使用::作用域限定符指明成员属于哪个域

class Person
{
public:
	void PrintPersonInfo();

private:
	char _name[20];
	char _gender[10];
	int _age;

};

//这里指定PrintPersonInfo是属于Person这个域的
void Person::PrintPersonInfo()
{
	cout << "_name" << " " << "_gender" << " " << " age" << endl;

}

二:类的实例化

用类类型创建对象的过程,称为类的实例化

1.类是对对象进行描述的,是一个模型一样的东西,限定了类有哪些成员,定义出一个类并没有分配实际的内存空间来储存它;比如:入学时填写的学生信息表,表格就可以看作是一个类,来描述具体的学生信息

类就像是一个迷题,对谜底进行描述,谜底就是谜语的一个实例

2.一个类可以实例化出多个变量,实例化出的对象,占用的实际的物理空间,存储类的成员变量

int main()
{
	Person._age = 100;//编译失败:error 语法错误

	return 0;
}

Person类是没有空间的,只有Person类的实例化出的对象才有具体的年龄

3.做个比方,类实例化出对象就像现实中使用建筑设计图创建出房子,类就像设计图,只设计需要什么东西,实际上并没有实体的建筑存在,同样类也是一个设计,实例化出对象才能实际存储东西,占物理空间

 


三:类的对象模型 

3.1如何计算类对象的大小

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

private:
	char _a;
};

问题::类中既可以有成员变量,也可以有成员函数,那么一个类的对象中包含了什么?如何计算类的大小?

7.2类对象的存储方式猜测

成员中包含类的各个成员

缺陷:每个对象中的成员变量是不同的,但是调用的函数是相同的,如果按照这种此种方式存储,当一个类创建多个对象时,每个对象中都会保存一份代码,相同的代码保存多次,浪费空间,那么如何解决呢? 

代码只保存一份,在对象中保存代码的地址

只保存成员变量,成员函数放在公共的代码段 

问题:对于上述三种存储方式,那么计算机到底是按照那种方式来存储的?

 我们可以通过下面的代码分析得出结论:

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

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

};

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

};

首先对于A1,来说:

通过求A1的字节大小,可以得出答案,应该是第三种方案

那么空类的字节大小是多少?是0吗?

 为什么空类没有成员变量,字节大小是一呢?

前面实例化时有讲到,对于函数变量的定义是开辟了空间给变量,如果空类的字节大小是0,那么下面的情况如何解释?

如果字节大小是零,变量的定义好的条件又是开辟空间,不就自相矛盾了吗?

所以空类的大小是1,这个1不存储有效数据,只是标识变量已经开辟出来了

 结论:

一个类的大小,实际上就是该类中“成员变量之和”,当然要注意内存对齐

注意空类的大小,空类比较特殊,编译器给了空类一个字节来唯一标识这个类的对象

3.3结构体内存对其规则

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

四:this指针

8.1this指针的引出

我们先定义一个日期类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(2024, 4, 5);
	d1.Print();

	d2.Init(2023, 4, 4);
	d2.Print();

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

译器自动完成。

8.2this指针的特性

1. this 指针的类型:类类型 * const ,即成员函数中,不能给 this 指针赋值,但是this指针指向的内容可以修改
2. 只能在 成员函数 的内部使用
3. this 指针本质上是 成员函数 的形参 ,当对象调用成员函数时,将对象地址作为实参传递给
this 形参。所以 对象中不存储 this 指针
4. this 指针是 成员函数 第一个隐含的指针形参,一般情况由编译器通过 ecx 寄存器自动传
递,不需要用户传递
5.this指针在实参,形参中不可以显示,编译器自己加,但是在类中可以用

this指针存在哪里?

 this指针作为形参存放在栈中,但是由于this指针频繁使用,优点编译器会进行优化,存放在寄存器中

4.3C语言和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) * 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]
};
可以看到,在用 C 语言实现时, Stack 相关操作函数有以下共性:
每个函数的第一个参数都是 Stack*
函数中必须要对第一个参数检测,因为该参数可能会为 NULL
函数中都是通过 Stack* 参数操作栈的
调用时必须传递 Stack 结构体变量的地址
结构体中只能定义存放数据的结构,操作数据的方法不能放在结构体中,即 数据和操作数据
的方式是分离开的 ,而且实现上相当复杂一点,涉及到大量指针操作,稍不注意可能就会出
错。
2.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 语言中需用用户自己维护