一、构造函数:
class date
{
public:
void display()
{
cout << _year <<'-'<< _month <<'-'<< _day << endl;
}
void setdate(int year,int month,int day)
{
_year = year;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
date a1,a2,a3;
a1.setdate(1997 , 10 , 8);
a2.setdate(1997 , 11 , 8);
a3.setdate(1997 , 12 , 8);
a1.display();
a2.display();
a3.display();
return 0;
}
1.引言:我们分析一下上面的这段代码,对于date类定义的对象a1,我们需要调用setdate函数来设置内容,再用类定义一个对象,需要再次调用setdate函数,那么我们是不是可以再定义类的时候就顺便给对象设置内容,构造函数就可以实现这个功能。
2.定义:构造函数是一个特殊的成员函数,名字与类名相同,创建类类型对象由编译器自动调用,保证每个类成员都有初始值,并在对象的生命周期内只能调用一次。
class date
{
public:
date()//无参构造函数
{}
date(int year,int month,int day)//带参构造函数
{
_year = year;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
date a1;//不能加括号,否则就成了函数声明
date a2(1997, 10, 8);
getchar();
return 0;
}
3.特性:
(1)函数名与类名相同;
(2)无返回值;
(3)对象实例化时,编译器自动调用对应的构造函数(首要任务就是初始化对象);
(4)构造函数可以重载;
(5)有初始化列表;(可以不用)
(6)如果类中没有显式定义构造函数,则编译器会自动生成一个无参的默认构造函数,如果用户已经显式定义,编译器将不再生成;
(7)无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个,注意:无参的构造函数、全缺省的构造函数、用户没有显式给出编译器默认生成的构造函数,均为默认成员函数;
class date
{
public:
date()
{
_year = 1997;
_month = 10;
_day = 8;
}
date(int year = 1997, int month = 10,int day = 8)
{
_year = year;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
//date a1;编译时会报错,有多个默认构造函数,编译器不知道调用哪个
return 0;
}
(8)构造函数不能用const修饰,因为可能在函数内部对成员进行赋值。
4.初始化列表:
(1)格式及说明:以一个冒号开始,接着以一个逗号分隔的数据成员列表,每个成员变量后面跟一个放在括号中的初始值或者表达式。
class date
{
public:
date(int year, int month, int day)
:_year(year)
,_month(month)
,_day(day)
{}
private:
int _year;
int _month;
int _day;
}
int main()
{
date a1(1997,10,8);
return 0;
}
(2)每个成员变量只能再初始化列表出现一次(初始化只能初始化一次)
(3)类中包含以下成员,必须放在初始化列表位置进行初始化:
- 引用成员变量
- const成员变量
- 类类型成员(该类没有默认构造函数)
class A
{
public:
A(int a)
:_a(a)
{}
private:
int _a;
};
class B
{
public:
B(int a,int n, int m)
:_aobj(a)
, _n(n)
, _m(m)
{}
private:
A _aobj;//类类型成员(该类没有缺省的构造函数,如果不初始化,编译器不知道传什么值)
const int _n;//const类型成员变量
int& _m; //引用成员变量
};
int main()
{
B b(1, 2, 3);
return 0;
}
(4)尽量使用初始化列表初始化,因为不管你是否使用初始化列表初始化,对于自定义类型成员变量,一定会先使用初始化列表初始化。
(5)成员变量在类中的声明次序就是再初始化列表中的初始化顺序,与其在初始化列表中的顺序无关。
5.引出explicit关键字:
(1)构造函数不仅可以构造与初始化对象,对于单个参数的构造函数,还具有类型转换的作用。
class Date
{
public:
explicit Date(int year)
:_year(year)
{}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1(2018);
//d1 = 2019;//这条语句会报错
return 0;
}
编译器给出的错误信息:没有与这些操作数匹配的 “=” 运算符,操作数类型为: Date = int
二、析构函数:
使用场景:如果一个类中管理了资源,(内存、文件、套接字)析构函数一定要由用户显式给出,否则会存在内存泄漏。
1.概念:
与构造函数功能相反,析构函数不是完成对象的销毁,局部对象销毁工作是由编译器完成的。而对象在销毁时会自动调用析构函数,完成类的一些资源清理工作。
2.特性:
(1)析构函数名是在类名前加上字符~;
(2)无参数无返回值;
(3)一个类有且只有一个析构函数,如果用户未显式定义,则系统会自动生成默认的构造函数;
(4)对象生命周期结束时,编译系统自动调用析构函数;
class String
{
public:
String(const char* str = "Jack")
{
_str = (char*)malloc(strlen(str) + 1);
strcpy(_str, str);
}
~String()
{
cout << "String()" << endl;
free(_str);
}
private:
char *_str;
};
class Person
{
private:
String _name;
int _age;
};
int main()
{
Person p;
return 0;
}
说明:Person类中无析构函数,编译器需要自动生成~Person()。对于String类定义的对象,系统会自动调用用户已经显式给出析构函数(在_name生命周期结束之后)。
三、拷贝构造函数:
1.概念:
只有单个形参,该形参时对本类类型对象的引用(一般用const修饰),在用已存在的类类型对象创建新对象时由编译器自动调用。
class Date
{
public:
Date(int year , int month, int day)
{
_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(1997, 10, 8);
Date d2(d1);
return 0;
}
2.特性:
(1)拷贝构造函数是构造函数的重载形式,构造函数的性质拷贝构造函数均满足;
(2)参数传递必须使用类类型对象引用传参(必须加&),否则会出现复制构造函数(无限递归调用);
(3)若未显式定义,系统生成默认的拷贝构造函数。默认的拷贝构造函数对象按内存存储按字节序完成拷贝,这种拷贝我们叫做浅拷贝,或者值拷贝;
四、赋值运算符重载:
1.运算符重载:
(1)概念引入:C++为了增强代码的可读性,引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有其返回值类型、函数名字以及参数列表,其返回值类型和参数列表与普通函数类似;
(2)函数名字:关键字operator加上要重载的运算符;
(3)函数原型:返回值类型 operator操作符(参数列表);
2.使用规则:
(1)不能通过连接其他符号来创建新的操作符:比如operator@;
(2)重载操作符必须有一个类类型或者枚举类型的操作数;
(3)用于内置类型的操作符,其含义不能改变,例如:内置的类型+,不能改变其含义;
(4)当重载函数作为类成员函数显式给出时,形参列表的第一个参数是this指针;
class Date
{
public:
Date(int year = 1997, int month = 10, int day = 8)
{
_year = year;
_month = month;
_day = day;
}
bool operator==(const Date& d2)//第一个参数为this指针
{
return _year == d2._year
&&_month == d2._month
&&_day == d2._day;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1(1997, 10, 8);
Date d2(1997, 10, 8);
cout << (d1 == d2) << endl;
return 0;
}
(5)五个运算符不能重载:
运算符 | 作用 |
---|---|
.* | 成员对象选择符 |
:: | 作用域限定符 |
sizeof | 计算类型大小 |
?: | 条件操作符 |
. | 成员选择符 |
3.赋值运算符重载:
(1)先来看一段代码:
class Date
{
public:
Date(int year = 1997, int month = 10, int day = 8)
{
_year = year;
_month = month;
_day = day;
}
Date(const Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
Date& operator=(const Date& d)//赋值操作符重载
{
if(this != &d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
return *this;//返回引用
}
private:
int _year;
int _month;
int _day;
};
(2)赋值运算符主要有以下几点:
- 参数类型
- 返回值
- 检测是否自己给自己赋值
- 返回*this
- 一个类如果没有显式定义赋值运算符重载,编译器也会自动生成一个,完成对象按字节序的值拷贝(当然不是所有的问题都可以通过赋值重载函数来完成,有些问题需要深拷贝去解决,后续会更博)
class Date
{
public:
Date(int year = 1997, int month = 10, int day = 8)
:_year(year)
, _month(month)
, _day(day)
{}
Date(const Date& d)
:_year(d._year)
,_month(d._month)
, _day(d._day)
{}
Date& operator=(const Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
return *this;//返回引用
}
Date& operator++()//前置++
{
++_day;
return *this;
}
const Date& operator++(int)//后置++,为与前置++形成重载
{
Date d(*this);//用当前对象拷贝构造d
++_day;
return d;//返回d
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1;
Date d2(1, 2, 3);
Date d3;
d3 = d2++;
d3 = ++d1;
return 0;
}
五、取地址及const取地址操作符重载和const成员:
1.取地址操作符重载和const取地址操作符重载:
class Date
{
public:
Date(int year = 1997, int month = 10, int day = 8)
:_year(year)
,_month(month)
,_day(day)
{}
Date(const Date& d)
:_year(d._year)
,_month(d._month)
,_day(d._day)
{}
Date& operator=(const Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
return *this;//返回引用
}
Date* operator&()//取地址操作符重载,this指针为 Date* const this(指向不能改变)
{
return this;
}
const Date* operator&()const //const修饰的取地址操作符重载,this指针类型为
//const Date* const this(除了指向不能改变,指针指向的内容也不能改变)
{
return this;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1;
const Date d2(1997, 10, 8);
cout << &d1 << endl;//调用取地址操作符重载
cout << &d2 << endl;//调用const修饰的取地址操作符重载
return 0;
}
2.const修饰类的成员函数:
将const修饰的成员函数称为const成员函数,const修饰成员函数,实际修饰的是该成员函数的this指针,表明在该成员函数内部不能对类的成员进行修改。
3.特征:
(1)const修饰的成员函数可与普通函数形成重载;
(2)若要对成员变量进行修改,需要加mutable关键字;
(3)const对象可以调用const成员函数(普通函数可能修改对象中的成员变量);
(4)非const对象可以调用const成员函数(若有普通函数,优先考虑);
(5)const成员函数不可以调用其他非const成员函数;
(6)非const成员函数可以调用const成员函数;