详解C++中的引用和指针,以及其区别和联系

C++中新引入的概念:引用

他们各有各自的优劣之分

那么我们在编程过程中,如何去抉择引用和指针?

接下来,我们将从指针和引用的区别和联系,以及他们的各自在编程实现中的优劣势。

1.指针和引用的定义

        引用:引用是已存变量的一个别名,对引用的操作就是对原变量的操作

        指针:在计算机中所有数据存储在内存单元中,而每个内存单元都有一个对应的地址,只要通过这个地址就能找到对应单元中存储的数据。

2.指针和引用的初始化

(1)指针的初始化

        C语言中提供了取地址运算符&来表示变量的地址。其一般形式为: &变量名;

指针变量初始化的方法有两种:定义的同时进行初始化和先定义后初始化

1.定义的同时进行初始化:

int a = 1;
int *pa = &a;

2.先定义再初始化:

在定义指针变量的时候,表示这个变量是一个指针,也就是说只有与类型名称(比如int、float 等)搭配使用的时候才表示是指针变量,其他时候有不同的含义

int a = 5;
int *pa, *pb, &pc;
pa = &a;
*pb = &a;//error,此时pb已经表示指针了,再加*表示将指针指向的存储区域的内容取出来

注意:

- 多个指针变量可以指向同一个地址

- 指针的指向是可以改变的

- 指针的类型要和变量的类型一致

int a = 1;
int b = 2;
int *pa = &a;
int *pb = &a;//多个指针变量可以指向同一个地址
pb = &b;//指针的指向是可以改变的
//double *p_a = &a;//error

(2)引用的初始化

格式:类型 &引用名 = 变量名;

引用与基本类型

#include<iostream>
using namespace std;
int main()
{
    int a = 1;
    int& r_a = a;//r_a引用a,r_a就是a的别名
    cout << "&a = " << &a << ",a = " << a << endl;
    cout << "&r_a = " << &r_a << ",b = " << r_a << endl;
    r_a = 2;
    cout << "a = " << a << endl;//2
    cout << "r_a = " << r_a << endl;
    a = 3;
    cout << "a = " << a << endl;//3
    cout << "r_a = " << r_a << endl;
return 0;
}

引用和类类型

#include<iostream>
using namespace std;
class A
{
public:
A(int i) : m_i(i){ }
void print()
{
    cout << "m_i = " << m_i << endl;
}
private:
    int m_i;
};
int main()
{
    A a(10);
    A& r_a = a;
    a.print();
    r_a.print();
    return 0;
}

3.引用在实际编程中的使用

(1)常引用

使用引用时,由于可以通过引用去修改变量的值,所以为了防止出现这种情况,可以将引用定义为常引 用。

定义引用时加const修饰,即为常引用,不能通过常引用修改引用的目标

const 类型 &引用名 = 变量名;
类型 const &引用名 = 变量名;
//const在&前即可

示例 对象的常引用和常对象一样,只能调用常函数

#include<iostream>
using namespace std;
class A
{
public:
    A(int i) : m_i(i){ }
void print() const
{
    cout << "m_i = " << m_i << endl;
}
void setValue(int i)
{
    m_i = i;
}
private:
    int m_i;
};
int main()
{
    A a(10);
    const A& r_a = a;
    a.setValue(20);
    a.print();
    //r_a.setValue(20);//常引用和常对象一样,只能调用常函数
    r_a.print();
    int a1 = 1;
    const int& r_a1 = a1;
    cout << r_a1 << endl;
    a1 = 2;
    cout << r_a1 << endl;
    //r_a = 3; //error 不能通过常引用修改引用的目标
    return 0;
}

普通引用只能引用左值,常引用也叫万能引用,既能引用左值,也能引用右值

左值和右值

左值:可以放在赋值运算符(=)左侧,一般普通的变量都是左值,表示了一个占据内存中某个可识别 的位置 (也就是一个地址)的对象。

        普通的变量

        赋值表达式结果

        前++,--表达式结果

右值:只能放在赋值运算符(=)右侧,一般常量都是右值,在内存中不占据位置

        常量

        大多数表达式的结果

        函数返回链式变量(将亡右值,即函数返回值)

#include<iostream>
using namespace std;
int func(void)
{
    int num = 30;
    cout << "&num = " << &num << endl;
    return num;//临时变量保存num
}
int main()
{
    //res = 临时变量
    const int& res = func();
    cout << "&res = " << &res << endl;
    cout << res << endl;//30
    int a = 3, b = 5;
    //a + b = 10;Error
    (a += b) = 10;
    cout << a << endl;//10
    ++a = 20;
    cout << a << endl;//20
    ++++++++a;//ok
    cout << a << endl;//24
    //a++ = 30;//Error
    return 0;
}

(2)引用型函数参数

将引用用于函数的参数,这时形参就是实参的别名,可以通过形参直接修改实参的值,同时避免参数值 传递过程,减小函数调用开销。

#include<iostream>
using namespace std;
void swap1(int* a, int* b)
{
    int tmp = 1;
    int* p_tmp = &tmp;
    *p_tmp = *a;
    *a = *b;
    *b = *p_tmp;
}
void swap2(int& a, int& b)
{
    int tmp = 1;
    tmp = a;
    a = b;
    b = tmp;
}
void swap3(int a, int b)
{
    int tmp = 1;
    tmp = a;
    a = b;
    b = tmp;
}
int main()
{
    int a = 3, b = 5;
    cout << "a =" << a << ", b = " << b << endl;
    //swap1(&a,&b);
    //swap2(a, b);
    swap3(a, b);
    cout << "a =" << a << ", b = " << b << endl;
    return 0;
}

引用型参数有可能意外修改实参的值,如果不希望修改实参本身,可以将形参定义为常引用,提高传参 效率的同时还可以接收常量型的实参

#include<iostream>
using namespace std;
class Student
{
public:
    Student(const string& name, int age) : m_name(name), m_age(age){}
public:
    string m_name;
    int m_age;
};
void print(const Student& s) //常引用
{
    cout << s.m_name << ',' << s.m_age << endl;
    //s.age++;//不允许通过常引用修改变量的值
}
int main()
{
    /*const*/ Student student("蔡徐坤", 18);
    print(student);
    return 0;
}

(3)引用型函数返回值

可以将函数的返回值声明为引用,避免返回值所带来的内存开销。

不要从函数中返回局部变量的引用,因为所引用的变量内存会在函数返回以后被释放,但是可以返回 成员变量,静态变量以及全局变量的引用

#include<iostream>
using namespace std;
class A
{
public:
    A(int data = 0) : m_data(data){}
int& getValue(void)
{
    return m_data;
}
//不能返回局部变量的引用
int& fun(void)
{
    int a = 123;
    return a;
}
private:
    int m_data;
};
int main()
{
    A a(100);
    //cout << a.data << endl;//100
    int b = a.getValue();
    cout << b << endl;//100
    int& c = a.fun();
    cout << c << endl;//VS不会报错 g++报错
    return 0;
}

如果一个函数返回值类型被声明为普通引用,那么该函数返回值是一个左值(函数返回值本来是右 值)

#include<iostream>
using namespace std;
class A
{
public:
    A(int data = 0) : m_data(data){}
int& getValue(void)
{
    return m_data;
}
private:
    int m_data;
};
int main()
{
    A a(100);
    //cout << a.data << endl;//100
    cout << (a.getValue())++ << endl;//100
    //cout << (a.getValue1())++ << endl;//100
    return 0;
}

如果不希望函数直接返回左值,可以返回常引用

#include<iostream>
using namespace std;
class A
{
public:
    A(int data = 0) : m_data(data){}
int& getValue(void)   
{
    return m_data;
}
const int getValue1(void)
{
    return m_data;
}
private:
    int m_data;
};
int main()
{
    A a(100);
    //cout << a.data << endl;//100
    cout << (a.getValue())++ << endl;//100
    //cout << (a.getValue())++ << endl;//error 右值不能++
    cout << a.getValue1() << endl;//101
    return 0;
}

(4)使用引用的时机(函数的参数和返回值):

        如果是基本类型,使用值传递或者指针

        如果是数组,只能以指针方式传递

        如果是结构体类型,指针、引用都可

        如果是类/对象,引用

(5)引用的注意事项

  • 避免返回局部变量的引用: 不要返回函数内部局部变量的引用,因为局部变量的生命周期在函数结 束时结束,返回对应的引用会导致悬空引用。
  • 确保引用指向有效的内存: 确保引用在其生命周期内始终指向有效的内存,避免使用悬空引用。
  • 引用作为函数返回值时的生命周期: 当函数返回引用时,确保返回的引用指向的对象在函数调用后 仍然有效。
  • 避免引用和指针混淆: 引用和指针是不同的概念,虽然它们都可以用于访问内存,但它们有着不同 的语法和语义。避免混淆引用和指针的用法。
  • 避免滥用引用: 引用是一种强大的工具,但滥用它可能导致代码的可读性和维护性下降。在确实需 要引用的情况下使用它,而不是为了避免传值而过度使用引用。

4.指针在实际编程中的使用

(1)野指针和空指针

指针没有初始化里面是一个垃圾值,称为野指针。程序里不可以出现野指针。

int *p;//野指针

把指针初始化为NULL,即为空指针

int *p = NULL;//推荐
int *q = 0;

(2)指针的大小

无论什么类型的指针得到的总是:4或8 在32位平台,

        所有的指针(地址)都是32位(4字节) 在64位平台

        所有的指针(地址)都是64位(8字节)

char ch = 'b';
int *a = NULL;
char *b = &ch;
float *c;
std::cout << "sizeof(a) = " << sizeof(a) << std::endl;
std::cout << "sizeof(b) = " << sizeof(b) << std::endl;
std::cout << "sizeof(c) = " << sizeof(c) << std::endl;

(3)通过指针修改变量的值

访问指针指向的存储空间

C++中提供了*来定义指针变量和访问指针变量指向的内存存储空间

        在定义变量的时候 * 是一个类型说明符,说明定义的这个变量是一个指针变量

        定义完指针变量后,再在指针变量前加*,表示对指针变量解引用,即获取指针变量所指向区 域的内容

int a = 5;
int *p = &a;//此处的*表示p为一个指针
std::cout << "*p = " << *p << std::endl;// 此处的*表示访问指针指向的存储空间(解引用)

通过指针变量修改指针指向的存储空间

int a = 1;
int *pa = &a;
*a = 3;
std::cout << "*pa = " << *pa << std::endl;
std::cout << "a = " << a << std::endl;

野指针和空指针不能解引用(因为没有绑定内存)

        但是可以重新绑定内存再解引用

int* p;//野指针
//*p = 1;
std::cout << *p << std::endl;
int* p1 = NULL;
*p1 = 1;
std::cout << *p1 << std::endl;
int a = 1;
p = &a;
std::cout << *p << std::endl;
p1 = &a;
std::cout << *p1 << std::endl;

(4)const修饰的指针变量

声明指针变量的时候可以使用const关键字

常量指针:声明指针变量的时候可以把const关键字写在类型名称前,不可以通过这种指针对捆绑存 储区做赋值,但是可以对这种指针本身做赋值(不可以通过指针对存储区赋值,但可以对指针的地址赋 值)

int a = 1;
int b = 2;
const int *pa = &a;//与int const *pa = &a;效果一样
*pa = 3;//error
a = 3;//right
pa = &b;//right

指针常量:声明指针变量的时候可以把const关键字写在指针变量名称前,可以通过这种指针对捆绑 存储区做赋值,但是不可以对这种指针本身做赋值(可以通过指针对存储区赋值,但不可以对指针的地 址赋值)

int a = 1;
int b = 2;
int * const pa = &a;
*pa = 3//right
pa = &b;//error

(5)数组指针

数组指针

一个变量有地址,一个数组包含若干元素,每个数组元素也有相应的地址, 指针变量也可以保存数 组元素的地址。

只要一个指针变量保存了数组元素的地址, 我们就称之为数组元素指针,即数组指针,数组指 针的本质是指针,指向数组中的某个元素的地址。

数组指针的本质是一个指针。

#include <iostream>
int main()
{
    int a[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
    int* pa = a;
    int* pb = &a[0];
    std::cout << "a = " << a << std::endl;
    std::cout << "pa = " << pa << std::endl;
    std::cout << "pb = " << pb << std::endl;
    return 0;
}//三者一致

(6)数组指针操作数组元素

由于数组名可以代表数组收元素地址,数组元素是可以通过 数组名[下标] 的格式访问,那么可以定 义一个指针来存放数组的地址,并通过 指针[下标] 的方式去访问数组元素。

#include <iostream>
int main()
{
    int a[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
    int* pa = a;
    *pa = 10;
    std::cout << "a[0] = " << a[0] << std::endl;//10
    std::cout << "pa[0] = " << pa[0] << std::endl;//10
    return 0;
}

(7)数组指针加减运算

        在指针指向数组元素时,允许以下运算:

        加一个整数(用+或+=),如p+1

        减一个整数(用-或-=),如p-1

        自加运算,如p++,++p

        自减运算,如p–,--p

地址加减整数n实际上加减的是加减n个数组元素的大小。

数组名不能参与加减计算

#include <iostream>
int main()
{
    int a[] = { 1, 2, 3, 4, 5, 6, 7 };
    int* pa = a;
    double a1[] = { 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0 };
    double* pa1 = a1;
    std::cout << "pa = " << pa << std::endl;
    std::cout << "pa1 = " << pa1 << std::endl;
    std::cout << std::endl;
    pa++;
    pa1++;
    //a++;//error 数组名不能参与加减计算
    std::cout << "pa = " << pa << std::endl;
    std::cout << "pa1 = " << pa1 << std::endl;
    std::cout << std::endl;
    pa += 2;
    //a += 2;//error
    std::cout << "pa = " << pa << std::endl;
    std::cout << std::endl;
    std::cout << "*(pa + 1) = " << *(pa + 1) << std::endl;
    std::cout << "*pa = " << *pa << std::endl;
    std::cout << "*(pa - 1) = " << *(pa - 1) << std::endl;
    std::cout << "*pa - 1 = " << *pa - 1 << std::endl;//这个不是数组元素
    std::cout << std::endl;
    return 0;
}

通过数组指针遍历数组

#include <iostream>
int main()
{
    int a[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
    int i = 0;
    int n = sizeof(a) / sizeof(a[0]);
for (i = 0; i < n; i++)
{
    std::cout << a[i] << " ";
}
std::cout << std::endl;
for (i = 0; i < n; i++)
{
    std::cout << *(a + i) << " ";//数组名+偏移量访问
}
std::cout << std::endl;
int* p = a;

//这将使指针向前移动i个位置,然后返回新的地址。
//这并不直接访问数组的特定元素,而是返回一个新地址,该地址是原始地址加上i个元素的大小。

for (int i = 0; i < sizeof(a) / sizeof(a[0]); i++)
{
    std::cout << *(p + i) << " ";//指针法
}
std::cout << std::endl;

//这是通过指针和下标索引访问数组元素的标准方法。
//在编译时,编译器知道数组的大小和类型,因此可以正确地计算出访问特定元素所需的内存地址。

for (int i = 0; i < sizeof(a) / sizeof(a[0]); i++)
{
    std::cout << p[i] << " ";//指针法
}
std::cout << std::endl;
//for (i = 0; i < n; i++, a++)//error 数组名是一个存储了数组首地址的常量,因此不能++
//{
    // std::cout << *a;
//}
    //std::cout << std::endl;
    return 0;
}

(8)函数指针

函数作为一段程序,在内存中也要占据部分存储空间,它也有一个起始地址

&不是必须的,因为函数名就表示了它的地址

函数有自己的地址,指针变量就是用来存储地址的。因此可以利用一个指针指向一个函数。其中,函数 名就代表着函数的地址。

函数指针的定义

        格式:返回值类型 (* 指针变量名)(形参1, 形参2...)

         = NULL; = NULL 可省略

#include <iostream>
int sum(int a, int b)
{
    return a + b;
}
int main()
{
    int (*p)(int, int) = NULL;//函数指针声明
    p = sum;//函数指针初始化
    int a = p(3, 5);//调用
    std::cout << "a = " << a << std::endl;//8
    return 0;
}

5.指针和引用的区别

相同点:

        如果从C的角度来看,其本质就是指针

  • 引用和指针都允许对其他变量进行间接访问,通过引用和指针可以修改或获取其他变量的值
  • 两者都可以用于函数参数传递,允许在函数内修改调用者传递的变量
  • 但是在C++中建议使用引用,而不是指针

不同点:

        指针可以不做初始化,其目标可以随便改变(指针常量除外),而引用必须初始化,而且引用目标不能被改变

  • 26
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值