目录
后记:●由于作者水平有限,文章难免存在谬误之处,敬请读者斧正,俚语成篇,恳望指教! ——By 作者:新晓·故知
1.类的6个默认成员函数
如果一个类中什么成员都没有,简称为空类。空类中什么都没有吗?并不是的,任何一个类在我们不写的情况下,都会自动生成下面6个默认成员函数
class Date {};
2. 构造函数
2.1 概念
对于以下的日期类:class Date { public: void SetDate(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.SetDate(2022, 5, 1); d1.Display(); Date d2; d2.SetDate(2022, 7, 1); d2.Display(); return 0; }
对于Date类,可以通过SetDate公有的方法给对象设置内容,但是如果每次创建对象都调用该方法设置信息,未免有点麻烦,那能否在对象创建时,就将信息设置进去呢?构造函数是一个特殊的成员函数,名字与类名相同,创建类类型对象时由编译器自动调用,保证每个数据成员都有 一个合适的初始值,并且在对象的生命周期内只调用一次。
2.2 特性
构造函数是特殊的成员函数,需要注意的是,构造函数的虽然名称叫构造,但是需要注意的是构造函数的主要任务并不是开空间创建对象,而是初始化对象。其特征如下:1. 函数名与类名相同。2. 无返回值。3. 对象实例化时编译器自动调用对应的构造函数。4. 构造函数可以重载。
class Date { public: // 1.无参构造函数 Date() {} // 2.带参构造函数 Date(int year, int month, int day) { _year = year; _month = month; _day = day; } private: int _year; int _month; int _day; }; void TestDate() { Date d1; // 调用无参构造函数 Date d2(2015, 1, 1); // 调用带参的构造函数 // 注意:如果通过无参构造函数创建对象时,对象后面不用跟括号,否则就成了函数声明 // 以下代码的函数:声明了d3函数,该函数无参,返回一个日期类型的对象 Date d3(); }
5. 如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦用户显式定义编译器将不再生成。class Date { public: /* // 如果用户显式定义了构造函数,编译器将不再生成 Date (int year, int month, int day) { _year = year; _month = month; _day = day; } */ private: int _year; int _month; int _day; }; void Test() { // 没有定义构造函数,对象也可以创建成功,因此此处调用的是编译器生成的默认构造函数 Date d; }
class A { public: A() { cout << "A()" << endl; _a = 0; } private: int _a; }; class Date { public: 1.如果用户显式定义了构造函数,编译器将不再生成 //Date (int year, int month, int day) //{ //_year = year; //_month = month; //_day = day; //} //2.如果用户没有定义构造函数,编译器将生成一个默认无参构造函数 //默认生成构造函数对于内置类型成员变量不作处理,对于自定义类型成员变量才做处理 private: int _year; int _month; int _day; A _aa; }; int main() { Date d1; return 0; }
6. 无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个。注意:无参构造函数、全缺省构造函数、我们没写编译器默认生成的构造函数,都可以认为是默认成员函数。// 默认构造函数 class Date { public: Date() { _year = 1900; _month = 1; _day = 1; } Date(int year = 1900, int month = 1, int day = 1) { _year = year; _month = month; _day = day; } private: int _year; int _month; int _day; }; // 以下测试函数能通过编译吗? void Test() { Date d1; }
如果一个类中的成员全是自定义类型,我们就可以用默认生成的函数
如果有内置类型的成员,或者需要显示传参初始化,那么都要自己实现构造函数7. 关于编译器生成的默认成员函数,很多童鞋会有疑惑:在我们不实现构造函数的情况下,编译器会生成 默认的构造函数。但是看起来默认构造函数又没什么用?d对象调用了编译器生成的默认构造函数,但 是d对象year/month/_day,依旧是随机值。也就说在这里编译器生成的默认构造函数并没有什么用。解答:C++把类型分成内置类型(基本类型)和自定义类型。内置类型就是语法已经定义好的类型:如 int/char...,自定义类型就是我们使用class/struct/union自己定义的类型,看看下面的程序,就会发现 编译器生成默认的构造函数会对自定类型成员_t调用的它的默认成员函数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; return 0; }
8. 成员变量的命名风格// 我们看看这个函数,是不是很僵硬? class Date { public: Date(int year) { // 这里的year到底是成员变量,还是函数形参? year = year; } private: int year; }; // 所以我们一般都建议这样 class Date { public: Date(int year) { _year = year; } private: int _year; }; // 或者这样。 class Date { public: Date(int year) { m_year = year; } private: int m_year; }; // 其他方式也可以的,主要看公司要求。一般都是加个前缀或者后缀标识区分就行。
3.析构函数
3.1 概念
前面通过构造函数的学习,我们知道一个对象时怎么来的,那一个对象又是怎么没呢的?析构函数:与构造函数功能相反,析构函数不是完成对象的销毁,局部对象销毁工作是由编译器完成的。而对象在销毁时会自动调用析构函数,完成类的一些资源清理工作
3.2 特性
析构函数是特殊的成员函数。其特征如下:1. 析构函数名是在类名前加上字符 ~。2. 无参数无返回值。3. 一个类有且只有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。4. 对象生命周期结束时,C++编译系统系统自动调用析构函数。typedef int DataType; class SeqList { public: SeqList(int capacity = 10) { _pData = (DataType*)malloc(capacity * sizeof(DataType)); 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; };
5. 关于编译器自动生成的析构函数,是否会完成一些事情呢?下面的程序我们会看到,编译器生成的默认析构函数,对会自定类型成员调用它的析构函数。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; }
4. 拷贝构造函数
4.1 概念
那在创建对象时,可否创建一个与一个对象一某一样的新对象呢?构造函数:只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰),在用已存在的类类型对象创建新对象时由编译器自动调用
4.2 特征
拷贝构造函数也是特殊的成员函数,其 特征 如下:1. 拷贝构造函数是构造函数的一个重载形式。2. 拷贝构造函数的参数只有一个且必须使用引用传参,使用传值方式会引发无穷递归调用。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; } private: int _year; int _month; int _day; }; int main() { Date d1; Date d2(d1); return 0; }
3. 若未显示定义,系统生成默认的拷贝构造函数。 默认的拷贝构造函数对象按内存存储按字节序完成拷贝,这种拷贝我们叫做浅拷贝,或者值拷贝。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; }; int main() { Date d1; // 这里d2调用的默认拷贝构造完成拷贝,d2和d1的值也是一样的。 Date d2(d1); return 0; }
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; }; int main() { String s1("hello"); String s2(s1); }