这篇文章讲述的是C++提供的一些由编译器自动生成的函数,而这些函数,对你来说,也许是不可知的。在编程的世界中,没有比不可知更让人害怕了。
C++自动提供了以下成员函数:
1.默认构造函数:如果没有定义构造函数
2.复制构造函数:如果没有定义
3.赋值操作符:如果没有定义
4.默认析构函数:如果没有定义
5.地址操作符:如果没有定义
其中,最让人心慌的是复制构造函数。下面我们一个一个来说明:
1.默认构造函数
如果没有定义构造函数,编译器将提供ClassName::ClassName(){ }这样的空的构造函数。这样的函数也可以显式的定义。需要注意的是所有参数都有默认值的构造函数也是默认构造函数。而默认构造函数只能有一个,也就是说不能出现:
ClassName::ClassName(){ }
ClassName::ClassName(int n=0){ }
这样的定义。
2.复制构造函数
复制构造函数其实经常出现在我们的程序中,比如说Pound p=12.5。上一篇文章说过的,这里的实现流程是首先,将12.5转换成一个Pound类,然后将其复制给p。注意,这里跟后面提到的赋值操作符一样, 因为这里的场景是对p进行初始化。如果是Pound p; p=12.5。这样就跟赋值操作符有关了。
其实除了上面说到的这种形式,还有另外一些形式会自动生成复制构造函数,如:
Pound p1;
Pound p2(p1);
Pound p2=p1;
Pound p2=Pound(p1);
Pound *pp2=new Pound(p1)
当然,程序生成对象副本的任何时候,都会使用复制构造函数,比如说函数参数的值传递。
有人问,这种自动的复制构造函数有什么危害啊?怎么我都看不出来?
危害大了。它在于,编译器自动生成的复制构造函数,只会对类成员进行逐个复制。
又有人问,对类成员进行逐个复制,不就满足要求了吗?
非也。大家如果还记得Java里面的深复制跟浅复制,现在可能猜出一二了。
比如说,有一个类StringTest,里面的一个类成员是char *str,另外一个类成员是length,也就是说,这个类用来封装一个字符串数组,但是并不是使用数组,而是字符指针,区别就在这里了。 StringTest有一个以字符指针为形参的构造函数,函数中使用str = new char[length+1]来创建字符数组。也有一个析构函数 ,析构函数中使用delete [] str来释放字符数组。
现在定义一个StringTest 的对象string1,用strcpy函数将string.str赋值成"test"。 当然length也要设置好。
现在有一个函数Test(StringTest t){}。注意,不是传引用,而是传值哦。以string1为实参调用Test,即Test(string1) 函数啥也不做。调用这个函数之前,我们打印string1.str,屏幕显示"test"。这是正确的。但是调用完这个函数之后,你再打印 string1.str。你会发现一切已经跟你想的不一样了。屏幕上输入的并不是test,而是无法识别的乱码!!
问题出在哪里呢?!
让我们一起回到案发现场:Test(StringTest t)。肯定是调用函数出问题了。将string1传入函数Test,这是值传递,因此 将会复制string1到t,t是一个临时对象。这时调用的就是复制构造函数 , 他将逐个复制类成员变量。本来呢,这是完全没有问题,可是注意,我们的str是一个指针,复制的时候,也只是会复制一个指针到t,而字符串"test" 呢?在内存中仍然是只有一个! OK,等到函数结束,它将自动调用StringTest类的析构函数来处理t,于是乎delete [] str,删除t中的字符数组,结果连原来的string1的字符数组都删掉了(因为他们本来就是同一个)
乖乖。这就是浅复制了。而原本应该进行的深复制,即对整个字符数组进行复制。这样就不会影响到原来的string1了。
如何改正呢?
当然是显式地定义一个复制构造函数了,形如:
ClassName ::ClassName(const Class _name&)
在显式复制构造函数的定义中,我们进行字符数组的生成和复制的处理,这样就保证了析构一些临时对象的时候,不会影响到原有对象!
相关推荐
一方面,使用初始化成员列表可以避免类成员的隐式默认构造函数的调用,避免了多余的构造和赋值操作,从而提高效率。例如,在下面的例子中,MyClass中的abc成员是ABC类的对象,而ABC类只有带参数的构造函数,没有默认...
构造函数是特殊的成员函数,用于初始化对象,而静态成员函数则与特定的对象实例无关,它们不属于任何特定对象。 **5. 堆对象的操作** - 创建或删除堆对象需要用到 `new` 和 `delete` 操作符(选项 B 和 C)。`new`...
Point1类中的所有成员函数都是导出的,而Point2类中只有一个成员函数是导出的。 在dlltest.cpp文件中,需要实现这些成员函数。注意这里使用了_declspec(dllexport)和_declspec(dllimport)关键字来控制函数的导出和...
6. **类型安全**:避免隐式类型转换,尤其是可能导致精度损失或意料之外行为的转换。使用强类型来提高代码安全性。 7. **模板和泛型编程**:合理使用模板可以提高代码复用,但过度使用会导致编译时膨胀。规范可能...
《华为技术有限公司C++语言编程规范》是一份深入细致的编程指南,旨在为开发者提供一套在C++编程中应遵循的最佳实践。这份规范不仅适用于华为公司内部开发,也对外界开发者具有很高的参考价值,帮助他们提升代码质量...
例如,在上面的代码中,我们定义了一个名为area的类,其中包括三个成员变量x、y和z,以及三个成员函数printxy、add和go。其中,printxy函数被定义为const,意味着它不能改变类的状态。 在main函数中,我们创建了一...
3. **函数或类的成员函数**: 如`void print() {}` 4. **自定义类型名**: 如`typedef int Integer;` 5. **标识宏的名字**: 如`#define MAX 100` 6. **宏的参数**: 如`#define ADD(x, y) (x + y)` #### 六、数据类型...
分配内存,调用构造函数 时,隐式/显示的初始化各数据 成员2.进入构造函数后在构造函数中执行一般计算 使用初始化列表有两个原因: 1.必须这样做:如果我们有一个类成员,它本身是一个类或者是一个结构,而且这个...
这些知识点涵盖了C++中结构体和类的基本概念,包括成员访问、运算符优先级、静态成员和常量成员函数的使用,以及友元函数和`this`指针的作用。理解和掌握这些知识点对于深入学习C++编程至关重要。
- 成员函数可以是普通的成员函数,也可以是静态成员函数。 - 成员函数可以重载,以提供不同的功能实现。 #### 31. 模式设计 - 桥接模式可以将抽象与实现解耦。 - 单例模式保证一个类只有一个实例,并提供全局访问点...
首先,我们来看C++中如何在构造函数的成员初始化列表中初始化类的成员。成员初始化列表是一种特殊的语法结构,允许我们在构造函数定义的括号内,以冒号(:)开头,列出成员变量和它们的初始值。这种方式比在构造函数体...
- 当需要导出类时,我们可以在头文件中使用条件宏来控制类和成员函数的导出。例如,`DLL_API`宏可以用于标记类和函数的导出。在DLL的实现文件中,我们定义这个宏为`_declspec(dllexport)`,而在使用DLL的客户端程序...
常数据成员可以在构造函数初始化列表中初始化,且常对象只能调用常成员函数,但常数据成员并不一定是公有的。 13. **友元函数**: 友元函数可以访问类的私有和保护成员,但不是成员函数,使用`friend`关键字声明...
构造函数是C++类的一个特殊成员函数,它的主要任务是在创建对象时进行初始化。当创建一个类的对象时,构造函数会被自动调用。如果类中没有定义任何构造函数,编译器会自动生成一个默认构造函数,即无参构造函数。...
- 成员函数重载二元运算符时,参数列表中只需要一个参数,因为它隐式地接收当前对象作为第一个操作数。 - 非成员函数重载二元运算符时,参数列表中需要两个参数。 - 示例: ```cpp class MyClass { public: ...
12. **常成员**:常数据成员可以在构造函数初始化列表中初始化,且常对象只能调用常成员函数,选项B错误。 13. **友元函数**:友元函数可以访问类的所有成员,不仅仅是私有成员,选项B错误。 14. **指针操作**:...
`operator+=`是成员函数,返回类型通常是类的引用,以便链式操作。它的形参通常是一个操作数,不是友元函数。 6. 运算符重载:C++中的运算符重载不能增加新运算符,也不能改变操作数个数。但可以为类类型的数据重...
#### 四、结构体成员函数调用错误 1. **问题描述**: - 指出了在定义结构体对象时出现的问题。 2. **代码示例**: ```cpp struct Test { Test(int) {} Test() {} void fun() {} }; void main(void) { ...