在C++中,类与C语言中的结构体类似,类与结构体的不同之处便是在其内部多了几个成员函数还有几个访问限定符,访问限定符有public(公共)、protected(保护)、private(私有),而成员函数总的来说共包括六大类,他们便是类与结构体的不同之处,六大默认成员函数分别是构造函数,拷贝构造函数,析构函数,赋值操作符重载,取地址操作符重载和const修饰的取地址操作符重载。
构造函数 ##-
——-一种随着对象创建而自动被调用的公有成员函数,有且仅在定义对象时自动执行一次,主要是为对象作初始化。
(1)函数名与类名相同;
(2)无返回值;
(3)对象构造(对象实例化)时系统自动调用对应的构造函数;
(4)构造函数可以重载(所以在一个类体内,不一定只有一个构造函数);
(5)构造函数可以在类中定义,也可以在类外定义;
(6)如果没有给出构造函数,则C++编译系统会自动生成缺省构造函数;
(7) 无参与全缺省值构造函数都是构造函数,但他们在类体内不能同时出现.
例如:
class Date
{
public:
//无参
Date()
{}
//带参---全缺省构造函数
Date(int year = 1996, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
//带参---半缺省构造函数
Date(int year, int month=3, int day=1)
{
_year = year;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
};
作用
- 完成对象的构造&初始化:
使用参数列表对数据成员进行初始化:C++还提供了一种初始化数据成员的方式:初始化列表。初始化列表以冒号开头,后跟一系列以逗号分隔的初始化字段。例如:
class Date
{
public:
Date(int year,int month,int day)
:_year(year)
,_month(month)
,_day(day)
{}
private:
int _year;
int _month;
int _day;
};
- 每个成员在初始化列表中只能出现一次。
- 初始化列表仅用于初始化类的数据成员,并不指定这些数据成员的初始化顺序,数据成员在类中定义顺序就是在参数列表中的初始化顺序。
- 尽量避免使用成员初始化成员,成员的初始化顺序最好和成员的定义顺序保持一致,对于类类型来说,使用初始化列表少了一次调用默认构造函数的过程。
这些必须放在初始化列表中:
- 常量成员,因为常量只能初始化不能赋值。
- 引用类型,因为引用必须在定义的时候初始化,并且不能重新赋值。
- 没有默认构造函数的类类型,因为使用初始化列表可以不必调用默认构造函数来初始化,而是直接调用拷贝构造函数初始化。
2.在单参构造函数中,有类型转换的作用,可以将其接受参数转化成类类型对象。
class Date
{
public:
Date(int year)
:_year(year)
{}
private:
int _year;
int _month;
int _day;
};
void Test()
{
Date d1(2018);
//用一个整形变量给日期类型对象赋值
//实际编译器背后会用2019构造一个无名对象,最后用无名对象给d1进行赋值
d1 = 2019;
}
【explicit】用explicit修饰构造函数,将会抑制构造函数的隐式转换。即禁止单参构造函数类型的转化。
析构函数
——是特殊的成员函数,做一些对象删除前的相关清理工作。
(1)析构函数在类名前加上前缀“~”;
(2)析构函数无参无返回值;
(3)一个类有且只有一个析构函数;(构造函数不止一个);
(4)对象生命周期结束时,C++编译系统自动调用析构函数;
class Arry
{
public:
Arry(int size)
{
_ptr = (int *)malloc(size*sizeof(int));
}
//析构函数
~Arry()
{
if (_ptr)
{
free(_ptr);
_ptr = NULL;
}
}
private:
int* _ptr;
};
拷贝构造函数
———如果程序想通过已有的对象复制出新的对象,就需要调用拷贝构造函数,拷贝构造函数是只有单个形参,而且该形参是对本类类型的引用(常用const)的构造函数,拷贝构造函数是特殊的构造函数,创建对象时使用已存在的同类对象来进行初始化,当我们没提供拷贝构造函数时编译器会自动创建默认的拷贝构造函数。
- 它是构造函数的重载。
- 它的参数必须使用同类型对象的引用传递。否则会在栈空间一直递归的调用拷贝构造函数
- 如果没有显式定义,系统会自动合成一个默认的拷贝构造函数。默认的拷贝构造数会依次拷贝类的数据成员完成初始化。
Mystring(const Mystring& s)
{
_str = new char[strlen(s._str) + 1];
strcpy(_str, s._str);
}
Mystring s1;
拷贝构造函数的2种调用方式
Mystring s2(s1);
Mystring s3=s2;
但为什么拷贝构造函数传递的参数为引用呢?
使用传值传递会引发无穷递归调用,如上的调用方式,拷贝构造函数传入的参数为Mystring的一个实例s,由于是传值参数,所以会把形参s复制到实参s1又调用拷贝构造函数,所以会无穷的递归。
【使用场景】
1、对象实例化对象
Date d1(1990, 1, 1);
Date d2(d1);
2、传值方式作为函数的参数
int operator-(const Date& d)
{}
3、传值方式作为函数返回值
Date operator+(int days)
{
Date date;
return date;
}
运算符重载
为了增强程序的可读性,C++支持运算符重载。
运算符重载特征:
- operator+ 合法的运算符 构成函数名(重载<运算符的函数名:operator< )。
- 重载运算符以后,不能改变运算符的优先级/结合性/操作数个数。
5个C++不能重载的运算符: .*/::/sizeof/?:/.
赋值运算符重载
- 赋值运算符的重载是对一个已存在的对象进行拷贝赋值。
- 当程序没有显式的提供一个以本类或本类的引用为参数的赋值运算符重载函数时,编译器会自动生成一个赋值运算符重载函数。
class Date
{
public:
Date()
{}
Date(int year,int month,int day)
:_year(year)
,_month(month)
,_day(day)
{}
Date& operator=(const Date &d)//赋值运算符重载
{
if(this!=&d)
{
_year = date._year;
_month = date._month;
_day = date._day;
}
return *this;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1(2017,10,29);
Date d2;
d2 = d1;
return 0;
}
类的取地址操作符重载 及const修饰的取地址操作符重载
一般不用重新定义,编译器默认会生成。
class Date
{
public :
Date* operator &()
{
return this ;
}
const Date * operator&() const
{
return this ;
}
private :
int _year ; // 年
int _month ; // 月
int _day ; // 日
};
友元
友元可以是一个函数,该函数被称为友元函数;友元也可以是一个类,该类被称为友元类,在这种情况下,整个类及其所有成员都是友元。
友元函数
类的友元函数是定义在类外部的普通函数,但有权访问类的所有私有(private)成员和保护(protected)成员。尽管友元函数的原型有在类的定义中出现过,但是友元函数并不是成员函数。因此它不属于任何类,但需要在类的内部声明。如果要声明函数为一个类的友元,需要在类定义中该函数原型前使用关键字 friend。
例如用友元函数实现输出运算符的重载:
#include<iostream>
using namespace std;
class Date
{
friend ostream& operator<<(ostream& _cout, const Date& d);
public:
Date(int year, int month, int day)
:_year(year)
, _month(month)
, _day(day)
{}
private:
int _year;
int _month;
int _day;
};
ostream& operator<<(ostream& _cout, const Date& d)
{
_cout << d._year << "-" << d._month << "-" << d._day;
return _cout;
}
int main()
{
Date d(2018,3,1);
cout << d << endl;
system("pause");
return 0;
}
注意:
- 友元函数其声明可以放在类的私有部分,也可放在共有部分。友元函数的定义在类体外实现,不需要加类限定。
- 一个函数可以是多个类友元函数。
- 友元函数可以访问类中的私有成员和其他数据,不受类访问限定符限制。
- 友元函数在调用上同一般函数一样,不必通过对对象进行引用。
- 友元函数不能用const修饰。
友元类
友元类的所有成员函数都可以是另一个类的友元函数,都可以访问另一个类中的非公有成员。
class Date;
class Time
{
friend class Date;//声明日期类为时间类的友元类
public:
Time(int hour,int minute,int second)
:_hour(hour)
, _minute(minute)
, _second(second)
{}
private:
int _hour;
int _minute;
int _second;
};
class Date
{
public:
Date(int year=1990, int month=1, int day=3)
:_year(year)
, _month(month)
, _day(day)
{}
void SetTimeOfDate(int hour, int minute, int second)
{
//可直接访问时间类私有的成员变量
_t._hour = hour;
_t._minute = minute;
_t._second = second;
}
private:
int _year;
int _month;
int _day;
Time _t;
};
友元的优缺点:
优点:可以使定义为友元函数或者友元类的函数直接访问另一个类中的私有成员和受保护成员,提高效率,方便编程。
缺点:破坏了类的封装性,提供了安全漏洞
注意:
- 友元关系是单向的,不具有交换性
- 友元关系不能传递
- 友元关系不能继承