C语言指针简介

指针的基本概念

一、内存和地址

想要理解指针,就先要理解内存和地址。我们知道计算机上的CPU(中央处理器)在处理数据时,需要的数据是在内存中读取的,处理之后的数据也会放回到内存中。为了高效管理内存空间,计算机是将内存划分为一个个的内存单元,而每个内存单元的大小是 1字节(byte)。

计算机中最小的单位是比特(bit),1 byte = 8 bit。一个比特能够存储一个二进制的1或0。为了高效管理,即快速准确地到达每一个数据所在的内存,我们就给每个内存单元进行编号,这个编号就是相应数据的地址。在C语言中地址又被称为指针。通常所讲的指针的实质就是地址。

二、指针变量

①:取地址操作符“&”

取地址操作符可以取出其后数据的地址,如果想要观察这一地址,可以用%p的形式进行输出。

int a=1;
printf("%p",&a);
//结果将以16进制的形式输出

实际上,int类型的变量a会在内存上申请4个字节的空间,而取地址操作符取出的是四个字节中较小的地址。

②:指针变量

int a=1;
int * pa=&a;

上述代码中的“*”说明我们创建的变量pa是指针变量,“int”则保证了pa指向的变量是int类型,由此我们就创建了指向a的地址(实质是四个字节中最小的地址)的指针变量。

指针变量是存放指针的,即是存放a的地址的变量。我们口头上说的指针通常是指针变量。其他类型的指针变量的创建结构同理,如:

char ch='x';
char * pc=&ch;

③:解引用操作符“*”

*pa=2;

解引用操作符是用来对指针变量解引用的,*pa就等价于a,上述代码进行的操作就是将a赋值为2。

④:指针变量的大小

指针变量的大小取决于其内地址的存放需要多大的空间。例如在32位机器上(x86环境中),有32根地址线,地址的二进制序列就有32bit位,存放起来,需要4个字节,32bit位的空间。故32位机器上指针变量的大小都是4个字节,而与指针变量的类型无关。在64位机器上(x64环境中),则是8个字节。当然我们也可以用如下代码进行验证:

char* pc = NULL;//这里的NULL仅代表0
int* pi = NULL; //用以表示无特定的指向
printf("%zd\n", sizeof(pc));
printf("%zd\n", sizeof(pi));

三、指针变量类型的意义

指针变量的类型决定了解引用指针时的权限,也就是一次能操作几个字节,或者说向前向后走一步有多大距离。比如 int*p,p+1 是一次走了四个字节,char* 类型,+1一次走一个字节。

需要特别指出的是,void* 类型的指针是无具体类型的,又叫泛型指针,它可以接受任何类型的地址,但是不能直接进行解引用、+1、-1等操作

四、const修饰指针

const意味着常属性、不变,可使修饰的变量无法修改。例如 const int a=1;  a=2; 这样的赋值就是错误的。但a的本质还是变量,const只在语法上限制,习惯上称a为常变量

如果仍要进行修改,可以使用如下代码:

const int a=1;
int *pa =&a;
*pa=2;

这时a已经被修改了。当然,也可以用const来修饰指针,如: const int *pa=&a; 这时上述代码将无法修改。

而const修饰指针有如下两种类型:

int *const p=&a;//const放在*右边,限制p
int const *p=&a;//const放在*左边,限制*p

限制p,则p变量无法再指向其他变量,但*p不受限制,还是可以通过p来修改p指向的空间的内容。可以写 *p=2;限制*p是不能通过p来修改p指向的空间的内容,但是p并不受限制。不能写 *p =2; 但可以写     int b=1;p=&b; 另外,我们也可以同时限制*p与p。

指针的运算与操作

一、指针运算

包括指针加减整数:这常用于打印数组等操作,比如用p++表示向后移相应类型的长度。

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

指针减指针:计算结果的绝对值是指针与指针之间的元素个数。这里有一个前提条件是两个指针指向同一块空间

指针的关系运算:实质就是指针比较大小(比较地址的大小)。

二、野指针

①:概念

野指针就是指针指向的位置随机的、不正确的、没有限制的,是指向未分配或者已经释放的内存地址。

②:成因

1,未初始化,比如直接 int* p; 然后对*p进行操作。2,越界访问。

③:规避野指针

1,初始化 int*pa=&a;或者int*pa=NULL;  2,小心指针越界。  3,指针变量不在使用之后,及时置为NULL,在使用之前也要检查有效性。

三、指针的使用与传址调用

在调用函数时,如果实参传入变量,就是传值调用,传入的是地址,就是传址调用。这两者也就是是否使用指针的区别,而有时一些问题是必须使用指针的。

值得指出的是,传值调用函数时,函数的实参传给形参,形参是实参的一份临时拷贝,形参有自己独立的空间,对形参的改变不会影响实参。传址调用可以让函数与主调函数之间建立起真正的联系。如下是用函数实现两个数交换。

void swap(int *pa,int *pb)
{
 int z=0;
 z=*pa;*pa=*pb;*pb=z;
}
int main
{int a=1,b=2;
swap(&a,&b);}

四、指针与数组

①:对数组名的理解

数组名是首元素的地址。

有两个例外:(1)sizeof(arr)计算的是整个数组的大小。(2)&arr 取出的整个数组的地址。

举例:对于int a[10]={0};

&arr[0]和&arr[0]+1,arr和arr+1 均相差四个字节,但&arr和&arr+1相差40个字节,此时加1跳过了整个数组。

②:使用指针访问数组

//以下为部分代码,sz为数组元素个数
int *p =arr;
for(i=0;i<sz;i++)
scanf("%d",p+i);//p+i代表相应位置的地址
for(i=0;i<sz;i++)
printf("%d",*(p+i));

实际上,也可以将 *(p+i)换成p[i],这就是说arr[i]等价于*(arr+i)

③:一维数组传参的本质

数组传参时,传递的数组名就只是数组首元素的地址。形参不会创建一个数组,需要视作地址来操作。以下是指针和函数实现冒泡排序:

void arrange(int*p, int n)
{
	int i, j, flag, temp;
	for (i = 0; i < n - 1; i++)
	{
		for (flag = 0, j = 0; j < n - 1; j++)
		{
			if (*(p + j) > *(p + j + 1))
			{
				temp = *(p + j);
				*(p + j) = *(p + j + 1);
				*(p + j + 1) = temp;
				flag = 1;
			}
		}
		if (flag == 0)
			break;
	}
	for (i = 0; i < n; i++)
		printf("%d ", *(p + i));
}

调用时,用arrange(arr,n),arr是数组名,n是元素个数。

④:二级指针

二级指针是指向指针的指针,存放一级指针

int a=1;
int *p=&a;
int* * pp=&p;
//int*表示pp指向对象的类型是int*,后一个*表示pp是指针变量
printf("%d",**pp);

当然,同样的有三级指针以及更高级的指针。

⑤指针数组

是存放指针的数组。

int* arr[4]; 这里的int*表示数组arr中的每个元素都是整形指针。下例是指针数组模拟二维数组:

int arr1[]={1,2,3,4,5};
int arr2[]={2,3,4,5,6};
int arr3[]={3,4,5,6,7};
int*arr[3]={arr1,arr2,arr3};
for(i=0;i<3;i++)
for(j=0;j<5;j++)
printf("%d",arr[i][j]);//这的arr[i][j]与平常的二维数组并不相同
//arr+i == arr[i]

指针变量与函数

一、字符指针变量

char*p="abcdef"; 这是将第一个字符的地址放在p中。

(1)可以把字符串想象成一个字符数组,可以使用p[3],"abcdef"[3],但是这个数组是不能修改的是常量字符串,可以用const char*p= 来避免出错。

(2)当常量字符串出现在表达式中的时候,他的值是第一个字符的地址。内容相同的常量字符串在内存中只会保存一份。

二、数组指针变量

数组指针变量是指针变量,存放的是数组的地址。

int arr[10]={1,2,3,4,5,6,7,8,9,10};
int (*parr)[10]=&arr;
//parr就是数组指针,括号是让parr先与*结合
//变量parr的类型是 “ int(* )[10] ”

不过事实上一维数组很少使用数组指针。

三、二维数组传参的本质

二维数组是元素是一维数组的数组,首元素的地址是第一行的地址。

void Print(int (*arr)[5],int r,int c)//用到数组指针变量
{
for(int i=0;i<r;i++)
for(int j=0;j<c;j++)
printf("%d",*(*(arr+i)+j));//这里可以逐层看
}
int main()
{
int arr[3][5]={0};
Print(arr,3,5);
}

四、函数指针变量

存放函数的地址。函数名,&函数名 都是函数的地址,意义相同。实例:

int Add(int x,int y)
{return x+y;}
//在主函数中创建变量
//...
int (*pf)(int,int)=&Add;//或者用Add
//调用
int ret=(*pf)(1,2);//此时ret即为3
//事实上,pf旁的括号和*可以不写

上述pf的类型即为int(* )(int ,int),创建函数指针变量时,一定要注意返回类型与参数类型与函数的对应。

五、函数指针数组

存放函数指针的数组。类型与数组元素(函数)对应,本质是数组。

如下是转移表的代码:(实现简单的计算器)

#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;
}
int del(int x, int y)
{
	return x % y;
}
int(*pf[])(int, int) = { NULL,add,sub,mul,div,del };
//这里的pf[]就是函数指针数组
int all(int input)
{
	int x, y;
	printf("输入两个操作数:");
	scanf("%d %d", &x, &y);
	int ret = pf[input](x, y);
	return ret;
}
int main()
{
	int input;
	printf("加法:1 减法:2 乘法:3 除法:4 取模;5\n");
	printf("选择运算:");
	scanf("%d", &input);
	printf("%d",all(input));
	return 0;
}

六、回调函数

函数的指针作为参数传给另一个函数,通过该指针调用的函数称作回调函数。

void calc(int (*pf)(int,int))
{//...
 ret=pf(1,2);
 //...
}

函数指针的应用——qsort函数

qsort函数可以完成任意类型数据的排序,需包含stdlib.h。有四个参数void*base(指针指向第一个元素),size_t num,(元素数量),size_t size,(元素大小,单位字节),int(*compare)(const void*p1,const void*p2)(指向的函数是用来比较数组中的两个元素的));

以下是对整形数据和结构体数据的比较:

#include<stdio.h>
#include<stdlib.h>
int cmp_int(const void* p1, const void* p2)
{
	return *(int*)p1 - *(int*)p2;  //这是升序排序,降序排只需交换p1,p2位置
}
struct stu
{
	char name[15];
	int high;
};
int cmp_name(const void* p1, const void* p2)
{
	return strcmp(((struct stu*)p1)->name, ((struct stu*)p2)->name);
    //strcmp函数用来比较字符串,实质按对应位置的ASCII码值比较
}
int cmp_high(const void* p1, const void* p2)
{
	return ((struct stu*)p1)->high-((struct stu*)p2)->high;
            //强制类型转换
}
int main()
{
	int i;
	printf("整形排序:\n");
	int arr1[] = { 2,3,1,4,5 };
	int sz1 = sizeof(arr1) / sizeof(arr1[0]);
	printf("排序前:");
	for (i = 0; i < sz1; i++)
		printf("%d ", arr1[i]);
	printf("\n");
	qsort(arr1, sz1, sizeof(arr1[0]), cmp_int);
	printf("排序后:");
	for (i = 0; i < sz1; i++)
		printf("%d ", arr1[i]);
	printf("\n");

	printf("结构体排序:\n");  //两个结构体数据不能用<,>,==来比较
	struct stu arr2[] = { {"Peter",168},{"Jack",175},{"Andy",170} };
	int sz2 = sizeof(arr2) / sizeof(arr2[0]);
	printf("排序前:");
	for (i = 0; i < sz2; i++)
		printf("%s %d  ", arr2[i].name,arr2[i].high);
	printf("\n");
	qsort(arr2, sz2, sizeof(arr2[0]), cmp_name);
	printf("按名字排序:");
	for (i = 0; i < sz2; i++)
		printf("%s ", arr2[i].name);
	printf("\n");
	qsort(arr2, sz2, sizeof(arr2[0]), cmp_high);
	printf("按身高排序:");
	for (i = 0; i < sz2; i++)
		printf("%d ", arr2[i].high);
	printf("\n");
	return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值