目录
一,什么是封装
将数据和操作数据的方法进行有机结合,隐藏对象的属性和实现细节,仅对外公开接口来和对象进行交互。
封装本质上是一种管理,让用户更方便使用类。
比如,身为计算机使用者,我们不需要关系内部核心部件,主板上的线路如何布局,CPU如何设计,如何关联各种硬件等,我们只需要知道怎么通过鼠标和键盘和计算机交互即可。
计算机在设计制造的时候,在外面套上外壳,隐藏内部的各种细节,仅仅对外提供开机,鼠标以及各种插孔等,对计算机进行封装,保护内部元件,方便用户使用
二,类
2.1什么是类
C语言实现封装时,将各种数据和操作方法利用struct结构体进行封装。
一样的,c++使用class关键字实现封装,简称类,如下
class className
{
//这里是类的主体部分
};
class位定义类的关键字,className为类的名字,{}中为类的主体,并且注意类后面的分号不能省略。
2.2类的三种访问权限
访问权限作用域从该访问限定符出现的位置到下一个访问限定符出现时为止,如果后面没有访问限定符,作用域到 } 即到类结束。
2.2.1 public(公有)
public修饰的成员可以直接在类外面访问
2.2.2 private(私有)
private和protected修饰的成员不能在类外部被访问,但是可以在类内部被访问。
2.2.3 protected(保护)
与private类似,但protected的主要应用在继承中。
2.3类的四个默认成员函数(重点)
2.3.1构造函数
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;
d1.Init(2023, 12, 31);
d1.Print();
Date d2;
d1.Init(2024, 1, 1);
d1.Print();
return 0;
}
如上代码,我们定义一个Date,并且想通过Init的方法给它设置时间,但如果每次创建对象的时候都这样去调用的时候,实在太麻烦了,于是前辈们创造了一个能在创建对象的时候就把信息设置进去的方法。
构造函数:是一个特殊的成员函数,名字与类名相同,无返回值,创建对象的时候自动调用,保证每个数据成员都有一个默认的合适的初始值,并且在整个生命周期里只调用一次。
class Date
{
public:
Date()//无参构造函数
{}
Date(int year, int month, int day)//带参构造函数
{
_year = year;
_month = month;
_day = day;
}
void Print()
{
cout << _year << "-" << _month << "-" << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1;//调用无参构造函数
Date d2(2023,9,12);//调用带参构造函数
d2.Print();
return 0;
}
注意:如果给带参构造函数的的参数设定缺省值,那么就要去掉无参构造函数,因为设置缺省值后,带参构造函数就包含了无参构造函数,如果再自己设置无参构造函数,那么就会报如下错误
2.3.2析构函数
通过构造函数,我们知道了对对象是怎么来的,那么对象是怎么没的呢?
析构函数:与构造函数相反,析构函数不是完成对象本身的销毁,局部对象销毁工作由编译器完成,而对象在销毁时会自动调用析构函数,完成对象中的清理工作
class Date
{
public:
Date(int year=1970, int month = 1, int day=1)
{
_year = year;
_month = month;
_day = day;
}
~Date()//析构函数名字是类名前面加上~,构成析构函数
{
cout << "~Date" << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1(2023, 9, 12);
return 0;
}
附:对于内置类型,由编译器自动生成的析构函数即可完成清理,但如果是通过malloc或者new申请了空间的,则需要自行实现析构函数,使用free手动释放,详情见主页“深入理解类与对象”博客。
2.3.3拷贝构造函数
我们创建一个对象,如果我们又想创建一个一模一样的对象呢?
拷贝构造函数:只有单个形参,该形参是本类类型对象的引用,一般用const修饰,在用已存在的类类型对象创建新对象时由编译器自动调用。
class Date
{
public:
Date(int year=1970, int month = 1, int day=1)
{
_year = year;
_month = month;
_day = day;
}
Date(const Date& d)//拷贝构造函数
{
_year = d._year;
_month = d._month;
_day = d._day;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1(2023, 9, 12);
Date d2(d1);//此处构成拷贝
return 0;
}
编译器生成的拷贝构造函数可以完成字节序的拷贝了,但如果涉及到资源申请时,需要我们手动实现拷贝,这里涉及深浅拷贝知识,详情请见主页“深入理解类与对象”博客。
拷贝构造的调用场景有三个:
①使用已存在对象创建新对象。
②函数参数类型为类类型对象。
③函数返回值类型为类类型对象。
class Date
{
public:
Date(int year, int minute, int day)
{
cout << "Date(int,int,int):" << this << endl;
}
Date(const Date& d)
{
cout << "Date(const Date& d):" << this << endl;
}
~Date()
{
cout << "~Date():" << this << endl;
}
private:
int _year;
int _month;
int _day;
};
Date Test(Date d)
{
Date temp(d);
return temp;
}
int main()
{
Date d1(2022, 1, 13);
Test(d1);
return 0;
}
注:新版本的编译器如VS2022等,打印出来后只有三个构造和析构,是因为编译器自身的优化,省去了部分构造。 (上面的this其实是一个指针,详情请见主页“深入理解类与对象”)
2.3.4运算符重载
C++为了增强代码可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,函数名字为:关键字operator后面接需要重载的运算符符号。且有以下几点需要注意
①不能连接其他符号来创建新的操作符,列入operator@,这是不允许的。
②重载操作符必须有一个类类型参数。
③内置类型的运算符,含义不能改变,列入内置的整形+,不能改变含义。
④作为类成员重载时,其形参看起来比操作数少1,因为成员函数的第一个参数为隐藏的this(关于this指针的详解请看主页“深入理解类与对象”)
class Date
{
public:
Date(int year=1970, int month=1, int day=1)
{
_year = year;
_month = month;
_day = day;
}
bool operator==(const Date& d)
{
return this->_year == d._year
&& this->_month == d._month
&& this->_day == d._day;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1(2022, 1, 13);
Date d2(2022, 1, 13);
cout << (d1 == d2) << endl;
return 0;
}
如果d1和d2的三个值都相等,就打印1,如果不相等就打印0;
注:关于赋值运算符重载因为涉及this指针等知识,详情请看主页“深入理解类与对象
三,class和struct的区别
1,C语言struct只能定义变量,但在class中可以定义函数。
2,C语言的struct定义的变量可以直接在外面进行访问,但C++的class不一样,如果变量定义在private中,类外面不能直接访问。
3,c++也可以用struct定义类,但是在不考虑有访问限定符存在的情况下,class默认的访问限定符是private,struct是public。
四,总结
封装本质上是一种管理,让用户更方便使用类,在C++中实现封装,可以通过类将数据以及操作数据的方法进行有机结合,通过访问权限来隐藏对象内部实现细节,控制哪些方法可以在类外部直接被使用