C++学习:类和对象(二)
一、默认成员函数
1. 什么是默认成员函数?
在C++中,每个类都有一些特殊的成员函数,如果程序员没有显式地声明,编译器会自动为类生成这些函数,这些函数称为默认成员函数
2. 默认成员函数列表
- 默认构造函数(Default Constructor)
- 默认析构函数(Destructor)
- 默认拷贝构造函数(Copy Constructor)
- 默认拷贝赋值运算符(Copy Assignment Operator)
- 默认移动构造函数(Move Constructor,C++11引入)
- 默认移动赋值运算符(Move Assignment Operator,C++11引入)
3. 编译器何时生成默认成员函数?
- 显式声明:如果程序员没有提供某个默认成员函数的定义,编译器会根据需要自动生成
- 特别注意:一旦程序员显式地声明了任何一个拷贝或移动操作,编译器将不会为该类生成移动操作,需要手动提供
4. 代码示例
#include <iostream>
using namespace std;
class Example {
public:
int value;
// 未显式声明任何默认成员函数
};
int main() {
Example ex1; // 调用默认构造函数
ex1.value = 10;
Example ex2 = ex1; // 调用默认拷贝构造函数
Example ex3;
ex3 = ex1; // 调用默认拷贝赋值运算符
cout << "ex1.value = " << ex1.value << endl;
cout << "ex2.value = " << ex2.value << endl;
cout << "ex3.value = " << ex3.value << endl;
return 0;
}
二、构造函数
1. 什么是构造函数?
构造函数(Constructor)是在创建对象时自动调用的特殊成员函数,用于初始化对象的成员变量。构造函数的名称与类名相同
2. 特点
- 没有返回类型(连
void
也没有) - 可以有参数(参数化构造函数)
- 支持函数重载,即可以有多个构造函数
- 可以在构造函数初始化列表中初始化成员变量
3. 默认构造函数
如果程序员未提供任何构造函数,编译器会为类生成一个默认构造函数,它对基本类型成员变量不进行初始化
4. 代码示例:
1.默认构造函数
#include <iostream>
using namespace std;
class Person {
public:
string name;
int age;
// 默认构造函数
Person() {
name = "Unknown";
age = 0;
}
void display() const {
cout << "姓名:" << name << ", 年龄:" << age << endl;
}
};
int main() {
Person p; // 调用默认构造函数
p.display();
return 0;
}
2.参数化构造函数
#include <iostream>
using namespace std;
class Person {
public:
string name;
int age;
// 参数化构造函数
Person(const string& n, int a) {
name = n;
age = a;
}
void display() const {
cout << "姓名:" << name << ", 年龄:" << age << endl;
}
};
int main() {
Person p("Alice", 25); // 调用参数化构造函数
p.display();
return 0;
}
3.构造函数初始化列表
#include <iostream>
using namespace std;
class Point {
private:
int x;
int y;
public:
// 使用初始化列表初始化成员变量
Point(int xCoord, int yCoord) : x(xCoord), y(yCoord) {}
void display() const {
cout << "坐标:(" << x << ", " << y << ")" << endl;
}
};
int main() {
Point pt(3, 4);
pt.display();
return 0;
}
5. 注意事项
- 如果类中有
const
成员变量或引用类型成员,必须使用初始化列表进行初始化 - 构造函数可以被重载,允许创建多个具有不同参数列表的构造函数
三、析构函数
1. 什么是析构函数?
析构函数(Destructor)是在对象生命周期结束时自动调用的特殊成员函数,用于释放对象占用的资源(如内存、文件等)。析构函数的名称是在类名前加上~
符号
2. 特点
- 没有参数
- 没有返回类型
- 每个类只有一个析构函数,不能重载
- 编译器会自动调用析构函数,无需手动调用
3. 代码示例
#include <iostream>
using namespace std;
class Resource {
public:
Resource() {
cout << "资源已分配。" << endl;
}
~Resource() {
cout << "资源已释放。" << endl;
}
};
int main() {
cout << "进入main函数。" << endl;
{
Resource res; // 创建对象,调用构造函数
} // 离开作用域,调用析构函数
cout << "退出main函数。" << endl;
return 0;
}
4. 在析构函数中释放资源
当类中使用了动态内存分配(如使用new
关键字),需要在析构函数中释放内存,防止内存泄漏
#include <iostream>
using namespace std;
class Array {
private:
int* data;
int size;
public:
Array(int s) : size(s) {
data = new int[size];
cout << "数组已分配。" << endl;
}
~Array() {
delete[] data;
cout << "数组已释放。" << endl;
}
};
int main() {
Array arr(10);
// 使用数组...
return 0;
}
5. 注意事项
- 析构函数必须为公有成员函数,否则对象在离开作用域时无法正确销毁
- 避免在析构函数中抛出异常,这可能导致程序不可预测的行为
四、拷贝构造函数
1. 什么是拷贝构造函数?
拷贝构造函数(Copy Constructor)是使用同类的另一个对象来初始化新对象时调用的构造函数。它用于定义对象的拷贝行为
2. 语法
ClassName(const ClassName& other);
参数为同类对象的引用,通常为const
引用,避免不必要的拷贝
3. 默认拷贝构造函数
- 如果程序员未提供拷贝构造函数,编译器会自动生成默认的拷贝构造函数,执行浅拷贝
- 对于没有动态内存分配的类,默认的拷贝构造函数通常够用
4. 浅拷贝与深拷贝
- 浅拷贝(Shallow Copy):拷贝对象的成员变量值,对于指针成员,仅拷贝指针值,两个对象指向同一内存位置
- 深拷贝(Deep Copy):在拷贝指针成员时,为新对象分配独立的内存空间,并复制内容
5. 示例
1.默认拷贝构造函数(浅拷贝)
#include <iostream>
using namespace std;
class Shallow {
public:
int* data;
Shallow(int val) {
data = new int(val);
}
~Shallow() {
delete data;
}
};
int main() {
Shallow obj1(5);
Shallow obj2 = obj1; // 调用默认拷贝构造函数
cout << "obj1.data = " << *(obj1.data) << endl;
cout << "obj2.data = " << *(obj2.data) << endl;
// 修改obj1的数据
*(obj1.data) = 10;
cout << "修改obj1后:" << endl;
cout << "obj1.data = " << *(obj1.data) << endl;
cout << "obj2.data = " << *(obj2.data) << endl;
return 0;
}
问题:
由于是浅拷贝,obj1
和obj2
的data
指向同一内存,当一个对象被析构时,内存被释放,另一个对象再使用时会导致悬空指针
2.自定义拷贝构造函数(深拷贝)
#include <iostream>
using namespace std;
class Deep {
public:
int* data;
Deep(int val) {
data = new int(val);
}
// 自定义拷贝构造函数
Deep(const Deep& other) {
data = new int(*(other.data));
}
~Deep() {
delete data;
}
};
int main() {
Deep obj1(5);
Deep obj2 = obj1; // 调用自定义拷贝构造函数
cout << "obj1.data = " << *(obj1.data) << endl;
cout << "obj2.data = " << *(obj2.data) << endl;
// 修改obj1的数据
*(obj1.data) = 10;
cout << "修改obj1后:" << endl;
cout << "obj1.data = " << *(obj1.data) << endl;
cout << "obj2.data = " << *(obj2.data) << endl;
return 0;
}
6. 注意事项
- 拷贝构造函数的参数必须是引用,否则会导致无限递归
- 当类中有指针成员,且需要独立的内存空间,必须提供自定义的拷贝构造函数(深拷贝)
五、赋值运算符函数
1. 什么是赋值运算符函数?
赋值运算符函数(Assignment Operator Function)用于定义对象间赋值行为(operator=
)。类似于拷贝构造函数,它也需要考虑浅拷贝和深拷贝
2. 语法
ClassName& operator=(const ClassName& other);
- 返回类型为引用,返回当前对象自身
*this
,以支持链式赋值 - 参数为同类对象的**
const
引用**
3. 默认赋值运算符
- 如果程序员未提供赋值运算符函数,编译器会生成默认的赋值运算符,执行浅拷贝
4. 示例
1.默认赋值运算符(浅拷贝)
#include <iostream>
using namespace std;
class Shallow {
public:
int* data;
Shallow(int val) {
data = new int(val);
}
~Shallow() {
delete data;
}
};
int main() {
Shallow obj1(5);
Shallow obj2(10);
obj2 = obj1; // 使用默认赋值运算符
cout << "obj1.data = " << *(obj1.data) << endl;
cout << "obj2.data = " << *(obj2.data) << endl;
// 修改obj1的数据
*(obj1.data) = 15;
cout << "修改obj1后:" << endl;
cout << "obj1.data = " << *(obj1.data) << endl;
cout << "obj2.data = " << *(obj2.data) << endl;
return 0;
}
2.自定义赋值运算符函数(深拷贝)
#include <iostream>
using namespace std;
class Deep {
public:
int* data;
Deep(int val) {
data = new int(val);
}
Deep(const Deep& other) {
data = new int(*(other.data));
}
// 自定义赋值运算符函数
Deep& operator=(const Deep& other) {
if (this == &other) {
return *this; // 检查自赋值
}
delete data; // 释放原有内存
data = new int(*(other.data)); // 分配新内存并拷贝
return *this;
}
~Deep() {
delete data;
}
};
int main() {
Deep obj1(5);
Deep obj2(10);
obj2 = obj1; // 使用自定义赋值运算符
cout << "obj1.data = " << *(obj1.data) << endl;
cout << "obj2.data = " << *(obj2.data) << endl;
// 修改obj1的数据
*(obj1.data) = 15;
cout << "修改obj1后:" << endl;
cout << "obj1.data = " << *(obj1.data) << endl;
cout << "obj2.data = " << *(obj2.data) << endl;
return 0;
}
5. 注意事项
- 检查自赋值:在赋值运算符函数中,应检查
this
和other
是否为同一对象,避免释放自己 - 释放原有资源:在进行赋值前,应释放对象原有的资源,防止内存泄漏
- 返回
*this
的引用,支持链式赋值
六、const
成员函数
1. 什么是const
成员函数?
const
成员函数是指在函数声明后加上const
关键字的成员函数,表示该函数不会修改对象的成员变量(除非成员变量被声明为mutable
)
2. 语法
返回类型 函数名(参数列表) const;
3. 特点
const
成员函数只能调用其他const
成员函数,不能调用非const
成员函数- 可以被
const
对象调用,而非const
成员函数不能被const
对象调用
4. 代码示例
#include <iostream>
using namespace std;
class Sample {
private:
int value;
public:
Sample(int v) : value(v) {}
int getValue() const {
return value;
}
void setValue(int v) {
value = v;
}
};
int main() {
const Sample s(10); // 常量对象
cout << "值是:" << s.getValue() << endl;
// s.setValue(20); // 错误,不能调用非const成员函数
return 0;
}
5. const
对象和成员函数
- **
const
对象:**对象被声明为const
,只能调用其const
成员函数,不能修改成员变量 - **非
const
对象:**可以调用所有成员函数,包括const
和非const
6. 成员函数重载
可以根据const
性对成员函数进行重载
代码示例:
#include <iostream>
using namespace std;
class Example {
public:
void func() {
cout << "非const版本的func()" << endl;
}
void func() const {
cout << "const版本的func()" << endl;
}
};
int main() {
Example e;
e.func(); // 调用非const版本
const Example ce;
ce.func(); // 调用const版本
return 0;
}
七、取地址及const
取地址操作符重载
1. 取地址操作符operator&
默认情况下,对象的取地址操作会返回对象的内存地址。可以通过重载operator&
来改变取地址操作的行为
2. 为什么需要重载取地址操作符?
- 在某些情况下,我们希望隐藏对象的内部地址,或提供特殊的地址计算方式
- 可以用于智能指针的实现
3. 语法
ClassName* operator&();
const ClassName* operator&() const;
4. 示例
1.重载取地址操作符
#include <iostream>
using namespace std;
class MyClass {
public:
int value;
MyClass(int v) : value(v) {}
// 重载取地址操作符
MyClass* operator&() {
cout << "自定义取地址操作符被调用。" << endl;
return this;
}
// 重载const版本
const MyClass* operator&() const {
cout << "自定义const取地址操作符被调用。" << endl;
return this;
}
};
int main() {
MyClass obj(10);
MyClass* ptr = &obj; // 调用自定义取地址操作符
cout << "value = " << ptr->value << endl;
const MyClass cobj(20);
const MyClass* cptr = &cobj; // 调用自定义const取地址操作符
cout << "const value = " << cptr->value << endl;
return 0;
}
5. 注意事项
- 小心使用:重载取地址操作符可能导致代码难以理解和维护,应该谨慎使用
- 避免陷阱:重载后,可能会影响模板代码或标准库的使用,需要确保兼容性