《21天学通C++》(第九章)类和对象(2)

1.析构函数

析构函数在对象被销毁时自动 被调用,使用~表示,析构函数用于释放资源、销毁对象,示例代码如下:

cpp 复制代码
#include<iostream>
#include<string.h>
using namespace std;
//封装一个字符缓冲区
//并展示了如何使用构造函数和析构函数来管理动态分配该缓冲区的内存
class MyString
{
private:
    char* buffer;//字符缓冲区
    
public:
    MyString(const char* initString){
        if(initString!=NULL){//如果初始参数不为空,则分配内存创建缓冲区
            buffer=new char[strlen(initString)+1];
            //在C++中,字符串是以空字符 '\0' 结尾的字符数组。
            //用 strlen 函数时,它会计算字符串中直到遇到第一个空字符 '\0' 为止的字符数量,但不包括空字符本身
            //所以为了保证内存分配成功,必须要加1
            strcpy(buffer,initString);
            //把数据复制给缓冲区
        }else{
            buffer=NULL;
        }
    }

    ~MyString(){//结束后释放掉缓冲区buffer的内存,会被自动调用
        cout<<"clearing up"<<endl;
        if(buffer!=NULL){
            delete[] buffer;
        }
    }

    int getLength(){
        return strlen(buffer);
    }

    const char* getString(){
        return buffer;
    }
};

int main(){
    MyString sayHello("Hello!");
    cout<<"buffer is "<<sayHello.getLength()<<endl;
    cout<<"buffer contains is "<<sayHello.getString()<<endl;
    
    system("pause");
    return 0;
}

2.复制构造函数

(1) 浅复制及其存在的问题

cpp 复制代码
#include<iostream>
#include<string.h>
using namespace std;
class MyString
{
private:
    char* buffer;
    
public:
    MyString(const char* initString){
        buffer=NULL;
        if(initString!=NULL){
            buffer=new char[strlen(initString)+1];
            strcpy(buffer,initString);
        }
    }

    ~MyString(){
        cout<<"clearing up"<<endl;
        delete[] buffer;
        }
    
    int getLength(){
        return strlen(buffer);
    }

    const char* getString(){
        return buffer;
    }
};

void useMyString(MyString str){
    cout<<"str buffer length is "<<str.getLength()<<endl;
    cout<<"str buffer contains is "<<str.getString()<<endl;
    return;
}

int main(){
    MyString sayHello("Hello!");//创建对象sayHello
    useMyString(sayHello);
    //这里是将sayHello的指针值(内存地址)复制给useMyString里面的str
    //所以导致sayHello.buffer和str.buffer指向了同一个内存单元
    //当调用完之后,~MyString()会释放掉buffer
    cout<<"buffer contains"<<sayHello.getString()<<endl;
    //这里调用sayHello.getString()就不会正确输出,甚至程序崩溃
    system("pause");
    return 0;
}

上述代码中的复制过程就叫浅复制,即对于动态分配的内存或指向动态内存的指针成员,则仅仅复制指针的值(内存地址),而不是复制指针指向的内存内容

(2) 使用复制构造函数确保深复制

复制构造函数主要通过对函数进行重载来实现的,对上述代码进行修改,修改后如下:

cpp 复制代码
#include<iostream>
#include<string.h>
using namespace std;
class MyString
{
private:
    char* buffer;
    
public:
    MyString(const char* initString){
        buffer=NULL;
        if(initString!=NULL){
            buffer=new char[strlen(initString)+1];
            strcpy(buffer,initString);
        }
    }

    //深复制、重载
    MyString(const MyString& copySource){//采用引用获取原参数的内容
        buffer=NULL;
        if(copySource.buffer!=NULL){
            buffer=new char[strlen(copySource.buffer)+1];//为新对象分配内存
            strcpy(buffer,copySource.buffer);// 将copySource的buffer内容复制到新分配的内存中
        }
    }

    ~MyString(){
        cout<<"clearing up"<<endl;
        delete[] buffer;
        }
    
    int getLength(){
        return strlen(buffer);
    }

    const char* getString(){
        return buffer;
    }
};

void useMyString(MyString str){
    cout<<"str buffer length is "<<str.getLength()<<endl;
    cout<<"str buffer contains is "<<str.getString()<<endl;
    return;
}

int main(){
    MyString sayHello("Hello!");
    cout<<"raw buffer contains "<<sayHello.getString()<<endl;
    useMyString(sayHello);
    //深复制后sayHello.buffer和str.buffer就不会指向同一个内存单元了
    //调用函数后,销毁的是str.buffer
    cout<<"now buffer contains "<<sayHello.getString()<<endl;
    //所以再次调用sayHello.buffer能够正常输出
    system("pause");
    return 0;
}

3.移动构造函数

(1)移动构造函数是C++11引入的一种特殊的构造函数,它允许在创建对象时,将一个已存在对象的资源转移 给新创建的对象,而不是复制资源

(2)在某些情况下,为了避免不必要的资源复制,提升程序效率,才会使用移动构造函数

4.构造函数和析构函数的其他用法

(1)通过声明私有的复制构造函数,禁止类型被复制

(2)通过关键词static确保只能有一个实例

cpp 复制代码
#include<iostream>
#include<string>
using namespace std;
class President
{
private:
    President(){};// 私有构造函数,防止外部直接创建对象
    President(const President&);// 私有拷贝构造函数,防止复制对象
    const President& operator=(const President&);// 私有赋值运算符,防止对象赋值

    string name;

public:
    static President& getInstance(){
    	// 局部静态变量,确保只创建一次实例
        static President onlyInstance;
        return onlyInstance;

    }
    
    string getName(){
        return name;
    }

    void setName(string inputName){
        name=inputName;
    }
};


int main(){
    President& firstPresident=President::getInstance();//单例模式常用方法
	//调用President类的getInstance()静态方法。
	//getInstance()方法返回President类的唯一实例的引用。
	//将这个引用赋值给名为firstPresident的引用变量。
	//这样,firstPresident就成为了President类唯一实例的一个引用
    firstPresident.setName("LiHua");
    cout<<"name is "<<firstPresident.getName()<<endl;

    system("pause");
    return 0;
}

PS: 为了方便扩充应用程序的功能,仅在绝对必要的情况下才使用单例模式

5.使用构造函数进行类型转换

cpp 复制代码
#include <iostream>
class Length {
private:
    double meters; // 以米为单位存储长度
public:
    // 默认构造函数
    Length() : meters(0) {}

    // 转换构造函数,将double类型的值转换为Length对象
    Length(double value) : meters(value) {}

    // 成员函数,返回以米为单位的长度
    double getMeters() const {
        return meters;
    }
};

int main() {
    // 使用默认构造函数创建Length对象
    Length len1;
    // 使用转换构造函数创建Length对象,隐式地将double转换为Length类型
    Length len2(100.0);
    return 0;
}

隐式转换有时会存在一些问题,所以会用explicit来避免无意识的隐式转换。

6.this指针

this包含当前对象的地址,在类成员方法中调用其他成员方法时,编译器将隐式的传递this指针

7.结构和类不同的地方

默认情况下,结构中的成员默认为公有 ,以公有方式继承 基结构

默认情况下,类成员默认为私有 ,以私有方式继承

8.声明友元

允许友元类友元函数外部 访问私有数据成员和方法

cpp 复制代码
#include <iostream>
using namespace std;

class Person {
private:
    int health; // 表示健康状态

public:
    Person(int h) : health(h) {} // 构造函数设置健康状态

    // 声明Doctor类为友元类
    friend class Doctor;

    // 声明一个友元函数,它不是类的成员,但可以访问Person的私有成员
    friend void checkHealth(Person& p);
};

//Doctor类则被声明为Person类的友元类,这样Doctor类的所有成员函数都可以访问Person类的私有成员
class Doctor {
public:
    void diagnose(Person& patient) {
        if (patient.health < 90) {
            cout << "The patient is not in good health." << endl;
        } else {
            cout << "The patient is in excellent health." << endl;
        }
    }
};

// 友元函数的实现
void checkHealth(Person& p) {
    cout << "Checking health status: " << p.health << endl;
}

int main() {
    Person patient(80); // 创建一个健康状态为80的Person对象
    Doctor dr; // 创建一个Doctor对象

    // Doctor对象可以访问Person对象的私有成员
    dr.diagnose(patient);

    // 友元函数也可以访问Person对象的私有成员
    checkHealth(patient);

    system("pause");
    return 0;
}

9.共用体

使用关键字union,示例如下:

cpp 复制代码
union unionName{
	string a;
	string b;
	string c;
}
//实例化对象并使用
unionName unObject;
unObject.a="a1";
unObject.b="b2";

PS: 共用体不能继承

10.对类和结构使用聚合初始化

使用花括号 {}初始化成员

cpp 复制代码
MyStruct ms = { 1, 2.0 };

或者

cpp 复制代码
struct test{
	int num;
	double pi;
}

//初始化
test a1{2017,3.14}

或者

cpp 复制代码
struct test2{
	int num;
	char h[5];
	int q[6];
}

//初始化
test a1{2017,['a','b','c','d','\0'},[1,2,3,4,5,6]};
相关推荐
stm 学习ing7 分钟前
FPGA 第十讲 避免latch的产生
c语言·开发语言·单片机·嵌入式硬件·fpga开发·fpga
湫ccc1 小时前
《Python基础》之字符串格式化输出
开发语言·python
mqiqe2 小时前
Python MySQL通过Binlog 获取变更记录 恢复数据
开发语言·python·mysql
AttackingLin2 小时前
2024强网杯--babyheap house of apple2解法
linux·开发语言·python
Ysjt | 深2 小时前
C++多线程编程入门教程(优质版)
java·开发语言·jvm·c++
ephemerals__2 小时前
【c++丨STL】list模拟实现(附源码)
开发语言·c++·list
码农飞飞2 小时前
深入理解Rust的模式匹配
开发语言·后端·rust·模式匹配·解构·结构体和枚举
一个小坑货2 小时前
Rust 的简介
开发语言·后端·rust