栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > C/C++/C#

第12章 类和动态内存分配

C/C++/C# 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

第12章 类和动态内存分配

第12章 类和动态内存分配 0. 前言

本章介绍如何对类使用new和delete以及如何处理由于使用动态内存而引起的一些微妙的问题。

1. 动态内存和类

为了让程序在运行时决定内存分配,C++使用new和delete运算符来动态控制内存。但是在类中使用这些运算符将导致许多问题。

1.1静态类成员

静态类成员特点:无论创建多少个对象,都只有一个静态类成员。也就是说,同一个类中所有对象都使用一个静态类成员。

**注意:**静态类成员是在类声明中声明,在类方法定义中初始化,初始化要在变量名前使用类+作用运算符::指向所属的类,另外不要static修饰。但是如果静态成员时const整数类型或枚举型,则可以在类声明中初始化。

**注意:**在构造函数中使用new来分配内存时,必须在相应的析构函数中使用delete释放内存。如果是使用new[]来分配内存,则应使用delete[]来释放内存。

1.2 特殊成员函数

C++会自动提供下面成员函数:

  1. 默认构造函数:如果没有提供任何构造函数,则C++会自动创建默认构造函数。注意:如果定义了构造函数,则C++将不会定义默认构造函数。另外,只能有一个默认构造函数。
  2. 复制构造函数:用于将一个对象复制到新创建的对象中。它用于初始化过程中(包括按值传递参数),而不是常规的赋值过程中。
Class_name(const Class_name &);//类的复制构造函数原型
  1. 何时调用复制构造函数:新建一个对象并将其初始化为同类现有对象时(包括按值传递参数)
  2. 默认的复制构造函数的功能:逐个复制非静态成员的值(浅复制)
1.3 显式复制构造函数

如果类中一些成员是使用new初始化的、指向数据的指针(例如指向字符串指针),另外,如果类中有静态类成员复制时需要更新,则应当定义一个显式复制构造函数,以复制指向的数据所用的空间创建副本,这称为深度复制。

1.4 赋值运算符

C++允许类对象进行赋值,这是通过自动为类重载赋值运算符实现。注意赋值运算符只能由类成员函数重载。运算符原型:

Class_name & Class_name::operator=(const Class_name &);		//接受并返回一个类对象的引用

功能:与复制构造函数相似,对对象中成员逐个复制,静态类成员不受影响。

何时使用:将已有对象赋给另一个对象,将自动使用重载的赋值运算符。

由于默认赋值运算符会直接复制指向同一块内存的指针,导致释放同一内存块两次,所以可以自定义赋值运算符进行深度复制。

自定义赋值运算符特点:

  • 目标对象可能引用以前分配的数据,所以函数应使用delete[]释放这些数据
  • 函数应当避免将对象赋给自身。否则,释放内存操作会删除原对象的内容。所以应首先判断操作数是否是本身。
  • 函数应返回一个指向调用对象的引用,这样就可以连续赋值。
2. 改进stringbad类 2.1 在构造函数尽量使用new []
str = new char[1];
str = new char;		
//上面两种方式分配的内存量相同,但是前者能够与类析构函数中delete []兼容,而后者不行
//字面值0可以表示数值0,也可以表示空指针。空指针可以使用NULL表示,C++11引入新关键字nullptr表示空指针
2.2 比较友元函数
bool operator==(const String &st1, const String &st2)
{
	return (std::strcmp(st1.str, st2.str) == 0);
}
//将比较函数作为友元,有助于将Stirng对象与常规的C字符串进行比较
2.3 重载[]访问运算符
//对于中括号运算符,一个操作数在中括号前面,另一个在括号中间
char &String::operator[] (int i)		//返回指定字符的引用,没有保证是否修改数据
{
	return str[i];
}
const char &String::operator[](int i) const
{
	return str[i];			//确保不会修改数据
}
//在重载时,C++会区分常量和非常量函数的特征标,例如上面两个函数
2.4 静态类成员函数

静态类成员函数:在成员函数声明前加上关键字static就可以声明静态成员函数。但是如果函数定义是独立的,则不能声明为静态类成员函数。声明时加上static,定义时就不需要了。

注意:

  • 不能通过对象调用静态成员函数,也就不能使用this指针
  • 如果静态成员函数在公有部分声明,则可以使用类名加上作用域解析运算符来调用它
  • 由于静态成员函数不与特定对象相关联,所以静态成员函数只能使用静态数据成员
3. 在构造函数中使用new的注意事项
  • 如果在构造函数中使用new来初始化指针成员,则应在析构函数中使用delete
  • new和delete必须相互兼容。new对应于delete,new[]对应于delete[]
  • 如果有多个构造函数,则必须以相同方式使用new,因为只有一个析构函数,所有的构造函数都必须与它兼容
  • 应定义一个复制构造函数
  • 应定义一个赋值运算符
4. 返回对象
  1. 返回指向const对象的引用:注意返回对象将调用复制构造函数,而返回引用不会。其次,引用指向的对象应该在调用函数执行时存在。返回类型应该与const &匹配。
  2. **返回指向非const对象的引用:**常见用于重载赋值运算符以及重载<<运算符。允许对对象进行更改
  3. **返回非const对象:**如果返回的对象是被调用函数中的局部变量,则采用这种方式,返回语句将隐式调用复制构造函数
  4. 返回const对象:相对于返回非const对象,加上const是为了防止对对象修改
5. 使用指向对象的指针

析构函数被调用的情况:

  • 如果对象是自动变量,则当执行完定义该对象的程序块时,将调用该对象的析构函数。
  • 如果对象是静态变量,则当程序结束时将调用对象的析构函数。
  • 如果对象是new创建的,则仅当显式使用delete删除对象时,将调用对象的析构函数。

要想使用对象指针,可以:

  • 使用常规表示法声明指向对象的指针
  • 可以将指针初始化为指向已有对象
  • 可以使用new初始化指针,可以同时对new对象初始化
  • 可以使用->运算符调用指针访问类方法
  • 可以对对象指针使用解除引用运算符*来获得对象

使用定位new运算符,我们知道定位new运算符是为了在分配内存时能够指定内存位置。但是我们使用定位new分配对象空间应注意:

  1. 如果不想新对象覆盖原位置对象的内存单元,应对内存位置进行相应的偏移
  2. 如果想为对象的销毁调用析构函数,应显式调用对象的析构函数来说明对象的消亡
  3. 对于使用定位new创建的对象,应以与创建顺序相反的顺序进行删除,因为晚创建的可能依赖于早创建的对象
  4. 仅当所有对象都被销毁后,才能释放用于存储这些对象的缓冲区
成员列表初始化

可以在类中嵌套结构或者类声明,这是C++的一项特性

如果数据成员是非静态const成员或引用,这需要在创建对象前进行初始化,可以在创建构造函数时使用成员列表初始化,例如:

queue(int qs) : qsize(qs), item(0)			//在创建对象时初始化数据成员qsize = qs,item = 0
{
		//函数体
}
//也可以将这种方式用于普通变量初始化
int p1(1);			//等价于int p1 = 1;
转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/890445.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号