C++基础概念(中)

简介: C++基础概念(中)


23.extern与include

1.extern可以置于变量或者函数前,以标示变量或者函数的定义在别的文件中,提示编译器遇到此变量和函数时在其他模块中寻找其定义。这样可以通过编译,但若其他地方也没定义,链接也通不过。还有个作用就是external“C”这种。

2.Include是在预编译的时候简单的文本替换,即将.h内容替换到.c总来。


大体上,你可以把extern 和 include 的区别当做是“零售”与“批发”的区别。include是批发,而extern 则是零售。

最好用include,这样程序更清晰,更容易维护.

当你需要很多extern的时候,可以考虑把它们放在一个头文件中,然后#include。


24.继承机制中对象之间,引用和指针之间如何转换

派生类转基类,自动完成。反之需要强制转换。


如何实现只能动态分配类对象,不能定义类对象

d3a0baebb8ad41cb87fc6c0571c7a73e.png

a5095040cf2041179088ace4ef9be055.png



26.模板的优缺点

优点:

实现了代码重用,节约了程序员时间和精力,这也是出现标准库的原因。


缺点:


模板是一种编译期间生成代码的行为,无法进行断点调试,所以很容易产生bug;

大量使用模板会造成代码空间膨胀,极大的延长了编译时间。(模板只能定义在.h文件中,当工程大了之后,编译时间十分的变态。)

27.模板特化

C++中的模板特化不同于模板的实例化,模板参数在某种特定类型下的具体实现称为模板的特化。模板特化有时也称之为模板的具体化,分别有函数模板特化和类模板特化。

cfc1cc1d8a9d403ba2b1d85d2ba411a5.png

0b0754f262b64660b84efa79a69b612d.png

再比如:

vector是vector的特化版。它一般来说是以位的方式来存储bool的值,并不是真正存储bool值。这个特化版本要解决的问题就是存储容量的问题。从这里我们可以看出,如果使用位来提升空间效率可能引出的问题就是时间效率了(时间效率低,但低空间)。


28.explicit作用: Primer P264

1)explicit 关键字只能用于类内部的构造函数声明上。

2)explicit 关键字作用于单个参数的构造函数((int x, int y=0)这种也行,只要能匹配就行)。

3)在C++中,explicit关键字用来修饰类的构造函数,被修饰的构造函数的类,不能发生相应的隐式类型转换。


ba47cff0c02348d48d4eb15a09ac2214.png

0fcd0017367548e88a3513fd6c26ba2e.png

160942d044f447379f9e305993a7904f.png

29.strcpy函数

91fe058d460f4369993dd8b1e234f1c3.png

7f2ea153e91a4ca580e396b2ca06cda5.png

strcpy()不会在乎数据来源,也不会检查字符串长度,唯一能让它停下来的只有字符串结束符’\0’。不过,如果没有遇到这个结束符,它就会一个字节一个字节地复制strSrc的内容,在填满strDest预设空间后,溢出的字符就会取代缓冲区后面的数据(内存溢出)。如果这些溢出的数据恰好覆盖了后面strcpy函数的返回地址,在该函数调用完毕后,程序就会转入攻击者设定的“返回地址”中,乖乖地进入预先设定好的陷阱。为了避免落入这样的圈套,给作恶者留下可乘之机。可以考虑用strncpy。同理strcat和strncat。

3 30.memcpy的缺陷

memcpy能够实现前向拷贝(即从高地址拷贝数据到低地址),不能够实现后向拷贝(即从低地址拷贝数据到高地址)。

因为它的实现是从前往后依次复制,如果两者有重叠,原地址的头会被目的地址的尾改写掉。


自己实现的时候可以:

源地址<目的地址,从前往后复制。

源地址>目的地址,从后往前复制。


31.内存溢出,内存泄漏

当要表示的数据超出了计算机为该数据分配的空间范围时,就产生了溢出。

引起内存溢出的原因有很多种,常见的有以下几种:

1.内存中加载的数据量过于庞大,如一次从数据库取出过多数据;

2.集合类中有对对象的引用,使用完后未清空,使得不能回收;

3.代码中存在死循环或循环产生过多重复的对象实体;

4.启动参数内存值设定的过小;


为了便于理解,我们不妨打个比方。缓冲区溢出好比是将十磅的糖放进一个只能装五磅的容器里。一旦该容器放满了,余下的部分就溢出在柜台和地板上,弄得一团糟。由于计算机程序的编写者写了一些编码,但是这些编码没有对目的区域或缓冲区——五磅的容器——做适当的检查,看它们是否够大,能否完全装入新的内容——十磅的糖,结果可能造成缓冲区溢出的产生。如果打算被放进新地方的数据不适合,溢得到处都是,该数据也会制造很多麻烦。但是,如果缓冲区仅仅溢出,这只是一个问题。到此时为止,它还没有破坏性。当糖溢出时,柜台被盖住。可以把糖擦掉或用吸尘器吸走,还柜台本来面貌。与之相对的是,当缓冲区溢出时,过剩的信息覆盖的是计算机内存中以前的内容。除非这些被覆盖的内容被保存或能够恢复,否则就会永远丢失。

如果这些溢出的数据恰好覆盖了函数后面的返回地址,在该函数调用完毕后,程序就会转入攻击者设定(通过溢出覆盖的)的“返回地址”中,乖乖地进入预先设定好的陷阱。


容易造成内存溢出的函数:

strcpy(),strcat(),sprintf(),scanf(),sscanf()这些都不会检查目的地址的大小是否大于输入的大小,容易产生异常。可以使用加n版本,比如strncpy,strncat(),snprintf()等。


内存溢出 out of memory,是指程序在申请内存时,没有足够的内存空间供其使用,出现out of memory;比如申请了一个integer,但给它存了long才能存下的数,那就是内存溢出。

内存泄露 memory leak,是指程序在申请内存后,无法释放已申请的内存空间,一次内存泄露危害可以忽略,但内存泄露堆积后果很严重,无论多少内存,迟早会被占光(最终危害)。

memory leak会最终会导致out of memory!

内存溢出就是你要求分配的内存超出了系统能给你的,系统不能满足需求,于是产生溢出。


内存泄漏是指你向系统申请分配内存进行使用(new),可是使用完了以后却不归还(delete),结果你申请到的那块内存你自己也不能再访问(也许你把它的地址给弄丢了),而系统也不能再次将它分配给需要的程序。一个盘子用尽各种方法只能装4个果子,你装了5个,结果掉倒地上不能吃了。这就是溢出!比方说栈,栈满时再做进栈必定产生空间溢出,叫上溢,栈空时再做退栈也产生空间溢出,称为下溢。就是分配的内存不足以放下数据项序列,称为内存溢出.


以发生的方式来分类,内存泄漏可以分为4类:


常发性内存泄漏。发生内存泄漏的代码会被多次执行到,每次被执行的时候都会导致一块内存泄漏。

偶发性内存泄漏。发生内存泄漏的代码只有在某些特定环境或操作过程下才会发生。常发性和偶发性是相对的。对于特定的环境,偶发性的也许就变成了常发性的。所以测试环境和测试方法对检测内存泄漏至关重要。

一次性内存泄漏。发生内存泄漏的代码只会被执行一次,或者由于算法上的缺陷,导致总会有一块仅且一块内存发生泄漏。比如,在类的构造函数中分配内存,在析构函数中却没有释放该内存,所以内存泄漏只会发生一次。

隐式内存泄漏。程序在运行过程中不停的分配内存,但是直到结束的时候才释放内存。严格的说这里并没有发生内存泄漏,因为最终程序释放了所有申请的内存。但是对于一个服务器程序,需要运行几天,几周甚至几个月,不及时释放内存也可能导致最终耗尽系统的所有内存。所以,我们称这类内存泄漏为隐式内存泄漏。

从用户使用程序的角度来看,内存泄漏本身不会产生什么危害,作为一般的用户,根本感觉不到内存泄漏的存在。真正有危害的是内存泄漏的堆积,这会最终消耗尽系统所有的内存。从这个角度来说,一次性内存泄漏并没有什么危害,因为它不会堆积,而隐式内存泄漏危害性则非常大,因为较之于常发性和偶发性内存泄漏它更难被检测到


缓冲区溢出攻击原理

详细见此

http://www.cnblogs.com/fanzhidongyzby/archive/2013/08/10/3250405.html

32.查看内存泄漏的软件都有什么

1.ccmalloc-Linux和Solaris下对C和C++程序的简单的使用内存泄漏和malloc调试库。

2.Dmalloc-Debug Malloc Library.

3.Electric Fence-Linux分发版中由Bruce Perens编写的malloc()调试库。

4.Leaky-Linux下检测内存泄漏的程序。

5.LeakTracer-Linux、Solaris和HP-UX下跟踪和分析C++程序中的内存泄漏。

Visual C++的Debug版本的C运行库(C Runtime Library)。它已经提供好些函数来帮助你诊断你的代码和跟踪内存泄漏。Visual Leak Detector是一款用于Visual C++的免费的内存泄露检测工具。

6.MEMWATCH-由Johan Lindh编写,是一个开放源代码C语言内存错误检测工具,主要是通过gcc的precessor来进行。

7.Valgrind-Debugging and profiling Linux programs, aiming at programs written in C and C++.

grind(磨碎)

8.KCachegrind-A visualization tool for the profiling data generated by Cachegrind and Calltree.

9.IBM Rational PurifyPlus-帮助开发人员查明C/C++、托管.NET、Java和VB6代码中的性能和可靠性错误。PurifyPlus 将内存错误和泄漏检测、应用程序性能描述、代码覆盖分析等功能组合在一个单一、完整的工具包中。

10.ParasoftInsure++-针对C/C++应用的运行时错误自动检测工具,它能够自动监测C/C++程序,发现其中存在着的内存破坏、内存泄漏、指针错误和I/O等错误。并通过使用一系列独特的技术(SCI技术和变异测试等),彻底的检查和测试我们的代码,精确定位错误的准确位置并给出详细的诊断信息。能作为MicrosoftVisual C++的一个插件运行。

11.Compuware DevPartner for Visual C++ BoundsChecker Suite-为C++开发者设计的运行错误检测和调试工具软件。作为Microsoft Visual Studio和C++ 6.0的一个插件运行。

12.Electric Software GlowCode-包括内存泄漏检查,code profiler,函数调用跟踪等功能。给C++和.Net开发者提供完整的错误诊断,和运行时性能分析工具包。


33.初始化列表

哪些东西必须放在初始化列表(初始化式)中:

常量成员,因为常量只能初始化不能赋值,所以必须放在初始化列表里面

引用类型,引用必须在定义的时候初始化,并且不能重新赋值,所以也要写在初始化列表里面

没有默认构造函数的类类型,因为使用初始化列表可以不必调用默认构造函数来初始化,而是直接调用拷贝构造函数初始化。

为什么使用初始化列表

初始化类的成员有两种方式,一是使用初始化列表,二是在构造函数体内进行赋值操作。

使用初始化列表主要是基于性能问题:

1)对于内置类型,如int, float等,使用初始化列表和在构造函数体内初始化差别不是很大。

2)但是对于类类型来说,最好使用初始化列表,为什么呢?使用初始化列表少了一次调用默认构造函数的过程(构造函数体内的话要先调用默认构造函数(无论如何在进入函数体之前都要调用默认构造函数)再拷贝构造函数(详见28.深拷贝还是浅拷贝)),这对于数据密集型的类来说,是非常高效的。所以一个好的原则是,能使用初始化列表的时候尽量使用初始化列表。

16f7262f458c443f8fbc22d3214615c7.png

你可能以为上面的代码将会首先做m_y=I,然后做m_x=m_y,最后它们有相同的值。但是编译器先初始化m_x,然后是m_y,,因为它们是按这样的顺序声明的。结果是m_x将有一个不可预测的值。有两种方法避免它,一个是总是按照你希望它们被初始化的顺序声明成员,第二个是,如果你决定使用初始化列表,总是按照它们声明的顺序罗列这些成员。这将有助于消除混淆。


34.public、protected、private访问属性及继承

40ffacdf9b2948ec8e4cfac9a4b63fa8.png

35.

智能指针

auto_ptr 98

C++11中有unique_ptr、shared_ptr与weak_ptr

详细见此

http://www.cnblogs.com/lanxuezaipiao/p/4132096.html

a16f1c1737324938b6cf183df91fc2f6.png

2.智能指针可以防止这个问题

当remodel这样的函数终止(不管是正常终止,还是由于出现了异常而终止),本地变量都将自动从栈内存中删除—因此指针ps占据的内存将被释放,如果ps指向的内存也被自动释放,那该有多好啊。

我们知道析构函数有这个功能。如果ps有一个析构函数,该析构函数将在ps过期时自动释放它指向的内存。但ps的问题在于,它只是一个常规指针,不是有析构凼数的类对象指针。如果它指向的是对象,则可以在对象过期时,让它的析构函数删除指向的内存。

这正是 auto_ptr、unique_ptr和shared_ptr这几个智能指针背后的设计思想。我简单的总结下就是:将基本类型指针封装为类对象指针(这个类肯定是个模板,以适应不同基本类型的需求),并在析构函数里编写delete语句删除指针指向的内存空间。


3.unique_ptr和auto_ptr

从上面的例子可以看出,unique_ptr和auto_ptr真的非常类似.其实你可以这样简单的理解,auto_ptr是可以说你随便赋值,但赋值完了之后原来的对象就不知不觉的报废.搞得你莫名其妙.而unique就干脆不让你可以随便去复制,赋值(更安全,通过禁用拷贝构造函数和=操作符,=delete).如果实在想传个值就哪里,显式的说明内存转移std:move一下.然后这样传值完了之后,之前的对象也同样报废了.只不过整个move你让明显的知道这样操作后会导致之前的unique_ptr对象失效.

b49a43b829434e908092aa7c539a2597.png

4.unique_ptr

unique_ptr“唯一”拥有其所指对象,同一时刻只能有一个unique_ptr指向给定对象(通过禁止拷贝语义、只有移动语义来实现)。

unique_ptr指针本身的生命周期:从unique_ptr指针创建时开始,直到离开作用域。离开作用域时,若其指向对象,则将其所指对象销毁(默认使用delete操作符,用户可指定其他操作)。

unique_ptr指针与其所指对象的关系:在智能指针生命周期内,可以改变智能指针所指对象,如创建智能指针时通过构造函数指定、通过reset方法重新指定、通过release方法释放所有权、通过移动语义转移所有权。

详细见此http://blog.csdn.net/pi9nc/article/details/12227887


5.shared_ptr

多个智能指针公用内存块。


1.引进了一个计数器shared_count,用来表示当前有多少个智能指针对象共享指针指向的内存块(因为要共享,比unique_ptr多了这个计数器)

2.析构函数中不是直接释放指针对应的内存块,如果shared_count大于1则不释放内存只是将引用计数减1,只是计数等于1时释放内存(unique_ptr独占内存块,析构时直接释放内存块)

3.复制构造与赋值操作符只是提供一般意义上的复制功能,并且将引用计数加1

(当一个智能指针对象被创建时,会在堆上创建一个用于计数的空间,在析构时,先将引用计数-1,为0的话再释放内存空间)

6.weak_ptr

weak_ptr是为了配合shared_ptr而引入的一种智能指针,它更像是shared_ptr的一个助手而不是智能指针,因为它不具有普通指针的行为,没有重载operator*和->,它的最大作用在于协助shared_ptr工作,像旁观者那样观测资源的使用情况.

用法:

weak_ptr被设计为与shared_ptr共同工作,必须从一个shared_ptr或者另一个weak_ptr对象构造,获得资源的观测权(并不复杂内存管理)。但weak_ptr没有共享资源,它的构造不会引起指针引用计数的增加。

使用weak_ptr的成员函数use_count()可以观测资源的引用计数,另一个成员函数expired()(过期的,失效) 用于检测所管理的对象是否已经释放。weak_ptr可以使用一个非常重要的成员函数lock()从被观测的shared_ptr获得一个可用的shared_ptr对象, 从而操作资源。但当expired()==true的时候,lock()函数将返回一个存储空指针的shared_ptr.


weak_ptr解决循环引用

35d2cdfedd874ef786c8c82ef058d39c.png

32行创建时father引用计数为1, 36行father引用计数加1为2,出作用域-1引用计数为1无法释放。

修改后:

aeb32e19f9b44e419bdbf6328e446131.png

原36行是weak_ptr不会增加father引用计数,因而出作用域,father的引用计数为0被释放。Father里的指向son的智能指针析构使son的引用计数-1为1,出作用域再-1为0从而son也可以释放。


36.虚函数和纯虚函数

(1)抽象类的定义:称带有纯虚函数的类为抽象类。

(2)抽象类的作用:

抽象类的主要作用是将有关的操作作为结果接口组织在一个继承层次结构中,由它来为派生类提供一个公共的根,派生类将具体实现在其基类中作为接口的操作。所以派生类实际上刻画了一组子类的操作接口的通用语义,这些语义也传给子类,子类可以具体实现这些语义,也可以再将这些语义传给自己的子类。

(3)使用抽象类时注意:

抽象类只能作为基类来使用,其纯虚函数的实现由派生类给出。如果派生类中没有重新定义纯虚函数,而只是继承基类的纯虚函数,则这个派生类仍然还是一个抽象类。如果派生类中给出了基类纯虚函数的实现,则该派生类就不再是抽象类了,它是一个可以建立对象的具体的类。

抽象类是不能定义对象的。


总结:

1、纯虚函数声明如下:virtual void funtion1()=0; 纯虚函数一定没有定义(没有函数体,只有声明),纯虚函数用来规范派生类的行为,即接口。包含纯虚函数的类是抽象类,抽象类不能定义实例,但可以声明指向实现该抽象类的具体类的指针或引用。

2、虚函数声明如下:virtual ReturnType FunctionName(Parameter);虚函数必须实现,如果不实现,编译器将报错,错误提示为:

error LNK****: unresolved external symbol “public: virtual void __thiscall ClassName::virtualFunctionName(void)”

3、对于虚函数来说,父类和子类都有各自的版本。由多态方式调用的时候动态绑定。

4、实现了纯虚函数的子类,该纯虚函数在子类中就变成了虚函数,子类的子类即孙子类可以覆盖该虚函数,由多态方式调用的时候动态绑定。

5、虚函数是C++中用于实现多态(polymorphism)的机制。核心理念就是通过基类访问派生类定义的函数。

6、在有动态分配堆上内存的时候,析构函数必须是虚函数,但没有必要是纯虚的。

7、友元不是成员函数,只有成员函数才可以是虚拟的,因此友元不能是虚拟函数。但可以通过让友元函数调用虚拟成员函数来解决友元的虚拟问题。

8、析构函数应当是虚函数,将调用相应对象类型的析构函数,因此,如果指针指向的是子类对象,将调用子类的析构函数,然后自动调用基类的析构函数。

9、在虚函数和纯虚函数的定义中不能有static标识符,原因很简单,被static修饰的函数在编译时候要求前期bind,然而虚函数却是动态绑定(run-time bind),而且被两者修饰的函数生命周期(life recycle)也不一样。

有纯虚函数的类是抽象类,不能生成对象,只能派生。他派生的类的纯虚函数没有被改写,那么,它的派生类还是个抽象类。

定义纯虚函数就是为了让基类不可实例化化

因为实例化这样的抽象数据结构本身并没有意义,或者给出实现也没有意义


37.构造函数为什么不能是虚函数,虚析构函数是用来做什么的

虚函数对应一个vtable,这大家都知道,可是这个vtable其实是存储在对象的内存空间的。问题出来了,如果构造函数是虚的,就需要通过 vtable来调用,可是对象还没有实例化,也就是内存空间还没有,无法找到vtable,所以构造函数不能是虚函数。

虚函数表指针是什么时候初始化的呢?当然是构造函数。当我们通过new来创建一个对象的时候,第一步是申请需要的内存,第二步就是调用构造函数。试想,如果构造函数是虚函数,那必然需要通过vtbl来找到虚构造函数的入口地址,显然,我们申请的内存还没有做任何初始化,不可能有vtbl的。因此,构造函数不能是虚函数。


多态中父类析构函数可以设成虚函数,这是为了防止那种情况发生,可能造成什么后果:

用父类指针 new一个子类对象,释放父类对象不会释放子类对象 有可能造成内存泄露。

我们往往通过基类的指针来销毁对象。这时候如果析构函数不是虚函数,就不能正确识别对象类型从而不能正确调用析构函数。


38.pthread_create()的函数(Linux环境中的一个创建线程函数)原型写出来

b7835da54a794614bd4d5d66e2e5b4e6.png

39.

C中volatile关键字

volatile的本意是“易变的”,告诉编译器不要进行相关优化:

1.不会在两个操作之间把volatile变量缓存在寄存器中。在多任务、中断、甚至setjmp环境下,变量可能被其他的程序改变,编译器自己无法知道,volatile就是告诉编译器这种情况。

2.不做常量合并、常量传播等优化,所以像下面的代码:


if的条件不会当作无条件真。

3.对volatile变量的读写不会被优化掉。如果你对一个变量赋值但后面没用到,编译器常常可以省略那个赋值操作。

af62f6752f2443c8995d3e084012ac20.png


为什么父类指针可以指向子类反之则不行

9140d2f8c57d49f4ae685a5d6039228b.png

5d72d63646b6423b8011c087250d5571.png


41.stl中的list中的size函数是如何实现的?是遍历还是设置一个变量来保存?让你实现,你倾向哪一种,为什么?

使用遍历实现的,因为作者考虑其它函数的性能,取了个折中方案。比如若不用一个遍历存储size(),下面函数实现就会快一些。不需要在改变链表的时候而去花时间维护size。

splice(iterator position, list& x, iterator first, iterator last);

方法所取的折衷,为了它的实现而把size方法设计成了O(N)。

splice方法就是为了把链表A中的一些元素直接串联到链表B中,如果size()设计为O(1)复杂度,那么做splice时就需要遍历first和last间的长度(然后把链表A保存的链表长度减去first和last(待移动的元素)之间的长度)!于是作者考虑到size方法设计为O(N),即不保存size变量,这样就无需在splice方法执行时做遍历来维护size,而只需要修改几个指针就可以了!

同理:如果采取设置变量来保存size,当执行链表拆分操作(比如给出一个中间节点,以此拆分链表)时,怎么实现?怎么更新size?(只能遍历更新,如此就相当于之前设置的变量无用)


两种方法各有好处,保存size变量,遍历size()函数快一些,但有改变链表大小的操作时需要更新size(),有事更新需要遍历。不保存size变量,size()函数需要遍历,但有改变链表大小的操作时不需要被动更新size。


能用empty()代替size()就用empty,他能保证O(1),而size()有的容器是O(n),比如stl中的list就是。


具体见:具体见此http://blog.csdn.net/russell_tao/article/details/8572000


42.子类a多重继承于b和c,b和c都有虚函数,此时a中有几张虚函数表?有几个虚函数指针?当依次调用分属b和c的虚函数时,虚函数指针怎么变化?

1.虚函数表:

与单继承相同的是所有的虚函数都包含在虚函数表中,所不同的多重继承有多个虚函数表,当子类对父类的虚函数有重写时,子类的函数覆盖父类的函数在对应的虚函数位置,当子类有新的虚函数时,这些虚函数被加在第一个虚函数表的后面。

具体:

2.父类指针强转子类指针

从多重继承的内存布局,我们可以看到子类新加入的虚函数被加到了第一个基类的虚函数表,所以当dynamic_cast的时候,子类和第一个基类的地址相同,不需要移动指针,但是当dynamic_cast到其他的父类的时候,需要做相应的指针的移动。

35d109bb9a9a4df0ad3db273745de2df.png

afa7694ddc944fafa80fc6e6b1824693.png

A a;

B b;

其中B继承自A。

a=b;

所有程序员可见的比如a中的数据成员都会被改写成b中的值,但编译器隐藏的,比如虚函数指针,a中存的还是a的指针,而没有被b覆盖。

如果是b的指针赋值给a,这时候就是多态,a的虚函数指针会进行相应改变。

只有派生类的对象可以赋值给基类的对象,反之,则不可以。

基类的指针可以指向派生类对象,但是反过来则不行,派生类的指针不可以指向基类的指针。这是为什么呢?这是因为派生类的对象所占的存储空间通常要比基类的对象大,原因就是派生类除了继承基类的成员之外,还拥有自己的成员,所以基类的指针操作派生类的对象时,由于基类指针会向操作基类对象那样操作派生类对象,而基类对象所占用的内存空间通常小于派生类对象,所以基类指针不会超出派生类对象去操作数据。

同样的道理,基类的引用可以作为派生类对象的别名,但是反过来则不行,派生类的引用不可以作为基类对象的别名。例如:

用is a来解释以上过程。


43.浅拷贝还是深拷贝

c++中string的赋值函数怎么实现?浅拷贝还是深拷贝?浅拷贝会有什么问题?(一个string delete后,另一个string持有的数据也被清了)深拷贝会有什么问题?(重复,耗费内存)所以,较好的实现应该怎样?(执行浅拷贝,设置一个引用计数,计数减少为0时delete)这样会有线程安全问题吗?怎么解决?

20a551e561824dcaae3031ef765de1be.png20a551e561824dcaae3031ef765de1be.png


浅拷贝,只是对指针的拷贝,拷贝后两个指针指向同一个内存空间,深拷贝不但对指针进行拷贝,而且对指针指向的内容进行拷贝,经深拷贝后的指针是指向两个不同地址的指针。

浅拷贝就是成员数据之间的一一赋值:把值一一赋给要拷贝的值。但是可能会有这样的情况:对象还包含资源,这里的资源可以是堆资源,或者一个文件。。当值拷贝的时候,两个对象就有用共同的资源,同时对资源可以访问,这样就会出问题。深拷贝就是用来解决这样的问题的,它把资源也赋值一次,使对象拥有不同的资源,但资源的内容是一样的。对于堆资源来说,就是在开辟一片堆内存,把原来的内容拷贝。


Ps:如你设计了一个没有类而没有提供它的复制构造函数,当用该类的一个对象去给令一个对象赋值时所执行的过程就是浅拷贝。


string的赋值函数怎么实现?浅拷贝还是深拷贝?

是深拷贝(没有涉及指针,系统资源,深浅都会分配空间,应该效果一样吧),如下:

537dbbb1c9154200bf207d6c0922cd8a.png

3d3b8acb7be74c47b39b55232eabf936.png

3ac101645d044691946bc43a54475689.png

3ac101645d044691946bc43a54475689.png


相关文章
|
7月前
|
存储 算法 NoSQL
【C/C++ 数据结构 概念】计算机数据结构基础:探索核心概念与术语
【C/C++ 数据结构 概念】计算机数据结构基础:探索核心概念与术语
80 0
|
2月前
|
程序员 C++ 开发者
C++入门教程:掌握函数重载、引用与内联函数的概念
通过上述介绍和实例,我们可以看到,函数重载提供了多态性;引用提高了函数调用的效率和便捷性;内联函数则在保证代码清晰的同时,提高了程序的运行效率。掌握这些概念,对于初学者来说是非常重要的,它们是提升C++编程技能的基石。
26 0
|
5月前
|
JSON Go C++
开发与运维C++问题之在iLogtail新架构中在C++主程序中新增插件的概念如何解决
开发与运维C++问题之在iLogtail新架构中在C++主程序中新增插件的概念如何解决
50 1
|
5月前
|
C++ 开发者
C++一分钟之-概念(concepts):C++20的类型约束
【7月更文挑战第4天】C++20引入了Concepts,提升模板编程的类型约束和可读性。概念定义了模板参数需遵循的规则。常见问题包括过度约束、约束不完整和重载决议复杂性。避免问题的关键在于适度约束、全面覆盖约束条件和理解重载决议。示例展示了如何用Concepts限制模板函数接受的类型。概念将增强模板的安全性和灵活性,但需谨慎使用以防止错误。随着C++的发展,Concepts将成为必备工具。
111 2
|
6月前
|
C++
C++一分钟之-继承与多态概念
【6月更文挑战第21天】**C++的继承与多态概述:** - 继承允许类从基类复用代码,增强代码结构和重用性。 - 多态通过虚函数实现,使不同类对象能以同一类型处理。 - 关键点包括访问权限、构造/析构、菱形问题、虚函数与动态绑定。 - 示例代码展示如何创建派生类和调用虚函数。 - 注意构造函数初始化、空指针检查和避免切片问题。 - 应用这些概念能提升程序设计和维护效率。
45 2
|
5月前
|
C++ 开发者
C++一分钟之-概念(concepts):C++20的类型约束
【7月更文挑战第6天】C++20引入了Concepts,提升模板编程的精确性和可读性。概念允许设定模板参数的编译时约束。常见问题包括过度约束、不完整约束及重载决议复杂性。要避免这些问题,需适度约束、全面覆盖约束条件并理解重载决议。示例展示了如何定义和使用`Incrementable`概念约束函数模板。概念是C++模板编程的强大力量,但也需谨慎使用以优化效率和代码质量。
114 0
|
7月前
|
Java C语言 C++
从C语言到C++_28(红黑树RedBlackTree)概念+插入接口实现(上)
从C语言到C++_28(红黑树RedBlackTree)概念+插入接口实现
60 4
|
7月前
|
C语言 容器
从C语言到C++_27(AVL树)概念+插入接口实现(四种旋转)(上)
从C语言到C++_27(AVL树)概念+插入接口实现(四种旋转)
57 4
|
7月前
|
设计模式 开发框架 算法
C++中的设计模式:基本概念与应用
C++中的设计模式:基本概念与应用
74 2
|
6月前
|
算法 程序员 编译器
C++一分钟之概念(concepts):C++20的类型约束
【6月更文挑战第30天】C++20的Concepts革新了模板编程,允许更清晰地表达类型要求,提升代码可读性和编译错误反馈。本文探讨Concepts基础、应用场景、易错点及避免策略,展示如何通过概念定义如Iterable、Addable,创建更健壮的泛型代码,强调了理解和利用编译器错误信息的重要性,以及概念与类型别名的区别。Concepts现已成为现代C++程序员的关键技能。
157 0