在之前的文章中也对指针有一点了解了,今天我们再来更加深入的学习指针。指针内容其实有一点的多因为它其实还是非常重要的,ok现在就让我们步入正题吧!
一,一些常用的指针变量
字符指针变量
直接来个小题来了解一下这个变量:
#include<stdio.h>
int main()
{
char str1[] = "holle bit.";
char str2[] = "holle bit.";
const char* str3 = "holle bit.";
const char* str4 = "holle bit.";
if (str1 == str2)
printf("str1 and str2 are same\n");
else
printf("str1 and str2 are not same\n");
if(str3==str4)
printf("str1 and str2 are same\n");
else
printf("str1 and str2 are not same\n");
return 0;
}
运行结果
我们可以发现str1和str2不一样,str3和str4一样。这是为什么,我们知道指针变量的用法可以通过它来改变所指向数据的内容。str3和str4所指向的内容相同,这两个一样并不奇怪。那str1和str2的内容也是相同的为啥编译器显示它们不一样呢?
这里str3和str4指向的是同一个常量字符串,C/C++会把常量字符串储存到单独的一个内存区,当几个指针指向同一个字符串的时候,他们实际会指向同一块内存。但是在相同的常量字符串在初始化不同的数组的时候会开辟出不同的内存块。所以str1str2不相同。
其实字符指针和整型指针的原理其实都是一样的都是吧这个数据的首元素地址传到指针变量中。
数组指针变量
上一篇我们讲了指针数组我们都知道它其实是个数组,那么数组指针是数组还是指针呢?
答案:指针变量
我们来回忆一下:
整型指针变量:int* pint;存在的是整型变量的地址,能够指向整型数据的指针。
字符指针变量:char* pint;存放的是字符变量的地址,能够指向字符数据的指针。
那么数组指针的形式是什么样的呢?我们都知道整型数组的形式为int arr[ ]。那么数组指针的形式呢?int* arr[ ]还是int (*arr)[ ]?其实是int (*arr)[ ],*先和arr结合就说明arr是一个指针变量,指向的是整型数组(int[ ]),如果*先和int结合就说明其实一个指针变量指向的是整型(int)。
怎么初始化数组指针变量
我们知道数组指针所指向的是数组。那么初始化中的数据就是数组的地址。
int main()
{
int arr[10] = { 0 };
int(*p)[10] = &arr;
return 0;
}
我们调试可以看到&arr和p的类型完全一致的。
我们来解析一下这个指针变量:
int (*p)[10]=&arr;
int[ ]: 返回的类型,p: 数组指针变量的变量名,10: p指向的元素个数。
注意:这里的[10]不是指数组指针变量可以接收10个地址,而是指指针变量所指向返回的类型是int[10]。如果为int arr[12],int (*p)[10]是不可以接收的。
函数指针变量
我们都知道函数的形式为int add(int x,int y);那么函数指针变量的形式是什么呢?
int(*add)(int x,int y);是的就是这样。我们来解析一下:
int(int x ,int y):返回类型,add:函数指针变量的变量名,int x,inty:函数接收数据的类型。
来做个测试:
void test()
{
printf("hehe\n");
}
int main()
{
printf("test:%p\n",test);
printf("&test:%p\n",&test);
return 0;
}
运行结果:
这就说明函数是有地址的函数名就是函数的地址,当然也可以通过&test来获得函数的地址。
函数指针变量的使用
#include<stdio.h>
int Add(int x, int y)
{
return x + y;
}
int main()
{
int (*p)(int x, int y) = Add;
printf("%d\n", (*p)(2, 3));
printf("%d\n", p(2, 3));
return 0;
}
运行结果:
这就说明可以用指针来直接调用函数。
两端有趣的代码:
代码1
(*(void(*)())0)();
首先把0强制类型转换为void(*)()类型的函数指针变量,然后解引用,就得到了在0位置上的函数。
代码2
void(*signa(int,void(*)(int)))(int);
这是一个函数指针变量,返回类型为void(int),变量名为signa,函数接收的数据类型为int,void(*)(int)。
typedef关键字
typedef是用来类型重命名的,可以将复杂的类型,简单化。
比如:我们可以把函数类型void(*)(int )重命名为pf_t,就可以这样写:
typedef void(*pf_t)(int);
就可以把上面的代码2转换为
pf_t signal(int ,pf_t);
函数指针数组
数组我们已经很熟悉了那么函数指针数组到第是什么样子呢?
首先函数指针数组的返回类型是函数指针类型为int(*)( )它又是一个数组那么怎么表示呢?
我们知道数组指针类型是int (*)[ ]把它们结合起来还是什么呢?
判断一下下面那个是函数指针数组
int (*parr1[3])();
int *parr2[3]();
int (*)()parr3[3];
答案是parr1
这里解析一下:parr1先和[ ]结合说明它是一个数组,它的返回类型为int(*)( ) ,所以数组的内容是函数指针变量。
转移表
函数指针的用途:
举例计算机的一般实现:
#include<stdio.h>
void menu()
{
printf("**************************\n");
printf("***** 1:add 2:sub*****\n");
printf("***** 3:mul 4:div*****\n");
printf("***** 0:exit *****\n");
printf("**************************\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)
{
return x / y;
}
int main()
{
int x = 0;
int y = 0;
int input = 1;
int ret = 0;
int (*p[5])(int x, int y) = { 0,add,sub,mul,div };
do
{
menu();
printf("请选择:");
scanf("%d", &input);
if ((input < 4 && input >= 1))
{
printf("输入操作数:");
scanf("%d %d", &x, &y);
ret = (*p[input])(x, y);
printf("ret=%d\n", ret);
}
else if (input==0)
{
printf("退出计算器\n");
}
else
{
printf("输入错误\n");
}
} while (input);
return 0;
}
如果你没有学过函数指针数组,那么你就会用switch()函数进行,但是如果你要在添加一个类似的功能时,就会重复写许多重复的代码,而且还会极大的加长代码的长度(加入好的case)。用函数指针数组会更加简便。
二,回调函数
回调函数是一个通过函数指针调用的函数。
如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针用来调用其所指向的函数时,被调用的函数就是调用函数。
#include<stdio.h>
void menu()
{
printf("**************************\n");
printf("***** 1:add 2:sub*****\n");
printf("***** 3:mul 4:div*****\n");
printf("***** 0:exit *****\n");
printf("**************************\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)
{
return x / y;
}
void calc(int(*pf)(int, int))
{
int x, y;
int ret = 0;
printf("输入操作数:");
scanf("%d %d", &x, &y);
ret = pf(x, y);
printf("ret=%d\n", ret);
}
int main()
{
int x = 0;
int y = 0;
int input = 1;
int ret = 0;
int (*p[5])(int x, int y) = { 0,add,sub,mul,div };
do
{
menu();
printf("请选择:");
scanf("%d", &input);
switch (input)
{
case 1:
calc(add);
break;
case 2:
calc(sub);
break;
case 3:
calc(mul);
break;
case 4:
calc(div);
break;
case 0:
printf("退出计算器:\n");
break;
default:
printf("选择错误:\n");
break;
}
} while (input);
return 0;
}
这是用回调函数改造后的代码。
三,qsort函数
qsort函数的使用
void qsort (void* base, size_t num, size_t size, int (*compar)(const void*,const void*))
首先认识一下qsort函数qsort函数需要四个参数。
base:接收排序数据的首元素地址,返回类型为void*。
num:接收排序数据的元素个数,返回类型为size_t。
size:接收排序元素的字节大小,返回类型为size_t。
compar:一个函数指针需要两个参数,把这两个参数进行比较返回类型为int。
如果返回数>0,p1>p2。<0,p1<p2。==0,p1==p2。
qsort函数排序整型数据
#include<stdio.h>
int cm_p(const void* p1, const void* p2)
{
return (*(int*)p1 - *(int*)p2);
}
int main()
{
int arr[10] = { 0,3,6,5,7,2,4,8,9,1 };
qsort(arr, sizeof(arr) / sizeof arr[0], sizeof(int),cm_p);
for (int i = 0; i < sizeof(arr) / sizeof(arr[0]); i++)
{
printf("%d ", arr[i]);
}
printf("\n");
return 0;
}
qsort函数排序结构体数据
#include<stdio.h>
#include<string.h>
struct stu
{
char name[20];
int age;
};
int cm_p2(const void* p1, const void* p2)
{
return strcmp(((struct stu*)p1)->name, ((struct stu*)p2)->name);
}
int cm_p1(const void* p1, const void* p2)
{
return ((struct stu*)p1)->age - ((struct stu*)p2)->age;
}
void test1()
{
struct stu s[] = { {"zahngsan",18},{"lishi",20},{"wangwu",15} };
int sz = sizeof(s) / sizeof(s[0]);
qsort(s, sz, sizeof(struct stu), cm_p1);
for (int i = 0; i < sz; i++)
{
printf("%d\n", s[i].age);
}
}
void test2()
{
struct stu s[] = { {"zahngsan",18},{"lishi",20},{"wangwu",15} };
int sz = sizeof(s) / sizeof(s[0]);
qsort(s, sz, sizeof(struct stu), cm_p2);
for (int i = 0; i < sz; i++)
{
printf("%s\n", s[i].name);
}
}
int main()
{
test1();
test2();
return 0;
}
int strcmp ( const char * str1, const char * str2 );
这里在比较字符串是用的函数strcmp,我们来介绍一下。它有两个参数类型为char*。其头文件为string.h。
其比较的是首位的ASCll码值如果相等就比较下一位,strcmp函数的返回类型和compar是一样的。
qsort模拟实现
为了更好的理解其原理我们来模拟实现一下:
这里就用到了之前写的冒泡排序,我们就用qsort函数排序的思路模拟实现一个冒泡排序。
#include<stdio.h>
int cm_p(const void* p1,const void* p2)
{
return *(int*)p1 - *(int*)p2;
}
void _swap(const void* p1, const void* p2,size_t sz)
{
int i = 0;
for (i = 0; i < sz; i++)
{
char* tem = *((char*)p1 + i);
*((char*)p1 + i) = *((char*)p2 + i);
*((char*)p2 + i) = tem;
}
}
void bubble(void* base, size_t num, size_t size, int (*cmp)(const void* p1, const void* p2))
{
int i = 0;
int j = 0;
for (i = 0; i < num - 1; i++)
{
for (j = 0; j < num - 1 - i; j++)
{
if (cmp((char*)base + j * size, (char*)base + (j + 1) * size) > 0)
{
_swap((char*)base + j * size, (char*)base + (j + 1) * size,size);
}
}
}
}
int main()
{
int arr[10] = { 0,3,6,5,7,2,4,8,9,1 };
int i = 0;
int sz = sizeof(arr) / sizeof(arr[0]);
bubble(arr, sz, sizeof(int), cm_p);
for (i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
return 0;
}
这个模拟实现如果你能看懂那么恭喜你qsort函数和冒泡排序你以轻松的拿下了。
四,sizeof和strlen对比
sizeof是个操作符,sizeof计算的是变量所占内存空间的大小。单位是字节。
strlen是个库函数,功能是求字符串的长度,strlen会从参数首元素的一个一个的往后找直到找到\0后才会结束。所以可能存在越界访问。
五,一些数组和指针运算题目
我把代码提供给大家,大家可以自己在编译器上运行一下。但是不是所有的代码编译器都会运算的。请理解其中的逻辑,希望对你有帮助。
#include<stdio.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[0]));
printf("%d\n", sizeof(&a[0]+1));
return 0;
}
#include<stdio.h>
#include<string.h>
int main()
{
char a[] = "abcdef";
printf("%d\n", strlen(a));//6
printf("%d\n", strlen(a+0));//6
//printf("%d\n", strlen(*a));//err
//printf("%d\n", strlen(a+1));//err
//printf("%d\n", strlen(a[1]));//err
printf("%d\n", strlen(&a));//6
printf("%d\n", strlen(*&a));//6
printf("%d\n", strlen(&a[0]));//6
printf("%d\n", strlen(&a[0]+1));//5
printf("%d\n", strlen(&a + 1));//随机值
return 0;
}
#include<stdio.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[1]));
printf("%d\n", sizeof(&a));
printf("%d\n", sizeof(*&a));
printf("%d\n", sizeof(&a[0]));
printf("%d\n", sizeof(&a[0]+1));
printf("%d\n", sizeof(&a + 1));
return 0;
}
#include<stdio.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[1]));
printf("%d\n", sizeof(&a));
printf("%d\n", sizeof(*&a));
printf("%d\n", sizeof(&a[0]));
printf("%d\n", sizeof(&a[0] + 1));
printf("%d\n", sizeof(&a + 1));
return 0;
}
#include<stdio.h>
#include<string.h>
int main()
{
char *a = "abcdef";
printf("%d\n",strlen(a));//6
printf("%d\n", strlen(a + 0));//6
//printf("%d\n", strlen(*a));//err
printf("%d\n", strlen(a+1));//5
//printf("%d\n", strlen(a[1]));//err
printf("%d\n", strlen(&a));//6
printf("%d\n", strlen(*&a));//6
printf("%d\n", strlen(&a[0]));//6
printf("%d\n", strlen(&a[0] + 1));//5
printf("%d\n", strlen(&a + 1));//随机值
return 0;
}
提示一下这里主要考察的是数组名相关的知识点。
其实数组名就是首元素的地址是对的,就是有两个例外:
1.sizeof(数组名),sizeof中单独放数组名,这里的数组名表示的是整个数组,计算的是整个数组的大小单位是字节。
2.&数组名,这里的数组名表示的是整个数组,取出的是整个数组的地址(整个数组的地址和数组首元素的地址是有区别的)。
除了这两个情况之外其它的情况数组名都是表示数组首元素的地址。
再来一个比较简单的指针提练练手:
#include<stdio.h>
int main()
{
int a[5] = { 1,2,3,4,5 };
int* ptr = (int*)(&a + 1);
printf("%d %d", *(a + 1), *(ptr - 1));
return 0;
}
那么今天的讲解就到这里了,指针也是基本上讲解完了,你也可以看到指针的内容是非常多的。这也没有办法毕竟指针很重要。其实指针的题目远远不止这一点,如果你还要了解可以在下面评论。