string

  • 1.定位new运算符
    • (1)介绍最基本的定位new
    • (2)定位new运算符用于对象
  • 2.重载<<运算符,转换函数,构造函数使用new
    • (1)重载<<运算符
    • (2)转换函数
    • (3)其构造函数使用new类
  • 3.类的补充

1.定位new运算符

(1)介绍最基本的定位new

通常,new负责在堆中找到一个足以满足要求的内存块。new运算符还有另一种变体,被称为定位new运算符。

功能:可以指定要在内存中的位置存放数据。

该内容简单,直接用一个小程序来说明即可:

#include<iostream>
#include<new>
using namespace std;char buffer[5000];int main()
{double* p1, * p2;   //先定义两个double类型的指针p1 = new double[100];    //开辟一个内存空间:(1)大小为100个double  (2)p1指向这个空间的起始位置p2 = new(buffer)double[100];    //开辟一个内存空间: (1)大小为100个double   (2)p2指向这个空间的起始位置   (3)这个内存空间从buffer的//内存空间占用,且占用起始位置为buffer数组的首地址。//可以输出他们的地址来求证for (int i = 0; i < 100; i++)   //先初始化赋值{p1[i] = p2[i] = i + 1;}cout << " buffer数组的首地址是:" << (void*)buffer << endl;cout << "数组p1的首地址是:" << p1 << endl;cout << "数组p2的首地址是:" << p2 << endl;//结果说明buffer的地址和p2的地址是一样的。//那么这就要注意了,虽然说p2数组是使用new运算符的,但是不能使用delete删除,因为本质上,p2是使用静态数组的空间,而不是从堆空间开辟的delete[]p1;return 0;
}

上面的程序说明了3点:

  1. 使用new运算符从静态数组(buffer)里“开辟”空间本质上没有开辟空间,只是利用了原本定义了的数组的内存,这里的数组应该是在栈空间,属于自动变量,所以不能使用delete来释放空间。
  2. 使用new运算符开辟空间,其初始位置和被占用空间的数组的头地址一样(上面程序里p2的值和buffer一样)
  3. 注意使用(void*)输出buffer,因为我们要得到的是一个地址,否则会输出一个字符串。

再加深一点
如果想要在buffer数组的中间位置新建一个数组,而不是在buffer的头位置新建数组,该怎么搞?
很简单,看下面的程序:

#include<iostream>
#include<new>
using namespace std;char buffer[5000];int main()
{double* p1, * p2;   //先定义两个double类型的指针p1 = new double[100];    //开辟一个内存空间:(1)大小为100个double  (2)p1指向这个空间的起始位置p2 = new(buffer)double[100];    //开辟一个内存空间: (1)大小为100个double   (2)p2指向这个空间的起始位置   (3)这个内存空间从buffer的//内存空间占用,且占用起始位置为buffer数组的首地址。//可以输出他们的地址来求证for (int i = 0; i < 100; i++)   //先初始化赋值{p1[i] = p2[i] = i + 1;}cout << " buffer数组的首地址是:" << (void*)buffer << endl;cout << "数组p1的首地址是:" << p1 << endl;cout << "数组p2的首地址是:" << p2 << endl;//结果说明buffer的地址和p2的地址是一样的。//那么这就要注意了,虽然说p2数组是使用new运算符的,但是不能使用delete删除,因为本质上,p2是使用静态数组的空间,而不是从堆空间开辟的delete[]p1;//delete[]p2  //error//第二阶段;增加一个偏移量,从而灵活使用buffer空间p2 = new(buffer + 100 * sizeof(double))double[100];   //可以这样理解:从buffer的头地址开始,依次移动100*8个字节,也就是buffer[800].那么//也就是说,从buffer[800]开始,一直到buffer[800+100*8]的位置都是归p2所有(即使这里面有数据)cout << endl << endl << "p2的地址是:" << p2 << endl;cout << "buffer[800]的地址是:" << (void*)&buffer[800] << endl << "buffer的地址是:" << (void*)buffer << endl;cout << " 会发现,p2的地址和buffer[800]是一样的,800这个偏移量没有算错,可能会发现地址只差了320,不是800,但是注意,地址是16进制!!\n";return 0;
}

(2)定位new运算符用于对象

定位new运算符用于对象有一点问题。具体是什么;看一下下面的程序,有详细说明:

#include<iostream>
#include<string>
#include<new>using namespace std;const int BUF = 512;class JustTesting   //搞一个类
{private:string words;int number;
public:JustTesting(const string& s = "Just Testing", int n = 0)   //默认构造函数(只能有一个){words = s;number = n;cout << "对象--"<<words << " 构建\n";}~JustTesting()  //析构函数,里面加一个追踪功能{cout <<"对象--"<< words << " 析构\n";}void Show()const{cout << words << ", " << number << endl;}
};int main()
{char* buffer = new char[BUF];   //使用常规new开辟对空间,buffer指向这个空间的首地址。等价于buffer[BUF],但是在堆空间中JustTesting* pc1, * pc2;    //两个对象指针pc1 = new (buffer)JustTesting;    //使用new的重载,定位new运算符pc2 = new JustTesting("Heap1", 20);   //常规new,开辟空间的同时调用构造函数初始化对象,然后让pc2指向这个对象cout << " 内存空间地址:\n" << "buffer: " << (void*)buffer << endl << "  pc2: " << pc2 << endl<<"pc1: "<<pc1<<endl;cout << "  内存里的数据:\n";cout << " pc1:";pc1->Show();cout << "pc2:";pc2->Show();cout << "上面的测试说明pc1不需要析构,也就是说,pc1的空间在buffer数组里面,还是自动变量,静态数组变量,不会调用析构函数。";delete pc2;return 0;}

相信到这里,鉴于之前的重载复制函数和赋值运算符(上一篇博客C++P356-386),想到一个问题。如果说,使用定位new运算符导致对象不能析构,那么如果对象里面有字符串指针,那么岂不是不能释放空间了?

#include<iostream>
#include<cstdio>
#include<string>
#include<new>using namespace std;const int BUF = 512;class JustTesting
{private:int number;
public:string words;JustTesting(const string& s = "Just Testing", int n = 0)   //默认构造函数(只能有一个){words = s;number = n;cout << "对象--"<<words << " 构建\n";}~JustTesting()  //析构函数,里面加一个追踪功能{cout <<"对象--"<< words << " 析构\n";}void Show()const{cout << words << ", " << number << endl;}
};int main()
{char* buffer = new char[BUF];   //使用常规new开辟对空间,buffer指向这个空间的首地址。等价于buffer[BUF],但是在堆空间中JustTesting* pc1, * pc2;    //两个对象指针pc1 = new (buffer)JustTesting;    //使用new的重载,定位new运算符pc2 = new JustTesting("Heap1", 20);   //常规new,开辟空间的同时调用构造函数初始化对象,然后让pc2指向这个对象cout << " 内存空间地址:\n" << "buffer: " << (void*)buffer << endl << "  pc2: " << pc2 << endl<<"pc1: "<<pc1<<endl;cout << "  内存里的数据:\n";cout << " pc1:";pc1->Show();cout << "pc2:";pc2->Show();cout << "上面的测试说明pc1不需要析构,也就是说,pc1的空间在buffer数组里面,还是自动变量,静态数组变量,不会调用析构函数。\n\n\n";JustTesting* pc3, * pc4;pc3 = new(buffer)JustTesting("Bad idea", 6);pc4 = new JustTesting("Heap2", 10);cout << "  内存里的数据:\n";cout << " pc3:";pc3->Show();cout << "pc4:";pc4->Show();delete pc2;delete pc4;cout << pc3->words<<endl;pc3->~JustTesting();     //显示使用析构函数即可delete[]buffer;return 0;}

总结:使用定位new运算符创建对象,那么要显示使用析构函数,否则对象不会被析构。这有什么坏处呢?暂时也不知道.好像释放buffer空间就相当于析构了对象了,毕竟对象是在buffer里面创建的。

2.重载<<运算符,转换函数,构造函数使用new

(1)重载<<运算符

要重新定义<<运算符,以便将它和cout一起用来显示对象的内容,要像下面这样:

ostream &operator<<(ostream &os,const c_name &obj)  //c_name是类名
{os<<...;return os;
}

(1)如果该类提供了能够返回所需内容的公有方法,则可以在运算符函数中使用这种方法,这样就不需要设置为友元函数了。
(2)返回类型必须是引用

(2)转换函数

1.如果要将单个值转换为类类型,需要创建原型如下所示的类构造函数:
c_name(type_name value);
其中,c_name是类名,type_name是要转换的类型的名称。
2.如果要将类类型转换为其它类型,则需要创建原型如下的类成员函数
operator type_name();
即使该函数没有类型,但是仍然需要返回所需类型的值.
如果不想被隐式转换,应该使用关键字explicit;

(3)其构造函数使用new类

如果类使用new运算符来分配类成员指向的内存,在设计时应采取一些预防措施

  • 对于指向的内存是由new分配的所有类成员,都应在类的析构函数中对其使用delete。
  • 如果析构函数通过对指针类成员使用delete来释放内存,则每个构造函数都应当使用new来初始化指针,或将它设置为空指针
  • 构造函数中要么使用new[],要么使用new,不能混合使用。如果构造函数使用new,则析构函数应该使用delete,如果构造函数使用new[],则相应的析构函数应该使用delete[];
  • 应定义一个分配内存的复制构造函数,这样程序将能够将类对象初始化为另一个对象。这种构造函数的原型通常如下:
    className(const className&);
  • 应定义一个重载赋值运算符的类成员函数。
    c_name & c_name::operator=(const c_name& cn)
    {
    if(this==&cn)
    return *this;
    delete [] c_pointer;
    c_pointer=new type_name[size];

    return *this;
    }

3.类的补充

(1)嵌套结构和类
在类声明中声明的结构,类,或枚举被称为是被嵌套在类中,其作用域为整个类。这种声明不会创建数据对象(当然,声明只是声明),只是说明了这个类型可以在类中使用。
如果在类的私有部分声明,则只能在该类中使用被声明的类型;
如果在类的公有部分声明,则可以从类的外部通过作用域解析运算符使用被声明的类型。

(2)类的常量成员初始化
如果在类中又const 的成员,则只能初始化,而不能赋值。所以对于const类型数据(和引用类型数据),必须在执行到构造函数体之前,即创建对象时初始化。C++提供了一种特殊的初始化方式,叫初始化列表(只有构造函数可以使用)。成员初始化列表由逗号分隔的初始化列表组成(前面带冒号)。它位于参数列表的右括号之后,函数体左括号之前。如果数据成员的名称为mdata,需要初始化为val,则初始化mdata(val)。

一个例子来说明初始化列表的使用和语法:

//class是一个类,a,b,c都是类的数据成员。则应该这样使用初始化列表
class ::class(int n,int m) :a(n), b(0), c(n*m)
{...
}

上面的代码将a初始化为n,b初始化为0,c初始化为n*m;从概念上来说,这些工作都是创建对象的时候完成的,因为此时没有执行大括号里面的代码块。
注意几点:

  1. 初始化列表只能用于构造函数
  2. 必须使用这种格式来初始化非静态的const数据成员(C++11之前)
  3. 必须使用这样格式来初始化引用数据成员

C++primeplus P368-P391相关推荐

  1. C++PrimePlus第5章编程练习答案及运行结果

    1. 第1题 #include <iostream>using namespace std;int main() {int n1 = 0;//第一个数int n2 = 0;//第二个数in ...

  2. C++primeplus p247-277

    这里写目录标题 1.头文件 2.#ifdefine 和#pragma once的区别 3.说明符和限定符 (1) cv-限定符 (2)mutable (3)const 4.函数链接性 5.语言链接性 ...

  3. 《果壳中的C# C# 5.0 权威指南》 (09-26章) - 学习笔记

    <果壳中的C# C# 5.0 权威指南> ========== ========== ========== [作者] (美) Joseph Albahari (美) Ben Albahar ...

  4. UG基础知识学习视频目录整理(建模篇)

    为了方便搜索需要的视频资料,整理了唐康林老师发布在B站的视频目录,支持全局目录搜索,点击直达视频. NX8.5视频教程(建模篇) P1第1讲:NX8.5软件的安装! P2第2讲:NX8.5界面的初识! ...

  5. 读书笔记:《C++ PrimerPlus》 第九章~第十一章

    第一章 预备知识 第二章 开始学习C++ 第三章 处理数据 第四章 复合类型 第五章 循环和关系表达式 第六章 分支语句和逻辑运算符 第七章 函数--C++的编程模块 第八章 函数探幽 第九章 内存模 ...

  6. Downloading NEX-GDDP data from google Earth Engine

    https://code.earthengine.google.com/139432f76ae3f6a81b1459762325ef7f UCASleon // 指定开始和结束日期 var start ...

  7. 面试+学习+做项目+最全Java视频讲解

    关注公众号:计算机视觉与图形学实战 Java零基础教程视频(适合Java 0基础,Java初学入门) 尚硅谷Java零基础入门教程(含百道Java真题,2万多行Java代码实战) 黑马程序员全套Jav ...

  8. C++ primer笔记

    序:Goole C++ style Guide: 无符号与带符号线程的结果会把带符号当成无符号计算 p17:带.h会使用C的标准库文件,部分IDE不支持C++程序带.h头文件 可以使用nullptr代 ...

  9. 《深入理解Java虚拟机》知识点目录

    深入理解Java虚拟机 第二章 Java内存区域与内存溢出异常 2.2 运行时数据区域 p42 Java内存区域划分概述 p43 程序计数器 p43 程序计数器是唯一一个不会存在OOM的内存区域 p4 ...

最新文章

  1. 一条数字链路连接的端口无法UP及后续相应故障的排除
  2. OSPF从Down到Full的七个状态
  3. MySQL cast()函数
  4. JDBC笔记01-JDBC,Connection,Statement,ResultSet,PreparedStatement,Properties
  5. 疫情挡不住上市步伐:视频模拟敲锣 A股云上市了解一下
  6. 为什么Docker不能解决云上的所有问题
  7. PowerBuilder 生成条形码
  8. php throw,PHP的Try, throw 和 catch简单用法
  9. 定常系统(时不变系统)和时变系统
  10. 【今日爆点】华为HDC开发者大会上正式发布深度欧拉V1.0
  11. pyQt怎样设置窗口标题、图标
  12. 巨掌柜,新模式,新机遇!
  13. Matlab-稀疏矩阵
  14. Lumberjack库在GCDAsyncSocket上的打印输出
  15. 移动硬盘数据恢复该如何进行?2个方法告诉你
  16. iOS 蓝牙 Bluetooth 外围设备 中央设备
  17. linux gtk 仿qq聊天程序
  18. 验证 %java_home%_报错:JAVA_HOME cannot be determined from the Registry的解决
  19. android 蓝牙 bluetooth OPP文件传输
  20. 在线图片格式转换,svg转png,jpg转ico

热门文章

  1. 【使用Redis分布式锁实现优惠券秒杀功能】-Redis学习笔记05
  2. latex tips 偏导数符号 单词partial+倒三角 \nabla
  3. 【技能教学】如何通过FFMPEG编码推RTSP视频直播流到EasyDarwin开源平台时叠加时间水印?
  4. 计算机勾兑双绝是谁发明,那些跟你说“勾兑酒”是酒精酒的人,都是在不懂装懂...
  5. 概念图创作-IHMC CmapTools
  6. 微信小程序picker组件 - 省市二级联动
  7. 「C语言进阶」数据内存的存储
  8. vue简易微前端项目搭建(二):子项目模板及项目脚手架搭建
  9. windows efi分区修复
  10. 古魂魂之刃2电脑版用逍遥模拟器电脑上玩手机账号数据互通