c++ char*初始化_C开发实战-深入理解指针

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

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

c2fe0b8eda38231c3a74542aa772f423.png


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

8a736b1a76943b7fcd09bac5410cbdc8.png

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

bf478661b5de23733acb2ccbb91e0314.png

设置启动项

指针基础

指针与指针变量

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

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

首先回顾下变量的声明和赋值,这里以大家熟悉的整型变量定义为例int number; 表示在内存中开辟四个字节,假设number变量的内存地址是0x00000002number = 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", 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", p_array[10]);

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

#define _CRT_SECURE_NO_WARNINGS#include #include /*指针变量的定义,声明和初始化@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",p);//使用指针变量保存char类型变量的地址char ch = 'x';char *p_char=&ch;//输出指针变量p_char的内存地址printf("p_char = %p", 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", p_array[10]);system("pause");return 0;}

程序运行结果

0b3d6beb6f61ee4c5b5de9066c005887.png

指针变量的使用

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

#define _CRT_SECURE_NO_WARNINGS#include #include /*指针变量的使用@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",&number);printf("number的内存地址是%p",p);// 就可以通过p来操作number代表那块空间的内容//使用时*p表示取p指向number所代表那块内存空间的内容printf("*p = %d ",*p);//通过指针修改number的值*p = 80;printf("*p = %d ", *p);printf("number = %d ", number);int x = 20;//第二次指向x变量的内存空间p = &x;printf("*p = %d ", *p);printf("x = %d ", x);system("pause");return 0;}

程序运行结果

88fcf712eef475b2a6790317baa2a98f.png

赋值运算符(=)两边的类型必须一致,指针变量也是如此,对于指针变量和地址符(&)而言:在使用时(例如赋值)对一个表达式取*就会对表达式减一级*,如果对表达式取&,就会加一级*。
例如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 #include /*指针变量的大小@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个字节",sizeof(p_char));printf("指针变量 p_short的大小是%d个字节",sizeof(p_short));printf("指针变量 p_int的大小是%d个字节",sizeof(p_int));printf("指针变量 p_second_int的大小是%d个字节",sizeof(p_second_int));system("pause");return 0;}

x86表示32位编译器

4f8fb5a752eace9aca25513cb446a8c9.png

32位编译器


32位编译器运行效果

2e4e2d52b6ef212f40010bedb3da3e08.png

x64表示64位编译器

86548ddcc68fc7e54b775a8372d87238.png


64位编译器运行效果

6a1a1e3dbe5e5a317e933289b3f17821.png

指针的宽度和步长

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

#define _CRT_SECURE_NO_WARNINGS#include #include /*指针变量的宽度和步长@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 ", *p_char);printf("*p_short = %x ",*p_short);printf("*p_int = %x ", *p_int);//不同类型的指针获取的指针指向的内容是由指针变量的类型决定的//通过*取指针变量所指向那块空间内容时,取得内存宽度是和指针变量本身的类型有关系// p_char获取的一个字节的宽度,p_short获取的是2个字节的宽度,p_int获取的是4个字节的宽度//即将指针变量和指针变量最近的*去掉后的类型就是指针变量指向的内存表示的宽度printf("打印三个指针变量指向内容的宽度");printf("p_char指针指向内容的宽度是%u字节 ",sizeof(char));printf("p_short指针向内容的宽度是%u字节 ",sizeof(short));printf("p_int指针向内容的宽度是%u字节 ",sizeof(int));printf("int* 指针向内容的宽度是%u字节 ",sizeof(int*));//宽度也是步长//指针加1跨过多少个字节//打印三个指针变量的地址printf("打印三个指针变量的地址");printf("p_char = %p",p_char);printf("p_short = %p", p_short);printf("p_int = %p",p_int);//打印三个指针变量地址的宽度+1//步长为1printf("p_char+1 = %p", p_char + 1);//步长为2printf("p_short+1 = %p", p_short + 1);//步长为4printf("p_int+1 = %p", p_int + 1);system("pause");return 0;}

程序运行结果

387ec5d2ccd02d4308a6dee7b392d638.png

野指针

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

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

#define _CRT_SECURE_NO_WARNINGS#include #include /*野指针@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 ",*p);system("pause");return 0;}
726faeec80faa19f386909d362b469ba.png

C4700错误

空指针

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

#define _CRT_SECURE_NO_WARNINGS#include #include /*空指针:指针变量的值为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 ",*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 #include /*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",number);//此处编译不通过//number = 20;//但是可以通过指针来修改number变量的值int *p = &number;*p = 20;printf("通过指针修改number的值为%d",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 #include /*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",number);//此处编译不通过//number = 20;//但是可以通过指针来修改number变量的值int *p = &number;*p = 20;printf("通过指针修改number的值为%d",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 #include /*多级指针的定义与初始化定义多级指针保存数据的地址时,定义的指针类型只需要比要保存的数据类型多一级*即可。@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 ",**q);//如果*和&相邻,就相互抵消了printf("*&number =  %d ",*&number);//定义变量保存q的地址//三级指针的定义int ***u = &q;printf("***u = %d ",***u);system("pause");return 0;}

程序运行结果

74a8e09042833bff962584ff070f894f.png

指针操作数组

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

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

for(int i=0;i

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

for (int i = 0; i < sizeof(numbers)/sizeof(numbers[0]);i++) {//通过指针遍历数组的元素printf("numbers[%d] = %d ",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 ", *(ptr - 1));

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

指针操作数组的元素

#define _CRT_SECURE_NO_WARNINGS#include #include /*指针操作数组的元素@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("通过指针遍历数组的元素");for (int i = 0; i < size;i++) {//通过指针遍历数组的元素printf("numbers[%d] = %d ",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 ",*(data+1));//*(ptr-1)=5//表示整个data数组的地址加1,然后为了类型匹配转换成`int *`类型,而`*(ptr-1)`表示ptr原有的地址值减1后获取指向的内容为5int* ptr = (int*)(&data + 1);printf("*(ptr-1) = %d ", *(ptr - 1));system("pause");return 0;}

程序运行结果

8c62afbc7be45283f63268d2b285c536.png

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

#define _CRT_SECURE_NO_WARNINGS#include #include /*方括号[]在指针变量,指针数组的应用@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 ", number);int numbers[] = { 1,2,3,4,5,6,7,8,9,10 };int* p_array = numbers;for (int i = 0;i

程序运行结果

79283d5cffadde5d86fa0fd53fd529d0.png

指针的运算

指针可以进行加减运算

#define _CRT_SECURE_NO_WARNINGS#include #include /*指针的算术运算 @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 ",(q-p));// *(p+3) =4//因为p指向第一个数组的第一个元素printf("*(p+3) = %d ",*(p+3));//在C语言中两指针相加没有意义//printf("*(p+3) = %d ",(p+q));system("pause");return 0;}

程序运行结果

4cddf9f979633682dc53cdff27a033b3.png

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

指针数组

一级指针操作指针数组

整型数组:数组中存放多个整型元素,顾名思义指针数组,就是数组中存放多个指针。
指针数组的类型由指针指向元素的类型决定的,例如指针指向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个字节",sizeof(p_numbers));

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

printf("left = %d ",*p_numbers[0]);

指针数组的遍历

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

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

#define _CRT_SECURE_NO_WARNINGS#include #include /*一级指针数组的定义、初始化和遍历指针数组:数组中存放多个指针@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个字节",sizeof(p_numbers));printf("left = %d ",*p_numbers[0]);// 遍历指针数组,获取数组中的指针指向空间的内容int size = sizeof(p_numbers) / sizeof(p_numbers[0]);for (int i = 0; i < size;i++) {printf("*p_numbers[%d] = %d ",i,*(p_numbers[i]));}printf("");system("pause");return 0;}

程序运行结果

f027010e26ae706fb3b4fbf6767c7264.png

二级指针操作指针数组

当想要把一级指针数组以指针变量的方式保存在另外一个指针中,此时就需要使用到二级指针.
例如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 ", (**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 #include /*多级指针操作指针数组@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 ", (**p_first_element));//通过p_first_element获取middle的值//*(p_first_element+1)表示middle的地址//*(*(p_first_element+1))表示middle地址的内容printf("通过p_first_element获取middle的值 middle = %d ", (*(*(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 ", (*(*(p_first_element + 2))));//通过for循环获取p_first_element 的 left,middle,right值printf("通过for循环获取p_first_element 的 left,middle,right值");for (int i = 0; i < sizeof(p_numbers) / sizeof(p_numbers[0]); i++) {printf("%d ", **(p_first_element + i));}system("pause");return 0;}
9063c884a4254d36429d3113b4f5f869.png

程序运行效果

指针和函数

指针作为函数的形参

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

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

#define _CRT_SECURE_NO_WARNINGS#include #include /*交换两个整数形式参数的值单向值传递*/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 ",left,right);swap_variable(10,20);printf("普通变量交换之后 left = %d right = %d ", left, right);system("pause");return 0;}

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

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

#define _CRT_SECURE_NO_WARNINGS#include #include /*交换两个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 ", left, right);swap_pointer(p_left_val, p_right_val);printf("指针变量交换之后 left = %d right = %d ", left, right);system("pause");return 0;}

程序运行结果

8ea8293932d7d4281a630ab032fa2e13.png

数组作为函数的参数

数组作为函数的形参会退化为指针,即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", i, data[i]);}}

然后在main函数中调用函数

#define _CRT_SECURE_NO_WARNINGS#include #include /*数组作为函数的参数@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循环遍历时只输出了数组的第一个元素。

程序运行结果

be782156a987538f9c18d4f6640ce4dd.png

正确遍历数组的姿势

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

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

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

#define _CRT_SECURE_NO_WARNINGS#include #include /*数组作为函数的参数@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;}

程序运行结果

3a619963d66e94fe8a421aaaa835a79b.png

指针作为函数的返回值

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

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

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

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

#define _CRT_SECURE_NO_WARNINGS#include #include int main(int argc, char* argv[]){int* p = get_rand_num();printf("当前生成的随机数是%d",*p);system("pause");return 0;}
fe4645f2249138fa9c8a825840e766f9.png

程序运行结果

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

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

#define _CRT_SECURE_NO_WARNINGS#include #include /*指针作为函数的返回值@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",*p);system("pause");return 0;}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/433609.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

java游戏服务器面试_我做游戏开发这八年

点击上方“CSDN学院精品课”&#xff0c;选择“置顶公众号”CSDN学院精品课 IT人的职业提升平台作者 | kakashi8841简述这篇文章并不是想教会大家如何开发游戏&#xff0c;更不能教大家如何成为技术总监。而是分享我一路做开发的一些经历或心得体验。与编程擦肩而过2004年&…

SSM(Spring、SpringMVC、MyBatis)框架笔记——Spring入门

一、Spring简介 Spring是分层的Java SE/EE应用full-stack 轻量级开源框架&#xff0c; 以IoC&#xff08;Inverse Of Control&#xff1a;反转控制&#xff09;和AOP&#xff08;Aspect Oriented Programming&#xff1a;面向切面编程&#xff09;为内核。提供了展现层SpringM…

c++ 字符串拼接_字符串拼接新姿势:StringJoiner

来自&#xff1a;Hollis(微信号&#xff1a;hollischuang)在为什么阿里巴巴不建议在for循环中使用””进行字符串拼接一文中&#xff0c;我们介绍了几种Java中字符串拼接的方式&#xff0c;以及优缺点。其中还有一个重要的拼接方式我没有介绍&#xff0c;那就是Java 8中提供的S…

html5 内嵌网页_如何分析并优化网页的性能?新梦想软件测试

一个网站的网页是好是坏&#xff0c;往往是体现在速度和高度两个方面&#xff0c;速度是网页所展示出来的时间&#xff0c;能否为浏览用户节约时间。高度则是一个网站网页本身的质量&#xff0c;能否为浏览用户带来真正的好体验。然而看似简单的两个点&#xff0c;但是背后要实…

怎么修改file文件框的无文件提示_使用LativeLink时,DO文件编制步骤

大侠好&#xff0c;欢迎来到FPGA技术江湖&#xff0c;江湖偌大&#xff0c;相见即是缘分。大侠可以关注FPGA技术江湖&#xff0c;在“闯荡江湖”、"行侠仗义"栏里获取其他感兴趣的资源&#xff0c;或者一起煮酒言欢。今天和大侠简单聊聊使用LativeLink时&#xff0c;…

Spring笔记——数据源配置

常见的数据源&#xff08;连接池&#xff09;包括&#xff1a;DBCP、C3P0、BoneCP、Druid等接下来以C3P0为例讲述一下spring配置数据源的过程数据源的开发步骤 ①导入数据源的坐标和数据库驱动坐标 ②设置数据源的基本连接数据&#xff0c;将其放入jdbc.properties文件中 ③app…

间歇性掉帧卡顿_电脑卡顿问题靠它解决,我只能帮你到这儿了

因为不可抗力的原因&#xff0c;我们都要在家里呆上一段时间&#xff0c;在这期间之中如果电脑出现问题可是一件非常糟心的事情&#xff0c;因为没有人会去给你上门维修&#xff0c;线下门店又关门大吉&#xff0c;一切只能靠自己。抢救一下电脑最常见的问题就是卡顿&#xff0…

SpringMVC遇到的问题——GET http://localhost/spring_mvc_war_exploded/js/jquery-3.3.1.js net::ERR_ABORTED 404

学习SSM时遇到的问题GET http://localhost/spring_mvc_war_exploded/js/jquery-3.3.1.js net::ERR_ABORTED 404新建了js包&#xff0c;并在包下导入了jquery-3.3.1/js&#xff0c;发布项目后通过浏览器开发者工具 发现报错&#xff0c;信息如下&#xff1a;这个问题的解决方案&…

educoder平台_22个在线平台,2.4万门网课

截至2020年2月2日&#xff0c;教育部组织了22个在线课程平台制定了多样化在线教学解决方案&#xff0c;免费开放包括1291门国家精品在线开放课程和401门国家虚拟仿真实验课程在内的在线课程2.4万余门&#xff0c;覆盖了本科12个学科门类、专科高职18个专业大类&#xff0c;供高…

linspace函数matlab_从零开始的matlab学习笔记——(29)泰勒逼近函数

matlab应用——求极限&#xff0c;求导&#xff0c;求积分&#xff0c;解方程&#xff0c;概率统计&#xff0c;函数绘图&#xff0c;三维图像&#xff0c;拟合函数&#xff0c;动态图....更多内容尽在个人专栏&#xff1a;matlab学习上一节我们成功制作了能自己转圈的三维螺旋…

点云数据生成三维模型_可直接编辑的高质量3D生成模型:三维深度生成方法SDM-NET...

机器之心发布作者&#xff1a;赵悠悠中科院计算所、香港城市大学、英国卡迪夫大学以及加拿大西蒙弗雷泽大学的研究者&#xff0c;近日提出了一种能够表达几何细节和复杂拓扑结构的三维模型深度生成方法 SDM-NET&#xff0c;解决了之前方法的一部分局限性&#xff0c;使得生成的…

golang调用java的函数_大话golang性能分析(一):profile基本原理

引言&#xff1a;好久没分享了&#xff0c;不多废话了&#xff0c;准备一个专题分三期来分享下golang的性能分析。O 专题目标理解profile基本原理熟悉go常用性能分析工具pprof快速对线上服务的cpu、内存、goroutine的问题进行分析和排查对性能分析&#xff0c;golang是采取采样…

奥拉星插件flash下载手机版下载安装_终于等到你!安卓微信7.0.13内测版发布 支持夜间模式 附下载地址!...

3月22日&#xff0c;iOS版微信迎来了7.0.12正式版更新&#xff0c;最大的亮点在于为iOS13设备加入了“深色模式”功能&#xff0c;虽然没有独立的控制开关&#xff0c;但可以跟随系统开启或关闭夜间模式。此外&#xff0c;iOS版微信7.0.12还优化了语音消息的发送体验&#xff0…

boost::weak_ptr和enable_shared_from_this

boost::weak_ptr和enable_shared_from_this shared_ptr在我的实践中使用很广,在接口层面上,我基本都会默认的使用shared_ptr.而weak_ptr则很少使用;即便使用,也是间接的使用,例如使得class A继承自boost::enable_shared_from_this,这样A便具有了一个weak_ptr的成员对象,我便可以…

linux(centos7)安装jdk

一、下载jdk1.8压缩包 官网链接如下&#xff1a; jdk官网 用xftp直接传输文件&#xff0c; 把下载好的jdk压缩包上传到Linux的文件夹下去到我们存放软件的文件夹下面&#xff0c;将刚刚下载好的压缩包通过如下命令进行解压&#xff1a; tar -zxvf jdk-8u321-linux-x64.tar.g…

inner join 和join的区别_left join、right join和join ???

点击上方“JAVA”&#xff0c;星标公众号重磅干货&#xff0c;第一时间送达有个困扰 说到SQL&#xff0c;很多人可能用了挺久&#xff0c;但依然有个问题一直困扰着&#xff0c;那就是 left join、 join、 right join和 inner join等等各种 join的区别。网上搜&#xff0c;最常…

linux(centos7)安装MySQL

一、删除MariaDB的文件 通过命令查询MariaDB&#xff1a;rpm -pa | grep mariadb 通过命令删除查找到的程序rpm -e 查找到的文件名 如果删除失败&#xff0c;就通过下面的命令强制删除&#xff1a;rpm -e --nodeps 查找到的文件名 二、安装MySQL 1、安装mysql repo源 …

我有机器人合体成一个大力神_史上最菜大力神(三)——SS加强级高塔

史上最菜大力神&#xff08;三&#xff09;——SS加强级高塔​mp.weixin.qq.com上次开箱废渣和狂暴的时候&#xff0c;误把电影中出现的高塔当作废渣&#xff0c;犯下了一个不大不小的错误&#xff0c;如今真正的高塔终于来了。史上最菜大力神&#xff08;一&#xff09;——SS…

c语言判断闰年_C语言1博客作业06 - D丶千思

1.作业头实验作业输入在一行中按照格式“yyyy/mm/dd”(即“年/月/日”)给出日期。注意&#xff1a;闰年的判别条件是该年年份能被4整除但不能被100整除、或者能被400整除。闰年的2月有29天。.1数据处理数据表达&#xff1a;采用整形定义变量Y(year)&#xff0c;M(month)&#x…