面向对象编程详解目录
- 前言
- 面向对象和面向过程
- 类和结构体
- 构造函数和析构函数
- 拷贝构造和赋值重载
- 结语
前言
好久不见,首先祝大家元宵节快乐,万家元夕宴,一路太平歌,今天执此佳节,一起来学习一下类和对象吧~
面向对象和面向过程
初学类和对象,我们不需要急切了解何为面向对象。在之后文章中会慢慢渗透。
我们只需要知道,面向对象更喜欢将一件事拆分为不同的对象即可
例如,洗衣服这件事,就可以拆分成人,洗衣机,洗衣粉,衣服这四个对象。
类和结构体
我们习惯用类(class)或结构体(struct)来定义上述 “对象”
C++中的结构体与C语言不一样,C++结构体支持在结构体内定义函数
因此在我看来结构体与类 本质上没有任何区别
,只是结构体内定义的成员默认都是公有的(public)而类内定义的成员默认都是私有的(private)
public :可以在类外访问成员,即公有
private:只能在类内访问成员,即私有
-
访问权限作用域从该访问限定符出现的位置开始直到下一个访问限定符出现时为止
-
如果后面没有访问限定符,作用域就到 } 即类结束。
至于何时实用结构体,何时实用类,给出我个人的习惯,仅供参考~
结构体(struct):只存放变量
类(class):既存放变量,也存放成员函数时
类定义了一个新的作用域,在类体外定义成员时,需要使用 ::
作用域操作符指明成员属于哪个类域。
class Enity
{
public:
void func() {};
};
void Enity::func()
{
}
应当注意,类的实例化被称为对象,类相当于一张图纸,而对象相当于按照此图纸建造的房屋。
建造房屋的过程被称为 实例化
实例化后,对象才占用实际的物理空间
每一个被实例化出来的对象,只存有自己的成员变量,不会单独存有成员函数
既然不会单独存有成员函数,那怎么知道是谁调用了这个函数呢?
this指针
C++编译器给每个“非静态的成员函数“增加了一个隐藏的指针参数,让该指针指向当前对象(函数运行时调用该函数的对象),在函数体中所有“成员变量”的操作,都是通过该指针去访问。只不过所有的操作对用户是透明的,即用户不需要来传递,编译器自动完成。
- this指针本质上是“成员函数”的形参,当对象调用成员函数时,将对象地址作为实参传递给this形参。所以对象中不存储this指针。
- this指针的类型:类类型* const,即成员函数中,不能给this指针赋值。
使用了this指针,就能够做到共享成员函数,那么类的大小只存变量的大小即可,依然要按照内存对齐的原则计算。
若类内没有成员变量,系统会给实例化的对象分配一个字节占用空间!
构造函数和析构函数
构造函数与类同名,主要完成初始化对象的功能。
析构函数在类名加~,主要是完成对象中资源清理。
注意:析构函数不销毁对象本身,仅仅释放对象资源!
class Date
{
public:
Date(int year,int month,int day)
{
_year = year;
_month = month;
_day = day;
}
~Date()
{
}
private:
int _year;
int _month;
int _day;
};
对象在初始化的时候会自动调用构造函数,在对象生命周期结束前会自动调用析构函数。
若没有手动书写构造/析构函数,编译器会自动生成默认的构造/析构函数,如果书写,编译器就不会生成!
✔️默认生成的构造/析构函数,内置类型不做处理,自定义类型会调用对应的构造和析构函数。
因此一般我们采用两种方式替代默认构造函数
✔️ 全缺省构造函数,这样即使不传参,也能按照缺省值赋值
✔️C++11允许在类中为变量设置默认值
最后,构造函数是支持重载的,而析构函数只能有一个~
拷贝构造和赋值重载
🎶拷贝构造
C++类能制造出一个对象,也支持用这个对象去赋值另外一个对象,这就叫做拷贝构造。
拷贝构造是构造函数的一种,因此如果我们不自己书写,编译器会帮助我们自动生成。
✔️内置类型进行浅拷贝,直接拷贝过来。
✔️自定义类型则调用其拷贝构造。
拷贝构造只能传对象的引用,不能传值,会引发无穷递归。
传值为什么会无穷递归?
传值,并不是直接把对象传过去,要先把对象进行一次拷贝,而对象是自定义类型,要拷贝就要调用拷贝构造,但拷贝构造需要参数,参数又要进行拷贝。。。这样就引发了无穷递归
因此拷贝构造必须要传引用!!!
Date(const Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
因为拷贝构造也是构造函数一种,如果不自己生成,系统也会自动生成
系统生成的拷贝构造虽然会进行拷贝,但浅拷贝会造成一系列问题
因此在设计内存空间的拷贝时,我们要重构拷贝构造,并且使用深拷贝。
class Stack
{
public:
Stack(int capacity=0,int size=0)
{
_capacity = capacity;
_size = size;
}
Stack(const Stack& d)
{
//实现深拷贝
int* tmp = (int*)malloc(sizeof(int) * d._capacity);
if (tmp == NULL) perror("error malloc");
memcpy(tmp, d._a, sizeof(int) * d._capacity);
_a = tmp;
}
private:
int* _a;
int _capacity;
int _size;
};
🎶赋值重载
赋值重载又叫赋值运算符,是将我们各种操作符进行重载的工具。
上述我们所讲的日期类中,我们期望能直接将两个日期进行比较,看看谁更大
date1 > date2 //即达到如下效果
但我们知道,只有内置类型可以比较,比如两个整形。
4 < 5//内置类型直接比较
使用操作符重载即可完成上述比较,下面我们来看一下运算符重载如何实现,以<为例
class Date
{
public:
Date(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
bool operator<(const Date& d)
{
//运算符重载 < 号
if (_year > d._year)
{
return false;
}
else if (_year == _year && _month > d._month)
{
return false;
}
else if (_year == _year && _month == d._month && _day > d._day)
{
return false;
}
return true;
}
private:
int _year = 0;
int _month = 0;
int _day = 0;
};
int main()
{
Date d1(2023, 2, 5);
Date d2(2023, 2, 3);
cout << (d1 < d2) << endl;
}
大部分操作符都是可以重载,但要注意有五个操作符是不能重载的:
1 . (点运算符)
2 :: (域运算符)
3 .* (点星运算符)
4 ?: (条件运算符又叫三目运算符)
5 sizeof (计算大小)
以上五个操作符是不可以重载的!
结语
那么到这里,本篇文章就到此为止了,如果你觉得有所收获,可以给我一个 点赞+关注,这是对我最大的鼓励,我们下次见~