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

devtools/2025/3/15 10:40:04/

目录

对象的创建

对象的创建规则

对象的数据成员初始化

对象所占空间大小

总结

指针数据成员


对象的创建

在之前的 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/devtools/167271.html

相关文章

mac安装python没有环境变量怎么办?zsh: command not found: python

在mac电脑上,下载Python安装包进行安装之后,在终端中,输入python提示: zsh: command not found: python 一、原因分析 首先,这个问题不是因为python没有安装成功的原因,是因为python安装的时候,没有为我们添加环境变量导致的,所以我们只需要,在.zshrc配置文件中加上环…

研究整除的性质——最大公约数(GCD)和最小公倍数(LCM)

最大公约数&#xff08;GCD&#xff09;和最小公倍数&#xff08;Least Common Multiple&#xff0c;LCM&#xff09;研究整除的性质&#xff0c;非常古老&#xff0c;在2000多年前就得到了很好的研究。由于简单易懂&#xff0c;有较广泛的应用&#xff0c;它们是竞赛中频繁出现…

【AI论文】MM-Eureka:基于规则的大规模强化学习探索视觉“啊哈”时刻

摘要&#xff1a;我们提出了MM-Eureka&#xff0c;这是一个多模态推理模型&#xff0c;成功地将基于规则的大规模强化学习&#xff08;RL&#xff09;扩展到多模态推理领域。虽然基于规则的RL在提升大型语言模型&#xff08;LLMs&#xff09;在文本领域的推理能力方面已经取得了…

Qt项目中集成第三方模块的.pri文件

对于功能模块较多的Qt项目&#xff0c;使用pri文件管理模块文件&#xff0c;降低工程复杂度&#xff0c;提高软件模块的封装性和重用性。 一、.pro与.pri 对于模块化编程&#xff0c;Qt提供了pro和pri&#xff0c;pro管理项目&#xff0c;pri管理模块。 .pro 文件是Qt项目的…

TDengine 使用最佳实践

简介 阅读本文档需要具备的基础知识&#xff1a; Linux系统的基础知识&#xff0c;及基本命令网络基础知识&#xff1a;TCP/UDP、http、RESTful、域名解析、FQDN/hostname、hosts、防火墙、四层/七层负载均衡 本文档的阅读对象有&#xff1a;架构师、研发工程师&#xff0c;…

Python 推导式详解

Python 推导式详解 Python 推导式&#xff08;Comprehension&#xff09;是一种简洁高效的语法结构&#xff0c;用于快速生成列表、字典、集合等数据结构。它通过一行代码替代传统的多行循环&#xff0c;使代码更简洁易读。以下是常见的推导式类型及用法详解&#xff1a; 一、…

C#设计模式Demo——MVC

设计模式Demo——MVC 1.View1.1页面示例1.2View代码1.3修改界面以及代码 2.Model3.Controller4.数据结构5枚举类型6.工具类6.1缓存信息6.2扩展类. 文件结构图 1.View 1.1页面示例 1.2View代码 using System; using System.Data; using System.Windows.Forms; using MVC模式…

词向量:优维大模型语义理解的深度引擎

★ 放闸溯源 优维大模型「骨架级」技术干货 第二篇 ⇓ 词向量是Transformer突破传统NLP技术瓶颈的核心&#xff0c;它通过稠密向量空间映射&#xff0c;将离散符号转化为连续语义表示。优维大模型基于词向量技术&#xff0c;构建了运维领域的“语义地图”&#xff0c;实现从…