[类与对象]上

面向过程与面向对象初了解

C语言是面向过程的,关注过程,分析出求解问题的步骤,通过函数调用逐步解决
C++是基于面向对象的,关注的是对象,讲一件事情拆分成不同的对象,靠对象之间的交互完成
拿一个简单的外卖系统举例子:
面向过程:关注实现下单、接单、送餐这些过程;在代码层面就是–方法、函数
面向对象:关注实现类对象和对象之间的关系;用户、商家、骑手以及他们之间的关系;在代码层面就是类的设计及其类之间的关系
C++基于面向对象:面向对象与过程混编
原因在于C++兼容C
Java只有面向对象

类的引入与定义

C语言中,结构体中只能定义变量
C++中,结构体内部不仅可以定义变量,也可以定义函数
而在C++中,更喜欢用class去代替struct
例如定义一个学生类:

struct Student // Student就叫类名
{
	char name[10];
	int age;
	int id;
};
struct ListNode
{

};
int main()
{
	struct Student s1; // c语言结构体
	Student s2; // 升级到类,Student是类名也是类型
	// 因此在Oj网站一般没有struct
	// 以下两种都可以
	ListNode* node;
	struct ListNode* node1;
	// s1.name数组是不可以直接赋值的
	strcpy(s1.name, "zhangsan");
	s1.id = 1;
	s1.age = 18;

	strcpy(s2.name, "lisi");
	s2.id = 1;
	s2.age = 18;
	return 0;
}

在C++中兼容C语言中结构体的用法,同时struct在C++中也升级为了类
C++类与结构体的区别在于除了定义变量也可以定义函数(方法)
类中的数据称为类的成员:类中的数据称为类的属性或者成员变量,类中的函数称为类的方法或者成员函数

类的两种定义方法:
1、声明与定义全部在类体中(如果成员函数在类中定义,编译器会将其当成内联函数处理)

class Person
{
public: // 下面讲解此处public及后面private的含义
    void showInfo() // 成员函数存储在公共的代码段中
    {
        cout << _name << " " << _sex << endl;
    }

private:
    char *_name[10];
    char *_sex[10];
};

2、声明放在.h文件中,类的定义放在.cpp文件中

// person.h放声明
class Person
{
public:
	void ShowInfo();
private:
	char *_name[10];
	char *_sex[10];
};
// person.cpp放类的实现:
#include "person.h"
void Person::ShowInfo()
{
	cout << _name << " " << _sex << endl;
}

在正式的项目工程中,一般采用第二种方式

类的访问限定符及封装

访问限定符: C++实现封装的方式:用类将对象的属性和方法结合在一块,让对象更加完善,通过访问权限选择性的将其接口提供给外部用户使用
访问限定符有3种:public(共有),protected(保护),private(私有)
public:类内可以访问,类外也可以进行访问
private:类内可以访问,类外不能进行访问
protected在之后进行介绍,此处可以先忽略
[访问限定符说明]:
1、public修饰的成员在类外可以直接被访问
2、protected与private修饰的成员在类外不能直接被访问(在此处private与protected是类似的,在之后的章节说明此处两个的区别)
3、访问权限作用域从该访问限定符开始出现的位置开始到下一个访问限定符为止
4、class的默认访问权限为private(私有),struct为public(因为要兼容c)
注意:访问限定符只有在编译时才有用,当数据映射到内存后,没有任何访问限定符上的区别

将成员属性设置为私有的好处:
1、可以自己控制读写权限
2、对于写权限,我们可以检查数据的有效性

class T1
{
	int _A; // 默认权限:是私有成员
};
struct T2
{
	int _B; // 默认权限:是公有成员
};
int main()
{
	T1 t1;
	t1._A = 100; // 报错
	T2 t2;
	t2._B = 100; // 成功
}

[C++中struct与class的区别是什么?]
1、C++需要兼容C,因此C++中struct可以当成结构体去使用,另外C++中struct可以和class一样用来定义类
2、struct成员默认访问方式是public,class成员默认访问方式是private

[封装]:
面向对象的三大特性:封装、继承、多态
在类与对象阶段,这里值研究类的封装特性,那什么是封装?
封装: 将数据和操作数据的方法进行有机结合,隐藏对象的属性和实现细节
封装的本质上也是一种管理,我们使用类数据和方法都进行封装,使用private/protected把成员封装起来,开放一些共有的成员函数对成员合理的访问,所以封装的本质是一种管理

struct Student 
{
	char _name[10];
	int _age;
	int _id;

	void Init(const char* name, int age, int id)
	{
		strcpy(_name, name);
		_age = age;
		_id = id;
	}
	void Print()
	{
		cout << _name << endl;
		cout << _age << endl;
		cout << _id << endl;
	}
};
int main()
{
	struct Student s1;
	Student s2;
	s1.Init("张三", 18, 1);
	s2.Init("李四", 19, 2);
	
	s1.Print();
	s2.Print();

	return 0;
}
struct Student 
{
	// struct默认为共有
	char _name[10];
	int _age;
	int _id;

	void Init(const char* name, int age, int id)
	{
		strcpy(_name, name);
		_age = age;
		_id = id;
	}
	void Print()
	{
		cout << _name << endl;
		cout << _age << endl;
		cout << _id << endl;
	}
};
int main()
{
	struct Student s1;
	Student s2;
	s1.Init("张三", 18, 1);
	s2.Init("李四", 19, 2);
	
	s1.Print();
	s2.Print();

	return 0;
}

类的实例化

用类类型创建对象的过程称为类的实例化,一个类可以实例化多个对象,因此是一对多的关系
类就相当于是一个图纸,类无法存储各种数据,只有实例化出的对象才能存储对象
那为什么sizeof(类)可以计算出大小——这个大小的含义是实例化出的对象有多大,类被编译后存放在代码段中
定义出一个类并没有分配实际的内存空间去存储它,实例化出的对象,才占用实际的物理空间,存储成员变量

类的作用域

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

/* Stack.h文件 */
class Stack
{
	// 一般来说想修改私有成员变量都是通过共有的函数去使用
private:
	int* _a;
	int _top;
	int _capacity;

public:
	void Init();
	void Push(int x);
};
/* Stack.cpp文件 */
void Stack::Init() // 必须指明类域
{
	_a = nullptr;
	_top = _capacity = 0;
}

类对象模型

如何计算类对象的大小?

/* Stack.h文件 */
class Stack
{
	// 一般来说想修改私有成员变量都是通过共有的函数去使用
private:
	int* _a;
	int _top;
	int _capacity;

public:
	void Init();
	void Push(int x);
};
int main()
{
	Stack s;
	// 大小该为多少?
	cout << sizeof(Stack) << endl; // 12
	cout << sizeof(s) << endl; // 12
	return 0;
}

对象中存了成员变量,是否存了成员函数?——没有存
为什么只存储了成员变量,而没有存储成员函数?

int main()
{
	Stack s1;
	Stack s2;
	// 每个对象都是独立的空间,都有独立的成员变量
	// s1与s2的_top不是同一个成员
	s1._top = 0;
	s2._top = -1;
	// 不同对象调用成员函数,调的是同一个函数
	s1.Init();
	s2.Init();
	// 既然是同一个函数为什么还要对象.?
	// 原因——this指针
	cout << sizeof(Stack) << endl;
	cout << sizeof(s1) << endl;
	return 0;
}

[方案1——对象中包含类的各个成员]
每个对象中成员变量都是不同的,但是调用同一个函数,按照这种方式来进行存储,当一个类创建多个对象的时候,每个对象都会保存一份代码,相同代码保存多次,十分浪费空间
在这里插入图片描述
[方案2——只保存成员变量,成员函数放在公共代码段]

在这里插入图片描述
结论:计算类或者类对象大小,只看成员变量,考虑内存对齐,C++内存对齐规则与C语言结构体内存对齐规则一致

练习

// 类中既有成员变量,又有成员函数
class A1 
{
public:
	void f1() 
	{

	}

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

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

};

以上三个代码,sizeof结果为多少?
4,1,1

总结——空类大小为1
为什么是1不是0?

int main()
{
	A2 aa;
	A2 bb;
	cout << &aa << endl;
	cout << &bb << endl;
	return 0;
}

两个对象是有地址的,且地址不相同,如果他们没有空间,是没办法区分aa与bb,因此空类会给1Byte,这1Byte只是为了占位,不存储有效对象,表示对象存在

this指针

针对之前的一些补充
左右两个year都是形参,如何让他访问到成员变量?

class Date
{
public:
	void Init(int year, int month, int day)
	{
		year = year; // 没有_能不能编译通过?
		// 左右两边year是成员变量的year,还是形参的year
		// 都是形参——就近原则
		// 怎么样让左边的变成成员变量?
		_month = month;
		_day = day;
	}

private:
	int year;
	int _month;
	int _day;
};

int main()
{
	Date d1;
	d1.Init(2022, 1, 15);
	return 0;
}

解决方法1:用类域限定

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

解决方法2:用this指针

void Init(int year, int month, int day)
{
	this->year = year;
	_month = month;
	_day = day;
}

因此一般成员变量前面加_或者m(member)

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;
	d1.Init(2022, 1, 15);
	d1.Print();

	Date d2;
	d2.Init(2022, 1, 16);
	d2.Print();
	return 0;
}

这里可以看到d1和d2都调用了print函数,但是函数是怎么区分哪一个是d1的成员变量,哪一个是d2的?
进入反汇编查看:
在这里插入图片描述
这里d1和d2的Print函数的地址确实是一样的,那怎么识别是哪一个对象在调用这个函数?
对象.成员变量的含义:到这个对象所在的空间上找到这个成员变量的空间
对象.成员函数的含义:表明这个Print是这个类的成员函数;实际上是:Print(隐含的this指针);this指针是Date* this,如果是d1调用,就传的是d1的地址,d1.Print(&d1);里面从成员也是this -> _year , this -> _month , this -> _day
里面的this就是指向d1的

	void Print()
	{
		cout << this->_year << "-" << this->_month << "-" << this->_day << endl;
	}

所有的非静态成员都会被处理
实际上的Init函数也是有一个隐含的this指针

void Init(Date* this, int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}
int main()
{
	Date d2;
	d2.Init(2022, 1, 16); // d2.Init(&d2, 2022, 1, 16);
	d2.Print();
	return 0;
}

语法规定:
1、要用成员函数的时候,我们不能显示的传实参给this
2、定义成员函数时,也不能显示声明形参this
3、在成员函数内部,我们可以显示的使用this指针(因为有些场景是需要使用this指针的)

this指针是存储在哪里的?
一般情况下存放在栈中(形参在栈里面)
有些编译器会放到寄存器中(如VS——放到ecx)

以下代码编译运行的结果是?
A、编译错误 B、运行崩溃 C、正常运行

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

private:
	int _a;
};
int main()
{
	A* p = nullptr;
	p->Show();
	return 0;
}

分析:
A应该是不选择的,以上代码并没有语法错误,编译是无法检查出空指针的

答案是C

以下代码编译运行的结果是?
A、编译错误 B、运行崩溃 C、正常运行

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

private:
	int _a;
};
int main()
{
	A* p = nullptr;
	p->Print();
	return 0;
}

答案:B
1、p虽然是空指针,但是p调用成员函数的时候不会编译错误,因为空指针不是语法错误,编译器检查不出来
2、p虽然是空指针,p调用成员函数也不会出现空指针访问,因为成员函数没有存放在对象里面
3、这里会把p当做实参,传递给隐藏的this指针(空指针不解引用不会报错),传递空指针给this不会出错,然后使用了this指针,所以会崩溃

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值