C++默认成员函数
构造函数构造函数是特殊的成员函数需要注意的是构造函数虽然名称叫构造但是构造函数的主要任务并不是开空间创建对象(局部对象在栈帧创建时空间就开好了)而是对象实例化时初始化对象。构造函数的本质是要替代我们以前Stack和Date类中写的Init函数的功能构造函数自动调用的特点就完美的替代的了Init。 构造函数有如下特点 1. 函数名与类型同名 2. 可以重载 3. 没有返回值不用写void 4. 如果用户没有显式写构造函数编译器会生成一个默认的无参构造函数一旦用户显式定义编译器将不再生成。代码语言javascriptAI代码解释// 构造函数 // 1. 函数名和类名同名 2. 可以重载 3. 没有返回值 4. 用户不写编译器会默认生成无参的构造函数 class Date { public: // 无参构造 Date() { _year 1; _month 1; _day 1; } // 带参数构造 Date(size_t year, size_t month, size_t day) { _year year; _month month; _day day; } //// 全缺省构造 //Date(size_t year 1, size_t month 1, size_t day 1) //{ // _year year; // _month month; // _day day; //} // void Print() { cout _year - _month - _day endl; } private: size_t _year; size_t _month; size_t _day; }; int main() { // 调用带参数的构造 Date d1(2025,7,5); d1.Print(); //// 无参构造和全缺省构造会产生调用歧义 //Date d2; //d2.Print(); // 无参的不能这么写 会和函数声明搞混 eg: void func // 这是函数声明还是函数定义呢 /*Date d2(); d2.Print();*/ //// 如果注释掉无参的构造和全缺省构造会报错 //// C2512 没有合适的默认构造函数可用 //Date d2; //d2.Print(); // 调用无参的构造函数 Date d3; d3.Print(); return 0; }默认构造函数分为三类全缺省构造函数无参构造函数编译器默认生成的构造函数总结一下不传参的构造函数就是默认构造函数这三个函数不能同时存在 而全缺省构造函数和无参构造函数虽然构成函数重载但是调用时会产生调用歧义我们不显式写构造函数编译器默认生成的构造函数会如何处理数据代码语言javascriptAI代码解释using namespace std; class Time { public: Time() { _hour 1; _minute 1; _second 1; } private: size_t _hour; size_t _minute; size_t _second; }; class Date { public: // 不写构造函数 编译器会自动生成默认构造函数 // 对于内置类型 编译器是否处理没有明确要求 // 对于自定义类型 调用该类型的默认构造函数 void Print() { cout _year - _month - _day endl; } private: size_t _year; size_t _month; size_t _day; Time _t; }; int main() { Date d1; d1.Print(); return 0; }在这里插入图片描述观察调试结果我们可以得到如下结论 对于编译器默认生成的构造函数处理不同类型数据有不同行为对于内置类型编译器没有特别要求对于VS环境给出随机值对于自定义类型该类型会调用它默认的构造函数 如果把Time类的无参构造函数注释掉会有如下现象在这里插入图片描述Time类调用它的默认构造函数而Time类的默认构造函数是编译器生成的又是处理内置类型所以VS不做处理给出随机值 针对这个问题C11打了个补丁内置类型成员变量在声明时给缺省值用缺省值初始化代码语言javascriptAI代码解释using namespace std; class Time { public: /*Time() { _hour 1; _minute 1; _second 1; }*/ private: // C11 在声明时给缺省值 size_t _hour 1; size_t _minute 1; size_t _second 1; }; class Date { public: // 不写构造函数 编译器会自动生成默认构造函数 // 对于内置类型 编译器是否处理没有明确要求 // 对于自定义类型 调用该类型的默认构造函数 void Print() { cout _year - _month - _day endl; } private: // C11 在声明时给缺省值 size_t _year 1; size_t _month 1; size_t _day 1; Time _t; }; int main() { // 此时 Time类和Date类只有编译器默认生成的构造函数 Date d1; d1.Print(); return 0; }![[Pasted image 20250707095835.png]]总结什么时候要显式定义构造函数一般情况构造函数都要显式实现只有成员全为自定义类型的类不用显式实现3. 析构函数析构函数与构造函数功能相反析构函数不是完成对对象本身的销毁比如局部对象是存在栈帧的函数结束栈帧销毁他就释放了不需要我们管C规定对象在销毁时会自动调用析构函数完成对象中资源的清理释放工作。析构函数的功能类比我们之前Stack实现的Destroy功能而像Date没有Destroy其实就是没有资源需要释放所以严格说Date是不需要析构函数的 析构函数有如下特点 1. 函数名和类名相同在函数名前加~2. 没有返回值 3. 不能重载意味着一个类只有一个析构函数 4. 如果用户没有显式写编译器会默认生成析构函数 5. 对象的生命周期结束编译器自动调用析构函数代码语言javascriptAI代码解释class Stack { public: Stack(size_t n 4) { cout Stack(size_t n 4) 析构 endl; _arr (int*)malloc(sizeof(int) * n); if (_arr nullptr) { perror(malloc err!); return; } _capacity n; _top 0; } ~Stack() { cout ~Stack() 析构 endl; assert(_arr); free(_arr); _arr nullptr; _capacity _top 0; } private: int* _arr; int _capacity; int _top; }; int main() { Stack st1; return 0; }和构造函数一样如果我们不显式实现析构函数编译器生成的析构函数对于内置类型不做处理对于定义类型会调用它的析构函数值得一提的是是我们显式写析构函数对于自定义类型成员也会调用他的析构也就是说自定义类型成员无论什么情况都会自动调用析构函数代码语言javascriptAI代码解释class tmp { public: ~tmp() { cout ~tmp() 析构 endl; } private: int _num; }; class Stack { public: Stack(size_t n 4) { cout Stack(size_t n 4) 构造 endl; _arr (int*)malloc(sizeof(int) * n); if (_arr nullptr) { perror(malloc err!); return; } _capacity n; _top 0; } /*~Stack() { cout ~Stack() 析构 endl; assert(_arr); free(_arr); _arr nullptr; _capacity _top 0; }*/ private: int* _arr; int _capacity; int _top; tmp _t; }; int main() { Stack st1; return 0; }我们可以通过调试观察在这里插入图片描述总结什么时候需要显式实现析构函数有资源需要清理就必须写析构函数例如StackList…无资源要清理可以不写内置类型成员没有资源要清理剩下全是自定义类型可以不写 还有一个重要的点一个局部域的多个对象后定义的先析构代码语言javascriptAI代码解释设已经有A,B,C,D4个类的定义程序中A,B,C,D析构函数调用顺序为 C c; int main() { A a; B b; static D d; return 0 }类的析构函数调用一般按照构造函数调用的相反顺序进行调用但是要注意static对象的存在 因为static改变了对象的生存作用域需要等待程序结束时才会析构释放对象全局对象先于局部对象进行构造局部对象按照出现的顺序进行构造无论是否为static所以构造的顺序为c a b d析构的顺序按照构造的相反顺序析构只需注意static改变对象的生存作用域之后会放在局部对象之后进行析构因此析构顺序为B A D C4. 拷贝构造函数拷贝构造函数的第一个参数是自身类型的引用且任何额外的参数都有缺省值这样的函数叫做拷贝构造函数用于同类对象的拷贝初始化是构造函数的重载。 本文以最常规情况的拷贝构造函数展开即有且仅有一个参数类类型对象的引用拷贝构造函数有如下特点拷贝构造函数是构造函数的一个重载拷贝构造函数的第一个参数必须是类类型对象的引用使用传值方式编译器会报错会引发无穷递归调用拷贝构造函数也可以多个参数但是第⼀个参数必须是类类型对象的引用后面的参数必须有缺省值代码语言javascriptAI代码解释// 拷贝构造函数 // 构造函数的重载第一个参数必须是类类型对象的引用 // 用于同类对象的拷贝初始化 class Date { public: Date() { _year 1; _month 1; _day 1; } Date(Date d) { cout call Date(Date d) endl; _year d._year; _month d._month; _day d._day; } void Print() { cout _year - _month - _day endl; } private: size_t _year; size_t _month; size_t _day; }; int main() { Date d1; // 两种写法都可以 Date d2 d1; // d是d1的别名d3是this指针 Date d3(d1); d1.Print(); d2.Print(); d3.Print(); return 0; }再来看一段代码代码语言javascriptAI代码解释Date(Date d) { cout call Date(Date d) endl; // 如果不小心写反了会发生什么 d._year _year; d._month _month; d._day _day; }其余部分不变在这里插入图片描述初始的d1也被修改成随机值了我们进行拷贝构造提供拷贝值的对象是不能被修改的所以为了防止这样的情况发生我们做如下处理Date(const Date d)保证d的只读性代码语言javascriptAI代码解释// 拷贝构造函数 // 构造函数的重载第一个参数必须是类类型对象的引用 // 用于同类对象的拷贝初始化 class Date { public: Date() { _year 1; _month 1; _day 1; } Date(const Date d) { cout call Date(Date d) endl; _year d._year; _month d._month; _day d._day; } void Print() { cout _year - _month - _day endl; } private: size_t _year; size_t _month; size_t _day; }; int main() { Date d1; // 两种写法都可以 Date d2 d1; // d是d1的别名d3是this指针 Date d3(d1); d1.Print(); d2.Print(); d3.Print(); return 0; }C规定自定义类型对象进行拷贝行为必须调用拷贝构造所以自定义类型传值传参和传值返回都会调用拷贝构造
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2613607.html
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!