当前位置:   article > 正文

c【语言】了解指针,爱上指针(1)

c【语言】了解指针,爱上指针(1)

1. 内存和地址

再了解指针之前,我们需要先了解一下计算机中的内存与地址。
内存:就好比一栋学生公寓楼,整栋楼的空间就表示内存。
地址:地址就相当于每个寝室的编号,如:101,102,103…,每个房间的编号就是地址。

指针

在计算机中,数据是存储在内存中的,cpu从内存中读取数据,为了方便读取数据,内存又被划分为了一个个的内存单元,一个内存单元的大小就是一字节,一字节等于8bit位,我们给内存进行编号,这个编号就是地址,在c语言中给这个地址起了个名字。叫“指针”。
在这里插入图片描述
如图,内存空间的大小为一字节,地址为:0x00000000,0x00000001…
由此我们可以得结论:

内存单元的编号=地址=指针

如何编址的

我们要访问内存中的某个字节空间,就需要知道这个字节空间在内存中的什么位置,因为内存的空间是很大的,有许多的字节空间,所以需要给内存进行编址。

计算机中的编址,并不是把每个字节的地址记录 下来,⽽是通过硬件设计完成的。

什么意思呢?
就好比一架钢琴,钢琴、小提琴的按键上没有写上“dou rai mi”,但是演奏者却能够准确无误的找到这个音色的位置,这是因为厂商在出厂之前就已经在硬件层面上设计好了,并且演奏者都知道。或者说这是一中“共识

计算机的编址也是如此。

我们前面知道了:数据是存储在内存中的,cpu从内存中读取数据,经过处理后,在返回到内存当中。但是这一过程是怎么实现的呢?
首先,我们要知道,计算机是有很多硬件单元的,而硬件单元之间是要协同工作的,所谓协同工作就是至少能够进行数据的传输。但是硬件与硬件之间是相互独立的,这该如何实现数据传输呢?

原因就在于:硬件与硬件之间是用线连接起来的

在这里插入图片描述

在cpu与内存之间有大量的数据交互,在两者之间必须用线连接起来,但由于这部分知识只是我们这章所讲的指针内容的的拓展,所以我们只做了解。

通过控制总线传给cpu一个读取数据的信息,cpu再通过数据的地址,经过地址总线从内存找到相应的内存空间并读取数据,最后通过数据总线将数据传输到cpu中。

那么,我们的问题来了,地址是如何传输的呢?

我们可以简单理解,在32位机器中,有32根地址总线,每根总线只有两态,0与1(电脉冲的有无),那么一根地址总线能表示两种含义,四根就能表示2^4种含义,32根就能表示 2 ^32种含义,每一种含义都表示一个地址。地址信息被传达给内存,在内存上,就可以找到该地址对应的数据,再将数据通过数据总线传入cpu中寄存器。

2.指针变量和地址

  • 什么是指针:指针就是地址
  • 什么是指针变量:就是存储地址的变量。

指针变量的声明

int*变量名;
************
int *p;
************
这里*表示变量是指针变量,p为变量名。
这个p就是一个指针变量.
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

取地址操作符(&)

之前我们学过按位与操作符(&),它是一个双目操作符,在这里(&)是取地址操作符,是一个单目操作符,用于取出变量的地址

int a=20;
printf("%p",&a);//输出a的地址
  • 1
  • 2

在这里插入图片描述
可能有的读者在输出&a时忘记加&了,会输出一个值:0000000000000014

int a=20;
printf("%p",a);
  • 1
  • 2

这表示把a的值以地址的形式输出,因为地址是由一串16进制数组成的,将20转换成16进制就是14(关于进制的转换,这里就不多阐述,若读者不太了解进制转换,可以去学习一下进制转换的知识),所以会输出:0000000000000014

指针变量的初始化

在这里插入图片描述
这里初始化指针变量p指向a的地址,此时p里存储的值就是变量a的地址,或者说是变量a申请到的空间的首地址。
a是int类型,所以申请的空间为四个字节,它的首地址就是:0x0000009A8679F9F4,从这个地址开始,向后三位(包括该地址)都是a申请到的空间。
在这里插入图片描述

因为变量的创建就是向内存申请空间,这里变量a的创建就是申请了四个字节空间,指针p中存储的就是第一个字节空间的地址。

注意:因为每次运行分配的空间是不一样的,所以上下文的地址没有直接关系。

在这里插入图片描述
我们会发现,&a的值与p的值是一样的,因为p中存储的就是&a&a表示取出a的地址)的值:
p存储a的地址0x000000E68F2FFA14),我们可以通过这个地址找到a0x00000014转换成10进制就是20)的值。

讲到这,我们还需了解另一个操作符:解引用操作符"*"

int a=20;
int *p=&a;
printf("%d",*p);
  • 1
  • 2
  • 3

这里p中存储a的地址,对p解引用就得到a的值(对p解引用就相当于对a的地址解引用。)

不要把解引用操作符与指针变量声明时的*混淆了

解引用操作符我们可以理解为一张解密卡,通过对地址进行解密,得到在这个地址下的值,而声明指针变量时的“*”有有声明含义呢?这里我们就要讲讲指针的类型了。

指针的类型

我们先上一个例子:

int a=20;
int *p=&a;
  • 1
  • 2

在这段代码中,p为变量名,"*"表示变量p是指针变量,有的读者可能看到这里会好奇:“那指针变量的类型是什么?难道是int吗?”答案当然是否定的。

这段代码中指针变量的类型其实为:int*
根据这个,读者们大概就猜出了指针的类型有哪些:

  • int*整型指针
  • char*字符指针
  • double*浮点型指针
  • 等等…

指针变量的大小

在开头,我们将了32位机器有32根地址总线,每根地址总线的电信号转换成数字信号能表示01,那么我们把32根地址总线产生的二进制序列当作一个地址,那么这个地址就是32个bit位
64位机器下同样的道理,把64根地址总线产生的二进制序列当作一个地址,那么这个地址就是64个bit位

我们知道8bit=1byte,所以32bit=4byte,64bit=8byte.

前面我们学到“指针就是地址”,我们又把地址总线产生的二进制序列当作地址,所以由此得出:

  • 指针变量的大小就是在32位的环境下就是4byte或者在64位环境下就是8byte

(指针变量是用来存储地址的,一个地址位4byte或者8byte那么这个指针变量就得向计算机申请4byte或者8byte的空间)

结论:
32位平台下地址是32个bit位,指针变量⼤⼩是4个字节
64位平台下地址是64个bit位,指针变量⼤⼩是8个字节
注意指针变量的⼤⼩和类型是⽆关的,只要指针类型的变量,在相同的平台下,⼤⼩都是相同的。

我们上代码来验证一下:在64位平台下
在这里插入图片描述
在32位平台下:
在这里插入图片描述

3. 指针类型的意义

我们先来看两段代码:
第一段代码:


#include <stdio.h>
 int main()
 {
 int n = 0x11223344;
 int *pi = &n; 
*pi = 0;   
return 0;
 }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

在vs中调试运行一下:

在这里插入图片描述

此时还没有运行到*pi=0,n中的值没变,我们接着运行下去:
在这里插入图片描述
第二段代码:

#include<stdio.h>
int main()
{
 int n = 0x11223344;
 char *pc = &n; 
 *pc = 0;   
 return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

在vs调试运行一下得到:
在这里插入图片描述
此时还没运行到*pc=0,n的值没变,我们继续运行下去:
在这里插入图片描述
我们发现,当运行完*pc=0时,只有第一个字节空间的值被修改。

看到这,亲爱的读者你似乎已经明白了:

结论:指针类型决定了指针解引用时一次能访问多大空间

当时int类型时,一次可以访问四个字节的空间,而是char类型时,一次只能访问一个字节空间。
double一次可以访问8字节空间,float一次可以访问4字节空间…

指针加减整数

我们先来看一段代码:

int a=20;
int*p=&a;
char*pc=&a;

printf("%p",&a);
printf("%p",p);
printf("%p",pc);

printf("%p",&a+1);
printf("%p",p+1);
printf("%p",pc+1);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

观察运行结果:
在这里插入图片描述
是的,亲爱的读者你肯定发现了,&a、p和pc输出的地址是一样的,但&a+1与p+1输出的地址是一样的00EFF940,一次都跳过了4字节,而pc+1输出的却是00EFF93D,一次只跳过一个字节
由此我们可以得出:

指针的类型决定了指针向前或者向后⾛⼀步有多⼤(距离)。

void*指针(无具体指针类型)

void类型的值任何类型的地址都能存,但是存入后不能进行解引用操作,若要解引用,需先强转成其他类型的指针。
void
类型的指针更多的是用于泛型编程,什么是泛型编程?后面我们会提到。

4.const修饰指针

const修饰变量

变量的值是可以修改的,把变量的地址赋给指针变量,也可以通过指针变量来修改这个值,但如果我们不希望这个值被修改,该怎么做呢?

这里我们就要用到const

const修饰变量:被const修饰的变量叫做常变量

来看这段代码:

const int a=20;
a=100;
printf("%d",a);
  • 1
  • 2
  • 3

这段代码在运行时是会报错的,因为变量a被const修饰,是一个无法被修改的常变量
常变量:依然是变量,但是其值不能被修改
在这里插入图片描述
我们直接对a进行修改实现不了,那如果绕开a,通过指针来修改a的值呢?
答案当然是肯定的,虽然这样子打破了c语言的语法规则

const int a=20;
int*p=&a;
*p=100;
printf("%d",a);
  • 1
  • 2
  • 3
  • 4

代码运行后,我们很直观的发现a的值被修改成了100
在这里插入图片描述

const修饰指针变量

我们让const修饰一个变量,就是为了让这个变量的值能不被修改,但是我们却可以通过指针来修改,这不就打破了const的限制吗?

如果我们希望某个变量的值无法通过指针修改,该怎么做呢?可以用const修饰指针变量。

在修饰指针变量时,需要注意一点:

const是在“*”号的左边还是右边

  • 在“*”左边时
int n=10;
const int *p=&n;//也可以写成int const *p;
//这段代码表示:*p不能被改变,但是p可以被改变
  • 1
  • 2
  • 3
  • 在“*”右边时
int n=10;
int *const p=&n;//这段代码表示:*P可以被改变,但是p不能被改变
  • 1
  • 2

如果搞不清p与*p,没关系,我们这里来复习一下:

  • p,是指针变量,里面存放的是地址
  • *p,是指针变量的解引用,得到的是存放在p中的地址下的值

来一段代码解释一下:

代码1:

int a=10;
int *p=NULL;
p=&a;
printf("%p",p);//这里输出的是a的地址,因为把a的地址存放到了p中
  • 1
  • 2
  • 3
  • 4

在这里插入图片描述
运行后我们会发现,a与指针p的地址是一样的。

代码2

int a=10;
int *p=NULL;
p=&a;
printf("%d",*p);//输出在p中存放的地址下的值,也就是a的值
  • 1
  • 2
  • 3
  • 4

在这里插入图片描述
运行代码后发现,*p的值与a的值是一样的。

代码1:

int n=10;
const int *p=&n;//也可以写成int const *p;
//这段代码表示:*p不能被改变,但是p可以被改变
  • 1
  • 2
  • 3

此时,我们修改*p的值,编译器会报错,但是修改p指向的对象时,却没报错。
代码2

int n=10;
int *const p=&n;//这段代码表示:*P可以被改变,但是p不能被改变
  • 1
  • 2

此时,我们修改*p的值,编译器不会报错,但是修改p指向的对象时,却报错。

所以,关于const在“*”左边与右边所产生的效果,我们可以总结为:

const在" * “左边,无法修改 * p的值,但是可以修改变量p指向的对象
const在” * "右边,无法修改p指向的对象,但是能修改*p的值

5.指针运算

指针的基本运算有三种:

  • 指针±整数
  • 指针-指针
  • 指针的关系运算

指针±整数

因为数组在内存中是连续存放的,只要知道第一个元素的地址,就能顺藤摸瓜找到后面的所有元素。

int arr[]={1,2,3,4,5,6,7,8,9,10};
  • 1

在这里插入图片描述
当我们知道下标0处的地址时,就能找到后面的地址已经每个地址下对应的元素

指针±整数

int main()
{
int arr[]={1,2,3,4,5,6,7,8,9,10};
int *p=arr;//数组名就是数组的首元素地址
int i=0;
int sz=sizeof(arr)/sizeof(arr[0]);
for(i=0;i<sz;i++)
{
	printf("%d",*(p+i));//指针+整数
}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

因为p指向数组arr的首元素地址,首元素地址的值是int类型,所有p+i每次后移一个整型的位置。
指针-指针也是同理,每次往前移一个整型的单位。

指针-指针:

指针-指针求的是两指针之间元素的个数。


//这是一段模拟strlen函数实现的代码
int my_strlen(char*s)
{
	char*p=s;
	while(*p!='\0')
	{
		p++;
	}
	return p-s;
}

int main()
{
	printf("%d\n",my_strlen("abc"));
	return 0;
} 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

在这里调用my_strlen函数,传入字符串首字符的地址,在my_strlen函数内部,创建一个指针变量p初始化为s(将指针变量s中存放的地址赋值给指针变量p),通过while循环,当p!='\0’的时候让p后移,当p=='\0’时,让p-s,计算出两指针之间元素的个数。

有的读者可能回想”既然指针-指针计算的是两个指针之间元素的个数,那指针+指针计算的是什么呢?“
答案当然是:啥也不是,这种计算是毫无意义的。
这就类似于日期的加减计算,当你把两日期相减时,为的是得到的是两日期之间相差的天数,但是两日期相加得到的是什么,毫无意义?

指针的关系运算

指针进行关系运算的前提是指向同一块空间

//
指针的关系运算
 
#include <stdio.h>
 int main()
 {
 int arr[10] = {1,2,3,4,5,6,7,8,9,10};
 int *p = &arr[0];//p指向arr首元素的地址
 int sz = sizeof(arr)/sizeof(arr[0]);
 while(p<arr+sz) //当小于arr最后一个元素的地址
//指针的⼤⼩⽐较
{
 printf("%d ", *p);
 p++;
 }
 return 0;
 }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/我家自动化/article/detail/610288
推荐阅读
相关标签
  

闽ICP备14008679号