【C++】C++的引用
C++的引用
- 1. 引用的概念
- 2. 语法规则
- 示例代码:三种常见引用的定义
- 2.1 指向普通变量的引用
- 示例代码:
- 2.2 指向指针的引用
- 2.3 指向数组的引用
- 2.4 示例代码 引用的使用:
- 3. 特点
- 3.1 引用仅仅只是变量的一个别名,不占用额外的内存空间
- 示例代码:
- 3.2 引用具备了传值和传地址的双重属性
- 示例代码:
- 3.3 引用必须在定义的时候立马初始化(例外的情况:引用作为参数除外)
- 示例代码:
- 3.4 引用初始化以后不可以被改变
- 示例代码:
- 3.5 void fun(int &x) // 此时实参只能是左值,不能是右值
- 示例代码:
1. 引用的概念
定义:变量的一个别名
- C语言中传参的方式总共两种: 传值,传地址
- C++中传参的方式总共三种: 传值,传地址,传引用
2. 语法规则
类型名 &引用的名字=变量名;
比如:
int a=45;
int &b=a; // 定义了一个引用b,b指向a
// 定义了一个引用b,b是a的别名
float a=56.5;
float &b=a;
struct student a={"张三",18};
struct student &b=a;
示例代码:三种常见引用的定义
#include <iostream>
using namespace std; //使用命名空间std
//公式:类型名 &引用的名字 = 变量名;
int main(int argc, char const *argv[])
{
//第一种:指向普通变量的引用
double a = 45.6;
double &b = a; // 定义引用
cout<<"a的地址:"<<&a<<endl;
cout<<"b的地址:"<<&b<<endl;
//第二种:指向指针的引用
int c = 45;
int *p = &c;
int *&x = p; // 定义引用
cout<<"p的地址:"<<p<<endl;
cout<<"x的地址:"<<x<<endl;
char d[10] = "hello";
char *q = d;
char *&y = q; // 定义引用
cout<<"q的地址:"<<(int *)q<<endl;
cout<<"y的地址:"<<(int *)y<<endl;
//第三种:指向数组的引用
int e[5] = {45,96};
// 第一种书写方式
int (&z)[5]= e; // 定义引用
cout<<"e的地址:"<<e<<endl;
cout<<"z的地址:"<<z<<endl;
// 第二种书写方式
typedef int array[5]; // 给int [5] 取别名 为 array
array &o = e; // 定义引用
cout<<"e的地址:"<<e<<endl;
cout<<"o的地址:"<<o<<endl;
return 0;
}
/*
执行结果:
a的地址:0x7fff4d1e2090
b的地址:0x7fff4d1e2090
p的地址:0x7fff4d1e208c
x的地址:0x7fff4d1e208c
q的地址:0x7fff4d1e20ee
y的地址:0x7fff4d1e20ee
e的地址:0x7fff4d1e20d0
z的地址:0x7fff4d1e20d0
e的地址:0x7fff4d1e20d0
o的地址:0x7fff4d1e20d0
*/
2.1 指向普通变量的引用
示例代码:
#include <iostream>
using namespace std; //使用命名空间std
int main(int argc, char const *argv[])
{
int a = 666;
/*
有两种叫法:
1、定义引用叫做b,b指向a
2、定义引用叫做b,b是a的别名
*/
int &b = a;
// 证明引用就是变量的别名==》打印地址
cout<<"a的地址:"<<&a<<endl;
cout<<"b的地址:"<<&b<<endl;
cout<<"a的值:"<<a<<endl;
cout<<"b的值:"<<b<<endl;
return 0;
}
/*
执行结果:
a的地址:0x7fffc34d00dc
b的地址:0x7fffc34d00dc
a的值:666
b的值:666
*/
2.2 指向指针的引用
int a=89;
int *p=&a;
int *&b=p; //定义了指向p的引用
char buf[10]="hello";
char *q=buf;
char *&b=q;
2.3 指向数组的引用
int buf[10]; //类型 int[10]
// 第一种表达方式
typedef int array[10];
array &b=buf;
// 第二种表达方式
int (&b)[5]= buf;
2.4 示例代码 引用的使用:
总结:只要是引用,写代码的时候,当成指向的变量去使用即可
#include <iostream>
using namespace std; //使用命名空间std
//公式:类型名 &引用的名字 = 变量名;
int main(int argc, char const *argv[])
{
//第一种:指向普通变量的引用
double a = 45.6;
double &b1 = a; // 定义引用
// cout<<"a的地址:"<<&a<<endl;
// cout<<"b的地址:"<<&b<<endl;
//第二种:指向指针的引用
int c = 45;
int *p = &c;
int *&b2 = p; // 定义引用
// cout<<"p的地址:"<<p<<endl;
// cout<<"x的地址:"<<x<<endl;
char d[10] = "hello";
char *q = d;
char *&b3 = q; // 定义引用
// cout<<"q的地址:"<<(int *)q<<endl;
// cout<<"y的地址:"<<(int *)y<<endl;
//第三种:指向数组的引用
int e[5] = {45,96};
// 第一种书写方式
int (&b4)[5]= e; // 定义引用
// cout<<"e的地址:"<<e<<endl;
// cout<<"z的地址:"<<z<<endl;
// 第二种书写方式
typedef int array[5]; // 给int [5] 取别名 为 array
array &o = e; // 定义引用
// cout<<"e的地址:"<<e<<endl;
// cout<<"o的地址:"<<o<<endl;
// 引用的使用:只要是引用(无论什么类型),写程序时,当成指向的变量去使用即可
//a怎么用,b1就照着写
cout<<"修改前a的值: "<<a<<endl;
cout<<"修改前b1的值: "<<b1<<endl;
cout<<"修改前a的地址: "<<&a<<endl;
cout<<"修改前b1的地址: "<<&b1<<endl;
a=78.9;
b1=28.9;
cout<<"修改后a的值: "<<a<<endl;
cout<<"修改后b1的值: "<<b1<<endl;
cout<<"修改后a的地址: "<<&a<<endl;
cout<<"修改后b1的地址: "<<&b1<<endl;
//p怎么用,b2就照着写
cout<<"p存放的地址: "<<p<<endl;
cout<<"b2存放的地址: "<<b2<<endl;
cout<<"*p存放的值: "<<*p<<endl;
cout<<"*b2存放的值: "<<*b2<<endl;
//e怎么用,b4就照着写
cout<<"e[0] is: "<<e[0]<<endl;
cout<<"b4[0] is: "<<b4[0]<<endl;
return 0;
}
/*
执行结果:
修改前a的值: 45.6
修改前b1的值: 45.6
修改前a的地址: 0x7ffe22938920
修改前b1的地址: 0x7ffe22938920
修改后a的值: 28.9
修改后b1的值: 28.9
修改后a的地址: 0x7ffe22938920
修改后b1的地址: 0x7ffe22938920
p存放的地址: 0x7ffe2293891c
b2存放的地址: 0x7ffe2293891c
*p存放的值: 45
*b2存放的值: 45
e[0] is: 45
b4[0] is: 45
*/
3. 特点
3.1 引用仅仅只是变量的一个别名,不占用额外的内存空间
ps:引用本质上也是用指针来实现的,底层也分配了存储空间
应用:引用作为函数的形参,作为函数的返回值,使用频率最高(提高程序运行效率)
示例代码:
#include <iostream>
using namespace std; //使用命名空间std
/*
使用引用:为了提高程序的运行效率
*/
typedef struct student{
char name[10];
int age;
float score;
char addr[100];
}student_t;
void fun1(student_t stu)
{
cout<<"传值成员地址"<<endl;
cout<<"fun1中stu.name的地址:"<<(int *)(stu.name)<<endl; // cout中字符数组需要强转成int*才可打印地址,否则输出的是字符串
cout<<"fun1中stu.age的地址:"<<&(stu.age)<<endl;
cout<<"fun1中stu.score的地址:"<<&(stu.score)<<endl;
cout<<"fun1中stu.addr的地址:"<<(int *)(stu.addr)<<endl; // cout中字符数组需要强转成int*才可打印地址,否则输出的是字符串
}
void fun2(student_t &stu)
{
cout<<"引用成员地址"<<endl;
cout<<"fun2中stu.name的地址:"<<(int *)(stu.name)<<endl; // cout中字符数组需要强转成int*才可打印地址,否则输出的是字符串
cout<<"fun2中stu.age的地址:"<<&(stu.age)<<endl;
cout<<"fun2中stu.score的地址:"<<&(stu.score)<<endl;
cout<<"fun2中stu.addr的地址:"<<(int *)(stu.addr)<<endl; // cout中字符数组需要强转成int*才可打印地址,否则输出的是字符串
}
int main(int argc, char const *argv[])
{
student_t stu = {"李1", 12, 99.9, "山卡拉"};
cout<<"实参成员地址"<<endl;
cout<<"实参中stu.name的地址:"<<(int *)(stu.name)<<endl; // cout中字符数组需要强转成int*才可打印地址,否则输出的是字符串
cout<<"实参中stu.age的地址:"<<&(stu.age)<<endl;
cout<<"实参中stu.score的地址:"<<&(stu.score)<<endl;
cout<<"实参中stu.addr的地址:"<<(int *)(stu.addr)<<endl; // cout中字符数组需要强转成int*才可打印地址,否则输出的是字符串
fun1(stu); // 传值
fun2(stu); // 传引用
return 0;
}
/*
执行结果:可以看出引用传参的地址与实参成员地址相同,减少栈空间的使用,提高程序运行效率
实参成员地址
实参中stu.name的地址:0x7ffcb1bd5860
实参中stu.age的地址:0x7ffcb1bd586c
实参中stu.score的地址:0x7ffcb1bd5870
实参中stu.addr的地址:0x7ffcb1bd5874
传值成员地址
fun1中stu.name的地址:0x7ffcb1bd57d0
fun1中stu.age的地址:0x7ffcb1bd57dc
fun1中stu.score的地址:0x7ffcb1bd57e0
fun1中stu.addr的地址:0x7ffcb1bd57e4
引用成员地址
fun2中stu.name的地址:0x7ffcb1bd5860
fun2中stu.age的地址:0x7ffcb1bd586c
fun2中stu.score的地址:0x7ffcb1bd5870
fun2中stu.addr的地址:0x7ffcb1bd5874
*/
3.2 引用具备了传值和传地址的双重属性
即修改引用的形参相当于修改该地址的值
示例代码:
#include <iostream>
using namespace std; //使用命名空间std
/*
使用引用:为了提高程序的运行效率
*/
typedef struct student{
char name[10];
int age;
float score;
char addr[100];
}student_t;
void fun1(student_t stu)
{
stu.age = 22;
// cout<<"传值成员值"<<endl;
// cout<<"fun1中stu.name的值:"<<(int *)(stu.name)<<endl; // cout中字符数组需要强转成int*才可打印地址,否则输出的是字符串
// cout<<"fun1中stu.age的值:"<<&(stu.age)<<endl;
// cout<<"fun1中stu.score的值:"<<&(stu.score)<<endl;
// cout<<"fun1中stu.addr的值:"<<(int *)(stu.addr)<<endl; // cout中字符数组需要强转成int*才可打印地址,否则输出的是字符串
}
void fun2(student_t &stu)
{
stu.age = 32;
// cout<<"fun2中stu.name的值:"<<(int *)(stu.name)<<endl; // cout中字符数组需要强转成int*才可打印地址,否则输出的是字符串
// cout<<"fun2中stu.age的地址:"<<&(stu.age)<<endl;
// cout<<"fun2中stu.score的地址:"<<&(stu.score)<<endl;
// cout<<"fun2中stu.addr的地址:"<<(int *)(stu.addr)<<endl; // cout中字符数组需要强转成int*才可打印地址,否则输出的是字符串
}
int main(int argc, char const *argv[])
{
student_t stu = {"李1", 12, 99.9, "山卡拉"};
cout<<"实参成员值(修改前)"<<endl;
cout<<"实参中stu.name的值:"<<stu.name<<endl; // cout中字符数组需要强转成int*才可打印地址,否则输出的是字符串
cout<<"实参中stu.age的值===========:"<<stu.age<<endl;
cout<<"实参中stu.score的值:"<<stu.score<<endl;
cout<<"实参中stu.addr的值:"<<stu.addr<<endl; // cout中字符数组需要强转成int*才可打印地址,否则输出的是字符串
fun1(stu); // 传值
cout<<"实参成员值(传值后)"<<endl;
cout<<"实参中stu.name的值:"<<stu.name<<endl; // cout中字符数组需要强转成int*才可打印地址,否则输出的是字符串
cout<<"实参中stu.age的值===========:"<<stu.age<<endl;
cout<<"实参中stu.score的值:"<<stu.score<<endl;
cout<<"实参中stu.addr的值:"<<stu.addr<<endl; // cout中字符数组需要强转成int*才可打印地址,否则输出的是字符串
fun2(stu); // 传引用
cout<<"实参成员值(传引用后)"<<endl;
cout<<"实参中stu.name的值:"<<stu.name<<endl; // cout中字符数组需要强转成int*才可打印地址,否则输出的是字符串
cout<<"实参中stu.age的值===========:"<<stu.age<<endl;
cout<<"实参中stu.score的值:"<<stu.score<<endl;
cout<<"实参中stu.addr的值:"<<stu.addr<<endl; // cout中字符数组需要强转成int*才可打印地址,否则输出的是字符串
return 0;
}
/*
执行结果:
实参成员值(修改前)
实参中stu.name的值:李1
实参中stu.age的值===========:12
实参中stu.score的值:99.9
实参中stu.addr的值:山卡拉
实参成员值(传值后)
实参中stu.name的值:李1
实参中stu.age的值===========:12
实参中stu.score的值:99.9
实参中stu.addr的值:山卡拉
实参成员值(传引用后)
实参中stu.name的值:李1
实参中stu.age的值===========:32
实参中stu.score的值:99.9
实参中stu.addr的值:山卡拉
*/
3.3 引用必须在定义的时候立马初始化(例外的情况:引用作为参数除外)
int a=78;
int &b;
b=a; //语法错误
void swap3(int &a,int &b) 例外的情况:引用作为函数形参例外
{
}
示例代码:
#include <iostream>
using namespace std; //使用命名空间std
int main(int argc, char const *argv[])
{
int a = 1;
int &b;
b = a;
return 0;
}
编译时报错:
3.4 引用初始化以后不可以被改变
int a=78;
int b=98;
int &c=a; //c是a的别名
c=b; //不要理解成引用c重新指向b,应该理解为把b的值赋值给了a(引用c是a的别名)
示例代码:
#include <iostream>
using namespace std; //使用命名空间std
int main(int argc, char const *argv[])
{
int a = 1;
int c = 2;
//定义引用b,b是a的别名
int &b = a;
//b是a的别名,就永远都是a的别名
//这个特点跟引用底层实现有关,因为引用的底层是用 int *const p;指针实现
b = c; // 等价于 a=c
// 验证
cout<<"a="<<b<<endl;
return 0;
}
/*
执行结果:
a=2
*/
3.5 void fun(int &x) // 此时实参只能是左值,不能是右值
fun(a); // a是左值,正确
fun(88); // 88不是左值,错误
fun(a+a); // a+a是右值,错误
void fun(const int &x) //此时实参可以是左值/右值
常应用:
const修饰的引用叫做常引用
常引用:
作用1:防止引用修改指向的变量值
作用2:实参传递的时候既能传递左值,也能传递右值
int buf[8]={10,12};
int otherbuf[8]
buf=14; //错误,数组名不能作为左值
otherbuf=buf; //错误,数组名不能作为左值
buf=buf+1; //错误,数组名不能作为左值
int * const&b=buf; //数组名只能作为右值,此时引用必须用const修饰才能指向右值
int a=67;
const int &c=a; //等价于int const&c=a;
作用3:如果是常量,定义引用,必须使用常引用
const int a=78;
const int &b=a;
示例代码:
#include <iostream> //C++的标准输入输出流头文件
using namespace std; //我要使用命名空间std
/*
引用作为函数的形参:实参传递左值和右值
概念:const修饰的引用叫做常引用
例如: int a=45;
const int &b=a;
常引用的作用:
作用1:函数的形参是常引用,实参可以传递左值也可以传递右值
函数的形参是普通引用(非const修饰),实参只可以传递左值不能传递右值
作用2:常引用不可以修改指向的变量值
*/
//void fun(int &num)
void fun(const int &num)
{
cout<<"num is: "<<num<<endl;
}
int main()
{
int n=456;
//调用函数
//写法1:
fun(n);
//写法2:
/*
error: invalid initialization of non-const reference of type ‘int&’ from an rvalue of type ‘int’
*/
fun(n+1); //n+1是右值,普通引用不能传递右值
//写法3:
fun(147); //147也是右值 ,普通引用不能传递右值
return 0;
}