分享

C开发实战

 山峰云绕 2020-11-29

C开发实战-深入理解指针

原创ittimeline2020-11-27 20:29:35


【【ittimeline】C开发实战-深入理解指针】https://toutiao.com/group/6899778151484768780/?app=explore_article&timestamp=1606583825&use_new_style=1&req_id=202011290117040102040510944320876D&group_id=6899778151484768780&tt_from=copy_link&utm_source=copy_link&utm_medium=toutiao_ios&utm_campaign=client_share 


Visual Studio 2019 解决方案的多项目应用

在讲述变量,数据类型,运算符和表达式以及程序流程控制,数组,函数的相关内容,所有的代码都放在解决方案c-core的c-core-foundational项目下。
如果你有其他编程语言经验,类似于C ,Java,Python,Go语言它们都会有这些最最基本的内容,而且语法结构都很类似(但不完全相同)。

C开发实战-深入理解指针


而Visual Studio 2019的单个解决方案支持多个项目,因此指针以后的内容都会放在c-core-advanced项目下。

C开发实战-深入理解指针

但是Visual Studio 2019在编译单个解决方案下的多项目时,只能启动一个项目来参与编译和运行。
因此我们需要指定一个项目为启动项,设置为启动项后编译运行时,只有该项目会参与编译。

C开发实战-深入理解指针


设置启动项

指针基础

指针与指针变量

32位编译器上的程序运行时,系统会给程序随机分配一段内存空间,这段空间的最小单位是字节,每个字节都有自己的唯一编号,而32位编译器能寻址的范围按照十六进制表示是0x00000000到0xffffffff,这个编号就是指针。
整型变量用于存储整数的变量,而指针变量就是存储指针(地址或者编号,例如0x00000001)的变量,因为32位编译器的地址编号的范围是0x00000000到0xffffffff,因此指针变量的大小是4个字节。 64位编译器能寻址的范围是0x0000000000000000到0xffffffffffffffff,因此64位编译器的指针变量占据8个字节。

指针变量的定义、声明和初始化

首先回顾下变量的声明和赋值,这里以大家熟悉的整型变量定义为例
int number; 表示在内存中开辟四个字节,假设number变量的内存地址是0x00000002
number = 10; 表示将内存地址0x00000002代表的那块空间的内容修改为10

C语言中通过地址符&加上变量名就可以获取变量的地址,即&number表示number的地址。

而C语言中*加上变量名表示一个指针类型的变量,例如*p,其中指针的变量名是p,当然也可以是任何合法的标识符,例如p_int,那么声明定义一个指针变量呢?

声明指针变量有三个步骤
1.*加上变量名表示为指针变量,例如*p,p表示指针变量名
2.要保存变量的地址,将变量的声明放在此处,例如int number;表示声明一个整数变量
3.将*p 替换声明的变量名,例如 int number 使用*p 替换后就是 int *p

因为指针的本质就是变量内存地址,那么给指针变量赋值也就是变量的地址,即p =&number;

而指针变量的定义就是将指针变量的声明和赋值写在一行

int *p=&number;

其中 p是指针变量的名字,而p的类型是就是将变量p本身去掉后剩下的类型就是指针变量的类型,这里就是int *
指针变量p保存什么数据类型的地址,就是去掉变量p与其离的最近的一个*后剩下的类型就是保存地址的数据类型,这里就是int
例如多级指针变量int **p 的指针变量类型是int **,p保存地址的类型就是int *

我们还可以根据声明指针变量三个步骤来定义其他类型的指针变量,例如字符指针,数组指针,代码片段如下

//使用指针变量保存char类型变量的地址char ch = 'x';char *p_char=&ch;//输出指针变量p_char的内存地址printf('p_char = %p\n', p_char);//使用指针变量保存整数数组类型变量的地址int numbers[10] = {1,2,3,4,5,6,7,8,9,10};int (*p_array)[10]=&numbers;//输出指针变量p_array的内存地址printf('p_array = %p\n', p_array[10]);

指针变量的定义,声明和初始化

#define _CRT_SECURE_NO_WARNINGS#include <stdio.h>#include <stdlib.h>/* 指针变量的定义,声明和初始化 @author liuguanglei 18601767221@163.com @wechat 18601767221 @website ittimeline.net @version 2020/11/25 */int main (int argc, char* argv[]){int number = 10;//指针变量用于存放变量的地址//那么如何声明指针变量呢? 指针变量是什么类型/* 声明指针变量的三步骤 1. *加变量名表示指针变量,例如*p,p表示指针变量名 2. 要声明指针变量,将变量的声明形式放在此处,例如 int number; 3. 使用*p替换声明的变量名,例如 int number 使用`*p` 替换后就是 `int *p` *///声明指针变量int *p;/* *与p结合代表是一个指针变量,p是变量名 p是变量,该变量的类型是将变量p去掉剩下的类型就是指针变量类型,即int * 指针变量p保存什么数据类型的地址? 去掉指针变量p与指针变量p离得最近的一个*后剩下的数据类型就是指针变量p保存的数据类型的地址,即int 而多级指针 int **p ,p是变量,p的类型是int ** ,而p保存的类型是int * *///指针变量赋值p = &number;printf('p = %p\n',p);//使用指针变量保存char类型变量的地址char ch = 'x';char *p_char=&ch;//输出指针变量p_char的内存地址printf('p_char = %p\n', p_char);//使用指针变量保存整数数组类型变量的地址int numbers[10] = {1,2,3,4,5,6,7,8,9,10};int (*p_array)[10]=&numbers;//输出指针变量p_array的内存地址printf('p_array = %p\n', p_array[10]); system('pause');return 0; }

程序运行结果

C开发实战-深入理解指针

指针变量的使用

指针变量保存了变量的内存地址就可以操作变量代表的那块内存空间,在使用时*p表示p指向那块内存地址的空间内容,即可以通过*p间接修改变量的内容。
指针变量和普通变量一样,可以多次被赋值,即指向别的内存空间的地址。
&可以获取一个变量的内存地址,但是不能取寄存器变量,因此寄存器变量不在内存中,而在CPU中,CPU没有地址。

#define _CRT_SECURE_NO_WARNINGS#include <stdio.h>#include <stdlib.h>/*
指针变量的使用
@author liuguanglei 18601767221@163.com
@wechat 18601767221
@website ittimeline.net
@version 2020/11/25
*/int main(int argc, char* argv[]){int number = 100;//指针变量p指向了number的内存地址int *p = &number;printf('number的内存地址是%p\n',&number);printf('number的内存地址是%p\n',p);// 就可以通过p来操作number代表那块空间的内容//使用时*p表示取p指向number所代表那块内存空间的内容printf('*p = %d \n',*p);//通过指针修改number的值*p = 80;printf('*p = %d \n', *p);printf('number = %d \n', number);int x = 20;//第二次指向x变量的内存空间p = &x;printf('*p = %d \n', *p);printf('x = %d \n', x);


system('pause');return 0;
}

程序运行结果

C开发实战-深入理解指针

赋值运算符(=)两边的类型必须一致,指针变量也是如此,对于指针变量和地址符(&)而言:在使用时(例如赋值)对一个表达式取*就会对表达式减一级*,如果对表达式取&,就会加一级*。
例如p =&x;p的类型是int *,而x的类型是int,取&表示加一级*,此时=左右两边类型一致,都是int *
例如*p=80,80的类型是int,而在对于一个表达式取*就会减一级*,此时=左右两边类型一致,都是int
例如int *p; int **q; q=&*p,q的类型是int **,p的类型是int *,而对表达式取&加一级*,因此=左右两边类型一致,都是int **,*q=p

指针变量的大小

因为指针变量就是存储的变量的内存地址,而不管什么类型的指针(单级指针还是多级指针),32位编译器下指针变量的大小是4个字节,64位编译器下指针变量的大小是8个字节。

#define _CRT_SECURE_NO_WARNINGS#include <stdio.h>#include <stdlib.h>/* 指针变量的大小 @author liuguanglei 18601767221@163.com @wechat 18601767221 @website ittimeline.net @version 2020/11/25 */int main(int argc, char* argv[]){char *p_char; short *p_short;int *p_int;int **p_second_int;//不管是几级地址,32位系统下 指针变量都是4个字节,64位系统下 指针变量都是8个字节printf('指针变量 p_char的大小是%d个字节\n',sizeof(p_char));printf('指针变量 p_short的大小是%d个字节\n',sizeof(p_short));printf('指针变量 p_int的大小是%d个字节\n',sizeof(p_int));printf('指针变量 p_second_int的大小是%d个字节\n',sizeof(p_second_int)); system('pause');return 0; }

x86表示32位编译器

C开发实战-深入理解指针


32位编译器


32位编译器运行效果

C开发实战-深入理解指针

x64表示64位编译器

C开发实战-深入理解指针


64位编译器运行效果

C开发实战-深入理解指针

指针的宽度和步长

32位系统的指针变量大小为4个字节,但是不同类型的指针变量(char * ,short * ,int * )指向内存的实际宽度是不一样的,宽度也叫做步长,
求指针变量的宽度或者步长可以通过去掉指针变量p和离它最近的*,然后用sizeof()求即可。

#define _CRT_SECURE_NO_WARNINGS#include <stdio.h>#include <stdlib.h>/*
指针变量的宽度和步长
@author liuguanglei 18601767221@163.com
@wechat 18601767221
@website ittimeline.net
@version 2020/11/25
*/int main(int argc, char* argv[]){int number = 0x10203040;//由于=右边&number的类型是int *//=左边是char *//因此类型不一致,需要强制类型转换才能成功赋值//如果不强制类型转换,编译器会提示 warning C4133: “初始化”: 从“int *”到“char *”的类型不兼容//赋值时尽量保存左右两边类型一致char* p_char =(char *)&number;
short* p_short = (short*)&number;int* p_int = &number;//此时三个指针变量 p_char,p_short,p_int都指向同一块内存地址即number的内存地址//打印输出地址的内容printf('*p_char = %x \n', *p_char);printf('*p_short = %x \n',*p_short);printf('*p_int = %x \n', *p_int);//不同类型的指针获取的指针指向的内容是由指针变量的类型决定的//通过*取指针变量所指向那块空间内容时,取得内存宽度是和指针变量本身的类型有关系// p_char获取的一个字节的宽度,p_short获取的是2个字节的宽度,p_int获取的是4个字节的宽度//即将指针变量和指针变量最近的*去掉后的类型就是指针变量指向的内存表示的宽度printf('打印三个指针变量指向内容的宽度');printf('p_char指针指向内容的宽度是%u字节 \n',sizeof(char));printf('p_short指针向内容的宽度是%u字节 \n',sizeof(short));printf('p_int指针向内容的宽度是%u字节 \n',sizeof(int));printf('int* 指针向内容的宽度是%u字节 \n',sizeof(int*));//宽度也是步长//指针加1跨过多少个字节//打印三个指针变量的地址printf('打印三个指针变量的地址\n');printf('p_char = %p\n',p_char);printf('p_short = %p\n', p_short);printf('p_int = %p\n',p_int);//打印三个指针变量地址的宽度 1//步长为1printf('p_char 1 = %p\n', p_char   1);//步长为2printf('p_short 1 = %p\n', p_short   1);//步长为4printf('p_int 1 = %p\n', p_int   1);


system('pause');return 0;
}

程序运行结果

C开发实战-深入理解指针

野指针

在定义整数变量时,如果变量尚未初始化,是不能使用变量的。

指针变量也是如此,当声明一个指针变量,但是尚未初始化后就使用时,此指针变量称为野指针,野指针就是没有初始化过的指针,此时指针的指向是随机的内存地址,在日常开发中不能使用野指针。在Visual Studio 2019中使用野指针,编译器会提示C4700错误:使用了尚未初始化的局部变量

#define _CRT_SECURE_NO_WARNINGS#include <stdio.h>#include <stdlib.h>/* 野指针 @author liuguanglei 18601767221@163.com @wechat 18601767221 @website ittimeline.net @version 2020/11/25 */int main(int argc, char* argv[]){//野指针:没有初始化的指针,指针的指向是随机的,不能使用野指针。// 指针p保存的地址一定是初始化过的(向系统申请过的)int *p;//指针变量p没有指向任何内容,即尚未初始化//因此不能赋值*p = 200;printf('*p = %d \n',*p); system('pause');return 0; }
C开发实战-深入理解指针


C4700错误

空指针

在定义指针变量时,如果初始化为0或者初始化为NULL,此时的指针变量就是空指针,即指针的编号为0x00000000,而0x0000000的地址是不会给应用程序使用。
NULL是C语言的关键字,这里用于描述空指针。在使用指针之前,可以使用if语句判断指针是否为NULL,就可以判断该指针是否为空指针。如果指针不再使用可以赋值为NULL。

#define _CRT_SECURE_NO_WARNINGS#include <stdio.h>#include <stdlib.h>/*
空指针:指针变量的值为0
@author liuguanglei 18601767221@163.com
@wechat 18601767221
@website ittimeline.net
@version 2020/11/25
*/int main(int argc, char* argv[]){//空指针,即将指针的值赋值为0,0的32位十六进制表示0x00000000 // 0x00000000 被操作系统占用了int *p = 0;int number = 10;//C语言使用NULL关键字表示空指针int *p_null = NULL; //等价于 int *p_null=0// NULL作为标记if (NULL ==p_null)  {
p = &number;
}//因为p_null保存了0x00000000的地址,该地址不能给应用程序使用*p_null = 200; 
printf('*p_null = %d \n',*p_null);
system('pause');return 0;
}

在使用指针时必须首先初始化指针后再使用,禁止使用野指针和空指针。

const修饰的指针变量

在定义变量时使用const修饰的变量不能直接通过变量名修改
而const修饰指针变量时有三种情况,这里假设已经初始化了三个指针变量,p_int,p_dbl和p_char

  • 指针变量const int *p_int 此时const修饰的是*,表示p_int指向的空间内容不能改变

  • 指针变量int const *p_dbl 此时const修饰的是指针变量p_dbl,表示p_dbl不能再指向别的内存空间

  • 指针变量const int const *p_char,此时const同时修饰*和p_char,表示 p_char不能再指向别的内存空间的同时空间内容也不能改变。

#define _CRT_SECURE_NO_WARNINGS#include <stdio.h>#include <stdlib.h>/* const修饰的指针变量 @author liuguanglei 18601767221@163.com @wechat 18601767221 @website ittimeline.net @version 2020/11/25 */int main(int argc, char* argv[]){// const修饰变量后,不能通过赋值来修改变量的值const int number = 10;printf('const整数变量number的初始化值为%d\n',number);//此处编译不通过//number = 20;//但是可以通过指针来修改number变量的值int *p = &number; *p = 20;printf('通过指针修改number的值为%d\n',number); *p = 20;//const修饰的是*const int *p_int = &number;//const修饰指针变量后 不同通过*p_int 来修改number的值//*p_int = 20;//const修饰的是变量p_dbldouble dbl = 3.14;//初始化指针p_dbldouble * const p_dbl = &dbl;// p_dbl不能的值不能再指向别的内存空间,即不能再修改//p_dbl = &number;//p_char本身的指向不能改变,并且不能通过*p_char修改指向空间的内容const char* const p_char = &number; system('pause');return 0; }
#define _CRT_SECURE_NO_WARNINGS#include <stdio.h>#include <stdlib.h>/*
const修饰的指针变量
@author liuguanglei 18601767221@163.com
@wechat 18601767221
@website ittimeline.net
@version 2020/11/25
*/int main(int argc, char* argv[]){// const修饰变量后,不能通过赋值来修改变量的值const int number = 10;printf('const整数变量number的初始化值为%d\n',number);//此处编译不通过//number = 20;//但是可以通过指针来修改number变量的值int *p = &number;
*p = 20;printf('通过指针修改number的值为%d\n',number);
*p = 20;//const修饰的是*const int *p_int = &number;//const修饰指针变量后 不同通过*p_int 来修改number的值//*p_int = 20;//const修饰的是变量p_dbldouble dbl = 3.14;//初始化指针p_dbldouble * const p_dbl = &dbl;// p_dbl不能的值不能再指向别的内存空间,即不能再修改//p_dbl = &number;//p_char本身的指向不能改变,并且不能通过*p_char修改指向空间的内容const char* const p_char = &number; 
 
system('pause');return 0;
}

多级指针

#define _CRT_SECURE_NO_WARNINGS#include <stdio.h>#include <stdlib.h>/* 多级指针的定义与初始化 定义多级指针保存数据的地址时,定义的指针类型只需要比要保存的数据类型多一级*即可。 @author liuguanglei 18601767221@163.com @wechat 18601767221 @website ittimeline.net @version 2020/11/25 */int main(int argc, char* argv[]){int number = 10; // *p一级指针初始化int *p = &number;//定义二级指针q保存p的地址// *q 表示指针变量, **q表示二级指针变量int **q = &p;//通过**q取number的值 **q=// *q表示number的地址 即&number//printf('**q = %d \n',**q);//如果*和&相邻,就相互抵消了printf('*&number = %d \n',*&number);//定义变量保存q的地址//三级指针的定义int ***u = &q;printf('***u = %d \n',***u); system('pause');return 0; }

程序运行结果

C开发实战-深入理解指针

指针操作数组

指针除了操作变量以外,还可以用来操作数组。

当定义一个整数数组int numbers[10]={1,2,3,4,5,6,7,8,9,10};,numbers代表了数组的首元素地址。
而在没有学习指针时,通过数组名[下标]的方式来访问数组

for(int i=0;i<sizeof(numbers)/sizeof(numbers[0]);i  ){printf('numbers[%d] = %d \n',i,numbers[i]);
}

在学习了指针变量后,我们可以定义指针变量来保存数组元素的首地址:int *p =numbers,由于数组的每个元素都是连续的内存空间,而通过指针变量的加减运算(例如p 1表示获取下一个指针地址)就可以很容易获取数组的下一个元素的地址。

for (int i = 0; i < sizeof(numbers)/sizeof(numbers[0]);i ) {//通过指针遍历数组的元素printf('numbers[%d] = %d \n',i,*(p i)); }

再来初始化一个整数数组: int data[5]={1,2,3,4,5};,计算*(data 1)的值。
因为data表示数组首元素(即第一个元素)的地址,而data 1表示获取第二个元素的地址。*(data 1) 表示取第二个元素地址的内容,结果就是2

int* ptr = (int*)(&data   1);printf('*(ptr-1) = %d \n', *(ptr - 1));

(int*)(&data 1);表示整个data数组的地址加1,然后为了类型匹配转换成int *类型,而*(ptr-1)表示ptr原有的地址值减1后获取指向的内容为5

指针操作数组的元素

#define _CRT_SECURE_NO_WARNINGS#include <stdio.h>#include <stdlib.h>/* 指针操作数组的元素 @author liuguanglei 18601767221@163.com @wechat 18601767221 @website ittimeline.net @version 2020/11/25 */int main(int argc, char* argv[]){//初始化整数数组//数组名numbers代表数组首元素的地址int numbers[10] = {1,2,3,4,5,6,7,8,9,10};//定义指针变量指向numbers数组的首元素地址//指针p保存的是数组元素的首地址int *p = numbers;int size = sizeof(numbers) / sizeof(numbers[0]);printf('通过指针遍历数组的元素\n');for (int i = 0; i < size;i ) {//通过指针遍历数组的元素printf('numbers[%d] = %d \n',i,*(p i)); }int data[5] = {1,2,3,4,5};//*(data 1)2= 2 因为data表示data数组的首元素地址,然后data 1表示第二个元素的地址,*(data 1)表示取第二个元素地址对应的内容 因此是2printf('*(data) 1 = %d\n ',*(data 1));//*(ptr-1)=5//表示整个data数组的地址加1,然后为了类型匹配转换成`int *`类型,而`*(ptr-1)`表示ptr原有的地址值减1后获取指向的内容为5int* ptr = (int*)(&data 1);printf('*(ptr-1) = %d \n', *(ptr - 1)); system('pause');return 0; }

程序运行结果

C开发实战-深入理解指针

[]除了在声明数组时使用以外,还可以在定义指针变量和指针数组时使用,在指针变量和指针数组中使用[]时,[]等价于*()的缩写,例如p[0]=*(p 0)

#define _CRT_SECURE_NO_WARNINGS#include <stdio.h>#include <stdlib.h>/*
方括号[]在指针变量,指针数组的应用
@author liuguanglei 18601767221@163.com
@wechat 18601767221
@website ittimeline.net
@version 2020/11/25
*/int main(int argc, char* argv[]){int number = 10;int* p = &number;//[]等价于*()的缩写//p[0]=*(p 0)p[0] = 100;printf('number = %d \n', number);int numbers[] = { 1,2,3,4,5,6,7,8,9,10 };int* p_array = numbers;for (int i = 0;i<sizeof(numbers)/sizeof(numbers[0]);i  ) {//p_array[0]=*(p_array 0)//p_array[i]=*(p_array i)printf('numbers[%d] = %d \n',i,p_array[i]);
}


system('pause');return 0;
}

程序运行结果

C开发实战-深入理解指针

指针的运算

指针可以进行加减运算

#define _CRT_SECURE_NO_WARNINGS#include <stdio.h>#include <stdlib.h>/* 指针的算术运算 @author liuguanglei 18601767221@163.com @wechat 18601767221 @website ittimeline.net @version 2020/11/25 */int main(int argc, char* argv[]){int numbers[10] = {1,2,3,4,5,6,7,8,9,10};//数组第一个元素的地址//数组名表示数组首元素的地址int *p = numbers;//数组最后一个元素的地址//int* q = &numbers[9];//另外一种方式表示数组最后一个元素的地址//(int*)(&numbers 1)-1 表示整个数组的长度加1后转换为int*后就是元素的地址,减1即为数组的最后一个元素地址int *q = (int*)(&numbers 1) - 1;//p和q中间的元素个数printf('q - p = %d \n',(q-p));// *(p 3) =4//因为p指向第一个数组的第一个元素printf('*(p 3) = %d \n',*(p 3));//在C语言中两指针相加没有意义//printf('*(p 3) = %d \n',(p q));system('pause');return 0; }

程序运行结果

C开发实战-深入理解指针

两指针(相同类型的指针)相减,得到的结果是中间跨过多少元素。两指针相加没有任何意义。

指针数组

一级指针操作指针数组

整型数组:数组中存放多个整型元素,顾名思义指针数组,就是数组中存放多个指针。
指针数组的类型由指针指向元素的类型决定的,例如指针指向int类型数据的内存地址,那么指针数组的中的元素类型就是int *

指针数组的定义和初始化和整型数组类似,如果指针数组的元素类型是int *,那么每个int *的大小为4个字节

//初始化三个整数变量int left = 10, middle = 20, right = 30;//将三个整数变量赋值给numbers数组int numbers[3] = {left,middle,right};//定义三个int*类型的指针int* p_left = &left;
int* p_middle = &middle;
int* p_right = &right;//定义指针数组,存放三个整数变量的地址 每个元素都是int *类型int* p_numbers[3] = {&left,&middle,&right};// int *占据4个字节printf('p_numbers占%d个字节\n',sizeof(p_numbers));

指针数组的元素可以通过指*针数组名[下标]的方式获取,例如*p_numbers[0]

printf('left = %d \n',*p_numbers[0]);

指针数组的遍历

// 遍历指针数组,获取数组中的指针指向空间的内容int size = sizeof(p_numbers) / sizeof(p_numbers[0]);for (int i = 0; i < size;i  ) {printf('*p_numbers[%d] = %d \n',i,*(p_numbers[i]));
}printf('\n');

指针数组的定义、初始化和遍历

#define _CRT_SECURE_NO_WARNINGS#include <stdio.h>#include <stdlib.h>/* 一级指针数组的定义、初始化和遍历 指针数组:数组中存放多个指针 @author liuguanglei 18601767221@163.com @wechat 18601767221 @website ittimeline.net @version 2020/11/25 */int main(int argc, char* argv[]){//初始化三个整数变量int left = 10, middle = 20, right = 30;//将三个整数变量赋值给numbers数组int numbers[3] = {left,middle,right};//定义三个int*类型的指针int* p_left = &left;int* p_middle = &middle;int* p_right = &right;//定义指针数组,存放三个整数变量的地址 每个元素都是int *类型int* p_numbers[3] = {&left,&middle,&right};// int *占据4个字节printf('p_numbers占%d个字节\n',sizeof(p_numbers));printf('left = %d \n',*p_numbers[0]);// 遍历指针数组,获取数组中的指针指向空间的内容int size = sizeof(p_numbers) / sizeof(p_numbers[0]);for (int i = 0; i < size;i ) {printf('*p_numbers[%d] = %d \n',i,*(p_numbers[i])); }printf('\n'); system('pause');return 0; }

程序运行结果

C开发实战-深入理解指针

二级指针操作指针数组

当想要把一级指针数组以指针变量的方式保存在另外一个指针中,此时就需要使用到二级指针.
例如int ** p_first_element=p_numbers,由于p_numbers是int *类型,根据要保存int *类型的地址,就需要比它多一级*,因此使用int **p_first_element保存。
其中p_numbers是指针数组的首元素地址。

通过p_first_element变量获取left(即一级指针p_numbers指向的left内存地址的内容)的值
p_first_element 通过*p_first_element可以获取left变量的地址值,而**p_first_element可以获取left变量地址指向的空间内容,也就是left的值

printf('通过p_first_element获取left的值 left = %d \n', (**p_first_element));

通过p_first_element变量获取middle(即一级指针p_numbers指向的middle内存地址的内容)的值
p_first_element 通过*p_first_element可以获取left变量的地址值,而*(p_first_element 1)可以获取middle变量的地址值,通过**(p_first_element 1)就可以获取middle变量的值

通过p_first_element变量获取right(即一级指针p_numbers指向的right内存地址的内容)的值

p_first_element 通过*p_first_element可以获取left变量的地址值,而*(p_first_element 2)可以获取right变量的地址值,通过**(p_first_element 2)就可以获取right变量的值

也可以采用遍历二级指针的方式来获取left,middle,right的值

for (int i = 0; i < sizeof(p_numbers) / sizeof(p_numbers[0]); i ) {printf('%d ', **(p_first_element i)); }

二级指针操作指针数组

#define _CRT_SECURE_NO_WARNINGS#include <stdio.h>#include <stdlib.h>/*
多级指针操作指针数组
@author liuguanglei 18601767221@163.com
@wechat 18601767221
@website ittimeline.net
@version 2020/11/25
*/int main(int argc, char* argv[]){/***********************************************************二级指针操作指针数组*********************************************************///初始化三个整数变量int left = 10, middle = 20, right = 30;//将三个整数变量赋值给numbers数组int numbers[3] = { left,middle,right };//定义三个int*类型的指针int* p_left = &left;int* p_middle = &middle;int* p_right = &right;//定义指针数组,存放三个整数变量的地址 每个元素都是int *类型int* p_numbers[3] = { &left,&middle,&right };//定义指针保存p_numbers首元素的地址//p_numbers就是首元素的地址,其类型是int *//要保存int *的地址,需要比它多一级*int** p_first_element = p_numbers;// 通过p_first_element获取left的值//*p_first_element表示left的地址//**p_first_element表示left地址的内容printf('通过p_first_element获取left的值 left = %d \n', (**p_first_element));//通过p_first_element获取middle的值//*(p_first_element 1)表示middle的地址//*(*(p_first_element 1))表示middle地址的内容printf('通过p_first_element获取middle的值 middle = %d \n', (*(*(p_first_element   1))));//通过p_first_element获取right的值//*(p_first_element 2)表示right的地址//*(*(p_first_element 2))表示right地址的内容printf('通过p_first_element获取right的值 middle = %d \n', (*(*(p_first_element   2))));//通过for循环获取p_first_element 的 left,middle,right值printf('通过for循环获取p_first_element 的 left,middle,right值\n');for (int i = 0; i < sizeof(p_numbers) / sizeof(p_numbers[0]); i  ) {printf('%d ', **(p_first_element   i));
}
system('pause');return 0;
}
C开发实战-深入理解指针


程序运行效果

指针和函数

指针作为函数的形参

指针作为函数的形参可以改变实参的值

在调用函数时,如果没有使用指针,形参不会改变实参的内容。

#define _CRT_SECURE_NO_WARNINGS#include <stdio.h>#include <stdlib.h>/* 交换两个整数形式参数的值 单向值传递 */void swap_variable(int x,int y) {int tmp = x; x = y; y = tmp; }/* 指针和函数 指针作为函数的形参可以改变实参的值 @author liuguanglei 18601767221@163.com @wechat 18601767221 @website ittimeline.net @version 2020/11/25 */int main(int argc, char* argv[]){int left = 10;int right = 20;printf('普通变量交换之前 left = %d right = %d \n',left,right); swap_variable(10,20);printf('普通变量交换之后 left = %d right = %d \n', left, right); system('pause');return 0; }

运行上面的程序会看到当执行完swap_variable()方法后,形参x和y的值已经交换(即x=20,y=10),而实际参数left和right的值在交换前后并没有发生改变。

此时可以将指针变量作为函数的形式参数,当在函数内部改变形式参数的值,实际参数也会跟着改变

#define _CRT_SECURE_NO_WARNINGS#include <stdio.h>#include <stdlib.h>/*
交换两个int*指针变量 形式参数的值
*/void swap_pointer(int* p_left,int *p_right) {//交换指针变量指向空间内容的值int p_tmp = *p_left;
*p_left = *p_right;
*p_right = p_tmp;

}/*
指针和函数
指针作为函数的形参可以改变实参的值
@author liuguanglei 18601767221@163.com
@wechat 18601767221
@website ittimeline.net
@version 2020/11/25
*/int main(int argc, char* argv[]){int left = 10;int right = 20;int* p_left_val = &left;int* p_right_val = &right;printf('指针变量交换之前 left = %d right = %d \n', left, right);
swap_pointer(p_left_val, p_right_val);printf('指针变量交换之后 left = %d right = %d \n', left, right);

system('pause');return 0;
}

程序运行结果

C开发实战-深入理解指针

数组作为函数的参数

数组作为函数的形参会退化为指针,即int data[] 退化为 int *data

有个遍历数组元素的需求

首先定义一个函数,函数的参数为整型数组

/* 遍历data[]数组元素的内容 */void print(int data[]) {int size = sizeof(data) / sizeof(data[0]);for (int i = 0; i < size; i ) {printf('data[%d] = %d\n', i, data[i]); } }

然后在main函数中调用函数

#define _CRT_SECURE_NO_WARNINGS#include <stdio.h>#include <stdlib.h>/*
数组作为函数的参数
@author liuguanglei 18601767221@163.com
@wechat 18601767221
@website ittimeline.net
@version 2020/11/25
*/int main(int argc, char* argv[]){int numbers[10] = {1,2,3,4,5,6,7,8,9,10};//数组名numbers代表首元素地址print(numbers);
system('pause');return 0;
}

当运行该程序时,结果只输出了numbers数组的第一个元素。

因为当调用print()方法时传递的numbers表示数组首元素的地址,而int data[]会退化成 int *data,data的类型是int *,它占据四个字节,即sizeof(data)的结果就是4个字节。
而sizeof(data[0])等价于sizeof(*(data 0)),data表示首元素的地址,data 0依然表示首元素的地址,*(data 0)表示首元素地址的内容,也就是1,sizeof(1)的结果也是4个字节。
4/4的结果是1,因此for循环遍历时只输出了数组的第一个元素。

程序运行结果

C开发实战-深入理解指针

正确遍历数组的姿势

首先定义方法print_array,方法中的参数分别是int* data和数组的大小

/* 遍历数组元素的内容 */void print_array(int *data,int array_size) { // printf('data数组占据%d个字节\n',sizeof(data));for (int i = 0; i < array_size;i ) {printf('data[%d] = %d\n',i,*(data i)); } }

然后在调用时传递数组首元素的地址和大小,就可以正常遍历数组

#define _CRT_SECURE_NO_WARNINGS#include <stdio.h>#include <stdlib.h>/*
数组作为函数的参数
@author liuguanglei 18601767221@163.com
@wechat 18601767221
@website ittimeline.net
@version 2020/11/25
*/int main(int argc, char* argv[]){int numbers[10] = {1,2,3,4,5,6,7,8,9,10};//数组名numbers代表首元素地址//print(numbers);print_array(numbers,sizeof(numbers)/sizeof(numbers[0]));
system('pause');return 0;
}

程序运行结果

C开发实战-深入理解指针

指针作为函数的返回值

在定义函数时,函数的返回值也可以是指针。

例如定义一个返回随机数的指针

/* 获取随机数 */int* get_rand_num () {//以当前时间的毫秒数设置随机数的种子数srand(time(NULL));int number = rand();//返回随机数的地址return &number; }

然后在main函数中调用随机数函数

#define _CRT_SECURE_NO_WARNINGS#include <stdio.h>#include <stdlib.h>int main(int argc, char* argv[]){int* p = get_rand_num();printf('当前生成的随机数是%d\n',*p);
system('pause');return 0;
}
C开发实战-深入理解指针

程序运行结果

但是这里有个问题,因为在get_rand_num()函数中定义了int类型的number来接收生成的随机数,而函数内部定义的变量叫局部变量,局部变量会随着get_rand_num()函数执行完成被系统释放内存空间。
一旦空间被释放,则不能再使用因为在get_rand_num()。

因此这里改进下程序,将number的定义放到函数外,当程序启动时,系统会为number开辟空间,而当程序结束时,系统才会释放。

#define _CRT_SECURE_NO_WARNINGS#include <stdio.h>#include <stdlib.h>/* 指针作为函数的返回值 @author liuguanglei 18601767221@163.com @wechat 18601767221 @website ittimeline.net @version 2020/11/25 *///在函数外面定义的变量叫全局变量 整个项目都可以使用,变量在程序启动时开辟空间,程序运行结束时释放空间int number = 0;/* 获取随机数 */int* get_rand_num() {//以当前时间的毫秒数设置随机数的种子数srand(time(NULL));//获取随机数//一旦get_rand_num()函数执行完成,局部变量num会被释放。//int num = rand();number = rand();//返回随机数的地址return &number; }int main(int argc, char* argv[]){int* p = get_rand_num();printf('当前生成的随机数是%d\n',*p); system('pause');return 0; }
收藏
举报
2 条评论

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多