指针 拨开迷雾

指针 拨开迷雾

指针

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;
}

引入数组指针后,就存在两种方案来访问数组元素,一种是使用下标,另外一种是使用指针。

  1. 使用下标
    也就是采用 arr[i] 的形式访问数组元素。如果 p 是指向数组 arr 的指针,那么也可以使用 p[i] 来访问数组元素,它等价于 arr[i]。
  2. 使用指针
    也就是使用 *(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语言没有从语法上支持数据集合的直接赋值。

历经两年,现在才入门指针<悲伤>

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值