1. 构造函数与析构函数
- 类内定义的函数 编译器会以内联函数的方式编译
- 内存分区
栈区:int x=0; int *p=NULL
内存由系统分配和回收
堆区:int *p=new int[20]
程序员管理
全局区:存储全局变量和静态变量
常量区:string s="hello"
代码区:存储逻辑代码的二进制 - 类在实例化之前不会占用堆或栈的内存
- 对象初始化可以有且仅有一次(构造函数),也可以根据条件初始化
构造函数
的规则:对象实例化时自动调用,构造函数与类同名,构造函数没有返回值,构造函数可以有多个重载形式,实例化对象时只用到一个构造函数,当用户没有定义构造函数时,编译器自动生成一个不做任何事的构造函数- 在实例化对象时不需要传参数的构造函数叫
默认构造函数
,包括无参构造函数和参数都有默认值的有参构造函数 初始化列表
Student():m_name("Jim"),m_age(10){}
初始化列表先于构造函数执行,初始化列表只能用于构造函数,可以同时初始化多个数据成员,并且效率高,const类型的成员变量只能用初始化列表的方式初始化
拷贝构造函数
:形如Student stu2=stu1
或Student stu3(stu1)
这样的形式会调用拷贝构造函数。拷贝构造函数的定义方式:Student(const Student& stu){}
。如果没有定义拷贝构造函数,系统会自动生成一个默认的拷贝构造函数。
构造函数总结如下图:
- 析构函数:
~Student(){}
没有返回值,没有参数,不能重载,在对象销毁时自动调用,如果没有定义析构函数,系统会自动生成一个默认的析构函数
2. 对象数组
//栈中实例化对象数组
Coordinate coord[3];
coord[1].m_x=10;
//堆中实例化对象数组
Coordinate *p=new Coordinate[3];
p[0].m_x=10;
p->m_x=10;
delete []p;
p=NULL;
一个从堆中实例化的例子:
Coordinate *p =new Coordinate[3];
p->m_iX = 7; //直接写p的话,就说明是第一个元素
p[0].m_iY =9; //等价于 p->m_iY = 9
p++; //p指向第2个元素
p->m_iX = 11;
p[0].m_iY = 13; //这里p指向的是第二个元素,p[0]就是当前元素,等价于p->m_iY = 13
p[1].m_iX = 15;//第3个元素的横坐标
p++; //p当前还是指向第二个元素,p++将指针后移一个位置,p指向第3个元素
p[0].m_iY = 17;//这里p指向的是第三个元素,p[0]就是当前元素,等价于p->m_iY = 17
for(int j = 0; j < 3; j++)
{
//如果上面p没有经过++操作,就可以按下面来轮询
//cout <<"p_X: " << p[i].m_iX <<endl;
//cout <<"p_Y: " << p[i].m_iY <<endl;
//但是,上面我们对p做了两次++操作,实际p已经指向了第3个元素,应如下操作
cout <<"p_X: "<< p->m_iX <<endl;
cout <<"p_Y: "<< p->m_iY <<endl;
p--;
}
//经过了上面三次循环后,p指向了一个非法内存,不能直接就delete,而应该让p再指向我们申请的一个元素的,如下
p++; //这样p就指向了我们申请的内存
delete []p;
p = NULL;
3. 对象成员
当类中有对象成员时,实例化这个类和析构这个类,类和对象成员的构造顺序和销毁顺序是怎样的呢?
举个例子,Line中有两个Coordinate类的对象,m_CoorA和m_CoorB。
当实例化Line对象时,执行Line* p=new Line()
,先实例化m_CoorA,再实例化m_CoorB,最后实例化Line对象,销毁的时候顺序相反,先销毁Line对象,再销毁m_CoorB,最后销毁m_CoorA
。
另外,实例化时的参数怎么设置呢?
Coordinate类的构造函数Coordinate(int x, int y)
,Line类的构造函数可以用初始化列表的方式Line(int x1,int y1,int x2,int y2):m_CoorA(x1,y1),m_CoorB(x2,y2){}
,当然也可以用别的方式,这样就可以在main函数中Line* p=new Line(1,2,3,4)
;这样实例化对象。
4. 深拷贝与浅拷贝
浅拷贝只将数据成员的值进行简单的拷贝。遇到指针的时候会有问题,看下面的例子
这样arr1和arr2的m_pArr会指向同一个内存,对任何一个进行修改都会影响到另一个,但实际上这两个值应该是独立的。
更严重的是,在销毁arr1和arr2的时候,这块内存会被释放两次,计算机会崩溃。
我们希望的应该是arr1和arr2的m_pArr指向不同的地址。实现方法如下图:
这种拷贝就叫深拷贝。
5. 对象指针
实例化对象可以用指针的方式,在堆中实例化。下面的例子中,p指向的其实就是第一个数据成员的地址。记得在堆中申请的内存最后要释放。
6. 对象成员指针
和刚刚的对象成员不同,这里讲的是对象的指针作为成员。
和之前的对象成员不同,之前如果用sizeof操作符看Line对象的大小,结果应该是16,因为有两个Coordinate成员对象,每个Coordinate有两个int类型的成员m_iX和m_iY。现在这里如果用sizeof操作符看Line对象的大小,结果是8,因为不管是什么对象的指针,都是4个字节,指针指向的m_iX和m_iY都在堆中,并不在Line对象内存里。
再来看生成和销毁过程有什么不同。这里实例化Line对象时,对象成员指针也就被创建,然后Line再被实例化,销毁时先释放堆中的内存,即指针指向的内存,然后再释放Line对象。
7. this指针
类中参数是不能与成员变量同名的,可以用this指针。对象的this指针指向这个对象的地址,即Array arr;
this等价于&arr
。如果想用与成员变量同名的参数,就可以写成Array(int len){this->len=len;}
这里想一个问题,一个类的成员函数是在代码区,每次操作一个对象时都调用的同一份代码,那么成员函数是如何访问到对应的数据成员的呢?其实每个成员函数都有一个隐藏的this指针参数,指向当前对象的地址。
this指针还有一个好玩的用法。像下面这样定义Array类的print成员函数:
Array Array::print()
{
cout<<len<<endl;
return *this;
}//this是指针,*this就是Array对象
在main函数中这样用:
Array arr(10);
arr.print().setLen(5);
cout<<arr.getLen()<<endl;
发现结果是打印出两个10,说明setLen并没有对arr起到作用。为什么呢?因为print函数返回的*this是一个临时的对象,并不是arr。为什么是临时变量呢?可以这么理解:
Array Array::print(){
return *this;
}
//等价于
Array tmp=Array(arr->*this);
//等价于
Array tmp=Array(arr);
//即为一个拷贝构造出的临时变量,对临时变量的操作不会影响到源数据本身
那么解决方法是什么?我们想到了引用和指针。把Array Array::print()
改为Array& Array::print()
,这就等价于Array &tmp=arr;
;如果改成指针,就是Array* Array::print()
,return也要改成return this;
,调用的时候就要用->
,等价于Array *tmp=&arr;
。
如果同理改造一下setLen,就可以像arr.print().setLen(5).print()
这样连续调用了。
8. 再论const
- 常对象成员和常成员函数
常对象成员就是对象成员前面加个const,这样构造函数就只能用初始化列表的方式,参考对象成员那部分。
在成员函数后面加const就是常成员函数,常成员函数中不能修改成员函数的值,为什么呢?以Coordinate类为例:
//普通成员函数
void Coordinate::changeX(Coordinate *this)
|
V
void changeX(Coordinate *this)
{ this->m_iX=10; }
//常成员函数
void Coordinate::changeX() const//声明和定义都要加const
{ m_iX=10; }
|
V
void changeX(const Coordinate *this)//常指针改变数据是不允许的
{ this->m_iX=10; }
void changeX() const;
和void changeX();
其实是可以同时存在的,他们互为重载,但没必要这么做。如果这么做,那么Coordinate coor(3,5); coor.changeX();
调用的是哪个changeX?这里调用的是不带const的成员函数。如果定义的是常对象const Coordinate coor(3,5);
那么调用的changeX就是常成员函数。
2. 常指针与常引用
Coordinate类中,把print成员函数定义成const:
void print() const;
main函数中:
Coordinate coor1(3,5);
const Coordinate &coor2=coor1;//常引用
const Coordinate *pCoor=&coor1;//常指针
coor1.print();//可以
coor2.getX();//getX不是常成员函数,会出错,因为getX有读写权限,而coor2只有读权限,只能调用常成员函数
pCoor->getY();//getY不是常成员函数,同理会出错,只能调用常成员函数
更复杂的情况:
Coordinate coor1(3,5);
Coordinate coor2(7,9);
Coordinate* const pCoor=&coor1;//const位置不一样了,在*后面,表示不能指向其它对象
pCoor->getY();//可以,指针指向的内容本身是可变的,指针指向的内容有读写权限
pCoor=coor2;//出错,不能指向其它对象
pCoor->print();//可以,print是常成员函数,this指针有读权限,而pCoor有读写权限,是正确的
整理自慕课网c++远征