您现在的位置是:首页 >技术交流 >C++ ---- 类和对象(上)网站首页技术交流

C++ ---- 类和对象(上)

Bug程序员小张 2023-05-08 22:30:03
简介C++ ---- 类和对象(上)

 

目录

本节目标

常见问题

面向过程和面向对象的理解

什么是类+如何定义类

类的引入

类的定义

类的两种定义习惯

类的作用域

类的访问限定符

访问限定符介绍

封装

封装的意义

类的实例化

 类对象模型

类对象的存储方式

结构体对齐

计算类对象的大小

this指针

 问题解答


本节目标

对面向对象有初步认识,能够正确的定义和使用类,了解类对象的存储方式,能够计算类的大小。清楚类该如何实例化,类的作用域等。对封装的概念、访问限定符,this指针有初步了解。

常见问题

1.this指针存在哪里?

2.this指针可以为空吗?

3.一个类的对象中包含了什么?

4.如何计算一个类的大小。

5.一个空类的大小?

6.什么是封装?

7.C++中struct和class的区别是什么?

答案在文章最后!!!

面向过程和面向对象的理解

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

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

什么是类+如何定义类

类的引入

在C语言中,通常用结构体描述一些值的集合,这些值称为成员变量。在C++中,结构体内不仅可以定义变量,还可以定义函数。除了struct zxy{};的定义方式,C++中引入了class关键字专门用来定义类,class zxy{}; 两种方式都是可以的,区别后面在讨论。

类的定义

struct和class为定义类的关键字,关键字后面接类名,{}为类的主体,类定义结束后的分号不能省略。类体中的内容包括成员变量(类的属性)和成员函数

struct

struct person1
{
	//成员函数
	//....
	//成员变量
	//....
};

class

class person2
{
	//成员函数
	//....
	//成员变量
	//.... 
};

类的两种定义习惯

声明和定义全部放在类体中

class Date
{
public:
	//函数声明和定义全部放在类体中
	void Fun()
	{
		//.....
	}
private:
	int _year;
	int _month;
	int _day;
};

类声明放在.h文件中,成员函数定义放在.cpp中。这种写法成员函数名前需要加类名::

.h中只有函数的声明

class Date
{
public:
	//函数声明和定义全部放在类体中
	void Fun1();
	void Fun2();
	void Fun3();

private:
	int _year;
	int _month;
	int _day;
};

.cpp中写函数的定义

void Date::Fun1()
{
	//...
}
void Date::Fun2()
{
	//...
}
void Date::Fun3()
{
	//...
}

类的作用域

类定义了一个作用域,类中的成员都在类的作用域中。在类外定义成员时,要通过 类名::的方式指明类域。

class Date
{
public:

	void Fun1();
private:
	int _year;
	int _month;
	int _day;
};
void Date::Fun1()
{
	//...
}

类的访问限定符

在C++中,用类将对象的属性与方法结合在一块,让对象更加完善。通过访问限定符的控制,可以有选择性的将内部的部分接口提供给外部使用。这也体现了封装的特性。

访问限定符介绍

1.public修饰的成员在类外可以直接被访问

2.private和protected修饰的成员在类外不能直接被访问。

3.访问限定符的作用域是从该访问限定符出现的位置直到下一个访问限定符出现为止。

4.如果当前访问限定符的后面不在有其它访问限定符,作用域直到类结束。

5.class的默认访问权限为private,struct的默认访问权限是public。

封装

封装本质上是一种管理,让用户更方便使用类。将数据和操作数据的方法结合在一起,隐藏对象的属性和实现细节,仅对外提供接口来和对象进行交互。

 对于汽车这样的复杂的对象来说,提供给用户的接口只有方向盘、离合、刹车、油门等,让驾驶员和汽车进行交互,完成驾驶任务。但实际上汽车内部真正工作的却是发动机,驱动电机等等被封装起来的内部核心部件。在C++中实现封装,将数据和方法封装起来,通过访问权限来隐藏对象内部的实现细节,控制那些方法可以在类外部直接被使用。

封装的意义

在C语言中定义的结构体中,成员变量都是暴露出来的,在外部可以任意访问,方法暴露在外部。

struct Date
{
	int _year = 2001;
	int _month = 10;
	int _day = 1;
};

void _Show(Date* date)
{
	printf("%d年%d月%d日
", date->_year, date->_month, date->_day);
}

int main()
{
	Date da1;
	da1._day = 35;
	_Show(&da1);
	return 0;
}

结构体中的成员可以随意的访问,这就存在着一些风险。封装的价值就是将上述的成员和函数围在一个“墙”中,只把部分接口暴露出来。

class Date
{
public:
	void init(int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	void _Show()
	{
		printf("%d年%d月%d日
", _year, _month, _day);
	}
private:
	int _year = 2001;
	int _month = 10;
	int _day = 1;
};



int main()
{
	Date da1;
	da1.init(2001,10,3);
	da1._Show();
	return 0;
}

类的实例化

1.用类类型创建对象的过程,叫做类的实例化。

2.类是对对象进行描述的,类似于建筑图纸,并没有分配实际的空间。

class Date
{
public:
	void _Show()
	{
		printf("%d年%d月%d日
", _year, _month, _day);
	}

	int _year = 2001;
	int _month = 10;
	int _day = 1;
};

int main()
{
	Date::_year = 1;
	Date::_Show();
	return 0;
}

在上述代码中,即使将类成员变量和属性设置为共有,通过类域仍然是无法访问和调用函数的,因为上述定义的类,就好像是房子的建造图纸,并没有实际的空间,你无法在图纸中进行午睡!

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

class Date
{
public:
	void init(int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	void _Show()
	{
		printf("%d年%d月%d日
", _year, _month, _day);
	}

	int _year = 2001;
	int _month = 10;
	int _day = 1;
};

int main()
{
	Date d1;
	d1.init(2001, 10, 24);
	d1._Show();
	
	Date d2;
	d2.init(2020, 5, 7);
	d2._Show();
	return 0;
}

 类对象模型

类对象的存储方式

每个对象中的变量是不同,就像下图中的对象属性,每个对象都有各自的姓名,性别和学号,但是调用的函数是同一份,所以类对象的存储方式采用下图的结构,每个对象都有独自的成员变量,但是共享一份函数,这样一来就节省了空间。

如上图所示,只保存成员变量,成员函数存在公共的代码段。

结构体对齐

1. 第一个成员在与结构体偏移量为0的地址处。

2. 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。注意:对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值。VS中默认的对齐数为8

3. 结构体总大小为:最大对齐数(所有变量类型最大者与默认对齐参数取最小)的整数倍。

4. 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。

5.修改默认对齐数用#pragma(x),#pragma()恢复默认对齐数。

计算类对象的大小

类对象大小的计算,只考虑成员变量,不考虑函数。计算规则和计算结构体的规则一致,要考虑内存对齐。

class A
{
private:
	int _a;
	char _ch;
	int _b;
};

int main()
{
	int ret = sizeof(A);
	cout << ret << endl;
	return 0;
}

 

需要注意的是,当计算的是一个空类时,空类的大小是1,编译器给了空类一个字节来标识这个类的对象。

    class Date
	{
        void init(int year, int month, int day)
		{
			//...
		}
	};
	
	
	int main()
	{
		int ret = sizeof(Date);
		cout << ret << endl;
		return 0;
	}

this指针

1.this指针的类型是 类类型 *const,在成员函数中不能改变this指针的指向。

2.this指针只在“成员函数的内部使用”。

3.this指针本质上是“成员函数”的形参,当类对象调用成员函数时,将对象地址作为实参传递给形参(类类型* const ),所以this指针是存储在栈帧中的

4.this指针是“成员函数”第一个隐含的指针形参,一般由编译器通过ecx寄存器自动传递,不需要用户显示传递。

5.this指针可以为空,可以访问成员函数,成员函数是在公共代码区的,但是如果访问了成员变量就会运行崩溃。

当this指针为nullptr时,如果只访问了成员函数,是不会报错的。

class Date
{
public:
	void Fun1()
	{
		cout << "函数调用" << endl;
	}
private:
	int _year = 2001;
	int _month = 10;
	int _day = 1;
};

int main()
{
	Date* da = nullptr;
	da->Fun1();
	return 0;
}

this指针为空且访问了成员变量,程序就会崩溃。

class Date
{
public:
	void Fun1()
	{
		cout << _year<< _month<<_day<<endl;
	}
private:
	int _year = 2001;
	int _month = 10;
	int _day = 1;
};

int main()
{
	Date* da = nullptr;
	da->Fun1();
	return 0;
}

 问题解答

1.this指针是作为参数传递的,存在栈帧中。this指针是“成员函数”第一个隐含的指针形参,一般由编译器通过ecx寄存器自动传递,不需要用户显示传递。

2.this指针可以为空,可以调用成员函数,但是如果涉及到成员变量,就会崩溃,因为发生了空指针的解引用。

3.类中包含成员变量和成员函数,通过访问限定符的控制有选择性的向外部提供接口。

4.类的大小计算只关注成员变量即可,计算规则和遵循内存对齐的规则。

5.空的大小为1,编译器给了空类一个字节来标识这个类的对象存在。

6.封装的本质是一种管理,让用户更方便使用类。将数据和操作数据的方法结合在一起,隐藏对象的属性和实现细节,仅对外提供接口来和对象进行交互。

7.C++需要兼容C语言,在C++中struct仍然可以当结构体使用。struct和class都可以用来定义类,struct定义的类默认权限是public的,class定义的类默认权限是private的。

风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。