如果您对python浅拷贝和深拷贝感兴趣,那么本文将是一篇不错的选择,我们将为您详在本文中,您将会了解到关于python浅拷贝和深拷贝的详细内容,我们还将为您解答python浅拷贝和深拷贝的区别的相关
如果您对python 浅拷贝和深拷贝感兴趣,那么本文将是一篇不错的选择,我们将为您详在本文中,您将会了解到关于python 浅拷贝和深拷贝的详细内容,我们还将为您解答python浅拷贝和深拷贝的区别的相关问题,并且为您提供关于.net core 浅克隆和深克隆/浅拷贝和深拷贝、C++ 浅拷贝和深拷贝、c++ 浅拷贝和深拷贝 指针和引用的区别 malloc(free)和new(delete)的区别 重载重写重定义、c++浅拷贝和深拷贝的有价值信息。
本文目录一览:- python 浅拷贝和深拷贝(python浅拷贝和深拷贝的区别)
- .net core 浅克隆和深克隆/浅拷贝和深拷贝
- C++ 浅拷贝和深拷贝
- c++ 浅拷贝和深拷贝 指针和引用的区别 malloc(free)和new(delete)的区别 重载重写重定义
- c++浅拷贝和深拷贝
python 浅拷贝和深拷贝(python浅拷贝和深拷贝的区别)
Python中的对象之间赋值时是按引用传递的,如果需要拷贝对象,需要使用标准库中的copy模块。
1. copy.copy 浅拷贝 只拷贝父对象,不会拷贝对象的内部的子对象。
2. copy.deepcopy 深拷贝 拷贝对象及其子对象
一个很好的例子:
a = [ 1 , 2 , 3 , 4 , [ '' a '' , '' b '' ]] # 原始对象
b = a # 赋值,传对象的引用
c = copy.copy(a) # 对象拷贝,浅拷贝
d = copy.deepcopy(a) # 对象拷贝,深拷贝
a.append( 5 ) # 修改对象a
a[ 4 ].append( '' c '' ) # 修改对象a中的[''a'', ''b'']数组对象
print '' a = '' , a
print '' b = '' , b
print '' c = '' , c
print '' d = '' , d
输出结果:
a = [1, 2, 3, 4, [''a'', ''b'', ''c''], 5]
b = [1, 2, 3, 4, [''a'', ''b'', ''c''], 5]
c = [1, 2, 3, 4, [''a'', ''b'', ''c'']]
d = [1, 2, 3, 4, [''a'', ''b'']]
注意:还有一种浅拷贝的方式,就是使用类似于 a = set(a)这种格式,即复制列表L,使用list(L),要复制一个字典d,使用 dict(d),要复制一个集合s,使用set(s),这样,我们总结出一个规律,如果你要复制一个对象o,它属于内建的类型t,那么你可以使用t(o) 来获得一个拷贝.dict也提供了一个复制版本,dict.copy,这个和dict(d)是一样,我推荐你使用后者,这个使得代码更一致,而且还少几个字符.
.net core 浅克隆和深克隆/浅拷贝和深拷贝
除非语言里只有基础类型,没有引用类型,否则用任何一种编程语言克隆对象都是很棘手的事情。
1、前言
"老沉,什么是深克隆,什么是浅克隆?"
“哈,迷茫了?这深深浅浅的体验是不是把你搞晕了?”
“嗯,这都是啥程序员黑话吗?”
“这是专业术语!因为有“引用类型”这个概念,所以才引申出来深克隆和浅克隆的名词。”
“我们先聊聊堆(Heap)和栈(Stack)吧。”
“好啊,老沉,听你说说!”
2、堆和栈
堆:在.net 中准确的说是托管堆,它由 CLR 管理,当堆满了后,会自动清理垃圾,所以做.net开发,基本不需要关心内存的释放,原理还是需要了解的。
另外,根据引用类型实例的大小,"堆"分为"GC堆"和"LOH(Large Object Heap)堆",当引用类型实例大小小于85000个字节的时候,实例被分配在"GC堆"上;当实例大小大于或等于于85000个字节的时候,实例被分配在"LOH(Large Object Heap)堆"。
栈:翻译起来应该是堆栈,因为老和堆放一块,感觉容易混,因此现在一般都简称为栈。
堆和栈是程序运行时,数据主要存放的两个存储区。
**堆区**存放引用类型的对象,主要由CLR/GC释放;
**栈区**存放函数的参数、局部变量、返回数据,其内存无需我们管理,也不接受GC管理,当栈元素被弹出后,立马释放。
栈区大小在32位应用下有1MB大小,64位应用是4MB。为啥是这样的呢?看看下面这位`David Cutler`,就是他制定的规则,这是windwosNT系统的统一标配,和.net关系不大。
当程序的EXE 栈大小 或CreateThread()调用未明确指定堆栈大小时,它将选择一MB字节,几乎所有程序员都将其留给操作系统来选择大小。
当然1MB字节是很多的,一个真正的线程很少消耗超过几千字节的内存。因此,兆字节实际上是相当浪费的。并且由于Windows的内存机制,在按需分页的虚拟内存操作系统上您可以承受这种浪费,兆字节只是虚拟内存,只是处理器的编号,在实际寻址之前,您永远不会真正使用物理内存(机器中的RAM)。
在.net世界, 我们的程序一般不接受程序员自己分配栈空间,除非在不安全的模式使用stackalloc关键字。
也许你会觉得1MB有点小,其实真相恰恰是相反的,1MB已经太多了,这使得操作系统创建线程的能力大大降低,实际上 asp.net 可能只有 256KB~512KB之间,甚至于实际的使用可能只在 4KB左右,并且操作系统会倾向于进行优化,只提交你需要的堆栈大小。
windows在管理线程栈时的自动增加示意图。
通过`ILDASM`,查看PE头,可以看到实际已经分配的栈空间大小。
.net 运行时的栈提交方式也可以进行修改。
> 公共语言运行时的默认行为是在启动线程时提交完整的线程堆栈。如果必须在内存有限的服务器上创建大量线程,并且其中大多数线程将使用很少的堆栈空间,如果公共语言运行时未在线程执行完后立即提交完整的线程堆栈,则服务器的性能可能会更好。
<configuration>
<runtime>
<disableCommitThreadStack enabled="1" />
</runtime>
</configuration>
栈资源代表着什么呢?简言之,每个线程都需要1MB的栈大小,先不管其他的消耗,在windows 32位系统下, 因为只有2GB的内存地址空间,因此最多只能创建 2048 个线程,当然实际上会比这小。这样反推,windwos 64位操作系统也仅仅能创建 4096个线程。当然这个没考虑windows对栈提交的优化,如果它按需提交,那同样内存下,会支撑更多的线程创建。
因此如果要提升windwos创建线程的能力,需要降低默认的1MB的设定,当然有工具去修改这个设置,有兴趣的朋友可以去搜搜`Testlimit`。
为什么要从内存转移到栈或“加载”?另一方面,为什么要从栈转移到内存或“存储”呢?为什么不将它们全部都放在内存中呢?
因为简单!
因为从概念上讲,栈对于语言编译器编写者来说非常简单。栈是一种用于描述计算的简单易懂的机制。对于JIT编译器作者来说,栈在概念上也非常容易。使用栈是一种简化的抽象,因此,它又降低了我们的成本。
您问:“为什么要栈呢?” 为什么不直接将所有内存都耗尽?
好吧,让我们考虑一下。假设您要生成以下内容的CIL代码:
int x = A() + B() + C() + 10;
只有栈,则假设我们有一个约定,即“ add”,“ call”,“ store”等始终将其参数移出栈,并将其结果(如果有的话)放在栈上。要为此C#生成CIL代码,我们只需要说些类似的话:
load the address of x // The stack now contains address of x
call A() // The stack contains address of x and result of A()
call B() // Address of x, result of A(), result of B()
add // Address of x, result of A() + B()
call C() // Address of x, result of A() + B(), result of C()
add // Address of x, result of A() + B() + C()
load 10 // Address of x, result of A() + B() + C(), 10
add // Address of x, result of A() + B() + C() + 10
store in address // The result is now stored in x, and the stack is empty.
现在,我们将按照您的方式进行操作,其中每个操作码都将获取其操作数的地址以及将其结果存储到的地址:
Allocate temporary store T1 for result of A()
Call A() with the address of T1
Allocate temporary store T2 for result of B()
Call B() with the address of T2
Allocate temporary store T3 for the result of the first addition
Add contents of T1 to T2, then store the result into the address of T3
Allocate temporary store T4 for the result of C()
Call C() with the address of T4
Allocate temporary store T5 for result of the second addition
...
这是怎么回事吗?我们的代码越来越庞大,因为我们必须显式分配通常按照约定会放在栈上的所有临时存储。更糟糕的是,我们的操作码本身变得越来越庞大,因为它们现在都必须将要写入结果的地址以及每个操作数的地址作为参数。一条“ add”指令知道它将要从堆栈中取出两件事并放在一件事上,这可以是一个字节。一个带有两个操作数地址和一个结果地址的加法指令将非常庞大。
我们使用基于`栈的操作码`,因为栈可以解决常见的问题。即:我想分配一些临时存储,请尽快使用它,然后在完成后迅速删除它。假设我们有可用的栈,我们可以使操作码非常小,并使代码非常简洁。
3、值类型、引用类型
切入整体,bool 、byte 、char 、decimal 、double 、enum 、float 、int 、long 、sbyte 、short 、struct 、uint 、ulong 、ushort这些值类型都存储在栈内,而class 、interface 、delegate 、object 、string这些类型均存储在堆中。
对于引用类型,都会定义一个指针指向堆内存,因此在我们成为浅拷贝的时候,拷贝的实际是引用类型的指针。而值类型是直接拷贝的。
一个例子:
class Person
{
public int Age { get; set; }
public Person Father { get; set; }
public Person Mother { get; set; }
}
如果我对该对象进行了浅克隆并更改了使用期限,则原始对象的使用期限将不会更改。
但是,如果我随后更改了克隆对象的父亲的属性,那么我也会影响原始对象的父亲,因为未克隆引用。
换一种方式思考,在C#中,当您对对象进行浅克隆时,您相比深克隆“浅一层”,因为变浅了,您要克隆的对象内的任何对象本身也不会递归地克隆。
深度克隆显然是相反的。它一直尝试克隆对象的所有属性,然后克隆该属性的属性。
4、成员克隆
如果您对C#中的克隆进行了研究,则可能遇到了“成员方式”克隆方法。它对每个类均可用,但“仅在该类内部”可用,因为它是Object的受保护方法。您不能在另一个类的对象上调用它。
class Person
{
public string Name { get; set; }
public Person Father { get; set; }
public Person Mother { get; set; }
public Person Clone()
{
return (Person)this.MemberwiseClone();
}
}
然而,快速浏览一下智能感知就可以告诉我们一些…
创建当前对象的浅表副本。
因此,在此对象上调用clone只会对其进行浅克隆,而不会进行深克隆。
如果您的对象是纯粹的值对象,那么这实际上可以为您工作,您可以在这里停下来。但是在大多数情况下,我们正在寻找更深的克隆。
5、手动克隆
反正,你最了解你的类,不是吗?
class Person
{
public string Name { get; set; }
public Person Father { get; set; }
public Person Mother { get; set; }
public Person Clone()
{
return new Person
{
Name = this.Name,
Father = this.Father == null ? null : new Person { Name = this.Father.Name },
Mother = this.Mother == null ? null : new Person { Name = this.Mother.Name }
};
}
}
如果属性不多,不失为一个好办法。
6、二进制序列化器克隆
[Serializable]
class Person
{
public string Name { get; set; }
public Person Father { get; set; }
public Person Mother { get; set; }
public Person Clone()
{
IFormatter formatter = new BinaryFormatter();
using (stream = new MemoryStream())
{
formatter.Serialize(stream, this);
stream.Seek(0, SeekOrigin.Begin);
return (Person)formatter.Deserialize(stream);
}
}
}
我们必须用[Serializable]属性来修饰我们的类,否则,我们会得到异常错误,这看起来不Nice!
当然你可以改良为一个静态方法
public static class CloningService
{
public static T Clone<T>(this T source)
{
// Don''t serialize a null object, simply return the default for that object
if (Object.ReferenceEquals(source, null))
{
return default(T);
}
IFormatter formatter = new BinaryFormatter();
using (stream = new MemoryStream())
{
formatter.Serialize(stream, source);
stream.Seek(0, SeekOrigin.Begin);
return (T)formatter.Deserialize(stream);
}
}
}
当然 [Serializable]标记别忘了。
7、json序列化克隆
public static class CloningService
{
public static T Clone<T>(this T source)
{
// Don''t serialize a null object, simply return the default for that object
if (Object.ReferenceEquals(source, null))
{
return default(T);
}
var deserializeSettings = new JsonSerializerSettings { ObjectCreationHandling = ObjectCreationHandling.Replace };
var serializeSettings = new JsonSerializerSettings { ReferenceLoopHandling = ReferenceLoopHandling.Ignore };
return JsonConvert.DeserializeObject<T>(JsonConvert.SerializeObject(source, serializeSettings), deserializeSettings);
}
}
我只能说,每次必须这样做时,我都认为“将其转换为JSON并再次返回确实不是很好……”。
但这是可行的。它可以处理您扔给它的所有东西,而且几乎没有错。
**我认为,如果您正在寻找可靠的,可重用的方法来在代码中克隆对象,就是这个。**
8、json克隆的循环
眼尖的读者会注意到,在上述JSON克隆服务中,我们有一行处理引用循环。
这是非常非常普遍的情况,尤其是在用作DataModel的一部分的模型中,两个类将相互引用。无论您如何决定克隆对象,始终会遇到对象之间相互引用的问题,`并且任何克隆尝试都将陷入无休止的循环`。
因此,即使在上面的代码中,我们使用一个设置来解决它,也值得指出的是,无论您决定克隆对象的方式如何,这总是一个问题。
9、小结
亲爱的读者,你有什么高招?
本文分享自微信公众号 - dotNET跨平台(opendotnet)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。
C++ 浅拷贝和深拷贝
拷贝构造函数默认的是浅拷贝。当不涉及到堆内存时用浅拷贝完全可以,否则就需要深拷贝了。
浅拷贝相当于一个箱子有多个钥匙,但其中一个人打开箱子取走箱子里的东西时,其他人是不知道的。
深拷贝是有多个箱子每个箱子对应一个钥匙,但一个人取走他的钥匙对应的箱子里的东西时,不会对其他人产生影响。
1 #include <bits/stdc++.h>
2
3 using namespace std;
4
5 class A{
6 public:
7 A() {
8 cnt1++;
9 name = new char(20);
10 strcpy(name, "hello");
11 cout << "A" << name << endl;
12 }
13 ~A() {
14 cnt2++;
15 cout << "~A" << name << endl;
16 delete name;
17 name = nullptr;
18 }
19 char *name;
20 static int cnt1;
21 static int cnt2;
22 };
23
24 int A::cnt1 = 0;
25 int A::cnt2 = 0;
26 int main() {
27
28 {
29 A a;
30 A b(a);
31 }
32
33 cout << A::cnt1 << '' '' << A::cnt2 << endl;
34 return 0;
35 }
运行结果如下:
很明显对象b的name是一个空指针了,如果对空指针进行操作的话就会出现问题了。
所以涉及到堆内存时就需要自己实现拷贝构造函数了。
1 #include <bits/stdc++.h>
2
3 using namespace std;
4
5 class A{
6 public:
7 A() {
8 cnt1++;
9 name = new char(20);
10 memmove(name, "hello", strlen("hello"));
11 cout << "A" << name << endl;
12 }
13 A(const A&b) {
14 name = new char(strlen(b.name)+1);
15 memmove(name, b.name, strlen(b.name));
16 }
17 ~A() {
18 cnt2++;
19 cout << "~A" << name << endl;
20 delete name;
21 name = nullptr;
22 }
23 char *name;
24 static int cnt1;
25 static int cnt2;
26 };
27
28 int A::cnt1 = 0;
29 int A::cnt2 = 0;
30 int main() {
31
32 {
33 A a;
34 A b(a);
35 }
36
37 return 0;
38 }
运行结果如下:
随便讨论下vector。
vector可以看成一个可变大小的数组,有两个属性size和capacity,分别是大小和容量。大小表示当前元素的数量,而容量是表示当前能容量的元素数量。
当push_back()时,如果size和capacity相同时,首先向内存申请capacity*2(这个基数一般在1.5~2.0之间)大小的内存。然后将当前的元素拷贝到新的内存中,在释放原来的内存。
这样如果vector的类型没有自己实现拷贝构造函数时,默认是浅拷贝,比如下面这样就会出现问题。
1 #include <bits/stdc++.h>
2
3 using namespace std;
4
5 class A{
6 public:
7 A() {
8 cnt1++;
9 name = new char(20);
10 strcpy(name, "hello");
11 cout << "A" << name << endl;
12 }
13 ~A() {
14 cnt2++;
15 cout << "~A" << name << endl;
16 delete name;
17 name = nullptr;
18 }
19 char *name;
20 static int cnt1;
21 static int cnt2;
22 };
23
24 int A::cnt1 = 0;
25 int A::cnt2 = 0;
26 int main() {
27 {
28 vector<A> vs;
29 for(int i = 0; i < 4; i ++) {
30 A a;
31 vs.push_back(a);
32 cout <<"size:"<< vs.size() << '' '' << vs.capacity() << endl << endl;
33 }
34 }
35 cout << A::cnt1 << '' '' << A::cnt2 << endl;
36 return 0;
37 }
结果如下:
明显出现了问题,就如提示所言,产生了两次释放。
如果自己实现了拷贝构造函数就不同了。
1 #include <bits/stdc++.h>
2
3 using namespace std;
4
5 class A{
6 public:
7 A() {
8 cnt1++;
9 name = new char(20);
10 strcpy(name, "hello");
11 cout << "A" << name << endl;
12 }
13 A(const A&b) {
14 name = new char(strlen(b.name)+1);
15 strcpy(name, b.name);
16 }
17 ~A() {
18 cnt2++;
19 cout << "~A" << name << endl;
20 delete name;
21 name = nullptr;
22 }
23 char *name;
24 static int cnt1;
25 static int cnt2;
26 };
27
28 int A::cnt1 = 0;
29 int A::cnt2 = 0;
30 int main() {
31 {
32 vector<A> vs;
33 for(int i = 0; i < 4; i ++) {
34 A a;
35 vs.push_back(a);
36 cout <<"size:"<< vs.size() << '' '' << vs.capacity() << endl << endl;
37 }
38 }
39 cout << A::cnt1 << '' '' << A::cnt2 << endl;
40 return 0;
41 }
运行结果如下:
c++ 浅拷贝和深拷贝 指针和引用的区别 malloc(free)和new(delete)的区别 重载重写重定义
4.malloc(free)和new(delete)的区别
malloc()函数:
1.1 malloc的全称是memory allocation,中文叫动态内存分配。
原型:extern void *malloc(unsigned int num_bytes);
说明:分配长度为num_bytes字节的内存块。如果分配成功则返回指向被分配内存的指针,分配失败返回空指针NULL。当内存不再使用时,应使用free()函数将内存块释放。
1.2 void *malloc(int size);
说明:malloc 向系统申请分配指定size个字节的内存空间,返回类型是 void* 类型。void* 表示未确定类型的指针。C,C++规定,void* 类型可以强制转换为任何其它类型的指针。
备注:void* 表示未确定类型的指针,更明确的说是指申请内存空间时还不知道用户是用这段空间来存储什么类型的数据(比如是char还是int或者…)
1.3 free
void free(void *FirstByte): 该函数是将之前用malloc分配的空间还给程序或者是操作系统,也就是释放了这块内存。
1.4注意事项
1)申请了内存空间后,必须检查是否分配成功。
2)当不需要再使用申请的内存时,记得释放;释放后应该把指向这块内存的指针指向NULL,防止程序后面不小心使用了它。
3)这两个函数应该是配对。如果申请后不释放就是内存泄露;如果无故释放那就是什么也没有做。释放只能一次,如果释放两次及两次以上会出现错误(释放空指针例外,释放空指针其实也等于啥也没做,所以释放空指针释放多少次都没有问题)。
4)虽然malloc()函数的类型是(void *),任何类型的指针都可以转换成(void *),但是最好还是在前面进行强制类型转换,因为这样可以躲过一些编译器的检查。
1.5 malloc()到底从哪里得到了内存空间?
答案是从堆里面获得空间。也就是说函数返回的指针是指向堆里面的一块内存。操作系统中有一个记录空闲内存地址的链表。当操作系统收到程序的申请时,就会遍历该链表,然后就寻找第一个空间大于所申请空间的堆结点,然后就将该结点从空闲结点链表中删除,并将该结点的空间分配给程序。
new运算符:
new的实现分两步:1.分配内存,2.调用构造对象
下面是一个实现:
class T{
public:
T(){
cout << "构造函数。" << endl;
}
~T(){
cout << "析构函数。" << endl;
}
void * operator new(size_t sz){
T * t = (T*)malloc(sizeof(T));
cout << "内存分配。" << endl;
return t;
}
void operator delete(void *p){
free(p);
cout << "内存释放。" << endl;
return;
}
};
int main()
{
T * t = new T(); // 先 内存分配 ,再 构造函数
delete t; // 先 析构函数, 再 内存释放
return 0;
}
new操作符调用一个函数来完成必需的内存分配,你可以重写或重载这个函数来改变它的行为。new操作符为分配内存所调用函数的名字是operator new。
函数operator new 通常这样声明:
void * operator new(size_t size);
返回值类型是void*,由于这个函数返回一个未经处理(raw)的指针。未初始化的内存。(假设你喜欢。你能写一种operator new函数,在返回一个指针之前可以初始化内存以存储一些数值,可是一般不这么做。)參数size_t确定分配多少内存。你能添加额外的參数重载函数operator new,可是第一个參数类型必须是size_t。
2.1 C++中,用new和delete动态创建和释放数组或单个对象。
动态创建对象时,只需指定其数据类型,而不必为该对象命名,new表达式返回指向该新创建对象的指针,我们可以通过指针来访问此对象。
int *pi=new int;
这个new表达式在堆区中分配创建了一个整型对象,并返回此对象的地址,并用该地址初始化指针pi 。
2.2 动态创建对象的初始化
动态创建的对象可以用初始化变量的方式初始化。
int *pi=new int(100); //指针pi所指向的对象初始化为100
string *ps=new string(10,’9’);//*ps 为“9999999999”
如果不提供显示初始化,对于类类型,用该类的默认构造函数初始化;而内置类型的对象则无初始化。
也可以对动态创建的对象做值初始化:
int *pi=new int( );//初始化为0
int *pi=new int;//pi 指向一个没有初始化的int
string *ps=new string( );//初始化为空字符串 (对于提供了默认构造函数的类类型,没有必要对其对象进行值初始化)
2.3 撤销动态创建的对象
delete表达式释放指针指向的地址空间。
delete pi ;// 释放单个对象
delete [ ]pi;//释放数组
如果指针指向的不是new分配的内存地址,则使用delete是不合法的。
2.4 在delete之后,重设指针的值
delete p; //执行完该语句后,p变成了不确定的指针,在很多机器上,尽管p值没有明确定义,但仍然存放了它之前所指对象的地址,然后p所指向的内存已经被释放了,所
以p不再有效。此时,该指针变成了悬垂指针(悬垂指针指向曾经存放对象的内存,但该对象已经不存在了)。悬垂指针往往导致程序错误,而且很难检测出来。一旦删除了
指针所指的对象,立即将指针置为0,这样就非常清楚的指明指针不再指向任何对象。(零值指针:int *ip=0;)
malloc和new的区别
1.malloc和free是库函数,而new和delete是C++操作符
2.new 返回指定类型的指针,并且可以自动计算所需要大小;而 malloc 则必须要由我们计算字节数,返回类型是 void* 类型,即未确定类型的指针,返回后需强行转换为实际类型的指针。
int* parr;
parr = new int [100]; //返回类型为 int* 类型(整数型指针),分配大小为 sizeof(int) * 100;
int* p;
p = (int *) malloc (sizeof(int)*128);//分配128个(可根据实际需要替换该数值)整型存储单元,并将这128个连续的整型存储单元的首地址存储到指针变量p中
double *pd=(double *) malloc (sizeof(double)*12);//分配12个double型存储单元,并将首地址存储到指针变量pd中
3. malloc 只管分配内存,并不能对所得的内存进行初始化,所以得到的一片新内存中,其值将是随机的。
4.分配失败:new内存分配失败时,会抛出bac_alloc异常。malloc分配内存失败时返回NULL。
5.new在动态分配内存的时候可以初始化对象,调用其构造函数,delete在释放内存时调用对象的析构函数。而malloc只分配一段给定大小的内存,并返回该内存首地址指针
6.C++允许重载new/delete操作符,而malloc不允许重载。
7.内存区域:new操作符从自由存储区(free store)上为对象动态分配内存空间,而malloc函数从堆上动态分配内存。自由存储区是C++基于new操作符的一个抽象概念,凡是通过new操作符进行内存申请,该内存即为自由存储区。而堆是操作系统中的术语,是操作系统所维护的一块特殊内存,用于程序的内存动态分配,C语言使用malloc从堆上分配内存,使用free释放已分配的对应内存。那么自由存储区是否能够是堆(问题等价于new是否能在堆上动态分配内存),这取决于operator new 的实现细节。自由存储区不仅可以是堆,还可以是静态存储区,这都看operator new在哪里为对象分配内存。
8.对数组的处理
C++提供了new[]与delete[]来专门处理数组类型:
A * ptr = new A[10];//分配10个A对象
new对数组的支持体现在它会分别调用构造函数函数初始化每一个数组元素,释放对象时为每个对象调用析构函数。至于malloc,它并知道你在这块内存上要放的数组还是啥
别的东西,反正它就给你一块原始的内存,在给你个内存的地址就完事。所以如果要动态分配一个数组的内存,还需要我们手动自定数组的大小:
int * ptr = (int *) malloc( sizeof(int) );//分配一个10个int元素的数组
9.能否被重载:opeartor new /operator delete可以重载,而malloc不行
下面是一个new调用构造函数和析构函数的例子:
#include <iostream>
using namespace std;
class Player{
public:
Player(){
cout << "call Player::ctor\n";
}
~Player(){
cout << "call Player::dtor\n";
}
void Log(){
cout << "i am player\n";
}
};
int main(){
cout << "Initiate by new\n";
Player* p1 = new Player();
p1->Log();
delete p1;
cout << "Initiate by malloc\n";
Player* p2 = (Player*)malloc(sizeof(Player));
p2->Log();
free(p2);
}
输出结果为:
Initiate by new
call Player::ctor
i am player
call Player::dtor
Initiate by malloc
i am player
有了malloc/free为什么还要new/delete?
1) 对于非内部数据类型的对象而言,光用malloc/free无法满足动态对象的要求。对象在创建的同时要自动执行构造函数,对象在消亡之前要自动执行析构函数。由于malloc/free是库函数而不是运算符,不在编译器控制权限之内,不能够把执行构造函数和析构函数的任务强加于malloc/free。因此C++语言需要一个能完成动态内存分配和初始化工作的运算符new,以及一个能完成清理与释放内存工作的运算符delete。注意new/delete不是库函数。
2) 既然new/delete的功能完全覆盖了malloc/free,为什么C++不把malloc/free淘汰出局呢?这是因为C++程序经常要调用C函数,而C程序只能用malloc/free管理动态内存。
http://blog.jobbole.com/109234/
https://blog.csdn.net/nie19940803/article/details/76358673
5.指针和引用的区别
a.引用:引用变量是一个别名,也就是说,它是某个已存在变量的另一个名字,因此内存不会为引用开辟新的空间存储这个引用,而是和目标变量共同指向目标变量的内存地址。
引用使用方式:
int a;
int &ra=a;
注意:
1.引用的类型必须和其所绑定的变量的类型相同
2.声明引用的同时必须对其初始化,否则系统会报错
3.引用相当于变量或对象的别名,因此不能再将已有的引用名作为其他变量或对象的名字或别名
引用变量的主要用途是用作函数的形参
运用:
1、引用作为参数
使用引用传递函数的参数,在内存中并没有产生实参的副本,它是直接对实参操作;而使用一般变量传递函数的参数,当发生函数调用时,需要给形参分配存储单元,形参变量是实参变量的副本;如果传递的是对象,还将调用拷贝构造函数。因此,当参数传递的数据较大时,用引用比用一般变量传递参数的效率和所占空间都好。如果既要利用引用提高程序的效率,又要保护传递给函数的数据不在函数中被改变,加const。
2、常引用
常引用声明方式:const 类型标识符 &引用名 = 目标变量名;
用这种方式声明的引用,不能通过引用对目标变量的值进行修改,从而使引用的目标成为const,达到了引用的安全性。
3、引用和多态
引用是除指针外另一个可以产生多态效果的手段。这意味着,一个基类的引用可以指向它的派生类实例。
class A;
class B:public A{ ... ... }
B b;
A &Ref = b;//用派生类对象初始化基类对象的引用
Ref 只能用来访问派生类对象中从基类继承下来的成员,是基类引用指向派生类。如果A类中定义有虚函数,并且在B类中重写了这个虚函数,就可以通过Ref产生多态效果。
b.指针和引用的区别
指针:
指针是编程语言中的一个对象,利用地址,它的值直接指向存在电脑存储器中另一个地方的值。由于通过地址能找到所需的变量单元,可以说,地址指向该变量单元。因此,将地址形象化的称为“指针”。意思是通过它能找到以它为地址的内存单元。
相同点:
都是地址的概念,指针指向一块内存,它的内容是所指内存的地址;引用是某块内存的别名。
不同点:
1.指针是一个实体,而引用仅是个别名;
2.引用必须被初始化,指针不必;引用只能在定义时被初始化一次,之后不可变;指针可以改变所指的对象
3. 引用不可以初始化为空(NULL);但指针可以初始化为空。
4. ++运算符的含义不一样。引用的++是值的增加;指针的++是地址的增加。
5.程序为指针变量分配内存区域,而引用不需要分配内存区域
6.“sizeof 引用”得到的是所指向的变量(对象)的大小,而“sizeof 指针”得到的是指针本身(所指向的变量或对象的地址)的大小;
6.浅拷贝和深拷贝
浅拷贝:只增加了一个指针,指向已存在对象的内存
深拷贝:增加了一个指针,同时开辟了一块空间,让指针指向这块新开辟的空间
浅拷贝:在多个对象指向一个空间的时候,释放一个空间会导致其他对象所使用的空间也被释放掉
https://blog.csdn.net/qq_27011361/article/details/79518057
16.重写,重载,重定义有什么区别
a.重载:重载是应用于相同作用域之内的同名函数,由于参数列表不同而产生的不同的实现方法
想要构成重载函数必须要满足以下几个条件:
1.作用域相同(比如同一个类中);
2.函数名相同;
3.参数列表不同(参数个数、参数类型不同或者参数顺序不同);
注意:
1.若一个重载版本的函数面前有virtual修饰,则表示他是虚函数,但他也是属于重载的一个版本
2.不同的构造函数(无参构造、有参构造、拷贝构造)是重载的应用
3.未体现多态
4.函数返回值的类型可以相同,也可以不相同
b.重写:也叫做覆盖,一般发生在子类和父类继承关系之间,子类重新定义父类中有相同名称和参数的虚函数,重写是C++中实现多态这个特性基础
重写需要注意:
1.被重写的函数不能是static的,必须是virtual的
2.重写函数必须有相同的类型,名称和参数列表
3.重写函数的访问修饰符可以不同。尽管virtual是private的,派生类中重写改写为public、protect也是可以的
4.作用域不同,分别位于基类和派生类
c.重定义:也叫隐藏,子类重新定义父类中有相同名称的非虚函数,参数列表可以相同可以不同,会覆盖其父类的方法,未体现多态
重定义需要注意:
1.不在同一个作用域(分别位于基类、派生类)
2.函数的名字必须相同
3.对函数的返回值、形参列表无要求
4.如果派生类的函数和基类的函数同名,但是参数不同,此时,不管有无virtual,基类的函数被隐藏
5.如果派生类的函数与基类的函数同名,并且参数也相同,但是基类函数没有virtual,此时,基类的函数被隐藏;有virtual,就是重写了
c++浅拷贝和深拷贝
摘自《C++ Primer Plus》第6版 12.1.2和12.1.3
c++浅拷贝和深拷贝
浅拷贝只是对指针的拷贝,拷贝后两个指针指向同一个内存空间,深拷贝不但对指针进行拷贝,而且对指针指向的内容进行拷贝,经深拷贝后的指针是指向两个不同地址的指针。
c++ primer plus里深浅拷贝的图示:
逐个复制成员
深度复制
示例:
#include <iostream>
using namespace std;
class StringBad
{
private:
char * str;
int len;
static int num_strings;
public:
StringBad(const char * s);
StringBad();
~StringBad();
friend ostream & operator<<(ostream & os, const StringBad &st);
};
int StringBad::num_strings = 0;
StringBad::StringBad(const char * s)
{
len=strlen(s);
str=new char(len+1);
strcpy(str,s);
num_strings++;
cout << num_strings << ": /"" << str << "/" object created/n";
}
StringBad::StringBad()
{
len=4;
str=new char[4];
strcpy(str,"C++");
num_strings++;
cout << num_strings << "; /"" << str << "/" object created/n";
}
StringBad::~StringBad()
{
cout << "/"" << str << "/" object deleted, ";
--num_strings;
cout << num_strings << " left/n";
delete [] str;
}
ostream & operator << (ostream &os, const StringBad &st)
{
os << st.str;
return os;
}
void callme1(StringBad &);
void callme2(StringBad);
int main()
{
StringBad headline1("headline1");
StringBad headline2("headline2");
StringBad sports("sports");
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;
cout << "Assign one object to another: /n";
StringBad knot;
knot=headline1;
cout << "knot: " << knot << endl;
cout << "End of main" << endl;
return 0;
}
void callme1(StringBad & rsb)
{
cout << "String passed by reference: /n";
cout << " /" " << rsb << " /"" << endl;
}
void callme2(StringBad sb)
{
cout << "String passed by value: /n";
cout << " /" " << sb << " /"" << endl;
}
C++自动提供的成员函数: (如果没有定义)
- 默认构造函数
- 拷贝构造函数
- 赋值构造函数
- 默认析构函数
- 地址操作符
1)默认构造函数
如果定义了构造函数,C++将不会生成默认构造函数。否则,编译器将提供这样的默认构造函数:
StringBad::StringBad(){}
2)拷贝构造函数
拷贝构造函数的原型是
StringBad::StringBad(const StringBad &)
何时调用拷贝构造函数:
** 新建一个对象并将其初使化为同类对象,如
StringBad aa(headline1);
StringBad bb=headline1;
StringBad cc=StringBad(headline1);
StringBad * pdd=new StringBad(headline1);
其中,StringBad bb=headline1;
StringBad cc=StringBad(headline1);
可能会使用拷贝构造函数直接创建对象bb和cc,也有可能使用拷贝构造函数先生成一个临时对象,然后将临时对象的内容赋给bb和cc,这取决于具体的实现。
StringBad * pdd=new StringBad(headline1);会初使化一个匿名对象,并将新对象的地址赋给pdd指针
赋值操作符:
ANSI C 允许结构体赋值,C++允许对象赋值,通过自动重载赋值操作符实现的。
StringBad & StringBad::operator =(const StringBad &)
将已有的对象赋给另一个对象时,将使用重载的赋值操作符。
StringBad kk("just a test");
StringBad aa;
aa=kk;
但初使化对象的时候,使用的是拷贝构造函数
StringBad & StringBad::operator=(const StringBad &st)
{
if(this==&st)
return *this;
delete [] str;
len=st.len;
str=new char[len+1];
strcpy(str,st.str);
retern *this;
}
修改后代码
#include <iostream>
using namespace std;
class StringGood
{
private:
char * str;
int len;
static int num_strings;
public:
StringGood(const char * s);
StringGood(const StringGood & st);
StringGood();
~StringGood();
friend ostream & operator<<(ostream & os, const StringGood &st);
StringGood & operator = (const StringGood &);
};
int StringGood::num_strings = 0;
StringGood::StringGood(const char * s)
{
len=strlen(s);
str=new char(len+1);
strcpy(str,s);
num_strings++;
cout << num_strings << ": /"" << str << "/" object created/n";
}
StringGood::StringGood(const StringGood & st)
{
len=st.len;
str=new char[len+1];
strcpy(str,st.str);
num_strings++;
cout << num_strings << ": /"" << str << "/" object created/n";
}
StringGood::StringGood()
{
len=4;
str=new char[4];
strcpy(str,"C++");
num_strings++;
cout << num_strings << "; /"" << str << "/" object created/n";
}
StringGood::~StringGood()
{
cout << "/"" << str << "/" object deleted, ";
--num_strings;
cout << num_strings << " left/n";
delete [] str;
}
ostream & operator << (ostream &os, const StringGood &st)
{
os << st.str;
return os;
}
StringGood & StringGood::operator = (const StringGood &st)
{
if(this==&st)
return *this;
delete[] str;
len=st.len;
str=new char[len+1];
strcpy(str,st.str);
return *this;
}
void callme1(StringGood &);
void callme2(StringGood);
int main()
{
StringGood headline1("headline1");
StringGood headline2("headline2");
StringGood sports("sports");
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";
StringGood sailor = sports;
cout << "sailor: " << sailor << endl;
cout << "Assign one object to another: /n";
StringGood knot;
knot=headline1;
cout << "knot: " << knot << endl;
cout << "End of main" << endl;
return 0;
}
void callme1(StringGood & rsb)
{
cout << "String passed by reference: /n";
cout << " /" " << rsb << " /"" << endl;
}
void callme2(StringGood sb)
{
cout << "String passed by value: /n";
cout << " /" " << sb << " /"" << endl;
}
使用场景
1. 只要类里有指针时,就要写自己版本的拷贝构造函数和赋值操作符函数。这两个函数中可以拷贝那些被指向的数据结构,从而使每个对象都有自己的拷贝;或者可以采用某种引用计数机制,去跟踪当前有多少个对象指向某个数据结构。(也可以静态计数对象的个数,比如c++ primer plus第六版的例子,static int num_strings)
2. 某些类在实现拷贝构造函数和赋值操作符时,可以确信程序中不会做拷贝和赋值操作的时候,去实现它们会得不偿失,因此可以:只声明这些函数(声明为private成员)而不去定义实现它们。这就防止了会有人去调用它们,也防止了编译器去生成它们。
3. 浅拷贝在构造和析构对象时容易产生问题,如无必要尽量不用浅拷贝。当我们要传递复杂结构的信息,而又不想产生另一份数据时,可以使用浅拷贝,比如引用传参。
4. 智能指针是浅拷贝概念的增强。比如智能指针可以维护一个引用计数来表明指向某块内存的指针数量,只有当引用计数减至0时,才真正释放内存。
今天关于python 浅拷贝和深拷贝和python浅拷贝和深拷贝的区别的讲解已经结束,谢谢您的阅读,如果想了解更多关于.net core 浅克隆和深克隆/浅拷贝和深拷贝、C++ 浅拷贝和深拷贝、c++ 浅拷贝和深拷贝 指针和引用的区别 malloc(free)和new(delete)的区别 重载重写重定义、c++浅拷贝和深拷贝的相关知识,请在本站搜索。
本文标签: