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

本章主要内容:

一,关于指针

    1.指针的基础概念

    2.指针的定义和使用

    3.地址运算符&

    4.解引用运算符*

    5.char类型与指针

    6.数组与指针

    7.const与指针

二,关于引用

    1.引用的基础概念

    2.引用和指针的不同点

    3.引用的初始化

    4.函数的引用传参

    5.函数的const引用传参

三,参考阅读



一,关于指针

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

1.指针的基础概念

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

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

例如:long* num_ptr {};

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

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


2.指针的定义和使用

a.指针的初始化

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

因此,下面这段代码:
long* num_ptr {};

应该改为:

long* num_ptr {nullptr};

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

char16_t* char_ptr {nullptr};
b.指针的具体使用
(1)指针赋值

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

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

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

(2)指针的算术运算

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

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

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

3.地址运算符&

地址运算符”&”可以获取变量的内存地址,常用于指针变量的初始化。
&可以应用在任何类型的变量上面,然后用变量对应的指针类型去存储变量的地址。
例如要获得int类型变量的地址,则存储地址的指针必须也是int类型。
int num=10;
int* p_num=#
auto p_num=# //也可以让编译器判断类型
auto* p_num=# //auto*,让代码更清晰

4.解引用运算符*

间接运算符”*”可以获取内存地址所存储的对应的变量值。
因为”*”是通过内存地址来间接获取变量的值,所以称为间接运算符,通常也称它为解引用运算符。
符号”*”有时候是用来声明指针的,有时候是用来解引用的。
当它和类型放在一起,例如”int*”,便是声明指针的;
当它和变量放在一起(前面没有加类型或者auto),例如”*p_value”,便是解引用的。
下面这段处理逻辑相当于:”data_2 = data_1″。
ptr = &data_1;
data_2 = *ptr;

5.char类型与指针

代码样例: 
char* char_ptr {"Hello"};
初始化char*类型指针的示意图: 

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

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

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

完整C++代码实现: 

#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;
}

运行结果:

H
H
e
e

6.数组与指针

a.指向数组的指针

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

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

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

int* p_array = new int [10];
delete [] p_array;

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

完整C++代码实现: 

#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;
}

运行结果: 

006CFCC4
00BEC170
00BEC174
11
22
11
22
b.指针数组

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

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

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

完整C++代码实现: 
#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;
}
运行结果:
015420A8
10
015420AC
11
40
90

7.const与指针

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

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

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

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

const int value {20};
const int* p_value=&value;  //const类型的常量value

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

const int value_2 {30};
p_value=&value_2;

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

const intconst p_value=&value;
(2)常量指针——存储的值为变量,指针为常量。

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

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

int data {20}; //此时不能用const
intconst p_data=&data;
*p_data = 30;      //data的值被修改为30

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

此时,指针被声明为常量,且指向的是不可被修改的常量。指针指向的地址,地址存放的值,都不可以被修改。
const float value {3.1415f};
const floatconst p_value=&value;

完整C++代码实现: 

Demo1:

#include <iostream>
int main()
{
    int x{ 5 };
    int y{ 6 };
    intconst ptr{ &x };
    ptr = &y;
    return 0;
}

运行结果:

编译报错:
“ptr”: 不能给常量赋值

Demo2:

#include <iostream>

int main()
{
    int x{ 5 };
    intconst 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;
}

运行结果:

5
004FFA60
6
004FFA60

二,关于引用

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

1.引用的基础概念

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


2.引用和指针的不同点

1.引用被声明时必须被初始化。 一般用另外一个变量来初始化引用,使引用成为该变量的别名。
2.引用不能在中途被修改为指向别的变量,一旦引用被初始化为某个变量的别名,那么在这个引用的生命周期内,将一直引用该变量。
引用和指针的使用,在很多场景下减少了拷贝的操作次数,增加了代码的运行效率。

3.引用的初始化

引用的定义方式有些类似于指针,指针在定义的时候,是类型名+”*”,引用在定义的时候,是类型名+”&”。
关于”&”的位置,指针初始化时,”&”放在赋值语句右边;引用初始化时,”&”放在赋值语句左边。
引用可以和原始变量保持一样的使用方式,不需要解引用。
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++代码实现:
#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;
}

运行结果:

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++代码实现: 

#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;
}
运行结果:
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

三,参考阅读

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

《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/

原文始发于微信公众号(程序员与背包客):C/C++开发基础——指针与引用

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/128570.html

(0)
小半的头像小半

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!