C/C++基础之二(类的构造函数)

C/C++基础之二(类的构造函数)

Talk is cheap. Show me the code.

1. 构造函数,有参,无参?

2. 析构函数的调用时机?

3. 如何在构造函数中初始化某个成员类?

4. 拷贝构造函数的凶残?

5. 操作符重载的奇妙设计!

1
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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
#include <iostream>
#include <string.h>

using namespace std;

class Person
{
private:
char *name;
char *work;
char *sex;
int age;
public:
Person();
Person(char *name,char *work,int age,char *sex=(char *)"f");
~Person();
void setAge(int age);
void setName(char *name);
void printInfo();
};


//无参构造函数
Person::Person(/* args */)
{
cout << "Person()" << endl;
// 对指针变量赋初值很重要,否则是野指针,直接访问非常危险,会有问题的
this->name = NULL;
this->work = NULL;
this->sex = NULL;
}

Person::Person(char *name,char *work,int age,char *sex)
{
// 下面变量共享内存的写法,也叫做浅拷贝,如果外部被释放,那么这里就成了野指针。很危险
// this->name = name;
// this->work = work;
// 分配内存,也叫做深拷贝,安全,但要记得自己释放new出来的内存,释放交给析构函数
this->name = new char[strlen(name)+1];
strcpy(this->name,name);

this->work = new char[strlen(work)+1];
strcpy(this->work,work);

this->sex = new char[strlen(sex)+1];
strcpy(this->sex,sex);

this->age = age;
cout << "Person(char *name,char *work,int age)" << ",name:"<< name << ",work:"<< work <<",age:"<< age <<",sex:"<< sex << endl;
}

void Person::printInfo()
{
cout << "name:"<< name << ",work:"<< work <<",age:"<< age << endl;
}

// 析构函数
Person::~Person()
{
cout << "~Person(),name:" << this->name << endl;
if (this->name)
{
cout << "delete name:" << this->name << endl;
// C++ 要严格遵守 new ↔ delete,new[] ↔ delete[]。
// delete this->name;
delete[] this->name;
}
if (this->work)
{
cout << "delete work:" << this->work << endl;
// C++ 要严格遵守 new ↔ delete,new[] ↔ delete[]。
delete[] this->work;
}

if(this->sex)
{
// C++ 要严格遵守 new ↔ delete,new[] ↔ delete[]。
cout << "delete sex:" << this->sex << endl;
delete[] this->sex;
}

}

class Student
{
private:
Person father;
Person mother;
int id;
public:
Student()
{
cout << "Student()" << endl;
}
// 构造函数中完成成员变量的初始化,成员变量的初始化和:father(...),mother(...)无关,之和上面的成员定义顺序有关
Student(char *fatherName,char *motherName,int id):father(fatherName,"CFO",28,(char *)"M"),mother(motherName,"BOSS",18)
{
this->id = id;
}
~Student()
{
cout << "~Student()"<<endl;
}
};

void person_test()
{
Person *p = new Person("lisi","student",18);
//new出来的内存一定要释放掉,否则它是不会释放的
delete p;
}

int main()
{
Person per;
//注意下面这行代码并没有创建per1对象,只是一个方法声明,类似 int sum();
Person per1();
// 调用有参数的构造函数,最后一个sex参数使用默认值
Person per2("zhangsan","laywer",48);
Person per3("wangwu","CEO",29,"M");
person_test();
Student s("Jack","Alice",10);
return 0;
}
  • 输出如下
1
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
Person()
Person(char *name,char *work,int age),name:zhangsan,work:laywer,age:48,sex:f
Person(char *name,char *work,int age),name:wangwu,work:CEO,age:29,sex:M
Person(char *name,char *work,int age),name:lisi,work:student,age:18,sex:f
~Person(),name:lisi
delete name:lisi
delete work:student
delete sex:f
Person(char *name,char *work,int age),name:Jack,work:CFO,age:28,sex:M
Person(char *name,char *work,int age),name:Alice,work:BOSS,age:18,sex:f
~Student()
~Person(),name:Alice
delete name:Alice
delete work:BOSS
delete sex:f
~Person(),name:Jack
delete name:Jack
delete work:CFO
delete sex:M
~Person(),name:wangwu
delete name:wangwu
delete work:CEO
delete sex:M
~Person(),name:zhangsan
delete name:zhangsan
delete work:laywer
delete sex:f
~Person(),name:
我们写了一些属性以及get/set方法,当这个类交给一个工具类的时候他想做一些操作,因为属性一般是private的,因此他必须要调用get,set方法,那么就出现了一种叫做firend的函数,可以直接访问私有属性,上代码:
1
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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
#include <iostream>

using namespace std;

class Point;

class PointUtils
{
private:
/* data */
public:
PointUtils
(/* args */);
Point pointSum(const Point& p1,const Point& p2);
~PointUtils
();
};

class Point{
private:
int x;
int y;

public:
Point(){}

Point(int x,int y){
this->x = x;
this->y = y;
}

void setX(int x){
this->x = x;
}

void setY(int y){
this->y = y;
}

int getX(){
return this->x;
}

int getY(){
return this->y;
}
//声明PointUtils的pointSum是我的朋友,可以访问我的属性
friend Point PointUtils::pointSum(const Point& p1,const Point& p2);

void printInfo(){
cout << "x:" << this->x << ",y:" << this->y << endl;
}

};

PointUtils::PointUtils(/* args */)
{
}

Point PointUtils::pointSum(const Point& p1,const Point& p2){
Point result;
//友元函数,可以直接访问x,y,就不用使用get方法啦
result.x = p1.x + p2.x;
result.y = p1.y + p2.y;
return result;
}

PointUtils::~PointUtils()
{
}

int main(){
Point p1(10,20);
Point p2(20,30);
PointUtils util;
Point result = util.pointSum(p1,p2);
result.printInfo();
return 0;
}
然后上面的程序似乎没有问题,但是其实是有问题的。这个问题来自于类的拷贝:

1️⃣ 浅拷贝的定义

浅拷贝就是 直接拷贝对象的所有成员,包括指针 只拷贝指针本身的值(地址),而不拷贝指针指向的内容。

例如:

class Demo {
public:
int* ptr;
Demo(int val) { ptr = new int(val); }
~Demo() { delete ptr; }
};

int main() {
Demo d1(5);
Demo d2 = d1; // 默认拷贝构造函数执行 → 浅拷贝
}

d2.ptr 和 d1.ptr 指向同一块堆内存

当 d1 析构时释放了内存 → d2.ptr 悬空

再析构 d2 → double free / 崩溃

✅ 这就是浅拷贝的典型问题。

2️⃣ 什么时候会触发浅拷贝
(1) 按值传递对象
void foo(Demo d) { … } // d 是按值传递
Demo obj(5);
foo(obj); // 触发拷贝构造(默认浅拷贝)

函数内部会调用 拷贝构造函数

默认拷贝构造函数就是浅拷贝

(2) 返回对象(按值返回)
Demo createDemo() {
Demo d(10);
return d; // 触发拷贝构造(默认浅拷贝)
}

编译器通常会做 返回值优化(RVO) 避免拷贝,但如果没有 RVO,也会发生浅拷贝

(3) 对象赋值
Demo d1(5);
Demo d2(10);
d2 = d1; // 默认赋值运算符 → 浅拷贝

这里 d2 的原 ptr 被覆盖

如果原 ptr 已经分配了内存,可能造成内存泄漏

同时 d1 和 d2 共享同一块内存 → double-free

3️⃣ 浅拷贝的问题

悬空指针:多个对象指向同一块动态分配内存

double-free:析构函数释放同一块内存两次

内存泄漏:赋值时忘记释放旧的资源

4️⃣ 解决方法

深拷贝(deep copy):在拷贝构造和赋值运算符中,重新分配内存并复制内容

使用智能指针或 std::string:自动管理内存,避免浅拷贝问题

下面看看我们的例子,使用深拷贝构造函数&‘=’运算符重写,解决这个棘手的问题:
1
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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
#include <iostream>
#include <string.h>

using namespace std;

class Point;

class PointUtils
{
private:
/* data */
public:
PointUtils
(/* args */);
Point pointSum(const Point& p1,const Point& p2);
~PointUtils
();
};

class Point{
private:
int x;
int y;
char *niceName;

public:
Point(){
niceName = NULL;
cout << "Point()" << endl;
}

// 深拷贝构造函数
Point(const Point& p){
cout << "Copy (const Point& p)" << endl;
this->x = p.x;
this->y = p.y;
if(p.niceName){
this->niceName = new char[strlen(p.niceName)+1];
strcpy(this->niceName,p.niceName);
}else{
this->niceName = nullptr;
}
}

// 赋值运算符
Point& operator=(const Point& other){
cout << "operator=" << endl;
if(this == &other) return *this;
x = other.x;
y = other.y;
delete[] niceName;
if(other.niceName){
niceName = new char[strlen(other.niceName)+1];
strcpy(niceName, other.niceName);
} else niceName = nullptr;
return *this;
}

Point(int x,int y,const char *niceName){
cout << "Point(int x,int y)" << "x:" << x <<",y:" << y << niceName << endl;
this->x = x;
this->y = y;

this->niceName = new char[strlen(niceName)+1];
strcpy(this->niceName,niceName);
}

void setX(int x){
this->x = x;
}

void setY(int y){
this->y = y;
}

int getX(){
return this->x;
}

int getY(){
return this->y;
}

friend Point PointUtils::pointSum(const Point& p1,const Point& p2);

// 重载 int + point
friend Point operator+(int a, Point& p){
cout << "operator+(int a, Point& p)" << endl;
Point result;
result.x = a + p.x;
result.y = a + p.y;
result.niceName = new char[10];
strcpy(result.niceName,"operator+");
return result;
}

//重载 point + int
friend Point operator+(Point& p, int a){
cout << "operator+(Point& p, int a)" << endl;
Point result;
result.x = a + p.x;
result.y = a + p.y;
result.niceName = new char[10];
strcpy(result.niceName,"operator+");
// result.niceName = nullptr;
return result;
}

// 重载 前++
Point& operator++(){
cout << "operator++()" << endl;
this->x = this->x + 1;
this->y = this->y + 1;
if (niceName)
{
delete[] niceName;
}
niceName = new char[10+1];
strcpy(niceName,"operator++");
return *this;
}

Point operator++(int){
Point temp(*this); // 保存当前值
x++; y++;
return temp; // 返回修改前的副本
}


void printInfo(){
cout << "printInfo" << "x:" << this->x << ",y:" << this->y;
if (niceName)
{
cout << niceName;
}
cout << endl;
}

~Point(){
cout << "~Point()" << "x:" << this->x << ",y:" << this->y;
if (niceName)
{
cout << "delete:" << niceName;
// delete只能删除 new 出来的,niceName = "abcd" 就不能删除哦!否则要出大问题
delete[] niceName;
}
cout << endl;
// cout << this << end;
}

};

PointUtils::PointUtils(/* args */)
{
}

Point PointUtils::pointSum(const Point& p1,const Point& p2){
Point result;
result.x = p1.x + p2.x;
result.y = p1.y + p2.y;

// 判断 p1.niceName 和 p2.niceName 是否为空
const char* name1 = p1.niceName ? p1.niceName : "";
const char* name2 = p2.niceName ? p2.niceName : "";

// 分配内存:长度 = name1 + name2 + 1('\0')
size_t len = strlen(name1) + strlen(name2) + 1;
result.niceName = new char[len];

// 拼接字符串
strcpy(result.niceName, name1);
strcat(result.niceName, name2);

return result;
}

PointUtils::~PointUtils()
{
}

int main(){
// Point p1(10,20);
// Point p2(20,30);
// PointUtils util;
// Point result = util.pointSum(p1,p2);
// Point result = p1.add(p2);
int ax[] = {1,2};
int ay[] = {1,2};
int bx[] = {3,4};
int by[] = {3,4};
PointUtils util;
Point pArray[2];

for(int i=0;i<2;i++){
cout << "For" << endl;
char* aNiceName = (char*)malloc(sizeof(char)*2+1);
sprintf(aNiceName, "a%d", i);
char* bNiceName = (char*)malloc(sizeof(char)*2+1);
sprintf(bNiceName, "b%d", i);
Point p1(ax[i],ay[i],aNiceName);
Point p2(bx[i],by[i],bNiceName);
Point result = util.pointSum(p1,p2);
result.printInfo();
// operator= 对于‘=’的重载生效,深拷贝!
pArray[i] = result;
free(aNiceName);
free(bNiceName);
}
cout << "***last dance***" << endl;

Point result2 = 3 + pArray[0];
Point result3 = pArray[1] + 3;
result2.printInfo();
result3.printInfo();
// 运算符重载,前++
++result3;
result3.printInfo();

result2++;
result2.printInfo();
return 0;
}
输出结果
1
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
Point()
Point()
For
Point(int x,int y)x:1,y:1a0
Point(int x,int y)x:3,y:3b0
Point()
printInfox:4,y:4a0b0
operator=
~Point()x:4,y:4delete:a0b0
~Point()x:3,y:3delete:b0
~Point()x:1,y:1delete:a0
For
Point(int x,int y)x:2,y:2a1
Point(int x,int y)x:4,y:4b1
Point()
printInfox:6,y:6a1b1
operator=
~Point()x:6,y:6delete:a1b1
~Point()x:4,y:4delete:b1
~Point()x:2,y:2delete:a1
***last dance***
operator+(int a, Point& p)
Point()
operator+(Point& p, int a)
Point()
printInfox:7,y:7operator+
printInfox:9,y:9operator+
operator++()
printInfox:10,y:10operator++
Copy (const Point& p)
not null?operator+~Point()x:7,y:7delete:operator+
printInfox:8,y:8operator+
~Point()x:10,y:10delete:operator++
~Point()x:8,y:8delete:operator+
~Point()x:6,y:6delete:a1b1
~Point()x:4,y:4delete:a0b0