类与对象【上】

在这里插入图片描述

欢迎来到Cefler的博客😁
🕌博客主页:那个传说中的man的主页
🏠个人专栏:题目解析
🌎推荐文章:题目大解析2

在这里插入图片描述


👉🏻面向过程和面向对象

我们用洗衣机洗个衣服,存在着3个对象:人、衣服、洗衣机。
在这里插入图片描述

在c语言当中,我们会注重过程,人是怎么将衣服放入洗衣机的,洗衣机的内部是如何操作运行的。
C++是基于面向对象的,关注的是对象

👉🏻类的引入

在c语言中,结构体中只能定义变量,但是在c++中,结构体不但可以定义变量,还可以定义函数

struct Stack
{
	void Init()
	{
		a = NULL;
		top = capacity = 0;
	}
	int* a;
	int top;
	int capacity;
};
int main()
{
	Stack s1;//c++中可以忽略struct
	s1.Init();

	return 0;
}

👉🏻类的定义

语法

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

类中的变量叫做类的属性或成员变量;类中的函数叫做类的方法或成员函数;类中的内容称为类的成员

类的两种定义

☂️1.类的声明和定义全部都在类体中进行
若函数的定义也在类体中进行,编译器可能会将其当作内联函数处理
☂️2.类的声明和定义分开
若类的声明在.h文件中进行,类的函数定义在.cpp文件中进行,成员函数前需加上
类名::

类的变量声明规则

一般声明类的变量我们会在变量前/后加上_,m;
原因是为了说明其局部性。

class Date
{
	void Init(int year, int month, int day)
	{
		year = year;
	}
	int year;
	int month;
	int day;
};

如上述代码,编译器并不会报错,但这里问题出在函数的参数和类的成员变量的命名起冲突了,而一般在类中会先搜索类内的局部变量,所以这里是类中的year自己赋值给本身,并无意义。
所以我们才出现了这一套的命名规则。

👉🏻类的访问限定符

在这里插入图片描述

  1. public修饰的成员在类外可以直接被访问
  2. protected和private修饰的成员在类外不能直接被访问
  3. 访问权限作用域从该访问限定符出现的位置开始直到下一个访问限定符出现时为止
  4. 如果后面没有访问限定符,作用域就到 } 即类结束。
  5. class的默认访问权限为private,struct为public(因为struct要兼容C)

类的大小以及类中的函数在内存中的位置

class Date
{
	void Init(int year, int month, int day)
	{
		year = year;
	}
	int year;
	int month;
	int day;
};
int main()
{
	Date d1;
	cout << sizeof(Date) << endl;
	cout << sizeof(d1) << endl;

	return 0;
}

上述代码中,结果是多少呢?
如果只看类中的成员变量,我们知道有3个int,所以是12个字节。
但是函数在类中占不占空间呢?
我们先看下结果 👇🏻 👇🏻
在这里插入图片描述
由此观之,函数在类中是不占空间的成员函数存放在公共的代码段
事实上,类中的方法即函数共用内存的中的一块区域。
可以这样理解,当我们实例化多个类的对象时,类的对象就是一个房子,房子中有浴室、卧室、厨房,而每个对象的成员变量就是相当这些浴室、卧室、厨房。
所以成员变量是相对独立的,每实例化一个对象,都要在内存中开辟空间。
但是函数不一样,函数相当于小区里的篮球场,便利店,是共有的。
它既属于每个对象,又不单单只属于某个对象。所以它只是在内存中占用一个区域。

在这里插入图片描述

但如上的代码结果中,为什么显示所占内存空间为1呢?
实际上,一个空类,编译器会给一个字节的空间证明该类存在过,它不存储数据,但占位

类的访问

我们不可以用类名.成员变量去访问类的成员。
因为类是一个声明,它不开辟空间,自然就不能访问
我们只有实例化出一个对象(开辟空间)后,才能通过对象名.成员变量访问

👉🏻this 指针

#include <iostream>
using namespace std;
class Fun1
{
public:
	void Print()
	{
		cout << "Hello world" << endl;
	}
};

int main()
{
	Fun1 f1,f2;
	f1.Print();
	f2.Print();
	return 0;
}

如上代码中,我们创建了f1,f2两个对象,并且两个对象都一起调用了类中的Print函数,但是我们可以看到,在类中并没有对不同对象的区分,那么在调用类中的函数或成员变量时,编译器是怎么区分f1和f2这两个对象呢?
而这里,其实编译器用this关键字做了一个隐形加工,如果还原原貌,上述代码应为这样👇🏻👇🏻

class Fun1
{
public:
	void Print(Fun1* const this)
	{
		cout << "Hello world" << endl;
	}
};

int main()
{
	Fun1 f1,f2;
	f1.Print(&f1);
	f2.Print(&f2);
	return 0;
}

我们了解以下this关键字的特性⭐️

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

关于this的一些题目

this指针存在哪?答:this是形参,所以存储在栈区上
this指针可以为空吗?答:可以。

// 1.下面程序编译运行结果是? A、编译报错 B、运行崩溃 C、正常运行
class A
{
public:
 void Print()
 {
 cout << "Print()" << endl;
 }
private:
 int _a;
};
int main()
{
 A* p = nullptr;
 p->Print();
 return 0;
}

有人可能会认为选B,因为这里进行了空指针的引用。
但实际上,编译器在这里调用Print函数时,并没有对空指针进行引用。
我们在上述中知道,成员函数是存储在公共代码区中的,并没有存储在类当中,所以我们调用成员函数时,并不根据类的地址,而是成员函数名,所以,类即使是非空或空指针都没区别。
所以这里应该是正常运行,选C

class A
{ 
public:
    void PrintA() 
   {
        cout<<_a<<endl;
   }
private:
 int _a;
};
int main()
{
    A* p = nullptr;
    p->PrintA();
    return 0;
}

在PrintA中cout<<_a<<endl;还原原貌是this->_a;
而这里this是空指针,引用空指针会导致运行崩溃,选B。

👉🏻c和c++实现Stack的对比

1.c语言实现

#include <iostream>
#include <assert.h>
using namespace std;
typedef int DataType;
 struct Stack
{
	 DataType* a;
	int top;
	int capacity;
};

 void InitStack(Stack* st)
{
	 assert(st);
	 st->a = NULL;
	 st->top = 0;
	 st->capacity = 0;
}

 void PushStack(Stack* st, int x)
 {
	 assert(st);

	 //判断是否需要扩容
	 if (st->top == st->capacity)
	 {
		 int newcapacity = st->top == 0 ? 4 : st->capacity * 2;
		 int* tmp = (int*)realloc(st->a,sizeof(int) * newcapacity);
		 if (nullptr == tmp)
		 {
			 perror("realloc fail");
			 exit(-1);
		 }
		 st->a = tmp;
		 st->capacity = newcapacity;
	 }
	 st->a[st->top] = x;
	 st->top++;
}
 bool StackEmpty(Stack* st)
 {
	 assert(st);

	 return st->top == 0;
 }
 void PopStack(Stack* st)
 {
	 assert(st);
	 assert(!StackEmpty(st));
	 st->top--;
 }

可以看到,在用C语言实现时,Stack相关操作函数有以下共性:

  • 每个函数的第一个参数都是Stack*
  • 函数中必须要对第一个参数检测,因为该参数可能会为NULL
  • 函数中都是通过Stack*参数操作栈的
  • 调用时必须传递Stack结构体变量的地址
  • 结构体中只能定义存放数据的结构,操作数据的方法不能放在结构体中,即数据和操作数据的方式是分离开的,而且实现上相当复杂一点,涉及到大量指针操作,稍不注意可能就会出错。

2.c++实现

#include <iostream>
#include <assert.h>
using namespace std;
typedef int DataType;
 class _Stack
 {
 public:
	void  Init()
	 {
		a = NULL;
		top = 0;
		capacity = 0;
	 }
	void Push(DataType x)
	{
		if (top == capacity)
		{
			int newcapacity = top == 0 ? 4 : capacity * 2;
			DataType* tmp = (DataType*)realloc(a, sizeof(DataType) * newcapacity);
			if (tmp == NULL)
			{
				perror("realloc fail");
				exit(-1);
			}
			a = tmp;
			capacity = newcapacity;
		}
		a[top++] = x;
	}
	bool Empty()
	{
		return top == 0;
	}
	void Pop()
	{
		assert(!Empty());
		top--;
	}

 private:
	 DataType* a;
	 int top;
	 int capacity;
 };

C++中通过类可以将数据 以及 操作数据的方法进行完美结合,通过访问权限可以控制那些方法在类外可以被调用,即封装,在使用时就像使用自己的成员一样,更符合人类对一件事物的认知。
而且每个方法不需要传递Stack*的参数了,编译器编译之后该参数会自动还原,即C++中 Stack * 参数是编译器维护的,C语言中需用用户自己维护。


如上便是本期的所有内容了,如果喜欢并觉得有帮助的话,希望可以博个点赞+收藏+关注🌹🌹🌹❤️ 🧡 💛,学海无涯苦作舟,愿与君一起共勉成长
在这里插入图片描述
在这里插入图片描述

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值