【C/C++学院】(6)构造函数/析构函数/拷贝构造函数/深copy浅copy

简介: <h1>1.构造函数</h1> <p>    类的初始化即为构造函数。也为:隐式的初始化。</p> <p><span style="color:rgb(255,0,0); font-family:'Microsoft YaHei UI','Microsoft YaHei',SimSun,'Segoe UI',Tahoma,Helvetica,sans-serif,'Microsoft

1.构造函数

    类的初始化即为构造函数。也为:隐式的初始化。

构造函数在对象初始化的时候,自动被调用。隐式的调用。

构造函数分为三种:有参构造函数、无参构造函数、拷贝构造函数。

有参构造函数调用有三种:括号法、等号法、手工法。

#include <iostream>
using namespace std;

class Test
{
private:
	int m_a;
public:
	Test()//无参构造函数
	{	}
	Test(const Test &obj)//拷贝构造函数
	{	}
	Test(int a)//有参构造函数
	{
		m_a = a;
	}
	void print()
	{
		cout << "m_a:" << m_a << endl;
	}
	
};

void main()
{
	Test t1(10);//括号法  //c++编译器自动调用这个类的有参构造函数
	t1.print();
	Test t2 = 20;//等号法  //c++编译器自动调用这个类的有参构造函数
	t2.print();
	Test t3 = Test(30);//手工法 //程序员手工的调用构造函数 进行对象初始化
	t3.print();
	system("pause");
}

2.析构函数

析构函数(destructor) 与 构造函数 相反,当 对象 脱离其 作用域 时(例如对象所在的函数已调用完毕),系统自动执行析构函数。析构函数往往用来做“清理善后” 的工作(例如在建立 对象 时用new开辟了一片内存空间,应在退出前在析构函数中用delete释放)。
主函数结束的同时,对象stud1,stud2均应被“清理”,而清理就是通过调用了析构函数实现的。
#define _CRT_SECURE_NO_WARNINGS
#include<string>
#include<iostream>
using namespace std;

class stud//声明一个类
{
private://私有部分
	int num;
	char name[10];
	char sex;
public://公用部分
	stud(int n, char nam[], char s)//构造函数
	{
		num = n;
		strcpy(name, nam);
		sex = s;
	}
	~stud()//析构函数
	{
		cout << "stud has been destructed!" << endl;//通过输出提示告诉我们析构函数确实被调用了
	}
	void display()//成员函数
	{
		cout << "num:" << num << endl;
		cout << "name:" << name << endl;
		cout << "sex:" << sex << endl;
	}
};
int main()
{
	stud stud1(10010, "Wang-li", 'f');
	stud stud2(10011, "Zhang-fun", 'm');//建立两个对象
	stud1.display();//输出学生1的数据
	stud2.display();//输出学生2的数据
	system("pause");
	return 0;
}

3.拷贝构造函数

拷贝构造函数,是一种特殊的构造函数,它由编译器调用来完成一些基于同一类的其他对象的构建及初始化。其唯一的形参必须是引用,但并不限制为const,一般普遍的会加上const限制。此函数经常用在函数调用时用户定义类型的值传递及返回。拷贝构造函数要调用基类的拷贝构造函数和成员函数。如果可以的话,它将用常量方式调用,另外,也可以用非常量方式调用。

当我们没有编写拷贝构造函数的时候,c++编译器会默认给我们提供一个拷贝构造函数,执行的是浅拷贝。

copy构造函数四种应用场景;

第一种场景:=

#include <iostream>
using namespace std;
class CExample {
private:
    int a;
public:
    //构造函数
    CExample(int b)
    {
        a = b;
    }
    //拷贝构造函数
    CExample(const CExample& C)
    {
        a = C.a;
    }
    //一般函数
    void Show()
    {
        cout << a << endl;
    }
};
int main()
{
    CExample A(100);    
    CExample B = A; //注意这里的对象初始化要调用拷贝构造函数,而非赋值
    // CExample B(A); 也是一样的
    B.Show();
    return 0;
}  

第二种场景:()

#include <iostream>
using namespace std;
class CExample {
private:
    int a;
public:
    //构造函数
    CExample(int b)
    {
        a = b;
    }
    //拷贝构造函数
    CExample(const CExample& C)
    {
        a = C.a;
    }
    //一般函数
    void Show()
    {
        cout << a << endl;
    }
};
int main()
{
    CExample A(100);    
    //CExample B = A; //注意这里的对象初始化要调用拷贝构造函数,而非赋值
    CExample B(A); //也是一样的
    B.Show();
    return 0;
}  

第三种场景:对象以值传递的方式传入函数参数

#include <iostream>
using namespace std;
class CExample
{
private:
    int a;
public:
    //构造函数  
    CExample(int b)
    {
        a = b;
        cout << "creat: " << a << endl;
    }
    //拷贝构造  
    CExample(const CExample& C)
    {
        a = C.a;
        cout << "copy" << endl;
    }
    //析构函数  
    ~CExample()
    {
        cout << "delete: " << a << endl;
    }
    void Show()
    {
        cout << a << endl;
    }
};
//全局函数,传入的是对象  
void g_Fun(CExample C)
{
    cout << "test" << endl;
}
int main()
{
    CExample test(1);
    //传入对象  
    g_Fun(test);
    return 0;
} 

第四种场景:对象以值传递的方式从函数返回

#include <iostream>
using namespace std;
class CExample
{
private:
    int a;
public:
    //构造函数  
    CExample(int b=0)
    {
        a = b;
        cout << "a:" << a << endl;
    }
    ~CExample()
    {
        cout << "destroy a:" << a << endl;
    }
    //拷贝构造  
    CExample(const CExample& C)
    {
        a = C.a;
        cout << "copy a:"<< a << endl;
    }
};
//全局函数  
CExample g_Fun()
{
    CExample temp(10);
    return temp;
}
int main()
{
    CExample ret;
    ret = g_Fun();
    return 0;
}  
添加断点,逐条语句运行,观察程序的执行步骤以及打印输出:


4.深copy浅copy

    在某些状况下,类内成员变量需要动态开辟堆内存,如果实行位拷贝,也就是把对象里的值完全复制给另一个对象,如A=B。这时,如果B中有一个成员变量指针已经申请了内存,那A中的那个成员变量也指向同一块内存。这就出现了问题:当B把内存释放了(如:析构),这时A内的指针就是野指针了,出现运行错误。

  深拷贝和浅拷贝可以简单理解为:如果一个类拥有资源,当这个类的对象发生复制过程的时候,资源重新分配,这个过程就是深拷贝,反之,没有重新分配资源,就是浅拷贝。

#include "iostream"

using namespace std;



class name

{ 

public :

	name(char *pn) ;  

	name( name &obj)

	{

		cout <<" copy Constructing " << endl ;

		char *pn = obj.getPn();

		pname = (char *)malloc(strlen(pn) +1);

		if (pname!=NULL) strcpy(pname,pn) ;

		//pname = new char[strlen(pn)+1] ;

		//if (pname!=0) strcpy(pname,pn) ;

		size = strlen(pn) ;

	}

	~ name() ;

protected : 

	char *pname ;       int size ;

public:

	char * getPn()

	{

		return pname;

	}	

	void operator=(name &obj1)

	{

		cout <<" 执行=操作" << endl ;

		char *pn = obj1.getPn();

		pname = (char *)malloc(strlen(pn) +1);//此处malloc了内存,没有free,存在一个潜在的bug

		if (pname!=NULL) strcpy(pname,pn) ;

		//pname = new char[strlen(pn)+1] ;

		//if (pname!=0) strcpy(pname,pn) ;

		pname[0] = 'm';

		size = strlen(pn) ;

	}

} ;

name::name(char *pn)

{ 

	cout <<" Constructing " << pn << endl ;

	pname = (char *)malloc(strlen(pn) +1);

	if (pname!=0) strcpy(pname,pn) ;

	//pname = new char[strlen(pn)+1] ;

	//if (pname!=0) strcpy(pname,pn) ;

	size = strlen(pn) ;

} 

name :: ~ name()

{ 

	cout << " Destructing " << pname << endl ; 

	pname[0] = '\0' ;

	//delete  []pname ;

	free(pname);

	size = 0 ;

}
void playmain()
{
	name obj1("name1");
	//如果你不写copy构造函数,那么C++编译器会给我们提供一个默认的copy构造函数 (浅cpy)
	name obj2 = obj1;
	//如果你不写=操作,那么C++编译器会给我们提供一个=操作函数 (浅cpy)
	obj2 = obj1;
	cout<<obj2.getPn()<<endl;
}
void main()
{
	playmain();
	system("pause");
}


#define _CRT_SECURE_NO_WARNINGS
#include "iostream"
using namespace std;
class name
{
public:
    name(char *pn);
    name(name &obj)
    {
        cout << " copy Constructing " << endl;
        char *pn = obj.getPn();
        pname = (char *)malloc(strlen(pn) + 1);
        if (pname != NULL) strcpy(pname, pn);
        //pname = new char[strlen(pn)+1] ;
        //if (pname!=0) strcpy(pname,pn) ;
        size = strlen(pn);
    }
    ~name();
protected:
    char *pname;       int size;
public:
    char * getPn()
    {
        return pname;
    }
    void operator=(name &obj1)
    {
        cout << " 执行=操作" << endl;
        if (pname != NULL)//此处有一个疑问:如果不free, 直接将原来的内存进行重新赋值
        {
            char *pn = obj1.getPn();
            strcpy(pname, pn);
            pname[0] = 'N';
            size = strlen(pn);
        }
    }
    /*
    void operator=(name &obj1)
    {
        cout <<" 执行=操作" << endl ;
        if (pname != NULL)//此处有一个疑问:如果不free, 直接将原来的内存进行重新赋值
        {
            free(pname);
            pname = NULL;
            size = 0;
        }
        char *pn = obj1.getPn();
        pname = (char *)malloc(strlen(pn) +1);
        if (pname!=NULL) strcpy(pname,pn) ;
        //pname = new char[strlen(pn)+1] ;
        //if (pname!=0) strcpy(pname,pn) ;
        pname[0] = 'm';
        size = strlen(pn) ;
    }
    */
};
name::name(char *pn)
{
    cout << " Constructing " << pn << endl;
    pname = (char *)malloc(strlen(pn) + 1);
    if (pname != 0) strcpy(pname, pn);
    //pname = new char[strlen(pn)+1] ;
    //if (pname!=0) strcpy(pname,pn) ;
    size = strlen(pn);
}
name :: ~name()
{
    cout << " Destructing " << pname << endl;
    pname[0] = '\0';
    //delete  []pname ;
    free(pname);
    size = 0;
}
int playmain()
{
    name obj1("name1");
    name obj3("name3");
    //如果你不写copy构造函数,那么C++编译器会给我们提供一个默认的copy构造函数 (浅cpy)
    name obj2 = obj1;
    //做业务逻辑
    //此处省略500行
    //如果你不写=操作,那么C++编译器会给我们提供一个=操作函数 (浅cpy)
    //会调用对象2 的=号操作函数, obj3是形参, obj2干什么去了?
    obj2 = obj3;
    cout << obj2.getPn() << endl;
    return 0;
}
int main()
{
    playmain();
    //system("pause");
    return 0;
}

最终分析图:



目录
相关文章
|
21天前
|
编译器 C++
C++ 类构造函数初始化列表
构造函数初始化列表以一个冒号开始,接着是以逗号分隔的数据成员列表,每个数据成员后面跟一个放在括号中的初始化式。
64 30
|
2月前
|
算法 C++ 容器
C++标准库中copy算法的使用
C++标准库中copy算法的使用
22 1
|
2月前
|
编译器 C++
C++的基类和派生类构造函数
基类的成员函数可以被继承,可以通过派生类的对象访问,但这仅仅指的是普通的成员函数,类的构造函数不能被继承。构造函数不能被继承是有道理的,因为即使继承了,它的名字和派生类的名字也不一样,不能成为派生类的构造函数,当然更不能成为普通的成员函数。 在设计派生类时,对继承过来的成员变量的初始化工作也要由派生类的构造函数完成,但是大部分基类都有 private 属性的成员变量,它们在派生类中无法访问,更不能使用派生类的构造函数来初始化。 这种矛盾在C++继承中是普遍存在的,解决这个问题的思路是:在派生类的构造函数中调用基类的构造函数。 下面的例子展示了如何在派生类的构造函数中调用基类的构造函数:
32 1
|
3月前
|
编译器 C++
【C++】详解构造函数
【C++】详解构造函数
|
4月前
|
存储 编译器 C++
【C++】类和对象④(再谈构造函数:初始化列表,隐式类型转换,缺省值
C++中的隐式类型转换在变量赋值和函数调用中常见,如`double`转`int`。取引用时,须用`const`以防修改临时变量,如`const int& b = a;`。类可以有隐式单参构造,使`A aa2 = 1;`合法,但`explicit`关键字可阻止这种转换。C++11起,成员变量可设默认值,如`int _b1 = 1;`。博客探讨构造函数、初始化列表及编译器优化,关注更多C++特性。
|
4月前
|
存储 编译器 C++
【C++】类和对象③(类的默认成员函数:拷贝构造函数)
本文探讨了C++中拷贝构造函数和赋值运算符重载的重要性。拷贝构造函数用于创建与已有对象相同的新对象,尤其在类涉及资源管理时需谨慎处理,以防止浅拷贝导致的问题。默认拷贝构造函数进行字节级复制,可能导致资源重复释放。例子展示了未正确实现拷贝构造函数时可能导致的无限递归。此外,文章提到了拷贝构造函数的常见应用场景,如函数参数、返回值和对象初始化,并指出类对象在赋值或作为函数参数时会隐式调用拷贝构造。
|
4月前
|
存储 编译器 C语言
【C++】类和对象②(类的默认成员函数:构造函数 | 析构函数)
C++类的六大默认成员函数包括构造函数、析构函数、拷贝构造、赋值运算符、取地址重载及const取址。构造函数用于对象初始化,无返回值,名称与类名相同,可重载。若未定义,编译器提供默认无参构造。析构函数负责对象销毁,名字前加`~`,无参数无返回,自动调用以释放资源。一个类只有一个析构函数。两者确保对象生命周期中正确初始化和清理。
|
1天前
|
存储 C++
【C++篇】C++类和对象实践篇——从零带你实现日期类的超详细指南
【C++篇】C++类和对象实践篇——从零带你实现日期类的超详细指南
10 2
【C++篇】C++类和对象实践篇——从零带你实现日期类的超详细指南
|
1天前
|
存储 编译器 C语言
C++类与对象深度解析(一):从抽象到实践的全面入门指南
C++类与对象深度解析(一):从抽象到实践的全面入门指南
15 8
|
1天前
|
C++
【C++】实现日期类相关接口(三)
【C++】实现日期类相关接口