指针知识点笔记

指针是什么?

指针是编程语言中的一个对象,利用地址,它的值可以指向存在电脑存储器中另一个地方的值。

一般这样说指针就是地址。指针变量就是变量(用来存放地址的变量)。
变量有不同的类型,整型,浮点型。指针同样也有类型。

char *pc = NULL;
short *ps = NULL;
int *pi = NULL;
long *pl = NULL;
float *pf = NULL;
double *pd = NULL

char * 类型指针存放char类型变量的地址;
int * 类型指针存放int类型变量的地址。

 
1.指针+ - 整数、解引用:

#include <stdio.h>

int main()
{
	int n = 10;
	char *pc = (char *)&n;
	int *pi = &n;

	printf("%p\n", &n); //n的地址
	printf("%p\n", pc);//pc里面放的n的地址
	printf("%p\n", pc+1);//pc+1,实际是加上自己类型的大小
	printf("%p\n", pi);//pi里面放的n的地址
	printf("%p\n", pi+1);//pi+1,实际是加上自己类型的大小
	return 0;
}

结果:
在这里插入图片描述

解引用这块看这样一段代码:

	int n = 0x11223344;
	char *pc = (char *)&n;
	int *pi = &n;
	*pc = 0;
	*pi = 0;

执行完初始化语句时,内存为这样:在这里插入图片描述
执行完*pc=0之后,内存里面发生了变化:
在这里插入图片描述
pc指向n的第一个地址发生了改变。
执行完pi之后:
在这里插入图片描述
pi指向n的地址都发生了改变(清0).。
为什么会这样呢?
因为类型决定了能访问几个字节,char*类型只能访问一个字节。int*类型能访问4个字节。

 
 
总结:

  1. 对指针+1,看起来+1,实际上就是加上该指针所指向类型的大小
  2. 对指针解引用(无强转的情况),代表指针所指向变量(目标)。
  3. 指针变量也是变量,指针变量也要有空间、内容、地址(指针本身也有地址)。
  4. 指针进行解引用访问的时候,自己的类型决定了能访问几个字节。

 
 
 
2.野指针: 野指针就是指针所指向的位置是不可知的(随机的,不正确的,没有明确限制的)。

形成野指针的两个原因:

  1. 指针未初始化
	int *p;//指针变量未初始化,默认为随机值
	*p = 20;
  1. 指针越界访问
	int arr[10] = { 0 };
	int *p = arr;
	int i = 0;
	for (i = 0; i <= 11; i++)
	{
		*(p++) = i;//在这里,指针指向已经越界了,超出了arr的范围,p就是野指针
	}

如何避免野指针:

  1. 指针初始化(指针定义时,若不知道指向谁,可以直接指向NULL)
  2. 注意指针越界
  3. 指针指向空间释放及时置NULL
  4. 指针使用之前检查有效性

 
 
 
3.指针运算

  1. 指针 + - 整数(这里和上面第二节一致)
  2. 指针 - 指针
int my_strlen(char *arr)
{
	char *p = arr;
	while (*p != NULL)
	{
		p++;
	}
	return p - arr;
}

通过指针 - 指针的方式,可求出,该字符串的元素个数。若想求数组,字符串所经历的元素个数,可用指针 - 指针的方式。
总结:

1.指针 - 指针代表:两个指针之间所经历的“元素”个数。(不一定是1个字节的数)
2.两个指针相减,必须做到,这个指针类型是一致的。
3.两个指针相减,一般建议两个指针指向同一块内存。

  1. 指针的关系运算
①:
#define N 5
	float values[5];
	float *p = NULL;
	for (p = &values[N]; p > &values[0];)
	{
		*--p = 0;
	}
②:
#define N 5
	float values[5];
	float *p = NULL;
	for (p = &values[N-1]; p >= &values[0];p--)
	{
		*p = 0;
	}

在vs环境下,两种方案都可以运行,但应该尽量避免第二种方案。因为标准不保证第二种是可行的。

标准规定:

允许指向数组元素的指针与指向数组 最后一个元素后面的那个内存位置 的指针比较,但是不允许与指向 第一个元素之前的那个内存位置 的指针进行比较。

 
 
 
4.指针和数组:两者无任何关系。

  1. 指针和数组在类型上,没有关系,是两种不同的类型。
  2. 在访问数组元素的时候,有一定的相似性。

数组名在大部分情况下表示的是数组首元素的地址。

二级指针:存放一级指针的地址。(指针本身也包含着地址)

指针数组:是数组,用来存放指针的数组。如:int *arr[5];

数组指针:是指针,指向数组的指针。如:int (*arr)[5];

在二级指针中,我们经常用到关键字const来修饰变量,使指针的指向和内容不能做更改。练习:

	int a = 20;
	int *p = &a;//p指向a,对p解引用,就是a值
	int **q = &p;//q指向p,一次解引用,为p=&a,二次解引用,为a的值


	const int *p = &a;//p的内容可以更改,p的指向不能更改。不能通过解引用的方式对a值进行更改,即*p = 5,是错误的
	int const *p = &a;//同上
	const int *const p = &a;//p的内容和p的指向都不能更改。


	const int **q = &p;//q的内容和指向可以更改,*q的指向(**q)不能更改
	int const **q = &p;//同上
	int *const *q = &p;//q的内容可以更改,q的指向不能更改(*q的内容不能更改)。*q的指向(**q)可以更改
	int **const q = &p;//q的内容不能更改,q的指向(*q)可以更改,*q的指向(**q)可以更改
	int *const *const q = &p;//q的内容和指向(*q)都不能更改,*q的指向(**q)可以更改
	int const *const *q = &p;//q的内容可以更改,q的指向(*q)不能更改,*q的指向(**q)不可以更改
	int const * const *const q = &p;//q的内容和指向(*q)都不能更改,*q的指向(**q)不可以更改

 
 
 
 
 
字符指针:char *

两种用法:

  1. 直接指向字符
  2. 指向字符串(C没有字符串类型,但有字符串)

const char *s
char arr[]

char *s="hello world!"是把字符串“hello world!”的首地址放到字符指针s中。一般在前面加const,表示,该字符串不允许被修改。

	char str1[] = "hello world!";
	char str2[] = "hello world!";
	char *str3 = "hello world!";
	char *str4 = "hello world!";
	if (str1 == str2)
	{
		printf("1 and 2 are same");
	}
	else
	{
		printf("1 and 2 are not same");
	}
	if (str3 == str4)
	{
		printf("3 and 4 are same");
	}
	else
	{
		printf("3 and 4 are not same");
	}

在此例子中,str1str2是不同的变量,开辟了不同的地址空间。
str3str4的地址是一样的。因为"hello world!"存在于字符常量区,只会保存一份,所以只读就可以了,故*str3*str4只要指向它就行了。

 
 
&数组名和数组名:
当对进行输出的时候,他们的值是一样的,但是其含义和类型是不同。
数组名:数组首元素的地址。
&数组名 :对整个数组进行取地址,但输出的时候,输出的是第一个元素的地址。

数组类型的理解:不能只看前面的类型,更要看[]里面的标定元素个数。

	int arr[10];
	int arr1[10];
	int arr2[9];

前两个类型相同,第三个和前面两个类型不相同。

 
 
数组指针的使用int (*p)[5]
在二维数组传参时,方便降维,降维成一维数组指针。

void print_arr(int (*arr)[5], int row, int col)
{
	int i = 0;
	for (; i < row; i++)
	{
		int j = 0;
		for (; j < col; j++)
		{
			//printf("%d  ", arr[i][j]);//1
			printf("%d   ", *(*(arr + i) + j));//2
		}
	}
}
int main()
{
	int arr[3][5] = { 1,2,3,4,5,6,7,8,9,10,15 };
	print_arr(arr, 3, 5);
	return 0;
}

其中第二种方式的意思是:arr+1是指向下一个一维数组,对其解引用,代表的是,当前的一维数组,+j呢,是当前数组中第j个元素。

 
 
数组传参要注意类型是否可以传参,同时,一维数组中,不能省略数组的个数。二维数组中,不能省略第二个维度的个数。

指针传参:

  1. 一维指针传参
void print(int *p, int size)
{
	int i = 0;
	for (; i < size; i++)
	{
		printf("%d  ", p[i]);
	}
}
int main()
{
	int arr[11] = { 1,2,3,4,5,6,7,8,9,10,11 };
	int *p = arr;
	print(p, 11);
	return 0;
}

指针也是变量,在传参的时候,也要进行形参实例化,指针也要形成临时变量,不过,指针最大的特征是形成的临时变量内容和传入指针内容是相同的。决定了,上述代码中,两个指针指向的目标是一样的。上面两个P是不一样的,但指向相同的目标。

函数的参数为一级指针时,可以接受什么参数?

一维数组:int arr[] test(arr)
一级指针 int *p=xxx test§
具体变量 int a=10 test(&a)

  1. 二级指针传参

函数的参数为二级指针时,可以接受什么参数?

二级指针:int **q test(q)
一级指针:int *q test(&q)
指针数组:int *q[10] test[q]

 
 
函数指针:函数本质是代码块,代码本质是包含多组代码的,也就决定了会包含一个地址序列。
函数名(only),代码的是代码块的起始地址。

void print(int *p, int size)
{
	int i = 0;
	for (; i < size; i++)
	{
		printf("%d  ", p[i]);
	}
}

printf("%p\n", print);
printf("%p\n", &print);

对函数输出地址和&函数地址是一样的。
如何保存一个函数的地址呢?

使用函数指针: void (*p)();

 
 
函数指针数组int (*p[5])();

将函数的地址存放到一个数组中,那么这个数组就叫做函数指针数组。

函数指针数组的一个用途为转移表:比如在实现计算器功能中

void menu()
{
	printf("############################################\n");
	printf("######       1.add           2.sub    ######\n");
	printf("######       3.mul           4.div    ######\n");
	printf("######       0.exit                   ######\n");
}
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)
{
	if (0 == y)
	{
		return 0;
	}
	return x / y;
}

int main()
{
	int choice = 1;
	menu();
	printf("请做出你的选择:>");
	scanf("%d", &choice);
	int(*p[5])(int x, int y) = {0, add,sub,mul,div };//函数指针数组,可以很方便的实现许多相同参数的函数的调用,转移表
	while (choice)
	{
		int x = 0;
		int y = 0;
		printf("请输入两个数:>");
		scanf("%d %d", &x, &y);
		int ret = (*p[choice])(x, y);
		printf("%d\n", ret);
		menu();
		printf("请做出你的选择:>");
		scanf("%d", &choice);
	}
	return 0;
}

 
 
指向函数指针数组的指针

函数指针:int (*p)();
函数指针数组:int (p[5])();
指向函数指针数组的指针:int (
(*p)[5])();

 
 
回调函数: 通过一个函数指针调用的函数,如果把这个函数的地址作为另一个函数的参数传递,当这个指针被用来调用其所指向的函数时,这就是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。

你到一个商店买东西,刚好你要的东西没有货,于是你在店员那里留下了你的电话,过了几天店里有货了,店员就打了你的电话,然后你接到电话后就到店里去取了货。在这个例子里,你的电话号码就叫回调函数,你把电话留给店员就叫登记回调函数,店里后来有货了叫做触发了回调关联的事件,店员给你打电话叫做调用回调函数,你到店里去取货叫做响应回调事件。–by常溪玲

回调函数模拟实现qsort(冒泡排序法):

qsort函数是C函数库中,可以比较任意相同类型(char int double float),并将之排序

int int_cmp(void *s, void *p)//回调函数
{
	int *s1 = (int *)s;
	int *p1 = (int *)p;
	return *s1-*p1;
}
void swap(void *s, void *p, int size)
{
	int i = 0;
	char *s1 = (char *)s;
	char *p1 = (char *)p;
	for (; i < size; i++)
	{
		s1[i] ^= p1[i];
		p1[i] ^= s1[i];
		s1[i] ^= p1[i];
	}
}
void my_qsort(void *base, int num, int size, int (*cmp)(void *, void *))//实现调用回调函数的函数
{
	int i = 0;
	int j = 0;
	for (; i < num - 1 ; i++)
	{
		int flag = 1;
		for (j = 0; j < num - i - 1; j++)
		{
			if (cmp((char *)base + j * size, (char *)base + (j + 1) * size) > 0)
			{
				flag = 0;
				swap((char *)base + j * size, (char *)base + size * (j + 1),size);
			}
		}
		if (1 == flag)
		{
			break;
		}
	}
}
int main()
{
	int arr[] = { 5,7,8,1,5,4,1,6,3,2 };
	int num = sizeof(arr) / sizeof(arr[0]);
	my_qsort(arr, num, sizeof(int), int_cmp);
	for (int i = 0; i < num; i++)
	{
		printf("%d  ", arr[i]);
	}
	printf("\n");
	return 0;
}

 
 
 
 
 
一些指针和数组的练习题:

	int a[] = { 1,2,3,4 };
	printf("%d\n", sizeof(a));
	printf("%d\n", sizeof(a+0));
	printf("%d\n", sizeof(*a));
	printf("%d\n", sizeof(a+1));
	printf("%d\n", sizeof(a[1]));
	printf("%d\n", sizeof(&a));
	printf("%d\n", sizeof(*&a));
	printf("%d\n", sizeof(&a+1));
	printf("%d\n", sizeof(&a[0]));
	printf("%d\n", sizeof(&a[0]+1));

sizeof(a),求的是整个数组的字节大小,故为16
sizeof(a+0),a是首元素的地址,加0,仍然为第0个元素的地址,地址(指针)大小为4
sizeof(a),a是首元素的地址,对a解引用,为1,字节大小为4
sizeof(a + 1);//a为首元素的地址,加 1,为下一个元素(2)的地址,地址(指针)大小为4
sizeof(a[1]);//a[1]=2,字节大小为4
sizeof(&a);//&a,对整个数组进行取地址,即数组的地址,地址(指针)大小为4
sizeof(
&a);//对&a解引用,数组的地址解引用,一定为一个数组,大小为16
sizeof(&a+1),&a是对整个数组取地址,加+1之后,指向最后一个元素的下一位置处。为4
sizeof(&a[0]);//a[0]=1,对其取地址,大小为4
sizeof(&a[0] + 1);//a[0]=1,对其取地址,再加1,对下一个元素(2)进行取地址,地址(指针)大小为4

结果:
在这里插入图片描述

	char arr[] = { 'a','b','c','d','e','f' };
	printf("%d\n", sizeof(arr));//数组的大小为6
	printf("%d\n", sizeof(arr+0));//第0个元素(a)的地址,4
	printf("%d\n", sizeof(*arr));//第0个元素(a)解引用,大小为1
	printf("%d\n", sizeof(arr[1]));//第1个元素(b)解引用,大小为1
	printf("%d\n", sizeof(&arr));//对整个数组取地址,数组的地址,地址(指针)大小为4
	printf("%d\n", sizeof(&arr+1));//对整个数组取地址,数组的地址,再加1,指向数组最后一个元素的下一位置,地址(指针)大小为4
	printf("%d\n", sizeof(&arr[0]+1));//对第0个元素取地址,再加1,为b元素的地址,地址(指针)大小为4

结果:
在这里插入图片描述

	char arr[] = { 'a','b','c','d','e','f' };
	printf("%d\n", strlen(arr));//数组里面无‘\0’,随机值
	printf("%d\n", strlen(arr+0));//随机值
	//printf("%d\n", strlen(*arr));//传参错误,类型不匹配
	//printf("%d\n", strlen(arr[1]));//传参错误,类型不匹配
	printf("%d\n", strlen(&arr));//随机值,&arr是数组的地址,数组的地址char (*p)[6],告警,类型不匹配
	printf("%d\n", strlen(&arr+1));//随机值,数组的地址char (*p)[6],告警,类型不匹配
	printf("%d\n", strlen(&arr[0]+1));//随机值

在这里面,为什么会发生传参错误问题?

strlen定义为:size_t strlen(const char *str);

传入的必须为一个地址,所以上面34类型不匹配,导致错误。
5:&arr是数组的地址,数组的地址要用数组指针char (*p)[6],但它和数组的起始地址&arr[0],数值是一样的,含义不一样,所以传给strlen时,发生隐式类型转换,转换为字符指针,故最后可得到一个随机值
6:&arr+1,就相当于&arr[6],类型不匹配,char (*p)[6],也发生了隐式类型转换。
结果:
在这里插入图片描述

	char arr[] = "abcdef";
	printf("%d\n", sizeof(arr));//字符串,有'\0',大小为7
	printf("%d\n", sizeof(arr+0));//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));//数组的地址+1,数组末下一个地址,地址(指针)大小为4
	printf("%d\n", sizeof(&arr[0]+1));//第1个元素的地址(b),地址(指针)大小为4

结果:
在这里插入图片描述

	char arr[] = "abcdef";
	printf("%d\n", strlen(arr));//字符串长度6
	printf("%d\n", strlen(arr+0));//传入首元素的地址,字符串长度6
	//printf("%d\n", strlen(*arr));//传参错误
	//printf("%d\n", strlen(arr[1]));//传参错误
	printf("%d\n", strlen(&arr));//结果为6,但会有告警,类型不匹配,char(*p)[7]
	printf("%d\n", strlen(&arr+1));//随机值,有告警,类型不匹配,char(*p)[7]
	printf("%d\n", strlen(&arr[0]+1));//5

结果:
在这里插入图片描述

	char *p = "abcdef";
	printf("%d\n", sizeof(p));//p存放的地址,大小为4
	printf("%d\n", sizeof(p+1));//p+1为b的地址,大小为4
	printf("%d\n", sizeof(*p));//char *解引用,为char,大小为1
	printf("%d\n", sizeof(p[0]));//p[0]='a',大小为1
	printf("%d\n", sizeof(&p));//&p,p本身的地址,大小为4
	printf("%d\n", sizeof(&p+1));//&p+1,为地址,大小为4
	printf("%d\n", sizeof(&p[0]+1)); //&p[0] + 1,‘b’的地址,大小为4

结果:
在这里插入图片描述

	int a[3][4] = { 0 };
	printf("%d\n", sizeof(a));//4*3*4=48,字节大小为48
	printf("%d\n", sizeof(a[0][0]));//第0个数组,第0个元素,int型,大小为4个字节
	printf("%d\n", sizeof(a[0]));//第0个数组,里面有4个元素,大小为16
	printf("%d\n", sizeof(a[0]+1));//第0个数组,第一个元素,大小为4
	printf("%d\n", sizeof(*(a[0]+1)));//第0个数组,第一个元素解引用,大小为4
	printf("%d\n", sizeof(a+1));//第一个数组的地址,大小为4
	printf("%d\n", sizeof(*(a+1)));//第一个数组的地址,解引用,为第一个数组,大小为16
	printf("%d\n", sizeof(&a[0]+1));//第0个数组的地址+1,为第1个数组的地址,大小为4
	printf("%d\n", sizeof(*(&a[0]+1)));//第0个数组的地址+1,为第1个数组的地址,解引用大小为16
	printf("%d\n", sizeof(*a));//首元素的地址,解引用,为16
	printf("%d\n", sizeof(a[3]));//16,不会报错,含义一致,但不能写入

结果:
在这里插入图片描述

 
 
 
综合题:

int main()
{
	int a[5] = { 1,2,3,4,5 };
	int *ptr = (int *)(&a + 1);
	printf("%d %d\n", *(a + 1), *(ptr - 1));
	return 0;
}

&a,是整个数组的地址,+1,指向数组最后一个元素的下一个地址。
*(a+1),a是首元素的地址,加1,为第一个元素的地址,解引用为2
*(ptr-1),为数组最后一个元素的地址,解引用为5
在这里插入图片描述
2.

struct Test
{
	int num;
	char *name;
	short sdate;
	char cha[2];
	short sba[4];
}*p;
int main()
{
	p = 0x100000;
	printf("%p\n", p + 0x1);
	printf("%p\n", (unsigned long)p+0x1);
	printf("%p\n", (unsigned int*)p + 0x1);
	return 0;
}

这个结构体的大小为20字节。
p + 0x1,实际是加上自己类型的大小,20个字节,则为0x100014
(unsigned long)p+0x1,p被强转为了无符号长整数,是整数,加1就是直接加1,为0x100001
(unsigned int*)p + 0x1,p被强转为了unsigned int*,大小为4个字节,加1就是加类型的大小,为0x100004
在这里插入图片描述
3.

int main()
{
	int a[4] = { 1,2,3,4 };
	int *ptr = (int *)(&a + 1);
	int *ptr2 = (int *)((int)a + 1);
	printf("%x %x\n", ptr[-1], *ptr2);
	return 0;

}

在这里插入图片描述
结果:
在这里插入图片描述
4.

int main()
{
	int a[3][2] = { (0,1),(2,3),(4,5) };
	int *p;
	p = a[0];
	printf("%d\n", p[0]);
	return 0;
}

在这里插入图片描述
所以这个结果为:1
在这里插入图片描述

int main()
{
	int a[5][5];
	int(*p)[4];
	p = a;
	printf("%p,%d\n", &p[4][2] - &a[4][2], &p[4][2] - &a[4][2]);
	return 0;
}

在这里插入图片描述
%p为无符号整数,所以被解释为了一个特别大的数
指针-指针,为两指针所经历的元素的个数
结果:
在这里插入图片描述
6.

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

ptr1指向数组末下一个地址。
ptr2指向二维数组中,第二个数组
则结果分别为:10,5
在这里插入图片描述

int main()
{
	char *a[] = { "work","at","school" };
	char **pa = a;
	pa++;
	printf("%s\n", *pa);
	return 0;
}

a为指针数组。
二级指针pa指向a的起始地址,对pa++,指向指针数组中下一个数组,即“at”
结果为:
在这里插入图片描述

int main()
{
	char *c[] = { "enter","new","point","first" };
	char **cp[] = { c + 3,c + 2,c + 1,c };
	char ***cpp = cp;
	printf("%s\n", **++cpp);
	printf("%s\n", *--*++cpp + 3);
	printf("%s\n", *cpp[-2] + 3);
	printf("%s\n", cpp[-1][-1] + 1);
	return 0;
}

在这里插入图片描述
在这里插入图片描述
结果为:point,er,st,ew
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/w903414/article/details/106837545