C语言基础知识(6): 指针


注:转载请标明原文出处链接:https://xiongyiming.blog.csdn.net/article/details/105498628


指针是C语言中广泛使用的一种数据类型。运用指针编程是C语言最主要的风格之一。利用指针变量可以表示各种数据结构;能很方便地使用数组和字符串;并能像汇编语言一样处理内存地址,从而编出精练而高效的程序。指针极大地丰富了C语言的功能。



1 初识指针

举例说明

int main()

{
	int * p; // p是变量的名字;  int * 表示p变量存放的是int类型变量的地址
    int i=3; 
    p=&i;  // p保存的是i的地址,因此指向i;p不是i,i不是p;
           //修改p的值不影响i的值,修改i的值不影响p的值;


   //错误示例
    p=i; //错误,因为类型不一致, p只能存放int 类型变量的地址,不能存放int类型变量的值
    p=55; // 错误
   //错误示例


return 0}

注意事项:

  1. int ∗ p; // p是变量的名字; int ∗ 表示p变量存放的是int类型变量的地址;
                // p是变量名,p变量的数据类型是 int ∗ 类型,int ∗ 类型是存放int变量地址的类型。

  2. p=&i; //p保存的是i的地址,因此指向i;

  3. 如果一个指针变量指向了某个变量,则 ∗ 指针变量 就等同于普通变量;

  4. 如果p是指针变量,并且p存放了普通变量i的地址,则p指向了普通变量; ∗ p 就等同于 i


指针和指针变量

  1. 指针就是地址,地址就是指针,地址就是内存单元的编号。

  2. 指针变量就是存放地址的变量。

  3. 指针指针变量是两个不同的概念,通常我们叙述时,会把指针变量简称为指针。




2 指针的重要性

  1. 指针可以表示一些复杂的数据结构;
  2. 指针可以快速地传递数据;
  3. 指针可以使函数返回一个以上的值;
  4. 指针能直接访问硬件;
  5. 指针能够方便的处理字符串;
  6. 指针是理解面向对象语言中引用的基础;

总的来说:指针是C语言的灵魂




3 指针和地址

3.1 基础知识

地址是内存单元的编号,从0开始的非负整数。
地址的范围有:[0, 4G-1],[0, 8G-1],[0, 16G-1]等类型。

  1. 指针就是地址,地址就是指针,地址就是内存单元的编号。

  2. 指针变量就是存放地址的变量。

  3. 指针指针变量是两个不同的概念,通常我们叙述时,会把指针变量简称为指针。


CPU与内存之间的连接图如下图所示:

在这里插入图片描述


对于CPU与地址线之间,如下图所示:

在这里插入图片描述



3.2 代码示例


示例1——指针初始化1

#include<stdio.h>
#include<iostream>


int main()
{


	int i = 5;
	int *q;
	int *p;

	p = &i;
	printf("*p=%d\n", *p);
	printf("-----------\n");

	// *q = p; //错误
	// *q = *p; //错误
	// p = q; //错误  q未初始化,则*q代表的内存单元的控制权限并没有分配给本程序
	q = p; //正确

	printf("*q=%d\n", *q);


	system("pause");
	return 0;
	

}

运行结果

在这里插入图片描述


注: 未经赋值的指针变量不能使用,否则将造成系统混乱,甚至死机。指针变量的赋值只能赋予地址, 决不能赋予任何其它数据,否则将引起错误。



示例2——交换两个数字

方法1

#include<stdio.h>
#include<iostream>


int main()
{
	int a = 3;
	int b = 5;
	int t;

	printf("a=%d, b=%d\n", a, b);

	t = a;
	a = b;
	b = t;

	printf("a和b交换后:\n");
	printf("a=%d, b=%d\n", a, b);

	system("pause");
	return 0;
	

}

运行结果

在这里插入图片描述



按照上述方法,将交换用函数来实现,然后在主函数调用交换函数。

#include<stdio.h>
#include<iostream>

int swap(int i,int j)
{
	int t;
	t = i;
	i = j;
	j = t;

	return 0;
}


int main()
{
	int a = 3;
	int b = 5;
	int t;

	printf("a=%d, b=%d\n", a, b);

	swap(a,b);

	printf("a和b交换后:\n");
	printf("a=%d, b=%d\n", a, b);

	system("pause");
	return 0;
	

}

运行结果

在这里插入图片描述


如上图所示,可以发现a和b的值并没有交换。因为a和b通过调用swap函数交换时,函数结束调用就会立即释放,但主函数a和b的值并没有变化。此时需要使用指针来解决。使用指针变量作为函数参数



方法2:利用指针

#include<stdio.h>
#include<iostream>

int swap(int * i,int * j)
{
	int t;
	t = *i;  //i是 int * 类型, *i是int类型
	*i = *j;
	*j = t;


	/*错误写法
	int *t;
	t = i;
	i = j;
	j = t;
	仅仅是 i和j 的地址进行了交换。不能企图通过改变指针形参的值而使指针实参的值改变。
	*/

	return 0;
}


int main()
{
	int a = 3;
	int b = 5;
	int t;

	printf("a=%d, b=%d\n", a, b);

	swap(&a,&b);

	printf("a和b交换后:\n");
	printf("a=%d, b=%d\n", a, b);

	system("pause");
	return 0;
	

}

运行结果

在这里插入图片描述



程序中 ∗ 的含义有:

(1) 乘法

(2) 定义指针变量
例如:int ∗ p; //定义一个名为p的变量,int ∗ 表示p只能存放int 变量的地址

(3) 指针运算符(可以理解为取地址的逆运算)
指针运算符可以放在已定义好的指针变量的前面。如果p是一个已经定义好的指针变量,则 ∗ p表示以p的内容为地址的变量。



示例3——指针初始化2

#include<stdio.h>
#include<iostream>




int main()
{
	int a = 1;
	int * p;

	//*p=2;//错误 p并未初始化,则*p代表的内存单元的控制权限并没有分配给本程序
	p = &a;

	printf("*p=%d,a=%d\n", *p, a);

	*p = 2;
	printf("*p=%d,a=%d\n", *p, a);

	system("pause");
	return 0;
	

}

运行结果

在这里插入图片描述



3.3 小结

如何通过被调函数修改主调函数中普通变量的值?

(1) 实参必须为该普通变量的地址;
(2) 形参必须为指针变量;
(3) 在被调函数中,通过 ∗ 形参名= ……   的方式就可以修改主函数相关变量的值;




4 指针和数组

  1. 一个变量有一个地址,一个数组包含若干元素,每个数组元素都在内存中占用存储单元,它们都有相应的地址。所谓数组的指针是指数组的起始地址,数组元素的指针是数组元素的地址。
  2. 一个数组是由连续的一块内存单元组成的。
  3. 数组名就是这块连续内存单元的首地址。
  4. 一个数组也是由各个数组元素(下标变量)组成的。
  5. 每个数组元素按其类型不同占有几个连续的内存单元。
  6. 一个数组元素的首地址也是指它所占有的几个内存单元的首地址。

4.1 指针和一维数组

定义一个指向数组元素的指针变量,例如:

int a[5]; //a是数组名,5数组元素的个数
int a[3][4]; //3行4列 a[0][0] 是第一个元素 a[i][j] 是第i+1行 j+1列元素


代码示例

#include<stdio.h>
#include<iostream>


int main()
{
	int a[5]; //a是数组名,5数组元素的个数 
	//int a[3][4]; //3行4列  a[0][0]是第一个元素  a[i][j]是第i+1行 j+1列元素

	printf("%#X\n", a);//数组元素的首地址
	printf("%#X\n", &a[0]);//数组元素的首地址
	
	system("pause");
	return 0;
	

}

运行结果

在这里插入图片描述


结论: 数组中数组就是该数组的首地址,属于指针类型。



如果一个函数要处理一个一维数组,则需要接受该数组的哪些信息?

(1) 一维数组第一个元素的地址(元素首地址),用指针即可完成;
(2) 一维数组的长度;


代码示例

#include<stdio.h>
#include<iostream>


//f函数可以输出任何一个一维数组的内容
int f(int * pArr, int len)
{
	int i;

	for (i = 0; i < len; i++)
	{
		printf("%d\n", *(pArr + i));//  *(pArr+i) 等价于 pArr[i]
	}

	return 0;
}


int main()
{
	int a[5] = { 1,2,3,4,5 };

	f(a, 5); // a是 int * 类型

	
	system("pause");
	return 0;
	

}

运行结果

在这里插入图片描述


注: 一维数组中: ∗ (pArr+i) 等价于 pArr[i] 等价于 ∗ (a+i) 等价于 a[i] 。


在这里插入图片描述



如何使用数组中的元素?

(1) 下标法:用 a[i] 形式访问数组元素;
(2) 指针法:即采用 ∗ (a+i) 或 ∗ (p+i) 形式,用间接访问的方法来访问数组元素,其中 a 是数组名, p 是指向数组的指针变量,其处值 p=a 。


指针变量占用的字节数:32位下占用4个字节,64位下占用8个字节。
一个指针变量无论指向的变量的占用4个字节,该指针变量占用的字节不变。


代码示例

#include<stdio.h>
#include<iostream>



int main()
{
	int * p1;
	double * p2;
	float * p3;
	char * p4;


	printf("sizeof(int)=%d\n", sizeof(int));
	printf("sizeof(double)=%d\n", sizeof(double));
	printf("sizeof(float)=%d\n", sizeof(float));
	printf("sizeof(char)=%d\n", sizeof(char));
		
	printf("----------------------\n");
	
	printf("sizeof(p1)=%d\n", sizeof(p1));
	printf("sizeof(p2)=%d\n", sizeof(p2));
	printf("sizeof(p3)=%d\n", sizeof(p3));
	printf("sizeof(p4)=%d\n", sizeof(p4));

	
	system("pause");
	return 0;
	

}

运行结果

(1) 32位环境下

在这里插入图片描述


(2) 64位环境下

在这里插入图片描述



1) 指针变量可以实现本身的值的改变。如 p++是合法的;而 a++是错误的。
   因为 a 是数组名,它是数组的首地址,是常量。

2)  *p++,由于++*同优先级,结合方向自右而左,等价于*(p++)3)  *(p++)*(++p)作用不同。若 p 的初值为 a,则*(p++)等价 a[0]*(++p)等价 a[1]4)  (*p)++表示 p 所指向的元素值加 15)  如果 p 当前指向 a 数组中的第 i 个元素,则:
     *(p--) 相当于 a[i--]*(++p) 相当于 a[++i]*(--p) 相当于 a[--i]



代码示例

#include<stdio.h>
#include<iostream>



int main()
{
	int * p, i, a[10];
	p = a;
	for (i = 0; i < 10; i++)
	{
		*p++ = i;
	}

	p = a;

	for (i = 0; i < 10; i++)
	{
		printf("a[%d]=%d\n", i, *p++);
	}
		

	
	system("pause");
	return 0;
	

}

运行结果

在这里插入图片描述



4.2 指针和二维数组

4.2.1 二维数组的地址

设有整型二维数组 a[3][4]如下:
在这里插入图片描述


则该数组定义为:int a[3][4]={{0,1,2,3},{4,5,6,7},{8,9,10,11}};

设数组 a 的首地址为 1000,各下标变量的首地址及其值如下图所示:


在这里插入图片描述


C语言可以把一个二维数组分解为多个一维数组来处理。因此数组 a 可分解为三个一维数组,即 a[0], a[1], a[2]。每一个一维数组又含有四个元素。例如 a[0]数组,含有 a[0][0], a[0][1], a[0][2], a[0][3]四个元素。

在这里插入图片描述


从二维数组的角度来看, a 是二维数组名, a 代表整个二维数组的首地址,也是二维数组 0 行的首地址,等于 1000。 a+1 代表第一行的首地址,等于 1008。如下图所示:

在这里插入图片描述


a[0]是第一个一维数组的数组名和首地址,因此也为 1000。
∗(a+0) 或 ∗a 是与 a[0] 等效的, 它表示一维数组 a[0] 0 号元素的首地址,也为 1000。
&a[0][0]是二维数组 a 的 0 行 0列元素首地址,同样是 1000。
因此, a, a[0], ∗(a+0), ∗a, &a[0][0] 之间是相等的。


同理, a+1 是二维数组 1 行的首地址,等于 1008。
a[1]是第二个一维数组的数组名和首地址,因此也为 1008。
&a[1][0] 是二维数组 a 的 1 行 0 列元素地址,也是 1008。
因此 a+1, a[1], ∗(a+1), &a[1][0] 之间是相等的。
由此可得出: a+i, a[i], ∗(a+i), &a[i][0] 是等同的。


此外, &a[i] 和 a[i] 也是等同的。因为在二维数组中不能把 &a[i] 理解为元素 a[i]的地址,不存在元素 a[i]。
C语言规定,它是一种地址计算方法,表示数组 a 第 i 行首地址。
由此,我们得出: a[i], &a[i], ∗(a+i)和 a+i 也都是等同的。


a[0]也可以看成是 a[0]+0,是一维数组 a[0]的 0 号元素的首地址,
而 a[0]+1则是 a[0]的 1 号元素首地址,
由此可得出 a[i]+j 则是一维数组 a[i] 的 j 号元素首地址,它等于 &a[i][j]。


在这里插入图片描述



由 a[i]=∗(a+i) 得 a[i]+j=∗(a+i)+j 。
由于 ∗(a+i)+j 是二维数组 a 的 i 行 j 列元素的首地址,所以,该元素的值等于 ∗(∗(a+i)+j) 。



代码示例

#include<stdio.h>
#include<iostream>


int main()
{
	int a[3][4] = { {0,1,2,3},{4,5,6,7},{8,9,10,11}};
	printf("a=%d\n", a);
	printf("*a=%d\n", *a);
	printf("a[0]=%d\n", a[0]);
	printf("&a[0]=%d\n", &a[0]);
	printf("&a[0][0]=%d\n", &a[0][0]);

	printf("------------------------\n");

	printf("a + 1=%d\n", a + 1);
	printf("*(a + 1)=%d\n", *(a + 1));
	printf("a[1]=%d\n", a[1]);
	printf("&a[1]=%d\n", &a[1]);
	printf("&a[1][0]=%d\n", &a[1][0]);

	printf("------------------------\n");

	printf("a + 2=%d\n", a + 2);
	printf("*(a + 2)=%d\n", *(a + 2));
	printf("a[2]=%d\n", a[2]);
	printf("&a[2]=%d\n", &a[2]);
	printf("&a[2][0]=%d\n", &a[2][0]);

	printf("------------------------\n");

	printf("a[1] + 1=%d\n", a[1] + 1);
	printf("*(a + 1) + 1=%d\n", *(a + 1) + 1);

	printf("------------------------\n");

	printf("*(a[1] + 1)=%d,*(*(a + 1) + 1)=%d\n", *(a[1] + 1), *(*(a + 1) + 1));
	
	system("pause");
	return 0;
	

}

运行结果

在这里插入图片描述



4.2.2 指向二维数组的指针变量

把二维数组 a 分解为一维数组 a[0], a[1], a[2]之后,设 p 为指向二维数组的指针变量。则二维数组a可定义为:
int (∗p)[4];

它表示 p 是一个指针变量,它指向包含 4 个元素的一维数组。
若指向第一个一维数组a[0],其值等于 a, a[0],或 &a[0][0] 等。而 p+i 则指向一维数组 a[i]。
从前面的分析可得出 ∗(p+i)+j 是二维数组 i 行 j 列的元素的地址,而 ∗(∗(p+i)+j) 则是 i 行 j 列元素的值。


二维数组指针变量说明的一般形式为:

        类型说明符 (∗ 指针变量名)[长度]

其中 “类型说明符” 为所指数组的数据类型。“ ∗ ” 表示其后的变量是指针类型。 “长度” 表示二维数组分解为多个一维数组时,一维数组的长度,也就是二维数组的列数。应注意“(*指针变量名)”两边的括号不可少,如缺少括号则表示是指针数组,意义就完全不同了。


代码示例

#include<stdio.h>
#include<iostream>



int main()
{
	int a[3][4] = { {0,1,2,3},{4,5,6,7},{8,9,10,11}};
	int(*p)[4];
	int i, j;
	p = a;
	for (i = 0; i < 3; i++)
	{
		for (j = 0; j < 4; j++) 
		{
			printf("%2d ", *(*(p + i) + j));
		}
			
		printf("\n");
	}
	
	system("pause");
	return 0;
	

}

运行结果

在这里插入图片描述




5 指针和字符串

在 C 语言中,可以用两种方法访问一个字符串(数组和指针)。

(1) 用字符数组存放一个字符串,然后输出该字符串

#include<stdio.h>
#include<iostream>


int main()
{
	char string[] ="I love China!";
	printf("%s\n", string);
	
	system("pause");
	return 0;
	

}

运行结果

在这里插入图片描述


分析:string 是数组名,它代表字符数组的首地址。

在这里插入图片描述



(2) 用字符串指针指向一个字符串

#include<stdio.h>
#include<iostream>


int main()
{
	char * string = (char *)"I love China!";
	printf("%s\n", string);
	
	system("pause");
	return 0;
	

}

运行结果

在这里插入图片描述



使用字符串指针变量与字符数组的区别

用字符数组和字符指针变量都可实现字符串的存储和运算。但是两者是有区别的。在使用时应注意以下几个问题:

  1. 字符串指针变量本身是一个变量,用于存放字符串的首地址。而字符串本身是存放在以该首地址为首的一块连续的内存空间中并以‘ \0’作为串的结束。字符数组是由于若干个数组元素组成的,它可用来存放整个字符串。

  2. 对字符串指针方式

char *ps="C Language";

可以写为:
char *ps;
ps="C Language";

而对数组方式:

static char st[]={"C Language"};

不能写为:
char st[20];
st={"C Language"};

而只能对字符数组的各元素逐个赋值。




6 多级指针(指针的指针)

代码示例

#include<stdio.h>
#include<iostream>


int main()
{
	
	int i = 10;
	int * p = &i;
	int ** q = &p;
	int *** r = &q;

	printf("i=%d, i=%d, i=%d\n", *p, **q, ***r);
	
	system("pause");
	return 0;
	

}

运行结果

在这里插入图片描述


具体分析如下图所示:

在这里插入图片描述




7 小结

在这里插入图片描述

发布了187 篇原创文章 · 获赞 2039 · 访问量 97万+

猜你喜欢

转载自blog.csdn.net/zaishuiyifangxym/article/details/105498628