NzN的C++之路--类和对象入门

        在我们掌握了C++最入门的一些概念后,下面就要面对C++入门之后的第一座大山--类和对象。由于类和对象一章内容较多,小编就拆成多篇博客,方便大家学习。给小编个三连鼓励一下,下面我们就正式开始学习类和对象的内容。

目录

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

二、类的引入

三、类的定义

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

2. 声明和定义分离

3. 成员变量命名规则的建议

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

1. 访问限定符

2. 封装

五、类的作用域

六、类的实例化

七、类对象模型

1. 如何计算类对象的大小

2. 结构体内存对齐

3. 类对象的存储方式猜测


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

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

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

二、类的引入

        C语言结构体中只能定义变量,在C++中把struct升级成了类,类名就可以代表类型,因此结构体内不仅可以定义变量,也可以定义函数,并且一个类可以实例化出多个对象。 

struct Stack
{
	//成员函数
	//不需要起StackInit这种名字了,放在类里很明显就是对这个类进行操作
	//并且不需要传结构体指针,因为这个函数就在类中可以直接访问
	void Init(int n = 4)
	{
		array = (int*)malloc(sizeof(int) * n);
		if (nullptr == array)
		{
			perror("malloc fail");
			return;
		}
		capacity = n;
		top = 0;
	}
	//成员变量
	int* array;
	size_t capacity;
	size_t top;
};
//C语言定义节点,在struct里面必须是全称
typedef struct ListNodeC
{
	int val;
	struct ListNodeC* next;
}LTNode;

//C++定义节点,类名就能代表类型,即ListNodeCPP*就代表struct ListNodeCPP*
struct ListNodeCPP
{
	int val;
	ListNodeCPP* next;
};

        C++兼容C,所以上面的写法是正确的,但是在C++中,我们更习惯用class定义类。

三、类的定义

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

         C++中的类是C语言中结构体的plus版本,因此类的定义和结构体的定义方式非常相似。

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

        类体中内容称为类的成员:类中的变量称为类的属性或成员变量; 类中的函数称为类的方法(成员函数)。

        类共用两种定义方式。

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

class Stack
{
public:
	//成员函数
	void Init(int n = 4)
	{
		array = (int*)malloc(sizeof(int) * n);
		if (array == nullptr)
		{
			perror("malloc fail");
			return;
		}

		capacity = n;
		top = 0;
	}
	void Push(int x)
	{
		//扩容...
		array[top++] = x;
	}
	int Top()
	{
		assert(top > 0);
		return array[top - 1];
	}
private:
	// 成员变量
	int* array;
	size_t capacity;
	size_t top;
};

        上面代码中的private和public我们会在下面进行讲解,这里只是向大家简单介绍一下类的定义方式。

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

2. 声明和定义分离

注意:成员函数名前需要加类名::来进行指定。

        一般情况下,我们习惯采用第二种方式定义类。

3. 成员变量命名规则的建议

class Date
{
public:
	void Init(int year, int month, int day)
	{
		year = year;
		month = month;
		day = day;
	}
	//这里不会编译出错,但实际上并没有初始化成功
	//因为局部变量优先,这种写法很难区分函数形参和成员变量
private:
	int year;
	int month;
	int day;
};

        在C++中,为了方便区分函数形参和成员变量,我们习惯在成员变量前加一个下划线。

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

         这种写法并不固定,只要能区分函数形参和成员变量,并保持代码良好的可读性即可。

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

1. 访问限定符

        C++实现封装的方式:用类将对象的属性与方法结合在一块,让对象更加完善,通过访问权限选择性的将其接口提供给外部的用户使用

【说明】

  • public修饰的成员在类外可以直接访问
  • protected/private修饰的成员在类外不能直接访问
  • 访问权限作用域从该访问限定符出现的位置开始直到下一个访问限定符出现时为止
  • 如果后面没有访问限定符,作用域就到 } 即类结束。
  • class的默认访问权限为private,struct为public(使用class里的函数需要加上public)

注意:访问限定符只在编译时有用,当数据映射到内存后,没有任何访问限定符上的区别。

2. 封装

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

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

        封装:将数据和操作数据的方法进行有机结合,隐藏对象的属性和实现细节,仅对外公开接口来和对象进行交互

        封装本质上是一种管理,让用户更方便使用类。比如:对于电脑这样一个复杂的设备,提供给用户的就只有开关机键、通过键盘输入,显示器,USB插孔等,让用户和计算机进行交互,完成日常事务。但实际上电脑真正工作的是CPU、显卡、内存等一些硬件元件。

        对于计算机使用者而言,不用关心内部核心部件,比如主板上线路是如何布局的,CPU内部是如何设计的等。用户只需要知道怎么开机、怎么通过键盘和鼠标与计算机进行交互即可。因此计算机厂商在出厂时,在外部套上壳子,将内部实现细节隐藏起来,仅仅对外提供开关机、鼠标以及键盘插孔等,让用户可以与计算机进行交互即可。

        在C++语言中实现封装,可以通过类将数据以及操作数据的方法进行有机结合,通过访问权限来隐藏对象内部实现细节,控制哪些方法可以在类外部直接被使用

class Stack
{
public:
	//成员函数
	void Init(int n = 4)
	{
		array = (int*)malloc(sizeof(int) * n);
		if (array == nullptr)
		{
			perror("malloc fail");
			return;
		}

		capacity = n;
		top = 0;
	}
	void Push(int x)
	{
		//扩容...
		array[top++] = x;
	}
	int Top()
	{
		assert(top > 0);
		return array[top - 1];
	}
private:
	// 成员变量
	int* array;
	size_t capacity;
	size_t top;
};

//封装
int main()
{
	Stack st2;
	st2.Init();
	st2.Push(1);
	st2.Push(2);
	//cout << st2.array[st2.top] << endl;
	//初始化时top=0或者-1,输出的就是两种不同的结果
	//因此C++中就会把这些成员变量设置成私有,不允许随便访问,这就是封装
	cout << st2.Top() << endl;
	//只能使用public的方法
	return 0;
}

五、类的作用域

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

//Stack.h
class Stack
{
public:
	void Init();//定义和声明分离
private:
	int* _a;
	int _top;
	int _capacity;
};

//Stack.cpp
//定义和声明分离时,在外部指定类域
void Stack::Init()
{
	_a = nullptr;
	_top = 0;
	_capacity = 0;
}

六、类的实例化

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

        类是对对象进行描述的,像模型一样,限定了类有哪些成员,定义一个类,并没有分配实际的内存空间来存储它,只是单纯的声明。

//类定义了一个新的作用域
class Stack
{
public:
	void Init();//定义和声明分离
private:
	//成员变量这里仅仅是声明
	int* _a;
	int _top;
	int _capacity;
};

        一个类可以实例化出多个对象,实例化出的对象才会占用实际的物理空间。

int main()
{
	Stack st1;//类的实例化--对象
}

七、类对象模型

1. 如何计算类对象的大小

class Stack
{
public:
	void Init();//定义和声明分离
private:
	int* _a;
	int _top;
	int _capacity;
};

        类中既可以有成员变量,又可以有成员函数,那么一个类的对象中包含了什么?我们又该如何计算一个类的大小?

2. 结构体内存对齐

        我们前面提过:C++的类是在C语言的结构体之上升级的,因此类也是遵循内存对齐的原则,那我们先来回顾一下结构体的内存对齐规则。

  • 第一个成员在与结构体偏移量为0的地址处。
  • 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
  • 对齐数 = 编译器默认的一个对齐数与该成员大小的较小值。(VS默认对齐数为8)
  • 结构体总大小为=最大对齐数的整数倍
  • 如果嵌套了结构体,嵌套的结构体对齐到自己最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。

3. 类对象的存储方式猜测

【对象中包含类的各个成员】

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

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

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

        对于上述三种存储方式,计算机到底是按照第三种方式来存储的。每个实例化的对象有自己的成员变量,但是他们调用的函数地址是相同的,即计算机会在公共代码区存一份函数,对象在使用函数时,直接到公共代码区找就可以。

注意:这里所涉及到的公共代码区以及自己的成员变量,跟public/private无关。

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

class Stack
{
public:
	void Init();//定义和声明分离
private:
	int* _a;
	int _top;
	int _capacity;
};

        因此上面这个Stack类的大小就是12。

        那我再问大家两个问题:如果这个类里没有成员变量,只用成员方法,那这个类的大小是几个字节?如果我这个类里面什么都没有,那这个类的大小又是几?难不成两个都是0?

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

};
//类中什么都没有---空类
class A2
{};
int main()
{
	cout << sizeof(A1) << endl;
	cout << sizeof(A2) << endl;
}

         大家可以自己到编译器上运行一下这段代码,我们发现结果两个都是1。为什么呢?

        这个问题其实很简单,类中没有任何成员变量,占用的存储大小本该为0,但是如果是0,类实例化出的对象就不会在内存上占用空间,没有地址,也就无法区分这些对象。为了解决这个问题,会给空类隐含加1个字节,保证用此类定义的对象都有一个独一无二的地址。 

注意:空类的大小是1个字节用来唯一标识这个类的对象。如果类中只有成员函数,则类的大小也是1个字节。

        那以上就是类和对象part1的全部内容了,更多内容给个三连继续追吧!!!

  • 49
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值