C++笔记梁哥day3 | 我的日常分享

C++笔记梁哥day3

引用(reference)

引用是C++对C的重要扩充。在c/c++中指针的作用基本都是一样的,但是C++增加了另外一种给函数传递地址的途径,这就是按引用传递(pass-by-reference),它也存在于其他一些编程语言中,并不是是C++的发明。

变量名实质上是一段连续内存空间的别名,是一个标号(门牌号),程序中通过变量来申请并命名内存空间,通过变量的名字可以使用存储空间

对一段连续的内存空间只能取一个别名吗?c++中新增了引用的概念,引用可以作为一个已定义变量的别名。基本语法: Type& ref=val;

注意事项:&在此不是取地址运算,而是起标识作用。类型标识符是指目标变量的类型,必须在声明引用变量时进行初始化。引用初始化之后不能改变。不能有 NULL 引用。必须确保引用是和一块合法的存储单元关联。可以建立对数组的引用。

一、引用的初始

语法:

  1. &和别名结合,表示引用
  2. 给某个变量取别名,就定义某个变量
  3. 从上往下替换
  4. 赋值
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    int num = 10;
    //现在要给num定义一个引用a
    //第一步 &和a结合
    &a
    //第二步 给num取别名,定义num
    int num;
    //第三步 从上往下替换
    int &a;
    //第四步 赋值
    int &a = num;

    a完全等价于num,操作a就是操作num。

注意:引用必须初始化,一旦初始化就不能再次修改。

1
2
3
4
5
int num = 10;
int &a = num;

int data = 20;
a = data;//这个语句不是将data的别名设置成a,而是将data的值赋给了a(num)

二、引用作用于数组

1、方法一:直接替换法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
int arr[5] = {1,2,3,4,5};
//现在要给arr定义一个引用myArr
//第一步 &和myArr结合
&myArr
//第二步 给num取别名,定义num
int arr[5];
//第三步 从上往下替换
int (&myArr)[5];//要加() []的优先级比&高
//第四步 赋值
int (&myArr)[5] = arr;

void test01(){
int arr[5] = {1,2,3,4,5};
int (&myArr)[5] = arr;
for(int i = 0; i < 5; i++){
cout<<"myArr["<<i<<"]="<<myArr[i]<<" ";
}
cout<<endl;
}

运行结果:
图片

2、方法二:配合typedef

先用typedef给类型取个别名,在使用类型的别名定义变量,接着再给变量定义引用

1
2
3
4
5
6
7
8
9
10
void test02(){
int arr[5] = {1,2,3,4,5};
typedef int TYPE_ARR[5];
TYPE_ARR newArr={10,20,30,40,50};
TYPE_ARR &myArr = newArr;
for(int i = 0; i < 5; i++){
cout<<"myArr["<<i<<"]="<<myArr[i]<<" ";
}
cout<<endl;
}

运行结果:
图片

三、引用作为函数的参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
//交换失败
void swap1(int m,int n){
cout<<"m="<<m<<",n="<<n<<endl;
int temp=m;
m=n;
n=temp;
cout<<"m="<<m<<",n="<<n<<endl;
}
//交换成功 但是比较麻烦
void swap2(int *m,int *n){
cout<<"m="<<*m<<",n="<<*n<<endl;
int temp=*m;
*m=*n;
*n=temp;
cout<<"m="<<*m<<",n="<<*n<<endl;
}
//交换成功 操作简便
void swap3(int &m,int &n){
cout<<"m="<<m<<",n="<<n<<endl;
int temp=m;
m=n;
n=temp;
cout<<"m="<<m<<",n="<<n<<endl;
}
void test03(){
int a=10,b=20;
cout<<"a="<<a<<",b="<<b<<endl<<endl;
swap1(a,b);
cout<<"swap1(a,b) a="<<a<<",b="<<b<<endl;
swap2(&a,&b);
cout<<"swap2(&a,&b) a="<<a<<",b="<<b<<endl;
swap3(a,b);
cout<<"swap3(a,b) a="<<a<<",b="<<b<<endl;
}

运行结果:
图片

四、引用作为函数的返回值

  1. 不要返回局部变量的引用

    1
    2
    3
    4
    5
    6
    7
    8
    int& my_data1(){
    int num = 20;
    return num;
    }
    void test04(){
    int &ret = my_data1();
    cout<<"ret="<<ret<<endl;//非法访问内存
    }

    运行结果:
    图片
    如果返回局部变量的引用是很危险的,当函数执行完后,局部变量已经被释放,这个时候你去操作它就是操作非法空间。

  2. 可以返回静态局部变量的的引用,它的生命周期比较长

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    int& my_data2(){
    static int num = 20;
    return num;
    }
    void test05(){
    int &ret = my_data2();
    cout<<"ret="<<ret<<endl;
    ret = 200;
    cout<<"ret="<<ret<<endl;
    }

    运行结果:
    图片

  3. 当函数返回值作为左值时,那么函数的返回值的类型必须是引用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    int& my_data3(){
    static int num = 50;//static定义的变量只会初始化一次
    cout<<"num = "<<num<<endl;
    return num;
    }
    void test06(){
    //函数的返回值 作为左值
    my_data3() = 2000;//调用函数,同时将2000赋给了函数的返回值
    my_data3();
    }

    运行结果:
    图片

五、引用的本质是常量指针(了解)

引用的本质在C++内部实现是一个指针常量.

1
Type& ref = val;//Type* const ref = &val;

C++编译器在编译过程中使用常指针作为引用内部实现,因此引用所占用的空间大小与指针相同,只是这个过程是编译器内部实现,用户不可见。

1
2
3
4
5
int data = 10;
int &a = data;//a就是data的别名
//编译器内部转换: int * const a = &data; const修饰的是a,意思是a一旦保存了data的地址后就不能改变
a = 100;//等价于data=100;
//编译器内部转换:*a = 100;//*a == data;

六、指针的引用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
void func1(char **p){
*p = (char *)malloc(sizeof(char)*32);
strcpy(*p,"hello world!");
}
void func2(char *(&p)){
p = (char *)malloc(sizeof(char)*32);
strcpy(p,"hello!");
}
void test07(){
char *str = NULL;
//需求:定义一个函数 申请一段空间 给str 并将"hello world!"存进去
//采用二级指针
func1(&str);
cout<<"str = "<<str<<endl;
//采用引用
func2(str);
cout<<"str = "<<str<<endl;
}

运行结果:
图片

七、常引用

需求:定义一个函数需求遍历一个结构体

  1. 直接传入结构体
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    struct STU{
    int num;
    char name[32];
    };
    void myPrintSTU1(STU stu){
    cout<<"sizeof(stu)="<<sizeof(stu)<<endl;
    cout<<"num = "<<stu.num<<",name = "<<stu.name<<endl;
    }
    void test08(){
    STU stu = {100,"lucy"};
    myPrintSTU1(stu);
    }
    运行结果:
    图片

函数myPrintSTU1的缺点是形参占用空间太多

  1. 传入结构体的地址
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    struct STU{
    int num;
    char name[32];
    };
    struct STU{
    int num;
    char name[32];
    };
    void myPrintSTU2(STU *stu){
    cout<<"sizeof(stu)="<<sizeof(stu)<<endl;
    cout<<"num = "<<stu->num<<",name = "<<stu.name<<endl;
    }
    void test09(){
    STU stu = {200,"bob"};
    myPrintSTU2(&stu);
    }
    运行结果:
    图片

函数myPrintSTU2的解决了形参占用空间太多,在32位环境中只占用四字节,但是缺点是传入的是指针,这个函数只是用来遍历,读取操作,但是可以修改结构体stu里面的内容,这样很危险

  1. 2的优化
    将形参加了const修饰

    1
    2
    3
    4
    5
    6
    7
    8
    9
    void myPrintSTU3(STU const *stu){
    cout<<"sizeof(stu)="<<sizeof(stu)<<endl;
    //stu->num=2000;不能修改 会报错
    cout<<"num = "<<stu->num<<",name = "<<stu->name<<endl;
    }
    void test10(){
    STU stu = {200,"bob"};
    myPrintSTU3(&stu);
    }
  2. 采用引用的方法

    1
    2
    3
    4
    5
    6
    7
    void myPrintSTU4(STU &stu){
    cout<<"num = "<<stu.num<<",name = "<<stu.name<<endl;
    }
    void test11(){
    STU stu = {300,"Lily"};
    myPrintSTU4(stu);
    }

    运行结果:
    图片

函数myPrintSTU4的解决了形参占用空间太多,其不占用空间,这个函数只是用来遍历,读取操作,但是可以修改结构体stu里面的内容,这样很危险,所以就有了常引用

  1. 4的优化,使用常引用
1
2
3
4
5
6
7
8
void myPrintSTU5(STU const  &stu){
stu.num=4000;//不能修改 会报错
cout<<"num = "<<stu.num<<",name = "<<stu.name<<endl;
}
void test12(){
STU stu = {300,"Lily"};
myPrintSTU5(stu);
}

运行结果:
图片

常量的引用

给常量取一个别名(定义一个引用)

  1. 会报错10的类型是const int,类型不匹配,系统怕你后面把常量10改了 num=20;

    1
    2
    3
    4
    void test13(){
    int &num = 10;
    cout<<"num = "<<num<<endl;
    }

    运行结果:
    图片

  2. 1
    2
    3
    4
    void test14(){
    const int &num = 10;
    cout<<"num = "<<num<<endl;
    }

    运行结果:
    图片