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); //初始化顺序只跟数据成员声明时的顺序有关,与其在初始化列表中的顺序无关
初始化成员列表的赋值语句先执行,构造函数体中的赋值语句后执行。
|
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
}
|