🚀write in front🚀
🔎大家好,我是謓泽,希望你看完之后,能对你有所帮助,不足请指正!共同学习交流🔎
🏅2021年度博客之星物联网与嵌入式开发TOP5~2021博客之星Top100~阿里云专家^星级博主~掘金 || InfoQ创作者~周榜34»总榜2815🏅
🆔本文由 謓泽 原创 CSDN首发🙉如需转载还请通知⚠
📝个人主页:打打酱油desuCSDN博客💬
🎁欢迎各位→点赞👍 + 收藏⭐️ + 留言📝
📣系列专栏:【C】系列_打打酱油desu-CSDN博客〇~①🎓
✉️我们并非登上我们所选择的舞台,演出并非我们所选择的剧本📩
✨⒉万字带你玩转高阶指针『0»1』目录✨
在指针当中的类型中我们知道有一种指针类型为字符串指针 char*; 可以通过两种方法来访问一个字符或者是字符串。 1、第一种就是使用字符数组来存放字符串或者字符来实现操作。 2、下面所介绍的就是使用字符指针指向一个字符串,此时可不能定义数组。
一般的使用方法如下 👇
#include<stdio.h>
int main(void)
{
char ch = 'w';
char *pc = &ch;
*pc = 'w';
return 0;
}
字符型指针方法如下 👇
#include<stdio.h>
int main(void)
{
char *str = "hello C";
printf("%s\n", str);
return 0;
}
运行结果🖊 hello C 如上示例 👆 定义了字符型指针变量 str,用字符串常量"hello C"为其进行赋初值。 注意🔥:这里并不是把"hello C"中的所有字符存放在 str 当中,只是把该字符串中的第一个字符'h'赋值给指针变量 str,然后printf()再通过'h'找到下一个元素直到遇到'\0'则停止。 本质上是把"hello C"这个字符串的首地址存储在了 str 当中。 如下图所示 👇
常量字符串顾名思义:就是不能被改变的字符/串。 如下示例代码 👇
#include<stdio.h>
int main(void)
{
char *str = "hello C";
char arr[] = "hello C";
*str = 'w';
arr[0] = 'w';
printf("%s\n", str);
printf("%s\n", arr);
return 0;
}
上述代码所示 👆 这里的*str指针变量指向这个字符串,严格意义上来说是指向这个字符串的起始地址。 *str 是不能改变字符串当中的值的,最终程序会"挂掉"。 arr 数组是可以改变字符串当中的值。 用调试当中的监视可以证明上述例子!
代码崩溃的原因是告诉我们,当我们程序在写入对应的内存空间的时候发生了访问冲突。上述图中的报错才是真正的报错因为它是由系统来约束你的。这个字符串常量才是真正意义上的不可被修改。 💥重点核心:常量字符串是不能被改变的,在内存当中的地址也是一样不能被改变的。
一个数组的元素值为指针则是指针数组,指针数组是一组有序的指针的集合。 指针数组的所有元素都必须是具有相同存储类型和指向相同数据类型的指针变量。 那么指针数组是怎么样的呢,如下代码所示 👇
int* arr[5];//是什么?
arr 是一个数组,有⑤个元素,每个元素是一个整形指针,是一个存放整形指针的数组。
💥注意:除了每个元素的不同,而指针数组和普通数组在其它方面其实都是一样的!
#include <stdio.h>
int main(void)
{
int a = 12, b = 17, c = 55;
int *arr[3] = { &a, &b, &c };
int **parr = arr;
printf("%d, %d, %d\n", *arr[0], *arr[1], *arr[2]);
printf("%d, %d, %d\n", **(parr), **(parr+1), **(parr + 2));
return 0;
}
运行结果 👇
上述运行结果都是相同的。
上述代码解释 👆 arr 是一个指针数组,它包含了 3 个元素,每个元素都是一个指针,在定义 arr 的同时,我们使用变量 a、b、c 的地址对它进行了初始化,这和普通数组是多么地类似。 parr 是指向数组 arr 的指针,确切地说是指向 arr 第 0 个元素的指针(首元素的地址),它的定义形式应该理解为
int *(*parr)
,括号中的*
表示 parr 是一个指针,括号外面的int *
表示 parr 指向的数据的类型。arr 第 0 个元素的类型为 int *,所以在定义 parr 时要加两个 *。 第一个 printf() 语句中,arri 表示获取第 i 个元素的值,该元素是一个指针,还需要在前面增加一个 *(解引用) 才能取得它指向的数据,也即 *arri 的形式 第二个 printf() 语句中,parr+i 表示第 i 个元素的地址,*(parr+i) 表示获取第 i 个元素的值(该元素是一个指针),**(parr+i) 表示获取第 i 个元素指向的数据 指针还可以与字符串相互结合进行使用,如下代码所示 👇
#include <stdio.h>
int main(void)
{
char *arr[3] = { "C", "CSDN", "Electricity" };
printf("%s <==> %s <==> %s\n", arr[0], arr[1], arr[2]);
return 0;
}
需要注意的是,字符数组 str 中存放的是字符串的首地址,不是字符串本身,字符串本身位于其他的内存区域,和字符数组是分开的。 也只有当指针数组中每个元素的类型都是char *时,才能像上面那样给指针数组赋值,其他类型不行。 改成下面的形式,与上面的数组是等价的,如下代码所示 👇
#include <stdio.h>
int main(void)
{
char *str0 = "C";
char *str1 = "CSDN";
char *str2 = "Electricity";
char **str[3] = { str0, str1, str2 };
printf("%s <==> %s <==> %s", str[0], str[1], str[2]);
return 0;
}
运行结果:C <==> CSDN <==> Electricity
接下来,演示下指针数组的应用场景,情况。 如下代码所示 👇
#include<stdio.h>
int main(void)
{
int a[5] = { 1, 2, 3, 4, 5 };
int b[5] = { 6, 7, 8, 9, 10 };
int c[5] = { 11, 12, 13, 14, 15 };
int *pa[3] = { a, b, c };
for (int i = 0; i < 3; i++)
{
int j = 0;
for (j = 0; j < 5; j++)
{
printf("%-2d ",pa[i][j]);
}
printf("\n");
}
return 0;
}
运行结果如下所示 👇
💥重点核心:指针数组本质上其实是个数组,数组中存放的是指针(地址) 而已😅
数组是一系列具有相同类型的数据的集合,每一份数据叫做一个数组元素。数组中的所有元素在内存中是连续排列的,整个数组占用的是一块内存。那么以下列代码为例子,我们来分布图如下可进行观察 🧐
int arr[] = {1,2,3,4,5};
定义数组时,要给出数组名和数组长度,数组名可以认为是一个指针,它指向数组的第 0 个元素。在C语言中,我们将第 0 个元素的地址称为数组的首地址。以上面的数组为例,下图是 arr 的指向:👇
数组指针是一种指向数组的指针,这句话在我看来是很重要的你能理解透彻的话,我觉得你的数组指针能上一个台阶多看多读几遍🥰 那么指针数组是怎么样的呢,如下代码所示 👇
int (*p)[10];
从这里我们不难去发现,原来指针数组和数组指针形成根本问题就在于操作符大小的优先级关系所在,所以当你定义变量的时候一定要去注意这个问题,不然的话就会有根本性质的不同。
int arr[10] = {0};
int (*p)[10] = &arr;
上述代码当中取出的是数组的地址,注意取地址是取出数组所有的地址。arr 才是数组名首元素的地址相当于是 arr0,而上述代码中取地址是不一样的,这个要区分开来。 💥重点核心:在上面的代码中 p 就是一个数组指针,它是可以存放数组的地址的! 再举出一个例子,如下代码所示 👇
double* d[10];
double* (*pa)[10] = &d;
首先 pa 是个指针*,然后先把 (*pa) 先结合。pa 指向的是数组,指向的是d的数组有10个元素,所以这里一定是要写10个元素。切记! 💥注意:这个地方只能是10个元素,因为指向的数组它就是10个元素。 这样的话 pa 是一个指针指向的数组是10个元素,那么它所指向的类型是double*类型的。所以要在前面+上double*,所以,数组指针 p 指向的类型也必须要是 double* 类型的才可以。以上 pa 就 是 数组指针,和上面 p 都是属于是数组指针类型的。 不知道,你看完之后明白了 数组指针 和 指针数组 了没有,总之我刚学的时候真的是一脸懵逼。这两个总是能够搞混淆😅还放弃了不想学指针的念头哈哈,但是这是绝对不可以的 🤐
&数组名:取地址是取出它数组的地址 数组名 :数组名仅仅是表示它首元素的地址 如下代码所示 👇
int arr[10] = {0};
int* p1 = arr;
int (*p2)[10] = &arr;
在上述代码所示👆 p1 只需要是 指针数组 就可以了 p2 它却需要的是 数组指针 就可以了 虽然,它们最后打印出来的值是一模一样的,但是他们的类型终究是不一样的。 如下代码所示 👇
#icnlude<stdio.h>
int main(void)
{
int arr[10] = {0};
int* p1 = arr;
int (*p2)[10] = &arr;
printf("p1 = %p\n",p1);
printf("p1+1 = %p\n",p1+1);
printf("%p2 = %p\n",p2);
printf("%p2+1= %p\n",p2+1);
return 0;
}
指针类型决定指针+1到底+多少 p1 是一个整形指针+1,它会跳过④个字节。 p2 是一个数组指针,p2指向的是一个数组。于是,p2+1就应该是跳过一个数组,指向对象的数组。 运行结果如下所示 👇
第一行的编译结果与第二行的编译结果相差了④个字节,一个整形类型。 第三行的编译结果与第四行的编译结果相差了40个字节,十个整形类型。 💥重点核心:以后取地址数组名的话,一定要是数组指针! 注意⇢数组指针的第二个下标不能被省略,如果省略了第二个下标的话。那么指针类型就不明确了。
①. sizeof(数组名) - 数组名表示整个数组,计算的是整个数组大小,单位是字节。 🔥注意:sizoof(数组名)这个是必须要单独存放的才算是表示数组的整个大小! ②. &数组名 - 数组名表示整个数组,取出的是整个数组的地址。 除了上述这两个,sizeof(数组名) 和 &数组名 其它的表示都是数组名(arr)首元素的地址。
示例代码如下👇
#include<stdio.h>
int main(void)
{
int arr[3][3] = { { 1, 2, 3 }, { 4, 5, 6 }, { 7, 10, 9 } };
int (*p)[3] = arr; //arr首元素(而是一个数组具有三个元素char arr[3])地址
printf("%d", *(p[2]+1));
return 0;
}
运行结果🖍 10
当然还有一种形式可以用指针变量来访问,如下代码所示👇
#include<stdio.h>
int main(void)
{
int arr[3][3] = { { 1, 8, 3 }, { 0, 5, 6 }, { 7, 10, 9 } };
int* ptr = (int*)arr;
printf("%d", *(ptr+4));
return 0;
}
运行结果🖍
5
💥注意: 使用数组指针的时候一般都不会在一维数组当中去使用!通常都是在二维数组中使用 大家可以思考下这是为什么,你可以试着去看看数组指针的形式在去思考🤔 那就假设我们先在一维数组当中去使用下,如下代码所示 👇
#include <stdio.h>
int main(void)
{
int arr[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
int(*pa)[10] = &arr;
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%d ", *((*pa) + i));
}
return 0;
}
上述代码👆,就是配合数组指针去使用的,可能小伙伴们看了会觉得很不自在我也觉得是,明明就可以用指针变量解决的一道题目,却还要用数组指针去解决。 指针变量解决,如下代码所示 👇
#include <stdio.h>
int main(void)
{
int arr[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
int* pa = arr;
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%d ",*pa+i);
}
return 0;
}
这样岂不是更方便吗?😜 接下来,我们来正确的使用数组指针。 如下代码所示 👇
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#define row 3
#define col 3
void print(int(*p)[col], int r, int c) //p是数组指针
{
int i, j;
for (i = 0; i < row; i++)
{
for (j = 0; j < col; j++)
{
printf("\t%d ", *(p[i] + j));
}
printf("\n");
}
}
int main(void)
{
int arr[3][3] = { { 1, 2, 3 }, { 4, 5, 6 }, { 7, 8, 9 } };
print(arr, row, col);
return 0;
}
编译运行结果 👇
💥注意:二维数组的数组名表示首元素的地址,第一行是二维数组首元素地址!因为是一维数组的地址,所以在形参当中使用了指向一维数组指针来去进行使用。而这样的一维数组+i的话相当于找到了第i行当中的地址,解引用相当于拿到了第 i 行的数组名。这又相当于这①行首元素的地址,然后再+上 j 的话就相当于 j 的地址。最后,再括起来解引用,就找到了第 i行下标为 j 的地址进行引用 🥳
int p;
这是一个普通的整型变量。
int *p;
首先从 p 处开始,先与 * 结合,所以说明 p 是一个指针, 然后再与 int 结合, 说明指针所指向的内容的类型为 int 型。所以 p 是一个返回整型数据的指针。
int p[3];
首先从 p 处开始,先与 [] 结合,说明 p 是一个数组, 然后与 int 结合, 说明数组里的元素是整型的, 所以 p 是一个由整型数据组成的数组。
int *p[3];
首先从 p 处开始, 先与 [] 结合,因为其优先级比 * 高,所以 p 是一个数组, 然后再与 * 结合, 说明数组里的元素是指针类型, 然后再与 int 结合, 说明指针所指向的内容的类型是整型的, 所以 p 是一个由返回整型数据的指针所组成的数组。
int (*p)[3];
首先从 p 处开始, 先与 * 结合,说明 p 是一个指针然后再与 [] 结合(与"()"这步可以忽略,只是为了改变优先级), 说明指针所指向的内容是一个数组, 然后再与int 结合, 说明数组里的元素是整型的。所以 p 是一个指向由整型数据组成的数组的指针。
int **p;
首先从 p 开始, 先与 * 结合, 说是 p 是一个指针, 然后再与 * 结合, 说明指针所指向的元素是指针, 然后再与 int 结合, 说明该指针所指向的元素是整型数据。由于二级指针以及更高级的指针极少用在复杂的类型中, 所以后面更复杂的类型我们就不考虑多级指针了, 最多只考虑一级指针。
int (*pa[10])[5];
首先 pa 和 [] 进行结合了, pa 所接收的数组是10个。这里的 pa 是一个存储数组指针的数组,该数组能够存放10个数组指针,每个数组指针能够指向①个数组,数组⑤个元素,每个元素是int类型。
用一级指针接收数组名(首元素地址),写个函数把一级指针变量传递过去。打印出 arr 数组名当中的内容。函数当中传参是一级指针,所以也被称之为一级指针的传参。那么在实参当中我们就可以拿指针变量去接收传参当中的一级指针。 💥重点核心:一级指针传参就用一级指针来进行接收! 示例,如下代码所示 👇
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
void print(int* p2, int sz)
{
printf("p2 = %p\n", p2);
printf("*p2 = %p\n", *p2);
printf("&(*p1) = %p\n", &(*p2));
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d ", *(p2 + i));
}
}
int main(void)
{
int arr[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
int sz = sizeof(arr) / sizeof(arr[0]);
int* p1 = arr;
printf("p1 = %p\n", p1);
printf("arr = %p\n", arr);
printf("&(*p1) = %p\n", &(*p1));//先获取值再转换地址
printf("*p1 = %p\n", *p1);
print(p1, sz);
return 0;
}
💥注意:好好看下它们之间地址的关系。
一→指针,即是变量的地址。
二→指针变量,它也是个变量,存放的是另外一个变量的地址。也可以说指针变量指向谁的变量地址。
三→解引用是指针。
取出 &a 的地址,存放到指针变量 pa,pa 此时此刻就是一级指针变量。对于一级指针变量来说,取出 pa 的地址就要用个二级指针来进行存放 ppa。这个时候我们把二级指针传参到自定义函数当中,这样我们就把二级指针给进行传参。形参当中那么就需要用到二级指针来接收。 💥注意:二级指针是专门存放一级指针变量的地址的。 示例,如下代码所示 👇
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
void print(int** ppa)
{
*ppa = 20;
}
int main(void)
{
int a = 0;
int *p = &a;
int **ppa = &p;
print(ppa);
printf("a = %d", p);
printf("a = %d", a);//a是不会改变的
return 0;
}
编译运行结果:p = 20 从上述结果可以得知,我们通过二级指针改变了原本整形指针 p 的结果。
从语法的角度看,你只要把指针声明语句里的指针名字去掉,剩下的部分就是这个指针的类型。这是指针本身所具有的类型。 1、int *p; 指针的类型是 int* 2、char *p; 指针的类型是 char* 3、int **p; 指针的类型是 int** 4、int (*p)2; 指针的类型是 int(*)2 5、int *(*p)4; 指针的类型是 int*(*)4
当你通过指针来访问指针所指向的内存区时,指针所指向的类型决定了编译器将把那片内存区里的内容当做什么来看待。 从语法上看,你只须把指针声明语句中的指针名字和名字左边的指针声明符*去掉,剩下的就是指针所指向的类型。 1、int*ptr; 指针所指向的类型是 int 2、char*ptr; 指针所指向的的类型是 char 3、int**ptr; 指针所指向的的类型是 int* 4、int(*ptr)3; 指针所指向的的类型是 int()3 5、int*(*ptr)4; 指针所指向的的类型是 int*()4 在指针的算术运算中,指针所指向的类型有很大的作用。
数组指针:是指向数组的指针。 函数指针:是指向函数的指针,存放函数地址的指针。 那么 & 地址函数名,取出的就是函数的地址。 从这里我们就联想到了 数组名 != &数组名,那么函数也是这样的吗? 答案:不是!函数名 == &函数名(完全等价) 示例,如下代码所示 👇
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
int add(int x,int y)
{
return x + y;
}
int main(void)
{
int a = 10;
int b = 20;
add(a,b);
printf("&函数名 - %p\n",&add);
printf(" 函数名 - %p\n",add);
return 0;
}
编译运行结果如下 👇
由此,证明了 函数名 == &函数名(完全等价) 那么指针所指向的类型就是函数返回值当中的类型。 当然,函数的地址也是可以取&出来,赋值给函数指针变量。 格式:函数返回值(*指针变量)(函数参数类型) 💥注意:这个函数指针变量是因为我们存放的地址是函数! 接着上面代码,示例,如下代码所示 👇
int (*ptr)(int,int) = &add;
这里的 ptr 就是函数指针变量。 那么这里 ptr 相当于存放函数的地址,如果我对 ptr 进行解引用的话就相当于找到了函数的地址。而找到这个函数我们需要把 (*ptr) 用小括号给括起来。调用的时候进行传参,所以需要在后面给上() 示例,如下代码所示 👇
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
int add(int x,int y)
{
return x + y;
}
int main(void)
{
int (*ptr)(int, int) = &add;
int ret = (*ptr)(3, 5);
printf("ret = %d\n", ret);
return 0;
}
编译运行结果👉 ret = 8 从上述代码中可以说明调用时✔的,木的问题。这个地方用的就是函数指针,用了一个函数指针去调用它所指向的函数。
int (*ptr)(int, int) = add;
当然,你这样子也是和上面是等价的。
int ret = ptr(3, 5);
以及这样子也都是没有问题的,运行结果都是一样的。 函数指针的应用 拓展示例如下👇
说函数指针数组的时候,先让我们来回顾下整形指针数组。
int* arr[5];
这是整形指针数组 — 就是这里面存放的都是整形指针。 那么函数指针数组 — 就是存放函数指针的数组。 示例,如下代码所示 👇
#include<stdio.h>
int add(int x, int y)
{
return x + y;
}
int sub(int x, int y)
{
return x - y;
}
int main(void)
{
int(*p1)(int, int) = &add;
int(*p2)(int, int) = ⊂
int(*Funtwo[2])(int, int) = { add, sub };//(13)
return 0;
}
int(*Funtwo[2])(int, int) = { add, sub };
int(*Funtwo[2])(int, int) = { &add, &sub };
这两种写法都是一样的、取地址和不取地址、在函数都是一样的。
解释上述代码👆:Funtwo首先和 进行结合,和 结合的时候就说明是个数组,数组是具有两个元素。那么当 Funtow2 被去掉的时候,剩下的就是函数指针了,int (*)(int,int); 加2是因为只是打算放入两个函数进去,那么③个④个都是一样以此类推......(13) 然后,就是对数组进行初始化。放入相应的函数进去即可!上述代码当中就放入了add、sub函数。 💥重点核心:函数指针数组里面就可以存放同类型的函数指针! (数据类型同、且能存放多个)
其实主要实现在当你创建的函数过多的时候,可以用函数指针数组来进行接收进行初始化。 例如假设你有多个函数的时候,当然单个函数也行。就假设你有多个函数的时候。 示例,如下代码所示 👇
int(*pa[4])(int,int) = {max1,max2,max3,max4};
那么就可以用函数指针对应的下标来存放多少的函数的地址了,其实和指针数组特别类似。
指向函数指针数组的指针是一共指针指针指向一共数组,数组的元素都是函数指针。 函数指针的数组本质上是数组,取出函数指针数组的地址。 整形数组,示例,如下代码所示 👇
int arr[3];
int(*pa1)[3] = &arr;
整形指针数组,示例,如下代码所示 👇
int* arr[2];
int*(*pa2)[2] = &arr;
💥注意:在这里 pa2 是一个指向 整形指针数组 的指针! 那么接下来来说说函数指针数组 指针,示例,如下代码所示 👇
int (*p1)(int,int);//函数指针
int (*p2[5])(int,int);//函数指针数组
int(*(*p3)[5])(int,int) = &p2;//取出的是函数指针数组的地址
p3 就是一个指向 函数指针的数组 的 指针! 这里无非就是在p3加了个指针,然后用小括号括起来。之所以要用小括号,因为p3 会和 进行结合。那么把 (*p3)5 去掉的话不就是函数指针了吗?你再一看(*p3)5不其实也是一个数组指针🤔 当然指向函数指针数组的指针这个东西知道就可以了,最主要的是去理解,不必深究,不然你将越套越深(doge)
回调函数就是一个通过函数指针调用的函数,如果你把函数的指针(地址)作为参数传递给另一个函数的时候,当这个指针被用来调用其所指向的函数时,我们就说这个是回调函数,回调函数不是由函数实现方直接调用,而是在特定的时间发生时由另外的一方进行调用的,用于对该事件或条件进行响应。 也就是 A函数 B函数(A函数的地址)解释:把A函数的地址传递给B函数,那么B函数的形参一定是A函数的指针,因为指针里面才能够存A函数的地址。然后通过A函数的指针返回来调用A函数。那么这个A函数不是直接调用的,而是通过A函数当中的指针变量存放的这个地址。再回去调用A函数的时候,这个时候称之为这种机制叫做:回调函数! 简单讲:回调函数是由别人的函数执行时调用你实现的函数,也就是回调函数必须借鉴一个函数的参数为函数指针才行。 示例,如下代码所示 👇
#include<stdio.h>
int Callback_1(int x)
{
printf(" Hello, this is Callback_1: x = %d\n ", x);
return x;
}
int Callback_2(int x)
{
printf("Hello, this is Callback_2: x = %d\n ", x);
return x;
}
int Callback_3(int x)
{
printf("Hello, this is Callback_3: x = %d\n ", x);
return x;
}
int Handle(int y, int(*Callback)(int))
{
return Callback(y);//进行调用
}
int main(void)
{
int a = 2;
int b = 4;
int c = 6;
Handle(a, Callback_1);
Handle(b, Callback_2);
Handle(c, Callback_3);
return 0;
}
编译运行结果👇 Hello, this is Callback_1: x = 2 Hello, this is Callback_2: x = 4 Hello, this is Callback_3: x = 6 💥注意:回调函数是通过一个函数指针再进行调用的函数。
嵌套函数实际上就是指在某些情况下,您可能需要将某函数作为另一函数的参数使用,这一函数就是嵌套函数。 那么接下来我就来举出一个例子,带大家看看嵌套函数的使用。示例代码如下 👇
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
void swap(int *p1, int *p2)
{
int temp;//创建临时变量
temp = *p1;
*p1 = *p2;
*p2 = temp;
}
void My_sort(int *pa, int *pb, int *pc)
{
//判断
if (*pa < *pb)
swap(pa, pb);//嵌套调用swap()
if (*pa < *pc)
swap(pa, pc);//注意:交换当中的形参值!
if (*pb < *pc)
swap(pb, pc);
}
int main(void)
{
//创建三个变量
int a, b, c;
printf("请输入三个数字:");
scanf("%d %d %d", &a, &b, &c);
//把三个变量赋值给指针变量
int *pa = &a;
int *pb = &b;
int *pc = &c;
//实现程序
My_sort(pa, pb, pc);
printf("排序:{< %d %d %d >}\n", a, b, c);
return 0;
}
编译运行结果👇 请输入三个数字:2 3 4 排序:{< 4 3 2 >} 在上面swap()其实也可以使用按位异或(^),也可以做到交换不用创建临时变量。 那么接下来我们来对本道程序进行下详细的讲解。 本程序创建了一个自定义函数 swap,swap()用于两个数字的交换。 除了 swap() 在程序当中还创建了一个My_sort 函数,其作用是将 3 个数字由大到小的进行排列。 在 My_sort 函数中调用的时候调用了前面自定义函数当中 swap 函数,这里的 swap 函数和 My_sort 函数都是以指针变量作为形式参数。程序在运行的时候,通过键盘的输入 3 个数字 a,b,c,分别将 a,b,c 的地址赋值给了 pa、pb、pc 分别用指针变量进行存放,这里之所以用指针变量而不直接 a,b,c 的值传递给 My_sort 函数当中是因为如果不用指针变量的方式最后返回的值依旧是原先输入的值不会有任何的改变,因为当它一出 swap() 函数就会销毁原先交换的值!当执行 swap(pa,pb)的时候,pa 也是指向了变量 a,pb 也是指向了变量 b! C语言在实参变量和形势变量之间的数据是单向的"值传递"方式。指针变量作为函数参数也是如此,调用函数不可能改变实参指针变量的值,但可以改变实参指针变量当中所指向的变量的大小。
C语言当中 qsort 函数 能够类似实现冒泡排序的效果。 在此说说什么是冒泡排序如下↓
qsort 函数的整体思路是用的是快速排序的方法,快速排序是对冒泡排序的一种改进 快速排序算法通过多次比较和交换来实现排序,其排序流程如下 Ⅰ:首先设定一个分界值,通过该分界值将数组分成左右两部分。 Ⅱ:将大于或等于分界值的数据集中到数组右边,小于分界值的数据集中到数组的左边。此时,左边部分中各元素都小于或等于分界值,而右边部分中各元素都大于或等于分界值。 Ⅲ:然后,左边和右边的数据可以独立排序。对于左侧的数组数据,又可以取一个分界值,将该部分数据分成左右两部分,同样在左边放置较小值,右边放置较大值。右侧的数组数据也可以做类似处理。 Ⅳ:重复上述过程,可以看出,这是一个递归定义。通过递归将左侧部分排好序后,再递归排好右侧部分的顺序。当左、右两个部分各数据排序完成后,整个数组的排序也就完成了。 qsort 函数详解:qsort 函数是什么类型的数据都可以进行排序,例如:整形数据、字符串数据、结构体数据,都是可以进行排序。如果是你写冒泡排序你就只能给①种数据类型进行排序,但是,在这里qsort函数就可以进行排任意的数据!
void qsort(void *base,//1
size_t nitems,//2
size_t size,//3
int (*compar)(const void *, const void*))//4
(1):base 👉 指向要排序的数组的第一个元素的指针。 (2):nitems 👉 由 base 指向的数组中元素的个数。 (3):size 👉 数组中每个元素的大小,以字节为单位。 (4):compar 👉 用来比较待排序数据种两个元素的函数。如果,返回的是大于0的数字表示第一个元素大于第二个元素、等于0的话就是表示第一个元素等于第二个元素、小于0的话就是第一个元素小于第二个元素。
qsort引用头文件 #include <stdlib.h> 题目:将arr数组当中元素进行排序,用qsort函数实现! 示例,如下代码所示 👇
#include<stdio.h>
#include<stdlib.h>
void print(int arr[], int sz)
{
int i;
for (i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
printf("\n");
}
int sort_max (const void* a1,const void* a2)
{
//函数内部实现
return *(int*)a1 - *(int*)a2;
}
int main(void)
{
int arr[10] = { 10,9,8,7,6,5,4,3,2,1 };
int sz = sizeof(arr) / sizeof(arr[0]);
print(arr,sz);//排序之前
qsort(arr, sz, sizeof(arr[0]), sort_max);
print(arr, sz);//排序之后
return 0;
}
编译运行结果👉 1 2 3 4 5 6 7 8 9 10 当然如果你想实现降序排列也是很容易的,只需要把用来比较待排序数据种两个元素的函数交换一下位置即可。例如:上面的 a1 和 a2只需要交换下位置就可以了,这样就可以实现我们的一个降序排列。 🔥降序排列 注:qsort() 排序默认是升序排序(从低到高),那么我们想要降序排列(从高到低)应该怎么办呢。实际上这也是很好解决的。示例代码如下↓
int sort_max (const void* a1,const void* a2)
{
//函数内部实现
return *(int*)a2 - *(int*)a1;
}
只需要改变上述两个值(a1、a2)的位置交换下就可以了。 那么我们接下来来实践下,把arr数组当中的元素改成如下↓
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
其余代码和上述一样不变,注:sort_max()自定义函数当中要交换(a1和a2)的值。 运行结果🖊
①. sizeof(数组名) - 数组名表示整个数组,计算的是整个数组大小,单位是字节√ ②. &数组名 - 数组名表示整个数组,取出的是整个数组的地址√ ③. 除此之外所有的数组名都是代表数组首元素的地址√ 💥注意:以上在32位平台上是4个字节,如果你是在64位的平台上那就是8个字节了。(在这里我们使用的是32位地址)
示例,如下代码所示 👇
int a[] = { 1, 2, 3, 4 };
printf("%d\n",sizeof(a)); //计算整个数组的地址。
printf("%d\n",sizeof(a+0)); //这个不是单独放在sizeof()的内部,而是把(a+0)给一起放进去了。所以它表示的是首元素的地址。
printf("%d\n",sizeof(*a)); //*a是数组的第一个元素,所以计算的是第一个元素的大小。
printf("%d\n",sizeof(a+1)); //(a+1)是计算第二个元素的地址。
printf("%d\n",sizeof(a[1]));//计算数组下标第二个元素的地址。
printf("%d\n",sizeof(&a)); //&a,虽然是数组的地址,但是也是地址。所以,sizeof(&a)计算的是一个地址的大小。
printf("%d\n",sizeof(*&a)); //取出a的地址再进行解引用,指针是能够指向数组的地址。
printf("%d\n",sizeof(&a+1));//数组后面空间的地址,跳过一个数组地址。
printf("%d\n", sizeof(&a[0]));//取出第一个元素下标的地址
printf("%d\n", sizeof(&a[0] + 1));//取出第一个元素下标地址再加1那么就是第二个元素的地址。
编译运行结果👇 16 4 4 4 4 4 16 4 4 4
示例,如下代码所示 👇
char arr[] = { 'a','b','c','d','e','f'};
printf("%d\n", sizeof(arr));//计算整个数组的地址,注意数据类型。
printf("%d\n", sizeof(arr + 0));//表示首元素的地址,注意字符所占空间大小是1个字节。但是它是地址所以是4个字节
printf("%d\n", sizeof(*arr));//数组指针进行解引用,找到元素a的地址。
printf("%d\n", sizeof(arr[1]));//数组名第下标找到第二个字节。
printf("%d\n", sizeof(&arr));//取地址arr,arr是个地址。
printf("%d\n", sizeof(&arr + 1));//取出数组arr的地址加1跳过arr数组到‘f’的后面
printf("%d\n", sizeof(&arr[0] + 1));//取出arr下标第一个地址,再加1找到第二个元素的地址
编译运行结果👇 6 4 1 1 4 4 4
示例,如下代码所示 👇 字符串长度头文件是#include<string.h>
char arr[] = { 'a', 'b', 'c', 'd', 'e', 'f' };
printf("%d\n", strlen(arr));
printf("%d\n", strlen(arr + 0));
printf("%d\n", strlen(*arr));
printf("%d\n", strlen(arr[1]));
printf("%d\n", strlen(&arr));
printf("%d\n", strlen(&arr + 1));
printf("%d\n", strlen(&arr[0] + 1));
编译运行结果👇 随机值 随机值 报错 - 不是合法地址 报错 - 不是合法地址 随机值 随机值 随机值 💥注意:上列数组是没有'\0',而'\0'是字符串长度的结束标志。
示例,如下代码所示 👇
char arr[] = "abcdef";
printf("%d\n", sizeof(arr));
printf("%d\n", sizeof(arr + 0));//首元素地址加0,还是首元素地址。
printf("%d\n", sizeof(*arr));//第一个元素'a'进行解引用。
printf("%d\n", sizeof(arr[1]));
printf("%d\n", sizeof(&arr));//arr取出的是整个数组的地址,但还是地址。
printf("%d\n", sizeof(&arr + 1));
printf("%d\n", sizeof(&arr[0] + 1));
编译运行结果👇 7 4 1 1 4 4 4 💥注意:这里是字符串数组所以当中是有'\0'!
示例,如下代码所示 👇 从起始位置开始遇到'\0'停止
char arr[] = "abcdef";
printf("%d\n", strlen(arr));
printf("%d\n", strlen(arr + 0));
printf("%d\n", strlen(*arr));
printf("%d\n", strlen(arr[1]));
printf("%d\n", strlen(&arr));
printf("%d\n", strlen(&arr + 1));
printf("%d\n", strlen(&arr[0] + 1));
编译运行结果👇 6 6 报错 - 不是合法地址 报错 - 不是合法地址 6 随机值 5
示例,如下代码所示 👇
char *p = "abcdef";
printf("%d\n", sizeof(p)); //算出的是指针变量的大小。
printf("%d\n", sizeof(p+1)); //p+1是是b的地址,但它还是个地址。
printf("%d\n", sizeof(*p)); //指针解引用首元素的地址
printf("%d\n", sizeof(p[0]));//当成数组的形式取访问等价*(p+0)
printf("%d\n", sizeof(&p)); //取地址p取的也是p的地址
printf("%d\n", sizeof(&p + 1));//取出p的地址加1跳过p的地址
printf("%d\n", sizeof(&p[0] + 1));//第一个元素地址+1就是'b'的地址
编译运行结果👇 4 4 1 1 4 4 4
示例,如下代码所示 👇
char *p = "abcdef";
printf("%d\n", strlen(p)); //p里面存放的是a的地址,向后数字符遇到'\0'就停下。
printf("%d\n", strlen(p + 1));//p+1是b的地址,向后数字符遇到'\0'就停下。
printf("%d\n", strlen(*p)); //报错 - 不是合法地址
printf("%d\n", strlen(p[0])); //报错 - 不是合法地址
printf("%d\n", strlen(&p)); //随机值
printf("%d\n", strlen(&p + 1));//随机值
printf("%d\n", strlen(&p[0] + 1));//取出第一个元素地址+1就是'b'的地址。
编译运行结果👇 6 5 报错 - 不是合法地址 报错 - 不是合法地址 随机值 随机值 5
示例,如下代码所示 👇
int a[3][4] = { 0 };
// (0)(1)(2)(3)
//(0) 0 0 0 0
//(1) 0 0 0 0
//(2) 0 0 0 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));//a[0]就是第一行第一个元素的地址,a[0]+1就是第一行第二个元素的地址。
printf("%d\n", sizeof(*(a[0] + 1)));//a[0]+1就是第一行第二个元素的地址,然后对其进行解引用整形类型元素。
printf("%d\n", sizeof(a + 1));//a代表的是首元素的地址,然后+1。
printf("%d\n", sizeof(*(a + 1)));//第二行的地址进行解引用
printf("%d\n", sizeof(&a[0] + 1));//取出第一行的地址然后加1就代表第二行的地址。
printf("%d\n", sizeof(*(&a[0] + 1)));//第二行地址,解引用计算第二行的地址。
printf("%d\n", sizeof(*a));//表示第一行首元素的地址进行解引用得到第一行
printf("%d\n", sizeof(a[3]));//这里可以推测类型,a[3]起始是第四行的数组名(如果有)即使不存在也可以通过类型计算大小。
编译运行结果👇 48 4 16 4 4 4 16 4 16 16 16
指针的安全问题很重要也就是在程序中如何使用,如果你能避免指针的危险性那么你指针就肯定学的还是不错的。指针固然是很好用的,但好用的同时又避免不了指针自身实际上是很危险的一个东西,所以我们在使用指针的操作一定要考虑指针的安全性避免程序挂掉或者造成崩溃。 我认为对指针的安全常见问题存在这③个问题 ①:野指针 ②:空指针 ③:字节(地址)的改变 以上这三种都是我认为我们会常见的一些问题所在,在博客当中我也都写到过。所以当我们在使用指针的时候应该思考下我所定义的这个指针是不是一个安全性高的指针。 再举出一些例子供大家参考。示例代码如下 👇
#include <stdio.h>
int main(void)
{
char str='c';
int *ptr=(int *)&str;
*ptr=10086;
printf("%d\n",ptr);
return 0;
}
解析代码如下👇: ①:指针 ptr 是一个 int * 类型的指针,它指向的类型是 int 。它指向的地址就是 str 的首地址,在32位系统为4字节,64位为8字节。 ②:改变了 str 所占的一个字节,还把和 str 相临的高地址方向的三个字节也改变了。这三个字节是干什么的?只有编译程序知道,而写程序的人是不太可能知道的。也许这三个字节里存储了非常重要的数据,也许这三个字节里正好是程序的一条代码,而由于你对指针的马虎应用,这三个字节的值被改变了!这会造成崩溃性的错误。(关键点在于强制改变char的类型导致取出的字节数发生改变,而这个是我们并不清楚的) 那么相信你对这个能完全掌握的话,对指针的理解会更上一层楼。 那么再举出一个例子供大家去理解:示例代码如下 👇
#include <stdio.h>
int main(void)
{
char a;
int *ptr = &a;
ptr++;
*ptr = 10086;
printf("%d\n", *ptr);
return 0;
}
解析代码如下👇: 这段代码实际上是可以运行成功的,但是它的漏洞实际上很多。就比如很明显的一点: 从char* 到 int* 实际上类型并不兼合。 ptr 对指针 ptr 进行自加1 运算后,ptr 指向了和整形变量a 相邻的高地址方向的一块存储区。这块存储区里是什么?这可出现了巨大的bug,如果一个项目这样的话,很有可能就会丢失一块重要的数据。 而*ptr = 10086,这里就更加离谱了,所以这个指针就近指向了哪里?
既然都看到这里了,还不上手做下关于指针的练习题吗(╹ڡ╹ ) 相信当你做完的时候会对指针理解会更加的深刻哟,当然一下几道是相较于来说比较基础。
题目→输入一个整形数组为10个元素,使指针累加起来数组所有元素之和。 本道题目的解题步骤实际上很容易,接下来说说解题思路↓ 根据要求先输入十个元素,使用循环遍历数组当中的所有的元素,最后再把所有的元素进行相加。那么实际上本道题目就完成了。简单明了(¬‿¬) 示例代码如下所示↓
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#define num 10
int main(void)
{
int sum = 0;//总和
int i = 0;
int* arr[num] = { 0 };
int* p = arr;
printf("请输入十个元素->:");
for (i = 0; i < num; i++)
{
scanf("%d", &arr[i]);
}
while (*p < arr)
{
sum += *p;//每一次元素的相加
*p++;//指向下一个元素
}
//打印sum
printf("sum = %d\n", sum);
return 0;
}
运行结果🖊 请输入十个元素->:10 20 30 40 50 60 70 80 90 100 sum = 550
题目→创建一个函数实现用指针实现两个值的交换,不能创建临时变量来进行交换替换。 本道题目有两点我们需要注意下↓
这里来说下,为什么要用指针变量进行交换。通过指针传递方式,形参为指向实参地址的指针,当对形参的指向操作时,就相当于对实参本身进行的操作。如果你不通过指针的方式进行交换的话,一旦出了函数的形参当中就立马会销毁其中的值,就达不到交换的结果。
其次,不能创建临时变量来进行交换。其实这个很好办用按位异或就可以了,那么我们要知道按位异或是什么才行接下来来介绍下什么是按位异或(^∀^●)ノシ 那么我们要知道按位异或的运算规则才行。0^0=0,0^1=1,1^0=1,1^1=0; 示例代码如下所示↓
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
void swap(int *x, int *y)
{
//按位异或 在这里我们可以使用代入法:假设 是 x = 1,y = 2
*x ^= *y;//x = x^y // 0001 ^ 0010 = 0011 = 3 此时x = 3
*y ^= *x;//y = x^y // 0011 ^ 0010 = 0001 = 1 此时y = 1
*x ^= *y;//x = x^y // 0011 ^ 0001 = 0010 = 2 此时x = 2 结果 x = 2,y = 1
}
int main(void)
{
int a = 0, b = 0;
printf("请输入两个数字:");
scanf("%d %d", &a, &b);
printf("a,b交换前:a = %d,b = %d\n", a, b);
swap(&a, &b);
printf("a,b交换后:a = %d,b = %d\n", a, b);
return 0;
}
运行结果🖊 请输入两个数字:10 20 a,b交换前:a = 10,b = 20 a,b交换后:a = 20,b = 10
题目→创建自定义函数,从而实现strcat()的功能,用指针进行实现。 strcat() 的功能就是连接字符串,假设在arr1当中是"Hello ",而arr2当中是'Cyyds',此时所连接起来的字符就因该是"Hello Cyyds",如果不了解strcat()的函数声明可以去看看,这样对做题的帮助是很大的。 示例代码如下↓
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<assert.h>
char *My_strcat(char *dest, const char *src)
{
assert(dest && src != NULL);//断言
char *ret = dest;
while (*dest != '\0')//'\0'的ASCLL码值就是0
{
dest++;
}
//dest指向的是'\0'
while (*dest++ = *src++)
{
;
}
return ret;
}
int main(void)
{
char arr1[20] = "hello C";
char arr2[20] = "yuyan";
printf("%s\n", My_strcat(arr1, arr2));
return 0;
}
运行结果🖊 hello yuyan
那么接下来我们再来看一组代码,示例代码如下↓
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
void Revise(int *p1)
{
*p1 = 1314;
}
int main(void)
{
int *p1 = NULL;
int p = 520;
p1 = &p;
printf("*p1 = %d\n , p1 = %d\n , &p1 = %d\n , &(*p1) = %d\n , p = %d\n , &p = %d\n", *p1, p1, &p1, &(*p1), p, &p);
Revise(p1);
printf("*p1 = %d\n , p1 = %d\n , &p1 = %d\n , &(*p1) = %d\n , p = %d\n , &p = %d\n", *p1, p1, &p1, &(*p1), p, &p);
return 0;
}
试着把上面的代码给分析出来,其实不难就是这"指针比较臭"而已(ง •_•)ง
这个&p1的地址的本身的地址,而我们取&(*p1)的地址是取解引用p1的值然后再取出&(*p1)的地址。也就相当于是取地址p赋值给指针变量p1。如果你觉得我这句话对你来说不怎么理解的话,那么多看几遍还是可以理解的。
指针可以说在C语言真的算比较难了也是因为指针有些人就被劝退了,但是指针学好了。但是学好C语言指针好处是大大滴多的(这个在前面的初阶指针已经讲的非常清楚了)。刚开始学指针的时候我特别懵,尽管现在我也觉得蛮懵的。但是起码比刚开始好多了不至于连指针数组和数组指针都分不清不会用了(●'◡'●),指针这个东西一定要花时间多看多打代码多调试。 💬如果你是刚学C语言的初学者或者是指针,推荐看看前面博主写的一篇初阶指针的内容🌹 【C语言】万字速通初阶指针“zero → One“ 🔎链接🔎⇥【C语言】万字速通初阶指针 zero → One_謓泽的博客-CSDN博客 相信对你有所帮助,再来看这个可能就会好点了。不过在学指针的时候最好是多打代码,以及做指针的练习,这样对你所理解指针会有更深层的理解,搞不懂的地方其实C语言的调试我觉得如果你会调试的话能帮你解决一半以上的问题,C语言编译器当中的调试工具其实可以说就是你在自己上手"打代码最好的老师了",当然我也就这么说,还要靠自己的理解🥰 🎉如果觉得对你有帮助的话,麻烦点个赞支持一下🎉 📚总而言之就是一句话»C语言指针非常重要!!!坚持下去奥里给🔥🔥🔥
帮助到你的话别忘了支持下博主🌹