解析指针!

指针详解!!!

 指针是C中极为重要的一个概念,在计算机科学中,指针(Pointer)是编程语言中的一个对象,利用地址,可以指向存在电脑存储器中另一个地方的值。由于通过地址能找到所需的变量单元,可以说,地址指向该变量单元。因此,将地址形象化的称为“指针”。意思是通过它能找到以它为地址的内存单元。而在不同的机器中,指针变量所占据的内存也不相同。
32位机器中,一共有32个跟地址线,每个地址线出来一个0或1电信号,根据分配,地址用4个字节的空间来存储最为合适,因此指针变量大小即为 4个字节
同理可分析有,在 64位机器中,指针变量则占据8个字节的内存
接下来,我将从指针级别,指针与数组关系等层面来详细的解析一下指针。
1.一级指针
首先来看一个例子:
#include 
int main()
{
	int num = 10;//在内存中开辟一块空间
	int *p = #//这里我们对变量num,取出它的地址,可以使用&操作符。
	//将num的地址存放在p变量中,p就是一个指针变量。
	return 0;
}
此处代码的p即为一个一级指针,其内存储的是变量num的地址。
我们可以验证一下:
#include <stdio.h>
int main()
{
	int num = 10;//在内存中开辟一块空间
	int *p = #//这里我们对变量num,取出它的地址,可以使用&操作符。
                  //将num的地址存放在p变量中,p就是一个指针变量。
	printf("%p\n", p);  //将p中保存的地址输出
	system("pause");
	return 0;
}
观察num在内存中的地址及最终输出如下:
通过上面我们可以总结有: 指针就是变量,用来存放地址的变量。
定义一个指针: type *  Pointer_name
type 基类型,用来指定此指针变量可以指向的变量的类型。上面的例子中:int *,即为 int 型的指针,表示其指向的是整形变量。同时,基类型也确定了对指针解引用的时候有多大的权限(能操作几个字节),上面的例子中:int * 的指针解引用能访问4个字节,而char * 的指针的解引用就只能访问1个字节。
Pointer_name 是指针变量名,由使用者取名。
如何引用指针?如上例子:p = &num,通过取地址符号 & ,将变量的地址赋给变量。
引用指针变量所指向的变量则要通过解引用符号 * 。
 
2. 二级指针
从上面可知指针变量是变量,是变量就有地址,那指针变量的地址存放在哪里?这就是 二级指针 。
看代码如下:
#include <stdio.h>
int main()
{
	int num = 10;
	int *p = &num; //将num的地址存放在p变量中,p就是一个一级指针变量。
	int **pa = &p; //将指针p的地址存放在pa变量中,pa就是一个二级指针变量。
	printf("%p\n\n", p);  //将p中保存的地址输出
	printf("%p\n", pa); //将pa中保存的地址输出
	system("pause");
	return 0;
}
观察其在内存中的存储和最终输出有:

对其再进行分析有下图:
对于二级指针的引用,**pa 先通过 *pa 找到 p ,然后对 p 进行解引用操作: *p,那找到的是 num.
3. 指针与数组
数组与指针的关系:
看如下例子:
#include<stdio.h>
int main()
{
	int arr[] = { 1, 2, 3, 4 };
	int *p = arr;
	printf("%d\n", *arr);
	printf("%p\n", p);
	system("pause");
	return 0;
}
其运行结果有:
由上分析有: 数组名代表的是数组首元素的地址,因此在将数组名赋给指针时,是将数组首元素的地址赋给指针,而不是将整个数组的地址赋给指针
在引用数组元素作为指针时的运算有以下规则:
若有 int *p = arr[2];(还是上面的例题),那么p+1指向同一数组中的下一个元素,p-1则指向同一数组中的下一个元素。
如有p = &arr[0];,则 p+i 和 a+i 就是数组元素 arr[i] 的地址。而对其解引用,则是其所指向的值。
如果指针变量p1和p2都指向同一个数组,如执行p2-p1,则结果是p2-p1(地址之差)除以数组元素的长度
此处有一道经典的例题, 模拟实现strlen函数,既是通过这个方法求得的,代码如下:
#include<stdio.h>
#include <assert.h>
int my_strlen(const char *str)
{
	const char* start = str;
	assert(str != NULL);   //断言

	while (*str++ != '\0')
	{
		;
	}
	return str - start - 1; //通过数组内部两个指针相减求得字符串长度
}

int main()
{
	char str[] = "abcdef";
	int len = my_strlen(str);
	printf("%d\n", len);
	system("pause");
	return 0;
}
注意: 两个地址不能相加,如上面的p1+p2是无意义的。最终会产生不可预测的结果
通过指针引用数组元素,见下代码:
#include<stdio.h>
int main()
{
	int arr[] = { 0, 1, 2, 3, 4 , 5, 6, 7, 8, 9};
	int i = 0;
	int *p = arr;

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

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

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

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

	system("pause");
	return 0;
}
程序运行结果为:


上面的例子中,便是通过指针变量来对数组元素进行了引用。
针对指针与数组,还有许多的关系,我不再做仔细地解析,接下来看一组题指针与一维数组的题:
#include <stdio.h>
int main()
{
	一维数组
	int a[] = { 1, 2, 3, 4 };
	printf("%d\n", sizeof(a));//16
	printf("%d\n", sizeof(a + 0));//4
	printf("%d\n", sizeof(*a));//4
	printf("%d\n", sizeof(a + 1));//4
	printf("%d\n", sizeof(a[1]));//4
	printf("%d\n", sizeof(&a));//4
	printf("%d\n", sizeof(&a + 1));//4
	printf("%d\n", sizeof(&a[0]));//4
	printf("%d\n", sizeof(&a[0] + 1));//4

	//字符数组
	char arr[] = { 'a', 'b', 'c', 'd', 'e', 'f' };
	printf("%d\n", sizeof(arr));//6
	printf("%d\n", sizeof(arr + 0));//4
	printf("%d\n", sizeof(*arr));//1
	printf("%d\n", sizeof(arr[1]));//1
	printf("%d\n", sizeof(&arr));//4
	printf("%d\n", sizeof(&arr + 1));//4
	printf("%d\n", sizeof(&arr[0] + 1));//4

	printf("%d\n", strlen(arr));//随机值
	printf("%d\n", strlen(arr + 0));//随机值
	printf("%d\n", strlen(*arr));//无法运行
	printf("%d\n", strlen(arr[1]));//无法运行
	printf("%d\n", strlen(&arr));//随机值
	printf("%d\n", strlen(&arr + 1));//随机值
	printf("%d\n", strlen(&arr[0] + 1));//随机值

	//字符串
	char *p = "abcdef";
	printf("%d\n", sizeof(p));//4
	printf("%d\n", sizeof(p + 1));//4
	printf("%d\n", sizeof(*p));//1
	printf("%d\n", sizeof(p[0]));//1
	printf("%d\n", sizeof(&p));//4
	printf("%d\n", sizeof(&p + 1));//4,其指向了p后面的一个地址
	printf("%d\n", sizeof(&p[0] + 1));//4
	
	printf("%d\n", strlen(p));//6
	printf("%d\n", strlen(p + 1));//5
	printf("%d\n", strlen(*p));//无法运行
	printf("%d\n", strlen(p[0]));//无法运行
	printf("%d\n", strlen(&p));//随机值
	printf("%d\n", strlen(&p + 1));//随机值
	printf("%d\n", strlen(&p[0] + 1));//5
        system("pause");
	return 0;
}
指针与二维数组:
#include <stdio.h>
int main()
{
	//二维数组
	int a[3][4] = { 0 };
	printf("%d\n", sizeof(a));//48
	printf("%d\n", sizeof(a[0][0]));//4
	printf("%d\n", sizeof(a[0]));//16,表示的是二维数组的第一行
	printf("%d\n", sizeof(a[0] + 1));//4,表示的是a[0][1]
	printf("%d\n", sizeof(a + 1));//4,表示第二行的地址
	printf("%d\n", sizeof(&a[0] + 1));//4,表示第二行的地址
	printf("%d\n", sizeof(*a));//16,表示对第一行的所有值进行解引用
	printf("%d\n", sizeof(a[3]));//16,其表达的意义等价于a[0],即表示二维数组的一行

	system("pause");
	return 0;
}
4. 指针数组
一个数组,若其元素均为指针类型数据,则称为指针数组。也就是说指针数组中的每一个元素都存放一个地址,相当于一个指针变量。
定义一个指针数组: type * arr_name[const_n]
type 表示指针数组中指针所指向的数据类型;
arr_name 表示指针数组的名称;
const_n 表示的是指针数组元素的个数,用来指定指针数组的大小;
接下来看一道例题:
#include<stdio.h>
int main()
{
	int p1 = 1;
	int p2 = 2;
	int p3 = 3;
	int *str1[3] = { &p1, &p2, &p3 };
	char *str2[3] = { "hello", "world", "yoooo" };
	int i = 0;
	for (i = 0; i < 3; i++)
	{
		printf("%d\n", *str1[i]);
	}
	
	for (i = 0; i < 3; i++)
	{
		printf("%s\n", str2[i]);
	}
	system("pause");
	return 0;
}
其运行结果为:

 
上面例子中通过定义指针数组,将多个整形数据,字符串的地址进行了存储。然后通过指针数组就可以对其进行访问。
5. 数组指针
数组指针,是指针。
我们已经知道:整形指针: int * pint; 能够指向整形数据的指针。浮点型指针: float * pf; 能够指向浮点型数据的指针。
那么同样可分析有, 数组指针即是能指向数组的指针
数组指针的定义: type  (* Pointer_name) [const_n]   
type 表示数组指针所指向数组的类型;
Pointer_name 表示数组指针的名称;
const_n 表示数组指针所指向的数组的元素的个数,即指定数组的大小;
eg:int (* p) [10]
解释:p先和*结合,说明p是一个指针变量,然后指针指向的是一个大小为10个整型的数组。
前面已经分析过:
int arr[10] = {0};
arr;//表示数组首元素的地址
&arr;//表示数组的地址
//具体差异在哪里?
printf("%p\n", arr);
printf("%p\n", arr+1);
printf("%p\n", &arr+1);
//我们可以看出产生的结果是截然不同的
//这里是因为数组的地址和数组首元素的地址值是相同的,但是意义不同。
那数组的地址的存储就要用到数组指针:int  (* p)[10] = &arr;
二维数组传参时也可通过数组指针来进行
#include <stdio.h>
void print(int(*arr)[4]) //通过数组指针来对二维数组进行了传参
{
	int i = 0;
	int j = 0;
	for (i = 0; i < 3; i++)
	{
		for (j = 0; j < 4; j++)
		{
			printf("%3d", (* (arr + i))[j]);
		}
		printf("\n");
	}
}
int main()
{
	int arr[3][4] = { { 1, 2, 3, 4 },
			  { 5, 6, 7, 8 },
		          { 9, 10, 11, 12 } };
	print(arr);
	system("pause");
	return 0;
}
运行结果如下:
6. 函数指针
先看一段代码:
#include <stdio.h>
int Add(int x, int y)
{
	return (x + y);
}
int main()
{
	int a = 1;
	int b = 2;
	printf("%p\n", Add);
	printf("%p\n", &Add);
	system("pause");
	return 0;
}
此代码输出的是函数Add的地址:

函数的地址该如何保存?  这时候就用到了函数指针。
函数指针,顾名思义, 指向函数的指针
在程序中定义一个函数,编译时,系统为函数代码分配了一段存储空间,这段存储空间的起始地址成为这个函数的指针。而若定义一个指针变量,来保存这个函数的起始地址,则成这个指针变量为函数指针。
定义一个函数指针: type (*  Pointer_name)(Function parameter list);
type 表示函数返回值的类型;
Pointer_name 表示函数指针的名称;
Function parameter list 表示函数的参数列表;
对上面的例子,则可以通过函数指针:int (*p)(int ,int); 来进行保存。
对上面例子通过函数指针进行调用如下:
#include <stdio.h>
int Add(int x, int y)
{
	return (x + y);
}
int main()
{
	int a = 1;
	int b = 2;
	printf("%p\n", Add);
	printf("%p\n", &Add);
	int (*p)(int, int);
	p = Add;
	printf("%p\n", *&p);
	printf("%d\n", (*p)(a, b)); //通过函数指针来访问其所指向的函数Add
	system("pause");
	return 0;
}
输出结果为:

具体分析过程见下图:
7. 函数指针数组
数组是一个存放相同类型数据的存储空间,我们了解了指针数组的应用,那么把函数的地址存到一个数组中,这个数组就叫函数指针数组。
所以, 函数指针数组即是存放函数指针的数组
定义一个函数指针数组: type (* arr_name [const_n])(Function parameter list)
type 表示函数返回值的类型;
arr_name 表示函数指针数组的名称;
const_n 表示函数指针数组的元素多少,即指定函数指针数组的大小。
Function parameter list 表示函数的参数列表;
如下代码:
#include <stdio.h>
int Add(int x, int y)
{
	return (x + y);
}
int Sub(int x, int y)
{
	return (x - y);
}

int main()
{
	int i = 0;
	int (*p1)(int, int) = Add;
	int (*p2)(int, int) = Sub;
	int (*arr1[2])(int, int) = { *p1, *p2 }; //定义函数指针数组,数组内容为p1和p2两个函数指针

	printf("%d\n", (*p1)(1, 2)); //通过函数指针p1来访问它所指向的函数Add
	printf("%d\n", (*p2)(1, 2)); //通过函数指针p2来访问它所指向的函数Sub

	for (i = 0; i < 2; i++)
	{
		printf("%d\n", (*arr1[i])(1, 2)); //通过函数指针数组里的函数指针来访问其所指的函数
	}
	system("pause");
	return 0;
}
程序运行结果如下:
8. 函数指针的数组的指针
指向函数指针数组的指针是一个指针。
指针指向一个 数组 ,数组的元素都是函数指针 。
如何定义?
void test(const char* str)
{
 printf("%s\n", str);
}
int main()
{
	//函数指针pfun
 	void (* pfun)(const char*) = test;
 	//函数指针的数组pfunArr
 	void (* pfunArr[5])(const char* str);
 	pfunArr[0] = test;
 	//指向函数指针数组pfunArr的指针ppfunArr
 	void (* (* ppfunArr)[10])(const char*) = &pfunArr;
 	return 0;
}	//函数指针pfun
 	void (* pfun)(const char*) = test;
 	//函数指针的数组pfunArr
 	void (* pfunArr[5])(const char* str);
 	pfunArr[0] = test;
 	//指向函数指针数组pfunArr的指针ppfunArr
 	void (* (* ppfunArr)[10])(const char*) = &pfunArr;
 	return 0;
}
定义一个函数指针数组指针: type (* (* Pointer_name)[const_n])(Function parameter list);
type 表示函数的返回值类型;
Pointer_name 表示函数指针数组的指针的名称;
const_n 表示函数指针数组的元素多少,即指定函数指针数组的大小;
Function parameter list 函数的参数列表;
通过函数指针数组指针来完成一个对数值简单的计算:
#include <stdio.h>
int Add(int x, int y)
{
	return (x + y);
}
int Sub(int x, int y)
{
	return (x - y);
}
int Mul(int x, int y)
{
	return (x*y);
}
int Div(int x, int y)
{
	return (x/y);
}
void print( int(*(*p)[2])(int , int)) //通过函数指针数组的指针来进行传参
{
	int i = 0;
	int j = 0;
	for (i = 0; i < 2; i++)
	{
		for (j = 0; j < 2; j++)
		{
			printf("%2d ", (*(p + i))[j](1,2)); //通过函数指针数组的指针来对函数指针数组里的函数指针进行访问
		}                                           //而通过访问函数指针,再由函数指针来访问它所指向的函数
		printf("\n");
	}
}
int main()
{
	int i = 0;
	int (*p1)(int, int) = Add;
	int (*p2)(int, int) = Sub;
	int (*p3)(int, int) = Mul;
	int (*p4)(int, int) = Div;
	int(*arr[2][2])(int, int) = { { *p1, *p2 },{ *p3, *p4 } };
	print(arr);
	system("pause");
	return 0;
}
上面代码运行结果如下:
总结:指针是C中极为重要的一部分,其概念比较复杂,而且极为灵活,如果我们能正确灵活的运用指针,那么可以使我们的程序简洁,高效,紧凑。因此,无论如何,我们都应该深入学习和掌握指针。
 
  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值