🌱博客主页:青竹雾色间
🌱系列专栏:C++学习日记
😘博客制作不易欢迎各位👍点赞+⭐收藏+➕关注
✨人生如寄,多忧何为 ✨
目录
在C++编程中,类与对象是面向对象编程(OOP)的核心概念之一。了解类与对象的基本概念以及常用操作,对于提高程序的可读性、可维护性和复用性至关重要。本篇博客将深入探讨C++中类与对象的相关内容,包括类的默认成员函数、构造函数、析构函数、拷贝构造函数、赋值运算符重载、const成员函数、取地址及const取地址操作符、重载移动构造函数(C++11);重载移动赋值操作符函数(C++11)
类的8个默认成员函数
1. 构造函数
构造函数是特殊的成员函数,需要注意的是,构造函数虽然名称叫构造,但是构造函数的主要任务并不是开空间创建对象,而是用于在创建对象时进行初始化操作。
其特征如下:
- 函数名与类名相同。
- 无返回值。
- 对象实例化时编译器自动调用对应的构造函数。
- 构造函数可以重载以支持不同的参数列表
例如:
class Rectangle {
private:
int length;
int width;
public:
// 有参构造函数
Rectangle(int len, int wid) {
length = len;
width = wid;
}
// 无参构造函数
Rectangle() {
}
};
void TestDate()
{
Rectangle t1; // 调用无参构造函数
Rectangle t2( 1, 1); // 调用带参的构造函数
// 注意:如果通过无参构造函数创建对象时,对象后面不用跟括号,否则就成了函数声明
}
ps:. 如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦用户显式定义编译器将不再生成。
无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个
。注意:无参构造函数、全缺省构造函数、我们没写编译器默认生成的构造函数,都可以认为是默认构造函数。
2. 析构函数
析构函数是特殊的成员函数用于在对象销毁时执行清理工作,释放对象占用的资源,如动态分配的内存、关闭文件等,其特征如下:
- 析构函数名是在类名前加上字符波浪号
~
。 - 无参数无返回值类型。
- 一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。注意:析构函数不能重载
- 对象生命周期结束时,C++编译系统系统自动调用析构函数
例如:
#include <iostream>
class Resource {
public:
Resource() {
std::cout << "Resource acquired" << std::endl;
}
~Resource() {
std::cout << "Resource released" << std::endl;
}
};
class MyClass {
private:
Resource* ptr;
public:
MyClass() {
ptr = new Resource();
std::cout << "MyClass Constructor called" << std::endl;
}
~MyClass() {
delete ptr; // 释放动态分配的资源
std::cout << "MyClass Destructor called" << std::endl;
}
};
int main() {
MyClass obj; // 构造函数被调用
// 一些代码...
return 0; // 析构函数被调用,资源被释放
}
ps:如果类中没有申请资源时,析构函数可以不写,直接使用编译器生成的默认析构函数;有资源申请时,一定要写,否则会造成资源泄漏.
3. 拷贝构造函数
拷贝构造函数的作用是创建一个新对象并将其初始化为另一个已存在对象的副本。它是特殊的成员函数,当新对象被创建时,编译器会自动调用它。拷贝构造函数有以下特点:
- 拷贝构造函数是构造函数的一种重载形式,用于特定的拷贝初始化场景。
- 拷贝构造函数的参数只有一个,通常是类类型对象的引用。为了避免意外修改被拷贝对象,参数通常会被声明为const引用。如果使用传值方式传递参数,会导致无限递归调用,因此通常会编译器直接报错。
示例代码:
#include <iostream>
class MyClass {
private:
int data;
public:
// 默认构造函数
MyClass()
: data(0)
{}
// 拷贝构造函数
MyClass(const MyClass& other) {
std::cout << "拷贝构造函数被调用" << std::endl;
data = other.data; // 将其他对象的数据复制给当前对象
}
// 设置数据的方法
void setData(int value) {
data = value;
}
// 获取数据的方法
int getData() const {
return data;
}
};
int main() {
MyClass obj1;
obj1.setData(10);
// 使用拷贝构造函数创建新对象
MyClass obj2 = obj1;
std::cout << "obj1 的数据为:" << obj1.getData() << std::endl;
std::cout << "obj2 的数据为:" << obj2.getData() << std::endl;
return 0;
}
在这个示例中,包括一个简单的类 MyClass
,其中包含默认构造函数、拷贝构造函数以及设置和获取数据的方法。在 main()
函数中,我们创建了一个对象 obj1
并设置其数据为10,然后使用拷贝构造函数创建了另一个对象 obj2
,其数据与 obj1
相同。
值得注意的是,若未显式定义拷贝构造函数,编译器会生成默认的拷贝构造函数默认的拷贝构造函数对象按内存存储按字节序完成拷贝,这种拷贝叫做浅拷贝,或者值拷贝。
默认的拷贝构造函数会按照字节方式直接拷贝内置类型的数据,
而对于自定义类型,则会调用其拷贝构造函数完成拷贝。
当涉及资源申请时,拷贝构造函数是必须要写的,以避免浅拷贝带来的问题。
为了提高程序效率,一般对象传参时,尽量使用引用类型,返回时根据实际场景,能用引用尽量使用引用。
拷贝构造函数典型调用场景包括:
- 使用已存在对象创建新对象
- 函数参数类型为类类型对象
- 函数返回值类型为类类型对象
4. 运算符重载
C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,其函数名由关键字operator
后接需要重载的运算符符号组成。函数原型为返回值类型 operator操作符(参数列表)
,其返回值类型与参数列表与普通的函数类似。
注意事项:
- 不能通过连接其他符号来创建新的操作符,比如
operator@
。 - 重载操作符必须至少有一个类类型参数。对于内置类型的运算符,其含义不能改变,例如内置的整型
+
,不能改变其含义。 - 作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐藏的
this
指针。 - 以下5个运算符不能重载:
:
、.*
、::
、sizeof
、?
。
4.1 赋值运算符重载
赋值运算符重载允许程序员自定义类对象之间的赋值操作。通过重载赋值运算符,可以实现对象的深度拷贝或其他特定操作。赋值运算符重载通常以成员函数的形式定义。
赋值运算符重载格式:
- 参数类型为
const T&
,传递引用可以提高传参效率。 - 返回值类型为
T&
,返回引用可以提高返回的效率,有返回值的目的是为了支持连续赋值。 - 检测是否自己给自己赋值。
- 返回
*this
,以支持连续赋值。
class MyClass {
public:
MyClass& operator=(const MyClass& rhs) {
if (this == &rhs) // 检测是否自己给自己赋值
return *this;
// 执行赋值操作
// 返回*this,以支持连续赋值
return *this;
}
};
赋值运算符只能重载为类的成员函数,不能重载为全局函数。原因在于,如果未显式实现赋值运算符,编译器会生成一个默认的。此时,如果用户再在类外自己实现一个全局的赋值运算符重载,就会和编译器在类中生成的默认赋值运算符重载产生冲突。因此,赋值运算符重载只能是类的成员函数。
如果类中未涉及到资源管理,赋值运算符是否实现都可以。一旦涉及到资源管理,则必须实现。
4.2 前置++和后置++重载
class Counter {
private:
int count;
public:
// 前置++
Counter& operator++() {
++count;
return *this;
}
// 后置++
Counter operator++(int) {
Counter temp(*this);
++(*this);
return temp;
}
};
在前置++中,先将对象的值加一,然后返回加一后的对象;而在后置++中,先创建一个临时对象保存加一前的值,再将对象的值加一,最后返回保存的临时对象。## 5. const成员函数
const成员函数是指在函数声明或定义时使用const关键字修饰的成员函数。它们承诺不会修改对象的任何成员变量,因此可以在const对象上调用。const成员函数通常用于实现只读操作或避免意外修改对象状态的情况。例如:
class Circle {
public:
// const成员函数
double getArea() const {
// 计算并返回面积,但不修改对象成员变量
}
};
以下是对您提供的文章进行润色后的版本:
5.取地址操作符重载
当你需要在C++中对自定义类型进行地址操作时,你可以通过重载取地址操作符(&
)来实现。下面是一个简单的示例,展示了如何为自定义类重载取地址操作符:
#include <iostream>
class MyClass {
private:
int data;
public:
MyClass(int d) : data(d) {}
int getData() const {
return data;
}
// 重载取地址操作符
MyClass* operator&() {
return this;
}
};
int main() {
MyClass obj(10);
// 使用取地址操作符获取对象地址
MyClass* ptr = &obj;
// 输出对象地址及其数据
std::cout << "Address of obj: " << ptr << std::endl;
std::cout << "Data of obj: " << ptr->getData() << std::endl;
return 0;
}
在这个例子中,MyClass
类中重载了取地址操作符 operator&()
,使其返回对象的地址。在 main()
函数中,我们创建了一个 MyClass
类的对象 obj
,然后使用 &
操作符获取了该对象的地址,并将其存储在指针 ptr
中。最后,输出了对象的地址和数据。
ps:重载取地址操作符可能会改变语言的基本语义,因此需要谨慎使用,并确保其行为符合预期。
6. const 取地址操作符重载
const 成员
将 const 修饰的“成员函数”称之为 const 成员函数。const 修饰类成员函数时,实际上修饰的是该成员函数隐含的 this 指针,表明在该成员函数中不能对类的任何成员进行修改。
请思考下面的几个问题:
-
const 对象可以调用非 const 成员函数吗?
- 可以。因为非 const 成员函数不会修改对象的状态,所以允许 const 对象调用它们。
-
非 const 对象可以调用 const 成员函数吗?
- 可以。const 成员函数保证不修改对象的状态,因此对于非 const 对象也是安全的。
-
const 成员函数内可以调用其它的非 const 成员函数吗?
- 可以。const 成员函数本身不修改对象的状态,调用非 const 成员函数也不会违反 const 成员函数的约束。
-
非 const 成员函数内可以调用其它的 const 成员函数吗?
- 可以。非 const 成员函数可以修改对象的状态,但调用 const 成员函数不会导致对象状态的改变,因此是允许的。
const 取地址操作符重载
对象的取地址操作符用于获取对象在内存中的地址。对于 const 对象,其地址应该是常量。通过重载 const 取地址操作符,可以返回一个常量指针,防止修改 const 对象。例如:
class MyClass {
private:
int data;
public:
// const 取地址操作符重载
const int* operator&() const {
return &data;
}
};
7. 移动构造函数(C++11)
移动构造函数是 C++11 中引入的一个新特性,用于在对象之间转移资源所有权而不进行深度拷贝,从而提高程序的性能。移动构造函数通常与右值引用一起使用,其语法形式为 类型名(类型名&&)
。当编译器检测到右值时,会调用移动构造函数而不是拷贝构造函数。
示例:
#include <iostream>
class MyResource {
private:
int* data;
public:
MyResource() : data(new int(0)) {
std::cout << "Resource acquired" << std::endl;
}
// 移动构造函数
MyResource(MyResource&& other) noexcept : data(other.data) {
other.data = nullptr;
std::cout << "Move constructor called" << std::endl;
}
~MyResource() {
if (data != nullptr) {
delete data;
std::cout << "Resource released" << std::endl;
}
}
};
int main() {
MyResource res1;
MyResource res2 = std::move(res1); // 使用移动构造函数
return 0;
}
在这个示例中,MyResource
类中定义了移动构造函数,当 res1
被 std::move()
转移后,将调用移动构造函数,将 res1
的资源所有权转移到 res2
中。
8. 重载移动赋值操作符函数(C++11)
与移动构造函数类似,移动赋值操作符函数也是 C++11 中引入的一个新特性,用于在对象之间转移资源所有权。移动赋值操作符函数通常与右值引用一起使用,其语法形式为 类型名& operator=(类型名&&)
。当编译器检测到右值时,会调用移动赋值操作符函数。
示例:
#include <iostream>
class MyResource {
private:
int* data;
public:
MyResource() : data(new int(0)) {
std::cout << "Resource acquired" << std::endl;
}
// 移动构造函数
MyResource(MyResource&& other) noexcept : data(other.data) {
other.data = nullptr;
std::cout << "Move constructor called" << std::endl;
}
// 移动赋值操作符函数
MyResource& operator=(MyResource&& other) noexcept {
if (this != &other) {
delete data;
data = other.data;
other.data = nullptr;
std::cout << "Move assignment operator called" << std::endl;
}
return *this;
}
~MyResource() {
if (data != nullptr) {
delete data;
std::cout << "Resource released" << std::endl;
}
}
};
int main() {
MyResource res1;
MyResource res2;
res2 = std::move(res1); // 使用移动赋值操作符函数
return 0;
}
在这个示例中,MyResource
类中定义了移动赋值操作符函数,当 res1
被 std::move()
转移后,将调用移动赋值操作符函数,将 res1
的资源所有权转移到 res2
中。
总结
通过理解以上这些基本概念和常用操作,我们可以更好地设计和使用类与对象,提高代码的可维护性和可扩展性。同时,合理利用构造函数、析构函数、拷贝构造函数、赋值运算符重载、 重载移动构造函数、重载移动赋值操作符函数等特性,可以更高效地管理资源和对象的生命周期,避免内存泄漏和资源浪费。
希望本篇博客能够帮助读者