析构函数
class CLA_NAME{
访问控制属性:
CLA_NAME(形参列表):INIT_LIST{
}
~CLA_NAME(void){
}
};
1.在C++中,任何一个类中都有唯一的一个析构函数
2.如果程序员没有手动实现析构函数,那么编译器将自动生成析构函数
3.析构函数形如:
~类名(void){
}
析构函数名 ~类名
析构函数没有返回值 也不是void
析构函数没有参数 所以不能重载
在一个类中,析构有且只有一个
一般来说,析构函数用编译器自动生成的即可,但如果需要手动释放内存,则需要自行添加
4.析构函数的调用顺序和构造函数严格相反
构造函数的执行顺序:
父类(按继承顺序)的构造函数-->类类型成员(按定义的顺序)的构造函数-->本类的构造函数
析构函数的执行顺序:
本类的析构函数-->类类型成员的析构函数(按定义顺序的逆序)-->父类的析构函数(按继承的逆序)
5.对象消亡时,自动调用析构函数
delete释放 new的对象时,调用析构函数
6.析构函数的功能
(1)释放动态内存 比如在构造函数时 new malloc 内存
(2)关闭文件 fclose() close()
(3)删除临时文件
(4)释放资源
拷贝构造函数
特殊的构造函数
如果一个类没有添加拷贝构造函数,那么编译器将自动生成一个拷贝构造函数,实现是浅拷贝
如果一个类需要实现深拷贝,则需要自己添加拷贝构造函数
拷贝构造函数形如:
class CLA_NAME{
CLA_NAME(const CLA_NAME& otherobj){
}
};
拷贝构造函数没有返回值 也不是void
拷贝构造函数名字和类名相同
拷贝构造函数的形参只有一个 同类型的 引用对象 必须是引用
拷贝构造函数什么时候调用:
当用一个已经存在的对象去实例化一个同类型的对象时,调用的是拷贝构造函数
浅拷贝和深拷贝:
浅拷贝就是按字节拷贝,即对象中的成员属性进行按字节复制,
如果对象中有指针成员,就是把指针的值进行了拷贝(拷贝之后,两个指针指向同一块内存)
深拷贝就是拷贝指针所指向的内容,而不是拷贝指针变量本身的值
默认的拷贝构造函数:
依次调用父类类型的拷贝构造函数(按继承顺序)
依次调用类类型成员的拷贝构造函数(按成员定义顺序)
本类类型的拷贝构造函数
如果自己实现拷贝构造函数需要注意的问题:
(1)需要实现父类成员的拷贝 在初始化列表中调用父类的拷贝构造函数
如果在初始化列表中没有调用父类的拷贝构造,则默认调用无参构造
(2)需要实现类类型成员的拷贝 在初始化列表中调用类类型成员的拷贝构造函数
如果在初始化列表中没有调用类类型成员的拷贝构造,则默认调用无参构造
(3)需要实现本类中其它成员的拷贝
拷贝复制函数
当存在的两个同类型的变量进行赋值操作时,会调用拷贝赋值函数
如果一个类没有实现拷贝赋值函数,则编译器会自动生成一个拷贝赋值函数,默认实现是浅拷贝赋值
如果需要实现深拷贝赋值,则需要手动实现
拷贝赋值函数形如:
class CLA_NAME{
CLA_NAME& operator=(const CLA_NAME& otherobj){
}
};
obj1 = obj2;
obj1.operator=(obj2);
默认的拷贝赋值函数:
1.依次调用父类的拷贝赋值函数 按继承顺序
2.依次调用类类型成员的拷贝赋值函数 按声明定义顺序
3.本类的拷贝赋值函数
如果自己实现拷贝赋值函数需要注意:
1.需要拷贝父类的成员
2.需要拷贝类类型成员
3.其它成员的拷贝
无先后顺序,先写先执行
编译器自动生成无参构造和拷贝构造
如果手动提供无参构造,编译器自动生成拷贝构造
如果手动提供拷贝构造,编译器不再生成无参构造
要能够区分:什么时候调用拷贝构造 什么时候调用拷贝赋值
拷贝构造: 有新对象 用存在的对象构造一个新的对象 普通参数传递(没有引用)拷贝构造
拷贝赋值:对象都已存在 对象之间进行赋值操作
默认的拷贝构造和拷贝构造都是浅拷贝
什么是浅拷贝,什么是深拷贝?
浅拷贝按字节拷贝 只拷贝成员本身的内容,如果成员是指针,则拷贝指针本身,一个地址,指针所指向的内容共享
深拷贝对于普通成员也是按字节拷贝 如果成员是指针,则拷贝指针所指向的内容
友元
在一个类中可以声明全局函数为该类的友元函数,
在友元函数中,可以通过对象访问该类的所有的属性和方法
在一个类中可以声明其它类为该类的友元类
那么,在友元类中,可以通过对象访问该类的所有的属性和方法
class CLA_NAME{
friend RET_TYPE FUNC_NAME(ARGLIST,...);
friend class OTHER_CLASS_NAME;
};
运算符重载
运算符分类:
1.单目运算符
+(正号) -(负号) &(取地址) *(取值) !(取反) ~(按位取反)
++ --
2.双目运算符
+ - * / % && || & | ^ >> << []
3.三目运算符
?: 不能重载
4. () 操作数个数不确定
成员方式重载
#obj obj.operator#()
M#N M.operator#(N)
友元方式重载(全局函数的重载形式)
#obj operator#(obj)
M#N operator#(M,N)
<< >> 只能以友元方式重载
cout.operator<<(obj); operator<<(cout,obj)
cin.operator>>(obj); operator>>(cout,obj)
friend ostream& operator<<(ostream& os,const CLA_NAME& obj);
friend istream& operator>>(istream& is,CLA_NAME& obj);
单目运算符以成员方式重载,该函数无参数;
单目运算符以友员方式重载,该函数有一个参数
双目运算符以成员方式重载,该函数有一个参数
双目运算符以友员方式重载,该函数有两个参数
双目运算符:
1.+ - * /
左右操作数不会改变
表达式的结果是右值 所以返回值没有引用
2.+= -= *= /= =
左操作数发生改变 右操作数不变
表达式的结果是左值 返回左操作数的引用
3. >> / <<
只能以友元方式(全局函数)重载
左操作数 ostream/istream类型 不能是常量,不能拷贝(参数和返回值都是引用)
ostream& operator<<(ostream& os,const CLS& obj);
istream& operator>>(istream& is,CLS& obj);
表达式的结果是左操作数的引用 cout/cin 支持连用 cout << a << b << endl;
单目运算符
1. -(取负) !(取反) ~(按位取反)
操作数不会改变
表达式的结果是右值
2. ++ --
前++ 前--
操作数发生改变
表达式的结果是自增减之后的值
表达式的结果是左值 返回操作数的引用
后++ 后--
操作数发生改变
表达式的结果是原来的值
表达式的结果是右值 返回操作数原来的值
下标运算符:
obj[index]
双目运算符,左操作数是一个具有容器特性(String)的对象
右操作数是容器中特定位置的数据元素的索引(基零的下标)
下标运算符的结果可以是左值,也可以是右值,由对象的常属性决定
常对象返回的是右值 普通对象返回的是左值
一般来说,[]运算符会实现两个版本的
比较运算符 关系运算符:
== != < > <= >=
== != a==b((a<b||a>b) == false)
注意:如果把类类型对象存储于容器中,一般都需要重载< 及 == 和 !=
如果重载==,也有必要重载!=
重载()运算符 ----- 函数对象
如果一个类重载了()运算符,那么该类的对象可以称为 函数对象
重载的()运算符,返回值类型可以视情况而定 是否引用也可以视情况而定
重载的()运算符函数参数可以任意
在C++中,重载()运算符非常有用,取代了C语言中的函数指针
重载()运算符类的对象 仿函数 函数对象
& 取地址运算符
编译器自动生成两个&运算符函数 返回对象的地址
形式:
class CLA_NAME{
CLA_NAME *operator&(void){
return this;
}
const CLA_NAME* operator&(void)const{
return this;
}
};
C++11之前的版本中,如果有一个空类(啥也没写),至少有6个函数
无参构造 拷贝构造 拷贝赋值 析构函数 取址函数(2个 const版本和非const版本)
C++11之前一个类中最少会有几个函数? 5个
手动实现一个拷贝构造函数 无参构造将不会自动生成
C++11版本会自动再添加两个函数:
移动拷贝构造函数
移动拷贝赋值函数
重载位运算符
& | ~ ^ >> <<
重载解引用(*)以及间接解引用(->)
成员指针
.* 直接成员指针解引用 不能重载
->* 间接成员指针解引用 能够重载
重载类型运算符:
class A{
public:
A(const B& b){}
operator B(void)const{
}
};
重载new/delete运算符
在类里用静态方法
class A{
static void *operator new(size_t size){
return malloc(size);
}
static void *operator new[](size_t size){
return malloc(size);
}
static void operator delete(void *ptr){
free(ptr);
}
static void operator delete[](void *ptr){
free(ptr);
}
};
用全局方式实现
void *operator new(size_t size){
return malloc(size);
}
void *operator new[](size_t size){
return malloc(size);
}
void operator delete(void *ptr){
free(ptr);
}
void operator delete[](void *ptr){
free(ptr);
}
实例化对象:
分配内存 ---> 调用构造函数
对象消亡:
调用析构函数 ---> 回收内存
继承
在继承的语法中,被继承的类称为父类,基类,继承父类的这个类称为子类或者派生类
继承的意义:
子类拥有父类的属性和方法 子类可以定义自己的属性和方法
代码的复用 功能的扩展
#继承方式:
私有继承 private 默认的
保护继承 protected
公开继承 public
继承方式所表达的含义是 从父类中继承到子类中的属性将是怎样的访问控制属性
1.访问控制属性
关键字 属性 基类 子类 外部 友元
public 公有 OK OK OK OK
protected 保护 OK OK NO OK
private 私有 OK NO NO OK
2.继承方式对基类中继承下来的成员变量的访问控制属性的影响
基类中不同访问控制属性的变量 通过不同的继承方式 到达子类的变成的访问控制 属性
基类 public公开继承 protected保护继承 private私有继承
公有 公有 保护 私有
保护 保护 保护 私有
私有 私有(不能访问) 私有(不能访问) 私有(不能访问)
基类中的私有属性不管通过什么继承方式,到达子类都不能访问
通过继承,在基类中定义的任何成员,也都成为了子类的成员。
但是基类中的私有成员,子类虽然拥有但却不能访问
基类中的保护成员和公开成员,在子类中是直接可以访问的
公开继承:
基类中保护的成员,通过公开继承方式,在子类中依然是保护的成员
基类中公司的成员,通过公开继承方式,在子类中依然是公开的成员
在使用时基本使用公开继承
保护继承:
基类中保护的、公开的成员,通过保护继承到子类变成子类中保护的成员
私有继承:
基类中保护的、公开的成员,通过私有继承到子类变成子类中私有的成员
|