文章目录
引用
基本使用
**作用: **给变量起别名
语法: 数据类型 &别名 = 原名
int a = 10;
int &b = a;
注意事项
- 引用必须初始化
- 引用在初始化后,不可以改变
int &a; //这是错误的,因为必须初始化
int &c = a; //一旦初始化后,就不可以更改
c = b; //这是赋值操作,不是更改引用
引用作为函数参数
**作用:**函数传参时,可以利用引用的技术让形参修饰实参
**优点:**可以简化指针修改实参
void func(int &a, int &b){
int tmp = a + b;
}
**注意:**在函数参数中的引用不可以传入一个临时变量,如果真的想引用这个变量,用const
来保证我们不会对临时变量的内存进行更改。
示例:
//这里重载了左移运算符和递减运算符
class myInteger{
friend ostream& operator<<(ostream &cout, const myInteger &i);
public:
myInteger(){
this->mNum = 0;
}
myInteger& operator--(){
this->mNum--;
return *this;
}
myInteger operator--(int){
myInteger tmp = *this;
this->mNum--;
return tmp;
}
private:
int mNum;
};
//由于后置递减运算符返回的是一个临时变量,
//所以左移的引用必须用const修饰
ostream& operator<<(ostream &cout, const myInteger &i){
cout << i.mNum;
return cout;
}
int main(){
myInteger m;
cout << m-- << ' ' << m << endl;
}
引用做函数返回值
注意:不要返回局部变量引用
//返回局部变量引用
int& test01() {
int a = 10; //局部变量
return a;
}
//不能返回局部变量的引用,
//局部变量释放后编译器只回对其保留一次,之后就是乱码
引用的本质
本质:引用的本质在c++内部实现是一个指针常量. `int* const ref = &a;
int main(){
int a = 10;
//自动转换为 int* const ref = &a; 指针常量是指针指向不可改,也说明为什么引用不可更改
int& ref = a;
ref = 20; //内部发现ref是引用,自动帮我们转换为: *ref = 20;
}
常量引用
作用: 常量引用主要用来修饰形参,防止误操作
在函数形参列表中,可以加const修饰形参,防止形参改变实参
int main() {
//int& ref = 10; 引用本身需要一个合法的内存空间,因此这行错误
//加入const就可以了,编译器优化代码,int temp = 10; const int& ref = temp;
const int& ref = 10;
}
构造函数与析构函数
- 构造函数:用于初始化对象。
- 语法:Name(){};
- 没有类型,但是可以有参数,所以可以重构。
- 系统默认给一个空的默认构造函数。
- 可以用参数进行重构,只要参数数量、类型、顺序不一即可。
- 程序在对象申请时自动调用,且只调一次。
- 析构函数:用于释放初始化时申请的堆区内存。
- 语法:~Name(){};
- 没有类型也没有参数,故不可重构。
- 系统也是默认给个空的函数。
- 程序在对象销毁之前自动调用,且只调一次。
注意:
- 函数写在public里。
构造函数的类型及调用
两种分类方式:
- 按参数:有参构造和无参构造。
- 按类型:普通构造和拷贝构造。
三种调用方式:
- 括号法、显式调用、隐式调用。
拷贝构造函数的写法
Name(const Name &p){
// 对一些值进行拷贝
}
三种调用方式
- 括号法
- Name p1(10);
- 直接在创建的对象后面打括号写上参数,但是执行默认构造时不能打括号,因为
Name p()
会被认为是一种函数。
- 显示调用
- Name p2 = Name(p1);
- 单独的
Name(p1)
会被视作给一个匿名对象进行构造,在这句语句执行完之后立即执行析构。
- 隐式调用
- Name p3 = 10;
注意:
- 不可以用 拷贝构造函数 初始化匿名对象,编译器会认为这是一个对象的声明。
- Name (p3) == Name p3;
拷贝构造函数调用的时机
- 使用一个已经创建完毕的对象来初始化一个新对象
-
Name p(100); Name p1 = p; Name p3; p3 = p; // 不是调用拷贝函数,而是赋值
-
- 值传递的方式给函数参数传值
-
void doWork(Name p1){ } void main(){ Name p; doWork(p); }
- 此处其实是调用拷贝函数复制了一个类过去,而并非原来那个。
-
- 以值方式返回局部对象
-
Name doWork(){ Name p; return p; }
- 此时,因为p是局部对象,所以在函数执行完之后就销毁了,于是编译器用拷贝函数复制了一个新对象返回。
-
构造函数的调用规则
默认情况下,编译器提供给每个类三个函数:
- 默认构造函数
- 默认析构函数
- 默认拷贝函数
调用规则:
- 你写了构造函数,默认提供你析构和拷贝函数。
- 你写了析构,提供拷贝,不提供构造。
- 你写了拷贝,其他两个都不提供。
深拷贝与浅拷贝
- 浅拷贝:简单的赋值拷贝操作。
- 深拷贝:在堆区重新申请空间,进行拷贝操作。
简单来说,对于一些数值来说,我们直接给对应属性拷贝过去是可以的。但是对于一些地址来说,只拷贝地址数值是不行的,因为对于这两个对象的释放来说,这两个属性所指向的地址是相同的,所以会造成二次释放的问题,所以我们就需要在拷贝的时候在堆区申请一个新空间。
总结:如果属性有在堆区开辟的,一定要自己提供拷贝构造函数,防止浅拷贝带来的问题
初始化列表
class Person{
public:
//传统方式初始化
/*Person(int a, int b, int c) {
m_A = a;
m_B = b;
m_C = c;
}*/
//初始化列表
Person(int a, int b, int