C语言指针(一):基本介绍

指针概念

首先,我们知道基本每种编程语言都有多种数据类型,那么为什么会有数据类型呢?
设置多种数据类型的本质目的是为了节约空间,这个空间就是我们计算机的内存。计算机中的内存单位是Byte,比如一个int型数据占用4个Byte,char占用1个Byte。当我们声明一个int型变量时,计算机就给我们开辟一个内存空间,这个内存空间里可以存放int型数据,存进数据之后我们怎样才能找到这个数据呢?我们先来这样想,比如一个人A和他好朋友B约好去网吧开黑,A先到的网吧,并且找了104号坐下,后来B也到了网吧,但是他找不到A在哪,A给B发消息说我在104号,后面两个人就开始愉快的玩游戏了。
在计算机里面我们存了一个数据后,要找到这个数据同样也需要一个位置信息,也就是地址或者指针。我们来看一段代码:怎样获取地址?

#include <stdio.h>

int main()
{
    
    
	int a=4;//开辟一个int型的内存空间,内存空间的名字叫a,并且将数据4放进去
	int b=8;//开辟一个int型的内存空间,内存空间的名字叫b,并且将数据8放进去
	printf("%p,%p\n",&a,&b);//&就是取变量地址的意思 
							//%p是一个控制符,专门用来以十六进制形式输出地址
	return 0;
}

在64位win10下的DEV C++运行结果:
在这里插入图片描述
通过结果看出输出了两个十六进制形式的地址。
目前我们记住以下几点即可:

  • 指针就是内存空间的地址
  • &符号作用是取出变量地址
  • %p用来以十六进制形式输出地址

现在我们从一个全新的角度来思考一个问题(很重要):
我们写好上面这段代码后,按下Ctrl+S,这个时候会弹出一个路径,问我们需要把这个C程序放在哪,我们给出一个路径后,按下保存,这段程序就被保存在我们给出的路径里了。也就是说我们存在了电脑的硬盘。当我们按下编译链接运行按键后,从硬件的角度考虑,硬盘只能存数据,并不能在硬盘上运行这段代码。这时,计算机会把这段代码从硬盘复制到内存里,因为要运行这段代码只能由CPU来处理,只有在内存中的数据才能让CPU处理。但是数据和代码都以二进制的形式存储在内存中,计算机无法从格式上区分某块内存到底存储的是数据还是代码。
操作系统会给不同的内存块指定不同的权限,拥有读取和执行权限的内存块就是代码,而拥有读取和写入权限(也可能只有读取权限)的内存块就是数据。
CPU 只能通过地址来取得内存中的代码和数据,程序在执行过程中会告知 CPU 要执行的代码以及要读写的数据的地址。
CPU 访问内存时需要的是地址,而不是变量名和函数名!变量名和函数名只是地址的一种助记符,当源文件被编译和链接成可执行程序后,它们都会被替换成地址。编译和链接过程的一项重要任务就是找到这些名称所对应的地址。
例如:
假设变量 a、b、c 在内存中的地址分别是 0X1000、0X2000、0X3000,那么加法运算c = a + b;将会被转换成类似下面的形式:

0X3000 = (0X1000) + (0X2000);

( )表示取值操作,整个表达式的意思是,取出地址 0X1000 和 0X2000 上的值,将它们相加,把相加的结果赋值给地址为 0X3000 的内存。

通过前面这些话,相信大家对指针的概念有了深刻的认识!

指针变量概念和使用

前面已经说过,地址就是指针。如果一个变量存储了一份数据的指针,我们就称它为指针变量
在C语言中,允许用一个变量来存放指针,这种变量称为指针变量。指针变量的值就是某份数据的地址,这样的一份数据可以是数组、字符串、函数,也可以是另外的一个普通变量或指针变量
我们看个简单点的例子:
在这里插入图片描述

  1. 先定义一个int型变量,取名为A,并且存放了123这个数
  2. 这个int型变量A的地址是0X13A
  3. 再定义一个指针变量,取名为B,刚好存储了变量A的地址0X13A
  4. 这时候我们就称B指向了A
  5. 指针变量也有它自己的地址0X23F

指针变量定义

我们已经说了指针变量时怎么一回事,现在看下如何定义指针变量。
定义格式:
datatype *name;
或者
datatype *name = value;

  • 表示这是一个指针变量,datatype表示该指针变量指向的数据的类型,比如:
    int *p1;
    p1 是一个指向 int 类型数据的指针变量,至于 p1 究竟指向哪一份数据,应该由赋予它的值决定。再如:
int a = 100;
int *p_a = &a;

在定义指针变量 p_a 的同时对它进行初始化,并将变量 a 的地址赋予它,此时 p_a 就指向了 a。值得注意的是,p_a 需要的一个地址,a 前面必须要加取地址符&,否则是不对的。
星号是一个特殊符号,表明一个变量是指针变量,定义 p1、p2 时必须带星号。而给 p1、p2 赋值时,因为已经知道了它是一个指针变量,就没必要多此一举再带上星号,后边可以像使用普通变量一样来使用指针变量。也就是说,定义指针变量时必须带星号,给指针变量赋值时不能带星号。

指针变量使用

我们看一个简单的例子,应该就明白了:

#include <stdio.h>

int main()
{
    
    
	int a=4;//定义一个int变量a,并赋值4
	int *p1;//定义一个指针变量p1
	p1=&a;//p1指向a

	printf("变量a的值是%d\n",a);
	printf("变量a的地址是%#X\n",&a);
	printf("p1的值是%#X\n",p1);
	printf("p1指向的变量的值是%d\n",*p1); //*的作用是取出p1指向的变量的值
	printf("p1的地址是%#X\n",&p1);
	printf("p1指向的变量的值的地址是%#X\n",&(*p1));
	
	return 0;
}

运行结果:
在这里插入图片描述
注意:定义指针变量时的 *和使用指针变量时的 * 意义完全不同!
大家应该可以通过这个例子明白p1,&p1,*p1的含义。
现在我们把上面的例子变得复杂一点:

#include <stdio.h>

void test(int *p)//指针变量作为函数参数
{
    
    
	*p=12345;
}
int main()
{
    
    
	int a=4;
	int *p1;
	p1=&a;

	printf("变量a的值是%d\n",a);
	printf("变量a的地址是%#X\n",&a);
	printf("p1的值是%#X\n",p1);
	printf("p1指向的变量的值是%d\n",*p1); 
	printf("p1的地址是%#X\n",&p1);
	printf("p1指向的变量的值的地址是%#X\n",&(*p1));
	
	test(p1);
	printf("-------------------------------------\n");
	printf("-------------------------------------\n");
	printf("变量a的值是%d\n",a);
	printf("变量a的地址是%#X\n",&a);
	printf("p1的值是%#X\n",p1);
	printf("p1指向的变量的值是%d\n",*p1);
	printf("p1的地址是%#X\n",&p1);
	printf("p1指向的变量的值的地址是%#X\n",&(*p1));
	
	return 0;
}

运行结果:
在这里插入图片描述
从结果看:

  1. 分割线前面的和上一段代码一样
  2. test函数:把指针变量指向的值改为12345
  3. 运行test后,发现a的值变为12345,说明我们可以通过指针来改变指向的变量的值
  4. 地址并没有任何变化

指针变量基本运算

本小节来自C语言中文网
指针变量保存的是地址,而地址本质上是一个整数,所以指针变量可以进行部分运算,例如加法、减法、比较等,请看下面的代码:

#include <stdio.h>

int main(){
    
    
    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);
    }else{
    
    
        printf("%d\n", *pa);
    }
    return 0;
}

运行结果:
在这里插入图片描述
从运算结果可以看出:pa、pb、pc 每次加 1,它们的地址分别增加 4、8、1,正好是 int、double、char 类型的长度;减 2 时,地址分别减少 8、16、2,正好是 int、double、char 类型长度的 2 倍。

指针变量加减运算的结果跟数据类型的长度有关,而不是简单地加 1 或减 1,这是为什么呢?
以 a 和 pa 为例,a 的类型为 int,占用 4 个字节,pa 是指向 a 的指针,如下图所示:
在这里插入图片描述
刚开始的时候,pa 指向 a 的开头,通过 *pa 读取数据时,从 pa 指向的位置向后移动 4 个字节,把这 4 个字节的内容作为要获取的数据,这 4 个字节也正好是变量 a 占用的内存。
如果pa++;使得地址加 1 的话,就会变成如下图所示的指向关系:
在这里插入图片描述
这个时候 pa 指向整数 a 的中间,*pa 使用的是红色虚线画出的 4 个字节,其中前 3 个是变量 a 的,后面 1 个是其它数据的,把它们“搅和”在一起显然没有实际的意义,取得的数据也会非常怪异。

如果pa++;使得地址加 4 的话,正好能够完全跳过整数 a,指向它后面的内存,如下图所示:
在这里插入图片描述
指针变量除了可以参与加减运算,还可以参与比较运算。当对指针变量进行比较运算时,比较的是指针变量本身的值,也就是数据的地址。如果地址相等,那么两个指针就指向同一份数据,否则就指向不同的数据。
上面的代码(第一个例子)在比较 pa 和 paa 的值时,pa 已经指向了 a 的上一份数据,所以它们不相等。而 a 的上一份数据又不知道是什么,这样会导致 printf() 输出一个没有意义的数,所以不要对指向普通变量的指针进行加减运算。

参考资料

  • C语言中文网
  • B站视频C指针
  • 书籍《C和指针》

猜你喜欢

转载自blog.csdn.net/burningCky/article/details/109668265
今日推荐