构造函数的分类
- 构造函数重载:构造函数可以通过不同的参数列表进行重载,这意味着可以有多个构造函数,每个构造函数有不同的参数。
- 多参构造函数:通过传递多个参数来创建对象。
- 无参(缺省)构造函数:不需要参数来创建对象。
- 类型转换构造函数:使用不同类型的参数来创建对象。
- 拷贝构造函数:使用同类型的另一个对象来创建新对象。
代码演示
#include <iostream>
#include <cstring>
using namespace std;
class Book {
public:
// 无参构造函数
Book() {
title = "未命名";
author = "未知";
pages = 0;
cout << "无参构造函数被调用" << endl;
}
// 带参构造函数,使用 const char* 类型参数
Book(const char* t, const char* a, int p)
: title(t), author(a), pages(p) {
cout << "带参构造函数被调用" << endl;
}
// 重载带参构造函数,支持 string 参数
Book(const string& t, const string& a, int p)
: Book(t.c_str(), a.c_str(), p) /*委托构造函数*/{}
// 拷贝构造函数
Book(const Book& b) {
title = b.title;
author = b.author;
pages = b.pages;
cout << "拷贝构造函数被调用" << endl;
}
// 显示图书信息
void displayInfo() {
cout << "书名: " << title << ", 作者: " << author <<
", 页数: " << pages << endl;
}
private:
string title;
string author;
int pages;
};
// 主函数
int main() {
// 使用无参构造函数创建对象
Book book1;
book1.displayInfo();
// 使用带参构造函数创建对象,传递 C 风格字符串
Book book2("C++ Primer", "Stanley Lippman", 976);
book2.displayInfo();
// 使用带参构造函数创建对象,传递 string 对象
string title = "Effective C++";
string author = "Scott Meyers";
Book book3(title, author, 320);
book3.displayInfo();
// 使用拷贝构造函数创建对象
Book book4 = book2;
book4.displayInfo();
return 0;
}
(1).std::string 类型:
- std::string 是 C++ 标准库提供的字符串类,封装了字符数组,并提供了很多方便的操作方法。
- 但是 std::string 和 C 风格的字符串(const char*)是不同的类型,不能直接传递给接受 const char* 参数的函数或构造函数。
(2).c_str() 方法:
- c_str() 是 std::string 类中的一个成员函数,它返回一个指向以 null 终止的字符数组的指针(const char*),这个数组表示与调用对象相同的字符序列。
- 换句话说,t.c_str() 将 std::string 类型的 t 转换为 C 风格的字符串(const char*),同理,a.c_str() 也是如此。
(3) Book(t.c_str(), a.c_str(), p) 的含义:
- 这行代码调用了 Book 类的构造函数,将 t 和 a 这两个 std::string 类型的字符串转换为 const char* 类型,再加上一个整数 p,一起传递给 Book 的构造函数。
- 最终,Book 类的构造函数使用 const char* 类型的 title 和 author 参数以及整数 pages 来初始化 Book 对象。
浅拷贝与深拷贝
概述
- 浅拷贝(Shallow Copy)和深拷贝(Deep Copy)是两种不同的对象复制方式,主要区别在于它们如何处理对象内部的指针或动态分配的资源。
- 浅拷贝:只是复制对象的各个成员的值,对于指针成员,浅拷贝只复制指针的值(即内存地址),而不复制指针所指向的内存内容。结果是多个对象共享同一块内存。
- 深拷贝:不仅复制对象的各个成员的值,还会为指针成员分配新的内存,并复制指针指向的实际内容。结果是每个对象都有自己的一份独立的内存副本。
- 浅拷贝:只是复制对象的各个成员的值,对于指针成员,浅拷贝只复制指针的值(即内存地址),而不复制指针所指向的内存内容。结果是多个对象共享同一块内存。
浅拷贝的实现与问题
浅拷贝的实现通常由编译器默认提供的拷贝构造函数完成,它执行的是成员按位复制(bitwise copy)。
- 问题:
- 如果两个对象共享同一块内存,修改其中一个对象的数据会影响另一个对象。
- 当其中一个对象被销毁时,另一对象的指针可能变成悬空指针,导致潜在的内存错误。
- 如果两个对象共享同一块内存,修改其中一个对象的数据会影响另一个对象。
代码演示
#include <iostream>
using namespace std;
class ShallowCopyExample {
public:
int* data;
// 构造函数
ShallowCopyExample(int value) {
data = new int(value);
}
// 拷贝构造函数 - 浅拷贝
ShallowCopyExample(const ShallowCopyExample &source)
: data(source.data) {
cout << "浅拷贝构造函数被调用" << endl;
}
// 显示数据
void display() const {
cout << "Data: " << *data << endl;
}
// 析构函数
~ShallowCopyExample() {
delete data;
cout << "析构函数被调用" << endl;
}
};
int main() {
ShallowCopyExample obj1(42);
ShallowCopyExample obj2 = obj1; // 浅拷贝
obj1.display();
obj2.display();
// 修改 obj1 的数据
*obj1.data = 84;
obj1.display();
obj2.display(); // obj2 也会受到影响
return 0;
}
在浅拷贝中,obj1 和 obj2 共享同一块内存,因此修改 obj1 的数据会影响到 obj2。当 obj1 和 obj2 都超出作用域时,会分别调用它们的析构函数,尝试释放同一块内存,导致双重释放错误(double free)
深拷贝的实现与解决方法
解决办法
在编写类时,如果类包含指针或动态分配的资源,通常需要提供自定义的拷贝构造函数和赋值运算符,以实现深拷贝,确保每个对象拥有自己的内存副本,而不是与其他对象共享内存。这种方式可以避免潜在的内存错误,保证程序的健壮性。
代码实现
#include <iostream>
using namespace std;
class DeepCopyExample {
public:
int* data;
// 构造函数
DeepCopyExample(int value) {
data = new int(value);
}
// 拷贝构造函数 - 深拷贝
DeepCopyExample(const DeepCopyExample &source) {
data = new int(*source.data);
//strcpy(data,source.data) //同样可以实现深拷贝
cout << "深拷贝构造函数被调用" << endl;
}
// 显示数据
void display() const {
cout << "Data: " << *data << endl;
}
// 析构函数
~DeepCopyExample() {
delete data;
cout << "析构函数被调用" << endl;
}
};
int main() {
DeepCopyExample obj1(42);
DeepCopyExample obj2 = obj1; // 深拷贝
obj1.display();
obj2.display();
// 修改 obj1 的数据
*obj1.data = 84;
obj1.display();
obj2.display(); // obj2 不受影响
return 0;
}
在深拷贝中,obj1 和 obj2 各自拥有一份独立的内存。因此,修改 obj1 的数据不会影响 obj2。每个对象的析构函数都会释放自己独立的内存,从而避免双重释放(double free)错误。
总结
浅拷贝:适合不涉及动态内存或资源管理的简单对象。对于指针成员,浅拷贝可能会导致多个对象共享同一内存,容易出现资源管理问题,如双重释放、悬空指针等。
深拷贝:适用于包含动态内存或资源管理的复杂对象。深拷贝确保每个对象有自己独立的资源,避免了共享资源带来的问题。
初始化表
初始化列表提供了一种直接初始化成员变量的方法,而不是在构造函数体内进行赋值。这样不仅代码更加简洁,还可以提高性能,特别是在需要初始化 const
或引用类型成员时。
改进代码1
#include <iostream>
#include <cstring>
using namespace std;
class Book {
public:
// 无参构造函数,使用初始化列表
Book() : title("未命名"), author("未知"), pages(0) {
cout << "无参构造函数被调用" << endl;
}
// 带参构造函数,使用 const char* 类型参数,使用初始化列表
Book(const char* t, const char* a, int p)
: title(t), author(a), pages(p) {
cout << "带参构造函数被调用" << endl;
}
// 重载带参构造函数,支持 string 参数,使用初始化列表
Book(const string& t, const string& a, int p)
: title(t), author(a), pages(p) {
cout << "带参构造函数被调用" << endl;
}
// 拷贝构造函数,使用初始化列表
Book(const Book& b)
: title(b.title), author(b.author), pages(b.pages) {
cout << "拷贝构造函数被调用" << endl;
}
// 显示图书信息
void displayInfo() const {
cout << "书名: " << title << ", 作者: " << author
<< ", 页数: " << pages << endl;
}
private:
string title;
string author;
int pages;
};
// 主函数
int main() {
// 使用无参构造函数创建对象
Book book1;
book1.displayInfo();
// 使用带参构造函数创建对象,传递 C 风格字符串
Book book2("C++ Primer", "Stanley Lippman", 976);
book2.displayInfo();
// 使用带参构造函数创建对象,传递 string 对象
string title = "Effective C++";
string author = "Scott Meyers";
Book book3(title, author, 320);
book3.displayInfo();
// 使用拷贝构造函数创建对象
Book book4 = book2;
book4.displayInfo();
return 0;
}
代码说明
无参构造函数:
- 改进前:在构造函数体内赋值。
- 改进后:使用初始化列表直接初始化成员变量 title、author 和 pages。
带参构造函数:
- 改进前:在构造函数体内赋值。
- 改进后:使用初始化列表直接初始化 title、author 和 pages,减少了赋值操作的开销。
拷贝构造函数:
- 改进前:在构造函数体内进行赋值。
- 改进后:使用初始化列表直接将 b.title、b.author 和 b.pages 赋值给新对象的成员变量。
重载带参构造函数:
- 使用 string 作为参数时,直接在初始化列表中初始化 title 和 author,避免了不必要的 c_str() 转换。
在拷贝构造函数引入初始化表
#include <iostream>
#include <cstring> // 包含 strcpy 所需的头文件
using namespace std;
class String {
public:
// 构造函数,动态分配内存并复制字符串内容
String(const char* str) : data ( new char[strlen(str) + 1]) {
strcpy(data, str); // 深拷贝字符串内容
cout << "构造函数被调用" << endl;
}
// 拷贝构造函数,进行深拷贝
String(const String &source) : data(new char[strlen(source.data) + 1]) {
strcpy(data, source.data); // 深拷贝字符串内容
cout << "深拷贝构造函数被调用" << endl;
}
// 显示字符串
void display() const {
if (data) {
cout << "Data: " << data << endl;
} else {
cout << "Data is null" << endl;
}
}
// 析构函数
~String() {
// 注意:这里的 delete[] 仅当数据是由深拷贝生成时使用,否则可能导致未定义行为
delete[] data;
cout << "析构函数被调用" << endl;
}
private:
char* data; // 指向传入字符串的指针
};
int main() {
char str[] = "Hello, World!";
String s1(str); // 动态分配内存并复制字符串内容到 s1.data
s1.display();
String s2 = s1; // 深拷贝,s2 的 data 指向新的内存,内容与 s1 相同
s2.display();
// 修改原字符数组
str[0] = 'h';
// 直接输出原字符数组 str 的内容
cout << "str: " << str << endl;
s1.display(); // s1 的内容不变,因为它是深拷贝,独立于原字符数组
s2.display(); // s2 的内容不变,因为它是深拷贝,独立于 s1
return 0;
}
标签:std,初始化,对象,详解,拷贝,data,构造函数
From: https://blog.csdn.net/qq_62850449/article/details/141886290