C++构造函数基础小结

C++构造函数基础小结

c++的类中,最基础的东西就是构造函数。不过,构造函数也是有分类和区分的。这其中是有一些说法的。本文主要是针对构造函数的基础进行总结,包含一些特点和值得注意的地方。

构造函数的概念

c++的构造函数属于c++类的最基础的一种成员函数,它会在每次生成类对象时执行。
构造函数的特点是:
1、构造函数名称和类的名称一致(包括大小写);
2、构造函数没有返回类型,也没有void类型返回;(同时,析构函数也一样)
3、构造函数可以对成员变量进行初始化

构造函数的分类

构造函数根据入参类型的不同,有不同的分类。这种特性,是基于函数重载的使用。

默认构造函数

默认构造函数有如下特定:
1、不接受任何实参,不接受任何实参有两种方式,一种是没有明显形参的构造函数,第二种提供了默认实参的构造函数;
2、每个类只能有一个默认构造函数,否则会有歧义;
3、如果一个类没有声明任何构造函数,且在有需要的时候,编译器会自动为该类生成一个默认构造函数;
4、如果一个类声明了一个非默认构造函数,那么如果需要使用该类的默认构造函数,需要显示的声明。示例代码如下:

#include <iostream>
using namespace std;
class A
{
public:
	// 没有带任何形参的默认构造函数
	A()
	{
		cout << "default constructor" << endl;
	}
}
class B
{
public:
	// 提供默认实参的默认构造函数
	B(int m = 1, int n = 2)
	{
		cout << "default constructor" << endl;
	}
}
class C
{
	// 没有声明任何构造函数,当需要时,编译器会自动为类C提供默认构造函数
public:
	void add();
private:
	int x;
}
// A和B的构造函数都可以称为默认构造函数。
// C的构造函数由编译器提供,提供的默认构造函数中,不进行任何操作。

关于特性4,代码如下

#include <iostream>
using namespace std;
class Test
{
public:
	Test(int x)
		:s(x)
	{
	}
private:
	int s;
}
int main()
{
	Test t();// fault, 不存在default constructor
	return 0;
}

关于默认构造函数,其使用方式也有几种不同

#include <iostream>
using namespace std;
class Test
{
public:
	Test()
	{
		s = 0;
	}
private:
	int s;
}
int main()
{
	// 1、隐式调用
	Test t;
	// 2、显示调用
	Test t();
	//
}

拷贝构造函数

拷贝构造函数特性

拷贝构造函数的入参为const type& 形式
type和类名一致。其用意是根据一个已有对象,生成一个数据一致的新对象。
拷贝构造函数特性:
1、拷贝构造函数中必须有一个参数是被类型常量的一个引用;
2、当用户没有声明拷贝构造函数时,在需要使用的时候,编译器会自动为类添加拷贝构造函数;
3、默认的,由编译器生成的拷贝构造函数,是将对象的成员,按照声明顺序依次进行值传递的方式进行赋值。
这里需要有两点进行注意:
(1)、默认拷贝构造函数,并不会处理类的静态成员,只会去对类对象的成员进行值传递赋值。
(2)、默认拷贝构造函数,针对特殊的对象成员,并不会做特殊处理,如指针类型变量,也只是将地址进行赋值,使 得新对象和原有对象的指针类型成员指向的地址一致。也就是说,默认拷贝构造函数,使用浅拷贝进行赋值。
关于以上特性

#include <iostream>
using namespace std;
class Test 
{
public:
	Test(int x, int y):
		a(x),
		b(y)
	{
		cout << "Test constructor" << endl;
	}
private:
	int a;
	int b;
}
Class Test1
{
public:
	Test1(int x, int y):
		a(x),
		b(y)
	{
		cout << "Test1 constructor" << endl;
	}
	Test1(const Test1& t):
		a(t.a),
		b(t.b)
	{
		cout << "Test1 copy constructor" << endl;
	}
private:
	int a;
	int b;
}
Class Test2
{
public:
	Test2(int *x, int y):
		b(y)
	{
		a = new int(*x);
		count++;
		cout << "Test2 constructor" << endl;
	}
	Test2(const Test1& t):
		b(t.b)
	{
		a = new int(*t.a);
		count++;
		cout << "Test2 copy constructor" << endl;
	}
	~Test2()
	{
		delete a;
		count--;
	}
private:
	int *a;
	int b;
	static int count = 0;
}
Test testA(1, 2); // "Test constructor"
Test testB(a); // 调用默认拷贝构造函数,由编译器自动生成

Test1 test1A(1, 2);// "Test1 constructor"
Test1 test1B(a); // "Test1 copy constructor"

int* x = new int(1);
Test2 test2A(x, 2);
Test1 test2B(a); // "Test2 copy constructor"

在上面的用例中,Test和Test1的成员变量一致,都是基本类型,也并没有静态成员变量,所以,Test1的拷贝构造函数的功能实现只是Test的默认拷贝构造函数的一种显化,两者实现的功能一致,即,Test1和Test类可以不用显示声明拷贝构造函数。而Test2中,成员变量a,是int*类型,并且count是静态成员变量,默认拷贝构造函数,只是简单的进行值得赋值行为,只是将test2A.a的地址,赋值给了test2B.a指向的地址,如果test2A进行析构,那么test2B.a由于和Test2A.a指向地址一样,就会释放,其次,调用test2B的析构函数时,同一地址两次释放,编译器会报错。
小结:
1、如果一个类中的需要操作地址,需要显示的声明拷贝构造函数,并且对操作地址的成员变量进行深拷贝。
2、针对静态成员函数,如果需要,也必须声明拷贝构造函数,对静态成员变量进行值得改变。

拷贝构造函数的调用

在代码的使用中,某一些场景会进行拷贝构造函数的调用,有些是显示调用,有些是隐式调用,显示调用部分其实没有过多注意,隐式调用部分则和我们的代码优化息息相关。

class Test 
{
public:
	Test()
	{
		cout << "default constructor" << endl;
	}
	Test(Test& t)
	{
		cout << "copy constructor" << endl;
	}
}
void mTest(Test t)
{
}
void nTest(Test& t)
{
}

Test rTest()
{
	Test a;
	return a;
}
Test& sTest(Test& a)
{
	return a;
}

Test& uTest()
{
	Test a;
	return a;
}
Test a; //  "default constructor" 
Test b(a) // "copy constructor" 显示调用拷贝构造函数
Test* c = new Test(a) // "copy constructor" 显示调用拷贝构造函数
Test d = a; // "copy constructor" 隐式调用拷贝构造函数

mTest(a) //"copy constructor" 隐式调用拷贝构造函数,此时,由于mTest在调用时,会调用Test的拷贝构造函数,将实参a拷贝为形参t。
nTest(a) // 由于是值传递,则不会调用拷贝构造函数。可以节省调用拷贝构造函数这一过程。

Test d = rTest()//  "default constructor" "copy constructor",rTest中,生成对象a时,会先进行默认构造函数调用,在return a时,调用拷贝构造函数,将a的值复制到d中。

Test p,q;
q = sTest(p)// 此种调用的回传,并不会调用拷贝构造函数,而是将a的别名赋值给q。对于此类函数,需要注意的是,如果是形如uTest这种类型return函数体中声明的变量,是不能使Test& 这种方式进行return,因为当函数的生命周期结束时,栈上的变量进行回收,将导致外部接收的变量也会有问题。

普通构造函数

普通构造函数没有什么特别的形式,函数名和类名一致,且没有任何返回。需要注意的是,隐式类型转换。当普通构造函数只有一个形参或者只有一个没有默认变量的形参时,某些调用会进行变量的隐式类型转换

class Test
{
public:
	Test(int x)
	{
		a = x;
		cout << "ordinary constructor" << endl;
	}
public:
	int a;
}
int main()
{
	Test a = 11.3; // "ordinary constructor", 此用法会调用隐式类型转换,将double类型转换成为int类型, a.a 输出为11而不是11.3。,如果不想构造函数有这种行为,则需要在构造函数前加入 关键字 explict
}

移动构造函数

移动构造函数的使用场景比较特殊,当我们需要用对象a构造一个对象b,而构建完对象b后,我们不再需要对象a,需要释放a的资源时,可以使用移动构造函数。
通俗一点的解释就是,拷贝构造函数中,对于指针,我们一定要采用深拷贝,而移动构造函数中,对于指针,我们采用浅拷贝。
在这里插入图片描述
还有就是,针对移动构造函数,需要将原有对象的指针置位nullptr,这样,在析构函数中记性判断,如果是nullptr,则不进行delete
具体代码如下:

class Test
{
public:
	Test()
	{
		a = new int(0);
	};
	//&&表示右值引用参数类型
	Test(Test&& t)
	{
		if(nullptr != t.a)
		{
			this->a = t.a;
			t.a = nullptr;
		}
	}
	~Test()
	{
		if(nullptr != this->a)
		{
			delete a;
		}
	}
public:
	int* a
}

操作符重载(operator=)

该方法是类的成员函数,利用操作符重载,达到了拷贝构造函数的作用。但是,和拷贝构造函数有些区别。
1、该函数是类的成员函数,并非构造函数,不能生成新对象,必须使用已有的对象。
2、该函数有返回类型。
代码如下

class Test
{
public:
	Test()
	{
		cout << "default constructor" << endl;
		x = new int(0);
	}
	Test(int m)
	{
		x = new int(m);
	}
	Test(cosnt Test& t)
	{
		cout << "copy constructor" << endl;
		x = new int(*t.x);
	}
	Test& operator= (const Test& t)
	{
		cout << "operator =" << endl;
		if(this == &t)
		{
			// 判断如果两者指向地址一致,直接进行返回
			return *this;
		}
		x = new int(*t.x);
		return *this;
	}
private:
	int* x;
}
int main()
{
	Test a(1);
	Test b = a; // "copy constructor" 隐式调用拷贝构造函数
	Test c; // "default constructor"
	c = a; //"operator =" 调用操作符=重载,因为此时,对象c已存在,不会c对象的生成,调用c的默认拷贝构造函数,此时调用c = a 时,会调用Test的 operator= ,其作用和拷贝构造函数一致。
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值