【C++初阶2-类和对象-上】面向对象大法好

前言

本期打C++学习的第一个大boss——类与对象!这一块内容第一次接触确实绕,一起看看吧

内容概览:

  1. 面对对象的概念
  2. 类和对象
    1. 和结构体的对比
    2. 成员
    3. 定义
    4. 访问权限
    5. 封装
    6. 类域
    7. 类对象模型
    8. this指针
    9. Stack再实现

1. 面向对象的概念

对比面向过程,面向过程是关注步骤,把问题分解,调用函数逐步解决;面向对象是关注对象,解决问题依靠对象之间的交互完成

好处:更好地抽象,更好地建模

在这里插入图片描述

2. 类和对象

2.1 类的介绍

和C中的结构体对比一下,因为类很像是结构体的“升级”,先看看C++对结构体增加了什么东西

C++中的结构体

:结构体内可以声明、定义函数

C++中的类
  • C++中,称上文的结构体为 ,类和结构体一样,都是声明一个类型

  • 创建结构体变量叫创建,而“创建”一个对象,叫做实例化对象——用类(类型)来创建一个对象(变量),把类型变成实实在在的一个“例子”

  • 类也定义了一个作用域,叫类域
    类的所有成员都在此作用域内,类外用需要用 :: 指定类域

2.2 成员

结构体内可定义变量和函数,就称类,结构体有了“新名字”,其中的内容也有

  • 原结构体的成员变量,称 属性
  • 原结构体的成员函数,称 方法

所以:类包含属性和方法。

2.3 声明

简单讲了什么是类,来看看类怎么声明的

class People 
{
	void ShowInfo()
	{
		cout << name << '-' << age << '-' << sex << endl;
	}

	char* _name;
	int _age;
	char* _sex;
};
  • class :关键字
  • People:类名
  • void ShowInfo( ): People类的方法
  • _name,_age,_sex:People类的属性

诶?属性名前面加个下划线干嘛?

命名规范

:为了区分成员变量和形参

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

	int year;
	int month;
	int day;
};

int main()
{
	Date d;
	d.Init(2022, 9, 30);
	return 0;
}

这样就晕了,简直没有可读性了,所以为了区分类的属性和形参,通常要做标识,可以是_name、mName、m_name之类

如果重名会如何?

编译器会把它识别为局部变量, 而不会识别为成员变量,若要区分,用this

声明定义分离

一般都要声明定义分离,为了可读性,但在类外给出方法的定义时,要指定这个定义是哪个类的方法定义(用域作用限定符)

void People::ShowInfo()

如果不分离,在对象内定义,可能会被视为内联函数

2.4 封装

本质是一种 严格管理

诶,那我自由管理,像C语言一样,不行吗?

孰优孰劣,也分情况,不过可以参照一下 中米疫情…

多数情况还是严格管理更优

2.5 访问权限

而为了进一步封装,又产生了一个关于类的新东西:访问修饰限定符.

  • public:类内类外都能访问(类外访问要用作用域限定符指定)
  • private:类外不能访问
  • protected:后续再讲,现在讲不透
class People 
{
public://限定访问权限为公有
	void ShowInfo()
	{
		cout << name << '-' << age << '-' << sex << endl;
	}

private://限定访问权限为私有
	char* _name; 
	int _age;
	char* _sex;
};

把属性放到private,保护它们,做到封装(如果放到public,数据不安全,容易被动)

注意:

  • 类中如果不限定,默认私有

  • 结构体中如果不限定,默认公有(为了兼容C)

2.6 类对象的模型

我们已经学过,结构体中的变量经过内存对齐保存在结构体变量里,那类的对象中,方法、属性是如何存储的呢?

可以做出如下猜测

  1. 方法、属性都存在对象内

在这里插入图片描述

  1. 属性存在对象内,对于方法, 只存它的地址

在这里插入图片描述

  1. 只有属性存在对象内,方法放在代码段

在这里插入图片描述

分析:

  1. 显然不合理,若方法定义较长,再实例化10000个对象,就多了9999份冗余重复的代码
  2. 好像还行,但是这个地址是不是一定要存呢?其实没必要
  3. 此方法是C++对象存储采用的方案,方法定义放进代码段,编译后方法直接成为二进制指令,需要call,就jump到代码段对应位置执行指令(不会造成任何空间浪费)

知道了对象的存储模型,如何算对象大小?

对象大小的计算

方法是不用计算了,因为直接放在代码段,而属性的存储方式和结构体一模一样,要进行内存对齐

复习一下内存对齐:依照对齐数对齐

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

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

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

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

2.7 this指针

还有一个问题,我们方法全放在代码段了,不同对象调用同一块指令,执行的时候,方法本身怎么知道它是在给哪个对象干活呢?

:this指针就是解决此问题的

this指针:对象调用其方法的时候,会隐式地传递一个对象的指针作为形参,所有访问属性的操作都经过this指针

这不就是把C语言中老是要传传传的结构体指针给咱们自动传了嘛!美滋滋

现在就可以得出:

  • 访问对象成员:通过this指针
  • 调用对象方法:直接跳到代码段执行指令

看完这么妙的东西,再来两道题目考察一下:

【this指针存在哪里?】

:堆?代码段?都不是,而是栈!为什么?this是形参,存在栈帧内!

【this指针可以为空吗?】

:有时在纯语法上来说可以的——

  • 访问属性时要解引用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;
}

答案是,C

虽然p作为this指针为空,但是程序中只调用方法,没有访问属性,不需要解引用this指针,也就不会报错

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

答案是,B

p作为this指针为空,程序中访问了属性,需要解引用this指针,也就会运行崩溃

3. Stack再实现

//Stack.h
#include <iostream>
#include <assert.h>
using namespace std;

typedef int STDataType;
class Stack
{
public:
	void Init(int capacity);
	void Push(STDataType x);
	void Pop();
	STDataType Top();
	bool Empty();
	int Size();
	void Destroy();
private:
	void CheckCapacity();
	STDataType* _a;
	int _capacity;
	int _top;
};

//Stack.cpp
#include "stack.h"

void Stack::Init(int capacity)
{
	_a = (STDataType*)malloc(sizeof(STDataType) * capacity);
	assert(_a);

	_capacity = capacity;
    _top = 0;
}

void Stack::CheckCapacity()
{
	if (_capacity == _top)
	{
		int newCap = _capacity * 2;
		STDataType* tmp = (STDataType*)realloc(_a, sizeof(STDataType) * newCap);
		assert(tmp);

		_a = tmp;
		_capacity = newCap;
	}
}

void Stack::Push(STDataType x)
{
	CheckCapacity();

	_a[_top++] = x;
}


void Stack::Pop()
{
	assert(!Empty());
	_top--;
}

STDataType Stack::Top()
{
	return _a[_top - 1];
}

void Stack::Destroy()
{
	if (_a)
	{
		free(_a);
		_a = NULL;
		_capacity = _top = 0;
	}
	else
	{
		cout << "栈已空!\n" << endl;
	}
}

bool Stack::Empty()
{
	return _top == 0;
}

//test.cpp
int main()
{
	Stack s;

	s.Init(4);

	s.Push(1);
	s.Push(2);
	s.Push(3);
	s.Push(4);
	s.Push(5);

	s.Pop();
	cout << s.Top() << endl;
	s.Pop();
	cout << s.Top() << endl;
	s.Pop();
	cout << s.Top() << endl;

	s.Destroy();

	return 0;
}

:4
3
2

可以看到,

  1. 函数命名简洁
  2. 访问类的属性,通过自动传递的this指针,不用像之前每次传结构体指针,方便

今天的分享告一段落了,如有错落不足的地方,望请斧正~

浅尝了类和对象的甜头,后面还有更带劲的,我们一起努力吧!

这里是培根的blog,期待与你共同进步!

下期见~

评论 11
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

周杰偷奶茶

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值