一、动态内存和类

C++在分配内存的时候是让程序是在运行时决定内存分配,而不是在编译时再决定。C++使用new和delete运算符来动态控制内存。但是在类中使用这些运算符将导致许多新的编程问题,在这种情况下,析构函数是必不可少的。有时候还必须重载赋值运算符,以保证程序正常运行。
静态类成员函数,首先设计一个StringBad类,然后设计一个功能稍强的String。StringBad和String类对象将包含一个字符串指针和一个表示字符串长度的值。这里使用StringBad和String类,主要是深入了解new、delete和静态成员的工作原理。因此,构造函数和析构函数调用时将显示一些消息,以便你能够按照提示来完成操作。另外,将省略一些有用的成员和友元函数,如重载++和>>运算符以及转换函数,以简化类接口。

// strngbad.h -- flawed string class definition
#include <iostream>
#ifndef STRNGBAD_H_
#define STRNGBAD_H_
class StringBad
{private:char * str;                // pointer to stringint len;                   // length of stringstatic int num_strings;    // number of objects
public:StringBad(const char * s); // constructorStringBad();               // default constructor~StringBad();              // destructor
// friend functionfriend std::ostream & operator<<(std::ostream & os, const StringBad & st);
};
#endif

为何将这个类命名为StringBad?为了说明这是一个不太完整的类,它是使用动态内存分配开发类的第一个阶段,正确地完成了一些显而易见的工作,例如,在构造函数和析构函数中正确的使用new和delete。这个类并没有什么错误,但忽略了一些不明显却必不可少的定西。通过了解这个类的存在,将有助于后面将其转换为更强大的String类,所做的不明显修改。
在这个声明里,首先,它使用char指针(而不是char数组)来表示姓名。这意味着类声明没有为字符串本身分配存储空间,而是在构造函数中使用new来为字符串分配空间。这避免了在类声明中预先定义字符串长度。其次,将num_strings成员声明为静态存储类。静态成员有一个特点就是无论创建多少对象,程序都只创建一个静态变量副本。也就是说,类的所有对象共享同一个静态成员。`

// strngbad.cpp -- StringBad class methods
#include <cstring>                    // string.h for some
#include "strngbad.h"
using std::cout;
// initializing static class member
int StringBad::num_strings = 0;//初始化为0
// class methods
// construct StringBad from C string
StringBad::StringBad(const char * s)
{len = std::strlen(s);             // set sizestr = new char[len + 1];          // allot storagestd::strcpy(str, s);              // initialize pointernum_strings++;                    // set object countcout << num_strings << ": \"" << str<< "\" object created\n";    // For Your Information
}
StringBad::StringBad()                // default constructor
{len = 4;str = new char[4];std::strcpy(str, "C++");          // default stringnum_strings++;cout << num_strings << ": \"" << str<< "\" default object created\n";  // FYI
}
StringBad::~StringBad()               // necessary destructor
{cout << "\"" << str << "\" object deleted, ";    // FYI--num_strings;                    // requiredcout << num_strings << " left\n"; // FYIdelete [] str;                    // required
}
std::ostream & operator<<(std::ostream & os, const StringBad & st)
{os << st.str;return os;
}
// vegnews.cpp -- using new and delete with classes
// compile with strngbad.cpp
#include <iostream>
using std::cout;
#include "strngbad.h"
void callme1(StringBad &);  // pass by reference
void callme2(StringBad);    // pass by value
int main()
{using std::endl;{cout << "Starting an inner block.\n";StringBad headline1("Celery Stalks at Midnight");StringBad headline2("Lettuce Prey");StringBad sports("Spinach Leaves Bowl for Dollars");cout << "headline1: " << headline1 << endl;cout << "headline2: " << headline2 << endl;cout << "sports: " << sports << endl;callme1(headline1);cout << "headline1: " << headline1 << endl;callme2(headline2);cout << "headline2: " << headline2 << endl;cout << "Initialize one object to another:\n";StringBad sailor = sports;cout << "sailor: " << sailor << endl;cout << "Assign one object to another:\n";StringBad knot;knot = headline1;cout << "knot: " << knot << endl; cout << "Exiting the block.\n";}cout << "End of main()\n";// std::cin.get();return 0;
}
void callme1(StringBad & rsb)
{cout << "String passed by reference:\n";cout << "    \"" << rsb << "\"\n";
}
void callme2(StringBad sb)
{cout << "String passed by value:\n";cout << "    \"" << sb << "\"\n";
}

二、改进后新的string类

首先,复制构造函数和赋值运算符,使类能够正确管理类对象使用的内存。其次,由于已经知道了对象何时被创建和释放,因此可以让类构造函数和析构函数保持沉默,不再在每次被调用时都显示消息。另外,也不用再监视构造函数的工作情况,因此可以简化默认构造函数,使之创建一个空字符串,而不是“C++"。
1.修订后的默认构造函数
str = new char[1]与类析构函数兼容。析构函数中包含如下的代码:delete [] str;
2.比较成员函数
在String类中,执行比较操作的方法有3个。按机器排序序列,第一个字符串在第二个字符串之前,则Operator<()函数返回true。要实现字符串比较函数,最简单的方法时使用标准的trcmp()函数,如果按照字母顺序,第一个参数位于第二个参数之前,则该函数返回一个负值:如果两个字符串相同,则返回0;如果第一个参数位于第二个参数之后,则返回一个正值。
3.使用中括号表示法访问字符
可以使用方法operator来重载该运算符,通常,二元C++运算符位于两个操作数之间。但对于中括号运算符,一个操作数位于第一个中括号的前面,另一个操作数位于两个中括号之间。
4.静态成员函数
可以将成员函数声明为静态的,(函数声明必须包含关键字static,但如果函数定义是独立的,则其中不能包含关键字static)这样做有两个重要的后果。
首先,不能通过对象调用静态成员函数:实际上,静态成员函数甚至不能使用this指针,如果静态成员函数是在公有部分声明的,则可以使用类名个作用域接写运算符来调用它。
其次,由于静态成员函数不与特定的对象相关联,因此只能使用静态数据成员。
5.进一步重载赋值运算符
程序使用构造函数String(const char*)来创建一个临时String对象,其中包含temp中的字符副本。
使用String&String::operator = (const String &)函数将临时对象中的信息复制到name对象中。
程序调用析构函数~String()删除临时对象。

三、在构造函数中使用new时应注意的事项

  • 如果构造函数中使用new来初始化指针成员,则应在析构函数中使用delete;
  • new和delete必须相互兼容,new对应于delete,new[]对应于delete[];
  • 如果有多个构造函数,则必须以相同的方式使用new,要么都带中括号,要么都不带。因为只有一个析构函数,所有的构造函数都必须与它兼容。然而,可以在一个构造函数中使用new初始化指针,而在另一个构造函数中将指针初始化为空,这是因为delete可以用于空指针。
  • 应定义一个复制构造函数,通过深度复制将一个对象初始化为另一个对象。
  • 应当定义一个赋值运算符,通过深度复制将一个对象复制给另一个对象。

四、有关返回对象的说明

当成员函数或独立的函数返回对象时,有几种返回方式可供选择。可以返回指向对象的引用、指向对象的const引用或const对象。
1.返回指向const对象的引用
使用const引用的常见原因是旨在提高效率,但对于何时可以采用这种方式存在一些限制。如果函数返回(通过调用对象的方法或将对象作为参数)传递给它的对象,可以通过返回引用来提高其效率。

//version 1
Vector Max (const Vector & v1,const Vector & v2)
{if (v1.magva1() > v2.magva1())return v1;else return v2;
}      
//version 2
const Vector & Max (const Vector & v1,const Vector & v2)
{if (v1.magva1() > v2.magva1())return v1;else return v2;
}      

首先,返回对象将调用复制构造函数,而返回引用不会。因此,第二个版本所做的工作更少,效率更高。
其次,引用指向的对象应该在调用函数执行时存在。
第三,v1和v2都被声明为const引用,因此返回类型必须为const,这样才匹配。
2.返回指向非const对象的引用
两种常见的返回非const对象的情形是,重载赋值运算符以及重载与cout一起使用的<<运算符,前者这样做旨在提高效率,而后者必须这样做。
3.返回对象
如果被返回对象是在被调用函数中的局部变量,则不应该按引用的方式返回它,因为在被调用函数执行完毕时,局部对象将调用析构函数。因此,当控制权回到调用函数时,引用指向的对象将不再存在,在这种情况下,应返回对象而不是引用。通常,被重载的算术运算符属于这一类。
4.返回const对象
前面的Vector::operator+()定义有一个奇异的属性,它旨在让您能够以下面的方式使用它:
net = force1+force2
然而,这种定义也允许您这样使用它:
force1 + force2 = net;
cout << (force1 +force2 = net).magval()<<endl;

五、使用指向对象的指针

// sayings2.cpp -- using pointers to objects
// compile with string1.cpp
#include <iostream>
#include <cstdlib>      // (or stdlib.h) for rand(), srand()
#include <ctime>        // (or time.h) for time()
#include "string1.h"
const int ArSize = 10;
const int MaxLen = 81;
int main()
{using namespace std;String name;cout <<"Hi, what's your name?\n>> ";cin >> name;cout << name << ", please enter up to " << ArSize<< " short sayings <empty line to quit>:\n";String sayings[ArSize];char temp[MaxLen];               // temporary string storageint i;for (i = 0; i < ArSize; i++){cout << i+1 << ": ";cin.get(temp, MaxLen);while (cin && cin.get() != '\n')continue;if (!cin || temp[0] == '\0') // empty line?break;                   // i not incrementedelsesayings[i] = temp;       // overloaded assignment}int total = i;                   // total # of lines readif (total > 0){cout << "Here are your sayings:\n";for (i = 0; i < total; i++)cout << sayings[i] << "\n";// use pointers to keep track of shortest, first stringsString * shortest = &sayings[0]; // initialize to first objectString * first = &sayings[0];for (i = 1; i < total; i++){if (sayings[i].length() < shortest->length())shortest = &sayings[i];if (sayings[i] < *first)     // compare valuesfirst = &sayings[i];     // assign address}cout << "Shortest saying:\n" << * shortest << endl;cout << "First alphabetically:\n" << * first << endl;srand(time(0));int choice = rand() % total; // pick index at random// use new to create, initialize new String objectString * favorite = new String(sayings[choice]);cout << "My favorite saying:\n" << *favorite << endl;delete favorite;}elsecout << "Not much to say, eh?\n";cout << "Bye.\n";
// keep window open
/*    if (!cin)cin.clear();while (cin.get() != '\n')continue;cin.get();
*/return 0;
}

1,再谈new和delete
使用new创建的每一个对象的名称字符串分配存储空间,这是在构造函数中进行的,因此析构函数使用delete来释放这些内存。因为字符串是一个字符数组,所以析构函数使用的是带中括号的delete。这样,当对象被释放时,用于存储字符串内容的内存将被自动释放。
这不是为要存储的字符串分配内存,而是为对象分配内存。也就是说,为保存字符串地址的str指针和len成员分配内存。创建对象将调用构造函数,后者分配用以保存字符串的内存,并将字符串的地址赋给str。然后,当程序不再需要该对象时,使用delete删除它。对象是单个的,因此,程序使用不带中括号的delete。与前面介绍的相同,这将只释放用于保存str指针和len成员的空间,并不释放str指向的内存,而该任务由析构函数来完成。
在下述情况下析构函数将被调用:

  • 如果对象是动态变量,则当执行定义该对象的程序块时,将调用该对象的析构函数。

  • 如果对象是静态变量,则在程序结束时将调用对象的析构函数。

  • 如果对象是new创建的,则仅当您显示使用delete删除对象是,其析构函数才会被调用。
    2.指针和对象小结

  • 使用常规表示法来声明指向对象的指针:String * glamuor;

  • 可以将指针初始化为指向已有的对象:string *first = &sayings[0];

  • 可以使用new来初始化指针,这将创建一个新的对象:String * favorite = new String(saying [choice])

  • 对类使用new将调用相应的类构造函数来初始化创建的对象:String * gleep = new String;

  • 可以使用->运算符通过指针访问类方法:if (sayings[i].length() < shortest ->length())

  • 可以对对象指针应用解除引用运算符(* )来获得对象;
    if(saying[i]< * first)
    first = &sayings[i];
    3.再谈定位new运算符
    定位new运算符能够在分配内存时能够指定内存位置。

// placenew2.cpp  -- new, placement new, no delete
#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 << " constructed\n"; }~JustTesting() { cout << words << " destroyed\n";}void Show() const { cout << words << ", " << number << endl;}
};
int main()
{char * buffer = new char[BUF];       // get a block of memoryJustTesting *pc1, *pc2;pc1 = new (buffer) JustTesting;      // place object in bufferpc2 = new JustTesting("Heap1", 20);  // place object on heap  cout << "Memory block addresses:\n" << "buffer: "<< (void *) buffer << "    heap: " << pc2 <<endl;cout << "Memory contents:\n";cout << pc1 << ": ";pc1->Show();cout << pc2 << ": ";pc2->Show();JustTesting *pc3, *pc4;
// fix placement new locationpc3 = new (buffer + sizeof (JustTesting))JustTesting("Better Idea", 6);pc4 = new JustTesting("Heap2", 10);    cout << "Memory contents:\n";cout << pc3 << ": ";pc3->Show();cout << pc4 << ": ";pc4->Show();    delete pc2;           // free Heap1         delete pc4;           // free Heap2
// explicitly destroy placement new objectspc3->~JustTesting();  // destroy object pointed to by pc3pc1->~JustTesting();  // destroy object pointed to by pc1delete [] buffer;     // free buffer// std::cin.get();return 0;
}

六、复习各种技术

1.重载<<运算符
要重新定义<<运算符,以便将它和cout一起用来显示对象的内容。请定义下面的友元运算符函数:

ostream & operator << (ostream & os, const c_name & obj)
{os << ……;return os ;
}

其中,c_name是类名,如果该类提供了能够返回所需内容的公有方法,则可在运算符函数中使用这些方法,这样便不用将它们设置为友元函数。
2.转换函数
要将单个值转换为类型,需要创建原型如下所示的类构造函数:
c_name(type_name value)
其中c_name为类名,type_name是要转换的类型的名称。
要将类型转换为其他类型,需要创建原型如下所示的类成员函数:
operator type_name();
虽然该函数没有返回声明类型,但应该返回所需类型的值。
3.其构造函数使用new的类

  • 对于指向的内存是new分配的所有成员,都应在类的析构函数中对其使用delete,该运算符
    将释放分配的内存。
  • 如果析构函数通过对指针类成员使用delete来释放内存,则每个构造函数都应当使用new来初始化指针,或将它设置为空指针。
  • 构造函数中要么使用new[],要么使用new,而不能混用。如果构造函数使用的new[],则析构函数应使用delete[];反之。
  • 应定义一个分配内存的复制构造函数,这样程序将能够将类对象初始化为另一个类对象。

七、队列模拟

队列是一种抽象的数据类型,可以存储有序的项目序列。新项目被添加在队尾,并可ui删除队首的项目。队列有点像栈,但栈在同一端进行添加和删除。栈是后进先出的结构,而队列是先进先出的。
1.队列类
队列的特征:

  • 存储有序的项目序列
  • 容纳的项目数有一定的限制
  • 能够创建空队列
  • 检查队列是否为空
  • 检查队列是否为满的
  • 在队尾添加项目
  • 从队首删除项目
  • 能够确定队伍的项目数
    2.Customer类
class Customer
{private:long arrive;int processtime;
public:Customer() {arrive = processtime = 0;}void set(long when);long when() cosnt{return arrive;}int ptime() const{return processtime;}
};
void Customer::set(long when)
{procresstime = std::rand() %3/1;arrive = when;
}

默认构造函数创建一个空客户。set()成员函数将到达时间设置为参数,并将处理时间设置为1~3中的一个随机值。

读书笔记||类和动态内存分配相关推荐

  1. 《C++ Primer Plus》读书笔记之十—类和动态内存分配

    第12章 类和动态内存分配 1.不能在类声明中初始化静态成员变量,这是因为声明描述了如何分配内存,但并不分配内存.可以在类声明之外使用单独的语句进行初始化,这是因为静态类成员是单独存储的,而不是对象的 ...

  2. C++类与动态内存分配

    11.10 类与动态内存分配 通常,最好是在程序运行时(而不是编译时)确定诸如使用多少内存等问题.对于在对象中存储姓名来说,通常的C++方法是,在类构造函数中使用new运算符在程序运行时分配所需的内存 ...

  3. 第12章类和动态内存分配

    第12章类和动态内存分配 (1) class student {char name[40];//并不是每一个字符串都是40//如果是一个对象数组,则浪费空间 }; 12.1 (1)静态成员在类声明中声 ...

  4. C++ Primer Plus学习(十一)——类和动态内存分配

    类和动态内存分配 动态内存和类 静态类成员 特殊成员函数 string类的改进 构造函数中的new 返回对象 指向对象的指针 成员初始化列表(member initializer list) 动态内存 ...

  5. 第12章-cpp类和动态内存分配

    本章内容包括: • 对类成员使用动态内存分配. • 隐式和显式复制构造函数. • 隐式和显式重载赋值运算符. • 在构造函数中使用new所必须完成的工作. • 使用静态类成员. • 将定位new运算符 ...

  6. C++ 学习笔记之---类和动态内存分配

    参考自<C++ Primer Plus 6th Edition> 程序对内存的使用: 链接:http://zhidao.baidu.com/link?url=An7QXTHSZF7zN9r ...

  7. C++ Primer Plus学习笔记之类和动态内存分配

    前言 个人觉得学习编程最有效的方法是阅读专业的书籍,通过阅读专业书籍可以构建更加系统化的知识体系. 一直以来都很想深入学习一下C++,将其作为自己的主力开发语言.现在为了完成自己这一直以来的心愿,准备 ...

  8. 类和动态内存分配——C++ Prime Plus CH12

    ①动态内存和类 1.复习示例和静态类成员 使用程序复习new和delete用法. // badstring.h文件 #include<iostream> #ifndef STRING_BA ...

  9. 第12章、类和动态内存分配

    12.1.2 特殊成员函数 C++自动提供了下面的这些成员函数: 1.默认构造函数,如果没有定义构造函数 带参数的构造函数也可以是默认构造函数,只要所有参数都有默认构造函数(只能有一个默认 构造函数, ...

最新文章

  1. yii2笔记: 单元测试
  2. JS重写toString(),打印想要的值
  3. EntityFramework_MVC4中EF5 新手入门教程之三 ---3.排序、 筛选和分页
  4. 自制简单表单验证relative与absolute定位
  5. 野生前端的数据结构练习(9)冒泡排序,选择排序,插入排序
  6. 搜狐“狐友”正式版上线 扩张我的社交圈
  7. 第九十五题(推断一字符串是不是对称的)
  8. 基于机器视觉的洋葱霜霉病症状自动检测
  9. 实验一 DOS命令解释程序的编写
  10. 自定义Qml控件:ImageButton
  11. 菜鸟的草缸 篇五:五个月的草缸血泪史:除藻-鱼病-翻缸的经验(完结篇)
  12. android: unexpected end of stream
  13. bim软件32位计算机条件,哪些bim软件能在在win732位上运行?
  14. ftpclient覆盖上传文件
  15. python生成随机字符串包含数字字母_使用python生成一个指定长度的字符串(随机密码),要求包括数字、字母、特殊符号(string库解析)...
  16. layui upload 提示语
  17. nodejs 写入html,html2markdownnodejs也能写爬虫?记一次blog迁移至Ghost
  18. 《喜羊羊与灰太狼》中羊村的团队建设
  19. vs2017--MFC读写excel
  20. uniapp微信客服

热门文章

  1. 存储区域网络SAN简介
  2. php 3木目,拼多多质量调查:相机买回来才发现是木目木几
  3. App Store 开发者账号续费,找不到入口:非活跃用户
  4. 大脑的「行为性」成瘾,如赌博、购物、游戏、工作、饮食和性等成瘾,与毒品、药物类成瘾有什么区别?心理上有什么区别?
  5. JS 基础篇(发布订阅模式)
  6. VJ 1474 雷曼兔
  7. 单片机延时程序的实现
  8. 国嵌linux内核编程,国嵌视频学习笔记---linux内核开发1
  9. Linux上搭建高可用nacos集群详细步骤
  10. 禁止乱用:一款牛批的开源去马赛克工具。。。