指针悬挂:

问题:使用new申请的内存内存空间无法访问,也无法释放。

原因:直接对指向new申请的存储空间的指针变量进行赋值修改

后果:失去了原来的地址,原来的空间无法访问也无法释放,造成内存泄漏

还可能造成同一个内存释放两次

容易引起指针悬挂的方式:对象的初始化和对象间赋值

容易引起指针悬挂的条件:类中含有指针类型的成员时,使用默认的拷贝构造函数和赋值函数都会出现两个指针变量互相赋值,产生指针悬挂的问题。

解决方法:需要重新定义拷贝构造函数和超载赋值运算符

赋值操作符:

作用:两个已经存在的对象间相互赋值,产生两个完全相同的内存拷贝

举例:string a("hello");//调用构造函数

string b("would");//调用构造函数

string c=a;//调用拷贝构造函数--风格差,应使用string c(a)

c=b;  //调用拷贝赋值函数

重载赋值运算符:

语法:

    X& X::operator=(const X & fm)
{
函数体
}

注意:

1、第一个引用的作用(为什么使用返回函数引用):

原因:为了实现对象间的连续赋值。

使用返回函数引用的好处:结果得到的是一个变量,它既可以当左值,也可当右值,且采用赋值时没有引入临时变量,直接从原结果拷贝。

2、第二个引用的作用:防止调用拷贝构造函数,因为拷贝构造函数也可能引起指针悬挂

3、const的作用:当参数使用引用时,可能会改变传入的参数,为了避免这样,就使用const

具体代码:

    class Point
{
private:
char * name;
public:
Point(char * className)
{
name = new char[strlen(className)+1];
strcpy(name, className);
}
Point(const Point& p)//深拷贝
{
name = new char[strlen(p.name)+1];
strcpy(name,p.name);
}
~Point()
{
cout<<name<<endl;
delete []name;
}
Point& operator=(const Point&p);
};
系统自带的等号运算符:
Point& Point::operator=(const Point&p)
{
name=p.name;//造成指针悬挂
}
重载后的等号运算符:
Point& Point::operator=(const Point&p)
{
delete[]name;//释放原来的
name=new char[strlen(p.name)+1];
strcpy(name,p.name);
return *this;
}

拷贝构造函数

为什么要引入拷贝构造函数?

作用:创建一个对象的同时,使用一个已经存在的对象给另一个对象赋值

具体来说:它将一个已经定义过对象的数据成员 逐一拷贝给 新对象,而产生两个完全相同的内存拷贝

做比较:拷贝构造函数:对象被创建 +  用一个已经存在的对象进行初始化

拷贝赋值函数:对象已经存在不用创建 + 用一个已经存在的对象进行初始化

举例:string a("hello");//调用构造函数

string b("would");//调用构造函数

string c=a;//调用拷贝构造函数--风格差,应使用string c(a)

c=b;//调用拷贝赋值函数

什么时候使用拷贝构造函数?(系统自己调用)

在创建新对象的时候,希望将一个已经存在的对象拷贝给这个新对象,这时系统会自动调用拷贝构造函数

总结:1、拷贝构造函数的参数必须是引用,否则出错。

2、执行的语句类似 Coord p=p1; 则会调用拷贝构造函数

有三种情况:

1)创建一个新类 +  并使用类的一个对象初始化该类的另一个对象

Coord p2(p1);//用对象p1初始化对象p2

Coord p3=p1;//用对象p1初始化对象p1

2)函数的形参是类的对象 +  参数使用值传递(参数为引用的时候不调用拷贝构造函数),传参时,会调用拷贝构造函数

fun1(Coord p)
{
函数体
}
调用语句:
Coord p1;
fun1(p1);
//分析:调用拷贝构造函数 Coord p=p1; fun1(Coord& p)
{
函数体
}
调用语句:
Coord p1;
fun1(p1);

//分析:参数表中使用了引用,没有调用拷贝构造函数啊,执行 Coord& p=p1;

3)函数的返回值是对象,函数调用完毕,返回调用者时,会调用拷贝构造函数

    Coord fun1(Coord& fun)
{
return fun;
} 调用语句:
Coord c;
Coord p=fun(c);

//分析:return fun调用两次拷贝构造函数; VC测试,但是VS2005只调用一次,应该是进行了优化

Coord& fun1(Coord& fun1)
{
return fun1;
} 调用语句:
Coord c;
Coord p=fun1(c);

// 分析:Coord p=fun1(c)调用一次拷贝构造函数,因为最后使用返回函数引用,return时没有借助临时变量,直接是 Coord p=fun1;

出现这种现象的原因:

1、在使用return返回一个对象时,系统是先申请一个临时对象temp,执行Coord temp=fun1;(调用一次拷贝构造函数)

之后在执行Coord p=temp;(第二次调用)

2、在使用返回函数引用时,系统不会申请临时对象,只是直接把fun1拷贝给p

即直接执行Coord p=fun1,而没有引入temp,故少用一次拷贝构造函数

注意:这时要注意一个常出现的错误,返回的值不能是一个临时变量,常常的解决办法是函数参数使用引用,之后在使用return返回即可

怎么使用拷贝构造函数?

语法:函数名与类名相同,参数为本对象的引用,无返回类型,只有一个

类名::类名(类名& 对象名)

{拷贝成员}

代码:

    class Point
{
public:
Point(int xx=0,int yy=0){X=xx; Y=yy;}
Point(Point& p);
private:
int X,Y;
};
Point::Point (Point& p)
{
X=p.X; //参数p可以直接引用私有变量
Y=p.Y;
}

注意:参数必须为本对象的引用 + 函数体内参数可以直接引用私有变量(老忘)

常见问题:

1、为什么拷贝函数的参数必须是引用?

简单点说,为了避免递归。

具体来说,引用传递的时候不需要调用拷贝构造函数 而 值传递需要调用拷贝构造函数

所以,在进入拷贝构造函数时,需要把对象传进来,这时使用值传递还要再调一次拷贝构造函数.....这时要无限传递下去

2、浅拷贝与深拷贝

出现这个问题的原因:构造函数中需要为指针申请空间(简单点说,成员变量含有指针)

深拷贝:在拷贝构造函数中,为指针显式申请空间(正确的方式)

浅拷贝:在拷贝构造函数中,仅仅使用成员间的对应复制,两个对象的指针的都指向同一个空间,一个指针指向的空间释放,另一个指针为野指针,危害江湖。

解决方法:

如果构造函数需要为类显示申请空间(含指针,使用new),则要使用显式的拷贝构造函数

如果类中成员都是非指针,则可以使用系统默认的拷贝构造函数。

浅拷贝代码

    #include <iostream>
#include <string>
using namespace std;
class Point
{
private:
char * name;
public:
Point(char * className)
{
name = new char[strlen(className)+1];
strcpy(name, className);
}
Point(Point& p)//浅拷贝,或者不写
{
name = p.name;//(系统默认拷贝函数执行的代码)
}
~Point()
{
cout<<name<<endl;//测试关键语句
delete []name;
}
}; int main()
{
Point a("123");
Point b=a;
return 0;
}

结果:

深拷贝:

    #include <iostream>
#include <string>
using namespace std;
class Point
{
private:
char * name;
public:
Point(char * className)
{
name = new char[strlen(className)+1];
strcpy(name, className);
}
Point(Point& p)//深拷贝
{
name = new char[strlen(p.name)+1];
strcpy(name,p.name);
}
~Point()
{
cout<<name<<endl;
delete []name;
}
}; int main()
{
Point a("123");
Point b=a;
return 0;
}

结果:

拷贝构造函数和重载赋值运算符的代码对比:

    Point::Point(const  Point& p)//拷贝构造函数
Point& Point::operator=(const Point&p)//重载赋值运算符

注意:

1、参数都是一样的,都有const和引用,但是带他们的原因不同,具体见上面。

2、拷贝构造函数无返回值,而重载赋值运算符使用 返回函数引用。

C++ 指针悬挂和赋值操作符的重载,拷贝构造函数实现的更多相关文章

  1. 5.5 C++重载赋值操作符

    参考:http://www.weixueyuan.net/view/6383.html 总结: 重载赋值操作符同重载类的是拷贝构造函数的原因是一样,将一个对象拷贝给另一个对象,同时当类中存在指针类型的 ...

  2. C++ 数组操作符重载、函数对象分析、赋值操作符

    string类型访问单个字符 #include <iostream> #include <string> #include <sstream> using name ...

  3. [c++基础]3/5原则--拷贝构造函数+拷贝赋值操作符

    /* * main.cpp * * Created on: Apr 7, 2016 * Author: lizhen */ #include <iostream> #include &qu ...

  4. C++雾中风景6:拷贝构造函数与赋值函数

    在进行C++类编写的过程之中,通常会涉及到类的拷贝构造函数与类的赋值函数.初涉类编写的代码,对于两类函数的用法一直是挺让人困惑的内容.这篇文章我们会详细来梳理拷贝构造函数与赋值函数的区别. 1.调用了 ...

  5. 【C++】拷贝构造函数和赋值符函数

    在C++中,调用拷贝构造函数有三种情况: 1.一个对象作为函数参数,以值传递的方式传入函数体. 2.一个对象作为函数返回值,以值传递的方式从函数返回. 3.一个对象用于给另外一个对象进行初始化(复制初 ...

  6. C++ 拷贝构造函数和赋值构造函数

    转自:http://blog.chinaunix.net/uid-28662931-id-3496326.html 一.拷贝构造函数 int main(int argc, char * argv[]) ...

  7. 关于C++中的拷贝构造函数和赋值函数

    如果类定义的数据成员中存在指针或引用,那么最好重载这两个函数. 1.     定义 拷贝构造函数的定义格式:构造函数名(const 源类名& 引用对象形参名){} 赋值函数定义格式:源类名 & ...

  8. C++的拷贝构造函数、operator=运算符重载,深拷贝和浅拷贝、explicit关键字

    原文地址:https://blog.csdn.net/shine_journey/article/details/53081523 1.在C++编码过程中,类的创建十分频繁. 简单的功能,当然不用考虑 ...

  9. C++中构造函数,拷贝构造函数和赋值函数的区别和实现

    C++中一般创建对象,拷贝或赋值的方式有构造函数,拷贝构造函数,赋值函数这三种方法.下面就详细比较下三者之间的区别以及它们的具体实现 1.构造函数 构造函数是一种特殊的类成员函数,是当创建一个类的对象 ...

随机推荐

  1. 使用neo4j-import工具导入数据

    从Neo4j2.2版本开始,系统就自带了一个大数据量的导入工具:neo4j-import,可支持并行.可扩展的大规模csv数据导入(本例版本为:3.4.7版本) 1.前提条件 关闭neo4j 无法在原 ...

  2. scp -r拷贝目录不会拷贝软连接

    scp -r拷贝目录,不会拷贝 软连接的 解决方法: 使用rsync拷贝 参考:rsync本地及远程复制备份[原创] - paul_hch - 博客园 https://www.cnblogs.com/ ...

  3. Ex 6_4 判断序列是否由合法单词组成..._第六次作业

    设字符串为s,字符串中字符的个数为n,vi[i]表示前i+1个字符是否能组成有效的单词vi[i]=true表示能组成有效的单词,vi[i]=false表示不能组成有效的单词,在每个字符串前加一个空格, ...

  4. VS2008/2005 MFC程序调试经验

    我的VS2008不知道是有bug还是自己的问题,很多时候变量定义后CTRL+F5运行却没反应,一定要“生成解决方案”下才行? 1.没有可用于当前位置的源代码 将工具->选项->调试-> ...

  5. php安全篇过滤用户输入的参数(转)

    规则 1:绝不要信任外部数据或输入 关于Web应用程序安全性,必须认识到的第一件事是不应该信任外部数据.外部数据(outside data) 包括不是由程序员在PHP代码中直接输入的任何数据.在采取措 ...

  6. 异构平台同步(mysql-->oracle)

    https://www.cnblogs.com/andy6/p/6159060.html

  7. webpack - require 概要

    webpack 是一个预编译模块打包工具,它只会对使用到的模块进行打包. 一个模块是否被使用?可以根据该模块是否被 require 来判断.如果require时指定的是具体的模块名称与正确的路径,那么 ...

  8. C#中decimal ,double,float的区别

    浮点型 Name CTS Type Description Significant Figures Range (approximate) float System.Single 32-bit sin ...

  9. python爬取糗百内容

    #-*- coding: utf-8 -*- import urllib import urllib2 import re #页面为1 page=1 url='http://www.qiushibai ...

  10. centos7网卡名修改

    centos7网卡名不是以etho的方式命名,有时候在自动化方面不便于管理,在安装的时候输入如下代码即可命名: net.ifnames=0  biosdevname=0