云计算百科
云计算领域专业知识百科平台

第9章 指针

9.1 指针的理解

9.1.1 变量的访问方式

1、直接访问,直接通过变量名进行访问
2、间接访问,通过指针进行访问

9.1.2 内存地址

为了能够有效地访问到每个内存单元,就给内存单元进行了编号,这些编号被称为内存地址

32位系统,内存地址通常是32位二进制数字,即4个字节,最大寻址232
64位系统,内存地址通常是64位二进制数字,即8个字节,最大寻址264

9.1.3 指针的定义

如果一个变量专门用来存放内存地址,则称为指针变量,即指针,指针的存储大小通常与系统的内存地址大小有关,64位系统指针的存储大小为8字节

数据类型 *指针变量名
int *ptr;
int* ptr;
int * ptr;

取址运算符:‘&’ ,取出变量的内存地址,对应的格式占位符 %p
取值运算符:‘*’ ,取出指针指向的内存地址里的值,用内存地址里的值对应的格式占位符

//定义int类型的变量
int num = 5;
//定义此变量对应的指针
int *ptr = #
//通过变量名直接访问变量的值和地址
printf("num的值 = %d \\n" , num);//5
printf("num的地址: %p \\n" , &num);//存放变量num的内存地址
//通过指针间接访问变量的值和地址
printf("ptr指针的值 = %p \\n" , ptr);//存放变量num的内存地址
printf("ptr指向的值: %d \\n" , *ptr);//变量num的值:5
printf("指针ptr的地址: %p \\n" , &ptr);//指针也有一个自己的存放地址
//通过变量名修改变量值
num = 10;
*ptr = 15;

9.2 指针运算

9.2.1 指针加减整数

指针与整数的加减运算,表示指针所指向的内存地址的移动(加:向后移动;减:向前移动),指针移动多少和所指向的数据类型有关,int类型指针+1向后移动4个字节

//数组在内存中是连续存储
int nums[] = {10, 20, 30, 40, 50};
//定义指针,指向数组的首元素
int *ptr = &nums[0];
printf("ptr的值: %p \\n" , ptr);//元素10的内存地址
printf("ptr指向的值: %d \\n" , *ptr);//10
// 指针加减整数 ptr + 1
printf("ptr+1的值: %p \\n" , ptr+1 );//元素20的内存地址
printf("ptr+1指向的值: %d \\n" , *(ptr+1) );//20
ptr += 2;
printf("ptr的值: %p \\n" , ptr);//元素30的内存地址
printf("ptr指向的值: %d \\n" , *ptr);//30
ptr -= 1;
printf("ptr的值: %p \\n" , ptr);//元素20的内存地址
printf("ptr指向的值: %d \\n" , *ptr);//20

9.2.2 指针自增自减

指针自增、自减本质上就是指针加减整数,自增地址后移,自减地址前移。

// 指针自增自减
// 定义double类型的数组
int len = 5;
double nums[] = {10.1, 20.2, 30.3, 40.4, 50.5};
// 定义指针,指向数组首元素
double *ptr = &nums[0];
printf("ptr的值: %p \\n" ,ptr );//元素10.1的内存地址
printf("pter指向的值: %.2lf \\n" , *ptr);//10.1
//指针++
ptr++;
printf("ptr的值: %p \\n" ,ptr );//元素20.2的内存地址
printf("pter指向的值: %.2lf \\n" , *ptr);//20.2
//指针–
ptr;
printf("ptr的值: %p \\n" ,ptr );//元素10.1的内存地址
printf("pter指向的值: %.2lf \\n" , *ptr);//10.1
// 通过指针自增自减遍历数组
// 首元素
double *ptr_index = &nums[0];
printf("\\n\\n正序遍历数组:\\n");
for( int i = 0 ; i < len ; i++)
{
printf("%.2lf " , *ptr_index);
ptr_index ++;
}
printf("\\n\\n倒序遍历数组:\\n");
for( int i = 0 ; i < len ; i++)
{
ptr_index;
printf("%.2lf " , *ptr_index);
}

9.2.3 同类型指针相减

相同类型的指针可以进行减法运算,返回它们之间的距离,即相隔多少个数据单位。高位地址减去低位地址,返回的是正值;低位地址减去高位地址,返回的是负值。

同类型指针相减的结果是一个 ptrdiff_t 类型数据,ptrdiff_t 类型是一个带符号的整数,格式输出中对应的格式占位符是%td

// 同类型指针相减
int nums[] = {10, 20, 30, 40, 50};
//定义指针,指向首元素
int *ptr_first = &nums[0];
printf("ptr_first的值: %p \\n" , ptr_first );//元素10的内存地址
//定义指针,指向第三个元素
int *ptr_third = &nums[2];
printf("ptr_third的值: %p \\n" , ptr_third );//元素30的内存地址
// ptr_third – ptr_first
ptrdiff_t tf = ptr_third ptr_first ;
printf("ptr_third – ptr_first的值: %td \\n" , tf); // 2
// ptr_first – ptr_third
ptrdiff_t ft = ptr_first ptr_third ;
printf("ptr_first – ptr_third的值: %td \\n" , ft); // -2

9.2.4 指针的比较运算

指针之间可以进行比较运算,如 ==、<、 <= 、 >、 >=,比较的是各自指向的内存地址的大小,返回值是 int 类型整数 1(true)或 0 (false)

//指针比较大小
int nums[] = {10, 20, 30, 40, 50};//数组在内存中是连续存储的
//定义指针,指向首元素
int *ptr_first = &nums[0];
int *ptr_first1 = &nums[0];
//定义指针,指向尾元素
int *ptr_last = &nums[4];
printf("ptr_first > ptr_last ? %d \\n" , ptr_first > ptr_last);//内存地址10在50前面:0
printf("ptr_first < ptr_last ? %d \\n" , ptr_first < ptr_last);//内存地址10在50前面:1
printf("ptr_first == ptr_first1? %d \\n" , ptr_first == ptr_first1);//都指向10的内存地址:0
printf("ptr_first == (ptr_last-4)? %d \\n" , ptr_first == (ptr_last 4) );//都指向10的内存地址:0
// 无意义,无法确定num1 和 num2在内存中的地址,分别定义的变量在内存存储时地址不一定相邻
int num1 = 100;
int *ptr_num1 = &num1;
int num2 = 200;
int *ptr_num2 = &num2;
printf("ptr_num1 > ptr_num2 ? %d \\n" , ptr_num1 > ptr_num2);

9.3 指针和数组

9.3.1 数组名和指向数组首元素的指针

数组名在大多数情况下会被隐式地转换为指向数组第一个元素的指针,在特定情况下数组名可以被视为一个指针,具有一些指针的特性。

不同点:

1、sizeof 运算符返回的是整个数组的大小,而指针返回的是本身的大小

int nums[5] = {10, 20, 30, 40, 50};
int *ptr = &nums[0];//定义指针,指向数组首元素
printf("sizeof计算nums的存储大小: %zu \\n" , sizeof nums ); //20
printf("sizeof计算ptr的存储大小: %zu \\n" , sizeof ptr); // 8

2、数组名不能进行自增、自减运算

3、数组名本质是标识符常量,不能更改;指针本质上是变量,可以修改

相同点:——(将数组名当成指针对待)

本质上:
1、数组名加下标本质上就是指针的解引用
num[0] == (nums + 0) == ptr[0] == (ptr + 0) 都表示数组的第一个元素的值
2、数组名、指针名和对数组元素取址都能表示元素的地址
nums == ptr == &nums[0] 都表示数组的地址

1、通过 %p 格式化输出地址

printf("ptr的值: %p \\n" , ptr);//输出为数组nums第一个元素的内存地址
printf("nums的值: %p \\n" , nums);//输出为数组nums第一个元素的内存地址

2、都可以通过解引用输出数组中的元素

printf("ptr指向的值: %d \\n" , *ptr);//10
printf("nums指向的值: %d \\n" , *nums);//10

3、都可以通过加减运算切换数组中的元素地址和值

//输出都是数组中第二个元素的地址和值
printf("ptr+1的值: %p , ptr+1指向的值: %d \\n" , ptr + 1, *(ptr+1));
printf("nums+1的值: %p , nums+1指向的值: %d \\n" , nums + 1, *(nums+1));

4、都可以用于表示数组中的元素

//以下方式都可以输出数组中的第三个元素
printf("nums[2]数组的第一个元素: %d \\n" , nums[2]);
printf("ptr[0]数组的第一个元素: %d \\n" , ptr[2]);
printf("*(ptr+2)取数组的元素: %d \\n" , *(ptr + 2) );
printf("*(nums+2)取数组的元素: %d \\n" , *(nums + 2) );

9.3.2 指针数组

指针数组:是个数组,数组中的每一个元素都是指针(存储指针的数组)

// 若使用二维字符数组,必须明确行和列,而且每一行的长度必须相同
// 使用指针数组,维护不同长度的字符串(字符数组)
char str1[] = "ShenZhen1";
char str2[] = "ShangHaiHai1";
char str3[] = "BeiJingTianAnMen1";
char str4[] = "WuHan1";
// 定义指针数组
char *citys2[] = { str1 , str2 , str3 , str4};
for( int i= 0 ;i < 4 ; i ++)
{
printf("%s \\n" , citys2[i]);
}
printf("citys2的存储长度: %zu \\n" , sizeof citys2);

9.3.3 数组指针

数组指针:是个指针,指向一个数组(指向的是整个数组)

int arr[5] = {10, 20, 30, 40, 50};
//int *ptr = &arr[0];//定义指针,指向数组首元素,指针的类型是:int *
int (*ptr)[5] = &arr;//定义数组指针,指向 int [5] 类型,指针的类型:int *[5]
//遍历数组,取数组中的元素
for( int i = 0 ;i < 5 ; i ++)
{
printf("arr指针取数组的元素: %d \\n" , arr[i]); // *(arr + i)
printf("ptr指针取数组的元素: %d \\n", (*ptr)[i]); // *((*ptr) + i)
printf("ptr指针取数组的元素: %d \\n", *((*ptr) + i));
}

arr[i] == (*arr + i) == (ptr)[i] == ((*ptr) + i)

在这里插入图片描述

9.3.4 字符指针

字符指针,是一个指针,可以指向一个普通字符,也可以指向一个字符串(字符数组)

1、字符指针是可变的,允许使用下面方法重新赋值,指向新的字符串。而字符数组不允许重新赋值,只允许对数组中的元素进行单独重新赋值

// 定义字符指针,指向字符串常量(字面量)
char *ptr = "How Are You";
ptr = "How Old Are You?";//指针本身的值可以修改
// 定义字符数组(字符串)
char arr[12] = "Hello World";
arr[0] = 'L';//只能单独对各个元素进行重新赋值

9.4 指针和函数

9.4.1 值传递和指针传递

1、值传递: 将实参的值传递给函数的形参。可以理解位将实参的值拷贝一份传递给函数的形参。实参本来的值和函数中形参的值没有关系。在函数内改变参数的值,不影响函数外面原本实参的值

2、指针传递: 将实参的地址传递给函数的形参。实参本来的地址和函数中形参的地址是一样的,指向的同一个数据。在函数内改变参数的值,会改变函数外面原本实参的值

9.4.2 指针函数

如果函数的返回值是一个指针,这样的函数称为指针函数,返回值不能指向局部变量

语法:返回类型 *指针函数名(参数列表)

int *func()
{
// int n = 100; // 局部变量, 在func返回时,就会销毁
// 如果这个局部变量是static性质的,那么n存放数据的空间在静态数据区
static int n = 100;
return &n;
}
int main()
{
int *p = func();
int n = *p;
printf("value=%d", n);
return 0;
}

9.4.3 函数指针(指向函数的指针)

函数指针:是个指针,指向一个函数

函数名在表达式中有时也会被转换为该函数所在内存区域的首地址,这和数组名非常类似

语法:返回类型 (*函数指针名)(参数列表)

注意:()的优先级高于*,第一个括号不能省略,如果省略,就成了指针函数。

//定义函数,求两个数的平均值
double get_avg(int a, int b)
{
return (a + b) / 2.0;
}

// 使用%p格式化输出结果都一样,都是输出函数所在地址
printf("get_avg的值: %p \\n" , get_avg);
printf("*get_avg的值: %p \\n" , *get_avg);
printf("&get_avg的值: %p \\n" , &get_avg);
// 直接使用函数
double result1 = get_avg(10,20);//直接使用函数名调用
printf("result1 = %.2lf \\n" , result1);//15.00
//通过函数指针调用,显式地解引用了函数指针,编译器会忽略这个解引用操作,仍然将其视为函数调用。
double result2 = (*get_avg)(10,20);
printf("result2 = %.2lf \\n" , result2);//15.00
//通过取地址符调用,函数名本身已经是一个指针,因此 &get_avg 和 get_avg 是等价的。
double result3 = (&get_avg)(10,20);
printf("result3 = %.2lf \\n" , result3);//15.00
// 定义函数指针,指向get_avg函数。get_avg的数据类型: double(int,int)
// 定义指针,指向double(int,int)类型函数,指针的类型: double *(int,int)
//函数名get_avg本身就是一个函数名,所以*get_avg、&get_avg和get_avg等价
double (*fun_ptr1)(int,int) = get_avg;
double (*fun_ptr2)(int,int) = *get_avg;
double (*fun_ptr3)(int,int) = &get_avg;
// 使用函数指针,调用函数
double d1 = fun_ptr1(10,20);
double d2 = fun_ptr2(10,20);
double d3 = fun_ptr3(10,20);
printf("d1 = %.2lf \\n" , d1 );//15.00
printf("d2 = %.2lf \\n" , d2 );//15.00
printf("d3 = %.2lf \\n" , d3 );//15.00

9.4.4 回调函数

回调函数就是一个函数指针调用的函数,就是将函数作为参数传入另一个函数中

//定义函数,实现两个整数的计算, 返回计算结果, 计算的方式由调用者来决定
//`int (*cpt)(int,int)`是函数指针
int compute_2_num( int num1, int num2 , int (*cpt)(int,int) )
{
// 调用回调函数完成两个整数的计算
return cpt(num1, num2);
}
int add(int num1, int num2)//加
{
return num1 + num2;
}
int sub(int num1, int num2)//减
{
return num1 num2;
}
int mul(int num1, int num2)//乘
{
return num1 * num2;
}
int div(int num1 , int num2)//除
{
return num1 / num2;
}
int main()
{
//通过过改变传入compute_2_num函数的回调函数,来改变函数的功能
//int result = compute_2_num(100,200 ,add);
//int result = compute_2_num(100,200,sub);
//int result = compute_2_num(100,200,mul);
int result = compute_2_num(100,200,div);
printf("result = %d \\n" , result);
return 0;
}

9.5 多级指针

指向指针的指针,多级间接寻址的形式,或者说是一个指针链

在这里插入图片描述

1、声明多级指针

int *ptr;//一级指针
int **ptr;//二级指针
int ***ptr;//三级指针

2、初始化多级指针

int var;
int *ptr = &var;//一级指针指向 var
int **pptr = &ptr;//二级指针指向 ptr
int ***ppptr = &pptr;//三级指针指向 pptr
printf("Value of var: %d\\n", var);
printf("Value of ptr: %d\\n", *ptr); // 解引用一次
printf("Value of pptr: %d\\n", **pptr); // 解引用两次
printf("Value of ppptr: %d\\n", ***ppptr); // 解引用三次

9.6 空指针

赋为NULL 值的指针被称为空指针,NULL 是一个定义在标准库 <stdio.h>中的值为零的宏常量

声明指针变量的时候,如果没有确切的地址赋值,为指针变量赋一个 NULL 值是一个好的编程习惯int \\*p \\= NULL;

9.7 野指针

野指针就是指针指向的位置不可知(随机性、不正确、没有明确限制的)

原因:
1、指针使用前未初始化——int *p
2、指针越界访问
3、指针指向已释放的空间(比如指针指向局部变量)

变量定义类型表示含义
int i int i 是一个整型变量
int *p int * p 是一个指向整型数据的指针
int a[5] int [5] a 是一个5个元素的整型数组
int *p[4] int *[4] p 是一个指针数组,每个元素都是指向整型数据的指针
int (*p)[4] int (*)[4] p 是一个数组指针,指向一个4个元素的整型数组
int f() int () f 是一个返回整型数据的函数
int *f() int *() f 是一个指针函数,返回指向整数数据的指针
int (*p)() int (*)() p 是一个函数指针,指向一个返回整型数据的函数
int **p int ** p 是一个指向指针的指针
赞(0)
未经允许不得转载:网硕互联帮助中心 » 第9章 指针
分享到: 更多 (0)

评论 抢沙发

评论前必须登录!