一、类和对象的思想
面对对象的特点:封装、继承、多态
1、类和对象的概念
c++是一门面向对象的语言,理解c++首先要理解类和对象这一重要概念。
我们知道c++是在c语言的基础上经过前人大佬的改造而来,既cplusplus,意为c的升级版,因此c++中的许多重要概念也离不开c语言,而类和对象可以看做是c语言结构体的升级版,结构体是一种自定义类型,可以包含若干个若干种成员变量。
自定义类前用class来修饰。
以下是关于类的几点说明:
1、类的定义后面有一个分号,意味着类定义的结束不可省略。
2、一个类可以有多个对象,每个对象对应一个变量。
3、类是结构体演变而来,因此在计算内存大小时和结构体一样,需要进行内存对齐。
4、类包括成员函数和成员变量,成员变量的作用范围和访问权限由类的定义决定,而普通函数是独立的,作用范围是全局或者某个命名空间。
在结构体struct中:内部默认的权限是public(公有)的,结构体外部可以访问成员变量和成员函数。
在类class中:内部默认的权限是private(私有)的,在类的外部不可以直接访问内部成员。
2、类的访问权限符
public( 公共的):公有属性,在public下声明的变量或者函数都是可以在类外部直接访问的。
protected(保护的):保护属性,在protectd下面声明的成员只能在类的内部或者其派生类(子类)中访问。
private(私有的):私有属性,在private下声明的成员只能在类的内部访问。
由上图可知我们在当我们将变量a的访问权限设置为私有时,我们在类的外部对其进行赋值时被编译器阻拦了。当我们将变量a的访问权限设置为public时就可以了。
小结
类相当于创建一个作用域。
用类创建对象的过程,称为类的实例化。类是对对象进行描述的,定义一个类并没有实际的内存空间去存储它,在类实例化对象时才会分配实际的内存。
从视觉上看每一个实例化中的对象中都不仅有成员变量又有成员函数,但是实际上同一个类的所有的成员函数都存在了同一个空间,无论有多少个实例化的对象都对应同一个成员函数,因此在计算对象的内存大小及内存都对齐时只考虑成员函数。不同编译器的默认对齐数都不同。
二、this指针
在c++标准中,引用了this指针这一概念,让this指针指向当前的对象,在函数调用时通过this指针来访问成员变量,只不过在代码实现中编译器自动完成this指针的使用,将this指针引用给省略了,因此我们在函数调用时只需要写成员变量的名称就可以了。
1、this指针的特性
1、this指针属于类类型的指针且属于const*,不能够对其进行修改,只能在成员函数内部使用,内部函数不能对this指针进行赋值。
2、在成员函数调用参数时this指针作为第一个隐含的形参,是本质对象调用函数时,将对象的地址作为实参传给this指针,所以在对象中不存储this指针。
3、this指针存放在栈上,不存在对象中,是对象的成员函数在调用时以对象的地址为实参默认传递的一个指针。
4、this指针可以为空指针,注意:当this为空时,我们的成员函数不能调用成员变量,否则程序会崩溃。
三、默认成员函数
一个类中有六个默认的成员函数,是c++标准中规定的来保证类的基本功能的函数,如果用户自己没有编写那么编译器会自己生成。一旦用户显式定义编写一个则编译器不在生成。
1、构造函数
构造函数是特殊的成员函数,名字与类名相同,完成对象的初始化工作。创建对象时由编译器自动调用以保证成员变量有一个合适的初始值。在对象的生命周期只调用一次。
构造函数的特性
1、构造函数名与类名相同
2、构造函数无返回值。
3、支持函数重载。
4、无参构造函数和全缺省构造函数以及系统自动生成的构造函数统称为默认构造函数,默认的构造函数只能有一个。
在构造函数函数体内,并不是对对象实例化,而是对成员变量进行赋值。
以时间类对象为例,我们自己不写构造函数,调用系统默认生成的构造函数时,当我们调用printf函数来打印对象中的成员变量时我们可以发现成员变量的值并不是一个规则的具体的值而是一个随机值,由此可知默认构造函数并不是开空间进行创建对象而是初始化对象。
因此建议大家在创建类和对象时,自己写一个给默认值的全缺省构造函数,这将会给我们写c++带来很大的便利。如下图。
2、析构函数
析构函数概念:
析构函数与构造函数相反,析构函数不是对对象本身的销毁,局部对象的销毁是由编译器自身完成的,在程序结束时自动释放,而对象在销毁时会自动调用析构函数,完成对象内的资源的释放。
析构函数特性:
1、函数名是类名前加上一个字符“~”。
2、析构函数无参无返回值
3、一个函数只能有一个析构函数,若无显式定义,系统会自动生成一个析构函数,系统默认生成的析构函数中什么都没有。注意:
析构函数不支持重载。
4、对象生命周期结束时,c++系统自动调用析构函数。
对于类中嵌套其他类资源类似stack的时候,必须自己写一个析构函数来对对象进行资源的释放,避免资源泄露,其余情况可不写。
3、拷贝构造函数
拷贝构造函数概念:
拷贝函数只有一个参数,该参数是对本类类型对象的引用。在用已存在的类创建对象创建新对象式由编译器自动调用
拷贝构造函数特性:
1、拷贝构造函数是构造函数的一个重载形式。
2、拷贝构造函数的参数只有一个且必须是类类型对象的引用,使用传值方式编译器直接报错,因为会引发无穷递归调用。
3、若未显式定义,编译器会生成默认的拷贝构造函数。默认的拷贝构造函数对象按内存存储按字节序完成拷贝,这种拷贝叫做浅拷贝,或者值拷贝。
拷贝函数的经典使用场景:
1、使用已存在对象创建新对象。
2、函数参数类型为类类型对象。
3、函数返回值类型为类类型对象
浅拷贝或者说值拷贝其实是有风险的,例如如果对象中有指针类型是,如果用系统生成的拷贝构造函数时,两个对象进行拷贝时,两个对象的指针指向同一片空间,当一个对象释放时,那么另一个对象就指向了一片已经被系统回收的空间,就会出现野指针,可能会造成内存泄漏。
所以类中如果没有涉及资源申请时,拷贝构造函数是否写都可以;一旦涉及到资源申请时,则拷贝构造函数是一定要写的,否则就是浅拷贝。为了提高程序效率,一般对象传参时,尽量使用引用类型,返回时根据实际场景,能用引用尽量使用引用。
4、赋值运算符重载
1、运算符重载
c++为了增强代码的可读性加入了运算符重载,运算符重载是具有特殊函数名的函数,也具有其返回值的类型,函数名字以及参数列表,其返回值类型与普通函数类似。
函数名字:返回值类型 operator 后面接需要重载的运算符号
函数原型:返回值类型 operator操作符(参数)
注意:
1、不能通过连接其他符号来创建新的操作符 :比如 operator@
2、重载操作符必须有一个类类型的参数
3、用于内置类型的运算符,其含义不能改变,例如内置的整型+,不能改变其含义。
4、作为类成员函数时,其形参看起来比操作数数目少一,因为成员函数的第一个函数为隐藏的this指针。
注意:.* :: sizeof ?: . 这五个操作符不能重载,这个在笔试中经常出现。
2、赋值运算符重载
1、赋值运算符重载格式
*参数类型:const&传递调用可以提高传参效率
*返回值类型:T&,返回引用可以提高返回的效率,有返回值的目的是为了支持连续赋值
*检测是否自己给自己赋值
*返回*this:要复合连续赋值的含义
class Date
{
public:
int GetMonthDay(int year, int month)
{
int m[13] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };
if ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0))
{
if (month == 2)
{
return 29;
}
else
return m[month];
}
else
return m[month];
}
// 全缺省的构造函数
Date(int year = 1900, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
// 拷贝构造函数
// d2(d1)
Date(const Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
// 赋值运算符重载
// d2 = d3 -> d2.operator=(&d2, d3)
Date& operator=(const Date& d)
{
_day = d._day;
_month = d._month;
_year = d._year;
}
// 析构函数
~Date()
{
cout << "~Date()" << endl;
}
void Print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
注意:编译器实现的赋值运算函数只能实现字节序的值拷贝。如果没有涉及资源管理,赋值运算符是否自行实现都可,一旦涉及资源管理必须自行实现,不然可能造成资源泄露。
3、前置++和后置++重载
由于前置++和后置++都是单目操作符,为了在重载时做区分。
c++规定:,后置++重载时参数多加一个int类型,但实际上不用传递,编译器会自己传
1、前置++是先++再使用所以可以直接对对象++,然后返回this指针所指向的内容即可所以前置++的返回值是引用
2、后置++是先使用再++所以再操作时要先把this指针的值保存在一个临时变量中,然后再对this++,由于临时变量出了函数作用域就被销毁了,所以返回值只能是值传递,所以返回类型是类类型。
前置++实现:
后置++实现:
前置++和后置++使用
6、日期类函数实现
#include<iostream>
using namespace std;
bool IsLeapYear(int y)
{
if ((y % 4 == 0 && y % 100 != 0) || y % 400 == 0)
return 1;
else
return 0;
}
class Date
{
public:
int GetMonthDay(int year, int month)
{
int m[13] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };
if ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0))
{
if (month == 2)
{
return 29;
}
else
return m[month];
}
else
return m[month];
}
// 全缺省的构造函数
Date(int year = 1900, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
// 拷贝构造函数
// d2(d1)
Date(const Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
// 赋值运算符重载
// d2 = d3 -> d2.operator=(&d2, d3)
Date& operator=(const Date& d)
{
_day = d._day;
_month = d._month;
_year = d._year;
}
// 析构函数
~Date()
{
cout << "~Date()" << endl;
}
// 日期+=天数
Date& operator+=(int day)
{
if (day < 0)
{
*this -= -day;
}
else
{
_day += day;
while (_day > GetMonthDay(_year, _month))
{
_day -= GetMonthDay(_year, _month);
_month++;
while(_month > 12)
{
_year++;
_month -=12 ;
}
}
}
return *this;
}
// 日期+天数
Date operator+(int day)
{
Date tem = *this;
tem += day;
return tem;
}
// 日期-天数
Date operator-(int day)
{
Date tem = *this;
tem -= day;
return tem;
}
// 日期-=天数
Date& operator-=(int day)
{
if (day < 0)
*this += -day;
else
{
_day -= day;
while (_day <= 0)
{
if (_month == 1)
{
--_year;
_month = 12;
_day += GetMonthDay(_year, _month);
}
else
{
--_month;
_day += GetMonthDay(_year, _month);
}
}
}
return *this;
}
// 前置++
Date& operator++()
{
return *this += 1;
}
// 后置++
Date operator++(int)
{
Date tem(*this);
*this += 1;
return tem;
}
// 后置--
Date operator--(int)
{
Date tem(*this);
*this -= 1;
return tem;
}
// 前置--
Date& operator--()
{
return *this -= 1;
}
// >运算符重载
bool operator>(const Date& d)
{
if (_year > d._year)
return true;
else if (_year == d._year)
{
if (_month > d._month)
return true;
else if (_month == d._month)
{
if (_day > d._day)
return true;
else
return false;
}
else return false;
}
else
return false;
}
// ==运算符重载
bool operator==(const Date& d)
{
return _year == d._year &&
_month == d._month &&
_day == d._day;
}
bool operator >= (const Date& d)
{
if (_year > d._year)
return true;
else if (_year == d._year)
{
if (_month > d._month)
return true;
else if (_month == d._month)
{
if (_day >= d._day)
return true;
else
return false;
}
else
return false;
}
else
return false;
}
// <运算符重载
bool operator < (const Date& d)
{
if (_year < d._year)
return true;
else if (_year == d._year)
{
if (_month < d._month)
return true;
else if (_month == d._month)
{
if (_day < d._day)
return true;
else
return false;
}
else
return false;
}
else
return false;
}
// <=运算符重载
bool operator <= (const Date& d)
{
if (_year < d._year)
return true;
else if (_year == d._year)
{
if (_month < d._month)
return true;
else if (_month == d._month)
{
if (_day <= d._day)
return true;
else
return false;
}
else
return false;
}
else
return false;
}
// !=运算符重载
bool operator != (const Date& d)
{
return _year != d._year || _month != d._month || _day != d._day;
}
// 日期-日期 返回天数
int operator-(const Date& d)
{
int dev = 0;
if(_year>d._year)
for (int i = d._year; i < _year; i++)
{
dev += IsLeapYear(i) ? 366 : 365;
}
else
for (int i = _year; i <d. _year; i++)
{
dev += IsLeapYear(i) ? 366 : 365;
}
if (_month > d._month)
for (int i = d._month; i < _month; i++)
{
dev += GetMonthDay(d._year, i);
}
else
for (int i = d._month; i < _month; i++)
dev += GetMonthDay(d._year, i);
return dev;
}
void Print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1(2024,2,26);
Date d2(2023, 1, 27);
Date d3(d2);
d3.Print();
cout << (d1 - d2) << endl;
d2 += 5;
d2.Print();
return 0;
}
7、const成员
将const修饰的成员函数称之为const成员函数,const修饰类成员函数,实际修饰成员函数隐含的this指针,表明在该成员函数中不能对类的任何变量有任何的改变。
8、取地址及const取地址操作符重载
这两个默认成员函数一般不用重新定义,编译器会自动生动生成
这两个运算符一般不需要重载,使用编译器生成的默认取地址重载即可,只有特殊情况才需要重载,比如想要别人获取到指定的内容。