一、取地址运算符

& 称为取地址运算符或引用,可以取地址,在其后跟一个变量名,表示取该变量在内存中的起始地址,地址一般以十六进制表示,通过地址结合指针,我们可以间接访问或修改变量的值。

例如,&a 表示获取变量 a 在内存中的地址。

例如,下面代码定义了两个 int 类型变量 a 和 b ,分别输出变量在内存中的地址:

#include <iostream>
using namespace std;

int main() 
{
    int a = 10;
    int b = 100;
    cout << a << endl << &a << endl;
    cout << b << endl << &b << endl;
    return 0;
}

运行结果如下,由于a 和 b 都是 int 类型(通常占 4 个字节)。开头的 0x 是十六进制的明确标识,地址相差 4(十六进制中 4 - 0 = 4),正好对应一个 int 类型的字节大小,说明这两个变量在内存中是连续存储的:
在这里插入图片描述

二、指针变量

(一)指针变量的声明

指针是一个特殊的变量,存储的不是普通的数据(如整数、字符等),而是目标变量的内存地址。声明的格式如下:

数据类型 *指针变量名;

这里的数据类型表示该指针所指向目标变量的数据类型。在声明变量时,* 表示 “这是一个指针变量”,例如 * a 代表它是一个指针变量。

int* a;     // 声明一个指向int类型的指针变量a
char* p;     // 声明一个指向char类型的指针变量p
float *x, *y;     // 声明两个指向float类型的指针变量x和y

要注意,* 的优先级低于逗号,所以声明多个指针变量时,不能这样声明: float * x, y;

(二)指针变量的初始化

声明指针变量后,需要对其进行初始化,否则将是一个野指针(指向不确定的内存地址)。

我们可以将其指向一个已经定义的变量,由于不知道该定义变量的地址,可以首先通过 & 来获取变量地址,然后将其赋值给指针,如下:

int x = 10;   // 定义一个int变量x,值为10
int* a = &x;  // 定义指针a,指向x的地址

通过 & 取地址,来获取变量 x 的内存起始地址。【之后 a 存储的是 x 的地址】
这里两行代码以下面这张图理解,假如 0x1234 是其地址:

在这里插入图片描述

(三)解引用运算符

通过指针间接访问其指向的变量的值,称为解引用,解引用运算符也是 * 符号实现,格式如下:

*指针变量名

例如,访问 a 指向的值并输出,然后再通过指针修改 a 的值,

cout << *a;
*a = 20; 

完整代码如下:

#include <iostream>
using namespace std;

int main() 
{
    int x = 10;   // 定义一个int变量x,值为10
    int* a = &x;  // 定义指针a,指向x的地址
    cout << *a << endl;   // 解引用a,访问a指向的变量x的值,输出:10
    *a = 20;      // 通过解引用修改指向的变量的值,此时x的值变为20
    cout << x << endl;    // 输出:20
}

在这里插入图片描述
再举一个例子,如下代码:

int* p,a = 5;
p = &a;
int* q,b = 10;
q = &b;

相当于此时,*p 的值为5,*q 的值为10。
例如我们进行以下操作:

c = *p + 1;
d = *q - 2;

另外,如果有多层指针,一层指针只修改变量,二层指针可以修改另外一个指针的指向:

int a = 10;
int *p = &a; // 存a的地址
int **pp = &p; // 存p的地址
cout << a << " " << *p << " " << **pp;  // 输出 10 10 10

三、指针与数组

1、获取数组中某个特定元素的地址
例如,我们可以通过取地址符 & 获取数组中某个特定元素的地址,并将其赋值给一个指针。

&数组名[数字]      // 该元素在内存中的地址

例如,下面代码定义了一维数组:

#include <iostream>
using namespace std;

int main() 
{
    int arr[6] = {0, 10, 20, 30, 40, 50};
    int* p = &arr[3];    // 通过 &arr[0] 获取数组第一个元素的地址,并赋值给指针 p

    cout << "arr[3] 的值: " << arr[3] << endl;
    cout << "arr[3] 的地址: " << &arr[3] << endl;
    cout << "指针 p 存储的地址: " << p << endl;
   
    return 0;
}

在这里插入图片描述

2、获取指针指定的变量的值
例如,下面代码定义了一维数组,通过使用 *p 访问 p 指向的变量的值:

#include <iostream>
using namespace std;

int main() 
{
    int arr[6] = {0, 10, 20, 30, 40, 50};
    int* p = &arr[3];    // 通过 &arr[0] 获取数组第一个元素的地址,并赋值给指针 p

    cout << "arr[3] 的值: " << arr[3] << endl;
    cout << "arr[3] 的地址: " << &arr[3] << endl;
    cout << "指针 p 存储的地址: " << p << endl;
    cout << "通过 p 解引用访问的值: "  << *p << endl;
   
    return 0;
}

也就是指针 p 存储的是地址,*p是对指针的解引用,得到该地址上存储的值:
在这里插入图片描述
3、指针算术移动指针,指向数组中的其他元素
当我们将一个整数 i 加到一个指针上时,例如 :

p + i  //指针向后移动 i 个元素的位置

此时,指针并不是简单地增加 i 个字节,而是增加 i * sizeof(指针指向的数据类型) 个字节,从而指针可以方便地在一个连续的数据块(如数组)中移动。

&数组名[某数字+i]    //数组中某个元素向后移动 i 个位置后的元素的地址

例如,下面代码,执行p++。也就是 p=p+1,指针向后移动 1 个元素的位置,也就是向后移动一个 int 类型的大小(注:已经定义数据类型为 int):

#include <iostream>
using namespace std;
int main() 
{
    int arr[6] = {0, 10, 20, 30, 40, 50};
    int* p = &arr[0];

    cout << "p 指向的地址(arr[0]):" << p << endl;
    cout << "p 指向的值:" << *p << endl;

    p++; // 指针向后移动一个 int 的大小(通常是 4 个字节)
    
    cout << "p++ 后指向的地址 (arr[1]): " << p << endl;
    cout << "p++ 后指向的值: " << *p << endl;
    return 0;
}

地址进行了改变,p = p + 1,指针向后移动一个 int 的大小(通常是 4 个字节),同时进行 *p,也可以发现改变:
在这里插入图片描述

四、指针与字符串

首先,要注意 C 风格字符串中,以一个隐藏的空字符 ‘\0’ 作为结束标志:

char str[] = "hello";

如下图:
在这里插入图片描述
通过& 字符数组名 [数字],可以让一个字符指针指向字符串(字符数组)中的特定字符:

#include <iostream>
#include <cstring>
using namespace std;

int main() 
{
    char str[] = "hello";
    char* p = &str[1]; 
    
    cout << "指针 p 指向的地址: " << (void*)p << endl;  // (void*)p是为了让 cout 输出地址而不是把它当作字符串来打印,输出 0x101
    cout << "指针 p 指向的字符: " << *p << endl;        // 输出 'e'
    
    return 0;
}

在这里插入图片描述
和数组一样,通过解引用字符,获取它所指向的具体字符:

cout << "*(p + 0) 的值: " << *(p + 0) << endl; 

移动指针,字符指针加上一个整数 i,会指向当前位置往后的第 i 个字符的地址:

#include <iostream>
#include <cstring>
using namespace std;

int main() 
{
    char str[] = "hello";
    char* p = &str[1]; 
    
    cout << "指针 p 指向的地址: " << (void*)p << endl;  // (void*)p是为了让 cout 输出地址而不是把它当作字符串来打印,输出 0x101
    cout << "指针 p 指向的字符: " << *p << endl;        // 输出 'e'

    char* q = p + 2;     // q 指向 p 后面第 2 个字符的地址
    cout << "q 指向的地址: " << (void*)q << endl;
    cout << "q 指向的字符: " << *q << endl;        // 输出 'l'
    return 0;
}

在这里插入图片描述

指针应用场景

两个变量交换

可以通过指针来交换两个变量的值:

void swap_num(int* a, int* b) 
{ 
    int temp = *a; // 解引用 a,获取它指向的值
    *a = *b;       // 解引用 b,将其值赋给 a 指向的变量
    *b = temp;     // 将临时变量的值赋给 b 指向的变量
}

如下代码:

#include <iostream>
using namespace std;

void swap_num(int* a, int* b) 
{ 
    int temp = *a;
    *a = *b;      
    *b = temp;    
}

int main() 
{
    int x = 10, y = 20;
    cout << "交换前: x = " << x << ", y = " << y << endl;
    // 传递变量的地址给函数
    swap_num(&x, &y); 
    cout << "交换后: x = " << x << ", y = " << y << endl;
    return 0;
}

在这里插入图片描述
在 C++ 中,对于交换场景,更推荐使用“&”,因为它语法更简洁,且同样能达到传递地址的效果,同时更安全。

#include <iostream>
using namespace std;

void swap_num(int& a, int& b)   // 使用引用作为参数
{ 
    int temp = a;
    a = b;
    b = temp;
}

int main() 
{
    int x = 10, y = 20;
    cout << "交换前: x = " << x << ", y = " << y << endl;
    swap_num(x, y);    // 直接传递变量名
    cout << "交换后: x = " << x << ", y = " << y << endl;
    return 0;
}

遍历数组

例如,下面代码,是定义了一个一维数组,然后通过指针遍历数组,输出地址和值:

#include <iostream>
using namespace std;
int main() 
{
    int arr[6] = {0, 10, 20, 30, 40, 50};
    int num = sizeof(arr) / sizeof(arr[0]);    // 计算数组元素个数
    
    // 使用指针遍历数组
    int* p = arr;     // p 指向数组开头,和 int* p = &arr[0] 一样;
    for (int i = 0; i < num; i++) {
        cout << "arr[" << i << "] = " << p[i] << " (地址: " << p + i << ")" << endl;    // p[i] 或者 *(p + i) 都可以
    }
    
    // 移动指针
    p = arr;     // 重置 p 到数组开头,和 p=&arr[0] 一样
    for (int i = 0; i < num; i++) {
        cout << "当前指针指向的值: " << *p << endl;
        p++;    // 指针向后移动一位
    }
    
    return 0;
}
Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐