C语言-----指针初阶

目录

1、指针概述 

1.1 内存地址 

1.2 内存和指针

2、指针变量 

2.1 初识指针变量 

2.2 指针的大小 

2.3 使用指针变量 

2.4 指针的运算

3、指针与函数

3.1 指向函数的指针 

3.2 返回指针的函数 

4、void 指针类型 


访问内存中的数据有两种方式:直接访问和间接访问。直接访问就是通过变量来实现,因为变量是内存中某一块存储区域的名称;间接访问就是通过指针来实现。指针并不是用来存储数据的,而是用来存储数据在内存中的地址,可以通过访问指针达到访问内存中数据的目的。

1、指针概述 

1.1 内存地址 

计算机程序中使用的所有数据,必须存储在计算机的存储单元中,并且应能从计算机的存储单元中取出。每个存储单元都有唯一的地址,这就好比街道上每家每户都会有自己的门牌号一样。如下图所示,内存中从地址 1234 到地址 1237 中,存储了一个整型数值 66,而在内存地址1238中,存储了一个字符 “ A ”。

c27f81892a4c44fb95ed1ac81a8dc4eb.png

计算机内存被划分成按顺序编号的内存单元,这就是地址。如果在程序中定义了一个变量,在对程序进行编译时,系统就会给这个变量分配内存单元。

变量是内存中某一块存储区域的名称,对变量赋值就相当于把值存储到该存储区域中。如下图。 

d1143768a87b4ca7b29c52d96c1a9bd2.png

分析:表达式的进行如图所示,第 1 步是告诉计算机在存储中开辟一个整数的空间地址从1234到1237,也就是4个字节,第 2 步把数值 66 存储在这块存储区域,这块区域的名称叫做 value 。该表达式显示了变量和内存的直接联系。

不同的计算机使用不同的复杂的方式对内存进行编号,通常程序员不需要了解给定变量的具体地址,编译器会处理细节问题。在 C 语言中,只需要使用操作运算符 &(取地址运算符),就会返回一个对象在内存中的地址,如:&value,返回的地址指的是该存储区域的起始地址,对变量 value 来说就是1234。 

1.2 内存和指针

变量的内存地址就是变量的指针(指针就是地址(口语中说的指针通常指的是指针变量))。如果有一个变量专门用来存放另一个变量的指针,则称为指针变量(指针变量就是用来存放地址(存放在指针中的值都被当成地址处理))下图所:

5eda678b1eee47358cfeaf6f88c09489.png

p 是一个指针变量。变量 p 中存放的是变量 value 的指针(地址),指针变量 p 就是指向 value 的地址。

定义指针变量的一般形式:

指向数据类型 *指针变量名;

指针和变量的关系。代码和对应图如下: 

int i;/*声明变量i*/
int* p1;/*声明指向整型变量的指针p1*/
char c;/*声明变量c*/
char* p2;/*声明指向字符型变量的指针p2*/
p1=&i;/*指针赋值*/
p2=&c;/*赋值指针*/

66d62190bf3c4714a483c55a1421c74a.png

定义指针变量时,需注意:

(1)如果有int *p,指针变量名是 p,而不是 *p,*p是表达式。

(2)在定义指针变量时必须明确其指向的数据类型。 赋值的正确方式,如:

int i;
char c;
int* p;/*声明的是指向整型变量的指针p*/
p=&i;/*指针赋值时就须是整型变量的地址(除非有代码本身有特殊的需求)*/
/*p=&c;this is error*/

(3)指针变量中只能存放指针(地址),不要将一个非零数(或者任何其他非地址类型的数据)赋值给一个指针变量。如:

//int*p=1;/*this is error*/
int* p=NULL;/*正确的赋值形式,表示指针指向空*/

注意:指针变量 p 被初始化为NULL,值为NULL的指针称之为空指针。一般来说,所有的指针变量在定义时都必须初始化,否则就会成为野指针( 野指针概念: 野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的))。为了避免野指针,如果不明确要给指针赋什么值,就赋值为NULL。

2、指针变量 

通过上面的学习已经了解了内存是如何分配地址的,内存地址就是指针这一概念,也引出了指针变量的概念,专门用来存储变量地址的变量称之为指针变量。

2.1 初识指针变量 

指针变量也是变量。假如该指针变量名叫做 p,变量 p 的用途比较特殊,它很 “ 无私 ”,它代表的存储单元中存储的是另外一个变量的地址假如该变量名叫 value,可以理解为这个指针变量 p 指向变量 value。当然可以直接访问 value,从而获得 value 的数据,也可以间接地通过访问 p 来获得 value 的数据。 如:

#include<stdio.h>
int main()
{
	int value = 1;
	int* p = &value;
	*p = *p + 1;
	printf("value=%d,*p=%d", value, *p);
	return 0;
}

720abd64cde146fdb493135b468dc832.png

分析:代码中的 p = &value,表示指针变量 p 指向变量 value,在这里是使用 & 取地址运算符进行关联的;代码 *p = *p + 1 表示改变指针变量 p 指向的变量的值,在这里是使用 * (取内容)指针运算符间接访问变量 value。

注 意:* 称为指针运算符,它表示获取指针变量所指向的变量的值,这是非常重要的。 

假设已经存在一个整型变量 value,那么要得到指向变量 value 的指针变量 p,如下:

int* p=NULL;
p=&value;

 也可在声明指针变量 p 时,直接对其初始化,如下:

int* p=&value;

对于指针变量 p,如果在程序中使用了指针运算符,也就是使用了 *p,那么 *p 表示指针 p 指向的存储单元的值,也就是变量 value 的值,从数值意义上理解,二者是一样的。

看一段代码并对其进行分析:

#include<stdio.h>
int main()
{
	/*对变量进行声明和赋值*/
	int value = 1;
	int temp = 2;
	int* p1 = &value;
	int* p2 = &temp;
	int* p = NULL;
	printf("未交换前*p1、*p2值为:> ");/*打印出未交换地址时p1、p2存储的地址所对应单元的内容(所指向的存储单元的值)*/
	printf("*p1 = %d,*p2 = %d\n\n", *p1, *p2);
	if (value < temp)/*符合条件,对地址进行交换*/
	{
		p = p2;
		p2 = p1;
		p1 = p;
	}
	printf("交换后*p1、*p2值为:> ");/*打印出交换地址后p1、p2存储的地址所对应单元的内容(所指向的存储单元的值)*/
	printf("*p1 = %d,*p2 = %d\n\n", *p1, *p2);
	printf("value、temp的值:> ");
	printf("value = %d,temp = %d\n", value , temp);
	return 0;
}

63fc39383fa948d994ae179feaa52db5.png

分析:为什么交换后输出的 value 和 temp 的值没有变化,而 *p1 和 *p2 的值发生了改变呢?这是因为,这里交换的是 p1 和 p2 中存储的数据,p1 里存储的数据 &value 变成了 &temp,p2里存储的数据由 &temp 变成了 &value,这就意味着,p1 指向由指向变量 value 改为了指向变量 temp,p2 指向则由指向变量 temp 改为了指向变量 value,但此时 value 和 temp 所表示的存储区域的值并没有变化,所以发生了 value 和 temp 不变,而 *p1 和 *p2 交换的结果。

2.2 指针的大小 

 在32位的机器上,地址是 32 个 0 或者 1 组成二进制序列,那地址就得用 4 个字节的空间来存储,所以一个指针变量的大小就应该是 4 个字节;在64位机器上,有64个地址线,那一个指针变量的大小是 8 个字节,才能存放一个地址。

 总结:

(1)指针是用来存放地址的,地址是唯一标示一块地址空间的。

(2)指针的大小在32位平台是4个字节,在64位平台是8个字节。

2.3 使用指针变量 

 修改上面的例子,如下:

#include<stdio.h>
int main()
{
	/*对变量进行声明和赋值*/
	int value = 1;
	int temp = 2;
	int* p1 = &value;
	int* p2 = &temp;
	int* p ;
	printf("未交换前*p1、*p2值为:> ");/*打印出未交换时p1、p2存储的地址所对应单元的内容(所指向的存储单元的值)*/
	printf("*p1 = %d,*p2 = %d\n\n", *p1, *p2);
	if (value < temp)/*符合条件,对p1和p2所指向的存储单元的值进行交换*/
	{
		*p = *p2;
		*p2 = *p1;
		*p1 = *p;
	}
	printf("交换后*p1、*p2值为:> ");/*打印出交换后p1、p2存储的地址所对应单元的内容(所指向的存储单元的值)*/
	printf("*p1 = %d,*p2 = %d\n\n", *p1, *p2);
	printf("value、temp的值:> ");
	printf("value = %d,temp = %d\n", value, temp);
	return 0;
}

e20055407d2842b89c8c7a0922ab7449.png

 分析:运行,弹出错误对话框,为什么? 我们注意到了修改的代码和原来的代码的 if 语句中,交换的并不是指针变量 p1 和 p2 自身存储的值(也就是所存储的地址)(原来的代码是这样交换的 ),而是交换 p1 和 p2 所指向的存储单元的值。也就是说,我们试图去交换变量 value 和 temp 的值,但是指针变量 p 只是声明了,但没有明确的指向,那它很可能指向了一个内存中不存在的存储区域,这样执行 *p = *p1 时就会把变量 value 的值存储在一个不存在的存储区域内,程序自然会报错。

再次对代码进行修改 ,如下:

#include<stdio.h>
int main()
{
	/*对变量进行声明和赋值*/
	int value = 1;
	int temp = 2;
	int* p1 = &value;
	int* p2 = &temp;
	int p ;
	printf("未交换前*p1、*p2值为:> ");/*打印出未交换时p1、p2存储的地址所对应单元的内容(所指向的存储单元的值)*/
	printf("*p1 = %d,*p2 = %d\n\n", *p1, *p2);
	if (value < temp)/*符合条件,对p1和p2所指向的存储单元的值进行交换*/
	{
		p = *p2;
		*p2 = *p1;
		*p1 = p;
	}
	printf("交换后*p1、*p2值为:> ");/*打印出交换后p1、p2存储的地址所对应单元的内容(所指向的存储单元的值)*/
	printf("*p1 = %d,*p2 = %d\n\n", *p1, *p2);
	printf("value、temp的值:> ");
	printf("value = %d,temp = %d\n", value, temp);
	return 0;
}

fdb5a4f89f804241941ca05c5f3231f6.png

分析: 运行,又会出现什么情况?程序正常执行,没有异常错误报出,原因是变量 p 不再是指针变量,它的存储区域是声明 p 时程序分配好的、明确的。交换后输出的 value 、 temp、 *p1 和 *p2 的值都发生了改变。这次是交换 p1 和 p2 所指向的存储单元的值。也就是说,交换变量 value 和 temp 的值,所以最后导致了value 、 temp、 *p1 和 *p2 的值都发生了改变。

2.4 指针的运算

关于指针变量的运算方法,指针变量自身又是怎么运算的呢?指针的运算就是地址的运算。由于这一特点,指针运算不同于普通变量,只允许有限的几种运算。除了可以对指针赋值外,指针的运算还包括移动指针、两个指针相减、指针与指针或指针与地址之间进行比较等。可以通过将指针加减一个整数,或者通过对指针赋值来移动指针。

如:假设 p 是一个指针 ,p+n、p-n、p++、p--、++p和--p等,其中n是一个整数。

将指针 p 加上或者减去一个整数 n,表示 p 向地址增加或减小的方向移动 n 个元素单元,从而得到一个新的地址,使其能访问新地址中的数据。每个数据单元的字节数取决于指针的数据类型。

假设有五个变量a、b、c、d 和 e 都是整型数据 int 类型,它们在内存中占据一块连续的存储区域,地址从 1234 到 1250(为什么地址从 1234 到 1250,原因:一个字节给一个对应的地址),每个变量占用 4 个字节。指针变量 p 指向变量a,也就是 p 的值是1234,那么 p+1 按照前面的介绍,表示 p 向地址增加的方向移动了 4 个字节,从而指向一个新的地址,这个值就是 1238 指向了变量 b (变量从 a 到 e 占用一块连续的存储区域);同理,p+2地址就是 1242,再次增加了 4个字节,指向了变量 c,依次类推。 图如下:

cc5f01451f424130bcecaeb435ce5752.png

 指针和指针类型:

 变量有不同的类型,整型、字符型、浮点型等。指针也有类型,char* 类型的指针是为了存放 char 类型变量的地址。 short* 类型的指针是为了存放 short 类型变量的地址。 int* 类型的指针是为了存放 int 类型变量的地址。那么指针的类型有什么意义呢?指针的类型和指针的运算有着莫大的关系。

 例1:看代码总结知识

#include <stdio.h>
int main()
{
	int n = 10;
	char* pchar = &n;
	int* pint = &n;
	printf("%p\n", &n);
	printf("%p\n", pchar);
	printf("%p\n", pchar + 1);
	printf("%p\n", pint);
	printf("%p\n", pint + 1);
	return 0;
}

ad868cf0079549afa0a63e0f28b4e691.png

 分析:从结果可以看出&n、pchar、pint的地址都是00000073567BFCA4的。字符指针pchar+1变成了00000073567BFCA5,整型指针pint+1变成了00000073567BFCA8;可以看出,字符指针+1向后走了1个字节,跳过一个字符(字符指针+2跳过两个字符,依次类推);整型指针+1向后走了4个字节,跳过一个整型(整型指针+2跳过两个整型,依次类推)。不同类型的指针+1或者+n向走的字节数是不一样的。同理,减也是也是一样的。

总结: 指针的类型决定了指针向前或者向后走一步有多大(距离)。

  例2:对比两段代码总结知识

(1) 

#include <stdio.h>
int main()
{
	int n = 0x11223344;/*把一个十六进制数放到 n 里面*/
	int* pint = &n;
	*pint = 0;
	return 0;
}

F10调试,打开内存,地址那里:&n,这个时候可以看到 n 的地址和内存里的四个字节存储的数据。

91c39e8ccf924c588296cd9196763c5e.png

 再按F10调试,将指针 pint 进行解引用操作后赋值为零,可看到内存里的四个字节存储的数据都变为0;

8915acb622144063aec49e0662b24a0a.png

 (2)

#include <stdio.h>
int main()
{
	int n = 0x11223344;
	char* pchar = &n;
	*pchar = 0;
	return 0;
}

换成字符指针,存储 n 的地址, F10调试,打开内存,地址那里:&n,这个时候可以看到 n 的地址和内存里四个字节存储的数据,内存里存储的数据和上例一摸一样。

f44fb75affcc4fc0a00729f0364db9ad.png

 再按F10调试,将指针 pchar 进行解引用操作后赋值为零,仅看到内存里的一个字节存储的数据变为0,其他三个字节存储的数据都不发生改变。

0d89c5b6e31841988ea6f8d4b4578b10.png

对比上面的两段代码可以得出:指针类型决定了指针进行解引用操作的时候,能访问空间的大小。

 总结:指针的类型决定了,对指针解引用的时候有多大的权限(能操作几个字节)。 比如: char* 的指针解引用就只能访问一个字节,而 int* 的指针的解引用就能访问四个字节。

3、指针与函数

3.1 指向函数的指针 

用指针变量可以指向一个函数。函数在程序编译时被分配了一个入口地址,这个函数的入口地址就称为函数的指针。 (函数指针---是指向函数的指针---存放函数地址的指针)

函数指针变量常用的用途之一是把指针作为参数传递到其他函数。指向函数的指针可以作为参数,以实现函数地址的传递,这样就能够在被调用的函数中使用实参函数。 

指向函数的指针的代码,例: 

#include<stdio.h>

int Add(int x, int y)
{
	int z = 0;
	z = x + y;
	return z;
}

int main()
{
	int a = 10;
	int b = 20;
	int c = 0;
	int (*pAdd)(int, int) = Add;/*指向函数的指针Add函数(&Add和Add是等效的,都是取出Add的地址)*/
	printf("Add函数的地址为:%p\n", Add);
	printf("用来存放Add函数地址的函数指针变量pAdd中的值为:%p\n", pAdd);
	c = (*pAdd)(a, b);/*Add函数的返回值*/
	printf("%d", c);
	return 0;
}

 分析:从结果可看出,pAdd成功存储了Add的地址;int (*pAdd)(int,int); ”说明pAdd是一个指向函数的整型指针(函数是:int Add(int x,int y)函数的类型是 int 。若函数是:int* Add(int* x,int* y),则指向函数的指针为:int* (*pAdd)(int*,int*))。“ c=(*pAdd)(a,b); ”说明pAdd确切指向函数Add,相当于调用了c=Add(a,b);

指向函数的指针作为参数,以实现函数地址的传递,例:

/*****  修改冒泡排序,使它可以排序任何类型的数据,此例是对一个数组元素进行升序排序*****/

#include<stdio.h>

int cmp_int(const void* e1,const void* e2 )
{
	//比较两个整型的大小
	return (*(int*)e1 - *(int*)e2);
}
void sawp(char* e1, char* e2,int width)
{
	/*实现两个元素的交换*/
	int i = 0;
	for (i = 0; i < width; i++)
	{
		char temp = *e1;
		*e1 = *e2;
		*e2 = temp;
		e1++;
		e2++;
	}
}

void bubble_sort(void *base,int num,int width,int (*cmp)(const void *e1,const void *e2))
{
	int i = 0;
	int j = 0;
	for (i = 0; i < num - 1; i++)//趟数
	{
		for (j = 0; j < num - i - 1; j++)//一趟比较的次数
		{
			if ((*cmp)((char*)base + j * width, (char*)base + (j + 1) * width)>0)
			{
				sawp((char*)base + j * width, (char*)base + (j + 1) * width,width);
			}
		}
	}
}
void test()
{
	int i = 0;
	int arr[10] = { 9,8,7,6,5,4,3,2,1,0 };
	int sz = sizeof(arr) / sizeof(arr[0]);/*计算数组元素个数*/
	bubble_sort(arr,sz,sizeof(arr[0]),cmp_int);/*对数组进行升序排序*/
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
}
int main()
{
	test();
	return 0;
}

这里对于排序算法就不做解释分析了,主要目的是进一步了解函数指针的用途。 

3.2 返回指针的函数 

函数可以返回数值型、字符型、布尔型(bool:C语言里是用整数来代替布尔结果这一个角色的,只要数字非0都表示为 true ,0表示 false ;倒过来说,表示 true 的在执行期间会被转换为数字1处理, false 则用0代替。)等数据,也可以带回指针型的数据叫做返回指针值的函数。定义形式为:

类型名 *函数名(参数列表);

例如下式表示的含义是:Add函数调用后返回值的数据类型是整型指针。

int* Add(int* x,int* y);

返回指针的函数,例如:

#include<stdio.h>

int* Add(int* x, int* y)
{
	/*返回指针的函数*/
	static int temp = 0;
	temp = (*x) + (*y);
	*x = 66;
	*y = 99;
	return &temp;
}

int main()
{
	int a = 10;
	int b = 20;
	int* value = NULL;
	value=Add(&a, &b);/*用一个指针变量接收Add函数返回的地址*/
	printf("Value: %d\n", *value);/*拿到地址对地址进行解引用操作*/
	printf("a: %d\n", a);
	printf("b: %d\n", b);
	return 0;
}

 分析:从结果可以看出,函数Add接收a、b两个数的地址,对地址进行解引用操作,求这两个数的和,并将 temp 的地址作为 Add 的函数返回值。函数只能有一个返回值,然而我们却偏偏希望返回给主函数3个值,还有两个就是a、b重新赋值的,使用的方法叫做引用

int a=10;
int b=20;
int* value = NULL;
value=Add(&a, &b);/*参数&a、&b就是引用,用来接收形参*x、*y*/

在函数 Add() 中,“ *x=66; *y=99; ”就是把 66 和 99 存放在指针变量 x、y 所指向的存储单元中,也就是存放在实参a、b中。

通过本例子的引用方法,给我们开发程序带来很大的便利,特别是需要调用函数返回多个返回值时,我们可以根据需要灵活使用。 

4、void 指针类型 

 void 指针类型,可以用来指向一个抽象类型的数据,在将它的值赋给另一个指针变量时要进行强制类型转换,使之适合于被赋值的变量的类型。(void*)为 “ 无类型指针 ”。

通过一个例子来说明 void 指针类型的含义和用法,代码如下:

#include<stdio.h>
int main()
{
	char* pchar = NULL;
	void* pvoid = NULL;/*在将 pvoid 的值赋给另一个指针变量时要进行强制类型转换,使之适合于被赋值的变量的类型。*/
	pchar = (char*)pvoid;/*对 pvoid 进行强制类型转换,使之适合于 pchar 的变量的类型。(pchar 变量的类型是 char*)*/
	return 0;
}

同样可以使用 (void*)pchar 将 pchar 转换成 void* 类型,代码如下:

pvoid=(void*)pchar;

不只对变量可以使用 void 指针类型,也可以将一个函数定义为 void* 类型。如:下面的代码表示函数 funtion 返回的是一个地址,它指向空类型,如需要引用此地址,也需要根据情况对返回的地址进行类型转换。如下:对 funtion 函数调用得到的地址要进行以下转换。

#include<stdio.h>
void* funtion(char ch1, char ch2)
{
	//everything
   //return 地址
}
int main()
{
	char ch1 = 'A';
	char ch2 = 'B';
	char* pchar = NULL;
	pchar = (char*)funtion(ch1,ch2);/*对 funtion 函数调用得到的地址要进行转换,使之适合于 pchar 的变量的类型。( pchar 变量的类型是 char*)*/
	return 0;
}

 对于void 指针类型的使用,需要注意以下几点:  

(1)void* 类型的指针,可以接收任意类型的地址。如:

#include<stdio.h>

int main()
{
	/*void* 类型的指针接收下列的其他类型的地址是没有问题的*/
	int a = 10;
	char ch = 'W';
	void* pvoid1 = &a;
	void* pvoid2 = &ch;
	return 0;
}

(2)void* 类型的指针,不能进行解引用操作。

(3)void* 类型的指针,不能进行加减(+-)整数的操作。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值