C语言之深入理解指针

1.指针和数组没有关系:

>指针和数组是两种不同的类型

>数组不能整体赋值,所以数组名不能做左值,做右值表示首元素地址

2.指针数组:是数组,是一个存放指针的数组。

例如:

int *arr1[10];
char *arr2[3];
char **arr3[5];

3.数组指针:

>数组指针是指针,这个指针指向数组。

如:

int (*arr1)[10];
char (*arr2)[3];

与指针数组相比多了一个括号。

所以总结:对于一个数组指针:

int *p[10];  //p先和*结合,说明p是一个指针变量,然后指向一个大小为10的整形数组。*的优先级低于[]

>数组的地址存储:

例如:

int *arr[10]={0};
int (*P)[10]=&arr;   //这里数组维度必须和arr相同

指针就是地址,所以数组的地址要存储可以用数组指针来存储.

注:在数组中数组的维度也是类型的一部分,所以维度必须相同:如int a[10]和int b[3]不是一种类型。

>一维数组传参:任何数组的传参会发生将维,将成指向其内部元素类型的指针

例如:

void test(int arr[]){};
void test(int arr[10]){};
void test2(int *arr){};
void test2(int **arr){};
int main(){
        int arr[10]={0};
        int *arr2[20]={0};
        test(arr);
        test2(arr2);
}

>二维数组的传参:

任何数组的传参会发生将维,将成指向其内部元素类型的指针。任何数组可以看成是一维数组,不过一维数组中的元素是数组。

二维数组传参,函数只能省略第一个[]的内容,因为对二维数组,可以不知道有多少行,但必须知道一行有多少个元素(主要是为了访问元素)

如:

void test(){
  int arr[3][5]={0};
  print(arr);
}
void print(int arr[3][5]){}; //可以传数组
void print(int arr[][5]){};  //数组中第一个[]的数字可以省略

void print(int (*arr)[5]){}; //指针指向内部元素,内部元素是一个数组

4.指针和数组的定义,声明:

>定义就是不存在的时候要让他存在,也就是开辟空间。声明让别人知道这个内容存在,不需要开辟空间。

下面举一个例子:

//在test.c中定义
char arr[]="abcdef";
char *p="abcdef";


//在main.c中引入变量
extern char arr[];     //extern表示引入外部文件的定义
extern char *p;     //在这里只需要引入函数的声明就行,不用再去定义开辟空间
int main(){
  printf("%s\n",arr);
  printf("%s\n",p);
  return 0;
 }

运行结果都是:abcdef

下面有两种错误的声明方式:

(1)定义的是数组,引入的时候声明的是指针

//在test.c中定义
char arr[]="abcdef";

//在main.c中引入变量
extern char *arr;    
int main(){
      printf("%s\n",arr);
      return 0;
}

运行程序时会报错,在test.c中arr占7个字节。在main.c中声明成指针类型占4个字节,所以arr只能访问test.c中的前4个字节,系统肯定不会同意,所以运行肯定会出错。

(2)定义的是指针,引入声明的是数组:

//在test.c中定义
char *p="abcdef";

//在main.c中引入变量
extern char p[];    
int main(){
      printf("%s\n",p);
      return 0;
 }

能编译通过,但运行出来的是随机值。

因为在test.c中定义的p占4个字节,main.c中p访问的是指针的四个字节,所以是随机值。

5.函数指针:

>函数的函数名

首先看下面代码:

#include<stdio.h>
#include<Windows.h>
void test(){}
int main(){
	printf("%p\n", test);
	printf("%p\n", &test);
	system("pause");
	return 0;
}

运行结果:


上面运行结果一样,因为函数名表示的是函数的地址。

>函数的地址也需要存储,我们知道地址就是指针,那么能存储地址的只有指针,所以用函数指针来存储函数的地址。

如下面例子:

void test(){};
那么用来保存函数地址的指针为:
void (*p)();  //p先和*结合,p为指针,指向的是一个函数指向的函数无参数,返回类型为void

>下面两个有趣的代码:

(*(void(*)())0)();
void(*signal(int,void(*)(int)))(int);

第一个代码:将0强转成void(*)()函数指针,然后解引用,即函数的调用

第二个代码:是一个函数,参数是int和void(*)(int),函数返回类型是:void(*)(int);

6.函数指针数组:

>我们知道函数是一个存放相同类型数据的存储空间,如果把一个函数的地址存储到一个数组中,那么这个数组就是函数指针数组。

>函数指针数组定义是:

void (*arr[10])();  //arr先和[]结合,所以arr是数组,数组的内容是:void(*)()类型的函数指针

>函数指针数组的实例:转移表

计算器的应用:

#include<stdio.h>
#include<windows.h>
int add(int x, int y)
{
	return x + y;
}
int sub(int x, int y)
{
	return x - y;
}
int nul(int x, int y)
{
	return x*y;
}
int drv(int x, int y)
{
	return x / y;
}
int main(){
	int x, y;
	int input = 1;
	int ret = 0;
	int(*p[5])(int x, int y) = { 0, add, sub, nul, drv };//转移表
	while (input){
		printf("***********************\n");
		printf("  1:add        2:sub   \n");
		printf("  3:nul        4:div   \n");
		printf("***********************\n");
		printf("请选择:");
		scanf_s("%d", &input);
		if ((input<=4 && input>=1))
		{
			printf("输入操作数:");
			scanf_s("%d %d", &x, &y);
			ret = (p[input])(x, y);
		}
		else
			printf("输入错误\n");
		printf("%d\n", ret);
	}
	system("pause");
	return 0;
}

>函数指针数组的指针:

下面看一个代码:

#include<stdio.h>
#include<Windows.h>
void test(const char*str){}
int main(){
	void(*p)(const char*) = test;             //p是函数指针
	void(*p1[5])(const char*) = test;	  //p1是函数指针的数组
	void(*(*p2[5]))(const char*) = test;      //p2是指向函数数组的指针
	system("pause");
	return 0;
}

指向函数指针数组的指针是一个函数。

指针指向一个数组,数组的元素都是函数指针。

7.回调函数:

>回调函数就是通过函数指针调用的函数。把函数的之指针作为参数传递给另一个函数,这个指针可用来调用其所指向的函数。

回调函数不是由该函数的实现方直接调用,而是在特定的事件或发生条件时由另一方调用,用于对该事件或条件进行响应。简单来说,就是由别人的函数运行期间来回调你实现的函数。

例如;

#include<stdio.h>
#include<Windows.h>
void test(){ //定义回调函数
	printf("holle world!");
}
void test1(void(*test2)()){  //定义实现回调函数的调用函数
	test2();
}
int main(){
	test1(test);    //实现函数回调
	system("pause");
	return 0;
}

>回调函数的实例:

模拟实现qsort:qsort无类型快速排序void qsort(void*base,size_t num,size_t width,int(_cdecl*compare)(const void*eleml,const void*elem2));其中第一个参数 所要排序的内存空间的首地址,第二个参数是元素的个数,第三个参数是元素的宽度即元素的类型大小,第四个参数传入函数进行元素的比较。

使用冒泡方式实现qsort:

#include <stdio.h>
#include<windows.h>
#include <string.h>

int cmp_str(void *p1, void *p2)//字符串比较
{

	return strcmp(*(char **)p1, *(char**)p2);//强转成二级字符指针,这样解一级引用还是指针,这样就可取得4个字节的内容即是取得第数组一个元素的全部地址取得的地址就是整个字符串首元素地址,传入strcm函数进行比较。
}

int cmp_int(void *p1, void *p2)//整型数字比较
{
	int a = *(int*)p1;
	int b = *(int*)p2;
	if (a > b)
		return 1;
	else if (a < b)
		return -1;
	else
		return 0;
}

int cmp_ch(void *p1, void *p2)//字符比较
{

	return *((char*)p1) - *((char*)p2);
}

int cmp_double(void *p1, void *p2)//单精度浮点数字比较
{
	double a = *(double*)p1;
	double b = *(double*)p2;
	if (a > b)
		return 1;
	else if (a < b)
		return -1;
	else
		return 0;
}


void Swap(char *x, char *y, int size)
{
	while (size--)//将变量的每一字节进行交换
	{
		*x ^= *y;
		*y ^= *x;
		*x ^= *y;
		x++;
		y++;
	}

}


void my_qsort(void *arr, int num, int size, int(*cmp)(void *, void *))//void*型指针保证类型的兼容性,传入什么类型都可以进行排序在内部,将指针转成char*类型,是的每一字节进行交换,使得不必考虑虑参数类型
{
	int i = 0;
	for (i = 0; i < size - 1; i++)
	{
		int flag = 0;//有序标记
		int j = 0;
		for (j = 0; j < num - 1 - i; j++)
		{    //针对指针数组来说,存放的地址转换成用char*类型指针读取,读取只读取1个字节的内容,第二个参数加类型长度,指向数组下一个元素地址,也只读取一个字节内容
			if (cmp((char *)arr + j * size, (char *)arr + (j + 1) * size) > 0)
			{   //同理Swap函数,针对指针数组来说,转成char类型指针,交换的是一个字节的内容,并没有一次性交换4字节(整个字符串首元素)的地址
				flag = 1;   //进行一次排序,flag置为1,表示该序列本身不是有序的
				Swap((char *)arr + j * size, (char *)arr + (j + 1) * size, size);
			}
		}
		if (flag==0)//第一次循环,没有进行一次交换,表示序列本身就是有序的 
			break;
	}

}

int main()
{
	//char *p[] = { "hijs", "sdfd", "fgdg", "abcd", "defg", "abc" };//这是一个指针数组,是一个存放指针的数组,而不是指针
	int arr[] = { 12,623,8,2,6,234};
	//char ch[] = { 'a','f','y','s','b','d' };
	//double db[] = { 2.4,4.5,1.3,7.8,1.2 };
	int i = 0;
	int size = sizeof(arr) / sizeof(arr[0]);
	my_qsort(arr, size, sizeof(int), cmp_int);//整型数字排序
	//my_qsort(p, size, sizeof(char*), cmp_str);//字符串数组排序
	//my_qsort(ch, size, sizeof(char), cmp_ch);//字符排序
	//my_qsort(db, size, sizeof(double), cmp_double);//浮点数排序
	for (; i < size; i++){
		printf("%d ", arr[i]);
	}
	system("pause");
	return 0;
}

8.指针和数组相关练习;

>

#include<stdio.h>
#include<windows.h>
int main(){
	int a[5]={1,2,3,4,5};
	int *ptr=(int *)(&a+1);  //这里a代表整个数组,取地址加1代表下一个数组的地址,下一个数组为整形
	printf("%d\n",*(a+1));  //2  做右值a代表首元素地址,加1代表加上元素类型的大小即下一个元素的地址,
	printf("%d\n",*(ptr-1));  //5  ptr指向下一个数组的首元素地址,减1即指向数组a的最后一个元素地址
        system("pause");
	return 0;
} 
>
struct Test
{
	int Num;
	char *pcName;
	short sDate;
	char cha[2];
	short Sba[4];
}*p;

结构体大小为20.假设p的值为0x100000,则下值为:

p+0*1=0x100014                    //指针p加1即加上其类型的大小,即加20
(unsigned long)p+0*1=0x100001   //这里p为无符号long型,加1即加上值1
(unsigned int*)p+0*1=0x100004   //p为int *型指针,加1即加上类型大小4
>
int main(){
	int a[4]={1,2,3,4};
	int *ptr1=(int *)(&a+1);
	int *ptr2=(int *)((int)a+1);
	printf("%x\n",ptr1[-1]); //4 相当于*(ptr1-1)
	printf("%x\n",*ptr2);
	system("pause");
	return 0;
} 

读取*ptr2从ptr2处连续读取4个字节,要考虑大小端。

>

int main(){
	int a[2][3]={(0,1),(2,3),(4,5)}; //数组其实为{1,3,5,0,0,0}
	int *p;
	p=a[0];
	printf("%d\n",p[0]);
	system("pause");
	return 0;
} 

结果为:1

在这道题里要考虑数组里面的()里面是逗号表达式,即以最右边为主,所以数组应该为:{1,3,5,0,0,0};

>

#include<stdio.h>
#include<windows.h>
int main(){
	int a[5][5];
	int(*p)[4];
	p = a;
	printf("%p,%p\n", &a[4][2], &p[4][2]);
	printf("%p,%d\n", &p[4][2] - &a[4][2], &p[4][2] - &a[4][2]);
	system("pause");
	return 0;
}

运行结果:


分析:p[4][2]相当于*(*(p+4)+2),找到相对应位置,因为对指向同一数组的指针相加减代表的是两指针元素个数,因为&p[4][2] 小于 &a[4][2],所以%d输出结果为-4,当以%p输出时按无符号输出,即:

  在内存中:1000 0000 0000 0000 0000 0000 0000 0100
   取反:   1111 1111 1111 1111 1111 1111 1111 1100
             F    F    F    F    F    F    F    C


>
#include<stdio.h>
#include<Windows.h>
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);
	system("pause");
	return 0;
}

运行结果:

分析:

指针数组c中放的是字符串的地址,指针数组cp中放的是c的数组倒叙,指针cpp里放的是cp的地址。

**++cpp:


*--*++cpp+3:

*cpp[-2]+3:

cpp[-1][-1]+1:




猜你喜欢

转载自blog.csdn.net/qq_41889292/article/details/80375992