指针 拨开迷雾
指针
CPU 访问内存时需要的是地址,而不是变量名和函数名!变量名和函数名只是地址的一种助记符,当源文件被编译和链接成可执行程序后,它们都会被替换成地址。编译和链接过程的一项重要任务就是找到这些名称所对应的地址。
//定义普通变量
float a = 1, b = 2;
char c = '@', d = '#';
//定义指针变量
float *p1 = &a;
char *p2 = &c;
//修改指针变量的值
p1 = &b;//指针p1转而指向b的地址
p2 = &d;//指针p2转而指向d的地址
int *a, *b, *c; //a、b、c 的数据类型都是 int*
int *a, b, c;//只有 a 是指针变量,b、c 都是类型为 int 的普通变量
CPU 读写数据必须要知道数据在内存中的地址,普通变量和指针变量都是地址的助记符,虽然通过 *p 和 a 获取到的数据一样,但它们的运行过程稍有不同:a 只需要一次运算就能够取得数据,而 *p 要经过两次运算,多了一层“间接”。
指针指向关系图
#include <stdio.h>
int main(){
int a = 1;
int *p = &a;
printf("%d,%d\n", a, *p); //两种方式都可以输出a的值
return 0;
}
RESULT
通过指针变量修改内存上的数据&通过指针变量获取内存上的数据
#include <stdio.h>
int main(){
int a = 1, b = 2, c = 3;
int *p = &a; //定义指针变量
*p = b; //通过指针变量修改内存上的数据
c = *p; //通过指针变量获取内存上的数据
printf("%d, %d, %d, %d\n", a, b, c, *p);
return 0;
}
定义指针变量&使用指针变量
int *p = &a;//此处*p声明并引用指针
*p = 100;//此处*p用来获取指针所指向的数据
指针变量的输出
#include <stdio.h>
int main(){
int x=1, y=2, *px = &x, *py = &y;
printf("%d %d %d %d %d %d %d %d\n",x,y,px,py,&x,&y,*px,*py);
y = *px + 5; //表示把x的内容加5并赋给y,*px+5相当于(*px)+5
y = ++*px; //px的内容加上1之后赋给y,++*px相当于++(*px)
y = *px++; //相当于y=*(px++)
py = px; //把一个指针的值赋给另一个指针
printf("%d %d %d %d %d %d %d %d\n",x,y,px,py,&x,&y,*px,*py);
return 0;
}
RESULT
Exchange the values of two numbers by a pointer
#include <stdio.h>
int main(){
int a = 1, b = 2, temp;
int *pa = &a, *pb = &b;
printf("a=%d, b=%d %d %d %d %d\n", a, b,*pa,*pb,pa,pb);
/*****BEIGN*****/
temp = *pa; //将a的值先保存起来,此处*pa的意思为[pa指针所指向的]a的值
*pa = *pb; //将b的值交给a
*pb = temp; //再将保存起来的a的值交给b
/*****END*****/
printf("a=%d, b=%d %d %d %d %d\n", a, b,*pa,*pb,pa,pb);
return 0;
}
语句*pa=*pb执行后,a的值会被b覆盖,如果不用temp将a的值保存以后就找不到了
RESULT
Comparison
此处不涉及传值和传址调用的区别
#include <stdio.h>
int main(){
int a = 1, b = 2, temp;
int *pa = &a, *pb = &b;
printf("a=%d, b=%d %d %d %d %d\n", a, b,*pa,*pb,pa,pb);
/*****BEIGN*****/
temp =a; //将a的值先保存起来
a = b; //将b的值交给a
b = temp; //再将保存起来的a的值交给b
/*****END*****/
printf("a=%d, b=%d %d %d %d %d\n", a, b,*pa,*pb,pa,pb);
return 0;
}
RESULT
指针的地址是随机分配的
int 类型的变量 a,pa 是指向它的指针 *&a 与 &*a的区别
*&a等价于a
&*a等价于pa
*&a 可以理解为 * (&a),&a表示取变量 a 的地址(等价于 pa), * (&a)表示取这个地址上的数据(等价于 * pa),回到原点, * &a仍然等价于 a。
&*pa可以理解为&(*pa),*pa表示取得 pa 指向的数据(等价于 a),&(*pa)表示数据的地址(等价于 &a),所以&*pa等价于 pa。
指针变量保存的是地址,而地址本质上是一个整数,所以指针变量可以进行部分运算,例如加法、减法、比较等。
#include <stdio.h>
int main(){//int占4个字节,double占8个字节,char占一个字节
int a = 10, *pa = &a, *paa = &a;
double b = 99.9, *pb = &b;
char c = '@', *pc = &c;
//最初的值
printf("&a=%#X, &b=%#X, &c=%#X\n", &a, &b, &c);
printf("pa=%#X, pb=%#X, pc=%#X\n", pa, pb, pc);
//加法运算
pa++; pb++; pc++;
printf("pa=%#X, pb=%#X, pc=%#X\n", pa, pb, pc);
//减法运算
pa -= 2; pb -= 2; pc -= 2;
printf("pa=%#X, pb=%#X, pc=%#X\n", pa, pb, pc);
//比较运算
if(pa == paa){
printf("%d\n", *paa);
printf("%#X\n",*paa);
printf("%d\n",*pa);
printf("%#X\n",*pa);
}else{//pa与paa断然不等
printf("%d\n", *pa);
printf("%#X\n",*pa);
printf("%d\n", *paa);
printf("%#X\n",*paa);
}
return 0;
}
pa与paa断然不等的解释
在比较 pa 和 paa 的值时,pa 已经指向了 a 的上一份数据,所以它们不相等。而 a 的上一份数据又不知道是什么,所以会导致 printf() 输出一个没有意义的数。
注意:不要对指向普通变量的指针进行加减运算。
不按int对应的字节数倍数对指针自增输出
#include <stdio.h>
int main(){
int a = 1, b = 2, c = 3;
int *p = &c;
int i;
for(i=0; i<100; i++){
printf("%d, ", *(p+i) );
}
return 0;
}
RESULT
变量 a、b、c 并不挨着,它们中间还参杂了别的辅助数据。
以指针方式遍历数组元素(Iterates through array elements by a pointer)
#include <stdio.h>
int main(){//求数组长度的方法类似于matlab中的方法[matlab相关命令已经帮你处理好了]
int arr[] = {1,2,3,4,5,6,7};
int len = sizeof(arr) / sizeof(int); //求数组长度
int i;
for(i=0; i<len; i++){
printf("%d ", *(arr+i) ); //*(arr+i)等价于arr[i]
}
printf("\n");
return 0;
}
RESULT
数组指针
int arr[] = { 1,2,3,4,5,6,7 };
int *p = arr;
arr 被转换成一个指针,可以直接赋值给指针变量 p。arr 是数组第 0 个元素的地址,所以int *p = arr;也可以写作int *p = &arr[0];。即arr、p、&arr[0] 这三种写法都是等价的,它们都指向数组第 0 个元素,或者说指向数组的开头。
数组指针遍历数组元素
Comparison
#include <stdio.h>
int main(){//求数组长度的方法类似于matlab中的方法[matlab相关命令已经帮你处理好了]
int arr[] = {1,2,3,4,5,6,7};
int i,*p=arr,len = sizeof(arr) / sizeof(int); //求数组长度
for(i=0; i<len; i++){
printf("%d ", *(arr+i) ); //*(arr+i)等价于arr[i]
}
printf("\n");
return 0;
}
与之前有细微差别,类似于创建新指针和使用旧指针
在求数组的长度时不能使用sizeof( p ) / sizeof(int)
数组在内存中只是数组元素的简单排列,没有开始和结束标志,在求数组的长度时不能使用sizeof( p ) / sizeof(int),因为 p 只是一个指向 int 类型的指针,编译器并不知道它指向的到底是一个整数还是一系列整数(数组),所以 sizeof§ 求得的是 p 这个指针变量本身所占用的字节数,而不是整个数组占用的字节数。
对指针变量进行加法和减法运算时,是根据数据类型的长度来计算的
采用中心到两边的手动输出式
#include <stdio.h>
int main(){
int arr[] = {1,2,3,4,5,6,7};
int *p = &arr[3]; //也可以写作 int *p = arr + 3;
printf("%d, %d, %d, %d, %d, %d, %d.\n", *(p-3), *(p-2), *(p-1),*p, *(p+1), *(p+2) ,*(p+3));
return 0;
}
引入数组指针后,就存在两种方案来访问数组元素,一种是使用下标,另外一种是使用指针。
- 使用下标
也就是采用 arr[i] 的形式访问数组元素。如果 p 是指向数组 arr 的指针,那么也可以使用 p[i] 来访问数组元素,它等价于 arr[i]。 - 使用指针
也就是使用 *(p+i) 的形式访问数组元素。另外数组名本身也是指针,也可以使用 *(arr+i) 来访问数组元素,它等价于 *(p+i)。
不管是数组名还是数组指针,都可以使用上面的两种方式来访问数组元素。不同的是,数组名是常量,它的值不能改变,而数组指针是变量(除非特别指明它是常量),它的值可以任意改变。也就是说,数组名只能指向数组的开头,而数组指针可以先指向数组开头,再指向其他元素。
对指针使用自增符遍历数组元素
#include <stdio.h>
int main(){
int arr[] = {1,2,3,4,5,6,7};
int i, *p = arr, len = sizeof(arr) / sizeof(int);
for(i=0; i<len; i++){
printf("%d ", *p++ );//使用自增符遍历数组元素
}
printf("\n");
return 0;
}
*与p与++,假设 p 是指向数组 arr 中第 n 个元素的指针
*p++ 等价于 *(p++),表示先取得第 n 个元素的值,再将 p 指向下一个元素。
*++p 等价于 *(++p),会先进行 ++p 运算,使得 p 的值增加,指向下一个元素,整体上相当于 *(p+1),所以会获得第 n+1 个数组元素的值。
(*p)++ 会先取得第 n 个元素的值,再对该元素的值加 1。假设 p 指向第 0 个元素,并且第 0 个元素的值为 10,执行完该语句后,第 0 个元素的值就会变为 11。
#include <stdio.h>
#include <string.h>
int main(){
char str[] = "https://blog.csdn.net/zhanghanqmx?type=blog";
int i,len = strlen(str);//strlen()求字符串长度函数,类比matlab
//直接输出字符串
printf("%s\n", str);//输出方式为字符串[多个字符],%s
//每次输出一个字符
for(i=0; i<len; i++){
printf("%c", str[i]);//输出方式为单个字符,%c
}
printf("\n");
return 0;
}
字符数组归根结底还是一个数组,关于指针和数组的规则同样也适用于字符数组。
使用指针方式输出字符串[字符数组]
#include<stdio.h>
#include<string.h>
int main()
{
char str[]="https://blog.csdn.net/zhanghanqmx?type=blog";//注意字符数组与普通数组的创建区别
char *pstr=str;
int i,len=strlen(str);
for(i=0;i<len;i++)
{
printf("%c",pstr[i]);
}
printf("\n");
for(i=0;i<len;i++)
{
printf("%c",*(pstr+i));
}
printf("\n");
for(i=0;i<len;i++)
{
printf("%c",*(str+i));
}
printf("\n");
for(i=0;i<len;i++)
{
printf("%c",str[i]);//直接用数组名输出
}
printf("\n");
return 0;
}
除字符数组,C语言还支持另外一种表示字符串的方法:直接使用一个指针指向字符串
char *str="https://blog.csdn.net/zhanghanqmx?type=blog";
OR
char *str;
str="https://blog.csdn.net/zhanghanqmx?type=blog";
字符数组与字符串常量在储存上的区别
最根本的区别是在内存中的存储区域不一样,字符数组存储在全局数据区或栈区,而字符串存储在常量区。全局数据区和栈区的字符串(包括其他数据)有读取和写入的权限,而常量区的字符串(包括其他数据)只有读取权限,没有写入权限。
获取用户输入的字符串就是一个典型的写入操作
C语言有两种表示字符串的方法,一种是字符数组,另一种是字符串常量,它们在内存中的存储位置不同,使得字符数组可以读取和修改,而字符串常量只能读取不能修改。
#include <stdio.h>
#define MAX 100
int main(){
char str[MAX];
gets(str);
printf("%s\n", str);
return 0;
}
用指针变量作函数参数
用指针变量作函数参数可以将函数外部的地址传递到函数内部,使得在函数内部可以操作函数外部的数据,并且这些数据不会随着函数的结束而被销毁。
数组、字符串、动态分配的内存等都是一系列数据的集合,没有办法通过一个参数全部传入函数内部,只能传递它们的指针,在函数内部通过指针来影响这些数据集合。
有时,对于整数、小数、字符等基本类型数据的操作也必须要借助指针。
传值调用
#include <stdio.h>
void swap(int a, int b);
int main(){//computational results:Fail to exchange
int a = 1, b = 2;
swap(a, b);
printf("a = %d, b = %d\n", a, b);
return 0;
}
void swap(int a, int b){
int temp; //temporary variable
temp = a;
a = b;
b = temp;
}
a、b 的值并没有发生改变,交换失败。这是因为 swap() 函数内部的 a、b 和 main() 函数内部的 a、b 是不同的变量,占用不同的内存,它们除了名字一样,没有其他任何关系,swap() 交换的是它内部 a、b 的值,不会影响它外部(主函数内部) a、b 的值。
传址调用
#include <stdio.h>
void swap(int *p1, int *p2){
int temp; //temporary variable
temp = *p1;
*p1 = *p2;
*p2 = temp;
}
int main(){
int a = 1, b = 2;
swap(&a, &b);
printf("a = %d, b = %d\n", a, b);
return 0;
}
调用 swap() 函数时,将变量 a、b 的地址分别赋值给 p1、p2,这样 *p1、*p2 代表的就是变量 a、b 本身,交换 *p1、*p2 的值也就是交换 a、b 的值。函数运行结束后虽然会将 p1、p2 销毁,但它对外部 a、b 造成的影响是“持久化”的,不会随着函数的结束而“恢复原样”。
用数组作函数参数
#include<stdio.h>
#define MAX 10
int max(int *arr,int len)
{
int i,maxValue=arr[0];
for(i=1;i<len;i++)
{
if(maxValue<arr[i])
{
maxValue=arr[i];
}
}
return maxValue;
}
int main()
{
int i,a[MAX],len=sizeof(a)/sizeof(int);
for(i=0;i<len;i++)//Reads the data entered by the user and assigns values to the array elements
{
scanf("%d",a+i);
}
printf("Max value is:%d.\n",max(a,len));//在输出时将数组首位 和 数组长度带入最大值函数
}
参数 arr 仅仅是一个数组指针,在函数内部无法通过这个指针获得数组长度,必须将数组长度作为函数参数传递到函数内部。数组a的每个元素都是整数,scanf() 在读取用户输入的整数时,要求给出存储它的内存的地址,a+i就是第 i 个数组元素的地址。
RESULT
直接传整个数组
#include<stdio.h>
#define MAX 10
int max(int arr[MAX], int len){//直接传整个数组
int i, maxValue = arr[0]; //假设第0个元素是最大值
for(i=1; i<len; i++){//从下标1开始比较
if(maxValue < arr[i]){
maxValue = arr[i];
}
}
return maxValue;
}
int main()
{
int i,a[MAX],len=sizeof(a)/sizeof(int);
for(i=0;i<len;i++)//Reads the data entered by the user and assigns values to the array elements
{
scanf("%d",a+i);
}
printf("Max value is:%d.\n",max(a,len));//在输出时将数组首位 和 数组长度带入最大值函数
}
貌似省略数组长度
#include<stdio.h>
#define MAX 10
int max(int arr[], int len){//直接传整个数组
int i, maxValue = arr[0]; //假设第0个元素是最大值
for(i=1; i<len; i++){//从下标1开始比较
if(maxValue < arr[i]){
maxValue = arr[i];
}
}
return maxValue;
}
int main()
{
int i,a[MAX],len=sizeof(a)/sizeof(int);
for(i=0;i<len;i++)//Reads the data entered by the user and assigns values to the array elements
{
scanf("%d",a+i);
}
printf("Max value is:%d.\n",max(a,len));//在输出时将数组首位 和 数组长度带入最大值函数
}
实际上这两种形式的数组定义都是假象,二者都不会创建一个数组出来,编译器也不会为它们分配内存,实际的数组是不存在的,它们最终还是会转换为 int*arr 这样的指针。
两种形式都不能将数组的所有元素“一股脑”传递进来,应该按照规范使用数组指针。
int arr[10]这种形式只能说明函数期望用户传递的数组有 10 个元素,并不意味着数组只能有 10 个元素,真正传递的数组可以有少于或多于 10 个的元素。
不管使用哪种方式传递数组,都不能在函数内部求得数组长度,因为 arr 仅仅是一个指针,而不是真正的数组,所以必须要额外增加一个参数len来传递数组长度。
参数的传递本质上是一次赋值的过程,赋值就是对内存进行拷贝。所谓内存拷贝,是指将一块内存上的数据复制到另一块内存上。
对于像 int、float、char 等基本类型的数据,它们占用的内存往往只有几个字节,对它们进行内存拷贝非常快速。而数组是一系列数据的集合,数据的数量没有限制,可能很少,也可能成千上万,对它们进行内存拷贝有可能是一个漫长的过程,会严重拖慢程序的效率,C语言没有从语法上支持数据集合的直接赋值。
历经两年,现在才入门指针<悲伤>