创作不易,多多支持!
前言
有了上一篇博客的基础以后,就正式进入C++类和对象的领域了,如果看完本篇文章对你有用,还请多多支持!!??
一 面向过程和面向对象
1.面向过程
在之前用c语言的时候,我们写一个程序,通常需要把这个程序分成很多给步骤,所以c语言是一个关注过程,面向过程的语言。
很经典的例子就是我们洗衣服,得分为找到要洗的衣服,然后放进盆子里面,然后倒入洗衣液,再
之后放水,之后就是用手洗或者用洗衣机洗,洗完之后还需要晾衣服,假如是这样一个程序,那么
就是会分成很多个步骤逐个完成。
2.面向对象
C++是基于面向对象的,关注的是对象,它不太关注其中过程,只关心对象就行。
比如我们洗衣服就分为人,要洗的衣服,洗衣机,三个对象而已,我们不需要关注太多,那行繁琐
的步骤可能是在某个对象里面完成的,其他对象不需要关注它到底是怎么完成的
二 类的引入
在C语言中我们学过结构体,C++的类就是对结构体的升级
因为在结构体中只能是定义变量,却不能定义函数,然而类是可以的
#define _CRT_SECURE_NO_WARNINGS 1#include<iostream>using namespace std;struct A{void fun()//函数{cout << "类和对象" << endl;}int _year;int _momth;int _day;};int main(){A a;a.fun();return 0;}
如果是c语言则不能这样使用,但是对于C++来说,C++更喜欢用class来代替struct
#define _CRT_SECURE_NO_WARNINGS 1#include<iostream>using namespace std;class A{public:void fun(){cout << "类与对象" << endl;}private:int _year;int _momth;int _day;};int main(){A a;a.fun();return 0;}
如果用class去修饰,那么这个类的内容称为类的成员,里面的函数称为类的方法或成员函数,里面的变量称为类的成员变量
三 类的定义
类的定义有两种方式
第一种就是上面那种,全部放在类体中,这种放在类里面的函数,编译器会把他们当作内联函数
第二种是把函数的定义放在外面,也就是把声明凡在.h文件,把定义放在.cpp中,如果是这样定义的话,那么我需要在函数定义的地方加上域访问符
第一张图是.h文件的,第二张图是.cpp文件的
四 类的访问限定符及封装
4.1 访问限定符
这里的访问限定符其实正上面就已经有了介绍,就是外面用类把对象的属性和方法封装起来,通过访问权限选择性去给用户使用
这里就是public,private,protected,三种
1. public 修饰的成员在类外可以直接被访问 2. protected 和 private 修饰的成员在类外不能直接被访问 ( 此处 protected 和 private 是类似的 ) 3. 访问权限 作用域从该访问限定符出现的位置开始直到下一个访问限定符出现时为止 4. 如果后面没有访问限定符,作用域就到 } 即类结束。 5. class 的默认访问权限为 private , struct 为 public( 因为 struct 要兼容 C)
其实上面第五点提到了class与struct的区别,其实不然,他们的区别还有在继承和模板的位置有,这里就不一一介绍,在后面再说
4.2 封装
封装:将数据和操作数据的方法进行有机结合,隐藏对象的属性和实现细节,仅对外公开接口来 和对象进行交互 在 C++ 语言中实现封装,可以 通过类将数据以及操作数据的方法进行有机结合,通过访问权限来 隐藏对象内部实现细节,控制哪些方法可以在类外部直接被使用 。
五 类的实例化
用类类型创建对象的过程,称为类的实例化我们写的类其实是对对象的描述,也就是相当于打了一个草稿,或者画了一张图纸,并没有去把它变成一个物体也就是没用开辟空间给它,只有当我们把它实例化也就是创建对象的时候,它才会对分配空间存储成员变量
这里的空间并没有分配给里面的函数,因为函数是不存储再对象里面的,是存储在公共的代码区里面,这一点尤为重要
所以有了上面的认识,我们在计算类的大小的时候就不能把里面的函数大小算进来,只算里面的成员变量就行
对于空类来说,其大小为1个字节,这1个字节没有意义,只是说明它存在而已
结构体内存对齐规则 1. 第一个成员在与结构体偏移量为 0 的地址处。 2. 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。 注意:对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值。 VS 中默认的对齐数为 8 3. 结构体总大小为:最大对齐数(所有变量类型最大者与默认对齐参数取最小)的整数倍。 4. 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整 体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。 这个规则同样适用与类六 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 main(){ Date d1, d2; d1.Init(2022,1,11); d2.Init(2022, 1, 12); d1.Print(); d2.Print(); return 0;}
从代码中我们可以看出对象d1和d2都共用一个这个初始化函数,但是这个初始化函数是怎么分别出这两个对象不一样的呢?(类里面的函数都在代码区,只有成员变量算在类里面,占据类的内存)
其实原本应该是这样的
class Date{ public: void Init(Date*const this,int year, int month, int day)//这里就是把隐含的指针显示出来 { this->_year = year; this->_month = month; this->_day = day; } void Print() { cout <<_year<< "-" <<_month << "-"<< _day <<endl; }private: int _year; // 年 int _month; // 月 int _day; // 日};int main(){ Date d1, d2; d1.Init(2022,1,11); d2.Init(2022, 1, 12); d1.Print(); d2.Print(); return 0;}
所以说this指针的特性就是
1. this指针的类型:类类型* const,即成员函数中,不能给this指针赋值。 2. 只能在“成员函数”的内部使用 3. this指针本质上是“成员函数”的形参,当对象调用成员函数时,将对象地址作为实参传递给 this形参。所以对象中不存储this指针。 4. this指针是“成员函数”第一个隐含的指针形参,一般情况由编译器通过ecx寄存器自动传 递,不需要用户传递所以我们一般不会像第二个那样去写,一般是采用第一个的形式去写,这样比较方便一点
七 C语言和C++实现栈的比较
现在我们用C去实现一个栈
#pragma once#include<iostream>#include<assert.h>using namespace std;typedef int DataType;typedef struct Stack{DataType* a;int size;int capacity;}Stack;void StackInit(Stack* ps){assert(ps);Stack* tmp;tmp = (DataType*)malloc(sizeof(DataType)*4);if (tmp == NULL){return 0;}else{ps->a = tmp;}ps->size = 0;ps->capacity = 4;}void StackDestroy(Stack* ps){assert(ps);free(ps->a);ps->a = NULL;ps->size = 0;ps->capacity = 0;}void CheckCapacity(Stack* ps){assert(ps);if (ps->capacity == ps->size){int newcapacity = ps->capacity * 2;DataType* tmp = (DataType*)realloc(ps->a, sizeof(DataType) * newcapacity);if (tmp == NULL){perror("realloc fail");return 0;}else{ps->a = tmp;ps->capacity = newcapacity;}}}void StackPush(Stack* ps,DataType x){assert(ps);CheckCapacity(ps);ps->a[ps->size++] = x;}bool StackEmpty(Stack* ps){assert(ps);return 0 == ps->size;}void StackPop(Stack* ps){if (StackEmpty(ps))return;ps->size--;}DataType StackTop(Stack* ps){assert(!StackEmpty(ps));return ps->array[ps->size - 1];}int StackSize(Stack* ps){assert(ps);return ps->size;}
从里面可以看出来其实还是很麻烦的,注意这里我输入输出依然是c++的,因为c++兼容c,所以形式是c的,但是里面混了一点c++的东西
那现在我们用C++ 去实现这个栈
#pragma once#include<iostream>#include<assert.h>using namespace std;typedef int DataType;class Stack{public:void StackInit(){Stack* tmp;tmp = (DataType*)malloc(sizeof(DataType) * 4);if (tmp == NULL){return 0;}else{_a = tmp;}_size = 0;_capacity = 0;}void StackDestroy(){free(_a);_a = NULL;_size = 0;_capacity = 0;}void StackPush(DataType x){CheckCapacity();_a[size++] = x;}bool StackEmpty(){return 0 == _size;}void StackPop(){if (StackEmpty())return;_size--;}DataType StackTop(){assert(!StackEmpty());return ps->array[_size - 1];}int StackSize(){return _size;}private:void CheckCapacity(){if (_capacity == _size){int newcapacity = _capacity * 2;DataType* tmp = (DataType*)realloc(ps->a, sizeof(DataType) * newcapacity);if (tmp == NULL){perror("realloc fail");return 0;}else{_a = tmp;_capacity = newcapacity;}}}DataType* _a;int _size;int _capacity;};
从两者的比较我们可以看出,如果我们用c++的方式去实现栈,那么就会减少大量的指针问题
C++中通过类可以将数据 以及 操作数据的方法进行完美结合,通过访问权限可以控制那些方法在 类外可以被调用,即封装 ,在使用时就像使用自己的成员一样,更符合人类对一件事物的认知。 而且每个方法不需要传递Stack*的参数了 ,编译器编译之后该参数会自动还原,即 C++ 中 Stack * 参数是编译器维护的, C 语言中需用用户自己维护 。
这里的指针都被this指针所替代,不是说没有指针,只是这个this指针是看不见的隐含指针而已
所以指针不需要我们去维护,但是在C中需要自己去维护,这样就比较麻烦而且比较容易出错
希望看到这里,可以给一波关注加收藏!! ??