C语言中的指针(上)

目录

一、基本概念

1.变量的存储空间

2.定义指针

3.引用与解引用

二、指针的算术运算、类型以及通用指针

1.指针的算数运算

2.指针类型以及通用型指针

三、指向指针的指针(pointers to pointers)

四、函数传值以及传引用

1.局部变量

2.从存储地址出修改局部变量

五、指针和数组以及数组作为函数参数

1.指针与数组

2.数组作为函数参数

六、指针和字符数组

1.创建一个字符数组

2.字符数组作为函数参数

3.字符数组的存储方式


一、基本概念

1.变量的存储空间

       通常我们所说的内存是计算机的随机内存即RAM,并且不同的数据类型分配到的存储空间的大小也各不相同;整数类型数据 int 分配到的存储空间大小为 4bytes,字符类型的数据 char 分配到的存储空间为 1bytes,浮点数 float 分配到的存储空间为 4bytes。

2.定义指针

      指针的本质就是一个整数类型的变量,用来存放被指向数据的地址,即 int *p = &a; 代表的是创建一个指针 p 用来存储变量 a 的地址,其中 & 代表的是取得数据 a 的地址。

#include<stdio.h>int main()
{int a = 3; int *p = &a;printf("%d\n", p); return 0;}

运行上面的代码得到的结果为:

即数据 a 的存储地址为 e21ff898。

3.引用与解引用

       在定义指针时使用到的 * 表示的是引用,即得到被指向数据的地址,而在指针的前面使用 * 表示的是解引用,用来表示被指向数据的值。

#include<stdio.h>int main()
{int a = 3; int *p = &a;printf("%d\n", p);printf("%d\n", *p);  //输出p代表的地址的值,即a的值,为3return 0;
}

运行上面的代码,前一行打印语句的输出为 a 的地址,由于第二行打印是对指针 p 进行反引用,所以第二行打印语句的输出为 a 的值 3。

二、指针的算术运算、类型以及通用指针

1.指针的算数运算

       指针代表的是被指向数据的地址,因此指针的值加一或者减一,并不会使得被指向数据的值产生相应的加一或者减一的效果,而且指针加一或者减一,则指向的地址进行反引用得到的数据将会是乱码,而不是一个确切的值。

#include<stdio.h>int main()
{int a = 3; int *p = &a;printf("Address of a is : %d.\n", p);printf("size of integer is: %d bytes.\n", sizeof(int));printf("value of (p + 1) is : %d.\n", p + 1); //如果p = 2004,则 p+1 = 2008;这是因为整型的地址占用为4个字节printf("value at address of p is : %d.\n", *p);printf("value at address of (p + 1) is : %d.\n", *(p + 1)); //得到的是一个垃圾值return 0;
}

运行上面的代码,得到的结果如下:

从上面的结果中可以看出,由于整型数据的存储大小为4个字节,因此对指针 p 进行加一的操作得到的数据是在原来的 p 的值上加了4,而不是单纯的加一操作。并且对 p+1 进行解引用得到的数值将会是一个垃圾值。

2.指针类型以及通用型指针

       指针是强类型的(这是因为指针不止用来存储地址,也可以用它来解引用那些地址所对应的内容),也就是说不同的数据类型需要相对应类型的指针来进行指向;比如一个整型变量,指针指向的是第一个字节的位置,则计算机读取时向后再数三个字节得到该变量的值,若是使用其它类型的指针,则无法正确完成指向;虽然 float 类型的数据也分配有四个字节,但它和整型的数据并不相同,因此利用整型的指针并没有办法正确存储 float 类型的数据。

#include<stdio.h>int main()
{int a = 3; int *p = &a;float *p0 = &a;printf("While int pointer is used, address of a is : %d, value of a is ; %d;\n", p, *p);//172 = 1010 1100printf("While float pointer is used, address of a is : %d, value of a is ; %d;\n\n", p0, *p0); //可以看出两种类型的指针对应的地址是一样的,但值不一样return 0;
}

运行上面的代码,得到的结果如下:

可以从结果中看出,对于整数类型的数据,使用浮点数类型的指针会得到错误的指向结果。

       void 类型的指针,也被称为通用型指针,由于通用指针没有使用到任何类型的定义,因此不能对其进行反解。

#include<stdio.h>int main()
{int a = 3; int *p = &a;void *p0 = p; //通用指针没有使用到任何类型的定义,因此不能对其进行反解printf("While int pointer is used, address of a is : %d, value of a is ; %d;\n", p, *p);//172 = 1010 1100printf("While void pointer is used, address of a is : %d, value of a is ; %d;\n\n", p0, *p0); //可以看出两种类型的指针对应的地址是一样的,但值不一样return 0;
}

运行上面的代码,则会发现程序报错:

通用型指针,不会指向任何类型的地址,故无法进行解引用。

三、指向指针的指针(pointers to pointers)

       指针是用来表示被指向数据的地址的,故指针的本质是一个整型的变量,所以指向指针的指针的数据类型也是整型。

        当定义了一个指针 int *p,则可以用 int **q 来存储指针 p 的地址,也可以使用 int ***r来存储指针 q 的地址,以此类推即可。

#include<stdio.h>int main()
{int a = 3;int *p = &a;printf("Address of data a is : %d.\n", p);int **P = &p;int ***q = &P;printf("Address of pointer p is : %d.\n", P);printf("Value of p(Address of a) is : %d.\n", *P);printf("Value of a is : %d.\n", *(*P));printf("Address of P is : %d.\n", q);printf("Value of P(address of p) is : %d.\n", *q);printf("Value of p(address of a) is : %d.\n", *(*q));printf("Value of data a is : %d.\n", ***q);***q = 10;printf("a = %d\n", a);**q = *p +2;printf("%d\n", **q);return 0;
}

运行上述代码得到的结果为:

在上面的代码中使用 p 来作为指针指向数据 a 的地址,P 作为指针来指向指针 p 的地址,q 作为指针来指向指针 P 的地址;通过解引用可以利用指针来改变被指向数据的值。

四、函数传值以及传引用

1.局部变量

       当我在一个函数中声明一个变量,那么该变量称为局部变量,也就是说只能在该函数中对该变量进行修改以及处理。

#include<stdio.h>int Increment(int a)
{a = a + 1;  
}int main()
{int a = 3;Increment(a);printf("a = %d.\n", a);return 0;
}

运行上面的代码,若不考虑自定义函数 Increment() 中的局部变量的影响,则可以推测输出的 a 的值应该为 4。但通过运行代码得到的结果如下:

这说明在自定义函数中 a 是一个局部变量,和主函数中的 a 并不是同一个变量,也就是说在自定义函数中的变量存储的地址和主函数中的变量存储的地址是不一样的,所以我们需要从指针的层面对它们进行修改。

2.从存储地址出修改局部变量

由于局部变量和主函数中的变量的存储地址不相同,因此可以使用指针从变量的地址处进行处理。

#include<stdio.h>void Increment(int *p){*p = (*p) + 1;}int main()
{int a = 3;int *p = &a;Increment(&a);printf("a = %d.\n", a);return 0;
}

运行上面的代码,可以实现利用自定义函数对主函数中的变量进行修改。

        在自定义函数中的参数被称为形参,主函数中的参数称为实参,自定义函数的调用过程中实现了将实参转化到形参中进行处理的目的,这就是函数的调用。利用指针以及解引用进行处理的时候能够实现从参数的存储地址处修改参数而不是从参数的值处修改参数,从而达到处理数据的目的,这种方法被称为传引用。

五、指针和数组以及数组作为函数参数

1.指针与数组

       定义一个数组后,使用指针指向该数组时,指针存储的是数组中的第一个元素的地址。数组名本身也可以用来代表一个地址,在使用指针时甚至可以不用取地址符,即可以写为 int *p = &A[i]或者 int *p = A + i。

#include<stdio.h>int main()
{int b[] = {0, 1, 2, 3, 4};int *P = &b;       //得到的是数组中的第一个元素的地址,在此处甚至可以不用对数组b取地址int *P0 = &b[0];   //得到第一个元素的地址,此处也可以写成: int *P0 = b + 1;后面的写法类推int *P1 = &b[1];   //得到第二个元素的地址,与第一个元素的地址相差4个字节int *P2 = &b[2];   //与前一个元素的地址相差4个字节int *P3 = &b[3];   //与前一个元素的地址相差4个字节int *P4 = &b[4];   //与前一个元素的地址相差4个字节printf("Address of P is : %d.\n", P);printf("Address of P0 is : %d.\n", P0);printf("Address of P1 is : %d.\n", P1);printf("Address of P2 is : %d.\n", P2);printf("Address of P3 is : %d.\n", P3);printf("Address of P4 is : %d.\n", P4);//利用该循环使用两种不同的方法输出数组及地址for(int j = 0; j < 5; j++){printf("%d:\n", j + 1);printf("Address  = %d\n", &b[j]);  //直接取数组元素地址即可printf("Value = %d\n", b[j]);      //打印数组元素printf("Address = %d\n", b + j);   //数组名可以直接代表元素地址printf("Value = %d\n", *(b + j));  //对数组名解引用得到数组元素的值}P0 = P0 + 1;printf("%d\n", *P0);  //输出的值为b[1]的值,即输出1return 0;
}

运行上面的代码得到的输出为:

       从运行结果中可以看出,数组名可以直接作为地址进行指针指向,且 *p = A + i 和 *p = A[i] 的运行效果是一样的。

2.数组作为函数参数

       当数组作为一个参数时,编译器不会分配整个数组长度的空间用来进行存储,而是只分配出一个同名的指针的存储空间,即将 A 转化成为了 *A,并没有拷贝变量的值,而是直接拷贝了变量的地址,也就是说函数在传递变量的过程就是一个引用的过程。并且不同的操作系统中指针分配到的存储空间是不一样的,64位操作系统中占用了8个字节,32位操作系统中占用了4个字节。

尝试编写函数来计算数组的大小:

int SumofElements(int A[]) 
{int i, sum = 0;int size = sizeof(A)/sizeof(A[0]); //数组整体字节数目比上一个元素的字节数目printf("SOE - Size of A = %d, size of A[0] = %d.\n",sizeof(A), sizeof(A[0]));for(i = 0; i < size; i++){sum += A[i];}return sum;
}

       上面这个函数尝试以数组作为函数形参,并在函数中使用 for 循环来计算数组中的各个元素之和,但运行该自定义函数后发现计算结果是错误的。

#include<stdio.h>int SumofElements(int A[]) 
{int i, sum = 0;int size = sizeof(A)/sizeof(A[0]); //数组整体字节数目比上一个元素的字节数目printf("SOE - Size of A = %d, size of A[0] = %d.\n",sizeof(A), sizeof(A[0]));for(i = 0; i < size; i++){sum += A[i];}return sum;
}int main()
{//数组作为函数参数int A[] = {1, 2, 3, 4, 5};int size = sizeof(A)/sizeof(A[0]); //数组整体字节数目比上一个元素的字节数目int total = SumofElements(A);printf("Main - Size of A = %d, size of A[0] = %d.\n",sizeof(A), sizeof(A[0]));printf("Sum of elements = %d.\n", total);return 0;
}

       从运行的结果中可以看出,该函数在计算数组中的元素之和时发生了错误,这是因为在作为函数参数时数组名被默认为是一个指针,而不是整个数组。

对自定义函数进行改写:

#include<stdio.h>int SumofElements(int A[], int size) 
{int i, sum = 0;for(i = 0; i < size; i++){sum += A[i];}return sum;
}int main()
{int A[] = {1, 2, 3, 4, 5};int size = sizeof(A)/sizeof(A[0]); //数组整体字节数目比上一个元素的字节数目int total = SumofElements(A, size);printf("Main - Size of A = %d, size of A[0] = %d.\n",sizeof(A), sizeof(A[0]));printf("Sum of elements = %d.\n", total);return 0;
}

运行得到的结果为:

可以看出该计算结果是正确的。

六、指针和字符数组

1.创建一个字符数组

       在 C 语言中,创建一个字符数组需要的长度一般要比存储在该数组中的元素的个数多一,比如创建一个字符数组其中包含 john 这个字符串,那么可以将该字符数组的长度设置为5或者大于5即可,这是因为在字符数组中存储字符串之后,计算机并不知道字符串中的最后一个字母在哪一位上,故需要在最后一个元素的后面加上一个 NULL('\0')元素,用来指明字符串的结束,也就是说字符数组是以 '\0' 结尾的。

#include<stdio.h>int main()
{char C[4];C[0] = 'j';C[1] = 'o';C[2] = 'h';C[3] = 'n';printf("%s", C);return 0;
}

运行上面的代码得到的结果为:

       可以看出,由于没有将字符数组的长度设置为大于字符串长度的值,打印出来的字符串没有一个合适的结尾。因此对代码进行修改:

#include<stdio.h>int main()
{char C[4];C[0] = 'j';C[1] = 'o';C[2] = ‘h';C[3] = 'n';C[4] = '\0';printf("%s", C);return 0;
}

得到正确的结果为:

       但是无论字符数组的长度设置为多大, 函数 sizeof() 都不会考虑字符串末尾添加的 NULL 元素,也就是说使用该函数得到的数组的长度均为其中的字符串的长度。

       如果不想在定义字符数组的时候添加末尾的 NULL 元素,可以在定义字符数组的时候直接编写好其中需要增加的字符串,这样得到的字符数组进行打印不会出现错误的值。

#include<stdio.h>int main()
{char C[] = "john";printf("%s", C);return 0; 
}

2.字符数组作为函数参数

       当字符数组作为函数参数时,效果和一般的数组是一样的,也就是说字符数组在函数中也是被传引用,函数中传递的只是该数组的基地址(首个元素的地址),并不会将所有元素的地址在函数中进行传递。

#include<stdio.h>int print(char *C)
{int i = 0;while(C[i] != '\0')    //此处注意是单引号{printf("%c", C[i]); i++;}printf("\n");
}int main()
{   char C[20] = "HELLO"char* A;A = C;print(A); return 0;
}

 运行上面的代码得到的结果为:

 也可以将上面代码中的自定义函数进行修改,将其中的打印方式修改如下:

printf("%c", *(C + i));

效果和前面的是一样的,因为上面的修改是对数组指针进行了解引用。

还可以对自定义函数进行如下的修改:

int print(char *C)
{while(*C != '\0'){ printf("%c", *C);C++;}printf("\n");
}

得到的结果和前面仍然是一样的。

3.字符数组的存储方式

#include<stdio.h>int print(char *C)
{while(*C != '\0'){printf("%c", *C);C++;}printf("\n");
}int main()
{char C[20] = "HELLO";print(C);return 0;
}

上面的这部分代码和之前的一样,运行的时候得到的结果如下:
 而且这部分代码可以在自定义函数中对字符串进行修改:

#include<stdio.h>int print(char *C)
{C[0] = 'A';while(*C != '\0'){printf("%c", *C);C++;}printf("\n");
}int main()
{char C[20] = "HELLO";//char *C = "HELLO";print(C);return 0;
}

 得到的运行结果为:

从结果中可以看出,在自定义函数中实现了对字符串的修改。

主函数中的字符串也可以使用其他的定义方式:

#include<stdio.h>int print(char *C)
{while(*C != '\0'){printf("%c", *C);C++;}printf("\n");
}int main()
{char *C = "HELLO";print(C);return 0;
}

运行得到的结果为:

 尝试在自定义函数中对字符串进行修改:

#include<stdio.h>int print(char *C)
{C[0] = 'A';while(*C != '\0'){printf("%c", *C);C++;}printf("\n");
}int main()
{char *C = "HELLO";print(C);return 0;
}

运行上述代码会发现程序无法输出:

也就是说使用下面的定义字符串的方式时,无法在自定义函数中对字符串进行修改。

这是因为,当使用上面的方式定义字符串时,是直接将整个数组存储在一起,因此可以对其中的一两个元素进行修改;但当使用下面的方式定义字符串时,时将字符串的地址作为变量进行传输,没办法在自定义函数中对其中的某个元素进行修改。

到这里算是总结完指针中的部分内容了,还有一大堆需要学习的东西,真的是非常的棘手。

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

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

相关文章

gitlab环境准备

1.准备环境 gitlab只支持linux系统&#xff0c;本人在虚拟机下使用Ubuntu作为操作系统&#xff0c;gitlab镜像要使用和操作系统版本对应的版本&#xff0c;(ubuntu18.04,gitlab-ce_13.2.3-ce.0_amd64 .deb) book100ask:/$ lsb_release -a No LSB modules are available. Dist…

机器学习二元分类 二元交叉熵 二元分类例子

二元交叉熵损失函数 深度学习中的二元分类损失函数通常采用二元交叉熵&#xff08;Binary Cross-Entropy&#xff09;作为损失函数。 二元交叉熵损失函数的基本公式是&#xff1a; L(y, y_pred) -y * log(y_pred) - (1 - y) * log(1 - y_pred)其中&#xff0c;y是真实标签&…

中贝通信-603220 三季报分析(20231120)

中贝通信-603220 基本情况 公司名称&#xff1a;中贝通信集团股份有限公司 A股简称&#xff1a;中贝通信 成立日期&#xff1a;1999-12-29 上市日期&#xff1a;2018-11-15 所属行业&#xff1a;软件和信息技术服务业 周期性&#xff1a;1 主营业务&#xff1a;通信网络技术服务…

通信网络安全防护定级备案流程介绍(附流程图)

通信网络安全防护定级备案是拥有增值电信业务经营许可证并且有开展电信业务的企业要做的一件事情。刚接触这块的家人们在填报操作的时候可能对具体通信网络安全防护定级备案流程还不是很清楚&#xff0c;所以就给大家画张具体的流程图吧&#xff0c;可以更加直观的了解。 通信…

go语言学习-go环境安装

1、安装Go 1.1 下载安装 go官网 找对应电脑的版本进行安装即可。 点击安装包&#xff0c;直接下一步下一步即可&#xff0c;安装目录可以自行设置一下。 1.2 验证 windows通过cmd验证。 linux或者mac可以通过自带终端执行测试。 2、配置环境变量 2.1 windows 找到系统…

HarmonyOS开发(四):UIAbility组件

1、UIAbility概述 UIAbility 一种包含用户界面的应用组件用于与用户进行交互系统调度的单元为应用提供窗口在其中绘制界同 注&#xff1a;每一个UIAbility实例&#xff0c;都对应一个最近任务列表中的任务。 一个应用可以有一个UIAbility也可以有多个UIAbility。 如一般的…

深度学习YOLO安检管制物品识别与检测 - python opencv 计算机竞赛

文章目录 0 前言1 课题背景2 实现效果3 卷积神经网络4 Yolov55 模型训练6 实现效果7 最后 0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; **基于深度学习YOLO安检管制误判识别与检测 ** 该项目较为新颖&#xff0c;适合作为竞赛课题方向&…

【论文阅读】SPARK:针对视觉跟踪的空间感知在线增量攻击

SPARK: Spatial-Aware Online Incremental Attack Against Visual Tracking introduction 在本文中&#xff0c;我们确定了视觉跟踪对抗性攻击的一个新任务&#xff1a;在线生成难以察觉的扰动&#xff0c;误导跟踪器沿着不正确的&#xff08;无目标攻击&#xff0c;UA&#x…

设计模式--模板方法外观模式

模板方法模式 场景&#xff1a;需使用代码方式实现&#xff0c;考完试后&#xff0c;将各个学生的试卷及答案誊抄一份。 假如有两个学生的试卷誊抄完毕. // 学生A public class TestPaperA {// 试题1public void testQuestion1() {System.out.println("问题一:XXXXXXXX…

《opencv实用探索·一》QT+opencv实现图片拼接和Mat转QImage

本文利用opencv实现了几个好用的功能&#xff0c;包含两个文件&#xff0c;如下&#xff1a; 源码放在文章末尾 imageProcessing类包含三个功能&#xff1a; 1、图像拼接 cv::Mat imageMosaic(cv::Mat mat1, cv::Mat mat2, MosaicMode mosaicMode);mat1和mat2为两个待拼接的…

Matplotlib实现Label及Title都在下方的最佳姿势

Matplotlib实现Label及Title都在下方的最佳姿势 1. 问题背景2. 基本思想&#xff08;可以不看&#xff09;3. 方法封装4. 调用实例5. 总结6. 起飞 1. 问题背景 用python绘制下面这种图的时候&#xff0c;一般用xlable作为子图的标题&#xff0c;这是因为plt.title()方法绘制的…

Android studio run 手机或者模拟器安装失败,但是生成了debug.apk

错误信息如下&#xff1a;Error Installation did not succeed. The application could not be installed&#xff1a;List of apks 出现中文乱码&#xff1b; 我首先尝试了打包&#xff0c;能正常安装&#xff0c;再次尝试了debug的安装包&#xff0c;也正常安装&#xff1…

再谈谷歌GMS认证之Android 13

写在前面的话 2023年来到一个新的公司&#xff0c;传说中的做互联网金融即将上市的高大上公司。 入职后才发现就是做pos机设备的一个小厂 哎&#xff0c;什么命啊&#xff01; 工作和手机开发的工作重合度可以达到95%以上&#xff0c;我不想做手机&#xff0c;偏偏又干上…

计算机基础知识54

ORM的介绍 # ORM是什么&#xff1f; 我们在使用Django框架开发web应用的过程中&#xff0c;不可避免地会涉及到数据的管理操作&#xff08;增、删、改、查&#xff09;&#xff0c;而一旦谈到数据的管理操作&#xff0c;就需要用到数据库管理软件&#xff0c;例如mysql、oracle…

Ubuntu20.0中安装Gradle

下载Gradle到temp文件夹 wget https://services.gradle.org/distributions/gradle-8.3-bin.zip -P /tmp 然后解压文件到/opt/gradle目录 sudo unzip -d /opt/gradle /tmp/gradle-8.3.zip 配置Gradle环境变量 接下来我们会创建一个gradle.sh文件来保存Gradle的环境变量 sudo…

ubuntu20.04蓝牙连接airpods

ubuntu20.04蓝牙连接airpods 解禁蓝牙安装blueman设置模式连接上没有声音的问题 解禁蓝牙 sudo rmmod btusb sleep 1 sudo modprobe btusb sudo /etc/init.d/bluetooth restart安装blueman sudo apt install blueman sudo apt-get install pulseaudio-module-bluetooth sudo …

『亚马逊云科技产品测评』活动征文|构建生态农场家禽系统

『亚马逊云科技产品测评』活动征文&#xff5c;构建生态农场家禽系统 授权声明&#xff1a;本篇文章授权活动官方亚马逊云科技文章转发、改写权&#xff0c;包括不限于在 Developer Centre, 知乎&#xff0c;自媒体平台&#xff0c;第三方开发者媒体等亚马逊云科技官方渠道 前…

VBA如何快速识别Excel单元格中的文本数字

Excel中一种非常特殊的数字&#xff0c;这些数字看似数字&#xff0c;其实是文本格式&#xff08;下文简称为文本数字&#xff09;&#xff0c;在单元格的左上角会有一个绿色小三角作为标志&#xff0c;如B1:B3单元格。 在编程时为什么需要区分普通数字和文本数字呢&#xff…

SVG圆形 <circle>的示例代码

本专栏是汇集了一些HTML常常被遗忘的知识&#xff0c;这里算是温故而知新&#xff0c;往往这些零碎的知识点&#xff0c;在你开发中能起到炸惊效果。我们每个人都没有过目不忘&#xff0c;过久不忘的本事&#xff0c;就让这一点点知识慢慢渗透你的脑海。 本专栏的风格是力求简洁…

.NET 8.0 AOT 教程 和使用 和 .NET ORM 操作

NET AOT编译是一种.NET运行时的编译方式&#xff0c;它与传统的JIT编译方式不同。在传统的JIT编译中&#xff0c;.NET应用程序的代码在运行时才会被编译成本地机器码&#xff0c;而在AOT编译中&#xff0c;代码在运行之前就被提前编译成本地机器码。这样可以在代码运行的时候不…