【类与对象】--对象之舞,类之华章,共绘C++之美

前言

在 C++ 中,构造函数、析构函数和拷贝构造函数是管理对象生命周期的关键功能。这三者在类的使用中起着至关重要的作用,它们帮助程序员负责对象的创建、内存管理以及对象间的复制。本文将详细探讨这三种函数的定义、特点以及使用示例,帮助你更好地理解这些概念。

目录

一、类的定义

二、构造函数

三、析构函数

四、拷贝构造函数

五、运算符重载

5.1为什么需要赋值运算符重载

5.2赋值运算符重载的实现

5.3测试实现

六、友元

6.1友元的定义

6.1.2声明和实现

6.2友元的用途

6.3友元类

6.3.1定义

[6..3.2 友元类用途](#6..3.2 友元类用途)

6.4友元函数与友元类的注意事项


一、类的定义

class ClaeeName

{

//类体

};

在上述代码中,class为定义类的关键字,ClassName为类名,括号里面的是主体

二、构造函数

2.1构造函数 是一个特殊的成员函数,名字与类名相同****,**创建类类型对象时由编译器自动调用,以保证每个数据成员都有 一个合适的初始值,并且在对象整个生命周期内只调用一次**

特点

  • 名称与类名相同: 构造函数的名称必须与类名完全相同。
  • 可以重载: C++ 允许定义多个构造函数,只要它们的参数列表不同(例如参数的类型或数量)。
  • 默认构造函数:如果没有定义任何构造函数,编译器会提供一个默认的构造函数
  • 没有返回类型: 构造函数不返回任何类型,也不应该定义返回类型。

示例代码:

#include <iostream>  
using namespace std;  

class Date {  
private:  
    int day;  
    int month;  
    int year;  

public:  
    // 默认构造函数  
    Date() : day(1), month(1), year(2000) {  
        cout << "默认构造函数被调用,日期为 " << day << "/" << month << "/" << year << endl;  
    }  

    // 带参数的构造函数  
    Date(int d, int m, int y) : day(d), month(m), year(y) {  
        cout << "带参数的构造函数被调用,日期为 " << day << "/" << month << "/" << year << endl;  
    }  

    // 打印日期方法  
    void display() const {  
        cout << "日期为 " << day << "/" << month << "/" << year << endl;  
    }  
};  

int main() {  
    Date date1; // 调用默认构造函数  
    Date date2(21, 11, 2024); // 调用带参数构造函数  
    date1.display();  
    date2.display();  
    return 0;  
}

在上述代码中:

  • Date 类中定义了两个构造函数:一个默认构造函数和一个带参数的构造函数。
  • Date date1; 使用默认构造函数初始化,日期为 1/1/2000
  • Date date2(21, 11, 2024); 使用带参数构造函数初始化,日期为 21/11/2024
  • display 方法用于输出日期信息

三、析构函数

定义:析构函数是一种特殊的成员函数,在对象的生命周期结束时自动调用,用于释放对象占用的资源。

特点

  • 名称与类名相同,前面加波浪号(~ : 例如 ~Point()
  • 没有参数和返回值: 析构函数不能带参数,也不返回值。
  • 不能重载: 每个类只能有一个析构函数。
  • 调用顺序: 析构函数的调用顺序是与对象构造顺序相反的。

示例代码如下:

class Event {  
private:  
    string name;  
    Date date; // 使用日期类作为成员  

public:  
    // 构造函数  
    Event(const string& eventName, int d, int m, int y) : name(eventName), date(d, m, y) {  
        cout << "构造函数:事件 \"" << name << "\" 在 " << d << "/" << m << "/" << y << " 被创建" << endl;  
    }  

    // 析构函数  
    ~Event() {  
        cout << "析构函数:事件 \"" << name << "\" 被销毁" << endl;  
    }  

    // 打印事件信息  
    void display() const {  
        cout << "事件名称: " << name << ", 日期: ";  
        date.display();  
    }  
};  

int main() {  
    Event event("生日派对", 21, 11, 2024); // 创建事件  
    event.display();  
    return 0; // 在此处 event 对象被销毁,析构函数会被调用  
}

代码解析:

  • Event 类包含一个字符串成员 name 和一个**Date** 类型的成员 date
  • 在构造函数中,初始化事件名称和日期,当**Event** 对象创建时,构造函数被调用。
  • Event 对象超出作用域时,析构函数被自动调用,输出事件销毁的信息

四、拷贝构造函数

定义 : 拷贝构造函数是一种特殊的构造函数,用于通过复制现有对象来创建新对象。当对象通过传值方式传递或者被返回时,会调用拷贝构造函数。其签名为 **ClassName(const ClassName &obj),**它需要一个同类型对象的引用作为参数。

拷贝构造函数在以下情况下被调用:

  • 当一个对象被初始化为另一个对象。
  • 当对象作为参数传递给函数(使用值传递)。
  • 当一个对象从一个函数返回时
  • 浅拷贝:只复制指针的值,如果两个对象共享同一内存区域,可能导致内存管理问题。C++ 默认生成的拷贝构造函数是浅拷贝。
  • 深拷贝:复制对象指向的实际资源,确保每个对象都有独立的内存。这需要手动实现拷贝构造函数。

示例代码如下:

class Date {  
private:  
    int day;  
    int month;  
    int year;  

public:  
    // 默认构造函数  
    Date() : day(1), month(1), year(2000) {}  

    // 带参数构造函数  
    Date(int d, int m, int y) : day(d), month(m), year(y) {}  

    // 拷贝构造函数  
    Date(const Date& other) : day(other.day), month(other.month), year(other.year) {  
        cout << "拷贝构造函数被调用,日期为 " << day << "/" << month << "/" << year << endl;  
    }  

    // 打印日期  
    void display() const {  
        cout << day << "/" << month << "/" << year;  
    }  
};  
int main()
{
    Data d1(1,2,2025);
    d1.display();
    Data d2=d1; //调用拷贝构造函数
    d2.display();
}

代码讲解:

  • Date 类中定义了拷贝构造函数,用于深拷贝日期对象。
  • main 函数中,d1用于创建了个日期,Data d2=d1,触发拷贝构造函数,d1,d2各自拥有不同的内存区域

五、运算符重载

5.1为什么需要赋值运算符重载

在类中定义动态内存(如指针数组)的对象时,使用默认的赋值运算符将导致两个对象指向同一块内存区域。当其中一个对象被修改或销毁时,另一个对象可能会出现错误或崩溃。通过重载赋值运算符,我们可以实现深拷贝,从而避免这些问题。


5.2赋值运算符重载的实现

代码如下:

#include <iostream>  
using namespace std;  

class Date {  
private:  
    int day;  
    int month;  
    int year;  

public:  
    // 默认构造函数  
    Date() : day(1), month(1), year(2000) {}  

    // 带参数的构造函数  
    Date(int d, int m, int y) : day(d), month(m), year(y) {}  

    // 赋值运算符重载  
    Date& operator=(const Date& other) {  
        if (this != &other) { // 防止自我赋值  
            day = other.day;  
            month = other.month;  
            year = other.year;  
        }  
        return *this;  
    }  

    // 打印日期  
    void print() const {  
        cout << day << "/" << month << "/" << year << endl;  
    }  
};

我们主要对**Date& operator=(const Date& other)**这块代码讲解:

  • 返回类型 :返回类型为 Date&,表示我们返回当前对象的引用,以支持链式赋值操作(例如:a = b = c;)。
  • 参数 :接受一个 const Date& 参数,表示我们要赋值给当前对象的对象。
  • 自我赋值检查if (this != &other) 是为了检查当前对象是否与被赋值的对象相同(即自我赋值)。如果它们相同,我们就不需要执行任何操作。
  • 字段复制 :如果不是自我赋值,就将 other 对象的成员变量逐个复制到当前对象。
  • 返回当前对象 :最后,返回 *this,允许链式赋值。

5.3测试实现

我们在main函数中实现这块代码:

int main() {  
    Date date1(21, 11, 2024); // 创建第一个日期对象  
    Date date2;              // 创建第二个日期对象  
    date2 = date1;          // 使用赋值运算符重载  

    cout << "date1: ";  
    date1.print();           // 输出 date1 的值  
    cout << "date2: ";  
    date2.print();           // 输出 date2 的值  

    return 0;  
}
  1. 创建对象Date date1(21, 11, 2024); 创建一个日期为 2024 年 11 月 21 日的对象。
  2. 赋值操作date2 = date1; 调用重载的赋值运算符,将date1 的值赋给 date2
  3. 输出结果 :使用 **print()**方法输出两个日期对象的值。

注意:

  1. 避免自我赋值:在实现赋值运算符时,总是要检查自我赋值,以防止无意中导致错误。
  2. 返回当前对象的引用:这样可以使赋值操作支持链式调用。
  3. 深拷贝与浅拷贝:在处理动态内存时,请确保使用深拷贝,避免多个对象指向同一块内存。

六、友元

6.1友元的定义

  • 友元函数:一个被特定类声明为友元的外部函数,可以访问该类的私有和保护成员,但它并不是类的成员函数
  • 友元类:某个类的所有成员函数都可以是另一个类的友元,允许访问该类的私有和受保护成员

6.1.2声明和实现

友元函数在类内部声明,外部实现。下面是一个简单的示例:

#include <iostream>  
using namespace std;  

class MyClass {  
private:  
    int data;  

public:  
    MyClass(int d) : data(d) {}  

    // 声明友元函数  
    friend void display(const MyClass& obj);  
};  

// 友元函数实现  
void display(const MyClass& obj) {  
    cout << "Data: " << obj.data << endl; // 可以访问私有成员  
}

6.2友元的用途

  1. 运算符重载:在重载运算符时,友元函数可以直接访问对象的私有数据。
  2. 类间协作:当一个类需要访问另一个类的内部数据时,通过友元关系可以实现这种需求。
  3. 提高性能:友元函数可以减少对 getter/setter 方法的调用,直接访问数据从而提高性能。

6.3友元类

6.3.1定义

友元函数是一个被声明为某个类的友元的非成员函数。友元函数可以直接访问该类的私有和保护成员,即使它不是该类的一个成员。


实现:

#include <iostream>  
using namespace std;  

class Box; // 前向声明  

class BoxPrinter {  
public:  
    void printBoxDetails(const Box& box); // 成员函数声明  
};  

class Box {  
private:  
    int length;  

public:  
    Box(int l) : length(l) {}  

    // 将 BoxPrinter 声明为友元类  
    friend class BoxPrinter;   
};  

// 友元类的成员函数实现  
void BoxPrinter::printBoxDetails(const Box& box) {  
    cout << "Box length: " << box.length << endl; // 访问私有成员  
}  

int main() {  
    Box box(10);  
    BoxPrinter printer;  

    printer.printBoxDetails(box); // 使用友元类输出 Box 的长度  
    
    return 0;  
}

代码详解:

  1. 类前向声明 :使用 class Box; 声明 Box 类,以便在 BoxPrinter 中引用。
  2. 友元类BoxPrinter 类的成员函数 **printBoxDetails**被声明为友元,使其能够访问 Box 的私有成员 length
  3. 友元类功能 :**BoxPrinter::printBoxDetails**函数实现打印 Box 对象的私有成员 length
  4. 输出 :在 main 函数中,创建BoxBoxPrinter 对象,通过友元类方法打印盒子的长度。

6..3.2 友元类用途

  1. 信息隐藏与协作:友元类通过允许访问来实现必要的协作,同时保持其他类的封装性。
  2. 用于复杂数据类型:在创建复杂的数据结构时,友元类允许对数据的直接操作,而不让外部代码访问。
  3. 模块化设计:它们能够促进模块间的通信,而不损害数据的隐私。

6.4友元函数与友元类的注意事项

  1. Friendship is not inherited:友元关系不会被继承。子类不自动成为友元类,需显式声明。
  2. 易于使用但需谨慎:友元函数和类能够直接访问私有成员,过度使用可能会导致对象的封装性丧失。
  3. 设计时考虑:在设计类和友元关系时,应考虑未来可能的维护和扩展。

总结

过对构造函数、析构函数和拷贝构造函数的学习,我们可以更加灵活和高效地管理 C++ 对象的生命周期。掌握这些基础对于开发复杂的 C++ 应用程序是至关重要的。

  1. 构造函数用于初始化对象。
  2. 析构函数用于清理资源,防止内存泄漏。
  3. 拷贝构造函数确保对象之间的独立性,避免共享某些资源导致的问题。

友元函数和友元类在 C++ 中为设计灵活而强大的类提供了一个便捷的方式。它们允许我们在不改变封装性的情况下,控制对类内部数据的访问。然而,应谨慎使用友元关系,以免导致程序复杂化或不小心破坏了封装性。

希望这篇博客能够帮助你更深入理解 C++ 中的这些关键概念。如果你有任何问题或者想讨论的内容,请随时留言!

相关推荐
无 证明32 分钟前
new 分配空间;引用
数据结构·c++
别NULL5 小时前
机试题——疯长的草
数据结构·c++·算法
CYBEREXP20086 小时前
MacOS M3源代码编译Qt6.8.1
c++·qt·macos
yuanbenshidiaos6 小时前
c++------------------函数
开发语言·c++
yuanbenshidiaos6 小时前
C++----------函数的调用机制
java·c++·算法
tianmu_sama7 小时前
[Effective C++]条款38-39 复合和private继承
开发语言·c++
羚羊角uou7 小时前
【C++】优先级队列以及仿函数
开发语言·c++
姚先生977 小时前
LeetCode 54. 螺旋矩阵 (C++实现)
c++·leetcode·矩阵
FeboReigns7 小时前
C++简明教程(文章要求学过一点C语言)(1)
c语言·开发语言·c++
FeboReigns7 小时前
C++简明教程(文章要求学过一点C语言)(2)
c语言·开发语言·c++