##第六章、引用
给变量起别名
一、基本语法
数据类型 &别名 = 原名
- 如
int &b = a
- 若修改b的值,那么a的值也会变化
二、注意事项
- 引用必须初始化
- 引用在初始化后,不可以改变
- 初始化为一个变量的别名后,不可再改为其他变量的别名
三、引用做函数参数
- 作用:函数传参时,可以利用引用让形参修饰实参
- 优点:可以简化指针修饰实参
实例
int myswap(int &a,int &b)
{
int temp = a;
a=b;
b=temp;
}
这样在main函数中引用此函数,形参就会修饰实参了
swap中的a与b是别名,那么swap函数中对a与b的操作就是在对实参操作
四、引用函数做返回值
- 作用:引用可以作为函数返回值存在
- 注意:不要返回局部变量引用
实例
int& test01()
{
int a=10;
return a;
}
int main()
{
int& ref=test01;
cout<<ref<<endl;
cout<<ref<<endl;
}
这样,第一次输出ref的值时是正常的a,因为编译器做了暂时的保留,而第二次输出则为乱码,因为a在test01中执行完操作就被释放了
- 用法:函数调用可以作为左值
实例
int& test02()
{
static int a=10;
return a;
}
int main()
{
int& ref2=test02;
cout<<ref2<<endl;
test02 = 1000;//如果函数的返回值是引用,这个函数的调用可以作为左值进行赋值
cout<<ref2<<endl;
}
有“static”那么a就是静态变量,只有在整个程序结束后才会被系统释放
第一个ref2输出为10,第二个ref2的输出为1000
五、引用的本质
- 本质:引用的本质在c++内部实现是一个指针常量
- 编译器发现引用后,就转换为 int * const ref = &a;
六、常量引用
- 作用:常量引用主要用来修饰形参,防止误操作
- 在函数形参列表中,可以加const修饰形参,防止形参改变实参
如
const int & ref = 10;
这样编译器就自动修改为
int temp = 10;
int & ref = temp;
而写int & ref=10;
是错误的,并且加入const之后ref不可修改了
如
void a(const int &val)
这样在函数体内部或main
函数内部就不会对val进行误操作
##类和对象
面向对象三大特性:封装、继承、多态
###一、封装
1、意义:
- 将属性和行为作为一个整体
- 将属性和行为加以权限控制
2、语法:
class 类名{访问权限: 属性/行为};
3、访问权限:
- public 公共权限 成员 类内可以访问,类外可以访问
- protected 保护权限 成员 类内可以访问,类外不可访问
- private 私有权限 成员 类内可以访问,类外不可访问
4、struct和class的区别
- struct默认权限为公共
- class默认权限为私有
5、成员属性设为私有
- 可以自己控制读写权限
- 对于写,可以检测数据的有效性
- 可以再定义一个public内写一个函数来修改private内的内容
6、成员函数判断
- 定义一个判断的函数
- 该函数传入参数为一个未知的如传入&c(即未定义的)
- bool ret = c1.函数(c2);
7、全局函数判断
- 传入两个成员
- 直接判断
- bool ret=函数(c1,c2);
8、分文件编写
- 头文件中只写函数的声明以及属性的声明
- 源文件中注意:加上在谁的的成员函数下
- 用
point::
- 这样在主源文件下只需传入数据并进行判断
二、对象的初始化和清理
1、构造函数和析构函数
注:均在public下写
i、构造函数
实现初始化操作
- 主要作用于创建对象时为对象的成员赋值,构造函数由编译器自动调用,无需手动调用
- 语法:
类名(){}
- 没有返回值也不写void
- 函数名称与类名称相同
- 函数名称与类名相同
- 构造函数可以有参数,因此可以发生重载
- 只会调用一次
ii、析构函数
实现清理操作
- 主要作用于对象销毁前系统的自动调用,执行一些清理工作
- 语法:
~类名(){}
- 没有返回值也不写void
- 函数名称与类名称相同,在名称前加上 ~
- 析构函数不可以有参数,因此不可以发生重载
- 程序在对象销毁前会自动调用析构,无需手动,且只调用一次
构造和析构都是必须有的实现,若自己不提供,编译器会自动提供一个空实现的构造和析构
class person(){
public:
person(){
cout<<"111"<<endl;
}
~person(){
cout<<"222"<<endl;
}
void test(){
person p; //创建对象
//test01执行完毕后会释放这个对象
}
int main(){
test();
system("pause");
return 0;
}
上面代码会输出
111
222
若person p;
写在main函数内则会在system("pause");
之后才会输出析构函数的内容
2、构造函数的分类及调用
i、分类方式
- 按参数分为:有参构造和无参构造
- 有参:
person(int a){}
- 无参:
person(){}
- 按类型分为:普通构造和拷贝构造
- 拷贝构造:
person(const person &p){age = p.age;}
将传入的人身上的所有属性,拷贝到我身上
ii、调用方式
在一个void函数下
括号法
person p1;
//默认构造函数调用person p2(10);
//有参构造函数person p3(p2);
//拷贝构造函数
注:调用默认构造函数时,不要加(),因为编译器会认为person p1();
是一个函数的声明
显示法
person p2 = person(10);
//有参构造person p3 = person(p2);
//拷贝构造- person(10)为匿名对象,当前执行结束后,系统会 立即 回收匿名对象
注:不要利用拷贝构造函数来初始化匿名对象。如person(p3);
,编译器会认为person (p3)
等价于person p3
- 隐式转换法
person p4 = 10;
//相当于person p4 = person(10);
有参构造perosn p5 = p4;
//拷贝构造
3、拷贝构造函数调用时机
i、使用一个已经创建完毕的对象来初始化一个对象
void test01(){
person p1(20);
person p2(p1);
}
ii、值传递的方式给函数参数传值
void dowork(person p){
}
void test02(){
person p;
dowork (p);
}
实参在传给形参时,会调用一个拷贝构造函数,在dowork中对p进行的操作不会影响test02中的p
iii、值方式返回局部对象
person dowork2(){
person p1;
return p1;
}
void test03(){
person p = dowork2();
}
只返回值,不返回地址(地址被释放)
4、构造函数的调用规则
- 创建一个类,编译器会自动给出默认构造,默认析构,默认拷贝
- 如果定义有参构造函数,c++不再提供默认无参构造(此时会报错,必须要写一个默认构造),但会提供默认拷贝构造
- 如果定义拷贝构造函数,c++不会再提供其他构造函数(此时也会报错,必须要写默认构造和默认析构)
5、浅拷贝和深拷贝
- 简单的复制拷贝工作
在堆区申请一段内存:如new int(height);
注:要及时释放这一段内存(用delete)
析构代码
if(m_height != NULL){
delete m_height;
m_height = NULL;
- 不足:会导致堆区内存重复释放
- 在堆区重新申请空间,进行拷贝工作
如
person(const person &p){
m_age=p.m_age;
m_height = new int(*p.m_height);
6 、初始化列表
- 语法:构造函数():属性1(值1),属性2(值2)…{}
- 如person(int a,int b):A(a),B(b){}
7、类对象作为类成员
c++类中的成员可以是另一个类的对象,我们称该成员为对象成员
class phone{
public:
phone(string pname){
m_name = pname;
}
string m_pname;
}
class person{
public:
person(string name,string pname):m_name(name),m_phone(pname){}
string m_name;
phone m_phone;//在person类中使用了phone类
}
构造顺序 先有手机再有人,析构顺序 先释放人再释放手机。二者顺序相反
8、静态成员
加上关键字static
i、静态成员变量(也有访问权限,私有的类外访问不到)
- 所有对象共享同一份数据
访问方式1:
person p;
cout<<p.m_A<<endl;
访问方式2:
cout<<person::m_A<<endl;
- 在编译阶段分配内存
- 类内声明(
static int m_A;
),类外初始化(int person::m_A = 100;
)这样就可以在函数中访问到了
ii、静态成员函数(也有访问权限,私有的类外访问不到)
- 所有对象共享同一个函数
访问方式(通过对象)1:
person p;
p.func();
访问方式(通过类名)2:
person::func();
- 静态成员函数只能访问静态成员变量
- 没法区分是哪个对象的m_B
三、c++对象模型和this指针
1、成员变量和成员函数分开存储
i、
- 空对象占用的空间为:1
- 是为了区分空对象占内存的位置
- 一个非静态成员变量占用内存为:4,而成员变量和成员函数是分开存储的,所以将一个函数与一个成员变量写进同一个类内输出大小还是4
- 静态的成员变量 不属于类对象上,同理成员函数
只有非静态成员变量属于类对象上
2、this指针概念
- this指针指向被调用的成员函数所属的对象(谁调用就指向谁)
- 无需定义,直接使用
- 相当于
类名 * const this;
- 指向不可以修改
用途
- 当形参和成员变量同名时,可用this指针来区分
如
class Person{
public:
Person(int age){
//this指针指向被调用的成员函数所属的对象,指 向的是p1
this->age = age;
}
Person& addage(person &p){
this->age += p.age;
return *this;
}
int age;
}
void test01(){
Person p1(18);
}
void test02(){
Person p1(10);
Person p2(10);
p2.addage(p1).addage(p1).addage(p1);
//链式编程
}
这样就可以区分形参和成员变量
- 在类的非静态成员函数中返回对象本身,可用
return *this
3、空指针访问成员函数
- 空指针person *p=NULL;
- 空指针不可以访问没定义的属性
4、const修饰成员函数
- 常函数
* 成员函数后加const,相当于使this指针指向的值变得不可修改
* 常函数不可以修改成员属性
* 成员属性声明时加关键字mutable后,在常函数中依然可以修改 - 常对象
* 声明对象前加const
* 常对象只能调用常函数(因为普通成员函数可以修改属性)
四、友元
让类外特殊的一些函数或类进行访问
- 关键字为
frend
三种实现
- 全局函数做友元
`frend void func();`
- 类做友元
`friend class 类();`
类外写成员函数:
先在类内进行声明
类名::函数名(){
函数体;
}
对于有返回值类型的函数要加上返回值类型
- 成员函数做友元
`friend void 类名::函数名();`
五、运算符重载
对已有的运算符进行重新定义,赋予其另一种功能
一、加号运算符重载
实现两个自定义数据相加的运算
- 函数名:
operator+
- 通过成员函数重载+号(写在一个类下面)
如
Person operator+(Person &p){
Person temp;
temp.A=this->A+p.A;
temp.B=this->B+p.B;
return temp;
}
调用时可以Person p3=p1+p2;
本质为Person p3 = p1.operator+(p2);
- 全局函数重载+号(写在类外)
如
person operator+(Person &p1,Person &p2){
person temp;
temp.A=p1.A+p2.A;
temp.B=p1.B+p2.B;
return temp;
}
调用时可以person p3=p1+p2;
本质为person p3 = operator+(p1,p2);
其实还可以实现person类与int型进行相加,只需在函数相应的位置修改成对应的数据类型,就可以实现如p4= p1 + 10;
这样的运算
二、左移运算符重载
可以输出自定义数据类型
不会利用成员函数重载<<运算符,无法实现cout在左侧
- 利用全局函数
即
ostream &operator<<(ostream &cout, Person &p){
cout<<"A= "<<p.A<<"B= "<<p.B;
return cout;//这样就可以加endl了(运用链式编程思想)
}
使用时:cout<<p<<endl;
注:可以与友元进行配合从而输出自定义数据类型
三、递增运算符的重载(++)
重载递增运算符,实现自己的整形数据
- 前置++
即
myinteger& operator(){
num++;
return *this;
}
返回引用方便进行链式编程
- 后置++
即
myinteger operator(int)//int代表占位参数用于区分前置和后置
{
//先记录当时结果
myinteger temp = *this;
//再进行++
num++;
return temp;
}
不能返回引用,因为局部对象temp在函数执行完就被释放了,要返回值
四、赋值运算符重载
编译器有自己提供的operator=,但是只是浅拷贝,在释放堆区数据时代码易发生崩溃
故利用深拷贝
- 重载
即
Person& operator=(Person &p)
{
if(m_Age!=NULL){
delete m_Age;
m_Age=NULL;
}
m_Age= new int(*p.m_Age);
return *this;
}
五、关系运算符重载(==、!=)
- 在类内重载(==)
即
bool operator==(Person &p){
if (this->m_name == p.m_name && this->m_age == p.m_age){
return true;
}
return false;
}
- !=的重载
即
bool operator!=(Person &p){
if (this->m_name == p.m_name && this->m_age == p.m_age){
return false;
}
return true;
}
六、函数调用运算符重载
- 在类内
如
void operator()(string test){
cout<<test<<endl;
}
void test01(){
Myprint myprint;
mypeint("hello world");
}
由于重载后的与普通函数十分相似,故也称成为仿函数
特点:
- 没有固定形式,非常灵活
匿名函数对象
通过函数名加小括号即MyAdd()(1,1)
//匿名对象在当前行被执行完了就立即被释放