本期我们来学习类与对象
目录
面向过程和面向对象初步认识
类的引入
访问限定符
类的定义
封装
类的作用域
类的实例化
this指针
C语言和C++实现Stack的对比
面向过程和面向对象初步认识
这是C语言的的实现,我们要按步骤,一步一步来求解
再比如我们要设计一个外卖系统
如果是面向过程的话,就是上架,点餐,送餐,结算等等,关注的是步骤
而如果是面向对象的话,就是商家,骑手,客户三者之间的相互关系了,关注的是对象与对象之间关系和交互,就像在虚拟的世界里描述现实世界一样,是将现实世界的类和对象映射到计算机里
大家可以认为面向对象是一种更高级的开发方式,是语言的进化方向,现在新的语言几乎都是面向对象的,所以这也是我们学习的方向
类的引入
s1是我们以前创建栈的方式,因为现在的栈是一个类了,所以我们可以直接用类名来创建栈,如上面的s2
我们栈里面的a,capacity,top都叫做成员变量
此外,C++还可以在类里面定义函数,这叫做成员函数(方法)
我们之前为了区分,在各种函数前加上了前缀,比如StackInit,QueueInit,现在可以在类里面定义函数,我们就不需要加前缀了
两个函数都叫Init,但是因为域不同,所以不会干扰,C++里{ } 定义的都是域
我们随便写点函数
这里成员变量并不一定要在函数后面,在前面也是可以的,甚至写在中间也没问题,因为类域是一个整体
我们再调用函数就非常方便了,用 . 的方式即可,我们写起来是比C语言舒服的
但我们换成class后又编译不通过了,这就涉及到了权限的问题
访问限定符
【访问限定符说明】1. public 修饰的成员在类外可以直接被访问2. protected 和 private 修饰的成员在类外不能直接被访问 (我们暂时认为 protected 和 private 是类似的 )3. 访问权限 作用域从该访问限定符出现的位置开始直到下一个访问限定符出现时为止4. 如果后面没有访问限定符,作用域就到 } 即类结束。5. class 的默认访问权限为 private , struct 为 public( 因为 struct 要兼容 C)注意:访问限定符只在编译时有用,当数据映射到内存后,没有任何访问限定符上的区别
我们加上public就可以编译通过了,pubilc是公有的,直到遇到下一个访问限定符前都是公有的,但我们一般不希望成员变量也是公有的,所以要加上private
说明白点就是,我们想给别人用的是公有,不想给的是私有
我们切换成struct也是可以用访问限定符的,因为struct也是类,区别在于struct默认是公有,class默认是私有,所以我们之前用struct可以通过编译,而class不可以,不过我们不推荐用默认,最好显示的指定出来
类的定义
class className
{
// 类体:由成员函数和成员变量组成
}; // 一定要注意后面的分号
需要注意的是,我们在定义时要加上类域,让编译器知道,这不是一个普通函数,而是一个类的成员函数
比如我们的Push和Destroy都是内联,当然最终是否成为内联要取决于编译器
所以我们长的函数定义和声明分离,短的函数就直接定义了
关于类还有一个问题
这里的year是无法区分的,所以我们喜欢在成员变量前加上下划线
后面加下划线也是可以,也有人喜欢加m
其他方式也可以,大家根据喜好选择就行
封装
我们正常用是这样用的
但是如果有人这样写呢?
一会调用函数,一会自己访问
更严重一点的,如果这样搞,就可能会出现随机值了,你怎么知道top是栈顶元素,还是下一个位置呢?
但是如果我们没有提供STTop函数,让用的人自己去想top是什么,就会出现各种各样的问题
所以C++为了解决这些问题,为了杜绝这种乱写代码的情况产生,就有了封装
我们只能按照规则访问,不遵循规则就报错
类的作用域
class Person
{
public:
void PrintPersonInfo();
private:
char _name[20];
char _gender[3];
int _age;
};
// 这里需要指定PrintPersonInfo是属于Person这个类域
void Person::PrintPersonInfo()
{
cout << _name << " "<< _gender << " " << _age << endl;
}
类的实例化
我们先看这样一个问题
我们在类里面的成员变量是属于声明还是定义?
答案是声明,对于变量而言,声明和定义的真正区别在于开不开空间
而且对于类,是整体定义,而不是一个一个的定义
我们这里创建的s,叫做类的实例化对象,或者对象的定义
类就像房子的设计图一样,而对象就是我们根据设计图设计出的房子
类里面能不能存数据?不能,就像图纸里面不能住人
所以这种错误就是错误的,因为它没有空间,图纸不能住人
我们再看一个问题
这里应该输出多少呢?是只算成员呢,还是也要算函数呢?
我们看运行结果
结论:对象的大小只算成员变量(要结构体对齐),不算成员函数
另外,sizeof对象和sizeof类计算出的结果是一样的,就和sizeof(int)是一样的
我们举个例子
我们先把成员变量设置为公有的,我们来看
s1.top和s2.top,并不是同一块空间,就像两栋房子,都有厨房,都有卧室
但是两个Push,是同一个函数,就像我们在小区里,我们的篮球场,篮球场不需要每家每户都建一个,而是在公共的地方建一个,大家一起来使用,篮球场也可以在每家每户建一个,但是没必要,太浪费了,所以调用函数,就是去公共的区域调用
我们再看这个问题
为什么这里是1,而不是0呢?
举个例子就是我们在小区里建房子时,有一栋房子我们还没规划好,但是需要给这栋房子留个位置,不然之后就无法建造
这一个字节就是用来占位的,表示对象存在,不然取地址就没办法了
this指针
我们先看这样一段代码
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 a;
};
int main() {
Date d1, d2;
d1.Init(2022, 1, 2);
d2.Init(2023, 2,3);
d1.Print();
d2.Print();
}
我们知道d1,d2是两个不同的对象,但是Print函数是在公共区域的,为什么输出的结果却不同呢?
这里面就有一个隐藏的this指针,编译器会将函数处理为
同时,函数的调用也会被处理为
这些都是编译器的暗箱操作
另外,还规定了我们不能在形参和实参上显示的去写,所以上面都是报错
但是可以显示的去用,比如
我们也不能将this改为空指针
因为this指针的类型是 类类型* const
补充两种错误的写法
上面的是声明,即我们不能在图纸里住,第二个是 :: 这个符号是域作用限定符
this指针是存在哪里的?对象,栈,堆,静态区还是常量区?
答案是在栈里面,this指针是形参,形参是在栈里的
vs下对this指针的传递进行优化,对象的地址是放在ecx,ecx存储this指针(ecx是寄存器)
注意,这是vs下面的,不是所有的编译器
我们再看一个问题
答案为第一段代码选C,第二段代码选B
因为第一段代码p调用Print不会解引用,Print的地址不在对象里,不过p会作为实参传递给this指针
传递空指针并不会报错
第二段代码同样p调用Print不会解引用,但是this传递过去后,this是空指针,函数内部访问了_a,就是空指针解引用
总结一下
1. this 指针的类型:类类型 * const ,即成员函数中,不能给 this 指针赋值。2. 只能在 “ 成员函数 ” 的内部使用3. this 指针本质上是 “ 成员函数 ” 的形参 ,当对象调用成员函数时,将对象地址作为实参传递给 this 形参。所以对象中不存储 this 指针 。4. this 指针是 “ 成员函数 ” 第一个隐含的指针形参,一般情况由编译器通过 ecx 寄存器自动传递,不需要用 户传递
C语言和C++实现Stack的对比
C语言实现
我们发现用C语言实现栈(这里我只截了开头和结尾,相信大家都知道C实现的栈是什么样子)
每个函数的第一个参数都是 Stack*函数中必须要对第一个参数检测,因为该参数可能会为 NULL函数中都是通过 Stack* 参数操作栈的调用时必须传递 Stack 结构体变量的地址
typedef int DataType;
class Stack
{
public:
void Init()
{
_array = (DataType*)malloc(sizeof(DataType) * 3);
if (NULL == _array)
{
perror("malloc申请空间失败!!!");
return;
}
_capacity = 3;
_size = 0;
}
void Push(DataType data)
{
CheckCapacity();
_array[_size] = data;
_size++;
}
void Pop()
{
if (Empty())
return;
_size--;
}
DataType Top() { return _array[_size - 1]; }
int Empty() { return 0 == _size; }
int Size() { return _size; }
void Destroy()
{
if (_array)
{
free(_array);
_array = NULL;
_capacity = 0;
_size = 0;
}
}
private:
void CheckCapacity()
{
if (_size == _capacity)
{
int newcapacity = _capacity * 2;
DataType* temp = (DataType*)realloc(_array, newcapacity *
sizeof(DataType));
if (temp == NULL)
{
perror("realloc申请空间失败!!!");
return;
}
_array = temp;
_capacity = newcapacity;
}
}
private:
DataType* _array;
int _capacity;
int _size;
};
int main()
{
Stack s;
s.Init();
s.Push(1);
s.Push(2);
s.Push(3);
s.Push(4);
printf("%d\n", s.Top());
printf("%d\n", s.Size());
s.Pop();
s.Pop();
printf("%d\n", s.Top());
printf("%d\n", s.Size());
s.Destroy();
return 0;
}