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.rhkb.cn/news/199364.html

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

相关文章

Failed to execute org.scala-tools:maven-scala-plugin:2.15.2解决

原因也不是很清楚&#xff0c;查看一个博主文章(net.alchim31.maven:scala-maven-plugin&#xff1a;maven依赖无法下载或无法编译)得到的解决方案&#xff1a; 在idea的terminal执行以下语句即可实现maven对scala代码的编译&#xff1a; mvn clean scala:compile compile pac…

代码随想录算法训练营第四十一天【动态规划part03】 | 343. 整数拆分、96.不同的二叉搜索树

343. 整数拆分 题目链接&#xff1a; 力扣&#xff08;LeetCode&#xff09;官网 - 全球极客挚爱的技术成长平台 求解思路&#xff1a; 动规五部曲 确定dp数组及其下标含义&#xff1a;dp[i] 拆分i&#xff0c;可以得到的最大乘积为dp[i]确定递推公式&#xff1a;从1开始遍…

测试Bard和ChatGPT关于双休的法规和推理

Bard是试验品&#xff0c;chatgpt是3.5版的。 首先带着问题&#xff0c;借助网络搜索&#xff0c;从政府官方网站等权威网站进行确认&#xff0c;已知正确答案的情况下&#xff0c;再来印证两个大语言模型的优劣。 想要了解的问题是&#xff0c;在中国&#xff0c;跟法定工作…

linux rsyslog综合实战1

本次我们通过rsyslog服务将A节点服务器上的单个日志(Path:/var/log/245-1.log)实时同步到B节点服务器目录下(Path:/opt/rsyslog/245) 1.rsyslog架构 2.环境信息 环境信息 HostnameIpAddressOS versionModuleNotersyslog1192.168.10.245CentOS Linux release 7.9.2009 (Core)rs…

原理Redis-Dict字典

Dict 1) Dict组成2) Dict的扩容3) Dict的收缩4) Dict的rehash5) 总结 1) Dict组成 Redis是一个键值型&#xff08;Key-Value Pair&#xff09;的数据库&#xff0c;可以根据键实现快速的增删改查。而键与值的映射关系正是通过Dict来实现的。 Dict由三部分组成&#xff0c;分别…

ES6有何新特性?(下篇)

目录 函数参数的默认值设置 rest参数 扩展运算符 Symbol 迭代器 生成器 Promise Class 数值扩展 对象方法扩展 模块化 大家好呀&#xff01;今天这篇文章继续为大家介绍ES6的新特性&#xff0c;上上上篇文章介绍了一部分&#xff0c;这篇文章会将剩下的部分新增的特…

【CHI】Ordering保序

本节介绍CHI协议所包含的支持系统保序需求的机制&#xff0c;包括&#xff1a; • Multi-copy atomicity • Completion response and ordering • Completion acknowledgment • Transaction ordering 一、 Multi-copy atomicity CHI协议中所使用的memory model要求为mu…

怎么在echarts图上左右滑动切换数据区间

说在前面 不管前端还是后端&#xff0c;大家或多或少都了解使用过echarts图表吧&#xff0c;很多时候我们只是需要展示指定区间的数据&#xff0c;但有时我们希望在图表上能够轻松地切换数据的展示区间&#xff0c;以便更清晰地观察特定时间段或区域的变化。在本文中&#xff0…

电子学会C/C++编程等级考试2022年06月(一级)真题解析

C/C++等级考试(1~8级)全部真题・点这里 第1题:倒序输出 依次输入4个整数a、b、c、d,将他们倒序输出,即依次输出d、c、b、a这4个数。 时间限制:1000 内存限制:65536输入 一行4个整数a、b、c、d,以空格分隔。 0 < a,b,c,d < 108输出 一行4个整数d、c、b、a,整数之…

听GPT 讲Rust源代码--src/bootstrap

图片来自 使用rust的image库进行图片压缩[1] File: rust/src/bootstrap/build.rs 在Rust源代码中&#xff0c;rust/src/bootstrap/build.rs这个文件是一个构建脚本。构建脚本是一个在编译Rust编译器本身时运行的程序&#xff0c;它用于初始化和配置Rust编译器的构建过程。build…

沸点 | Ultipa 图数据库金融应用场景优秀案例首批入选,金融街论坛年会发布

为推进图数据库在金融行业的创新应用试点&#xff0c;近日&#xff0c;在2023金融街论坛年会“全球金融科技中心网络年会暨ZIBS北京论坛”上&#xff0c;北京前沿金融监管科技研究院发布了基于国际标准组织——国际关联数据基准委员会&#xff08;LDBC&#xff09;的《图数据库…

c语言:矩阵交换

题目&#xff1a; 代码和思路&#xff1a; #define _CRT_SECURE_NO_WARNINGS #include<stdio.h>int main() {int n 0;int m 0;int arr[10][10] { 0 }; // 输入行和列scanf("%d%d", &n, &m);int i 0;int j 0;//读取数组for (i 0; i < n; i)…

python爬虫SHA案例:某直播大数据分析平台

声明&#xff1a; 该文章为学习使用&#xff0c;严禁用于商业用途和非法用途&#xff0c;违者后果自负&#xff0c;由此产生的一切后果均与作者无关 一、找出需要加密的参数 js运行 atob(‘aHR0cDovL3d3dy5oaDEwMjQuY29tLyMvc2VhcmNoL3NlYXJjaA’) 拿到网址&#xff0c;F12打…

Linux本地WBO创作白板部署与远程访问

文章目录 前言1. 部署WBO白板2. 本地访问WBO白板3. Linux 安装cpolar4. 配置WBO公网访问地址5. 公网远程访问WBO白板6. 固定WBO白板公网地址 前言 WBO在线协作白板是一个自由和开源的在线协作白板&#xff0c;允许多个用户同时在一个虚拟的大型白板上画图。该白板对所有线上用…

将word中的表格无变形的弄进excel中

在上篇文章中记录了将excel表拷贝到word中来&#xff1a; 记录将excel表无变形的弄进word里面来-CSDN博客 本篇记录&#xff1a;将word中的表格无变形的弄进excel中。 1.按F12&#xff0c;“另存为...”&#xff0c;保存类型&#xff1a;“单个文件页面”&#xff0c;保存。…

CXL崛起:2024启航,2025年开启新时代

在2019年&#xff0c;Intel主导联合多家阿里巴巴、Facebook(也就是改名后Meta)、谷歌、Dell、华为、思科、微软、HPE最初的八巨头&#xff0c;发布了新的互联协议CXL&#xff0c;全称Comupte Express Link。由于在服务器领域享有绝对领导地位&#xff0c;Intel一经号令&#xf…

力扣-路径总和问题

路径总和 --简单 112. 路径总和 给你二叉树的根节点 root 和一个表示目标和的整数 targetSum 。判断该树中是否存在 根节点到叶子节点 的路径&#xff0c;这条路径上所有节点值相加等于目标和 targetSum 。如果存在&#xff0c;返回 true &#xff1b;否则&#xff0c;返回 f…

在网络攻击之前、期间和之后应采取的步骤

在当今复杂的威胁形势下&#xff0c;网络攻击是不可避免的。 恶意行为者变得越来越复杂&#xff0c;出于经济动机的攻击变得越来越普遍&#xff0c;并且每天都会发现新的恶意软件系列。 这使得对于各种规模和跨行业的组织来说&#xff0c;制定适当的攻击计划变得更加重要。 …

Python大数据之linux学习总结——day10_hive调优

hive调优 hive调优hive命令和参数配置1.hive数据压缩压缩对比开启压缩 2.hive数据存储[练习]行列存储原理存储压缩比拓展dfs -du -h 3. fetch抓取4. 本地模式5. join的优化操作6. 列裁剪7. 分区裁剪8. group by 操作9. count(distinct)10. 笛卡尔积11. 动态分区[练习]12. 如何调…

Run Legends将健身运动游戏化,使用户保持健康并了解Web3游戏

最近&#xff0c;我们有机会采访Talofa Games的首席执行官兼创始人Jenny Xu&#xff0c;一起讨论游戏开发&#xff0c;Talofa Games是Run Legends这款健身游戏的开发工作室。她已经创作了超过一百款游戏&#xff0c;对于推动游戏的可能性并将她的创造力和叙事技巧带入她最喜爱的…