body, table{font-family: 微软雅黑; font-size: 10pt}
table{border-collapse: collapse; border: solid gray; border-width: 2px 0 2px 0;}
th{border: 1px solid gray; padding: 4px; background-color: #DDD;}
td{border: 1px solid gray; padding: 4px;}
tr:nth-child(2n){background-color: #f8f8f8;}

1.  类和对象
2.  构造函数
3.  析构函数
4.  复制构造函数
5.  特殊成员变量的初始化
6.  特殊成员函数

C语言中,程序是由一个个函数组成的,是结构化的面向过程的编程方法。 程序  =   变量  +  函数  (程序 = 数据结构  +  算法)  理解难  修改难  重用难 
C++语言中,进行面向对象的程序设计,编写的程序是由对象组成的。
面向对象的四大特性:抽象、封装、继承、多态
在类里面定义的成员函数实现在类里面,都是inline函数,没有函数调用开销;
在类里面定义的成员函数实现在类外面,不再是inline函数;

类创建对象的过程---->类的实例化
构造函数:函数的名字与类名相同,没有返回类型和返回值

1、给对象一个标识符。
2、为对象数据成员开辟内存空间。
3、完成对象数据成员的初始化(函数体内的工作,由程序员完成)。
4、构造函数支持重载
5、用户没有显式地定义构造函数,编译器将为类生成“缺省构造函数”默认是没有参数的
6、一旦定义了构造函数,编译器便不会为类自动生成缺省构造函数
5、构造函数允许按参数缺省方式调用

Point(int x=0,int y=0):xpos(x),ypos(y);    //后面是初始化成员列表,顺序必须和私有成员列表定义顺序一致,如果私有成员有数组,不能用成员列表初始化(学到后面可以 array(new int[num]))

//初始化顺序只跟数据成员声明时的顺序有关,与其在初始化列表中的顺序无关

初始化成员列表的赋值语句先执行,构造函数体中的赋值语句后执行。
Point()
:xPos(0)
,yPos(0)
{}
Point(int x,int y)
:xPos(x)
,yPos(y)
{}
Point(int x=0,int y=0)
:xPos(x)
,yPos(y)
{}
         
Point(int iy )
: _iy(iy)    //初始化列表  
, _ix(_iy)
{}

析构函数:与类同名,之前冠以波浪号,以区别于构造函数,没有返回类型和返回值
1、释放对象数据成员开辟的内存空间
2、不支持重载
3、对象超出其作用域被销毁时,析构函数会被自动调用
4、如果用户没有显式地定义析构函数,编译器将为类生成“缺省析构函数”,缺省析构函数是个空的函数体,只清除类的数据成员所占据的空间,但对类的成员变量通过new和malloc动态申请的内存无能为力,因此,对于动态申请的内存,应在类的析构函数中通过delete或free进行释放,这样能有效避免对象撤销造成的内存泄漏
5、new 创建的对象,创建该对象时调用构造函数,delete 删除对象时调用析构函数,析构函数要显示定义
6、构造函数可以显示调用    comp.~computer();     //显式调用析构函数   不推荐这样做,如果这样做了,等到最后对象超出其作用域被销毁时,析构函数会被自动调用,就执行了两次析构,报错
~computer()
{
    delete []point;
    point = NULL;     //要显示调用就要有这个,不然会应为重复释放出现内存错误
}
//private数据成员只能由本类的函数访问,protected数据成员只能在派生类中访问,而public数据成员在派生类和类外均可访问。

注:
对于关键字static定义的静态局部变量,当程序流程第一次到达该对象定义处调用构造函数,在整个程序结束时调用析构函数。
对于用new运算符创建的对象,每当创建该对象时调用构造函数,当用delete删除该对象时,调用析构函数。
#include <iostream>
#include<string.h>
using namespace std;
class Computer
{
        private:
                char *brand;
                float price;
        public:
                Computer(const char* brand1,float price1)
                        :price(price1)
                {
                        cout<<"构造函数:"<<endl;
                        brand=new char[strlen(brand1)+1];
                        strcpy(brand,brand1);
                }
                ~Computer()
                {
                        delete []brand;
                        cout<<"析构函数:"<<endl;
                }
                void print()
                {
                        cout<<"品牌:"<<brand<<endl;
                        cout<<"价格:"<<price<<endl;
                }
};
#include"Computer.h"
Computer pc("Mac",49999);         //先于main执行,调用构造函数;
int main()
{
        cout<<"Enter main"<<endl;
        pc.print();
        Computer *p=new Computer("lenovo",4000);
        p->print();
        delete p;         
//new 创建的对象,delete 才调用析构函数
        return 0;
}

 
复制构造函数 (缺省的是  inline、public成员函数)       类名::类名(const 类名 &)
//形参必须是& ,操作类对象本省,不加&,直接传参会不停调用复制构造函数,直到栈溢出
//如果类定义中没有显式定义该复制构造函数时,编译器会隐式定义一个缺省的复制构造函数,它是一个inline、public的成员函数

point p1(2,3);
point p2=p1   或者  point p2(p1);
定义: point::point(const point&);          //可以在函数体实现整个函数复制过程,也可以用初始化成员列表

point(const point & pt)    //复制构造函数的定义及实现
{
            cout << "调用复制构造函数" << endl;
            xPos = pt.xPos;
            yPos = pt.yPos;
}
//pt在class里面,所以也可以直接访问私有成员
point(const point & pt):xPos(pt.xPos),yPos(pt.yPos)
{
            cout << "调用复制构造函数" << endl;
}

拷贝构造函数自动调用情况:
1、当把一个已经存在的对象赋值给另一个新的对象时。
2、当实参和形参都是对象,进行形参和实参的结合时。
3、当函数的返回值是对象,函数调用完成返回时。

g++ Point.cc -fno-elide-constructors   
 //在编译时取消返回值优化功能,就能看到函数 return  对象 时调用复制构造函数,不加这句话默认不会显示调用构造函数(可以在函数中加cout清楚查看是否显调用复制构造函数)
缺省复 
computer comp2(comp1)等价于: //浅拷贝
comp2.brand = comp1.brand;   //简单的指针复制,两个指向同一个内存区域
comp2.price = comp1.price;
后一句没有问题,但comp2.brand = comp1.brand却有问题:经过这样赋值后,两个对象的brand指针都指向了同一块内存,当两个对象释放时,其析构函数都要delete[]同一内存块,便造成了2次delete[],从而引发了错误。
error:double free

//浅拷贝
#include<iostream>
#include<string.h>
using namespace std;
class computer
{
        private:
                char * brand;
                float price;
        public:
                computer(const char*sz,float p)
                {
                        brand=new char[strlen(sz)+1];
                        strcpy(brand,sz);
                        price=p;
                }
                computer(const computer &cp):brand(cp.brand),price(cp.price)  
//拷贝构造函数,简单拷贝,浅拷贝,两个相同指针指向同一个内存区域
                {
                        cout<<"调用复制构造函数"<<endl;
                }
                ~computer()
                {
                        delete []brand;
                        brand=NULL;
                        cout<<"调用析构函数释放资源"<<endl;
                }
                void print()
                {
                        cout<<"品牌:"<<brand<<endl;
                        cout<<"价格:"<<price<<endl;
                }
};
int main()
{
        computer com1("联想",4999.99);
        com1.print();
        computer com2(com1);
        com2.print(); 
        //程序结束,两次调用析构函数释放new申请的对空间,产生错误
        return 0;
}
//深拷贝
#include <iostream>
#include<string.h>
using namespace std;
class computer
{
        private:
                char * brand;
                float price;
        public:
                computer(const char*sz,float p)
                {
                        brand=new char[strlen(sz)+1];
                        strcpy(brand,sz);
                        price=p;
                }
                computer(const computer & cp )                     //拷贝构造函数,深拷贝
                {
                       cout<<"调用复制构造函数"<<endl;
                        brand=new char[strlen(cp.brand)+1];            //对象自己开辟堆空间
                        strcpy(brand,cp.brand);                                  //对象指针指向自己开辟的对空间
                        price=cp.price;
                }
                ~computer()
                {
                        delete []brand;
                        brand=NULL;
                        cout<<"调用析构函数释放资源"<<endl;
                }
                void print()
                {
                        cout<<"品牌:"<<brand<<endl;
                        cout<<"价格:"<<price<<endl;
                }
};
int main()
{
        computer com1("联想",4999.99);
        com1.print();
        computer com2(com1);
        com2.print();
        //两个对象都指向各自开辟的对空间,调用两次析构函数释放资源就不会出错
        return 0;
}
构造函数前面加  explicit   显示调用构造函数防止CPoint pt1 = 1这种隐性转换,显示调用CPoint pt2 = pt1; CPoint pt2 ( pt1);
#include<iostream>
using namespace std;
class point
{
        private:
                int _x;
                int _y;
        public:
#if 0
                point(int x=0,int y=0)
                        :_x(x)
                        ,_y(y)
                {
                        cout<<"point(int,int)"<<endl;
                }
#endif
                explicit point(int x,int y)
                        :_x(x)
                        ,_y(y)
                {
                        cout<<"point(int,int)"<<endl;
                }
                explicit point()
                {
                        cout<<"point()"<<endl;
                }
                point & operator = (const point &rhs)
                {
                        this->_x = rhs._x;
                        this->_y = rhs._y;
                        cout<<"operator=(const point&)"<<endl;
                }
                void print()
                {
                        cout<<"("<<_x<<","<<_y<<")"<<endl;
                }
};

int main()
{
        point pt1(3,4);
        point pt2 = pt1;   //这里调用默认的拷贝构造函数
        pt2.print();
        //point pt3 = 1;   //加了关键字explicit,禁止这种转换
        //pt3.print();
        point pt4(pt1);
        pt4.print();
        return 0;
}
//类对象定义加上const
#include <iostream>
using namespace std;
class point
{
        private:
                int ix;
                int iy;
        public:
                point(int ix1=0,int iy1=0)
                :ix(ix1)
               ,iy(iy1)
                {
                        cout<<"构造函数:"<<endl;
                }
                point(const point &rhs)
                :ix(rhs.ix)
                ,iy(rhs.iy)
                {
                        cout<<"拷贝构造函数:const"<<endl;
                }
                point( point &rhs)
                :ix(rhs.ix)
                ,iy(rhs.iy)
                {
                        cout<<"拷贝构造函数:"<<endl;
                }
                void print()
                {
                        cout<<"("<<ix<<","<<iy<<")"<<endl;
                }
                void print()const
                {
                        cout<<"print const"<<endl;
                        cout<<"("<<ix<<","<<iy<<")"<<endl;
                }
};
int main()
{
        const point p1(3,4);  
        p1.print();       
//p1是const,print()函数也必须是const
        point p2(p1);            
//const成员变量,类中没有显示定义拷贝构造函数,可以调用缺省拷贝构造函数,但是如果显示定义,那么函数必须有关键字const
        p2.print();
        return 0;
}
///
/// @file    destruct.cpp
/// @author  meihao1203(meihao19931203@outlook.com)
/// @date    2017-12-17 10:01:36
///
// -fno-elide-constructors
// 编译器有返回值优化,要看到return 对象发生的赋值构造函数,就要加上面的命令
// 代码第一个输出结果是有构造函数的,第二个是无构造函数的
// 每次例子前的解释。。。不懂,以输出结构为准
#include<iostream>
using namespace std;
class point
{
        private:
                int _x;
                int _y;
        public:
                point()
                {
                        cout<<"默认构造函数"<<this<<" "<<endl;
                        _x = 0;
                        _y = 0;
                }
                //带一个参数的可用于类型转换的构造函数
                point(int x)
                {
                        cout<<"1参数构造函数"<<this<<" "<<endl;
                        _x = x;
                        _y = 0;
                }
                //带参数的构造函数
                point(int x,int y)
                {
                        cout<<"2参数构造函数"<<this<<" "<<endl;
                        _x = x;
                        _y = y;
                }
                //拷贝构造函数,如果此函数不定义,系统将生成缺省拷贝构造函数功能,
                //缺省拷贝构造函数的行为是:用传入的对象参数的成员初始化正要建立的对象的相应成员
                point(const point &p)
                {
                        cout<<"拷贝构造函数"<<this<<" "<<endl;
                        _x = p._x;
                        _y = p._y;
                }
                point &operator=(const point &p)
                {
                        cout << "赋值运算符重载函数 " << this << " " << endl;
                        if(this!=&p)  //=两边不是同一个
                        {
                                _x = p._x;
                                _y = p._y;
                        }
                        return (*this);
                }
                //析构函数,一个类中只能有一个析构函数,如果用户没有定义析构函数,
                //系统会自动未类生成一个缺省的析构函数
#if 1
                ~point()
                {
                        cout << "析构函数 " << this << " " << endl;
                }
#endif
};
point func1()
{
        point a;
        return a;
}
int main()
{
        //当有析构函数的时候,Point()不会调用构造函数生成临时的匿名对象。
        //当没有析构函数的时候,Point()会生成一个临时的匿名对象,等价于Point pt1;这句话只会调用无参构造函数,不会调用拷贝构造函数
        //point pt1 = point();   //只调用一次构造函数
                                 //point();   //会调用默认构造函数的
        //默认构造函数0x7ffd6a109ef0
        //拷贝构造函数0x7ffd6a109ee0
        //析构函数 0x7ffd6a109ef0
        //析构函数 0x7ffd6a109ee0
        //
        //默认构造函数0x7ffdec3a5a40
        //拷贝构造函数0x7ffdec3a5a30
        //当有析构函数的时候,CPoint(1)不会生成调用构造函数生成临时的匿名对象。
        //当没有析构函数的时候,CPoint()会生成一个临时的匿名对象,等价于CPoint pt(1);这句话只会调用一个参数的构造函数,不会调用拷贝构造函数
        //point pt2 = point(1);
        //1参数构造函数0x7ffc3b5a3360
        //拷贝构造函数0x7ffc3b5a3350
        //析构函数 0x7ffc3b5a3360
        //析构函数 0x7ffc3b5a3350
        //
        //1参数构造函数0x7ffc533a4ba0
        //拷贝构造函数0x7ffc533a4b90
        //普通数据类型转换为类类型,利用相应的构造函数就可以实现。等价于CPoint pt(1);
        //point pt3 = 1;
        //1参数构造函数0x7fff4bcbea20
        //拷贝构造函数0x7fff4bcbea10
        //析构函数 0x7fff4bcbea20
        //析构函数 0x7fff4bcbea10
        //
        //1参数构造函数0x7ffca3575720
        //拷贝构造函数0x7ffca3575710
        /*拷贝构造函数与赋值运算符重载函数的区别:
        *1. 拷贝构造函数是用已经存在的对象的各成员的当前值来创建一个相同的新对象。
        * 在下述3种情况中,系统会自动调用所属类的拷贝构造函数。
        * 1.1  当说明新的类对象的同时,要给它赋值另一个已经存在对象的各成员当前值。
        * 1.2  当对象作为函数的赋值参数而对函数进行调用要进行实参和形参的结合时。
        * 1.3  当函数的返回值是类的对象,在函数调用结束后返回主调函数处的时候。
        *2. 赋值运算符重载函数要把一个已经存在对象的各成员当前值赋值给另一个已经存在的同类对象*/
        //point pt4;
        //point pt5 = pt4;
        //有析构函数
        //默认构造函数0x7ffc2ba82f80
        //拷贝构造函数0x7ffc2ba82f90
        //析构函数 0x7ffc2ba82f90
        //析构函数 0x7ffc2ba82f80
        //调用无参构造函数,拷贝构造函数,此处如果没有写析构函数,则还会调用一次拷贝构造函数
        //因为函数返回会生成一个临时对象,然后再将这个临时对象赋值给pt6,所以多调用一次拷贝构造函数;
        //如果有析构函数,则不会生成中间的临时变量,所以少一次拷贝构造函数的调用
        //point pt6 = func1();
        // -fno-elide-constructors    编译带上这个命令才看的到
        //默认构造函数0x7fff3eaa2a50
        //拷贝构造函数0x7fff3eaa2a90
        //析构函数 0x7fff3eaa2a50
        //拷贝构造函数0x7fff3eaa2a80
        //析构函数 0x7fff3eaa2a90
        //析构函数 0x7fff3eaa2a80
        //func1()先执行,point a return的时候发生拷贝构造函数,完了析构a, = 发生拷贝,最后返回的匿名对象析构,pt6 析构
        //
        //默认构造函数0x7ffc26728660
        //拷贝构造函数0x7ffc26728690
        //拷贝构造函数0x7ffc26728680
        point pt7(1,2);
        point pt8;
        pt8 = pt7;
        //2参数构造函数0x7ffeded09530
        //默认构造函数0x7ffeded09540
        //赋值运算符重载函数 0x7ffeded09540
        //析构函数 0x7ffeded09540
        //析构函数 0x7ffeded09530 

}

C++构造函数和析构函数,以及构造函数特殊成员变量和函数的初始化的更多相关文章

  1. Java学习笔记11---静态成员变量、静态代码块、成员变量及构造方法的初始化或调用顺序

    当创建一个对象时,各种成员变量及构造方法的初始化或调用顺序是怎样的呢? (1).如果类尚未加载,则先初始化静态成员变量和静态代码块,再初始化成员变量,最后调用相应的构造方法: (2).如果类已经加载过 ...

  2. C++成员变量与函数内存分配

    关于结构体和C++类的内存地址问题 C++类是由结构体发展得来的,所以他们的成员变量(C语言的结构体只有成员变量)的内存分配机制是一样的.下面我们以类来说明问题,如果类的问题通了,结构体也也就没问题啦 ...

  3. C++中类中常规变量、const、static、static const(const static)成员变量的声明和初始化

    C++类有几种类型的数据成员:普通类型.常量(const).静态(static).静态常量(static const).这里分别探讨以下他们在C++11之前和之后的初始化方式. c++11之前版本的初 ...

  4. java类成员变量与代码块初始化

    首先根据下面的这个一段代码:引入关于java初始化顺序的问题public class InitationTest extends Person { public InitationTest() { S ...

  5. java成员变量和局部变量的初始化和内存中的运行机制

    成员变量: 当系统加载类或创建类的实例时,系统会自动为成员变量分配内存空间,并在分配内存空间后,自动为成员变量指定初始值. eyeNum是类属性.name是实例属性 所有person实例访问eyeNu ...

  6. java中static修改成员变量和函数和其他使用

    一.通过static修饰的成员变量初始化只会初始化一次 //静态变量初始化只会初始化一次 public class zuishuai { public static void main(String[ ...

  7. C++ struct结构体定义构造函数和析构函数,构造函数参数从VS2017平台转换到Qt5平台下构建出错,采用字符集转换函数将string类型转换为wstring,构建仍然出错!

    调试win硬件驱动,需要利用VS编译的win驱动构建自己的Qt5GUI程序: 其中部分win驱动源码如下 device_file::device_file(const std::string& ...

  8. C++列表初始化是初始化本类自身含有的成员变量,不能直接初始化继承过来的成员变量

    在构造函数体内赋值就是对的了

  9. Delphi会自动初始化全局变量和类成员变量,但不初始化局部变量

    If you don't explicitly initialize a global variable, the compiler initializes it to 0. Object insta ...

随机推荐

  1. SQL service 中的 ”输入SQL命令窗口“ 打开了 “属性界面” 回到 ”输入SQL命令窗口“

    输入SQL命令窗口点击上面的菜单栏中的 “窗口”

  2. Appium的工作原理

    把我们写的python语言代码,看做客户端 通过客户端向appium服务器发送请求 appium服务器把我们的代码转换成手机可以识别的指令 然后把指令发给手机,手机根据指令做出相应的操作 最后手机把操 ...

  3. boke练习: springboot整合springSecurity出现的问题,post,delete,put无法使用

    springboot 与 SpringSecurity整合后,为了防御csrf攻击,只有GET|OPTIONS|HEAD|TRACE|CONNECTION可以通过. 其他方法请求时,需要有token ...

  4. BIO NIO AIO之间的区别

    一.BIO.NIO.AIO的基本定义与类比描述: BIO (Blocking I/O):同步阻塞I/O模式,数据的读取写入必须阻塞在一个线程内等待其完成.这里使用那个经典的烧开水例子,这里假设一个烧开 ...

  5. android-------开发常用框架汇总

    响应式编程 RxJava https://github.com/ReactiveX/RxJava RxAndroid https://github.com/ReactiveX/RxAndroid 消息 ...

  6. Windows下如何使用Heroku

    1. 安装 进入https://devcenter.heroku.com/articles/heroku-cli#windows,选择对应版本安装 安装后使用heroku -v可检查版本号 2. 登陆 ...

  7. k8s相关端口表-以及周边工具

    k8s端口 kube-api 6443 kube-controller-manager 10252 kube-scheduler 10251 kubelet 10250 kube-proxy 1025 ...

  8. tornado 异步

    引言 注:正文中引用的 Tornado 代码除特别说明外,都默认引用自 Tornado 4.0.1. tornado.gen 模块是一个基于 python generator 实现的异步编程接口.通过 ...

  9. ATOM常用插件推荐

    转载:http://blog.csdn.net/qq_30100043/article/details/53558381 ATOM常用插件推荐 simplified-chinese-menu ATOM ...

  10. SpringBoot系列之Hikari连接池

    1.springboot 2.0 中默认连接池是Hikari,在引用parents后不用专门再添加依赖 2.application.yml中的配置 # jdbc_config datasource s ...