# 关于C++中构造函数的讨论
C++ 中的构造函数是一种特殊方法,在创建对象时自动调用。它通常用于初始化新对象的数据成员。C++ 中的构造函数与类或结构同名。它构造值,即为对象提供数据,这就是为什么它被称为构造函数。
# C++构造函数的特点
- 构造函数的名称与其类名相同。
- 构造函数通常在类的公共部分中声明,尽管它们可以在类的私有部分中声明。
- 构造函数不返回值;因此,它们没有返回类型。
- 当我们创建类的对象时,会自动调用构造函数。
- 构造函数可能会重载,即一个类可能会有多个构造函数。
- 构造函数不能声明为虚函数。
- 构造函数不能被继承。
- 构造函数的地址不能被引用。
# C++ 中的构造函数类型
[默认构造函数](#C++ 中的默认构造函数(无参构造函数))
[有参构造函数](#C++ 中的有参构造函数)
[拷贝构造函数](#C++ 中的拷贝构造函数)
[移动构造函数](# C++ 中的移动构造函数)
举一个去商店买笔例子来说明这四种构造函数的区别。默认构造函数相当于你去商场买笔,你没有告诉商家你需要的笔的特征,你仅仅只需要一直笔而已。有参构造函数相当于你告诉了商家你需要买的笔的颜色、品牌等特征。拷贝构造函数相当于你拿着你用过的笔去找店家再买一只一样的。移动构造函数相当于你并不去商店买新的笔,而是使用你朋友的笔。
# C++ 中的默认构造函数(无参构造函数)
默认构造函数是不接受任何参数的构造函数。它没有参数。它也被称为无参构造函数。
# 默认构造函数的语法
className() {
// body_of_constructor
}
2
3
# 默认构造函数的示例
示例1:
// CPP程序说明默认构造函数的语法
#include <iostream>
class pen{
public:
std::string color;
int size;
pen()
{
color = "red";
size = 10;
}
};
int main()
{
// 创建对象时自动调用默认构造函数
pen s;
std::cout << "笔的颜色为: " << s.color << std::endl << "笔的大小为: " << s.size;
return 0;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
输出1:
笔的颜色为: red
笔的大小为: 10
2
注意:即使我们没有显式定义任何构造函数,编译器也会自动隐式提供默认构造函数。
# C++ 中的有参构造函数
参数化构造函数使将参数传递给构造函数成为可能。通常,这些参数有助于在创建对象时初始化对象。要创建参数化构造函数,只需向其添加参数,就像向任何其他函数添加参数一样。定义构造函数的主体时,请使用参数初始化对象。
# 有参构造函数的语法
className (parameters...) {
// body
}
2
3
# 有参构造函数的示例
示例 1:在类中定义有参构造函数。
// CPP程序来说明有参的构造函数
#include <iostream>
class pen{
private:
std::string color;
int size;
public:
pen(std::string color1,int size1)
{
color = color1;
size = size1;
}
std::string getColor() { return color; }
int getSize() { return size; }
};
int main()
{
// 创建对象时调用有参构造函数
pen s("ren",10);
// 由构造函数赋值的访问值
std::cout << "笔的颜色为: " << s. getColor() << std::endl << "笔的大小为: " << s.getSize();
return 0;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
输出 1:
笔的颜色为: ren
笔的大小为: 10
2
示例 2:在类外部定义参数化构造函数。
// 说明如何在类外部定义参数化的
#include <iostream>
class pen{
std::string color;
int size;
public:
pen(std::string,int);
void display();
};
// 类外部的参数化构造函数
pen::pen(std::string color1,int size1)
{
color = color1;
size = size1;
}
void pen::display()
{
std::cout << "笔的颜色为: " << color << std::endl << "笔的大小为: " << size;
}
int main()
{
// pen s = pen("red",10); // 显示调用
pen s("red",10);// 隐式调用
s.display();
return 0;
}
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
输出 2:
笔的颜色为: ren
笔的大小为: 10
2
注意:当定义了参数化构造函数并且没有显式定义默认构造函数时,编译器不会隐式创建默认构造函数,所以每当我们为一个类定义一个或多个非默认构造函数(带参数)时,也应该显式定义默认构造函数(不带参数),因为在这种情况下编译器不会提供默认构造函数。但是,这不是必需的,但始终定义默认构造函数被认为是最佳做法。
示例 3:使用默认值定义有参构造函数
就像普通函数一样,我们也可以为参数化构造函数的参数定义默认值。默认参数的所有规则都将应用于这些参数。
#include <iostream>
class pen{
private:
std::string color;
int size;
public:
pen(std::string color1 = "red",int size1 = 10)
{
color = color1;
size = size1;
}
void display()
{
std::cout << "笔的颜色为: " << color << " 大小为: " << size << std::endl;
}
};
int main()
{
pen s1;
pen s2("black",5);
std::cout << "第一支";s1.display();
std::cout << "第二支";s2.display();
return 0;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
输出 3:
第一支笔的颜色为: red 大小为: 10
第二支笔的颜色为: black 大小为: 5
2
正如我们所看到的,当默认值被分配给参数化构造函数的每个参数时,在不传递任何参数的情况下创建对象是合法的,就像默认构造函数一样。因此,这种类型的构造函数既可以用作默认构造函数,也可以用作参数化构造函数。
# C++ 中的拷贝构造函数
拷贝构造函数是一个成员函数,它使用同一类的另一个对象初始化对象。
# 拷贝构造函数的语法
拷贝构造函数将对同一类的对象的引用作为参数。
ClassName (ClassName &obj)
{
// body_containing_logic
}
2
3
4
就像默认构造函数一样,如果没有显式复制构造函数定义,c++编译器也会提供隐式复制构造函数。在这里,需要注意的是,与默认构造函数(任何类型的显式构造函数的存在都会导致隐式默认构造函数的删除)不同,如果没有显式复制构造函数或显式移动构造函数,则隐式复制构造函数将始终由编译器创建。
# 拷贝构造函数的示例
示例 1:隐式拷贝构造函数
// c++程序说明隐式构造函数的使用
#include <iostream>
class pen{
private:
std::string color;
int size;
public:
pen(std::string color1,int size1)
{
color = color1;
size = size1;
}
void display()
{
std::cout << "笔的颜色为: " << color << " 大小为: " << size << std::endl;
}
};
int main()
{
pen s1("red",10);
std::cout << "第一支";s1.display();
// 从对象中创建一个pen类型的对象
pen s2(s1);// 写成 pen s2 = s1 也行
std::cout << "第二支";s2.display();
return 0;
}
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
输出 1:
第一支笔的颜色为: red 大小为: 10
第二支笔的颜色为: red 大小为: 10
2
示例 2:定义显式拷贝构造函数
// c++程序演示如何定义显示拷贝构造函数
#include <iostream>
class pen{
private:
std::string color;
int size;
public:
// 有参构造函数
pen(std::string color1,int size1)
{
color = color1;
size = size1;
}
// 拷贝构造函数
pen(pen& t)
{
color = t.color;
size = t.size;
}
void display()
{
std::cout << "笔的颜色为: " << color << " 大小为: " << size << std::endl;
}
};
int main()
{
pen s1("red",10);
std::cout << "第一支";s1.display();
// 从对象中创建一个pen类型的对象
pen s2(s1);// 写成 pen s2 = s1 也行
std::cout << "第二支";s2.display();
return 0;
}
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
输出 2:
第一支笔的颜色为: red 大小为: 10
第二支笔的颜色为: red 大小为: 10
2
# 拷贝构造函数的用途
- 通过从现有对象复制值来构造新对象。
- 可用于执行深度复制。
- 如果需要,在复制过程中修改特定属性
# C++ 中的移动构造函数
移动构造函数是 C++ 中构造函数系列的最新成员( C++11加入 )。它就像一个复制构造函数,从已经存在的对象构造对象,但它不是在新内存中复制对象,而是利用移动语义将已创建对象的所有权转移到新对象,而无需创建额外的副本。
# 移动构造函数的语法
className (className&& obj) {
// body of the constructor
}
2
3
移动构造函数采用同一类对象的右值引用,并将此对象的所有权转移给新创建的对象。与复制构造函数一样,编译器将为每个没有任何显式移动构造函数的类创建一个移动构造函数。
# 移动构造函数的示例
示例 1:定义移动构造函数
// c++程序演示如何定义移动构造函数
#include <iostream>
using namespace std;
class Box {
public:
int* data;
Box(int value)
{
data = new int;
*data = value;
}
// 移动构造函数
Box(Box&& other) noexcept
{
cout << "移动构造函数" << endl;
data = other.data; // 转让'other'的使用权
other.data = nullptr; // 将'other'置空,防止重复删除
}
// 析构函数
~Box() { delete data; }
};
int main()
{
Box originalBox(42);
// 通过从原始'box'中移动资源来创建一个新的'box'
Box newBox(move(originalBox));
cout << "newBox.data: " << *newBox.data;
// originalBox现在处于有效但未指定的状态(其资源被移动到newBox)
return 0;
}
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
输出 1:
Move Constructor Called
newBox.data: 42
2
这个示例中,我没有继续使用pen
来举例子,而是直接拿了它的例子。主要是考虑到想要体现移动构造中右值的特点,用指针能更加好的展示出来。移动构造可以看作是对复制构造的一种优化。示例中写道other.data = nullptr;
需要特别的说明一下,由于右值的特性( 左值持久;右值短暂 ),需要对other.data
这一指针进行置空。( 当然,由于时两个指针都指向同一块区域,这一行为很危险,所以将一中一个指针置空也十分的合理 )noexcept
也需要特别说明一下,移动构造函数都需要加上noexcept
这个关键词,不然编译器会首先调用拷贝构造函数来完成操作。noexcept
是告诉编译器这个移动构造函数不会异常。
由于移动这一特性,如果在移动的过程中发生异常就会处于
inconsistency
的状态。出于安全的考虑,编译器会优先调用拷贝构造函数来完成操作,所以移动构造函数尽量都加上noexcept
。
# 移动构造函数的用途
- 移动构造函数无需复制,而是有效地转移这些资源的所有权。
- 这样可以防止不必要的内存复制并减少开销。
- 您可以定义自己的移动构造函数来处理特定的资源传输。