吐槽C++
个人感觉,在c++ 道路的学习路上,遇到很多的坎坷,现在回想起来,最关键一点就是 c++知识点繁杂很多,教科书很多知识点都没有提到. 但是在实际工作中,这些没有提到的知识点,却又经常会用到(或者看开源代码里面有)。导致很多的代码看不懂,给弱小的内心受到不小的打击。一本大而全的C++书籍,虽难部分知识点当时可能看不懂,但是如果工作遇到后会再过去查得,但是书籍中不能因为隐蔽就不提。给学生一些恐慌.
比如:复制构造函数 与 赋值函数 的区别 ------如果教科书能提到这些感念,就不会对这块知识点纠结这么久了,其实也没什么难的,为什么不讲呢? 呵呵
构造函数、析构函数、赋值函数是每个类最基本的的函数。每个类只有一个析构函数和一个赋值函数。但是有很多构造函数(一个为复制构造函数,其他为普通构造函数。对于一个类A,如果不编写上述四个函数,c++编译器将自动为A产生四个默认的函数,即:
- A(void) //默认无参数构造函数
- A(const A &a) //默认复制构造函数
- ~A(void); //默认的析构函数
- A & operator = (const A &a); //默认的赋值函数
既然能自动生成函数,为什么还需要自定义?原因之一是“默认的复制构造函数”和"默认的赋值函数“均采用”位拷贝“而非”值拷贝“
位拷贝 v.s. 值拷贝
为便于说明,以自定义String类为例,先定义类,而不去实现。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | #include <iostream> using namespace std; class String { public : String( void ); String( const String &other); ~String( void ); String & operator =( const String &other); private : char *m_data; int val; }; |
位拷贝拷贝的是地址,而值拷贝拷贝的是内容。
如果定义两个String对象a, b。当利用位拷贝时,a=b,其中的a.val=b.val;但是a.m_data=b.m_data就错了:a.m_data和b.m_data指向同一个区域。这样出现问题:
- a.m_data原来的内存区域未释放,造成内存泄露
- a.m_data和b.m_data指向同一块区域,任何一方改变,会影响到另一方
- 当对象释放时,b.m_data会释放掉两次
因此
当类中还有指针变量时,复制构造函数和赋值函数就隐含了错误。此时需要自己定义。
结论
- 有一种特别常见的情况需要自己定义复制控制函数:类具有指针哈函数。
- 赋值操作符和复制构造函数可以看成一个单元,当需要其中一个时,我们几乎也肯定需要另一个
- 三法则:如果类需要析构函数,则它也需要赋值操作符和复制构造函数
注意
- 如果没定义复制构造函数(别的不管),编译器会自动生成默认复制构造函数
- 如果定义了其他构造函数(包括复制构造函数),编译器绝不会生成默认构造函数
- 即使自己写了析构函数,编译器也会自动生成默认析构函数
因此此时如果写String s是错误的,因为定义了其他构造函数,就不会自动生成无参默认构造函数。
复制构造函数 v.s. 赋值函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 | #include <iostream> #include <cstring> using namespace std; class String { public : String( const char *str); String( const String &other); String & operator=( const String &other); ~String( void ); private : char *m_data; }; String::String( const char *str) { cout << "自定义构造函数" << endl; if (str == NULL) { m_data = new char [1]; *m_data = '\0' ; } else { int length = strlen (str); m_data = new char [length + 1]; strcpy (m_data, str); } } String::String( const String &other) { cout << "自定义拷贝构造函数" << endl; int length = strlen (other.m_data); m_data = new char [length + 1]; strcpy (m_data, other.m_data); } String & String::operator=( const String &other) { cout << "自定义赋值函数" << endl; if ( this == &other) { return * this ; } else { delete [] m_data; int length = strlen (other.m_data); m_data = new char [length + 1]; strcpy (m_data, other.m_data); return * this ; } } String::~String( void ) { cout << "自定义析构函数" << endl; delete [] m_data; } int main() { cout << "a(\"abc\")" << endl; String a( "abc" ); cout << "b(\"cde\")" << endl; String b( "cde" ); cout << " d = a" << endl; String d = a; cout << "c(b)" << endl; String c(b); cout << "c = a" << endl; c = a; cout << endl; } |
执行结果
说明几点
1. 赋值函数中,上来比较 this == &other 是很必要的,因为防止自复制,这是很危险的,因为下面有delete []m_data,如果提前把m_data给释放了,指针已成野指针,再赋值就错了
2. 赋值函数中,接着要释放掉m_data,否则就没机会了(下边又有新指向了)
3. 拷贝构造函数是对象被创建时调用,赋值函数只能被已经存在了的对象调用
注意:String a("hello"); String b("world"); 调用自定义构造函数
String c=a;调用拷贝构造函数,因为c一开始不存在,最好写成String c(a);
参考: http://www.cnblogs.com/kaituorensheng/p/3245522.html
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南