专栏:C语言
每日一句:人贵有自知之明,知道什么可为和不可为。若不可为,怎样做才能可为,那何时可为。
进阶指针
- 前言
- 一、字符指针
- 二、指针数组
- 1.指针数组的介绍
- 2.指针数组的使用
- 三、数组指针
- 1.数组指针的介绍
- 2.&数组名和数组名
- 3.数组指针的使用
- 四、函数指针
- 五、函数指针数组
- 六、回调函数
- 总结
前言
如若不知道指针是什么,点击传送门
本文是指针的主题,在前面我们已经稍微了解了指针,
1.指针就是个变量,用来存放地址,
2.指针大小在32位中是4字节,在64位中是8字节
3.指针是有类型的,指针的类型决定了指针的±整数的步长。
4.指针的运算
一、字符指针
字符指针是什么?
C语言的基本类型中有char类型,那么在指针的类型中也有一种指针类型为字符的指针char*
,那么字符指针是怎么使用的呢?
int main()
{
char s = 'a';
char* ps = &s;
*ps = 'b';
return 0;
}
这是一种用法,还有下面一种用法。
#include <stdio.h>
int main()
{
const char* s = "Hao Hao Xue Xi";
printf("%s", s);
return 0;
}
const char* s = "Hao Hao Xue Xi";
为什么通过s可以把这个字符串打印出来?是因为把这个字符串存到s指针变量里面了吗?
其实不是的,这里只是把这个字符串的首元素地址,存在了ps里,由第一个元素的地址可以找到后面元素的地址,直到遇到\0
。
#include <stdio.h>
int main()
{
char a1[] = "Hello World";
char a2[] = "Hello World";
const char* ps1 = "Hello World";
const char* ps2 = "Hello World";
if (a1 == a2)
{
puts("a1 == a2");
}
else
{
puts("a1 != a2");
}
if (ps1 == ps2)
{
puts("ps1 == ps2");
}
else
{
puts("ps1 != ps2");
}
return 0;
}
那么,这个代码会输出什么结果呢?
结果为什么不是a1 == a2 和 ps1 == ps2
呢?
我们分别把a1,a2,ps1,ps2的地址打印出来观察一下
看代码结果,ps1和ps2指向的是同一个地址,因为他们指向的是同一个字符串常量,在C语言中会把字符串常量存储到单独的一个内存区域,当n个指针指向同一个字符串常量的时候,他们实际指向的是同一块内存区域
,所以,打印出来的地址是相同的。但是用不同的数组初始化为相同的字符串常量,结果就不是这样的,在创建数组的时候,计算机会自动在内存的某一区域开辟一段属于数组的空间,把数组的内容存在这个空间里,
所以a1和a2
的地址不相同。
二、指针数组
指针数组是指针还是数组?
答案:是数组,但是,指针数组是存放指针的数组
1.指针数组的介绍
我们经常用整型数组,字符数组等
int arr[]
char ch[]
整型数组,数组里面存放的都是整型,
字符数组,数组里面存放的都是字符,
同理:
指针数组,数组里面存放的应该是指针,指针是什么?指针就是地址,也可以这样说,数组里面存放的是地址。
int *arr[]
char *ch[]
2.指针数组的使用
那么,指针数组是怎么使用的呢?
看代码:
#include <stdio.h>
int main()
{
const char ch1[4] = "hao";
const char ch2[4] = "hao";
const char ch3[4] = "xue";
const char ch4[4] = "xi";
char* pch[5] = { ch1, ch2, ch3, ch4 };
for (int i = 0; i < 4; i++)
{
printf("%s\n", pch[i]);
}
return 0;
}
我们把ch[1,4]的地址都存到了pch里面,在通过ch[1,4]的地址,打印出字符串。
这就是指针数组的用法。
三、数组指针
上面刚刚介绍了指针数组,现在让我们来了解一下什么是数组指针。
数组指针是指针还是数组呢?
答案是:指针。
整型指针:int* ps:能够指向整型数据的指针
字符指针:char* ps:能够指向字符数据的指针
同理
那数组指针就应该是能够指向数组的指针
1.数组指针的介绍
int *ps[10]:是指针数组
int (*ps)[10]:是数组指针
ps先与结合,说明ps是一个指针变量,可以用来存放地址,在与[]结合,说明数组,所以,ps是一个指针,指向一个数组,叫数组指针
这里要注意:[]的优先级高于的优先级,所以要加上()
数组指针指向的是数组,那数组指针中存放的应该是数组的地址。
2.&数组名和数组名
&数组名是什么?
数组名又是什么?
#include <stdio.h>
int main()
{
int arr[10] = { 0 };
printf("数组名:%p\n", arr);
printf("&数组名:%p\n", &arr);
return 0;
}
从代码结果中,可以看到数组名的地址和&数组名是一样的,我们知道数组名代表的是首元素的地址,那么&数组名也代表的是数组首元素的地址吗?让我们再看一个代码:
#include <stdio.h>
int main()
{
int arr[10] = { 0 };
printf("数组名:%p\n", arr);
printf("&数组名:%p\n", &arr);
printf("数组名+1:%p\n", arr + 1);
printf("&数组名+1:%p\n", &arr + 1);
return 0;
}
数组名+1和&数组名+1的结果会是一样的吗?
很显然,不一样,数组名+1-数组名=4
&数组名+1-&数组名=40
,这个40哪里来的呢?是sizeof(arr)
为什么&数组名+1跨过了整个数组呢?实际上,&arr代表的是数组的地址,而不是数组首元素的地址,数组的地址+1才能跨过整个数组,而数组某一元素+1跨过的是一个元素的大小,这就是为什么&数组名+1-&数组名=40的原因
3.数组指针的使用
前面简单的说了数组指针,数组指针是怎么使用的呢?
#include <stdio.h>
int main()
{
int arr[3] = { 6 ,6 ,6 };
int(*parr)[3] = &arr;//把arr的地址取出来赋给parr,
for (int i = 0; i < 3; i++)
{
printf("%d ", (*parr)[i]);
}
return 0;
}
这样的代码少写,这样写没有任何的实际意义。
看下面的代码:
#include <stdio.h>
//这里的int(*parr)[3]等价于int arr[3][3]
void my_print(int(*parr)[3], int col, int row)
{
for (int i = 0; i < row; i++)
{
for (int j = 0; j < col; j++)
{
printf("%d ", parr[i][j]);
}
puts("");
}
puts("");
for (int i = 0; i < row; i++)
{
for (int j = 0; j < col; j++)
{
printf("%d ", *(*(parr + i) + j));
}
puts("");
}
}
int main()
{
int arr[3][3] = { 1,2,3,4,5,6,7,8,9 };
//数组名表示首元素的地址
//在二维数组中,数组名表示第一行的地址
//所以这里传arr,传的其实是二维数组第一行的地址,
//可以拿指针来接收
my_print(arr, 3, 3);
return 0;
}
这个写法 *(*(parr + i) + j)
表示什么意思呢?在这里,parr表示的是二维数组第一行的地址,第一行的地址+1表示二维数组第二行的地址,+n表示第n-1行的地址,对(parr + i)
进行解引用操作*(parr + i)
,找到的是某一行的第一个元素的地址,在+j,就能找到某一个元素的地址了 (*(parr + i) + j)
在进行解引用操作*(*(parr + i) + j)
,就可以精确到某一元素了。
四、函数指针
函数指针是什么呢?为什么会有函数指针这一概念呢?
首先看一段代码:
#include <stdio.h>
int add(int a, int b)
{
return a + b;
}
int main()
{
printf("add :%p\n", add);
printf("&add:%p\n", &add);
return 0;
}
可以看出,add和&add是一样的,都是函数add的地址,既然是地址,那就可以用指针进行保存,怎么保存呢?这就引出了函数指针的概念。
#include <stdio.h>
int add(int a, int b)
{
return a + b;
}
int main()
{
int (*pf)(int, int) = &add;
printf("(*pf)(1,2)=%d\n", (*pf)(1, 2));
printf("add(1,2)=%d\n", add(1, 2));
return 0;
}
这是一个很新颖的用法,把函数add的地址传给pf,利用指针把函数的地址存起来,通过这个指针去调用这个函数。
五、函数指针数组
数组是用来存放相同类型数据的存储空间,前面我们介绍过字符数组,整型数组,指针数组,那么也有函数指针数组这一概念。
函数指针数组就是,把函数的地址存到一个数组中,那个数组就叫函数指针数组,函数指针数组该如何定义呢?
int (*pf[10])(int,int....);
pf先和[]结合,说明pf是数组,数组的内容就是int (*)()类型的函数指针
看下代码就知道函数指针的用法了:
这是一个简单的计算器,不具有高精度,想了解高精度的点击传送门
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
void menu()
{
printf("1.Add 2.Sub 3.Mul 4.Div");
}
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 = 0;
int(*parr[5])(int, int) = { 0, Add,Sub,Mul, Div };//这个就是函数指针数组,数组里面存放的是函数,
//通过下标就能访问函数
do
{
menu();
printf("选择");
scanf("%d", &input);
if (input <= 5 && input >= 1)
{
printf("请输入数字");
scanf("%d %d", &x, &y);
int ret = parr[input](x, y);
printf("%d\n", ret);
}
else if (input == 0)
{
printf("退出");
}
else
{
printf("出错");
}
} while (input);
return 0;
}
其实还有一个指向函数指针数组的指针的概念,在这里就不多做介绍了。
六、回调函数
回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个
函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数
的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进 行响应。
给大家看一个代码
//这里面用到了回调函数
#include <iostream>
#include <time.h>
#include <stdlib.h>
using namespace std;
#define N 10
int score = 0;
int add(int x, int y)
{
cout << x << "+" << y << " =";
return x + y;
}
int sub(int x, int y)
{
cout << x << "-" << y << " =";
return x - y;
}
int mul(int x, int y)
{
cout << x << "*" << y << " =";
return x * y;
}
int div_(int x, int y)
{
cout << x << "/" << y << " =";
return x / y;
}
void shuru(int (*pf)(int,int))//这里用函数指针来接收函数的地址,然后通过pf就能调动函数
{//shuru函数就是一个回调函数,,如果不用这个函数,那么代码会增加很多
int x = rand() % 100 + 1;
int y = rand() % 100 + 1;
int ret = (*pf)(x,y);//在这里就调用了函数
int num = 0;
cin >> num;
cout << " ";
if (ret == num)
cout << "对" << endl, score += 10;
else
cout << "错" << endl;
}
int main()
{
int x, y, num;
int count = 0;
srand((unsigned int)time(NULL));
for (int i = 1;; i++)
{
int suiji = rand();
if (suiji % 10 == 0)
{
count++;
shuru(&add);//传函数的地址
}
else if (suiji % 10 == 1)
{
count++;
shuru(&sub);//传函数的地址
}
else if (suiji % 10 == 2)
{
count++;
shuru(&mul);//传函数的地址
}
else if (suiji % 10 == 3)
{
count++;
shuru(&div_);传函数的地址
}
if (count == N)
{
break;
}
}
cout << "总分" << score << endl;
return 0;
}
在这里,给大家展示一个C语言排序函数:qsort
qsort有四个参数,1.待排序数组的首元素的地址。2.待排序数组的元素个数。3.带排序元素的每个元素的大小,单位是字节。4.函数指针,比较两个元素的所用函数的地址,函数的参数是:带比较的两个元素的地址。
qsort的排序机制是快速排序,有不懂的小伙伴可以点击传送门学习一下快速排序。
#include <stdio.h>
#include <stdlib.h>
int cmp(const void* a ,const void* b)
{
return *(int*)b - *(int*)a;
}
int main()
{
int arr[9] = { 7,4,1,74,1,747,4741,741,7474741 };
int sz = sizeof(arr) / sizeof(arr[0]);
qsort(arr, sz, sizeof(arr[0]), cmp);
for (int i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
return 0;
}
这里是降序排序,也可以升序排序,只需要把cmp函数里面的返回值换成*(int*)a - *(int*)b;
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct STU
{
char name[20];
int length;
}S;
int cmp_name(const void* a, const void* b)
{
return strcmp(((S*)a)->name, ((S*)b)->name);
}
int main()
{
S student[3] = { {"zhangsan",20}, {"lisi", 50}, {"wangwu", 33} };
int sz = sizeof(student) / sizeof(student[0]);
qsort(student, sz, sizeof(student[0]), cmp_name);
for (int i = 0; i < 3; i++)
{
printf("%s %d ", student[i].name, student[i].length);
}
return 0;
}
这是给结构体排序,以名字排的序
再以身高排个序,把排序这个回调函数改变一就行
int cmp_length(const void* a, const void* b)
{
return((S*)a)->length - ((S*)b)->length;
}
注:给字符排序不能直接用-,需要用strcmp,专门用于比较字符串的函数
注:本文不解释qsort的底层实现原理,若感兴趣的话,可以自己模拟实现一下,在这里给点提示:1.要想实现qsort的底层,就得考虑什么类型能够存任意类型的地址,(void*)可以,void可以存任意类型的地址,可以把void比作万能钥匙,什么门都能打开。2.元素的个数,决定了要给多少个元素排序。3.每个元素的大小,每个类型有每个类型的大小,int4个字节,double8字节,那么,怎样才能去访问任意类型呢?可以用char类型,char是一个字节,char*+1访问的是第二个字节,这样就能访问任意类型了,只要知道带排序元素的大小即可。4.写一个比较的函数。
总结
以上就是对指针进阶的讲解,希望对大家有所帮助。