静态类成员

num_strings成员声明为静态存储类。静态类成员有一个特点:无论创建了多少对象,程序都只创建一个静态类变量副本。也就是说,类的所有对象共享一个静态成员。num_strings成员可以用来记录所创建的对象数目。

这里以StringBad类与String类为例,深入了解new、delete和静态类成员的工作原理。C++标准string类的友好接口涉及大量的编程技术,这里痛StringBad类与String类来了解其底层结构。

不能在类声明中初始化静态成员变量。这是因为声明描述了如何分配内存,但是并不分配内存。对于静态类成员,可以再类声明之外使用单独的语句来进行初始化。这是因为静态类成员是单独存储的,而不是对象的组成部分。

int StringBad::num_strings =0;

初始化指出了类型,并使用了作用域运算符,但没有使用关键字static。

初始化是在方法文件中,而不是在声明文件中。这是因为在类声明头文件中,程序可能将头文件包括在其他几个文件中。如果在头文件中进行初始化,将出现多个初始化语句副本,从而引发错误。

例子说明的所有问题都是编译器自动生成的成员函数引起的。

====================================

特殊成员函数

如果您将一个对象赋给另一个对象,编译器将提供赋值运算符的定义。

结果表明,StringBad类中的问题是由隐式赋值构造函数和隐式赋值运算符引起的。

默认构造函数

如果没有提供任何构造函数,C++将创建默认构造函数。假如定义了一个Klunk类,但没有提供任何构造函数,则编译器将提供下述默认构造函数:

Klunk ::Klunk()   //implicit default constructor

也就是说,编译器将提供一个不接受任何参数,也不执行任何操作的构造函数(默认的默认构造函数)。

如果定义了构造函数,C++将不会定义默认构造函数。如果希望在创建对象时不显式地对它进行初始化,则必须显式地定义默认构造函数。

复制构造函数

复制构造函数用于将一个对象复制到新创建的对象中。也就是说,它用于初始化过程中(包括按值传递参数),而不是常规的赋值过程中。类的赋值构造函数原型通常如下:

Class_name(const Class_name &);

它接受一个指向类对象的常量引用作为参数。

StringBad(const StringBad &);

对于复制构造函数,要知道两点:何时调用和有何功能。

何时调用复制构造函数

新建一个对象并将其初始化为同类现有对象时,复制构造函数都将被调用。最常见的情况是将新对象显式地初始化为现有的对象。例如假如motto是一个StringBad对象,则下面4种声明都将调用复制构造函数:

StringBad ditto(motto);

StringBad metoo = motto;

StringBad also = StringBad(motto);

StringBad * pStringBad = new StringBad(motto);

当函数按值传递对象或函数返回对象时,都将使用复制构造函数。记住,按值传递意味着创建原始变量的一个副本。编译器生成临时对象时,也将使用复制构造函数。

由于按值传递对象将调用复制构造函数,因此应该按引用传递对象。这样可以节省调用构造函数的时间以及存储新对象的空间。

默认的复制构造函数的功能

默认的赋值构造函数逐个复制非静态成员。成员的复制也称为浅复制。复制的是成员的值。

StringBad sailor = sports;

等同于下述代码:

sailor.str = sports.str;

sailor.len = sports.len;

==================================

复制构造函数的哪里出了问题

析构函数在任何对象过期时都会被调用。

复制构造函数被用来初始化callme2()的形参。默认的赋值构造函数不说明其行为,也不指出创建过程,也不增加计数器num_strings的值。但析构函数更新了计数器。这是一个问题。因为这意味着无法准确地记录对象计数。解决方法就是提供一个队计数进行更新的显式复制构造函数

字符串内容出现乱码,在于调用默认复制构造函数时,复制的并不是字符串,而是一个指向字符串的指针。也就是说将sailor初始化为sports后,得到的是两个指向同一个字符串的指针。而当sailor对象过期被销毁后,会调用其析构函数,这时候str指针指向的内存被释放掉了。这就导致sports.str指向的内存被sailor的析构函数释放掉了。这通常是内存管理不善的表现。(这也是浅复制会存在的问题)。

1、定义一个显式复制构造函数

解决类复制过程中的这种问题的方法是进行深度复制。也就是说复制构造函数应当复制字符串并将副本的地址赋给str成员,而不仅仅是复制字符串的地址。这样每个对象都有自己的字符串,而不是引用另一个对象的字符串。

浅复制仅仅是复制指针信息,而不会深入“挖掘”,以复制指针引用的结构。

====================================

StringBad的其他问题:赋值运算符

重载的赋值运算符原型如下:

Class_name & Class_name::operator=(const Class_name &);

赋值运算符的功能以及何时使用它

将已有的对象赋给另一个对象时,将使用重载的赋值运算符;

StringBad headline1(“Celery Stalks at Midnight”);

StringBad knot;

Knot = headline1;

初始化对象时,并不一定会使用赋值运算符,比如下述语句

StringBad metoo = knot; //使用复制构造函数

这是因为metoo是新建对象,被初始化为knot的值,因此使用复制构造函数。实现时也可能分两步来处理这个语句:使用复制构造函数创建一个临时对象,然后通过赋值将临时对象的值复制到新对象中。

也就是说,初始化总会调用复制构造函数,而使用=运算符也允许调用赋值运算符。

与赋值构造函数相似,赋值运算符的隐式实现也对成员进行逐个复制,如果成员本身就是类对象,则程序将使用为这个类定义的赋值运算符来复制该成员,但静态数据成员不受影响。

赋值的问题出在哪里

数据受损,也就是成员复制的问题。赋值和被赋值的对象的指针数据成员指向相同的地址。因此当一个对象调用析构函数时,将删除指向的字符串。当另一个对象调用析构函数时,将试图删除前面已经删除的字符串。这样做是不正确的,因此可能改变内存中的内容,导致程序的异常终止。如果操作结果是不确定的,则执行的操作将随编译器而异。

解决赋值问题:

对于默认复制运算符不合适而导致的问题,解决方法是提供赋值运算符(进行深度复制)定义。

l  其实现与复制构造函数相似,但也有一些差别。

l  由于目标对象可能引用了之前分配的数据,所以函数应使用delete[]来释放这些数据;函数应当避免将对象赋给自身;否则,给对象重新赋值前,释放内存的操作可能删除对象的内容。

l  函数返回一个指向调用对象的引用。

通过返回一个对象,函数可以像常规赋值操作那样,连续进行赋值,即如果S0、S1和S2都是StringBad对象,则可以这样编写代码:

S0=S1=S2;

用函数表示为S0.operator=(S1.operator(S2));

赋值运算符是只能由类成员函数重载的运算符之一。

StringBad & StringBad::operator=(const StringBad & st)

{

if(this == &st)

return *this

delete [] str;

len = st.len;

str = new char [len+1];

std::strcpy(str, st.str);

return *this;

}

delete [] str; //如果不使用delete运算符的话,则上述字符串将保留在内存中。由于程序中不再包含指向该内存的指针,那么这块内存将被浪费掉。

赋值操作并不创建新的对象,因此不需要调整静态数据成员num_strings的值。

再把赋值运算符的定义放到StringBad中,所有的问题都解决了。


使用默认复制构造函数是一个很坑的行为,容易导致内存管理不善。

这是因为其复制方式是浅复制,如果把一个指针复制给另一个指针,(这就导致两个指针指向同一块内存)那么在一个对象过期,并调用其析构函数的时候,会使内存被释放掉。那么另一个指针指向就变得不确定了。

C++_类和动态内存分配1—动态内存和类的更多相关文章

  1. rt-thread中动态内存分配之小内存管理模块方法的一点理解

    @2019-01-18 [小记] rt-thread中动态内存分配之小内存管理模块方法的一点理解 > 内存初始化后的布局示意 lfree指向内存空闲区首地址 /** * @ingroup Sys ...

  2. 目录_Java内存分配(直接内存、堆内存、Unsafel类、内存映射文件)

    1.Java直接内存与堆内存-MarchOn 2.Java内存映射文件-MarchOn 3.Java Unsafe的使用-MarchOn 简单总结: 1.内存映射文件 读文件时候一般要两次复制:从磁盘 ...

  3. (转)java内存分配分析/栈内存、堆内存

    转自(http://blog.csdn.net/qh_java/article/details/9084091) java内存分配分析/栈内存.堆内存 java内存分配分析 本文将由浅入深详细介绍Ja ...

  4. SQL SERVER 内存分配及常见内存问题(2)——DMV查询

    原文:SQL SERVER 内存分配及常见内存问题(2)--DMV查询 内存动态管理视图(DMV): 从sys.dm_os_memory_clerks开始. SELECT [type] , SUM(v ...

  5. SQL SERVER 内存分配及常见内存问题(1)——简介

    原文:SQL SERVER 内存分配及常见内存问题(1)--简介 一.问题: 1.SQL Server 所占用内存数量从启动以后就不断地增加: 首先,作为成熟的产品,内存溢出的机会微乎其微.对此要了解 ...

  6. malloc内存分配与free内存释放的原理

    malloc内存分配与free内存释放的原理 前段时间一直想看malloc的原理,在搜了好几篇malloc源码后遂放弃,晦涩难懂. 后来室友买了本深入理解计算机系统的书,原来上面有讲malloc的原理 ...

  7. C程序的内存分配及动态内存

    1.程序内存的分配 一个由C/C++编译的程序占用的内存分为以下几个部分:1)栈区(stack) — 由编译器自动分配释放 , 存放为运行函数而分配的局部变量. 函数参数. 返回数据. 返回地址等. ...

  8. malloc,colloc,realloc内存分配,动态库,静态库的生成与调用

     1.在main方法里面直接定义一个很大的数组的时候.可能会出现栈溢出:错误代码演示: #include<stdio.h> #include<stdlib.h> void ...

  9. Android JNI编程(五)——C语言的静态内存分配、动态内存分配、动态创建数组

    版权声明:本文出自阿钟的博客,转载请注明出处:http://blog.csdn.net/a_zhon/. 目录(?)[+] 一:什么是静态内存什么又是动态内存呢? 静态内存:是指在程序开始运行时由编译 ...

随机推荐

  1. 编程中&&和||的妙用

    &&符号在编程中表示“和”,也就是数学中的“且”! if(A && B){ } 上面的代表表示A==true并且B==true的情况下就执行花括号里面的代码. 值得注意 ...

  2. 2、awk的输出

    1.常见的输出格式整理 awk '{print "this is " $1, $2, $1*$2, NR, NF, $NF}' file1   ###字符输出,字段输出,运算输出, ...

  3. 【原创】linux signal处理中的几个问题(suse下莫名其妙死锁的处理)

    我在CSDN专栏写过的,老帖子最近发现在腾讯的CVM上,服务器总是平凡的死锁后查明真像为 当你发生sig 11的异常时,会进入处理函数 signalHandler同时此时生成相应的dump file时 ...

  4. 黑盒测试实践--Day7 12.1

    黑盒测试实践--Day7 12.1 今天完成任务情况: 录制小组作业中的自动化测试工具实践视频 汇总大家提交的各种作业模块,打包完成小组共同作业 小组成员完成个人情况说明后在截止时间前分别提交作业 小 ...

  5. linux-常用命令备注

    //杀掉某个进程-xargs应用 ps aux | grep "udplog.js" | cut -c 9-15 | xargs kill -9 //远程拷贝文件或文件夹 sudo ...

  6. LIRE教程之源码分析 | LIRE Tutorial of Analysis of the Source Code

    LIRE教程之源码分析 |LIRE Tutorial of Analysis of the Source Code 最近在做地理图像识别和检索的研究,发现了一个很好用的框架LIRE,遂研究了一通.网上 ...

  7. 下载特定区域内街景照片数据 | Download Street View Photos within Selected Region

    作者:姜虹,刘子煜,王玥瑶,杨安琪,天靖居士 街景图片可以通过api下载,但需要提供参数,参数中的poiid.panoid.location可以用来确定位置或全景图片的ID以确定对应的街景图片.优先级 ...

  8. JQuery.validator插件使用

    首先给变量validator赋值 var validator =$('#test').validate({validate构造 }); 接着调用 $('#test').valid() 会使用上面的验证 ...

  9. HackTen 格式化TextView的文本

    1.概要:     TextView是Android提供的一个简单却功能强大的UI控件.读者可以在应用程序中通过多种方法使用不同样式的文本. 监管TextView并不支持所有HTML标签,但是用于格式 ...

  10. Log--检查各数据库日志的使用情况

    -- Recovery model, log reuse wait description, log file size,-- log usage size and compatibility lev ...