第十一章 指针
11.2 指针的理解
11.2.2 变量的访问方式
内存是电脑上特别重要的存储器,计算机中程序的运行都是在内存中进行的 ,为了有
效的使用内存,就把内存划分成一个个小的内存单元,每个内存单元通常占用 1 个字节。
变量在内存中分配空间,不同类型的变量占用不同大小的空间,那如何访问内存中的变
量数据呢?有两种方式:
(1)直接访问,直接使用变量名进行的访问,以前的程序中都是采用这种方式。
(2)间接访问,通过指针来实现。
11.1.2 内存地址
为了能够有效地访问到每个内存单元,就给内存单元进行了编号,这些编号被称为内存
地址,因为每个内存单元都有地址,所以变量也是有地址的。

假设有 int 型变量 num,其在内存中会占用 4 个字节,也就是占用 4 个内存单元,第
一个内存单元的地址即是变量 num 的地址。如图;

在 32 位系统中,内存地址通常是 32 位二进制数字,即 4 个字节,这允许寻址 2^32(大
约 4GB)个内存位置。
在 64 位系统中,内存地址通常是 64 位二进制数字,即 8 个字节,这允许寻址 2^64 个内存位置,这个数量相当大,远远超过了通常需要的内存大小。
11.1.3 什么是指针
如果一个变量专门用来存放内存地址,则它称为指针变量,通常简称为指针。我们可以
通过指针间接访问内存中另一个数据。

如图,指针里面存储的是变量 num 的地址,我们可以说该指针指向变量 num,通过
该指针可以间接访问变量 num。
11.1.4 指针的定义
一般格式:
数据类型*指针变量名[=初始地址值];
数据类型是指针所指向的地址处的数据类型,如 int、char、float 等。
符号 * 用于通知系统,这里定义的是一个指针变量,通常跟在类型关键字的后面,表
示指针指向的是什么类型的值。比如,char * 表示一个指向字符的指针,float * 表示一个
指向浮点数的指针。
需要注意的是,以下三种定义指针的写法都是合法的:
int *ptr;
int* prt;
int * prt;
11.1.5 取址运算符和取值运算符
取址运算符,使用 & 符号表示,作用是取出变量的内存地址。如果要格式化输出地
址,需使用格式占位符 %p。
取值运算符,使用 * 符号表示,作用是取出指针指向的内存地址处的数据值,也称为
解引用运算符或间接引用运算符。

11.1.6 指针应用案例
(1)案例1:
创建一个 int 类型的变量,使用取址运算符取出其地址,并将其地址赋值给一个指针,
然后分别打印变量的值、变量的地址、指针的值、指针的地址、指针指向的值。
代码如下:
#include<stdio.h>
int main()
{ //定义变量
int num = 100;
//定义一个指针,指向变量num的地址
//这里使用&符号取出变量num的地址,再赋值给指针变量prt
int* ptr = #
//打印变量num的值与地址
printf("num的值是%d\n", num);
printf("num的地址是%p\n", &num);
//打印ptr的值。ptr的地址,ptr指向的值
printf("ptr的值是%p\n", ptr);//指针ptr的值与变量num的值是相同的
printf("ptr的地址是:%p\n", &ptr);
printf("ptr指向的值是%d\n", *ptr);//指针ptr指向的值就是变量num的值
return 0;
}
num的值是100
num的地址是000000A3E3CFF724
ptr的值是000000A3E3CFF724
ptr的地址是:000000A3E3CFF748
ptr指向的值是100
内存示意图:

案例二:
通过指针修改所指向的值,代码如下:
#include<stdio.h>
int main()
{
//创建double类型的变量
double num = 2.8;
//创建指针p1,指向变量num;
double* p1 = #
//创建指针p2,将p1的值赋值给p2,p1,存储的是num的值,此时p1,p2都指向num
double* p2 = p1;
//打印num的值
printf("%.2f\n", num);//2,8
//通过指针p1修改num的值,并打印
*p1 = 3.88;
printf("%.2f\n", num);
//通过指针p2修改num的值,并打印
*p2 += 10;
printf("%.2f\n", num);
return 0;
}
2.80
3.88
13.88

11.2 指针运算
11.2.1 指针加减整数
指针与整数的加减运算,表示指针所指向的内存地址的移动(加,向后移动;减,向前移动),指针移动多少,与指针指向的数据类型有关,数据类型占据多少个字节,每单位就
移动多少个字节,比如一个 int 类型指针,+1 向后移动 4 个字节,-2 向前移动 8 个字节。
数组的元素在内存中连续存储的,我们通过数组元素来演示指针加减整数的情况。
代码如下:
#include<stdio.h>
int main()
{
//创建数组
int nums[] = { 10,20,30,40,50 };
//创建指针并指向数组第一个元素的地址
int* ptr = &nums[0];
//打印指针的值和指向的值
printf("ptr=%p,*ptr=%d\n", ptr, *ptr);
//指针加3,指针指向int类型,每个占4个字节,此时指针会向后移动12个字节
ptr += 3;
printf("ptr=%p,*ptr=%d\n", ptr, *ptr);
//指针减2,指针会向前移动8个字节
ptr -= 2;
printf("ptr=%p,*ptr=%d\n", ptr, *ptr);
return 0;
}
ptr=000000164511FBA8,*ptr=10
ptr=000000164511FBB4,*ptr=40
ptr=000000164511FBAC,*ptr=20

11.2.2 指针自增自减
指针自增、自减本质上就是指针加减整数,自增地址后移,自减地址前移。下面我们利
用指针的自增自减实现数组的遍历,代码如下:
#include<stdio.h>
int main()
{
//创建数组,元素都是short类型,占2个字节
short nums[] = { 10,20,30,40,50 };
//定义常量记录数组长度
const int len = 5;
//利用指针自增自减遍历数组
//创建指针并指向第一个元素地址
short* ptr = &nums[0];
//循环
for (int i = 0; i < len; i++)
{
printf("元素索引:%d,元素地址:%p,元素值:%d\n", i, ptr, *ptr);
ptr++;//指针自增
}
printf("\n");
//利用指针再次遍历数组,此时指针超出界限,需先自减一次
for (int i = len-1; i >=0; i--)
{
ptr--;
printf("元素索引:%d,元素地址:%p,元素值:%d\n", i, ptr, *ptr);
}
return 0;
}
元素索引:0,元素地址:0000009BE2AFFA98,元素值:10
元素索引:1,元素地址:0000009BE2AFFA9A,元素值:20
元素索引:2,元素地址:0000009BE2AFFA9C,元素值:30
元素索引:3,元素地址:0000009BE2AFFA9E,元素值:40
元素索引:4,元素地址:0000009BE2AFFAA0,元素值:50元素索引:4,元素地址:0000009BE2AFFAA0,元素值:50
元素索引:3,元素地址:0000009BE2AFFA9E,元素值:40
元素索引:2,元素地址:0000009BE2AFFA9C,元素值:30
元素索引:1,元素地址:0000009BE2AFFA9A,元素值:20
元素索引:0,元素地址:0000009BE2AFFA98,元素值:10

11.2.3 同类指针相减
相同类型的指针可以进行减法运算,返回它们之间的距离,即相隔多少个数据单位。高位地址减去低位地址,返回的是正值;低位地址减去高位地址,返回的是负值。
同类型指针相减的结果是一个 ptrdiff_t 类型数据,ptrdiff_t 类型是一个带符号
的整数,格式输出中对应的格式占位符是 %td,相关案例如下:
#include<stdio.h>
int main()
{
//创建数组
int nums[] = { 10,20,30,40,50 };
//创建指针并且指向数组第一个元素的地址
int *ptr1=&nums[0];
//创建指针并且指向数组第四个元素的地址
int* ptr2 = &nums[3];
printf("ptr2-ptr1=%d\t", ptr2 - ptr1);
printf("ptr1-ptr2=%d\t", ptr2 - ptr1);
//创建两个指针进行相减
double d1 = 1.0;
double d2 = 2.0;
double* p1 = &d1, * p2 = &d2;
printf("p1-p2=%td\n", p1 - p2);//p1-p2=1
printf("p2-p1=%td\n", p2 - p1);//p2-p1=-1
return 0;
}
ptr2-ptr1=3 ptr1-ptr2=3 p1-p2=-4
p2-p1=4

11.2.4 指针的比运算
指针之间可以进行比较运算,如 ==、<、 <= 、 >、 >=,比较的是各自指向的内存地
址的大小,返回值是 int 类型整数 1 (true)或 0 (false)。案例演示如下:
#include<stdio.h>
int main()
{
//创建数组
int nums[] = { 10,20,30,40,50 };
double n = 1.0;
//创建指针并且指向数组第一个元素的地址
int* ptr1 = &nums[0];
//创建指针并且指向数组第四个元素的地址
int* ptr2 = &nums[3];
//创建指针并且也指向数组第一个元素的地址
int* ptr3 = &nums[0];
// 创建指针指向变量 n 的地址
double* ptr4 = &n;
//输出指针指向的地址
printf("ptr1=%p\n", ptr1);
printf("ptr2=%p\n", ptr2);
printf("ptr3=%p\n", ptr3);
printf("ptr4=%p\n\n", ptr4);
//进行比较
printf("ptr1>ptr2:%d\n",ptr1>ptr2);
printf("ptr1<ptr2:%d\n", ptr1 < ptr2);
printf("ptr1==ptr2:%d\n", ptr1 == ptr2);
printf("ptr4>ptr1:%d\n", ptr4 > ptr1);
return 0;
}
ptr1=000000d4385ffd10
ptr2=000000d4385ffd1c
ptr3=000000d4385ffd10
ptr4=000000d4385ffd08
ptr1>ptr2: 0
ptr1<ptr2: 1
ptr1==ptr3: 1
ptr4>ptr1: 0

11.3 指针和数组
11.3.1 数组名
数组名在大多数情况下会被隐式地转换为指向数组第一个元素的指针,在特定情况下数
组名可以被视为一个指针,具有一些指针的特性。
但是数组名与真正的指针是不同的,主要有以下几点区别:
(1)使用 sizeof 运算符,数组名得到的是整个数组的大小;指针得到的是本身的大小。
(2)数组名不能进行自增、自减运算。
(3)数组名的指向不可更改。
示例代码:
#include<stdio.h>
int main()
{
//创建数组
int nums[5] = { 10,20,30,40,50 };
//创建指针指向数组的第一个元素
int* ptr = &nums[0];
//数组名中储存了第一个元素的地址
printf("%p,%d\n", nums, *nums);
printf("%p,%d\n", ptr, *ptr);
//nums和ptr比较
if (nums==ptr)
{
printf("nums和ptr相等\n\n");
}
else
{
printf("nums和ptr不相等\n\n");
}
//数组名与真正的指针存在差别
//1.sizeof运算符返回值的是整个数组的大小,而指针返回值是本身的大小
printf("%zu,%zu", sizeof nums, sizeof ptr);
//2.数组名不能进行自增自减运算
//nums++ //报错
printf("%p,%d\n", ptr, *ptr);
//3.数组名的指向不可修改
int n = 100;
//nums=&n;//报错
ptr = &n;
printf("%p %d\n",ptr, *ptr);
return 0;
}
0000002E8E0FFAD8,10
0000002E8E0FFAD8,10
nums和ptr相等20,80000002E8E0FFAD8,10
0000002E8E0FFB24 100

11.3.2 指针数组
指针数组(Pointer Array)是一个数组,其中的每个元素都是指针。
语法规则:
数据类型 *指针数组名[长度];
示例代码:
#include<stdio.h>
int main()
{
//创建三个变量
int nums1 = 10, nums2 = 20, nums3 = 30;
//创建一个长度为3的指针数组
int* ptrArr[3];
//指针数组的每个元素指向不同的整数
ptrArr[0] = &nums1;
ptrArr[1] = &nums2;
ptrArr[2] = &nums3;
//遍历指针数组
for (int i = 0; i < 3; i++)
{
printf("%d,%p,%d\n", i, ptrArr[i], *ptrArr[i]);
}
return 0;
}
0,000000BE5B9AFC04,10
1,000000BE5B9AFC24,20
2,000000BE5B9AFC44,30

11.3.3 数组指针
数组指针(Array Pointer)是一个指针,它指向一个数组。注意,数组指针指向的是整
个数组的地址而不是第一个元素的地址,虽然二者值是相同的,但在运算中会表现出不同。
语法规则:
数据类型 (*数组指针名)[长度];
#include <stdio.h>
int main()
{
// 创建整数数组
int arr[5] = { 10, 20, 30, 40, 50 };
// 创建指针指向数组 arr &arr 表示整个数组的地址
int(*ptr)[5] = &arr;
// int (*ptr)[5] = arr; // 会有警告, arr 的类是 int *, ptr 类型是int (*ptr)[5]
// 二者值是相同的
printf("arr=%p \n", arr);
printf("ptr=%p \n\n", ptr);
// 数组指针指向的是数组的地址,而不是第一个元素的地址
// 数组指针+1 会向后移动 4*5=20 个字节;数组+1 会向后移动 4 个字节
printf("arr+1=%p \n", arr + 1);
printf("ptr+1=%p \n\n", ptr + 1);
// 使用数组指针遍历数组
for (int i = 0; i < 5; i++)
{
printf("%d \n", (*ptr)[i]);
}
return 0;
}
arr=0000008913AFF838
ptr=0000008913AFF838arr+1=0000008913AFF83C
ptr+1=0000008913AFF84C10
20
30
40
50

数组指针和数组名的区别:
(1)指向不同:数组名指向元素首地址,数组指针指向数组的地址。
(2)类型不同:上面案例中,arr 的类型是 int[5],;ptr 的类型是 int(*)[5]。
(3)可变性:数组名通常是不可变的;数组指针是可变的,你可以将它指向不同的数
组。
(4)初始化:数组名不需要显式初始化,它会自动指向数组的首元素;数组指针需要
显式初始化,指定它将指向的数组。
(5)访问元素:数组名访问数组元素不需要解引用;数组指针通常需要解引用才能访
问数组元素。
11.3.4 字符指针
(1)基本介绍
字符指针变量(简称字符指针)是 C 语言中的一种指针类型,它用于指向字符或字符
串(字符数组),通常用于处理字符串(字符数组)。
(2)用字符指针指向一个字符串
char *pStr= "hello tom";
C 语言对字符串" hello tom"是按字符数组处理的,在内存中开辟了一个字符数组用来存
放字符串,程序在定义字符串指针 pStr 时只是把字符串首地址(即存放字符串的字符数组
的首地址)赋给 pStr。

(3)字符数组名和字符指针表示字符串的区别
1)对字符数组只能对各个元素赋值,不能对字符数组名重新赋值。
char str[14];
str=" hello tom"; //错误
str[0] = 'i'; //ok
内存示意图

11.4 指针和函数
11.4.1传递指针给函数
当函数的形参类型是指针类型时,使用该函数时,需要传递指针、地址或者数组给该形
参。
(1)传地址或指针给函数
#include<stdio.h>
//函数原型
void func(int* );
int main()
{
int num=100;
int* ptr = #
//调用函数,传地址
func(&num);
printf("num=%d\n", num);
//调用函数,传指针
func(ptr);
printf("num=%d\n", num);
return 0;
}
//函数声明
void func(int* p)
{
*p += 1;
}
num=101
num=102
内存示意图

(2)传数组给函数
数组名本身就代表数组首地址,因此传数组的本质就是传地址。
示例代码:
#include<stdio.h>
//声明函数
double getAerage(int* arr, int size);
int main()
{
//带有5个元素的整形数组
int balance[5] = { 100,2,3,17,50 };
double avg;
//传递一个指向数组的指针作为参数
avg = getAerage(balance, 5);
//输出返回值
printf("Average value is: %f\n", avg);
return 0;
}
//函数定义
double getAverage(int* arr, int size)
{
int sum = 0;
double avg;
for (int i = 0; i < size; i++)
{
sum += *arr;
printf("arr存放的地址:%p\n", arr);
arr++;
}
avg = (double)sum / size;
return avg;
}
arr 存放的地址:000000f47adff850
arr 存放的地址:000000f47adff854
arr 存放的地址:000000f47adff858
arr 存放的地址:000000f47adff85c
arr 存放的地址:000000f47adff860
Average value is: 214.400000

问:如果在 getAverage()函数中,通过指针修改了数组的值,那么 main 函数中的 balance
数组的值是否会相应变化?
答:会的,因为 getVerage 函数中的指针,指向的就是 main 函数中的 balance 数组。
11.4.2 指针函数(返回指针的函数)
1)基本介绍和使用
如果函数的返回值是一个指针,这样的函数称为指针函数。
语法规则:
返回类型 *指针函数名(参数列表)
请编写一个函数 strlong(),返回两个字符串中较长的一个。
#include <stdio.h>
#include <string.h>
char* strlong(char* str1, char* str2)
{
if (strlen(str1) >= strlen(str2))
{
return str1;
}
else
{
return str2;
}
}
int main()
{
char str1[30], str2[30], * str;
printf("\n 请输入第 1 个字符串:");
scanf("%s", &str1);
printf("\n 请输入第 2 个字符串:");
scanf("%s", &str2);
str = strlong(str1, str2);
printf("\nLonger string: %s \n", str);
return 0;
}
value=100
(3)案例:编写一个函数,它会生成 10 个随机数,并使用数组名作为返回值。
说明:生成随机数可以使用 rand() 函数,由 C 语言标准库 <stdlib.h> 提供,可
以返回一个随机整数。
#include <stdio.h>
#include <stdlib.h>
// 函数返回一个数组
int* randArr()
{
static int arr[10]; // 必须加上 static ,让 arr 的空间在静态数据区分配
for (int i = 0; i < 10; i++)
{
arr[i] = rand();
}
return arr;
}
int main()
{
int* p = randArr(); // p 指向是在 f1 生成的数组的首地址(即第一个元素的地址)
for (int i = 0; i < 10; i++)
{
printf("%d\n", *(p++));
}
return 0;
}
41
18467
6334
26500
19169
15724
11478
29358
26962
24464
11.4.3 函数指针(指向函数的指针)
一个函数总是占用一段连续的内存区域,函数名在表达式中有时也会被转换为该函数所
在内存区域的首地址,这和数组名非常类似。
把函数的这个首地址(或称入口地址)赋予一个指针变量,使指针变量指向函数所在的
内存区域,然后通过指针变量就可以找到并调用该函数,这种指针就是函数指针。
语法规则:
返回类型 (*函数指针名)(参数列表);
注意:()的优先级高于*,第一个括号不能省略,如果省略,就成了指针函数。
示例代码:
用函数指针来实现对函数的调用,返回两个整数中的最大值。
#include <stdio.h>
// 返回两个数中较大的一个
int max(int a, int b)
{
return a > b ? a : b;
}
int main()
{
int x, y, maxVal;
// 定义函数指针
// int (*pmax)(int x, int y) = max;
int (*pmax)(int, int) = max; // 可以省略参数名
printf("Input first number:");
scanf("%d", &x);
printf("Input second number:");
scanf("%d", &y);
// 通过函数指针调用函数
maxVal = (*pmax)(x, y);
printf("Max value: %d\n", maxVal);
printf("pmax’s value is %p, pmax’s address is %p\n", pmax, &pmax);
printf("max’s value is %p, max’s address is %p\n", max, &max);
return 0;
}

1379

被折叠的 条评论
为什么被折叠?



