一.概述

C++的类其实可以理解为在C结构体的基础上添加了权限控制。其将数据和方法封装在与其,并加以权限区分,用户只能通过公共的方法访问私有的数据

二.类的定义

1.概述

**关键字:**Class

**类的权限:**private、protected、public

  • 类的内部不存在权限之分,只是对类外有效

image-20240303133439442

2.格式

格式: 类中的数据只声明不初始化、权限修饰符下所有数据都被权限修饰符修饰

准则:

  • 数据为私有
  • 要为其配有初始化类、Set类、Get类
class 类名 {
    权限修饰符:
    	声明数据
    权限修饰符:
    	声明数据
}

3.示例

(1)设计person类

#include 
#include 
#pragma warning(disable:4996)

using namespace std;

class Student {
private:
	int Sage;
	char Sname[30];
public:
	void init(char* name, int age) {
		strcpy(Sname, name);
		if (age > 0 && age < 100) {
			Sage = age;
		}
		else {
			cout << "年龄无效" << endl;
		}
	}

	void setAge(int age) {
		if (age > 0 && age < 100) {
			Sage = age;
		}
		else {
			cout << "年龄无效" << endl;
		}
	}

	void setName(char* name) {
		strcpy(Sname, name);
	}

	int getAge() {
		return Sage;
	}

	char* getName() {
		return Sname;
	}
};

int main() {
	Student student;
	char name[30] = "lisi";
	student.init(name, 18);
	cout << student.getName() << " " << student.getAge() << endl;
	student.setAge(20);
	char name2[30] = "zhangsan";
	student.setName(name2);
	cout << student.getName() << " " << student.getAge() << endl;
	/*
	lisi 18
	zhangsan 20
	*/
}

(2)设计正方体类-练习

设计正方体类,并提供初始化方法,get、set方法以及求面积和体积的方法

#include 
#pragma warning(disable:4996)
using namespace std;

/*
设计立方体类(Cube),求出立方体的面积( 2ab + 2ac + 2bc )和体积( a * b * c),分别用全局函数和成员函数判断两个立方体是否相等。
*/

class Cube {
private:
    int a;
    int b;
    int c;
public:
    //使用引用接收变量
    void init(int &Wa, int &Wb, int &Wc) {
        a = Wa;
        b = Wb;
        c = Wc;
    }

    int getA(void) {
        return a;
    }

    int getB(void) {
        return b;
    }

    int getC(void) {
        return c;
    }

    int getArea(void) {
        return ((2 * a * b) + (2 * a * c) + (2 * b * c));
    }

    int getBulk(void) {
        return (a * b * c);
    }
};

int main(void) {
    //实例化正方体的对象
    Cube cube;
    int a = 10;
    int b = 5;
    int c = 2;
    //初始化正方体类
    cube.init(a, b, c);
    //调用内部方法得出面积和体积
    cout << "面积为:" << cube.getBulk() << endl;
    cout << "体积为:" << cube.getArea() << endl;
    /*
    面积为:100
    体积为:160
    */
}

三.成员函数在类外实现

1.概述

**简述:**就是将上面那种成员函数即在类中声明又在类中实现,改为只在类中声明,将函数的实现放到类的外面

**好处:**使类不那么臃肿

2.实现

(1)步骤

  • 创建一个类
  • 对于类内的函数,只写函数的声明
  • 在类的外面,创建函数的实现并通过作用域运算符::,指向类的作用域

(2)例子

类: 对于成员函数,只是做了声明,并没有实现

class Person {
private:
	int Mage;
	char Mname[30];
public:
	void init(int age, char* name);
	void setAge(int age);
	void setName(char* name);
	int getAge(void);
	char* getName(void);
};

实现: 在类外,通过::作用域运算符,进行类成员函数的初始化

  • 可以直接以类标识符调用函数属性值,因为::让他们是在一个作用域内的
void Person::init(int age, char* name)
{
	Mage = age;
	strcpy(Mname, name);
}

void Person::setAge(int age)
{
	Mage = age;
}

void Person::setName(char* name)
{
	strcpy(Mname, name);
}

int Person::getAge(void)
{
	return Mage;
}

char* Person::getName(void)
{
	return Mname;
}

**测试:**通过测试,在外部实现的成员函数都是有效的

int main(void) {
	//创建Person实例
	Person person;
	char name[30] = "lisi";
	person.init(18, name);
	cout << "年龄: " << person.getAge() << " 姓名:" << person.getName() << endl;//年龄: 18 姓名:lisi
	//尝试使用其它设置函数修改对象值
	person.setAge(23);
	strcpy(name, "zhangsan");
	person.setName(name);
	cout << "年龄: " << person.getAge() << " 姓名:" << person.getName() << endl;//年龄: 23 姓名:zhangsan

}

四.类在其它文件实现(推荐)

1.概述

**简述:**就是将类的声明放在头文件中,然后将成员函数的实现放到一个或多个源文件中完成

**优点:**这种分离使得代码更加易于管理和维护

2.实现

(1)步骤

  • 当你在实现类成员函数的源文件中时,你需要包含类的头文件
  • 当你在其他源文件中使用这个类时,你也需要包含这个类的头文件
  • 创建一个头文件,在这个头文件内声明类
  • 再创建一个源文件,导入头文件,在这个源文件上实现类的成员函数
  • 当在其它源文件需要使用这个类时,要包含声明这个类的头文件

目录结构:

image-20240303182347039

(2)代码

**头文件:**做了编译判断,防止头文件重复声明(data.h)

#ifndef DATA_H
#define DATA_H

class Data{
private:
    int Mage;
    char Mname[30];
public:
    void init(int age,char *name);
    void show(void);
};

#endif // DATA_H

实现成员函数的源文件:(data.cpp)

需要包含声明类的头文件

#include "data.h"
#include 
#include 
using namespace std;

void Data::init(int age, char *name)
{
    strcpy(Mname,name);
    Mage = age;

}

void Data::show()
{
    cout << "年龄:" << Mage << " 名字:" << Mname << endl;
}

测试类:(main.h)

需要包含声明这个类的头文件

#include "data.h"

using namespace std;

int main()
{
    Data data;
    char name[30] = "lisi";
    data.init(18,name);
    data.show();
    return 0;
}

五.构造函数

1.概述

(1)介绍

无参构造和有参构造都是必要的,必须都写,无参构造负责对数据进行初始化,如赋0等。有参构造负责具体的赋值

构造函数是创建类实例化对象 时自动调用的函数,构造函数分为无参构造有参构造

(2)注意

  • 如果不提供任何 构造函数,编译器会自动提供一个无参空的 构造函数
    • 默认无参构造构造的数是一个未赋值的数,即随机数,无意义
  • 只要类内存在哪怕一个构造函数,编译器都会屏蔽默认的无参 构造

2.创建

(1)书写要求

  • 构造函数名要和类名保持一致

  • 不能有返回值,连void都不可以有

    不单只不能retrun,连返回值数据类型也不能写

  • 支持重载,可以写多个同名的构造

  • 权限修饰符必须是public

(2)格式

构造函数要写在类的内部,且构造函数内部的写法有点类似于yaml中的写法,总之不同于Java,要注意。

无参构造:

类名(){
权限修饰符:
    构造体
}

有参构造:

类名(参数列表){
权限修饰符:
    构造体
}

(3)示例

下面的示例中提供了两个构造——无参构造有参构造

class Data {
private:
    int num;
public:
    Data(){
        num = 0;
        cout << "无参构造" <

3.调用

推荐使用无参构造,另外要注意匿名对象只在其所在的语句有效

(1)分类

按构造的参数来说:

  • 无参
  • 有参

按构造的方式来说:

  • 隐式构造
    • 语法简单,推荐
  • 显示构造
    • 语句较多,意义不大

按是否有对象来说:

  • 非匿名对象,就是正常构造得到的
  • 匿名对象,其对象会在当期语句结束被立即释放

特殊:构造函数的隐式转换

  • 只有在类中只有一个数据成员时才会出现

    就其底层其实就是调用了类的有参构造

    类名 对象名 = 值;
    

(2)示例

int main()
{
    //隐式调用无参构造
    Data obj1;
    //显示调用无参构造
    Data obj2 = Data();

    //隐式调用有参构造
    Data obj3(8);
    //显示调用有参构造
    Data obj4 = Data(6);
     
    //匿名对象,只要当前语句结束,该对象就会释放
    Data();
    Data(9);
    return 0;
}

六.析构函数

1.概述

当类中没有指针成员时,虽然析构函数显得多余,但仍需创建析构函数,置空就好

(1)介绍

析构函数在对象生命周期结束的时候,会被系统自动调用,它的调用是先于系统对空间的清理的,它主要用于清理系统自动清理无法清除的东西,例如类内部指针指向对的堆区

(2)使用场景

  • 当类内存在指针成员时,必须写析构函数

    系统自动清理只能清理类对象所占用的空间,如果其内部有指针成员指向堆空间,那它是无法清理的,需要借助析构函数在它之前进行对堆区空间进行处理

2.语法

(1)特点

  1. 析构函数的名称是在类名前加上波浪符(~)
  2. 析构函数没有返回类型,也没有参数
  3. 析构函数不能是const、volatile或static的
  4. 一个类只能有一个析构函数,即不能重载

(2)格式

~类名() {  
    // 释放资源、执行清理操作的代码  
}

3.示例

下面的例子中str构造的形参是const char * str,这是C++标准规定的

下面的例子中str是一个指针变量,其指向了对象外的堆区,此时需要使用析构函数调用free()对str指向的堆区进行释放,后再进行对象的清理,以避免堆区的泄露

#include 
#include 
#include 
#pragma warning(disable:4996)
using namespace std;

class Data {
private:
	int num;
	char* str;
public:
	Data() {
		num = 0;
		cout << "无参构造,初始化num的值为:" << num << endl;
	}
	Data(int a) {
		num = a;
		cout << "有参构造,初始化num的值为:" << num << endl;
	}
	Data(const char* Mstr) {
		str = (char*)calloc(1, strlen(Mstr) + 1);
		strcpy(str, Mstr);
		cout << "构造结果: " << str << endl;

	}
	~Data() {
		cout << "析构函数" << endl;
		if (str != NULL) {
			free(str);
			str = NULL;
		}

	}

};

void test() {
	Data obj5("hello");
}

int main()
{
	test();
}

七.拷贝构造函数

1.概述

本质: 拷贝构造的本质就是构造函数,用于拷贝旧对象初始化新对象的构造函数

**默认拷贝函数:**如果用户不提供拷贝函数,编译器默认也会提供一个拷贝函数,它会将旧对象的所有属性值拷贝到新对象--浅拷贝

调用时机:

必须要创建对象的时候就用旧对象对其进行初始化,才会调用构造函数

对象 初始化 对象时才会调用拷贝构造

Data ob1(10);//创建对象,对后面来说就是旧对象
Data ob2 = ob1;//调用了拷贝函数
//----------------------------------------------------
Data ob3(10);//创建对象,对后面来说就是旧对象
Data ob4;//调用了无参构造
ob4 = ob3;//只是进行了简单的赋值,并没有调用拷贝函数

2.格式&示例

1.格式

**内部定义:**拷贝函数一般要在函数内将传入对象的数据考到新对象的对应位置

注意点:

  • 为了不让函数修改旧对象,一般使用Const修饰
  • 为了节省空间,一般参数使用引用
    类名(const 类名 &旧对象引用){
        cout << "拷贝函数" << endl;
        //拷贝旧对象信息到新对象的语句
    }

2.拷贝构造 和 无参构造 有参构造的关系

  • 如果用户定义了 拷贝构造或者有参构造 都会屏蔽无参构造。
  • 如果用户定义了 无参构造或者有参构造 不会屏蔽拷贝构造

3.示例

代码:

#include 

using namespace std;

class Data {
public:
    int num;
public:
    Data() {
        num = 0;
        cout << "无参构造,初始化num的值为:" << num << endl;
    }
    Data(int a) {
        num = a;
        cout << "有参构造,初始化num的值为:" << num << endl;
    }
    Data(const Data &p){
        cout << "拷贝函数" << endl;
        //拷贝函数一般要在函数内将传入对象的数据考到新对象的对应位置
        num = p.num;
    }
    ~Data() {
        //析构函数的调用次数间接表示了该类所创建的对象数量
        cout << "析构函数" << endl;
    }

};

int main()
{
    Data ob1(19);
    Data ob2 = ob1;
    cout << "ob2的num为:" << ob2.num << endl;
    return 0;
}

结果:

只调用了一次构造,就完成了两个对象的初始化,这就是调用了拷贝函数

有参构造,初始化num的值为:19
拷贝函数
ob2的num为:19
析构函数
析构函数

3.拷贝构造的调用时机

(1)旧对象给新对象初始化

当用旧对象给新对象初始化时会调用拷贝构造,将旧对象的值拷贝给新对象

Data obj(10);
Data obj2 =obj1;//调用拷贝构造

(2)普通对象作为函数参数

当使用普通函数作为函数参数时,调用函数会发生拷贝构造,将转入值拷贝给函数参数

void fun(Data obj){//调用拷贝构造,将obj1的值拷贝给obj,相当于Data4 obj = obj1;
    cout << ob.mA << endl;
}
void test(){
    Data obj1(100);
    fun(ob1);
}

(3)函数返回值普通对象

在vs和qt, Linux中因为优化策略不同,导致前者会调用拷贝函数,而后两者不会,就原理而言,后者两者更加高效

① Visual Studio
Ⅰ.概述

Visual Studio会发生拷贝构造

image-20240305170949734
Ⅱ.图解

至于ob2的赋值,编译器是让ob2直接接管匿名对象的空间,而不另外开辟空间,所以也不存在调用拷贝函数一说

在Vscode中,拷贝构造的调用是在return的,它返回数据时是将数据存储在临时栈区,它会在临时栈区创建一个匿名对象,而这拷贝构造的调用旧体现在其将返回对象交给临时栈区的匿名对象的时候

image-20240305170743552
② Qtcreater,linux

Qtcreater,linux不会调用拷贝函数

Qtcreater,Linux的编译器在上面的基础上进一步优化,它会先整体"审视"代码,发现函数返回的对象的内容最终还是会被调用函数的对象接收的,它就干脆不释放了,直接让接收的对象接管本该释放对象的空间,即将接收对象的指针指向了它。这样就省略了中间的拷贝和释放了

image-20240305171813381

4.拷贝函数的浅/深拷贝

(1)概念

① 浅深拷贝的概念
  1. 浅拷贝(Shallow Copy)

    就是简单的赋值操作,普通变量还好,遇到指针变量就会让新旧指针指向同一内存空间,造成内存管理问题

    • 浅拷贝仅复制对象的成员变量的值,而不复制对象所拥有的资源(如动态分配的内存)。
    • 如果对象中包含指针并指向动态分配的内存,那么浅拷贝会导致两个对象共享同一块内存。
    • 当其中一个对象被销毁时,它可能会释放这块内存,导致另一个对象的指针指向无效的内存。
  2. 深拷贝(Deep Copy)

    就是为新对象申请了和旧对象一样的内存空间,并存储相关信息

    • 深拷贝不仅复制对象的成员变量的值,还复制对象所拥有的资源。
    • 对于每个对象,深拷贝都会为其分配新的资源,确保每个对象都有其自己的资源副本。
    • 这样,即使一个对象被销毁,也不会影响到其他对象。

(2)使用时机

**前置:**默认的拷贝构造是浅拷贝

  • 如果类中没有指针成员,不用实现拷贝函数析构函数
  • 如果类中存在指针成员。则必须实现析构函数释放指针所指向的堆区空间,必须实现拷贝函数完成深拷贝动作
① 对含指针变量类使用浅拷贝(错例)

概述:

浅拷贝只是单纯的将旧对象的指针交给了新对象的指针,二者其实指向的是同一个堆空间,当其中一方生命周期结束,释放空间,该堆空间就消失了。这样,等到新对象生命周期结束,要释放对象调用析构释放空间时,就会出现双free()的情况了

image-20240306131505110

代码: 下面没有下拷贝函数,因为默认的就是浅拷贝

#include 
#include 

using namespace std;

class Data {
public:
    char *name;
public:
    Data() {
        name = NULL;
    }

    Data(const char *input) {
        name = (char *) calloc(1, strlen(input) + 1);
        strcpy(name, input);
        cout << "有参构造" << endl;
    }

    ~Data() {
        cout << "析构函数" << endl;
        if (name != NULL) {
            free(name);
            name = NULL;
        }
    }
};

int main(){
    Data obj1("HelloWorld!");
    Data obj2 = obj1;
}

返回值:

显示检测到双重free(),这就是新旧对象的指针成员都指向了同一个内存空间所导致的,第一个对象生命周期结束,空间释放,当第二个对象生命周期结束,再次调用析构尝试释放已释放空间,就导致了双重free()的出现

有参构造
析构函数
析构函数
free(): double free detected in tcache 2
Aborted
② 对含指针变量类使用深拷贝

当进行深度拷贝时,每一个对象都有独立的成员

image-20240306143814209

代码:

#include 
#include 

using namespace std;

class Data {
public:
    char *name;
public:
    Data() {
        name = NULL;
    }

    Data(const char *input) {
        name = (char *) calloc(1, strlen(input) + 1);
        strcpy(name, input);
        cout << "有参构造" << endl;
    }

/*    Data(const Data &input){
        //浅拷贝
        name = input.name;
        cout << "浅拷贝" << endl;
    }*/
    Data(const Data &input){
        name = (char *)calloc(1,strlen(input.name) +1);
        strcpy(name,input.name);
        cout << "深拷贝" << endl;

    }

    ~Data() {
        cout << "析构函数" << endl;
        if (name != NULL) {
            free(name);
            name = NULL;
        }
    }
};

int main() {
    Data obj1("HelloWorld!");
    Data obj2 = obj1;
}

输出:

可以看到,调用了两次析构函数释放对象的空间,程序也没有报错,那就是因为深拷贝使两个对象拥有了各自独立的空间,不会因为一边的释放空间而导致另一边出现问题

有参构造
深拷贝
析构函数
析构函数

八.初始化列表

简单说下用途,当类的成员对象包含其它类对象时,就要使用初始化列表对其它类的对象进行初始化

1.概述

(1)介绍

初始化列表(Initializer List)是一种在构造函数中初始化类成员变量的方法。初始化列表在构造函数体执行之前执行,因此它可以用于初始化那些不能在构造函数体中初始化的成员变量

(2)应用场景

就是类嵌套

简单说下用途,当类的成员对象包含其它类对象时,就要使用初始化列表优先对成员类对象进行初始化,否则默认会调用该类的无参构造为其赋值

**例:**Data2中包含了Data1的成员ob1,此时如果想要对Ob1进行初始化,就必须使用初始化列表

class Data1{
public:
    int num;
public:
/*省略构造*/
};

class Data2 {
public:
    char *name;
    Data1 ob1;
public:
/*省略构造*/
};

2.类内存在其它类成员对象的构造顺序

说这个是为了解释为什么不能用类自身的构造去构造其内部包含的其它类成员对象

构造类内包含其它类成员的类时,最先是进行类成员对象的构造的,而后才是类自身的对象,这也就导致了不能使用类自身的构造函数为其内部包含的类成员进行初始化的情况,因为等到类自身构造函数调用的时候,它内部的其它类成员早已使用默认的无参构造构造完了,完全轮不到类自身构造的什么事情

image-20240306140948592

3.初始化列表格式

初始化列表,其实就是在构造函数参数列表后加:号,然后接成员对象的构造方法

**格式:**在参数列表右边:后接的,就是初始化列表了,它的执行顺序优先于类自身的构造函数

  • 初始化列表的值:来源于参数列表,即外部传入用来构造的值
类名(参数列表):成员对象1(值),成员对象2(值){
    
}

4.示例

Data2类内包含Data1的成员方法,所以下面的例程中使用了参数列表对成员对象进行了初始化

#include 
#include 

using namespace std;

class Data1{
public:
    int num;
public:
    Data1(){
        num = 0;
    }
    Data1(int input){
        num = input;
    }
};


class Data2 {
public:
    char *name;
    //成员对象包含Data1类的对象
    Data1 ob1;
public:
    Data2() {
        name = NULL;
    }
    //使用了参数列表对Data1的对象进行初始化
    Data2(const char *input,int num):ob1(num){
        name = (char *) calloc(1, strlen(input) + 1);
        strcpy(name, input);
        cout << "有参构造" << endl;
    }
    Data2(const Data2 &input){
        ob1.num = input.ob1.num;
        name = (char *)calloc(1,strlen(input.name) +1);
        strcpy(name,input.name);
        cout << "深拷贝" << endl;
    }
    ~Data2() {
        cout << "析构函数" << endl;
        if (name != NULL) {
            free(name);
            name = NULL;
        }
    }
};

int main() {
    Data2 obj1("hello List",66);
    Data2 obj2 = obj1;
    cout << "obj2的值: " << obj2.name << obj2.ob1.num <

九.对象数组(栈区数组)

堆区数组的申请看下面New和Delete的笔记

1.概述

**介绍:**对象数组,就是数组里面放对象成员。

特点:

  • 如果不对对象数组成员进行初始化,默认会调用成员对象的无参构造器进行初始化

2.格式

不初始化成员:

对象类型 数组名[数组长度];

**初始化成员:**初始化成员必须显式的调用有参构造

对象类型 数组名[数组长度] = {
    对象类型(值),
    对象类型(值2),
    .....
}

3.例程

创建Data2数组,初始化其所有成员而后对成员进行遍历

对象:

class Data1{
public:
    int num;
public:
/*省略构造*/
};

class Data2 {
public:
    char *name;
    Data1 ob1;
public:
/*省略构造*/
};

测试代码:

int main() {
    Data2 obj[5];//不初始化成员
    Data2 arr[5] = {
            Data2("yiyi",21),
            Data2("erer",22),
            Data2("zhangsan",23),
            Data2("lisi",24),
            Data2("wangwu",25)
    };
    int index = sizeof(arr) / sizeof(Data2);//计算数组长度
    for(int i = 0;i < index;i++){//遍历对象数组
        cout << arr[i].name << " " << arr[i].ob1.num << endl;
    }
}

输出:

yiyi21
erer22
zhangsan23
lisi24
wangwu25

十.explicit关键字

这个关键字的作用其实就是用于控制是否允许隐式转换的

1.概述

介绍: explicit关键字用于修饰构造函数,声明禁止使用该构造函数通过构造函数隐式转换的形式初始化对象

**作用:**针对单参数的构造函数(或者除了第一个参数外其余参数都有默认值的多参构造),防止构造函数的隐式转化

2.构造函数的隐式转换

隐式转换即允许通过类似直接赋值的形式对类对象进行初始化,但其本质还是调用了类的有参构造,但这种构造方式极易造成误会,所以C++提供了explicit关键字用于禁止这种构造方式

代码:

class Data3{
public:
    int num;
public:
    Data3(){
        num = 0;
    }
    Data3(int input){
        num = input;
    }
};
int main(){
    //构造函数的隐式转换
    Data3 obj1 = 100;
    cout<<"obj1的num:"<

结果:

obj1的num:100

3.关键字使用

**使用:**explicit关键字的使用其实就是将其加到类的有参构造上,这样就不能通过隐式转换的形式调用这个有参构造了,进而达到进制隐式转换的目的

示例:

class Data3{
public:
    int num;
public:
    Data3(){
        num = 0;
    }
    //附加explicit关键字
    explicit Data3(int input){
        num = input;
    }
};
int main(){
    //构造函数的隐式转换
    Data3 obj1 = 100;
    cout<<"obj1的num:"<

**结果:**在编译阶段就会报错

honestliu@My-Pc:/mnt/C++Poject$ g++ test01.cpp -o test01
test01.cpp: In function ‘int main()’:
test01.cpp:17:18: error: conversion from ‘int’ to non-scalar type ‘Data3’ requested
   17 |     Data3 obj1 = 100;
      |                  ^~~

十一.New和Delete堆区空间操作

1.概述

在C++中建议使用new和delete取代malloc、calloc和free

(1)介绍

在C++中,newdelete是用于动态内存分配和释放的关键字。它们允许程序在运行时分配和释放内存,这对于创建对象、数组以及其他需要在程序执行期间动态分配和销毁的数据结构非常重要

(2)和原生C语言关键字的区别

  1. 函数与操作符mallocfree是C语言风格的函数,而newdelete是C++风格的操作符。这意味着newdelete可以像其他操作符一样重载,而mallocfree则不能。
  2. 初始化malloccalloc只分配内存,不会初始化。这意味着分配的内存区域包含的是垃圾值(即未定义的值)。相反,new会调用对象的构造函数来初始化分配的内存。
  3. 内存大小计算:使用malloc时,程序员需要手动计算所需内存的大小,并将其作为参数传递给malloc。而new会根据所分配对象的类型自动计算所需内存的大小。
  4. 返回值malloc返回一个void*指针,这意味着程序员需要将其强制转换为正确的类型。而new直接返回正确类型的指针,无需强制转换。
  5. 错误处理malloc在内存分配失败时返回NULL,而new在内存分配失败时会抛出一个bad_alloc异常。这使得new在错误处理方面更加灵活和强大。
  6. 内存释放free只释放内存,不会调用任何析构函数。而delete在释放内存之前会调用对象的析构函数来清理资源。
  7. 内存泄漏检测newdelete可以配合C++的内存泄漏检测工具(如智能指针)使用,而mallocfree则不能。这使得使用newdelete在编写健壮、无内存泄漏的程序时更加容易。

2.使用

(1)new 关键字

new关键字可以在申请空间的同时完成空间的初始化

介绍:new关键字用于在堆上分配内存。

当使用new分配内存时,C++会执行以下步骤:

  1. 分配指定大小的内存。
  2. 调用对象的构造函数来初始化分配的内存

new可以用于分配单个对象的内存,也可以用于分配对象数组的内存。

示例:注意申请基本数据类型空间和申请数组空间是存在差异的

申请基本数据类型空间:

int* p = new int;  // 分配一个int类型的内存,并返回指向它的指针

申请数组空间:

int* arr = new int[10];  // 分配一个包含10个int的数组的内存,并返回指向第一个元素的指针

(2)delete 关键字

介绍:delete关键字用于释放之前使用new分配的内存。

当使用delete释放内存时,C++会执行以下步骤:

  1. 调用对象的析构函数来清理对象。
  2. 释放对象的内存。

delete可以用于释放单个对象或对象数组的内存。

**示例:**注意释放基本数据类型空间和释放数组空间是存在差异的

释放基本数据类型空间:

delete p;  // 释放p指向的内存

释放数组空间:

delete[] arr;  // 释放arr指向的数组内存

3.示例

(1)申请堆区数组

使用new和delete可以很方便的创建堆区数组,避免了使用malloc还需要计算数组的长度以及进行类型转换

 //创建字符数组
    char *pStr = new char[100];
    //创建整型数组
    int *pArr1 = new int[100];
    //创建整型数组并初始化
    int *pArr2 = new int[10]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
    //释放数组内存 
    delete[] pStr; 
    delete[] pArr1; 
    delete[] pArr2;

十二.静态成员

最后修改:2024 年 03 月 07 日
如果觉得我的文章对你有用,请随意赞赏