02.指针的进阶
1.字符指针
在指针的类型中我们知道有一种指针类型为字符指针 char* ;
一般使用:
int main()
{
char ch = 'w';
char *pc = &ch;
*pc = 'w';
return 0;
}
char * p---const char * p(因为常量字符串不能被修改)
#include<stdio.h>
int main()
{
const char * pstr = "hello bit.";
printf("%s\n",pstr);//不用解引用,因为这是打印字符串,遇到\0就结束。
//而pstr收到的是首地址,遇到\0结束
//如果直接解引用,只能接收到首地址的字符。
printf("%c",*pstr);
return 0;
}
//本质是把字符串 hello bit. 首字符的地址放到了pstr中
const char* p = "abcdef"//首字符的地址传向p
//常量字符串不能被修改
char * p="abc";---改:const char * p="abc"(因为左侧为常量字符不能被修改)
EG1:
#include <stdio.h>
int main()
{
char str1[] = "hello bit.";
char str2[] = "hello bit.";//各自独立占一块空间//当创建数组时
const char* str3 = "hello bit.";
const char* str4 = "hello bit.";//常量字符串无法被改//故共用一份
if (str1 == str2)
printf("str1 and str2 are same\n");
else
printf("str1 and str2 are not same\n");
if (str3 == str4)
printf("str3 and str4 are same\n");
else
printf("str3 and str4 are not same\n");
return 0;
}
//str1这些都是首地址
2.指针数组
指针数组:存放指针的数组
//整型数组 - 存放整型的数组
//字符数组 - 存放字符的数组
//指针数组 - 存放指针(地址)的数组
2.1 字符指针数组(存放字符指针)
const char* arr[4] = { "abcdef","qwer", "hello bit", "hehe" };
//数组连续,内部的字符串不连续,字符串是连续的
int main()
{
//存放字符指针的数组
const char* arr[4] = { "abcdef","qwer", "hello bit", "hehe" };
int i = 0;
for (i = 0; i < 4; i++)
{
printf("%s\n", arr[i]);
}
return 0;
}
2.2 整形指针数组
int main()
{
int arr1[5] = { 1,2,3,4,5 };
int arr2[5] = { 2,3,4,5,6 };
int arr3[5] = { 3,4,5,6,7 };
int arr4[5] = { 0,0,0,0,0 };
//指针数组
int* arr[4] = {arr1, arr2, arr3, arr4};
int i = 0;
for (i = 0; i < 4; i++)
{
int j = 0;
for (j = 0; j < 5; j++)
{
printf("%d ", *(arr[i] + j));//arr[i][j]
}
printf("\n");
}
return 0;
}
//取到arr1,拿到的就是aar1的起始地址
分析:
int* arr1[10]; //整形指针的数组,每个元素是 int *
char *arr2[4]; //一级字符指针的数组,每个元素是char *
char **arr3[5];//二级字符指针的数组 ,每个元素是char **
3.数组指针
字符指针 - 存放字符地址的指针 - 指向字符的指针 char*
整型指针 - 存放整型地址的指针 - 指向整型的指针 int*
浮点型的指针 - 指向浮点型的指针 float* &double*
数组指针-存放数组地址的指针-指向数组的指针(本质是指针)
//数组名为首元素地址
int main()
{
char ch = 'w';
char* pc = &ch;
int num = 10;
int* pi = #
int arr[10];
//pa就是一个数组指针
int (*pa)[10] = &arr;
return 0;
}
/*数组名 - 数组首元素的地址
&数组名 - 是数组的地址
数组首元素的地址和数组的地址从值的角度来看是一样的,但是意义不一样*/
int main()
{
int a = 10;
int *pa = &a;
int arr[10] = {1,2,3,4,5,6,7,8,9,10};
//&arr取出的是数组的地址,只有数组的地址才需要数组指针来接收
//int (*p)[10] = &arr;//pa是一个指针
printf("%p\n", arr);//int*
printf("%p\n", arr + 1);//4
//数组首元素的地址,4
printf("%p\n", &arr[0]);//int*
printf("%p\n", &arr[0]+1);//4
//数组首元素的地址,4
printf("%p\n", &arr);// int(*)[10]
printf("%p\n", &arr+1);//40,10*4
//数组的地址,40
return 0;
}
//数组指针对一维数组不友好
int main()
{
//整个数组
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
int (* p)[10] = &arr;//p为整个数组//p指向整个数组
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%d ", (*p)[i]);//p指向整个数组,要是想指向1,2,3需要加入下标
//arr[i]
}
//首元素地址
int*p = arr;//p首元素的地址
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%d ", *(p + i));
}
return 0;
}
//数组指针一般用于二维数组
✳二维数组的打印方式
1.方法一
void print1(int arr[3][4], int r, int c)
{
int i = 0;
for (i = 0; i < r; i++)
{
int j = 0;
for (j = 0; j < c; j++)
{
printf("%d ", arr[i][j]);
}
printf("\n");
}
}
2.方法二
加一跳过一行
void print2(int(*p)[4], int r, int c)
{
int i = 0;
for (i = 0; i < r; i++)
{
int j = 0;
for (j = 0; j < c; j++)
{
//printf("%d ", (*(p + i))[j]);//p+i找到某一行,然后解引用找到这一行的元素,后找到这一行的某个元素
printf("%d ", p[i][j]);
}
printf("\n");
}
}
int main()
{
int arr[3][4] = { {1,2,3,4}, {2,3,4,5} , {3,4,5,6} };
//print1(arr, 3, 4);
print2(arr, 3, 4);//arr是首行的地址,所以自建的方程可以写为指针形式
//二维数组的首元素是第一行
return 0;
}
//二维数组转向一维数组
//数组指针存一个一维四项数组的地址
//int (* p)[10]=&arr
p=&arr
* p=*&arr=arr
*p[i]=arr[i]
int arr[5];//整型数组,数组是五个元素
int *parr1[10];//指针数组,数组10个元素。每个元素是int *(就是把int * parr1[10]的parr1[10]拿走)
int (*parr2)[10];//parr2是数组指针,该指针指向一个数组,数组是10个元素,每个元素都是int类型的
int (*parr3[10])[5];//数组指针//parr3是数组,数组有10个元素,数组的每个元素的类型是:int (*)[5],int (*)[5]的数组指针类型
4. 数组参数、指针参数
辨析【数组】或者【指针】传给函数,了解如何设计函数
4.1 一维数组传参
数组传参,数组接受
数组传参,指针接受
#include <stdio.h>
void test(int arr[])//ok?yes
{}
void test(int arr[10])//ok?yes//传数组
{}
void test(int* arr)//ok?yes//传指针
{}
void test2(int* arr[20])//ok?yes
{}
void test2(int** arr)//ok?yes//int *的地址//int * arr是不对的
{}
int main()
{
int arr[10] = { 0 };
int* arr2[20] = { 0 };
test(arr);
test2(arr2);
}
4.2 二维数组传参
int main()
{
int arr[3][5] = { 0 };
test(arr);
}
void test(int arr[3][5])//ok?yes
{}
void test(int arr[][])//ok?no//行可以省,列不可以省
{}
void test(int arr[][5])//ok?yes
{}
//总结:二维数组传参,函数形参的设计只能省略第一个[]的数字。
//因为对一个二维数组,可以不知道有多少行,但是必须知道一行多少元素。
//这样才方便运算。
void test(int* arr)//ok?//主函数是三行五裂,数组名是首元素地址,即就是第一行的地址
{}
void test(int* arr[5])//ok?//no
{}
void test(int(*arr)[5])//ok?yes将第一行指针传递
{}
void test(int** arr)//ok?no二级指针接受一级指针的地址变量
{}
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;//1的地址交给p
int sz = sizeof(arr)/sizeof(arr[0]);
//一级指针p,传给函数
print(p, sz);//指针变量传参,指针变量接收
return 0;
}
当函数为一级指针时,函数可以接收的参数:
void test(* p) {} int a=10; int * p=&a; int arr[10]; test(arr)//yes test(p)//yes test(&a)//yes
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;
}
当函数的参数为二级指针的时候,可以接收的参数
void test(char **p) { } int main() { char c = 'b'; char*pc = &c; char**ppc = &pc; char * arr[10]; test(&pc);//yes test(ppc);//yes test(arr);//yes return 0; }
//二维数组和二维指针不可以等同
//* arr==*(arr+0)=arr[0]
//**arr==*(arr[0]+0)==arr[0][0]
5. 函数指针
//数组指针-指向数组的指针
int (*p)[10]=&arr//数组的地址
//函数指针-指向函数的指针
int Add(int x,int y)
{ return x+y;}
int main()
{
//printf("%p\n", &Add);
//int arr[10];
//int (*pa)[10] = &arr;
//int (*pf)(int, int) = &Add;//函数的地址
int (* pf)(int, int) = Add;//函数指的是pf,不是*pf,*只是告诉pf是指针
int ret = (*pf)(2, 3);//解引用,但其实不用解引用
//分析:原来://int ret = Add(2, 3);
//但是由int (* pf)(int, int) = Add;可知,Add=pf
//所以可以直接:int ret = pf(2, 3);
printf("%d\n", ret);
//int (*pf)(int, int) = Add;
//&函数名和函数名都是函数的地址//ADD=&ADD
//pf 是一个存放函数地址的指针变量 - 函数指针
return 0;
}
#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。
EG1:
//代码1:此代码是一次函数调用
(*( void (*)()) 0 )();//解释:
//把0强制转化为函数指针类型,0被当作一个地址
//该代码是一次函数调用
//调用0地址处的一个函数
//首先代码中将0强制类型转换为类型为void (*)()的函数指针
//然后去调用0地址处的函数
//代码2
void (*signal(int , void(*)(int)))(int);//函数的返回类型,去掉signal(int , void(*)(int)
//signal没有和*括在一起,所以它是一个指针
// 该代码是一次函数的声明
// 声明的函数名字叫signal
// signal函数的参数有2个,第一个是int类型,第二个是函数指针类型,该函数指针能够指向的那个函数的参数是int
// 返回类型是void
// signal函数的返回类型是一个函数指针,该函数指针能够指向的那个函数的参数是int,返回类型是void
-----
代码2:简化版本
typedef void(*)(int) pfun_t//意思是你把前面的重命名,这只是分析,真实写法为:
typedef void(*pfun_t)(int);
-----改造
pfun_t signal(int, pfun_t);
总结:
//数组的地址,放在数组指针中
6. 函数指针数组
int my_strlen(const char* str)
{
return 0;
}
int main()
{
//指针数组
char* ch[5];
int arr[10] = {0};
//pa是数组指针
int (*pa)[10] = &arr;
//pf是函数指针
int (*pf)(const char*) = &my_strlen;
//函数指针数组
int (*pfA[5])(const char*) = { &my_strlen};
//[]优先于*,故pfA和[]结合,它为数组,去掉pfA[5]为函数指针
return 0;
}
指针数组
//存放的是字符指针char* arr1[107.
//存放整型指针int* arr2[5].
函数指针数组 - 存放函数指针的数组
数组是一个存放相同类型数据的存储空间,那我们已经学习了指针数组,比如:
int *arr[10];
//数组的每个元素是int *
那要把函数的地址存到一个数组中,那这个数组就叫函数指针数组,那函数指针的数组如何定义呢?
int (*parr1[10])();
int *parr2[10]();
int (*)() parr3[10];
答案是:parr1
parr1 先和 [] 结合,说明 parr1是数组,数组的内容是什么呢?
是 int (*)() 类型的函数指针。
函数指针数组的用途:转移表
EG:(计算器)
#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;
do
{
printf("*************************\n");
printf(" 1:add 2:sub \n");
printf(" 3:mul 4:div \n");
printf("*************************\n");
printf("请选择:");
scanf("%d", &input);
switch (input)
{
case 1:
printf("输入操作数:");
scanf("%d %d", &x, &y);
ret = add(x, y);
printf("ret = %d\n", ret);
break;
case 2:
printf("输入操作数:");
scanf("%d %d", &x, &y);
ret = sub(x, y);
printf("ret = %d\n", ret);
break;
case 3:
printf("输入操作数:");
scanf("%d %d", &x, &y);
ret = mul(x, y);
printf("ret = %d\n", ret);
break;
case 4:
printf("输入操作数:");
scanf("%d %d", &x, &y);
ret = div(x, y);
printf("ret = %d\n", ret);
break;
case 0:
printf("退出程序\n");
break;
default:
printf("选择错误\n");
break;
}
} while (input);
return 0;
}
//扩展,所以法一需要改进
//函数指针数组存放上述函数的地址
//函数指针数组存放上述函数的地址
//转移表
int (*pf[5])(int, int) = { NULL, Add, Sub, Mul, Div };
int main()
{
int input = 0;
int x = 0;
int y = 0;
int ret = 0;
do
{
menu();
printf("请选择:>");
scanf("%d", &input);
if (input == 0)
{
printf("退出计算器\n");
break;
}
else if (input>=1 &&input<=4)
{
printf("请输入两个操作数:>");
scanf("%d %d", &x, &y);
ret = pf[input](x, y);
printf("%d\n", ret);
}
else
{
printf("选择错误\n");
}
} while (input);
return 0;
}
//上述代码,只需要改变函数,和函数指针数组
7. 指向函数指针数组的指针
int main()
{
int arr[10];
int (*pA)[10] = &arr;
//函数指针数组
int (* pf[5])(int, int);
//ppf是指向函数指针数组的指针
int * ppf=&pf
int (*(* ppf)[5])(int, int) = &pf;//希望ppf是指针
//ppf的类型就是去掉ppf
return 0;
}
指向函数指针数组的指针是一个指针
指针指向一个 数组 ,数组的元素都是 函数指针 ;
定义方式:
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. 回调函数
回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个
函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数
的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进
行响应。
改进:法一的计算器代码,存在冗余,代码重复出现四份
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 = 0;
int y = 0;
int ret = 0;
printf("请输入两个操作数:>");
scanf("%d %d", &x, &y);
ret = pf(x, y);
printf("%d\n", ret);
}
int main()
{
int input = 0;
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;
}
【经典】
1.冒泡排序:
void bubble_sort(int arr[], int sz)
{
//趟数
int i = 0;
for (i = 0; i < sz - 1; i++)
{
//一趟冒泡排序的过程
int j = 0;
for (j = 0; j < sz-1-i; j++)
{
if (arr[j] > arr[j + 1])
{
int tmp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = tmp;
}
}
}
}
//只能排序整数
2.qsort函数的使用:
算法:快速排序
void qsort (void* base, size_t num, size_t size, int (*compar)(const void*,const void*));//第一个参数:数组的起始地址
//第二个参数:数组的大小,单位是元素
//元素的大小,一个元素是几个字节
//第四个参数:比较函数,void *指针无具体类型的指针,用具体的可以导致类型不匹配,可认为是通用指针
void *不能直接解引用,也不可以++,但指针类型可以转换
//size_t是标准C库中定义的,它是一个基本的与机器相关的无符号整数的C/C + +类型, 它是sizeof操作符返回的结果类型,该类型的大小可选择。其大小足以保证存储内存中对象的大小(简单理解为 unsigned int就可以了,64位系统中为 long unsigned int)。通常用sizeof(XX)操作,这个操作所得到的结果就是size_t类型。
//冒泡改变,需要改变比较大小的部分:
if (arr[j] > arr[j + 1])
{
int tmp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = tmp;
}//因为比较结构体、字符串不可以直接用大于号小于号1.比较整型的数据
✳<0,e1<e2
✳>0,e1>e2
//实现一个比较整型的函数 int cmp_int(const void* e1, const void* e2) { return *(int*)e1 - *(int*)e2;//e1强制转换为int *后解引用 } //使用qsort对数组进行排序,升序 void test1() { int arr[] = { 9,8,7,6,5,4,3,2,1,0 }; int sz = sizeof(arr) / sizeof(arr[0]); //bubble_sort(arr, sz); //库函数中一个排序函数:qsort //qsort(arr, sz, sizeof(arr[0]), cmp_int); //0 1 2 3 4 5 6 7 8 9 //打印 int i = 0; for (i = 0; i < sz; i++) { printf("%d ", arr[i]); } }
2.使用qsort排序结构体
//使用qsort 排序结构体 struct Stu { char name[20]; int age; }; //按照学生年龄排序 int cmp_stu_by_age(const void* e1, const void* e2) { return ((struct Stu*)e1)->age - ((struct Stu*)e2)->age;//先强制转化为结构体指针,箭头指向年龄 } int cmp_stu_by_name(const void* e1, const void* e2) { return strcmp(((struct Stu*)e1)->name, ((struct Stu*)e2)->name);//字符串的比较用strcmp } void test2() { struct Stu s[3] = { {"zhangsan",20}, {"lisi", 50}, {"wangwu", 33} }; int sz = sizeof(s) / sizeof(s[0]); //qsort(s, sz, sizeof(s[0]), cmp_stu_by_age); qsort(s, sz, sizeof(s[0]), cmp_stu_by_name); }
//qsort可以使用任何类型排序
改造冒泡排序,使得这个函数可以排序任何指定的数组(运用冒泡排序思想)
使用回调函数,模拟实现qsort(采用冒泡的方式)。
注意:这里第一次使用 void* 的指针,讲解 void* 的作用。
//第一个参数是void *,是为了能够接收任意地址
//第二个参数:元素个数,排序需要知道元素个数
//第三个参数:宽度,因为不知道是什么类型的
//第四个参数:不知道什么类型,const void *
void Swap(char* buf1, char*buf2, int width)
{
int i = 0;
for (i = 0; i < width; i++)
{
char tmp = *buf1;
*buf1 = *buf2;
*buf2 = tmp;
buf1++;
buf2++;
}
}
//改造冒泡排序函数,使得这个函数可以排序任意指定的数组
void bubble_sort(void*base, size_t sz, size_t width, int (*cmp)(const void*e1,
const void*e2))
{
//趟数
size_t i = 0;
for (i = 0; i < sz - 1; i++)
{
//一趟冒泡排序的过程
size_t j = 0;
for (j = 0; j < sz - 1 - i; j++)
{
if (cmp((char*)base+j*width, (char*)base+(j+1)* width)>0)//char *指针
{
//交换
Swap((char*)base + j * width, (char*)base + (j + 1) * width, width);
}
}
}
}
void test3()
{
int arr[] = { 9,8,7,6,5,4,3,2,1,0 };
int sz = sizeof(arr) / sizeof(arr[0]);
bubble_sort(arr, sz, sizeof(arr[0]), cmp_int);//自己写的函数,类比qsort
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
}
//使用我们自己写的bubble_sort函数排序结构体数组
void test4()
{
struct Stu s[3] = { {"zhangsan",20}, {"lisi", 50}, {"wangwu", 33} };
int sz = sizeof(s) / sizeof(s[0]);
//bubble_sort(s, sz, sizeof(s[0]), cmp_stu_by_age);
bubble_sort(s, sz, sizeof(s[0]), cmp_stu_by_name);
}
int main()
{
//test1();
//test2();
//test3();
test4();
return 0;
}