在C++中,有6个默认的成员函数(即如果不写成员函数,系统就会自动调用)。
一,构造函数
构造函数是特殊的成员函数。作用是:
在创建对象时,对对象进行初始化。
其特征有:
①构造函数是成员函数,可以写在类体外,也可以写在类体内。
②函数名与
类名相同。
③不指定类型说明,
无返回值。
④实例化对象时系统自动调用。
⑤构造函数可以
重载。
⑥如果在定义类时,没有写构造函数,系统就会自动生成一个缺省的构造函数,但是如果自己写了构造函数,系统调用写的那个函数。
⑦无参的构造函数和全缺省的构造函数都是缺省的构造函数。
下面我们通过一个具体的例子来认识构造函数:
1.无参的构造函数
#include <iostream> #include<windows.h> using namespace std; class Date { public: Date() { cout<<"Date"<<endl; } void Print() { cout<<_year<<"-"<<_month<<"-"<<_day<<endl; } private: int _year; int _month; int _day; }; int main() { Date d; d.Print(); system("pause"); return 0; }
无参的构造函数,在创建对象初始化时,什么都不会做。此时,如果不写构造函数,也会调用系统自动生成的构造函数,同样什么也不会做。但是如果类中有自定义的类,那么用自己写的构造函数就会初始化。
2.有参的构造函数
#include <iostream> #include<windows.h> using namespace std; class Date { public: Date(int year,int month,int day) { _year = year; _month = month; _day = day; cout<<"Date"<<endl; } void Print() { cout<<_year<<"-"<<_month<<"-"<<_day<<endl; } private: int _year; int _month; int _day; }; int main() { Date d; d.Print(); Date d1(2018,3,25); d1.Print(); system("pause"); return 0; }
有参的构造函数,创建对象调用构造函数初始化时的值就是传的值。
3.缺省的构造函数
⑤全缺省的构造函数
#include <iostream> #include<windows.h> using namespace std; class Date { public: Date(int year = 1900,int month = 1,int day = 1) { _year = year; _month = month; _day = day; cout<<"Date"<<endl; } void Print() { cout<<_year<<"-"<<_month<<"-"<<_day<<endl; } private: int _year; int _month; int _day; }; int main() { Date d; d.Print(); Date d1(2018,3,25); d1.Print(); system("pause"); return 0; }
全缺省的构造函数,如果在创建对象时不传任何参数,那么在调用构造函数时就会调用构造函数进行初始化,并不会生成随机值。而当我们传了参数时,就会调用我们自己传的参数。所以,一般建议在写构造函数时采用全缺省的构造函数。当类中有自定义的类时,调用缺省的构造函数并不是什么都不做。
②半缺省的构造函数
#include <iostream> #include<windows.h> using namespace std; class Date { public: Date(int year = 1900,int month = 1) { _year = year; _month = month; _day = 1; cout<<"Date"<<endl; } void Print() { cout<<_year<<"-"<<_month<<"-"<<_day<<endl; } private: int _year; int _month; int _day; }; int main() { Date d; d.Print(); Date d1(2018,3); d1.Print(); system("pause"); return 0; }
半缺省的构造函数,缺省参数怎么缺省(从右向左进行缺省),我在前面的博客中有介绍。。。。。。
深入探索构造函数:
在C++中,使用初始化列表也可以初始化对象,而且比构造函数更高效。(因为初始化列表无论写不写,系统都会调用)
用法:以冒号开始,以逗号分隔,括号里面是要初始化的值。
有些变量必须在初始化列表中进行初始化:
①自定义类型的类如果没有缺省的构造函数,必须在初始化列表初始化。
②引用类型的成员变量。
③常量。
注意:初始化列表按声明的顺序进行初始化。
二,析构函数
作用:析构函数的作用刚好和构造函数的功能相反,它的作用是:在一个对象的生命周期结束时,用析构函数进行清理一些东西。析构函数的特点与构造函数很相似。
#include <iostream> #include<windows.h> using namespace std; class Date { public: Date(int year = 1900,int month = 1,int day = 1) { _year = year; _month = month; _day = day; cout<<"Date"<<endl; } ~Date() { cout<<"~Date"<<endl; } void Print() { cout<<_year<<"-"<<_month<<"-"<<_day<<endl; } private: int _year; int _month; int _day; }; int main() { Date d; d.Print(); system("pause"); return 0; }
在VS2010中运行结果中并不会看到析构函数被调用(可能是被优化了),上面的截图是在Linux中运行的结果。我们可以看到,在函数调用完成后,就会调用析构函数进行清理工作(清理一些在堆上开辟的空间,想这样普通的函数在函数执行完后就会自动释放内存,并不需要去清理)。
三,拷贝构造函数
定义:创建对象时用同类对象来进行初始化,用一个已知的对象来创造一个新的对象。
格式:
<类名>::<拷贝构造函数名>(<类名>&<引用名>)
{
<函数体>
}
特点:
①拷贝构造其实是构造函数的重载;
②拷贝构造函数在传参时必须使用引用,如果使用传值的方式,就会引发无穷递归。
③如果没有写拷贝构造函数,系统会自动生成一个缺省的拷贝构造函数。
#include <iostream> #include<windows.h> using namespace std; class Date { public: Date(int year = 1900,int month = 1,int day = 1) { _year = year; _month = month; _day = day; cout<<"Date"<<endl; } ~Date() { cout<<"~Date"<<endl; } Date(Date& d) { _year =d._year; _month =d._month; _day = d._day; cout<<"Date&"<<endl; } void Print() { cout<<_year<<"-"<<_month<<"-"<<_day<<endl; } private: int _year; int _month; int _day; }; int main() { Date d(2018,3,25); d.Print(); Date d1(d); d1.Print(); system("pause"); return 0; }当我们不写拷贝构造函数时,系统也会自动生成一个拷贝构造。而且在上面的这个事例中,系统自动生成的拷贝构造函数也可以完成同样的拷贝。缺省的拷贝构造函数,会依次拷贝成员进行初始化。
#include<iostream> #include<windows.h> using namespace std; class SeqList { public: SeqList(const size_t capacity = 0) { if(capacity > 0) { data = (int*)malloc(sizeof(int)*capacity); _size = 0; _capacity = capacity; } else { data = NULL; _size = 0; _capacity = capacity; } } ~SeqList() { if(data) free(data); _size = 0; _capacity = 0; } private: int* data; size_t _size; size_t _capacity; }; int main() { SeqList L(100); SeqList L1(L); system("pause"); return 0; }
就像这个顺序表的拷贝构造,就会存在问题。
而且,当对象的生命周期结束时,就会调用它的析构函数进行清理,但是我们使用了拷贝构造创建了L1对象,
而且两个指向同一块空间,当一块空间被清理2次时,程序就会崩溃。
四,运算符重载
作用:提高代码的可读性。
特点:函数名为:operator+合法的运算符。
C++不能重载的5个运算符:.*/::/sizeof/?:/.
注意:运算符重载也不可以改变运算符的优先性。。。在后面的日期类会有详细的例子。
五,const成员函数
用const修饰的对象,必须用const修饰的函数实例化。而非const修饰的函数,有const和非const实例化的函数都可以用。因为权限只能缩小,不能放大。
#include <iostream> #include<windows.h> using namespace std; class Date { public: Date(int year = 1900,int month = 1,int day = 1) :_year(year) ,_month(month) ,_day(day) {} void Print() { cout<<_year<<"-"<<_month<<"-"<<_day<<endl; } void Print() const //void Print(const Date* this) { cout<<_year<<"-"<<_month<<"-"<<_day<<endl; } private: int _year; int _month; int _day; }; int main() { Date d(2018,3,25); d.Print(); const Date d1(2018,4,3); d1.Print(); system("pause"); return 0; }
上面用const修饰的对象,必须用const修饰的函数去实现。
日期类的代码就充分利用了这几个成员函数,下面是我实现的日期类的函数:
#pragma once class Date { public: Date(int year = 1900 ,int month = 1 ,int day = 1 ) :_year (year) ,_month (month) ,_day (day) {} bool IsInvalid(); int GetCorrectDay(int _year,int _month); bool IsLeapYear(int _year) { return ((_year % 4 == 0 && _year % 100 != 0) || _year % 400 == 0); } void Print(); Date& 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); Date operator++(int); //后置++ Date& operator++(); //前置++(默认是) Date operator--(int); Date& operator--(); Date operator+(const int day); Date& operator+=(const int day); Date operator-(const int day); Date& operator-=(const int day); int operator-(Date& d); private: int _year; int _month; int _day; };
#include <iostream> using namespace std; #include"Date.h" bool Date::IsInvalid() { if(this->_year < 0 || this->_month < 0 || this->_month > 13 || this->_day < 0 || this ->_day > GetCorrectDay(this->_year,this->_month)) { return false; } return true; } int Date::GetCorrectDay(int year,int month) { int m_day = 0; int day[13] = {-1,31,28,31,30,31,30,31,31,30,31,30,31}; if(month == 2 && IsLeapYear(year)) { m_day = 29; } else { m_day = day[month]; } return m_day; } void Date::Print() { cout<<_year<<"-"<<_month<<"-"<<_day<<endl; } Date& Date::operator=(const Date& d) { _year = d._year ; _month = d._month ; _day = d._day ; return *this; } bool Date::operator<(const Date& d) { if(_year < d._year || (_year == d._year && _month < d._month) || (_year == d._year && _month == d._month && _day < d._day )) { return true; } return false; } bool Date::operator==(const Date& d) { if(_year == d._year && _month == d._month && _day == d._day) { return true; } return false; } bool Date::operator!=(const Date& d) { if((*this == d) == false) { return true; } return false; } bool Date::operator<=(const Date& d) { if(*this < d || *this == d) { return true; } return false; } bool Date::operator>(const Date& d) { if(!(*this < d) && !(*this == d)) { return true; } return false; } bool Date::operator>=(const Date& d) { if((*this > d) || (*this == d)) { return true; } return false; } Date Date::operator+(const int day) { if(day < 0) { return (*this - (-day)); } Date tmp(*this); tmp._day += day; while(tmp.IsInvalid() == false) { if(tmp._month > 12) { tmp. _year += 1; tmp._month = 1; } int m_day = GetCorrectDay(tmp._year,tmp._month); tmp._day -= m_day; ++tmp._month; } return tmp; } Date& Date::operator+=(const int day) { *this = *this + day; return *this; } Date Date::operator-(const int day) { if(day < 0) { return (*this + (-day)); } Date tmp(*this); tmp._day -= day; while(tmp.IsInvalid() == false ) { if(tmp._month <= 1) { tmp._month = 12; tmp._year -= 1; } int m_day = GetCorrectDay(tmp._year,tmp._month - 1); tmp._day -= m_day; --tmp._month; } return tmp; } Date& Date::operator-=(const int day) { *this = *this - day; return *this; } Date Date::operator++(int) { Date tmp(*this); *this += 1; return tmp; } Date& Date::operator++() { *this += 1; return *this; } Date Date::operator--(int) { Date tmp(*this); *this -= 1; return tmp; } Date& Date::operator--() { *this -= 1; return *this; } int Date::operator-(Date& d) { int count = 0; while(*this != d) { if(*this > d) { ++d; ++count; } else if(*this < d) { ++(*this); ++count; } } return count; } int main() { Date d(2018,3,27); Date d1(2018,3,3); cout<<(d == d1)<<endl; cout<<(d < d1)<<endl; cout<<(d <= d1)<<endl; cout<<(d > d1)<<endl; cout<<(d >= d1)<<endl; cout<<(d1 - d)<<endl; d = d + 430; d.Print(); --d; d.Print(); d++; d.Print(); d--; d.Print(); d = d + 2; d1 = d1 + 30; d.Print(); d1.Print(); return 0; }
日期类的代码都是在Linux中实现的。。。。。。。。。。。。