前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >C/C++开发基础——指针与引用

C/C++开发基础——指针与引用

作者头像
Coder-Z
发布2023-10-06 14:50:11
1560
发布2023-10-06 14:50:11
举报

一,关于指针

1.指针的基础概念

指针是可存储地址的变量,存储在指针中的地址可以是变量或者其他数据的地址。

指针不仅仅是指向某地址,指针还关注指向该地址的数据类型。

例如:long* num_ptr {};

这里的num_ptr指针今后只能存储long类型的变量地址,尝试用它存储非long类型的变量地址将会产生编译报错。

注意,无论指针变量指向什么类型或者大小的数据,指针变量本身的大小是相同的,指针变量的大小仅仅取决于目标平台的可寻址内存的大小。例如,对于64位的计算机架构,指针变量的大小一般是8个字节。

2.指针的定义和使用

a.指针的初始化

未初始化的指针使用起来风险很大,因此,定义指针的同时必须初始化,如果不知道指定什么样的初始值,可以先初始化为nullptr。

因此,下面这段代码:

代码语言:javascript
复制
long* num_ptr {};

应该改为:

代码语言:javascript
复制
long* num_ptr {nullptr};

也可以使用自定义类型来初始化指针:

代码语言:javascript
复制
char16_t* char_ptr {nullptr};

b.指针的具体使用

(1)指针赋值

对指针变量使用操作符"="会改变指针的指向,所以,对指针采取赋值操作可以理解为指针方向的重定向。

例如,如果p1和p2是两个指针变量,"p2=p1"操作会让p2去指向p1当前正在指向的内存地址。

(2)指针的算术运算

算术运算的本质是让指针沿着一定的方向去移动指定大小的单位。

拿指针的加法运算举例,整数会先和指针所指向的类型大小(单位是字节)相乘,得到偏移量,然后指针的初始地址按照这个偏移量往前移动一定的单位。

3.地址运算符&

地址运算符"&"可以获取变量的内存地址,常用于指针变量的初始化。

&可以应用在任何类型的变量上面,然后用变量对应的指针类型去存储变量的地址。

例如要获得int类型变量的地址,则存储地址的指针必须也是int类型。

代码语言:javascript
复制
int num=10;
int* p_num=#
auto p_num=# //也可以让编译器判断类型
auto* p_num=# //auto*,让代码更清晰

4.解引用运算符*

间接运算符"*"可以获取内存地址所存储的对应的变量值。

因为"*"是通过内存地址来间接获取变量的值,所以称为间接运算符,通常也称它为解引用运算符。

符号"*"有时候是用来声明指针的,有时候是用来解引用的。

当它和类型放在一起,例如"int*",便是声明指针的;

当它和变量放在一起(前面没有加类型或者auto),例如"*p_value",便是解引用的。

下面这段处理逻辑相当于:"data_2 = data_1"。

代码语言:javascript
复制
ptr = &data_1;
data_2 = *ptr;

5.char类型与指针

代码样例:

代码语言:javascript
复制
char* char_ptr {"Hello"};

初始化char*类型指针的示意图:

在C语言的写法中,char数组不能被改变,因此在C++的初始化代码中,需要在char*前面加const修饰符,避免编译报错。

代码语言:javascript
复制
const char* char_ptr {"Hello"};

注意,指向数值类型的指针必须解引用,才能拿到指针所指向的元素值。但是指向char类型的指针,可以不经过解引用,直接利用指针名获得元素的值。有时候,为了让代码更清晰,也会对char类型的指针做解引用操作。

完整C++代码实现:

代码语言:javascript
复制
#include <iostream>
using namespace std;
int main()
{
       const char* char_ptr{ "Hello" };

       std::cout << char_ptr[0] << std::endl;
       std::cout << *char_ptr << std::endl;
       std::cout << char_ptr[1] << std::endl;
       std::cout << *(char_ptr + 1) << std::endl;
       return 0;
}

运行结果:

代码语言:javascript
复制
H
H
e
e

6.数组与指针

a.指向数组的指针

该指针指向数组中的第一个元素。

当程序使用new分配一段内存块时,应使用delete来释放。但是当使用new创建数组时,应该使用"delete []"来释放数组。

代码语言:javascript
复制
int* p_array = new int [10];
delete [] p_array;

此时,访问该数组也很简单,可以把指针变量名当作数组名来使用。

完整C++代码实现:

代码语言:javascript
复制
#include <random>
#include <iostream>
#include <memory>
#include <functional>
int main()
{
       int* p_array = new int[10];

       p_array[0] = 11;
       p_array[1] = 22;

       std::cout << &p_array << std::endl;
       std::cout << p_array << std::endl;
       std::cout << p_array+1 << std::endl;
       //指针变量当数组来用
       std::cout << p_array[0] << std::endl;
       std::cout << p_array[1] << std::endl;
       //对数组指针解引用
       std::cout << *p_array << std::endl;
       std::cout << *(p_array+1) << std::endl;
       delete [] p_array;
}

运行结果:

代码语言:javascript
复制
006CFCC4
00BEC170
00BEC174
11
22
11
22

b.指针数组

指针与数组还可以形成另一种结构,被称为指针数组,数组的元素都是指针类型。指针数组的使用可以节省操作时间,如果要交换数组中的元素,只需要交换彼此的指针就可以实现,避免了很多复制操作。

如图,基于指针数组实现的二维数组:

完整C++代码实现:

代码语言:javascript
复制
#include <iostream>
using namespace std;
int main()
{
       int N = 3;
       int** p = new int* [N];
       int x = 1;
       for (int i = 0; i < N; i++) {
              p[i] = new int[N];
              for (int j = 0; j < N; j++, x++) {
                      p[i][j] = 10 * x;
              }
       }
       cout << *p << endl;
       cout << **p << endl;
       cout << *p + 1 << endl;
       cout << **p + 1 << endl;
       cout << *(*(p + 1) + 0) << endl;
       cout << p[2][2] << endl;
       return 0;
}

运行结果:

代码语言:javascript
复制
015420A8
10
015420AC
11
40
90

7.const与指针

const与指针结合使用,分下面三种情况:

(1)指向常量的指针——存储的值为常量,指针为变量。

指针所指向的常量值不可以被修改,但是指针可以被修改为指向其他常量的地址。

此时的指针常用来指向const类型的常量。

代码语言:javascript
复制
const int value {20};
const int* p_value=&value;  //const类型的常量value

此时value是一个常量,它的大小不能被修改,但是可以修改p_value指向的地址,例如:

代码语言:javascript
复制
const int value_2 {30};
p_value=&value_2;

如果要禁止修改p_value指向的地址,可以在p_value前面再加一个const修饰符。

代码语言:javascript
复制
const int* const p_value=&value;

(2)常量指针——存储的值为变量,指针为常量。

常量指针只能指向初始化时指定的固定地址,此时虽然指针指向的地址不可以被修改,但是地址存放的变量值可以被修改。

此时的指针常用来指向非const类型的变量。

代码语言:javascript
复制
int data {20}; //此时不能用const
int* const p_data=&data;
*p_data = 30;      //data的值被修改为30

(3)指向常量的常量指针——存储的值为常量,指针为常量

此时,指针被声明为常量,且指向的是不可被修改的常量。指针指向的地址,地址存放的值,都不可以被修改。

代码语言:javascript
复制
const float value {3.1415f};
const float* const p_value=&value;

完整C++代码实现:

Demo1:

代码语言:javascript
复制
#include <iostream>
int main()
{
    int x{ 5 };
    int y{ 6 };
    int* const ptr{ &x };
    ptr = &y;
    return 0;
}

运行结果:

代码语言:javascript
复制
编译报错:
“ptr”: 不能给常量赋值

Demo2:

代码语言:javascript
复制
#include <iostream>

int main()
{
    int x{ 5 };
    int* const ptr{ &x };

    std::cout << x << std::endl;
    std::cout << &x << std::endl;

    *ptr = 6; //x的地址不变,值被改为6
    std::cout << x << std::endl;
    std::cout << &x << std::endl;

    return 0;
}

运行结果:

代码语言:javascript
复制
5
004FFA60
6
004FFA60

二,关于引用

1.引用的基础概念

引用的用法和指针类似,引用可以看作是某变量的别名,变量的引用在用法上和变量完全对等。

2.引用和指针的不同点

1.引用被声明时必须被初始化。 一般用另外一个变量来初始化引用,使引用成为该变量的别名。

2.引用不能在中途被修改为指向别的变量,一旦引用被初始化为某个变量的别名,那么在这个引用的生命周期内,将一直引用该变量。

引用和指针的使用,在很多场景下减少了拷贝的操作次数,增加了代码的运行效率。

3.引用的初始化

引用的定义方式有些类似于指针,指针在定义的时候,是类型名+"*",引用在定义的时候,是类型名+"&"。

关于"&"的位置,指针初始化时,"&"放在赋值语句右边;引用初始化时,"&"放在赋值语句左边。

引用可以和原始变量保持一样的使用方式,不需要解引用。

代码语言:javascript
复制
int value_1 {20};
int value_2 {20};
int* p_value=&value_1;  //指针的初始化
int& r_value=value_2;   //引用的初始化

*p_value += 10; //解引用
r_value += 10;  //没有解引用

注意,引用中的"&"也被当作是地址运算符,"r_value"是变量value_2的别名,"&r_value"是指向变量value_2的地址。

完整C++代码实现:

代码语言:javascript
复制
#include <random>
#include <iostream>
#include <memory>
#include <functional>
int main()
{
       int value_1{ 20 };
       int value_2{ 20 };
       int* p_value = &value_1;  //指针的定义
       int& r_value = value_2;   //引用的定义
       *p_value += 10; //解引用
       r_value += 10;  //没有解引用
       std::cout <<"value_1: " << value_1 << std::endl;
       std::cout <<"value_2: " << value_2 << std::endl;
       std::cout <<"data of value_1: " << *p_value << std::endl;
       std::cout <<"address of value_1: " << p_value << std::endl;
       std::cout <<"data of value_2: " << r_value << std::endl;
       std::cout <<"address of value_2: "  << &r_value << std::endl;
}

运行结果:

代码语言:javascript
复制
value_1: 30
value_2: 30
data of value_1: 30
address of value_1: 0048FAE8
data of value_2: 30
address of value_2: 0048FADC

4.函数的引用传参

函数传参有两种方式:

1,按值传递:

传参样例:funtion_name(int param1)

传递的是值,实际上传入的是原始变量的一个副本,因此不会修改原始变量的值。

2,按引用传递:

传参样例:funtion_name(int& param2)

传递的是引用,实际上传入的是指向原始变量的一个指针,因此会修改原始变量的值。

因此,引用传参的主要目的有:

为了在调用函数的时候,顺带修改原始变量的值。

为了在调用函数的时候,减少变量副本的生成。

5.函数的const引用传参

很多开发场景经常这样使用,函数在按引用传递参数的同时,加入了const修饰符。

传参样例:funtion_name(const int& param3)

const引用传参的特点:

1.向函数传入的是指向原始变量的一个指针,避免了原始变量的副本生成。

2.利用const修饰符,不允许修改原始变量。

const引用传参的主要目的是为了提升代码效率,因为它既不会像按值传递那样,会拷贝一个副本出来,也不会像按引用传递那样,原始变量值会在函数调用期间被任意修改。

完整C++代码实现:

代码语言:javascript
复制
#include <iostream>
using namespace std;

void swap_1(int& x, int& y);
void swap_2(const int& x, const int& y);

int main() {
    int a = 100;
    int b = 200;

    cout << "Before swap_1, value of a :" << a << endl;
    cout << "Before swap_1, value of b :" << b << endl;

    swap_1(a, b);
    cout << "After swap_1, value of a :" << a << endl;
    cout << "After swap_1, value of b :" << b << endl;

    swap_2(a, b);
    cout << "After swap_2, value of a :" << a << endl;
    cout << "After swap_2, value of b :" << b << endl;
    return 0;
}

void swap_1(int& x, int& y) {
    int temp;
    temp = x;
    x = y;
    y = temp;

    return;
}

void swap_2(const int& x, const int& y) {
    int temp;
    temp = x;
    /*
    这样操作会产生编译报错
    x = y;
    y = temp;
    */
    return;
}

运行结果:

代码语言:javascript
复制
Before swap_1, value of a :100
Before swap_1, value of b :200
After swap_1, value of a :200
After swap_1, value of b :100
After swap_2, value of a :200
After swap_2, value of b :100

三,参考阅读

《Beginning C++17, 5th Edition》

《C++ Primer Plus》

《C++高级编程》

https://en.cppreference.com/w/cpp/language/pointer

https://cplusplus.com/doc/tutorial/pointers/

https://www.programiz.com/cpp-programming/pointers-arrays

https://www.geeksforgeeks.org/creating-array-of-pointers-in-cpp/

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2023-03-02 08:30:00,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 程序员与背包客 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档