当前位置:网站首页>抽丝剥茧C语言(高阶)指针的进阶
抽丝剥茧C语言(高阶)指针的进阶
2022-07-07 03:31:00 【ℳℓ白ℳℓ夜ℳℓ】
指针的进阶
1. 字符指针
在指针的类型中我们知道有一种指针类型为字符指针 char*
一般使用:
int main()
{
char ch = 'w';
char *pc = &ch;
*pc = 'w';
return 0;
}
还有一种使用方式如下:
int main()
{
const char* pstr = "hello baiye.";//这里是把一个字符串放到pstr指针变量里了吗?
printf("%s\n", pstr);
return 0;
}
代码 const char* pstr = “hello baiye.”;
特别容易让我们以为是把字符串 hello bit 放到字符指针 pstr 里了,但是/本质是把字符串 hello baiye. 首字符的地址放到了pstr中。
上面代码的意思是把一个常量字符串的首字符 h 的地址存放到指针变量 pstr 中。
2. 指针数组
之前我们介绍过了,这里就不多说了。
int* arr1[10]; //整形指针的数组
char *arr2[4]; //一级字符指针的数组
char **arr3[5];//二级字符指针的数组
3. 数组指针
3.1 数组指针的定义
数组指针是指针?还是数组?
答案是:指针。
我们已经熟悉:
整形指针: int * pint; 能够指向整形数据的指针。
浮点型指针: float * pf; 能够指向浮点型数据的指针。
那数组指针应该是:能够指向数组的指针。
下面代码哪个是数组指针?
int *p1[10];
int (*p2)[10];
//p1, p2分别是什么?
P1:是指针数组。
P2:
//解释:p先和*结合,说明p是一个指针变量,然后指着指向的是一个大小为10个整型的数组。所以p是一个指针,指向一个数组,叫数组指针。
//这里要注意:[ ]的优先级要高于*号的,所以必须加上()来保证p先和*结合。
3.2 &数组名VS数组名
我们来看一段代码:
#include <stdio.h>
int main()
{
int arr[10] = {
0};
printf("%p\n", arr);
printf("%p\n", &arr);
return 0;
}
arr 和 &arr 分别是啥?
我们知道arr是数组名,数组名表示数组首元素的地址。
那&arr数组名到底是啥?
我们先来运行一下看看结果:
可见数组名和&数组名打印的地址是一样的。
难道两个是一样的吗?
我们再看一段代码:
#include <stdio.h>
int main()
{
int arr[10] = {
0 };
printf("arr = %p\n", arr);
printf("&arr= %p\n", &arr);
printf("arr+1 = %p\n", arr + 1);
printf("&arr+1= %p\n", &arr + 1);
return 0;
}
运行结果:
根据上面的代码我们发现,其实&arr和arr,虽然值是一样的,但是意义应该不一样的。
实际上: &arr 表示的是数组的地址,而不是数组首元素的地址。(细细体会一下)
本例中 &arr 的类型是: int(*)[10] ,是一种数组指针类型
数组的地址+1,跳过整个数组的大小,所以 &arr+1 相对于 &arr 的差值是40.
3.3 数组指针的使用
那数组指针是怎么使用的呢?
既然数组指针指向的是数组,那数组指针中存放的应该是数组的地址。
看代码:
#include <stdio.h>
int main()
{
int arr[10] = {
1,2,3,4,5,6,7,8,9,0 };
int(*p)[10] = &arr;//把数组arr的地址赋值给数组指针变量p
//但是我们一般很少这样写代码
return 0;
}
一个数组指针的使用:
#include <stdio.h>
void print_arr1(int arr[3][5], int row, int col)
{
int i = 0;
int j = 0;
for (i = 0; i < row; i++)
{
for (j = 0; j < col; j++)
{
printf("%d ", arr[i][j]);
}
printf("\n");
}
}
void print_arr2(int(*arr)[5], int row, int col)
{
int i = 0;
int j = 0;
for (i = 0; i < row; i++)
{
for (j = 0; j < col; j++)
{
printf("%d ", arr[i][j]);
}
printf("\n");
}
}
int main()
{
int arr[3][5] = {
1,2,3,4,5,6,7,8,9,10 };
print_arr1(arr, 3, 5);
//数组名arr,表示首元素的地址
//但是二维数组的首元素是二维数组的第一行
//所以这里传递的arr,其实相当于第一行的地址,是一维数组的地址
//可以数组指针来接收
print_arr2(arr, 3, 5);
return 0;
}
让我们来看一下运行结果:
那么,这段代码是什么意思呢?
int (*parr3[10])[5];
首先parr3和[10]结合,这说明是一个数组,此时数组还缺少一个类型,那么剩下的int(*)[5]就是类型,总的来讲parr3是存放数组指针的数组。
4. 数组参数、指针参数
在写代码的时候难免要把【数组】或者【指针】传给函数,那函数的参数该如何设计呢?
4.1 一维数组传参
#include <stdio.h>
void test(int arr[])//ok?
{
}
void test(int arr[10])//ok?
{
}
void test(int* arr)//ok?
{
}
void test2(int* arr[20])//ok?
{
}
void test2(int** arr)//ok?
{
}
int main()
{
int arr[10] = {
0 };
int* arr2[20] = {
0 };
test(arr);
test2(arr2);
}
上面这些是都对的。
第一个是数组的写法,第二个[]里面的数字可有可无。
第三个是地址,之前说过数组名是首元素的地址,也是一个类型,所以可以。
第四个也是形参与实参相同。
第五个用二级指针接受是可以的,因为实参是指针数组,数组名是首元素的地址,而元素都是指针,我们也知道二级指针是存放一级指针的地址的,所以他们是一个类型。
4.2 二维数组传参
void test(int arr[3][5])//ok?
{
}
void test(int arr[][])//ok?
{
}
void test(int arr[][5])//ok?
{
}
//总结:二维数组传参,函数形参的设计只能省略第一个[]的数字。
//因为对一个二维数组,可以不知道有多少行,但是必须知道一行多少元素。
//这样才方便运算。
void test(int* arr)//ok?
{
}
void test(int* arr[5])//ok?
{
}
void test(int(*arr)[5])//ok?
{
}
void test(int** arr)//ok?
{
}
int main()
{
int arr[3][5] = {
0 };
test(arr);
}
第一个可以,因为传过去的是二维数组,用二维数组接受,所以可以。
第二个不行,因为二维数组能省略行,不能省略列。
第三个可以。
第四个不行,因为arr是代表二维数组的首元素地址,也代表是数组的第一行,也就是一维数组,所以不能用一个整型指针来接收。
第五个也不行,因为这是一个指针数组,不是一个类型。
第六个可以,因为是用一个数组指针来接收一维数组。
第七个不行,一维数组的地址不可能放进二级指针里。
4.3 一级指针传参
#include <stdio.h>
void print(int* p, int sz)
{
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d\n", *(p + i));
}
}
int main()
{
int arr[10] = {
1,2,3,4,5,6,7,8,9 };
int* p = arr;
int sz = sizeof(arr) / sizeof(arr[0]);
//一级指针p,传给函数
print(p, sz);
return 0;
}
运行结果:
4.4 二级指针传参
#include <stdio.h>
void test(int** ptr) {
printf("num = %d\n", **ptr);
}
int main()
{
int n = 10;
int*p = &n;
int **pp = &p;
test(pp);
test(&p);
return 0; }
运行结果是:
5. 函数指针
那么,函数也一定有地址,也有相应的指针来接收函数的地址。
首先看一段代码:
#include <stdio.h>
void test()
{
printf("hehe\n");
}
int main()
{
printf("%p\n", test);
printf("%p\n", &test);
return 0;
}
代码运行结果:
输出的是两个地址,这两个地址是 test 函数的地址。
也就说明函数名就是函数的地址。
那我们的函数的地址要想保存起来,怎么保存?
下面我们看代码:
void test()
{
printf("hehe\n");
}
//下面pfun1和pfun2哪个有能力存放test函数的地址?
void (*pfun1)();
void* pfun2();
首先,能给存储地址,就要求pfun1或者pfun2是指针,那哪个是指针?
答案是:
pfun1可以存放。pfun1先和*结合,说明pfun1是指针,指针指向的是一个函数,指向的函数无参
数,返回值类型为void。
6. 函数指针数组
数组是一个存放相同类型数据的存储空间,那我们已经学习了指针数组,
比如:
int *arr[10];
//数组的每个元素是int*
那要把函数的地址存到一个数组中,那这个数组就叫函数指针数组,那函数指针的数组如何定义呢?
int (*parr1[10])();
int *parr2[10]();
int (*)() parr3[10];
答案是:parr1
parr1 先和 [] 结合,说明 parr1是数组,数组的内容是什么呢?
是 int (*)() 类型的函数指针。
至于函数指针的用途:
写一个计算器
#include <stdio.h>
int add(int a, int b) {
return a + b;
}
int sub(int a, int b) {
return a - b;
}
int mul(int a, int b) {
return a * b;
}
int div(int a, int b) {
return a / b;
}
int main()
{
int x, y;
int input = 1;
int ret = 0;
int(*p[5])(int x, int y) = {
0, add, sub, mul, div }; //转移表
while (input)
{
printf("*************************\n");
printf(" 1:add2:sub \n");
printf(" 3:mul4:div \n");
printf("*************************\n");
printf("请选择:");
scanf("%d", &input);
if ((input <= 4 && input >= 1))
{
printf("输入操作数:");
scanf("%d %d", &x, &y);
ret = (*p[input])(x, y);
}
else
printf("输入有误\n");
printf("ret = %d\n", ret);
}
return 0;
}
这里我们看到,选择运算法后,也就等于选择到了数组里面的函数指针,直接就到了上面的运算函数中。
这样比较方便。
7. 指向函数指针数组的指针
指向函数指针数组的指针是一个指针,
指针指向一个 数组 ,数组的元素都是函数指针 ;
如何定义?
#include <stdio.h>
void test(const char* str)
{
printf("%s\n", str);
}
int main()
{
//函数指针pfun
void (*pfun)(const char*) = test;
//函数指针的数组pfunArr
void (*pfunArr[5])(const char* str);
pfunArr[0] = test;
//指向函数指针数组pfunArr的指针ppfunArr
void (*(*ppfunArr)[5])(const char*) = &pfunArr;
return 0;
}
哈哈,这就是套娃
8. 回调函数
回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个
函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数
的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进
行响应。
首先演示一下qsort(点击这里可详细查看qsort函数)函数的使用:
#include <stdio.h>
#include <stdlib.h>
//qosrt函数的使用者得实现一个比较函数
int int_cmp(const void* p1, const void* p2)
{
return (*(int*)p1 - *(int*)p2);
}
int main()
{
int arr[] = {
1, 3, 5, 7, 9, 2, 4, 6, 8, 0 };
int i = 0;
qsort(arr, sizeof(arr) / sizeof(arr[0]), sizeof(int), int_cmp);
for (i = 0; i < sizeof(arr) / sizeof(arr[0]); i++)
{
printf("%d ", arr[i]);
}
printf("\n");
return 0;
}
代码的运行结果:
这里我们可以用冒泡排序和回调函数模拟实现qsort函数。
下面的void*是没有具体类型的指针,作用是可以接收任何类型的指针,也可以赋给任何类型的指针。
#include <stdio.h>
int int_cmp(const void* p1, const void* p2)
{
return (*(int*)p1 - *(int*)p2);
}
void _swap(void* p1, void* p2, int size)
{
int i = 0;
for (i = 0; i < size; i++)
{
char tmp = *((char*)p1 + i);
*((char*)p1 + i) = *((char*)p2 + i);//用char类型的指针因为长度为1个字节,无论我们要int类型还是ling类型都是一个字节一个字节相加的。
*((char*)p2 + i) = tmp;
}
}
void bubble(void* base, int count, int size, int(*cmp)(void*, void*))//这里就用了回调函数。
{
int i = 0;
int j = 0;
for (i = 0; i < count - 1; i++)
{
for (j = 0; j < count - i - 1; 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[] = {
1, 3, 5, 7, 9, 2, 4, 6, 8, 0 };
int i = 0;
bubble(arr, sizeof(arr) / sizeof(arr[0]), sizeof(int), int_cmp);
for (i = 0; i < sizeof(arr) / sizeof(arr[0]); i++)
{
printf("%d ", arr[i]);
}
printf("\n");
return 0;
}
代码的运行结果如下:
结束语
这里我们C语言的指针就结束了,因为之前有一篇指针的基础章,所以本章的字数偏少。
请大佬们指点错误和不足,如果觉得文章不错请家人们点个赞吧!!!
边栏推荐
- Pass parent component to child component: props
- Fast quantitative, abbkine protein quantitative kit BCA method is coming!
- Tujia, muniao, meituan... Home stay summer war will start
- A slow SQL drags the whole system down
- Sqlserver multithreaded query problem
- Introduction to abnova's in vitro mRNA transcription workflow and capping method
- Jetpack compose is much more than a UI framework~
- Big coffee gathering | nextarch foundation cloud development meetup is coming
- [noi simulation] regional division (conclusion, structure)
- Explain Bleu in machine translation task in detail
猜你喜欢
Implementation of AVL tree
RuntimeError: CUDA error: CUBLAS_ STATUS_ ALLOC_ Failed when calling `cublascreate (handle) `problem solving
Non empty verification of collection in SQL
$parent(获取父组件) 和 $root(获取根组件)
DHCP路由器工作原理
"Xiaodeng in operation and maintenance" meets the compliance requirements of gdpr
transform-origin属性详解
main函数在import语句中的特殊行为
Can 7-day zero foundation prove HCIA? Huawei certification system learning path sharing
LVS+Keepalived(DR模式)学习笔记
随机推荐
$refs:组件中获取元素对象或者子组件实例:
How can flinksql calculate the difference between a field before and after update when docking with CDC?
How can brand e-commerce grow against the trend? See the future here!
FullGC问题分析及解决办法总结
Prime partner of Huawei machine test questions
Chinese and English instructions prosci LAG-3 recombinant protein
From zero to one, I will teach you to build the "clip search by text" search service (2): 5 minutes to realize the prototype
MySQL SQL的完整处理流程
The startup of MySQL installed in RPM mode of Linux system failed
Reflection (II)
Learning records on July 4, 2022
Lm11 reconstruction of K-line and construction of timing trading strategy
Abnova circulating tumor DNA whole blood isolation, genomic DNA extraction and analysis
Special behavior of main function in import statement
关于数据库数据转移的问题,求各位解答下
Libcurl returns curlcode description
Asynchronous components and suspend (in real development)
详解机器翻译任务中的BLEU
.net core 访问不常见的静态文件类型(MIME 类型)
Matlab tips (29) polynomial fitting plotfit