目录
类的六个默认成员函数!
任何一个类,在不写的情况下,都会生成6个默认成员函数;
class Date{};
一,构造函数
- 构造函数是一种特殊的成员函数,名字和类名相同,创建类类型对象时由编译器自动调用,保证每个数据成员都有一个合适的初始值,并且在对象的生命周期内只调用一次;
- 构造函数主要任务并不是开空间创建对象,是初始化对象;
特性:
- 函数名与类名相同;
- 无返回值;
- 对象实例化时,编译器自动调用;
- 可以重载;
- 如类没有显示定义构造函数,C++编译器会自动生成一个无参的默认构造函数,用户显示定义则编译器不在生成;
- 无参构造函数和全缺省构造函数,都称为默认构造函数,且默认构造函数只能有一个;即不传参数的构造函数,都是默认函数;推荐使用全缺省的构造函数;
- 编译器自动生成的默认构造函数偏心处理,内置类型不初始化、自定义类型调用其自身的构造函数;
注:成员变量,建议加前缀或后缀标识,与形参加以区分;
//无参和有参构成函数构成重载
class Date
{
public:
//无参构造函数,默认构造函数
Date()
{}
//带参构造函数
Date(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
void Display()
{
cout << _year << "/" << _month << "/" << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1; //实例化时调用无参构造函数
d1.Display();
Date d2(2021, 10, 23); //实例化时调用带参构造函数
d2.Display();
return 0;
}
//默认构造函数只能有一个
//调用默认构造函数时会造成歧义,不调用没关系
class Date
{
public:
//无参默认构造函数
Date()
{
_year = 1921;
_month = 7;
_day = 1;
}
//全缺省默认构造函数
Date(int year = 2021, int month = 10, int day = 23)
{
_year = year;
_month = month;
_day = day;
}
void Display()
{
cout << _year << "/" << _month << "/" << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
注:C++把类型分为内置类型和自定义类型
- 内置类型/基本类型,即语法已经定义好的类型,如int/char等;
- 自定义类型,即自己定义的类型,如struct/class/union等;
class Time
{
public:
Time()
{
cout << "Time()" << endl;
_hour = 0;
_minute = 0;
_second = 0;
}
private:
int _hour;
int _minute;
int _second;
};
class Date
{
private:
//内置类型(基本类型)
int _year;
int _month;
int _day;
//自定义类型(类)
Time _t;
};
int main()
{
Date d; //会对自定义类型_t调用默认无参函数
return 0;
}
class Date
{
public:
Date()
{
_year = 2020;
}
private:
//默认构造函数的缺省值 C++11,即实例化时调用构造函数未初始化此变量,赋此值
int _year = 0; //2020
int _month = 1; //1
int _day = 1; //1
};
局部优先原则
//局部优先原则
class Date
{
public:
Date(int year)
{
year = year;
}
private:
int year;
};
int main()
{
Date d(1); //成员变量year还是随机值,可this->year指定
return 0;
}
二,析构函数
- 与构造函数相反,析构函数不是完成对象的销毁,局部对象的销毁工作是由编译器完成的;
- 对象在销毁时,会自动调用析构函数,完成类的一些资源清理工作;
特性:
- 析构函数名是在类名前加字符 ~ ;
- 无参数,无返回值;
- 一个类有且只有一个析构函数,若未显示定义系统会自动生成默认的析构函数;
- 对象生命周期结束时,C++编译器会自动调用;
- 编译器自动生成的默认析构函数偏心处理,内置类型不处理、自定义类型调用其自身的析构函数;
class SeqList
{
public:
//构造函数
SeqList(int capacity = 0)
{
_pData = (int*)malloc(capacity * sizeof(int));
assert(_pData);
_size = 0;
_capacity = capacity;
}
//析构函数
~SeqList()
{
if (_pData)
{
free(_pData);
_pData = NULL;
_capacity = 0;
_size = 0;
}
}
private:
int* _pData;
size_t _size;
size_t _capacity;
};
class String
{
public:
//构造函数
String(const char* str = "jack")
{
_str = (char*)malloc(strlen(str) + 1);
assert(_str);
strcpy(_str, str);
cout << _str << endl;
}
//析构函数
~String()
{
cout << "~String()" << endl;
free(_str);
}
private:
char* _str;
};
class Person
{
private:
String _name;
int _age;
};
int main()
{
Person P;
return 0;
}
三,拷贝构造函数
- 拷贝构造函数,也称复制构造函数,会拷贝复制一个同类对象;
- 只有单个形参,该形参是对本类类型对象的引用(一般使用const修饰,但并不限),在用已存在的类类型对象创建新对象时由编译器自动调用;
特征:
- 拷贝构造函数是对构造函数的一个重载;
- 拷贝构造函数的参数只有一个且必须使用引用传参,使用传值方式会引发无穷递归调用;
- 若未显式定义,系统会默认生成拷贝构造函数;
- 默认的拷贝构造函数对象按内存存储字节序完成拷贝(叫做浅拷贝或值拷贝),不区分基本类型和自定义类型都会处理;
- 内置类型,类似memcpy函数;
- 自定义类型,调用其自身的拷贝构造函数;
注,如成员变量是指针类型时,在完成拷贝(默认浅拷贝)构造后,程序销毁时会崩溃(对指针清理两次),应使用深拷贝构造;
以下形式会调用拷贝构造
- 传对象值传参,按值传递时会创建参数副本;
- 以对象值返回,按值返回时会创建对象副本;
- 以对象作为参数初始化对象;
class Date
{
public:
//构造函数
Date(int year = 2021, int month = 10, int day = 23)
{
_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;
Date d2(d1); //书写形式也可为:Date d2 = d1;
return 0;
}
class Date
{
public:
//构造函数
Date(int year = 2021, int month = 10, int day = 23)
{
_year = year;
_month = month;
_day = day;
}
//不是要拷贝构造函数,使用指针也可以,但不方便
Date(Date* p)
{
_year = p->_year;
_month = p->_month;
_day = p->_day;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1;
Date d2(&d1);
return 0;
}
//无拷贝构造函数,默认生成
class Date
{
public:
//构造函数
Date(int year = 2021, int month = 10, int day = 23)
{
_year = year;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1;
Date d2(d1); //浅拷贝或值拷贝
return 0;
}
//无拷贝构造函数,默认生成
class String
{
public:
//构造函数
String(const char* str = "jack")
{
_str = (char*)malloc(strlen(str) + 1);
assert(_str);
strcpy(_str, str);
}
//析构函数
~String()
{
cout << "~String()" << endl;
free(_str);
}
private:
char* _str;
};
int main()
{
String s1;
String s2(s1);
//无拷贝构造函数,调用默认生成的拷贝构造函数,内置类型浅拷贝;
//生命周期结束时,调用析构函数,s2释放空间后,s1又接着释放,会造成重复释放,系统会报错;
//需将浅拷贝,改为深拷贝;
return 0;
}
四,赋值运算符重载
- 运算符默认是给内置类型使用的,自定义类型需运算符重载,即需自己定义实现运算符行为;
- 为了增强代码的可读性,C++引入了运算符重载,其是具有特殊函数名的函数,有返回值类型,函数名,及参数列表,其返回值类型与参数列表与普通函数类似;
- 函数原型:返回值类型 + operator + 运算符 + (参数列表) { 重载操作 }
- 函数名:operator + 运算符
注:
- 不能通过连接其他符号来创建新的操作符,如operator@;
- 重载操作符必须有一个类类型或枚举类型的操作数;
- 用于内置类型的操作符,其含义不能改变,即符号和内容含义一致;
- 作为类成员的重载函数时,其形参看起来比操作数数目少一个成员函数的操作符,有一个默认的形参this,限定为第一个形参;
- .*、::、sizeof、?:、. 以上5个运算符不能重载;
class Date
{
public:
Date(int year = 1900, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
//private:
int _year;
int _month;
int _day;
};
//引用成员变量,不可为private
bool operator == (const Date& d1, const Date& d2)
{
return d1._year == d2._year
&& d1._month == d2._month
&& d1._day == d2._day;
}
int main()
{
Date d1;
Date d2;
//正常调用方式为,operator==(d1,d2),但可读性很差,还不如写一个EqualDate函数
cout << (d1 == d2) << endl;
return 0;
}
class Date
{
public:
//构造函数
Date(int year = 1900, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
//运算符重载
//bool operator == (Date* this, const Date& d2)
bool operator == (const Date& d2)
{
return _year == d2._year
&& _month == d2._month
&& _day == d2._day;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1;
Date d2;
//类似d1.operator==(d2),即编译器翻译为d1.operator==(&d1,d2)
cout << (d1 == d2) << endl;
return 0;
}
赋值运算符重载
- 参数类型,const 类&;
- 返回值,类&;
- 检查是否自己给自己赋值;
- 返回*this;
- 一个类如没有显示定义赋值运算符重载,编译器会自动生成,完成对象按字节序的值拷贝,类似拷贝构造函数;
- 内置类型,值拷贝;
- 自定义类型,调用其自身的赋值运算重载函数;
class Date
{
public:
//构造函数,无则默认会自动生成
Date(int year = 1900, 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;
}
//赋值运算符重载,无则默认会自动生成
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;
};
int main()
{
Date d1;
Date d2(2020, 2, 2);
Date d3(2021, 3, 3);
d1 = d2 = d3;
return 0;
}
//无赋值运算符重载,默认生成
class String
{
public:
//构造函数
String(const char* str = "jack")
{
_str = (char*)malloc(strlen(str) + 1);
assert(_str);
strcpy(_str, str);
}
//析构函数
~String()
{
cout << "~String()" << endl;
free(_str);
}
private:
char* _str;
};
int main()
{
String s1("hello");
String s2("world");
s1 = s2;
//无赋值运算符重载,调用默认赋值运算符重载函数,内置类型浅拷贝;
//生命周期结束时,调用析构函数,s2释放空间后,s1又接着释放,会造成重复释放,系统会报错;
//需将浅拷贝,改为深拷贝;
return 0;
}
五,日期类的实现
各种运算符重载的实现https://gitee.com/napoleoncode/start-code/tree/master/C++/Date
class Date
{
public:
//获取某年某月的天数
int GetMonthDay(int year, int month)
{
assert(month > 0 && month < 13);
static int monthDays[13] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };
if (month == 2 && (year % 4 == 0 && year % 100 != 0) || year % 400 == 0)
return 29;
return monthDays[month];
}
//构造函数
Date(int year = 0, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
//判断合法性
if (_year < 0 || (_month <= 0 || _month >= 13) || (_day <= 0 || _day > GetMonthDay(_year, _month)))
{
cout << _year << "/" << _month << "/" << _day << "->";
cout << "非法日期" << endl;
}
}
void print()
{
cout << _year << "/" << _month << "/" << _day << endl;
}
//运算符重载函数
//日期 += 天数
Date& operator += (int day);
//日期 -= 天数
Date& operator -= (int day);
//日期 + 天数
Date operator + (int day);
//日期 - 天数
Date operator - (int day);
//前置++
Date& operator ++ ();
//后置++
Date operator ++ (int);
//前置--
Date& operator -- ();
//后置--
Date operator -- (int);
//日期 - 日期 (返回天数)
int operator - (const Date& d);
// > 运算符重载
bool operator > (const Date& d);
// >= 运算符重载
bool operator >= (const Date& d);
// < 运算符重载
bool operator < (const Date& d);
// <= 运算符重载
bool operator <= (const Date& d);
// == 运算符重载
bool operator == (const Date& d);
// != 运算符重载
bool operator != (const Date& d);
private:
int _year;
int _month;
int _day;
};
六,const成员
const 修饰类的成员函数
- 将const修饰的类成员函数称为const成员函数;
- 其实际修饰的是该成员函数隐含的this指针;
- 表明在该成员函数中不能对类的成员进行修改;
注:
- const成员函数,const对象可调用,非const对象也可调用;
- 只针对只读型的成员函数,对修改型的成员函数不可加,如operator += ()等;
class Date
{
public:
//构造函数
Date(int year = 1900, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
//Display(Date* this)
void Display()
{
cout << _year << "/" << _month << "/" << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
const Date d(2020, 1, 1);
//d.Display(&d)
d.Display(); //报错,因为权限放大,void Display()const函数加const即可
}
//日期类,const修饰的成员函数
class Date
{
public:
void Display()const
{
cout << _year << "/" << _month << "/" << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
//编译器翻译为
class Date
{
public:
void Display(const Date* this)
{
cout << this->_year << "/" << this->_month << "/" << this->_day << endl;
}
private:
int _year;
int _month;
int _day;
};
七,取地址及const取地址操作符重载
- 这两个默认成员函数,一般不用重新定义,编译器会自动生成;
- 只有特殊情况,才需要重载,如想获取到指定内容;
class Date
{
public:
//取地址操作符重载
Date* operator&()
{
return this;
}
//const取地址操作符重载
const Date* operator&() const
{
return this;
}
private:
int _year;
int _month;
int _day;
};
附
- 在传值传参或传值做返回值的过程中,在一个表达式调用连续步骤中,构造、拷贝构造会被编译器优化合并;
class Date
{
public:
//构造函数
Date()
{
cout << "Date()" << endl;
}
//拷贝构造函数
Date(const Date& d)
{
cout << "Date(Date& d)" << endl;
}
//赋值运算符重载
Date& operator=(const Date& d)
{
cout << "operator=" << endl;
return *this;
}
};
Date f()
{
Date d;
return d;
}
int main()
{
//d拷贝构造给临时变量,临时变量在拷贝构造给ret1,两次拷贝构造
//但编译器会优化,直接将d拷贝给ret1,一次拷贝构造
// d -> tmp -> ret1 => d -> ret1
Date ret1 = f();
Date ret2;
//d拷贝构造给临时变量,临时变量在赋值重载给ret2,1次拷贝构造
// d -> tmp = ret2
ret2 = f();
return 0;
}
Date f(Date d)
{
return d;
}
int main()
{
Date ret;
//两个拷贝构造,Date d = ret,Date tmp = d;
f(ret);
cout << endl;
//Date()是匿名对象,生命周期只在这一行
//Date()先构造,在拷贝构造给d,但编译器优化为只有一个构造
f(Date());
return 0;
}