C++入门浅谈之类和对象
作者:SuchABigBug
一、面向过程vs面向对象
C语言面向过程,关注的是过程,分析出求解问题的步骤,通过函数调用逐步解决问题
C++是基于面向对象,关注的是对象,将一件事情拆分成不同的对象,靠对象之间的交互完成,C++不是纯面向对象的语言,C++既有面向过程,也有面向对象可以混合编程。C语言面向过程,数据和方法是分离的。CPP面向对象,数据和方法是封装在一起的,如struct里即可用存数据也可以调用方法(函数)
struct ListNodeCPP{ int val; ListNodeCPP* next; }; int main(){ //struct ListNodeCPP n1; 就不需要这样调用了 ListNodeCPP n1; }
所以这里的struct已经不仅仅是结构体,在cpp中已经升级成了类名
二、类的限定符及封装
类包括成员变量和成员函数
下图为类的访问限定符及封装
Warning:
在class中,如果不写访问限定符默认是私有
在struct中,如果不写访问限定符默认是公有
class类中的private里的成员变量前面都会加个下划线因为在函数复制时更加容易区分
面向对象的三大特性:封装、继承、多态
本节只介绍封装,封装是将数据和操作数据进行有机结合,隐藏对象的属性和实现细节,仅对外公开接口来和对象进行交互
封装本质上是一种管理,类似兵马俑,如果什么都不管兵马俑就会被随意破坏那么我们首先建一座房子把兵马俑给封装起来。
但是我们不能全部封装起来,所以开放了搜票通道,买了门票的可以进去参观。类也是一样,我们使用类数据和方法都封装一下。
不想给别人看的,我们使用proteced/private把成员封装起来。开放一些共有的成员函数对成员合理的访问。所以封装本质上是一种管理
Warning:在类中private域声明的变量仅仅是声明,但是没有开空间,我们只有在实例化的时候才算是定义,public域下亦是如此。
总结就是定义是开空间,在实例化后才是定义,class里只是声明
三、类的实例化
用类类型创建对象的过程,称为类的实例化
1.类限定了有哪些成员,定义出的一个类并没有分实际的内存空间来存储它
2.一个类可以实例化多个对象,实例化出的对象占用实际的物理空间,存储类成员变量
3.类实例化出对象就像现实中造车,类是车的设计图,只负责设计需要什么东西,但并没有实体,同样类也只是个设计,实例化出的对象才能实际存储数据,占用物理空间
上图这个只算class里变量的大小,因此只保存成员变量,成员函数存放在公共的代码段
那么定义一个空类呢?
class Cpp{}
那么sizeof的大小为1 , 而不是0,因为如果一个类没有成员,那么他的对象需要给1byte进行占位
标识对象存在,这1个byte不存储有效数据
四、this指针
C++编译器给每个非静态成员函数增加了一个隐藏的指针参数,让该指针指向当前对象(函数运行时调用该函数的对象),在函数体中,所以成员变量的操作都是通过该指针去访问
下面的代码为编译器处理成员函数的this指针,也可以不写那么编译器会自动传隐含的this指针
class Date{ public: void date(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; };
this指针特性:
1.this指针的类型:类类型* const
2.只能在成员函数的内部使用
3.this指针本质上是一个成员函数的形参,是对象调用成员函数时,将对象地址作为实参传递给this形参。所以对象中不存储this指针
4.this指针是成员函数第一个隐含的指针形参,一般情况由编译器通过ecx寄存器自动传第,不需要用户传递
五、默认成员函数
上图为默认的成员函数,那么如果定义了一个class而里面什么都不写的话,仍会自动生成上面6个默认成员函数
1. 构造函数
我们首先要介绍的就是构造函数,因为一般在C语言中一般会先进行初始化,在C++中一般写在类里,这样我们在实例化的过程中就可以省去很多时间,而且避免了忘记初始化的风险。
构造函数特征:
1.函数名与类名相同
2.无返回值
3.对象实例化时编译器自动调用对应的构造函数
4.构造函数可以重载
所以构造函数实际上能够做到我们对象一定义出来就初始化了
class Date{ public: Date(){ _year = 0; _month = 0; _day = 0; } //带参构造函数 Date(int year, int month, int day){ _year = year; _month = month; _day = day; } //全缺省,注意默认构造函数只能有一个,所以只能选一种我们推荐用全缺省方式的构造函数 Date(int year=2022, int month = 2, int day = 20){ _year = year; _month = month; _day = day; } void date(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 day1; //注意这种是调用无参构造函数 Date day2(2021,10,19); //带参构造函数 Date day3(); //函数声明,返回一个日期类型的对象 }
Warning:
如果用户显示定义了构造函数,编译器将不再生成内置类型不会初始化 ,如int/char/double/指针等自定义类型会调用他的构造函数初始化,如struct/class/union
2. 析构函数
对象在销毁时会自动调用析构函数,完成类的一些资源清理工作
析构特征:
1.在类名的前面加上字符: ~
2.无参数无返回值
3.一个类有且只有一个析构函数。如果没有定义系统将自动生成默认的
4.对象生命周期结束时,C++编译系统自动调用析构函数
class SeqList { public : SeqList (int capacity = 10) { _pData = (DataType*)malloc(capacity * sizeof(DataType)); assert(_pData); _size = 0; _capacity = capacity; } ~SeqList() { if (_pData) { free(_pData ); // 释放堆上的空间 _pData = NULL; // 将指针置为空 _capacity = 0; _size = 0; } } private : int* _pData ; size_t _size; size_t _capacity; };
3. 拷贝函数
顾名思义就是拷贝一个一摸一样的对象
特征:
1.是构造函数的一个重载形式
2.参数只有一个且必须使用引用传参,使用传值方式会引发无穷递归调用
下面就会引发无穷递归,解决办法就是在Date类型后面加一个引用,这里就变成了用别名区分
3. 如果未定义,系统生成默认的 拷贝构造函数。默认的拷贝构造函数对象按内存存储按字节序完成拷贝,这种拷贝成为浅拷贝
4. 赋值运算符重载
我们先了解一个概念叫运算符重载,具有其返回值类型,函数名以及参数列表
关键字:operator后面接需要重载的运算符符号
Warning:
1.作为类成员的重载函数时,其形参看起来比操作数数目少一个成员函数,实际上并不是这样子的,操作符有一个默认形参this,限定为第一个形参
2.以下符号不能重载:.* , :: . sizeof , ?: . .
3.我们定义operation一般在public里面而不是全局,因为如果我们修改不了private里面的成员变量,就保证不了封装性
特征:
1. 参数类型
2.返回值
3.检查是否自己给自己赋值
4. 返回*this
5. 一个类如果没有显示定义赋值运算符重载,编译器也会生成一个,完成对象按字节序的值拷贝
下面就简单实现了日期增加N天的运算符赋值
//Date day(2021,10,10); //day += 20; Date& Date::operator+=(int day){ if(day<0){ return *this -= -day; }else{ _day += day; //如果日期不合法,进位 while(_day > GetMonthDay(_year, _month)){ _day -= GetMonthDay(_year, _month); ++_month; if(_month == 13){ ++_year; _month = 1; } } } return *this; }
总结
本篇文章就到这里了,希望能够给你带来帮助,也希望您能够多多关注脚本之家的更多内容!