C语言指针详解(新手入门推荐)

发布于:2023-01-14 ⋅ 阅读:(431) ⋅ 点赞:(0)

目录

 指针的理解

字符指针:char *

无类型指针void *

指针数组

数组指针

函数指针

回调函数

函数指针数组

练习

指针和数组笔试题解析

 指针的理解

关于指针,我先讲一个故事:一个侦探在案发现场发现了一张字条,字条上说让他到卧室的床底下寻找第二张字条。侦探找到第二张字条后,字条上说让他到厨房的水槽中找第三张字条。当他找到最后一张字条后。字条上的信息让他破解了这个案件。

这个故事中的字条,就好比指针。

 

指针是C语言的灵魂,总的来说,有以下三种理解:

1,指针是一种类型,可以定义指针类型的变量。

2,指针就是内存地址,因为其指向了内存空间

3,指针说的是指针变量

指针的关注点

     俩种类型:指针本身的类型,指针指向的类型

char *p;int *p;

     俩种大小:指针的大小,所能访问的指向空间大小

sizeof(p)   sizeof(*p)

字符指针:char *

字符指针是个指针,指向了字符的地址

int main()
{
    char str[] = "apple";
    char *p = "apple";
    printf("%s--%p\n",str,str);
    printf("$s--%p\n",p,p);
    system("pause");
    return 0;
}

上述代码中,str和p的值相同,其地址却不相同,说明指向的并不是同一块空间。

我们再来扩展一下;


#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<stdlib.h>
int main()
{
	char str1[] = "apple";
	char str2[] = "apple";
	char *p1 = "apple";
	char *p2 = "apple";
	if (str1 == str2)
	{
		printf("str1 == str2\n");
	}
	else
	{
		printf("str1!=str2\n");
	}
	if (p1 == p2)
	{
		printf("p1 == p2\n");
	}
	else
	{
		printf("p1!= p2\n");
	}
	system("pause");
	return 0;
}

注意:

字符串给指针进行赋值时,赋值的是字符串的地址

字符数组进行字符串的初始化赋值时,赋值的是字符串内容

无类型指针void *

void 是无类型,通常定义函数的返回值类型或者参数类型表示没有返回值或参数

void 是无类型指针。

        通常表示无关大小,只关心指向的指针类型

int * cmp(const void *p1,const void *p2)
{
    return *(int*)p1-*(int *)p2;
}

比如上面的代码,参数类型为void *

指针数组

本质是一个数组,数组元素都是指针

char *p[10];

根据算数优先级可得,变量p先和[]结合,为数组,再和*结合,表示数组的每个元素是指针。int表明数组元素是char类型(指针指向char类型).

我们来看一个例子:

定义一个具有20个元素的字符指针数组

char *p[20] = {0};

数组指针

本质是一个指针,指向了一个数组

char (*p)[10];

例子:

定义一个具有10个整型指针元素的数组指针

int* (*p)[10] = NULL;

数组名和&数组名

数组名:数组首元素地址

对数组名取地址:数组的地址(数组指针);

int a[10];
a+1;//第二个元素地址
&a+1;//指向位置跳过整个数组,指向最后一个元素的结尾处

例子:

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include<stdlib.h>

int main()
{
	int  a[5] = { 1, 2, 3, 4, 5 };
	int *p = (int*)(&a + 1);//强制转换为int*类型,
	printf("*(a+1) == %d,*(p-1) == %d",*(a+1),*(p-1));
    //*(a+1)为第二个元素地址,接应用后为2,*(p-1)为最后一个元素的地址解引用,即最后一个元素值
	system("pause");
	return 0;
}

注意:

数字名大多数情况下被认为数组首元素地址,但有俩种情况不同

1,数组名取地址

2,sizeof(数组名)

上述俩中情况数组名代表整个数组,在后面的笔试题中我们会详细介绍。

函数指针

本质是指针,指向了函数的地址.

和数组名一样,函数名可以表示函数的地址

int (*p)(int a,int b);

例子:

定义一个函数指针变量,指向的函数有int *类型的参数,返回值是char *.

char * (*p)(int *b) = &fun;

回调函数

定义一个函数后,不会直接调用,而是在特定情况下才能调用。

这有个程序员买西瓜的段子,可以方便理解:

女朋友对程序员说:“亲爱的,去超市买一个西瓜吧,如果他们还有鸡蛋,再买20个。”结果程员带了21个西瓜回家。女朋友愤怒地说:“为什么买21个西瓜回来”?程序员答:“因为他们确实有鸡蛋”。

为什么要使用回调函数

因为可以把调用者与被调用者分开,所以调用者不关心谁是被调用者。它只需知道存在一个具有特定原型和限制条件的被调用函数。

简而言之,回调函数就是允许用户把需要调用的方法的指针作为参数传递给一个函数,以便该函数在处理相似事件的时候可以灵活的使用不同的方法。

 

如何使用回调函数? 

 

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include<stdlib.h>

int func1(int a)
{
	printf("this is func1,a = %d\n",a);
	return 0;
}

int func2(int b)
{
	printf("this is func2,a = %d\n", b);
	return 0;
}

int func3(int c)
{
	printf("this is func3,a = %d\n", c);
	return 0;
}

int F(int n,int (*func)(int))
{
	return func(n);
}
int main()
{
	F(1,func1);
	F(2, func2);
	F(3, func3);
	system("pause");
	return 0;
}

如上述代码:可以看到,F()函数里面的参数是一个指针,在main()函数里调用F()函数的时候,给它传入了函数func1()/fun2()/fyn3()的函数名,这时候的函数名就是对应函数的指针,也就是说,回调函数其实就是函数指针的一种用法。

函数指针数组

本质是个数组,数组元素是函数指针类型

int (*p[10])(int p1,int p2)

我们来解剖上面的代码

根据运算符优先级,p变量先和[]结合为数组,再和*结合,表明数组的每个元素都是一个指针,指针指向了有俩个int类型形参的函数,函数返回值是int类型。

下面来看一个更具体的例子:

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include<stdlib.h>

int Sum(int a,int b)
{
	return a + b;
}

int Sub(int a,int b)
{
	return a - b;
}

int Div(int a,int b)
{
	return a / b;
}

int Mul(int a,int b)
{
	return a*b;
}
int main()
{
	int(*arr[5])(int, int) = { 0, Sum, Sub, Div, Mul };
	int input = 0;
	while (true)
	{
		printf("*******计算器*******\n");
		printf("***1-加法  2-减法***\n");
		printf("***3-除法  4-乘法***\n");
		printf("********************\n");
		printf("请输入功能序号:");
		scanf("%d", &input);
		printf("请输入俩个数字:");
		int n1 = 0, n2 = 0;
		scanf("%d %d", &n1, &n2);
		int res = arr[input](n1, n2);
		printf("结果为:%d\n",res);
	}
	
	system("pause");
	return 0;
}

练习

分析下面代码分别是什么意思。

1,int*(*(*fun)(int *))[10];

2, int (*fun)(int *,int(*)(int *));

3, int (*fun[5])(int *);

4, int (*(*fun)[5])(int *);

5, int (*(*fun)(int *))[5];

6, int* (*fun(int *))[5];

在这我们可以停留一会来自己分析一下。想好后再往后看你想的是否正确!

-----------------------------------------------------------------------------------------------------------------------------

代码分析:

1,int*(*(*fun)(int *))[10];

fun先和*结合,为指针。指针指向了函数(函数有int *类型参数),函数返回值为指针,指针指向了一个数组(数组有十个元素,元素类型为int *)。

所以fun是函数指针:fun先和*结合,为指针。指针指向了函数(函数有int *类型参数)。

指针指向了指针数组

 指针数组:函数返回值为指针,指针指向了一个数组(数组有十个元素,元素类型为int *)。

2, int (*fun)( int *,int(*)(int *) );

fun先和*结合,为指针。指针指向了函数(函数有俩个参数,一个为int *,另一个为 函数指针,指针返回值为 int 类型)

所以fun是函数指针

3, int (*fun[5])(int *);

fun先和[ ]结合,为数组。数组元素类型为指针。指针指向了函数(函数有int *类型的形参),函数返回值为int类型

所以fun是函数指针数组

4, int (*(*fun)[5])(int *);

fun先和*结合,为指针。指针指向了数组(数组有5个元素),数组元素类型是指针类型。指针指向了函数(函数有int *类型形参),函数返回值为int 类型。

所以fun是数组指针

5, int (*(*fun)(int *))[5];

fun先和*结合,为指针。指针指向了函数(函数有int *类型形参)。函数返回值为指针类型,指针指向了数组(数组有5个元素),数组元素类型为int类型。

所以fun是函数指针

6, int* (*fun(int *))[5];

fun是函数(函数有int *类型形参),函数返回值为指针类型。指针指向了数组(数组有5个元素),数组元素类型为int *类型。

所以fun是函数声明

指针和数组笔试题解析


1,一维数组

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include<stdlib.h>

int main()
{
	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));
	system("pause");
	return 0;
}

同样的,先思考一下输出的值是多少吧

 

int main()
{
	int a[] = {1,2,3,4};
	printf("%d\n",sizeof(a));//16整个数组大小
	printf("%d\n", sizeof(a+0));//4/8 指针,a数组名为首元素地址
	printf("%d\n", sizeof(*a));//4元素大小,地址解引用为元素大小
	printf("%d\n", sizeof(a+1));//4/8 指针,a数组名为首元素地址
	printf("%d\n", sizeof(a[1]));//4元素大小
	printf("%d\n", sizeof(&a));//4/8
	printf("%d\n", sizeof(*&a));//16整个数组大小
	printf("%d\n", sizeof(&a+1));//4/8
	printf("%d\n", sizeof(&a[0]));//4/8
	printf("%d\n", sizeof(&a[0]+1));//4/8
	system("pause");
	return 0;
}

2,字符数组

1,

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include<stdlib.h>
#include<string.h>
int main()
{
	char  a[] = {'a','b','c','d','e','f'};
	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));//
	printf("%d\n", sizeof(&a+1));//
	printf("%d\n", sizeof(&a[0]));//
	printf("%d\n", sizeof(&a[0]+1));//

	printf("%d\n",strlen(a));//
	printf("%d\n", strlen(a+0));
	//printf("%d\n", strlen(*a));
	//printf("%d\n", strlen(a[1]));
	//printf("%d\n", strlen(&a));
	//printf("%d\n", strlen(&a+1));
	printf("%d\n", strlen(&a[0]+1));
	system("pause");
	return 0;
}

我相信你一定经过充分的思考才来查看的吧

 

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include<stdlib.h>
#include<string.h>
int main()
{
	char  a[] = {'a','b','c','d','e','f'};
	printf("%d\n",sizeof(a));//6整个数组大小
	printf("%d\n", sizeof(a+0));//4/8 指针,a数组名为首元素地址
	printf("%d\n", sizeof(*a));//1元素大小,地址解引用为元素大小
	printf("%d\n", sizeof(a[1]));//1元素大小
	printf("%d\n", sizeof(&a));//4/8
	printf("%d\n", sizeof(&a+1));//4/8
	printf("%d\n", sizeof(&a[0]));//4/8
	printf("%d\n", sizeof(&a[0]+1));//4/8

	printf("%d\n",strlen(a));//未知值,strlen()要遇到‘\0’才停止
	printf("%d\n", strlen(a+0));
	//printf("%d\n", strlen(*a));//错误用法,不符合语法规则,a为首元素地址,解引用后为字符a。字符不能当地址用
	//printf("%d\n", strlen(a[1]));//错误用法,同上
	//printf("%d\n", strlen(&a));//未知值或出错
	//printf("%d\n", strlen(&a+1));//未知值或出错,跳过了整个数组取寻找'\0'
	printf("%d\n", strlen(&a[0]+1));//
	system("pause");
	return 0;
}

2,

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include<stdlib.h>
#include<string.h>
int main()
{
	char  a[] = {"abcdef"};
	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));//
    printf("%d\n",sizeof(&a+1));//
	printf("%d\n", sizeof(&a[0]));//
	printf("%d\n", sizeof(&a[0]+1));//

	printf("%d\n",strlen(a));//
	printf("%d\n", strlen(a+0));//
	//printf("%d\n", strlen(*a));
	//printf("%d\n", strlen(a[1]));
	//printf("%d\n", strlen(&a));
	//printf("%d\n", strlen(&a+1));
	printf("%d\n", strlen(&a[0]+1));//
	system("pause");
	return 0;
}

解释:

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include<stdlib.h>
#include<string.h>
int main()
{
	char  a[] = {"abcdef"};
	printf("%d\n",sizeof(a));//7整个数组大小
	printf("%d\n", sizeof(a+0));//4/8 指针,a数组名为首元素地址
	printf("%d\n", sizeof(*a));//1元素大小,地址解引用为元素大小
	printf("%d\n", sizeof(a[1]));//1元素大小
	printf("%d\n", sizeof(&a));//4/8
	printf("%d\n", sizeof(&a+1));//4/8
	printf("%d\n", sizeof(&a[0]));//4/8
	printf("%d\n", sizeof(&a[0]+1));//4/8

	printf("%d\n",strlen(a));//6,strlen()要遇到‘\0’才停止
	printf("%d\n", strlen(a+0));//6
	//printf("%d\n", strlen(*a));
	//printf("%d\n", strlen(a[1]));
	//printf("%d\n", strlen(&a));
	//printf("%d\n", strlen(&a+1));
	printf("%d\n", strlen(&a[0]+1));//5从第二个开始往后遍历
	system("pause");
	return 0;
}

3,

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include<stdlib.h>
#include<string.h>
int main()
{
	char  *a = "abcdef";
	printf("%d\n",sizeof(a));//
	printf("%d\n", sizeof(a+0));//
	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[0]+1));//

	printf("%d\n",strlen(a));//
	printf("%d\n", strlen(a+1));//
	//printf("%d\n", strlen(*a));
	//printf("%d\n", strlen(a[0]));
	//printf("%d\n", strlen(&a));
	//printf("%d\n", strlen(&a+1));
	printf("%d\n", strlen(&a[0]+1));//
	system("pause");
	return 0;
}

解释:

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include<stdlib.h>
#include<string.h>
int main()
{
	char  *a = "abcdef";
	printf("%d\n",sizeof(a));//4/8,a存储的是字符串首地址
	printf("%d\n", sizeof(a+0));//4/8 指针,a数组名为首元素地址
	printf("%d\n", sizeof(*a));//1元素大小,地址解引用为元素大小
	printf("%d\n", sizeof(a[0]));//1元素大小
	printf("%d\n", sizeof(&a));//4/8
	printf("%d\n", sizeof(&a+1));//4/8
	printf("%d\n", sizeof(&a[0]+1));//4/8

	printf("%d\n",strlen(a));//6,strlen()要遇到‘\0’才停止
	printf("%d\n", strlen(a+1));//5
	//printf("%d\n", strlen(*a));//错误用法
	//printf("%d\n", strlen(a[0]));//错误用法
	//printf("%d\n", strlen(&a));//未知,指针变量取地址,得到的是指针变量的地址
	//printf("%d\n", strlen(&a+1));//未知
	printf("%d\n", strlen(&a[0]+1));//5从第二个开始往后遍历
	system("pause");
	return 0;
}

二维数组

4,

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include<stdlib.h>
#include<string.h>
int main()
{
	char  a[3][4] = {0};
	printf("%d\n",sizeof(a));//
	printf("%d\n", sizeof(a[0][0]));//
	printf("%d\n", sizeof(a[0]));//
	printf("%d\n", sizeof(a[0]+1));//
	printf("%d\n", sizeof(*(a[0]+1)));//
	printf("%d\n", sizeof(a+1));//
	printf("%d\n", sizeof(*(a+1)));//
	printf("%d\n",sizeof(&a[0]+1));//
	printf("%d\n", sizeof(*(&a[0]+1)));//
	printf("%d\n", sizeof(*a));//
	printf("%d\n", sizeof(a[3]));//
	system("pause");
	return 0;
}

解释:

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include<stdlib.h>
#include<string.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/8指针
	printf("%d\n", sizeof(*(a[0]+1)));//4,子数组中单个元素大小
	printf("%d\n", sizeof(a+1));//4/8,指针
	printf("%d\n", sizeof(*(a+1)));//16子数组元素大小
	printf("%d\n",sizeof(&a[0]+1));//4/8指针
	printf("%d\n", sizeof(*(&a[0]+1)));//16子数组大小
	printf("%d\n", sizeof(*a));//16子数组大小
	printf("%d\n", sizeof(a[3]));//16sizeof()求类型大小,不管是否越界。子数组大小
	system("pause");
	return 0;
}

 

5,

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include<stdlib.h>
#include<string.h>
int main()
{
	char *a[] = {"apple","banana","candy"};
	char* *aa = a;//aa这个二级指针变量,存储的是a的首元素地址
	aa++;//自增后指向第二个元素地址
	printf("%s\n",*aa);//解引用后为第二个元素
	system("pause");
	return 0;
}

 

6,

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include<stdlib.h>
#include<string.h>
int main()
{
	char *a[] = {"apple","banana","candy","lemon"};
	char* *aa[] = {a+3,a+2,a+1,a};
	char* **aaa = aa;
	printf("%s\n",**++aaa);
	printf("%s\n", *--*++aaa+3);
	printf("%s\n", *aaa[-2]+3);
	printf("%s\n",aaa[-1][-1]+1 );
	system("pause");
	return 0;
}

我们来逐行分析:

printf("%s\n",**++aaa);

 aaa本来指向aa[]数组中首元素地址,自增操作后,指向第二个元素地址,解引用后输出为candy

 

printf("%s\n", *--*++aaa+3);

aaa再次进行自增操作后,指向了aa【】数组中第三个元素的地址,解引用后指向了a【】中第二个元素的地址,再次解引用后指向了a【】数组中第二个元素值,并向后偏移3个字符后输出字符串。最后输出为le

 

printf("%s\n", *aaa[-2]+3);

看到这你会不会有这样的疑问,aaa[-2]可以这样写吗?

其实,上面的代码等同于

printf("%s\n", *(*(aaa-2)))+3;

因为此时,aaa数组存储的还是aa[]中第三个元素的地址,进行减2操作后,又指向了aa[]中第一个元素的地址,解引用后,指向了a[]数组中的第四个元素的地址,再次解引用后,指向了a[]中第四个元素,并向后偏移三个字符后输出为on

 

printf("%s\n",aaa[-1][-1]+1 );

因为上一步*aaa[-2]+3中,并没有进行自增自减操作,所以此时aaa数组中存储的还是aa[]中第三个元素的地址。根据上一步操作我们可以知道,上面代码等同于:

printf("%s\n",(*(*(aaa -1)-1) +1);

aaa进行自减操作后,指向了aa[]中第二个元素的地址。再进行自减操作后,指向了a[]中第二个元素的地址。解引用后为a[]中第二个元素,并向后偏移一个字符后输出为anana

 

最后我们来对照一下结果是否正确。

 

最后,如果你能理解并掌握上面所写,那恭喜你,指针你已经入门啦!

 


网站公告

今日签到

点亮在社区的每一天
去签到