类和对象
一、面向过程和面向对象初步认识
- C和C++之间的区别
- C语言是面向过程的,关注的是过程,分析出求解问题的步骤,通过函数调用逐步解决问题。
- C++是基于面向对象的,关注的是对象,将一件事情拆分成不同的对象,靠对象之间的交互完成。
二、类的引入
C语言结构体中只能定义变量,在C++中,结构体内不仅可以定义变量,也可以定义函数。比如:
之前在数据结构初阶中,用C语言方式实现的栈,结构体中只能定义变量;现在以C++方式实现,会发现struct中也可以定义函数。
故,类的引用也可以通过 C的结构体 进行过度
//C++兼容c中 struct 的用法
//C++ 升级 struct 升级成了类
//类和对象:
//1 个 类, 实例化 N 个对象
- 类和结构体有什么区别嘛?
1.结构体只有数据,升级成类之后,也可以写函数
//以前c是这样写的
struct Stack
{
int* _array;
size_t _capacity;
size_t _top;
};
void StackInit(struct Stack* ps,int n)
{}
- 升级成类之后,我们就可以把方法往类里面去放啦!
struct Stack
{
void Init(int n = 4) //全缺省函数,
{
_array = (int*)malloc(sizeof(int) * capacity);
if (nullptr == _array)
{
perror("malloc申请空间失败");
return;
}
_capacity = n;
_size = 0;
}
//成员变量
int* _array;
size_t _capacity;
size_t _top;
};
C++升级struct升级成类
1.类里面可以定义函数
2.名称就可以代表类型
struct Stack st1; //可以这么用因为C++兼容C
Stack st2; //最通常就是用这种表达方式
//调用函数
st1.Init(100);
st2.Init();
- C的结构体是这样定义的
typedef struct ListNode_C
{
struct ListNode_C* next;
int val;
}LTNode;
- C++类是这样定义的
struct ListNodeCPP
{
ListNodeCPP* next; //结构体的名称就可以代表类型
int val
};
//也体现了C++是兼容C的struct的用法
三、类的定义
-
接下来!我们就写一个正儿八经的C++的类吧!
- Let’s Go! !!👇
class className //其实也是改了一个名字
{
// 类体:由成员函数和成员变量组成
void Init(int n = 4) //全缺省函数,
{
_array = (int*)malloc(sizeof(int) * capacity);
if (nullptr == _array)
{
perror("malloc申请空间失败");
return;
}
_capacity = n;
_size = 0;
}
//成员变量
int* _array;
size_t _capacity;
size_t _top;
}; // 一定要注意后面的分号
----->感觉有点东西了?但是下面引出了一个问题!
当我们直接调用类里面的函数时,发生报错啦!
int mian()
{
Stack st1;
st1.Init();
}
- 其实C++还引入一个东西,叫做: 访问限定符!
四、类的访问限定符及封装
4.1 访问限定符
- 保护限定符有三种!
⭐公有–>在类外面可以直接访问(其他则不可以)
欸?所以为什么会出现上面的报错呢!
- class的默认访问权限为private,struct为public(因为struct要兼容C)
👉想要访问成员变量也很简单!
class classname
{
public:
//成员函数
//这里的成员就会变成公有的了!
private:
//成员变量
}
总结:一般情况下,成员变量设置为私有;而成员函数会设置为公有
4.2 封装
- 面向对象的三大特性:封装、继承、多态。
封装其实就是通过设置公有私有来对对象进行更好的管理
五、类的作用域
类定义了一个新的作用域,类的所有成员都在类的作用域中。在类体外定义成员时,需要使用 :: 作用域操作符指明成员属于哪个类域。
====Stack.h👇====
class Stack
{ //花括号括起来的基本都是域
public:
void Init();
private:
int* _a;
int _top;
int _capacity;
};
====Stack.cpp👇====
#include"Stack.h"
void Init()
{
_a = nullptr;
_top = 0;
_capacity = 0;
}
我们看一下报错的结果!
- 结果:其实是因为编译器不知道 Init 要访问的是哪里,所以我们只要在前面加上 Stack:: 就可以解决这个问题了
- 通过以上例子:我们提出了类域这个概念
- 当声明和定义分开的时候,我们就需要 :: 指定作用域
总结:我们现在一共学习了四个域!
- 局部域
- 全局域
- 命名空间域
- 类域
六、类的实例化
用类类型创建对象的过程,称为类的实例化
int main()
{
//类 -> 对象 一对多的关系
//对象的实例化
Stack st1;
Stack st2;
}
Stack 实例化的对象,调用成员函数地址都是一样的
每个对象内部都放一份,大大的浪费
1.代码只保存一份,在对象中保存存放代码的地址
2.只保存成员变量,成员函数存放在公共的代码段
七、类对象模型
但是,如果是空类的话,大小又是多少呢!
// 空类的大小为 ——> 1
// 这一个字节,不存储有效数据,
//标识对象被定义出来了程序
class A3
{};
结论:一个类的大小,实际就是该类中”成员变量”之和,当然要注意内存对齐 注意空类的大小,空类比较特殊,编译器给了空类一个字节来唯一标识这个类的对象。
- 什么是大小端?如何测试某台机器是大端还是小端,有没有遇到过要考虑大小端的场景
/*
什么是大小端:
大端:数据的低位字节内容保存在内存的高地址出
较低 的有效字节存放在 较高 的存储器地址
较高 的有效字节存放在 较低 的存储器地址
小端:较低 的有效字节存放在 较低 的存储器地址
较高 的有效字节存放在 较高 的存储器地址
*/
#include<stdio.h>
int main()
{
int i = 1;
char ret = *((char*)&i);
//获取i存储的内存地址指针,将其转换为char*类型,提取第一个字节地址指针
//再将获取得到的地址指针进行解引用
//1在内存中存储为0x000001
//如果第一个字节为0,说明机器是大端存储,低位字节1存储在高位地址
//如果第一个字节为1,说明机器是小端存储,低位字节1存储在低位地址
if (ret == 0)
{
printf("大端存储");
}
else
{
printf("小端存储");
}
}
/*
int check_sys()
{
union Un
{
char c;
int i;
}u;
u.i = 1;
return u.c;//返回1 就是小端
//返回0 就是大端
}
int main()
{
int ret = check_sys();
if (ret == 0)
{
printf("大端存储");
}
else
{
printf("小端存储");
}
return 0;
}
*/
八、this指针
- 隐含的this指针:
- 实参和形参的位置不能显示写,因为编译器会自己加
- 但是在类里面可以用
- 下面程序编译运行结果是?
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的对象里面找print的地址
- 传递一个空指针不会报错,因为没有对空指针访问
- 下面程序编译运行结果是?
A、编译报错 B、运行崩溃 C、正常运行
class a
{
public:
void printa()
{
cout << this << endl;
cout << _a << endl; //this->_a
}
private:
int _a;
};
int main()
{
a* p = nullptr;
p->printa();
return 0;
}
答案:B
原因分析👇
附加内容
- this 存在栈上面 因为他是形参
- 形参和局部变量都是存在栈上面的
C语言和C++实现Stack的对比
- 在用C语言实现时,Stack相关操作函数有以下共性:
- 每个函数的第一个参数都是Stack*
- 函数中必须要对第一个参数检测,因为该参数可能会为NULL
- 函数中都是通过Stack*参数操作栈的
- 调用时必须传递Stack结构体变量的地址
结构体中只能定义存放数据的结构,操作数据的方法不能放在结构体中,即数据和操作数据的方式是分离开的,而且实现上相当复杂一点,涉及到大量指针操作,稍不注意可能就会出错。
C++中通过类可以将数据 以及 操作数据的方法进行完美结合,通过访问权限可以控制那些方法在
类外可以被调用,即封装,在使用时就像使用自己的成员一样,更符合人类对一件事物的认知。
而且每个方法不需要传递Stack*的参数了,编译器编译之后该参数会自动还原,
即C++中 Stack * 参数是编译器维护的,C语言中需用用户自己维护。