目录
一、默认成员函数:
一个类中,里面什么都不写的话就叫做空类,但在其中并不是什么都没有的,编译器会自动生成6个默认成员函数。
默认成员函数:用户没有显式实现,编译器会生成的成员函数称为默认成员函数。
二、构造函数:
1、定义:
构造函数是一个特殊的成员函数,它的名字必须和类名相同,创建类类型对象时,由编译器自动调用,,以保证每个数据成员都有 一个合适的初始值,并且在对象整个生命周期内只调用一次。
这是每次创建一个实例时,这个函数都会调用一次。
2、理解:
1、函数名与类名相同。
2、无返回值(也不用写void)。
3、对象实例化的时候,编译器自动调用对应的构造函数。
接下来用一串代码理解上述3条特征。
cpp
class Date
{
public:
Date()
{
_year = 2;
_month = 2;
_day = 2;
}
void Print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1;
d1.Print();
return 0;
}
上述代码中,Date()这个就是一个构造函数,和类名相同,也不用写任何返回值,可以看到运行后的结果是已经调用构造函数Date后的值
4、函数构造可以进行重载。注意:无参构造函数的调用后面不加(),这样和函数的声明会产生歧义。
cpp
class Date
{
public:
Date()
{
cout << "无参数构造函数的调用" << endl;
_year = 2;
_month = 2;
_day = 2;
}
Date(int year, int month, int day)
{
cout << "有三个参数构造函数的调用" << endl;
_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.Print();
Date d2(2024,7,4);//调用带参数的构造函数
d2.Print();
return 0;
}
5、如果类中没有显式定义构造函数,那么C++编译器会自动生成一个无参的默认构造函数,一旦用户显式定义编译器将不再生成。
**6、**首先要知道:C++把类型分为了两种:
a、内置类型(基本类型):int/char/double...
b、自定义类型:struct/class/union...
如果我们不写构造函数,那么编译器就会默认生成构造函数,此时这个默认构造函数会对内置类型不做任何处理(虽然有些编译器会进行处理,但那是个性化的,不是所有的编译器都会处理),自定义类型就会去调用它的默认构造。
cpp
class Date
{
public:
Date()
{
cout << "无参数构造函数的调用by Date" << endl;
_year = 2;
_month = 2;
_day = 2;
}
Date(int year, int month, int day)
{
cout << "有三个参数构造函数的调用by Date" << endl;
_year = year;
_month = month;
_day = day;
}
void Print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
class Stu
{
private:
char name[20];
int age;
char id[15];
Date d1;
};
int main()
{
Stu s1;
return 0;
}
如上所示:
实例化Stu这个类,但是发现没有写构造函数,就会默认生成个构造函数,那些内置类型不做处理,自定义类型d1就会调用它的默认构造(Date())。
如下图都是内置类型,就不会做任何处理。
7、无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个
理解:不传参就可以调用的是默认构造函数
注意:无参构造函数、全缺省构造函数、我们没写编译器默认生成的构造函数,都可以认为
是默认构造函数。
所以:
1、一般情况下,有内置类型成员,就需要自己写构造函数,不能用编译器自己生成。
2、如果全部都是自定义类型的成员,那么就可以考虑使用编译器自己生成的构造函数。
ps:
在C++11中,在成员变量声明的时候可以给缺省值。
如上所示:
在Date这个类中,可以给默认缺省值,给编译器生成默认构造函数用。
注意:
C++11以后才支持的,这里是进行声明(因为没有创建空间),不是初始化!!!
对于构造函数:一般情况下都需要自己写,
但是,下面两种情况可以不用自己写:
a、内置成员都有缺省值,且初始化符合我们的要求。
b、全是自定义类型的构造,且这些类型都定义默认构造。
三、析构函数:
1、定义:
析构函数与构造函数功能相反,析构函数不是完成对对象本身的销毁,局部对象销毁工作是由编译器完成的。而对象销毁时会自动调用析构函数,完成对象中资源的清理工作。
2、理解:
1、析构函数名就是类名前面加上一个"~";
2、和构造函数一样没有参数返回类型 ,并且没有参数 ,所以不能够重载;
3、 一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数,在程序结束的时候自动调用;
4、自动生成的默认析构函数,对内置类型不做处理,自定义类型会调用其析构函数。
那么什么时候可以显示定义析构函数,什么时候可以不用显示定义析构函数呢:1、一般,当存在动态申请资源的时候就需要显示定义;
2、当没有动态申请资源的时候就不用显示定义;
3、当需要释放资源的成员变量都是自定义类型的时候,不用写析构,这个自定义类型就会调用它的析构函数。
cpp
#include<iostream>
using std::cout;
using std::endl;
class Stack
{
public:
Stack()
{
cout << "Stack()已经调用" << endl;
_a = (int*)malloc(sizeof(int) * 4);
if (_a == nullptr)
{
perror("stack malloc fail");
return;
}
_capacity = 4;
_top = 0;
}
Stack(int capacity)
{
cout << "Stack(int capacity)已经调用" << endl;
_a = (int*)malloc(sizeof(int) * capacity);
if (_a == nullptr)
{
perror("stack malloc fail");
return;
}
_capacity = capacity;
_top = 0;
}
~Stack()
{
cout << "~Stack()已经调用" << endl;
free(_a);
_a = nullptr;
_capacity = _top = 0;
}
private:
int* _a = nullptr;
int _top = 0;
int _capacity;
};
int main()
{
Stack s1;
Stack s2(10);
return 0;
}
如上~Stack()就是一个析构函数,当对象销毁时就会自动调用。
四、拷贝构造:
首先要了解:C++规定,传参赋值中:创建一个已有对象一模一样的新对象,内置类型直接拷贝,传值的自定义类型必须调用拷贝构造完成拷贝(深浅拷贝有关系)
1、定义:
首先要了解到,拷贝引入是在创建对象的时候,创建一个已有对象一模一样的新对象。
它只有一个形参,该形参是我要复制的对象(一般可以用const修饰),在用已存在的类类型对象创建新对象时由编译器自动调用。
2、理解:
1、拷贝构造是构造函数的一种重载形式;
2、拷贝构造函数的参数只有一个且必须是类类型对象的引用,使用传值方式编译器直接报错,因为会引发无穷递归调用。
3、当没有显示定义拷贝构造函数的时候,默认拷贝构造会对内置类型成员完成值拷贝,
对自定义类型成员会调用它的拷贝构造。
此时在日期类中,可以不用写默认拷贝拷贝构造,正常拷贝,但是有些情况,比如在栈这个类中,如果使用编译器给的默认拷贝构造,会存在问题,会拷贝出同一个指向同一空间的指针,这时如果析构的话,会对同一块空间释放两次,这时程序就会崩溃了。
针对上述为题,我们就需要自己实现拷贝构造,实现深拷贝来解决上述问题。
就要完成开辟一块空间,然后在这块空间里面,拷贝那些值。
cpp
class Date
{
public:
Date(int year = 2000, 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(2024, 7, 13);
Date d2(d1);
return 0;
}
上述就是浅拷贝的例子。
接下来,对这个栈实现深拷贝:
cpp
Stack(Stack& st)
{
_a = (int*)malloc(sizeof(int) * st._capacity);
if (_a == nullptr)
{
perror("stack malloc fail");
return;
}
memcpy(_a, st._a, sizeof(int) * st._capacity);
_top = st._top;
_capacity = st._capacity;
}
五、运算符的重载:
1、一般运算符的重载:
C++相对于C语言,增加了运算符重载。使用关键字operator后面加上要重载的运算符。
函数名:operator<
函数原型:返回值类型 operator操作符(参数列表)
注意:
1、不能通过连接其他符号来创建新的操作符:比如operator@(原来没有@这个操作符)
2、重载操作符必须有一个自定义型参数,不能都是内置类型,毕竟是比较自定义类型的,可以自定义类型和内置类型同时存在。
3、用于内置类型的运算符,其含义不能改变,例如:内置的整型+,不能改变其含义
4、作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐藏的this
5、 .* :: sizeof ?: . 注意以上5个运算符不能重载。
cpp
bool operator<(const Date& x)
{
if (_year > x._year)
{
return false;
}
else if (_year == x._year && _year > x._year)
{
return false;
}
else if (_year == x._year && _year > x._year && _day > x._day)
{
return false;
}
return true;
}
以上就是重载<运算符,_year实际上是this->_year,但是如果写在全局域中,那么就会无法访问私有成员,解决办法可以用友元,或者把operator<写成成员函数。
2、赋值运算符的重载:
1、格式:
1、参数类型:const 类名&,传递引用可以提高传参效率
2、返回值类型:类名&,返回引用可以提高返回的效率,有返回值目的是为了支持连续赋值
3、返回*this :要复合连续赋值的含义
2、注意:
赋值运算符只能重载成类的成员函数不能重载成全局函数,因为赋值运算符如果不显式实现,编译器会生成一个默认的。此时用户再在类外自己实现一个全局的赋值运算符重载,就和编译器在类中生成的默认赋值运算符重载冲突了,故赋值运算符重载只能是类的成员函数。
在这默认生成赋值重载和拷贝构造行为一样,
对内置类型成员进行值拷贝/浅拷贝,
对自定义类型成员会调用它的赋值重载。
cpp
Date& operator=(const Date& d)
{
if (this != &d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
return (*this);
}
以上就是在日期类的重载赋值符,这个与拷贝构造不一样,拷贝构造是通过一个类创造一个一样的类,而这个是将一个已经存在的类赋值给另一个已经存在的类。