[基础知识]3.拷贝构造函数

版权声明:By TimepassbyZ 转载请注明出处! https://blog.csdn.net/TimepassbyZ/article/details/84938914

下列程序能够执行成功吗?如果不能,怎样修改才能执行成功?

class A
{
private:
	int value;
	
public:
	A(int n) { value = n; }
	A(A other) { value = other.value; }
 
	void Print() { std::cout << value << std::endl; }
};
 
int _tmain(int argc, _TCHAR* argv[])
{
 	A a = 2019;
 	A b = a;
	b.Print();
 
	return 0;
}

A. 编译失败
B. 编译成功,运行时程序崩溃
C. 编译运行正常,输出2019

  • 分析:
    由于代码中拷贝构造函数A(A other)传入的参数是A的一个实例(传值调用),把形参复制到实参时又会调用拷贝构造函数,这样就会形成递归调用,进而导致栈溢出。C++的标准不允许拷贝构造函数有传值参数,因此在VS和GCC中都将编译错误。答案:A
  • 修改代码:
    要解决这个问题,可以把拷贝构造函数修改为A(const A& other),也就是把传值参数改成常量引用。
class A
{
private:
	int value;
	
public:
	A(int n) { value = n; }
	// 自定义拷贝构造函数的格式:类名::类名(const 类名 &对象名)
	A(const A &other) { value = other.value; }
 
	void Print() { std::cout << value << std::endl; }
};
 
int _tmain(int argc, _TCHAR* argv[])
{
 	A a = 2019;
 	A b = a;
	b.Print();
 
	return 0;
}

相关知识点:

  1. main()是标准C++的程序入口点函数,默认字符编码格式为ANSI;
    其函数签名为:int main(); 以及 int main(int argc, char* argv[]);
  2. _tmain()是微软操作系统(windows)提供的对unicode字符集和ANSI字符集进行自动转换用的程序入口点函数;函数签名为: int _tmain(int argc, TCHAR *argv[]);
    ● 当你程序当前的字符集为unicode时,int _tmain(int argc, TCHAR *argv[]); 会被翻译成: int wmain(int argc, wchar_t *argv[]);
    ● 当你程序当前的字符集为ANSI时,int _tmain(int argc, TCHAR *argv[]); 会被翻译成: int main(int argc, char *argv[]);
  3. 第一个参数argc表示在命令行中输入的程序名和参数个数之和,第二个参数中argv[0]记录程序名,后面的argv[i]记录输入的参数。
  4. 函数签名用于识别不同的函数,它包含了一个函数的信息(函数名、参数类型、参数个数、顺序以及它所在的类和命名空间),但不包含函数返回值(如果两个函数仅仅是返回值不同,那么系统将无法区分这两个函数,编译器会提示语法错误)。
  5. 调用拷贝构造函数的三种情况:
    5.1 用类的一个对象去初始化该类的另一个对象时
A a2(a1); // 用代入法调用拷贝构造函数,用对象a1初始化对象a2
A a3=a1;  // 用赋值法调用拷贝构造函数,用对象a1初始化对象a3

5.2 函数的形参是类的对象,在调用函数进行形参和实参结合时

void fun(A a1){ // 形参是类A的对象a1
	p.Print();
}
int main(){
	A a(2019);
	fun(a); // 调用函数fun时,实参a是类A的对象,将调用拷贝构造函数初始化形参a1
}

5.3 函数的返回值是类的对象,在函数调用完毕将返回值(对象)带回函数调用处时,会将此对象复制给一个临时对象并传到该函数的调用处。

A fun(){ // 函数fun的返回值类型是A类类型
	A a1(2019); // 定义类的对象a1
	return a1; // 函数的返回值是A类的对象
}
int main(){
	A a; // 定义类的对象a
	a = fun(); // 函数执行完毕,返回调用者时,调用拷贝构造函数
	return 0;
}
  1. 如果产生了新的对象实例,调用的是拷贝构造函数;如果没有,调用的是赋值运算符函数。
int main(){
	A a1(2019), a2;
	a2 = a1; // 调用默认赋值运算符函数,将对象a1的值赋给对象a2
	A a3(a1), a4 = a2; 
	// 用代入法调用拷贝构造函数,用对象a1初始化对象a3
	// 用赋值法调用拷贝构造函数,用对象a2初始化对象a4
}
  1. ● 在向函数传递对象时,是通过“传值调用”传递给函数的,即单向传递,只由实参传给形参,而不能由形参传回来给实参。
    ● 使用对象指针作为函数参数可以实现“传址调用”,即在函数调用时使实参对象和形参对象指针变量指向同一内存地址。
    ● 使用对象引用作为函数参数不仅具有对象指针的优点,而且更简单、直接。
  2. 自定义拷贝构造函数的格式:
类名::类名 ( const 类名 &对象名 )
{
	// 拷贝构造函数的函数体
}

参考文章
_tmain()和main()有什么区别?
C++中的函数签名

猜你喜欢

转载自blog.csdn.net/TimepassbyZ/article/details/84938914