C++类对象创建全解析:从构造函数到内存管理

server/2025/3/16 19:56:21/

目录

对象的创建

对象的创建规则

对象的数据成员初始化

对象所占空间大小

总结

指针数据成员


对象的创建

在之前的 Computer 类中,通过自定义的公共成员函数 setBrand 和 setPrice 实现了对数据成员的初始化。实际上,C++ 为类提供了一种特殊的成员函数——构造函数来完成相同的工作。

  • 构造函数的作用:就是用来初始化数据成员的

  • 构造函数的形式:

    没有返回值,即使是void也不能有;

    函数名与类名相同,再加上函数参数列表。

构造函数在对象创建时自动调用,用以完成对象成员变量等的初始化及其他操作(如为指针成员动态申请内存等)

对象的创建规则

  1. 当类中没有显式定义构造函数时 ,编译器会自动生成一个默认 (无参) 构造函数 ,但并不会初始化数据成员;

    以Point类为例:

    #include <iostream>// 其次是C++的文件,第三方库文件放最下using std::cout;
    using std::endl;
    class Point {public:void print(){cout << "(" << _ix << "," << _iy<< ")" << endl;}Point(){cout << "print" << endl;}private:int _ix;int _iy;};void test0(){Point pt;pt.print();}//运行结果显示,pt的_ix,_iy都是不确定的值int main(){test0();return 0;}

    这说明了,当类中没有显式定义构造函数时 ,编译器会自动生成一个默认 (无参) 构造函数 ,但并不会初始化数据成员,这里我们将他不做任何处理,看是否会自动调用。

class Point {
public:void print(){cout << "(" << _ix << "," << _iy<< ")" << endl;}
private:int _ix;int _iy;
};void test0()
{Point pt;pt.print();
}
//运行结果显示,pt的_ix,_iy都是不确定的值

Point pt; 这种方式创建的对象,其数据成员没有被初始化,输出的会是不确定的值

image-20240307114915395

 2.  一旦当类中显式提供了构造函数时 ,编译器就不会再自动生成默认的构造函数;

class Point {
public:Point(){cout << "Point()" << endl;_ix = 0;_iy = 0;}void print(){cout << "(" << _ix << "," << _iy<< ")" << endl;}
private:int _ix;int _iy;
};void test0()
{Point pt;pt.print();
}
//这次创建pt对象时就调用了自定义的构造函数,而非默认构造函数

3. 编译器自动生成的默认构造函数是无参的,构造函数也可以接收参数,在对象创建时提供更大的自由度;

class Point {
public:Point(int ix, int iy){cout << "Point(int,int)" << endl;_ix = ix;_iy = iy;}void print(){cout << "(" << _ix << "," << _iy<< ")" << endl;}
private:int _ix;int _iy;
};void test0()
{Point pt;//error,没有默认的无参构造函数可供调用Point pt2(10,20);pt2.print();
}

4. 如果还希望通过默认构造函数创建对象, 则必须要手动提供一个默认构造函数;

class Point {
public:Point(){}Point(int ix, int iy){cout << "Point(int,int)" << endl;_ix = ix;_iy = iy;}void print(){cout << "(" << _ix << "," << _iy<< ")" << endl;}
private:int _ix;int _iy;
};void test0()
{Point pt;//okPoint pt2(10,20);pt2.print();
}

5. 构造函数可以重载

如上,一个类中可以有多种形式的构造函数,说明构造函数可以重载。事实上,真实的开发中经常会给一个类定义各种形式的构造函数,以提升代码的灵活性(可以用多种不同的数据来创建出同一类的对象)。

image-20240307120328878

对象的数据成员初始化

上述例子中,在构造函数的函数体中对数据成员进行赋值,其实严格意义上不算初始化(而是算赋值)。

在C++中,对于类中数据成员的初始化,推荐使用初始化列表完成。初始化列表位于构造函数形参列表之后,函数体之前,用冒号开始,如果有多个数据成员,再用逗号分隔,初始值放在一对小括号中。

class Point {
public://...Point(int ix = 0, int iy = 0): _ix(ix), _iy(iy){cout << "Point(int,int)" << endl;}//...
};

如果没有在构造函数的初始化列表中显式地初始化成员,则该成员将在构造函数体之前执行默认初始化。如在“对象的创建规则”示例代码中,有参的构造函数中 _ix 和 _iy 都是先执行默认初始化后,再在函数体中执行赋值操作。

image-20240307143824881

补充:数据成员的初始化并不取决于其在初始化列表中的顺序,而是取决于声明时的顺序(与声明顺序一致)

  • 构造函数的参数也可以按从右向左规则赋默认值,同样的,如果构造函数的声明和定义分开写,只用在声明或定义中的一处设置参数默认值,一般建议在声明中设置默认值。

    class Point {
    public:Point(int ix, int iy = 0);//默认参数设置在声明时//...
    };Point::Point(int ix, int iy)
    : _ix(ix)
    , _iy(iy)
    {cout << "Point(int,int)" << endl;
    }void test0(){Point pt(10);
    }

  • C++11之后,普通的数据成员也可以在声明时就进行初始化。但一些特殊的数据成员初始化只能在初始化列表中进行,故一般情况下统一推荐在初始化列表中进行数据成员初始化。

class Point {
public://...int _ix = 0;//C++11int _iy = 0;
};

  • 数据成员初始化的顺序与其声明的顺序保持一致,与它们在初始化列表中的顺序无关(但初始化列表一般保持与数据成员声明的顺序一致)。

对象所占空间大小

之前在讲引用的知识点时,我们提过使用引用作为函数的返回值可以避免多余的复制。内置类型的变量最大也就是long double,占16个字节。但是现在我们学习了类的定义,自定义类型对象的大小可以非常大。

使用sizeof查看一个类的大小和查看该类对象的大小,得到的结果是相同的(类是对象的模板);

void test0(){Point pt(1,2);cout << sizeof(Point) << endl;cout << sizeof(pt) << endl;}

成员函数并不影响对象的大小,对象的大小与数据成员有关(后面学习继承、多态,对象的内存布局会更复杂);

现阶段,在不考虑继承多态的情况下,我们做以下测试。发现有时一个类所占空间大小就是其数据成员类型所占大小之和,有时则不是,这就是因为有内存对齐的机制。

class A{int _num;double _price;
};
//sizeof(A) = 16class B{int _num;int _price;
};
//sizeof(D) = 8

  • 为什么要进行内存对齐?

    1.平台原因(移植原因):不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。

    2.性能原因:CPU 对内存的读取不是连续的,而是分成块读取的,块的大小只能是1、2、4、8、16 ... 字节。若不进行内存对齐,可能需要做两次内存访问,性能会大打折扣;而进行过内存对齐仅需要一次访问。

    image-20240223163737665

    64位系统默认以8个字节的块大小进行读取。

    如果没有内存对齐机制,CPU读取_price时,需要两次总线周期来访问内存,第一次读取 _price数据前四个字节的内容,第二次读取后四个字节的内容,还要经过计算,将它们合并成一个数据。

    有了内存对齐机制后,以浪费4个字节的空间为代价,读取_price时只需要一次访问,所以编译器会隐式地进行内存对齐。

    规则一:

    按照类中占空间最大的数据成员大小的倍数对齐;

    如果数据成员再多一些,我们发现自定义类型所占的空间大小还与这些数据成员的顺序有关

    class C{int _c1;int _c2;double _c3;
    };
    //sizeof(C) = 16class D{int _d1;double _d2;int _d3;
    };
    //sizeof(D) = 24

    image-20240223163111420

    如果数据成员中有数组类型,会按照除数组以外的其他数据成员中最大的那一个的倍数对齐

    class E{double _e;char _eArr[20];double _e1;int _e2;
    };
    //sizeof(E) = 48class F{char _fArr[20];
    };
    //sizeof(F) = 20

    再判断一下,G类所占的空间是多少?

    class G{char _gArr[20];int _g1;double _g2;
    };//32

在C语言的涉及的结构体代码中,我们可能会看到#pragma pack的一些设置,#pragma pack(n)即设置编译器按照n个字节对齐,n可以取值1,2,4,8,16.在C++中也可以使用这个设置,最终的对齐效果将按照 #pragma pack 指定的数值和类中最大的数据成员长度中,比较小的那个的倍数进行对齐。

总结

除数组外,其他类型的数据成员中,以较大的数据成员所占空间的倍数去对齐。

内存对齐还与数据成员的声明顺序有关。

指针数据成员

类的数据成员中有指针时,意味着创建该类的对象时要进行指针成员的初始化,需要申请堆空间。

在初始化列表中申请空间,在函数体中复制内容。

class Computer {
public:Computer(const char * brand, double price): _brand(new char[strlen(brand) + 1]()), _price(price){strcpy(_brand,brand);}private:char * _brand;double _price;
};void test0(){Computer pc("Apple",12000);
}

思考一下,以上代码有没有问题?

image-20240307152000320

代码运行没有报错,但使用memcheck工具检查发现发生了内存泄漏。有new表达式被执行,就要想到通过delete表达式来进行回收。如果没有对应的回收机制,对象被销毁时,它所申请的堆空间不会被回收,就会发生内存泄漏。


http://www.ppmy.cn/server/175510.html

相关文章

鸿蒙开发:了解应用级配置信息

前言 本文基于Api13。 在创建任意一个项目后&#xff0c;我们会发现&#xff0c;都会默认创建一个AppScope目录&#xff0c;这个目录就是我们的应用级配置信息的地方&#xff0c;它是创建项目后自动生成的&#xff0c;不可进行删除的&#xff0c;它的作用也很明显&#xff0c;一…

Linux 中的管道:进程间数据传输的利器

个人主页&#xff1a;chian-ocean 文章专栏-Linux 前言 **进程间通信&#xff08;Inter-Process Communication, IPC&#xff09;**是指在操作系统中&#xff0c;不同进程之间交换数据或信息的方式。由于每个进程都有自己的地址空间&#xff0c;直接访问另一个进程的数据是不…

Word 小黑第17套

对应大猫18 在目录前面添加一个空白页 点布局 -分隔符 -下一页 制作封面图片移动不了 调整一下图片的环绕文字 对文档内容进行分节&#xff1a;在要分节内容的前面操作 布局 -分隔符 -下一页 &#xff08;通过导航窗格&#xff09; 修改定义新编号样式 将另一个文档中的样式…

基于SpringBoot的Mybatis和纯MyBatis项目搭建的区别

【由于之前学习MyBatis的时候是跟着视频敲的纯MyBatis项目&#xff0c;以至于在突然看到别人在SpringBoot项目里搭建MyBatis方式的时候很懵比…特此文字形式记录一下区别&#xff08;应该还有好多种其他方式是我不知道的&#xff0c;主要应该就是要知道关键的流程步骤&#xff…

翻硬币问题

小明正在玩一个“翻硬币”的游戏。桌上放着排成一排的若干硬币&#xff0c;用“”表示正面&#xff0c;用“o”表示反面&#xff08;是小写字母&#xff0c;不是零&#xff09;。比如可能情形是“**oo***ooo”&#xff0c;如果同时翻转左边的两个硬币&#xff0c;则变为“oooo*…

音视频入门基础:RTP专题(19)——FFmpeg源码中,获取RTP的音频信息的实现(下)

本文接着《音视频入门基础&#xff1a;RTP专题&#xff08;18&#xff09;——FFmpeg源码中&#xff0c;获取RTP的音频信息的实现&#xff08;上&#xff09;》&#xff0c;继续讲解FFmpeg获取SDP描述的RTP流的音频信息到底是从哪个地方获取的。本文的一级标题从“四”开始。 四…

钉钉项目报销与金蝶系统高效集成技术解析

钉钉报销【项目报销类】集成到金蝶付款单【画纤骨】的技术实现 在企业日常运营中&#xff0c;数据的高效流转和准确对接是提升业务效率的关键。本文将分享一个具体的系统对接集成案例&#xff1a;如何将钉钉平台上的项目报销数据无缝集成到金蝶云星空的付款单系统中。本次方案…

第四章-PHP文件包含

PHP文件包含 一&#xff0c;PHP文件包含简介 在 PHP 开发中&#xff0c;文件包含&#xff08;File Inclusion&#xff09;是一种代码复用和组织的重要机制&#xff0c;其核心目的是将代码模块化、提高可维护性。 文件包含的作用 1. 代码复用与模块化 拆分重复代码&#xff…